Commit Graph

21 Commits

Author SHA1 Message Date
Casey beb212dcc5 desktop: fix two managed-SSH-spawn bugs that break every fresh remote backend
1. Quoting: the spawn payload wrapped expandRemotePath() output -- already
   a shell-quoted fragment like "$HOME"'/...' -- in shq() again, so the
   reservation/lock/owner_file variables hold the quote characters
   literally and every mkdir "$reservation" fails forever (~5 min per
   attempt spinning in the reservation loop while holding the box-global
   update mutex; queued spawns starve behind it). The same double quoting
   sits in the stale-reaper identity guards, making every reap REFUSE.
   The lockfile-reuse path masks the bug for existing backends, so it
   only bites on fresh spawns.
2. Bashism: lockfile publication used ${var//__PID__/$child} -- bash-only
   substitution in a payload run under plain sh (dash on Ubuntu), which
   aborts the script AFTER the serve was spawned. The client then saw an
   unknown failure, ran its error cleanup (deleting the token file), and
   the just-booted serve died on the missing token -- orphaning one serve
   per attempt. Replaced with a POSIX sed substitution.

Adds two regression tests: payload variables must keep $HOME expandable
(no re-quoting), and the pid substitution must be POSIX sh. Both fail
against the previous code; all 89 remote-lifecycle tests pass with the
fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 02:48:21 -07:00
hermes-seaeye[bot] 79b8703d00 fmt(js): npm run fix on merge (#95969)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-27 01:31:15 +00:00
Andrex Ibiza, MBA 6170fff19c feat(desktop): managed SSH remote update engine — extracted from #93042
The Desktop can drive a full update of a REMOTE SSH instance: claim the
connection (ManagedConnectionUpdateGate pauses dials/mutations while the
update owns it), terminate the owned backend with an identity re-proof
at the signal boundary (terminateOwnedDashboardForUpdate — argv+creation
time re-read in the same remote shell that signals), run the updater
under an update-in-progress marker/mutex on the remote install root,
bootstrap the new backend, and fence its publication so a rollback
cannot leak a half-published serve (fenceManagedSshBootstrapPublication
+ waitForManagedSshBootstrapFence barrier).

Extraction notes (campaign #91277 Phase 4; rollout/canary engine stays
behind per sequencing):
- managed-ssh-update.ts + test: clean cherry-pick (new files).
- windows-remote-lifecycle.ts + test: applied as-is (zero main drift).
- remote-lifecycle.ts: PR hunks stitched AROUND current main's #95532
  skew guards and #91668 SIGKILL-escalating cleanupStale, which are
  PRESERVED verbatim — the PR's python identity-re-proof termination is
  wired for the managed-update path only; connect's stale replacement
  keeps main's proven kill. One PR test assertion re-pinned accordingly.
- One PR test fixed: floating coordinator.start() promise whose
  rejection IS the contract under test now has an explicit handler
  (vitest flagged it as an unhandled rejection).

tsc clean; 131/131 across the three touched electron suites. main.ts
wiring (IPC + deps bag) follows as a separate commit.
2026-08-26 17:42:44 -07:00
hermes-seaeye[bot] 77001a6be7 fmt(js): npm run fix on merge (#95924)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-26 23:21:02 +00:00
Hermes Agent 3ee0c62224 fix(desktop): SSH orphan reaper fails CLOSED on remote lockfile schema/ownership skew
A backend.lock.json that exists but doesn't match what this build writes
(unknown/future schemaVersion, truncated JSON, missing or foreign
ownershipId, malformed shape) was previously indistinguishable from 'no
lockfile': connect() would spawn a fresh backend on top of it and
overwrite the record, and cleanup paths could drop foreign state —
disarming the #78872 ownership guard exactly when another (e.g. forked)
desktop build shares the remote. readLockfile now returns a skew
sentinel for existing-but-foreign lockfiles; connect() refuses with a
'remote-lockfile-skew' error and a skew warning instead of
reaping/overwriting, disconnect() and cleanupStale() skip entirely.

Refs #95532
2026-08-26 15:51:54 -07:00
Teknium 697087f2eb fix(desktop): SIGKILL-escalate the owned SSH backend when it survives the graceful quit wait (#91668 remainder)
The #95085 quit teardown kills the owned serve --isolated before the
SSH tunnel closes, but a backend mid-turn (in-flight LLM call, live MCP
children) can ride out SIGTERM past cleanupStale's 5s graceful wait.
The old code then gave up (threw, kept the lockfile) and before-quit's
6s race closed SSH anyway — reparenting the still-running serve to
pid 1: the reported leak, now specific to quit-during-active-turn.
Escalate to kill -9 with a confirmed-exit wait; only an unkillable pid
(D-state, permissions) still throws and preserves the lock record so
the next connect's reap pass retries.
2026-08-26 08:40:07 -07:00
toprakeker 8624c1e8f7 fix(desktop): reject SSH backends with replaced runtimes 2026-08-26 06:24:30 -07:00
686f6c61 0c69cac48a fix(desktop): terminate owned SSH serve backends on quit
teardownSshConnection closed the tunnel and SSH transport but never
killed the detached serve --isolated process. Spawn uses setsid/nohup,
so the backend reparents to pid 1, keeps state.db open, and accumulates
across Cmd+Q. Reuse cleanupStale via disconnect while SSH can still
exec, sequence remote kill before close, and seal the bootstrap
coordinator so reconnect during a prevented first quit cannot respawn.
The quit race is 6s to cover cleanupStale's 5s wait-for-exit loop.
2026-08-26 00:40:35 -07:00
Brooklyn Nicholson f6e63237d4 refactor(desktop): extract IPC clusters from electron/main.ts
52 handlers move into five registrars — git, pet overlay, hud, fs and
terminal. Each takes injected deps (window handles, binary resolvers,
path hardening) following the existing electron/ module pattern rather
than closing over main.ts locals, and terminal-ipc returns its dispose
helpers so SSH teardown and app shutdown keep working.
2026-08-19 10:50:52 -05:00
hermes-seaeye[bot] d07be6e165 fmt(js): npm run fix on merge (#89403)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-18 19:28:51 +00:00
citizendev9c d758fdbce5 fix(desktop): make Bot Mode switches hydrate canonical chats 2026-08-18 12:22:44 -07:00
Teknium 62f376279b feat(desktop): cross-connection Bot Mode — Create-on picker + multi-machine group chats
Builds on the salvaged #88598 multi-source roster work:

- New Agent "Create on" picker (multi-connection registries only): the
  profiles.create/configure/describe/mcp.catalog calls route to the picked
  connection's backend via host.requestProfile route descriptors — the
  window's active gateway never switches. Remote-target drafts discard via
  the remote CLI; appearance/title write into the remote profile's ui_meta
  and asset store; the taken-name check is scoped to the target machine's
  roster; the live SkillsView Capabilities tab (active-gateway-bound) falls
  back to the staged checklists for remote targets.

- Group chats can seat bots from other registered connections: member turns
  (session.create/resume, prompt.submit, reply polling) route to each
  member's own source through the new requestForBot helper. Remote member
  descriptors persist on the room record (bot-meta is active-gateway-scoped
  by design); watermarks/sessions key by source-qualified member keys so
  same-named agents on two machines never share state; @name-device handles
  resolve in room mentions; room lines and turn prompts badge cross-machine
  speakers with their device.

- pidIsOurDashboard: a dead remote PID (ps exits non-zero) now reads as
  FOREIGN instead of throwing "Could not verify SSH backend process
  ownership" — the misleading error from #88625's secondary report.

Tests: cross-connection-bots.test.mjs (route descriptors, requestForBot
routing, member keys, disambiguated mentions, device badges, source
contracts); plugin suite 180/180; electron vitest 220/220.
2026-08-17 14:32:52 -07:00
Al Zilla 4068162118 fix(desktop): keep SSH Bot Mode agents visible across local clicks
Clicking the local agent left connectionId null, so the roster treated
the registry primary (often an SSH box) as active and dropped its
profiles while inventing a "This device" shadow of default.

Inventory undialed SSH sources with a cached ls of ~/.hermes/profiles
instead of requiring the window to switch onto that machine. Hostile
HERMES_HOME values are rejected before the listing command runs.
2026-08-17 14:32:52 -07:00
a-espinoza 91de3beb72 fix(desktop-ssh): raise remote backend file limit 2026-08-10 18:04:59 -07:00
hermes-seaeye[bot] e34c4c009a fmt(js): npm run fix on merge (#82771)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-09 22:43:38 +00:00
webtecnica 8b752603c4 fix(desktop-ssh): stop resolving exec-wrappers to python in locateHermes (#74411)
Problem 1: resolveLauncher() read bash 'exec <python> <script>' wrappers
and returned ONLY the python interpreter path, discarding the script.
This made probeHermesVersion() run '<python> --version', which always
printed 'Python x.y.z' instead of the Hermes version. And
remoteSupportsSshOwnership() ran '<python> serve --help' which failed
entirely because no 'serve' module exists in the python stdlib.

Problem 2: When the user set remoteHermesPath (an explicit override),
resolveLauncher() resolved it to the python interpreter, replacing the
user's specified path. The override was effectively ignored for version
checking and capability probing.

Fix: resolveLauncher now returns the candidate path directly. The hermes
binary or wrapper script is already executable and handles argument
forwarding (e.g. 'exec <python> <script> "$@"') correctly on its own.
No additional remote SSH round-trip or python script needed.
2026-08-09 15:35:25 -07:00
Cad from Arca d6be88fbc8 fix(desktop): map SSH profiles to remote profiles 2026-08-01 14:30:16 -07:00
yoniebans faa3bce6dc style(desktop): satisfy merged eslint/prettier config
The SSH modules predate the stricter lint config that landed on main (curly, no-empty, perfectionist sorting, prettier). Mechanical lint:fix + fmt pass, empty catch blocks filled with the codebase's void-0 convention, and inline no-control-regex disables on the three deliberate control-char patterns (same pattern as lib/ansi.ts).
2026-07-20 23:01:49 +02:00
yoniebans 45acb62c6d feat(desktop): route Desktop SSH to the Windows lifecycle
Detect the remote platform on connect and Test SSH: uname first (Linux/Darwin
keep the POSIX lifecycle), else an encoded-PowerShell probe that routes a
Windows host through connectWindowsRemote. The Windows lifecycle mirrors the
POSIX one — probe, one-shot token upload, spawn, readiness scrape, tunnel,
reuse-by-exact-ownership, and teardown — over a PowerShell helper dialect.

Preserve a backend on indeterminate process state (never destroy on unknowns);
a thrown terminate aborts before remove-lock so a live backend is never
orphaned. Interactive terminals on a Windows backend open PowerShell in the
requested cwd instead of a POSIX login shell.
2026-07-20 17:19:22 +02:00
yoniebans 9e13cd125b fix(desktop): discard dead SSH ownership records
Skip argv ownership verification after the remote PID is already proven dead, then remove only the validated lock/log metadata and continue with a fresh spawn.
2026-07-16 15:18:09 +02:00
yoniebans a340d0adc5 feat(desktop): add isolated SSH transport primitives
Add OpenSSH config discovery, scoped ControlMaster/no-mux transport, durable installation ownership, serialized bootstrap coordination, and transactional remote backend lifecycle with focused Vitest coverage.
2026-07-16 14:43:14 +02:00