COD-105 discover + attach existing remote tmux sessions (detach-not-kill)

Phase 2 of the remote-tmux arc. Discover codeman-* tmux sessions already
running on a remote host (created by the remote's own Codeman or another
instance) and attach to one this Codeman didn't launch, with detach-not-kill
ownership for non-owned sessions.

- remote-hosts.ts: listRemoteCodemanSessions (ssh, VITEST-guarded, never throws)
  + pure parseRemoteSessionList + buildRemoteListSessionsCommand. Parser splits
  on the LITERAL \t the remote tmux emits (next-3.7 does not expand \t) AND a
  real tab. toAttachedSessionRemote builds a non-owned SessionRemote; toSessionRemote
  now marks the COD-104 launch path owned:true.
- tmux-manager.ts: buildRemoteAttachCommand (sibling of buildRemoteLaunchCommand);
  buildRemoteSessionCommand selects attach vs launch by ownership. killSession gains
  a detach-not-kill early return for non-owned remote sessions: tears down only the
  LOCAL pane (kills local ssh -> remote attach detaches), NEVER issues a remote
  kill-session.
- types/session.ts: RemoteSessionInfo; SessionRemote.owned + remoteSessionName.
- schemas.ts: CreateSessionSchema.attachRemoteSession {hostId, remoteSessionName};
  fixed a pre-existing no-useless-escape lint error in the jumpHost regex.
- case-routes.ts: GET /api/remote-hosts/:hostId/sessions (explicit discovery).
- session-routes.ts: attachRemoteSession create path -> non-owned session.
- UI (index.html/session-ui.js/styles.css): explicit "Discover existing sessions"
  button + Attach action (owned:false). No auto-discover.

Verified on aa-desktop: discovered codeman-disco1, attached (attached=1, shared
view), killed local probe pane -> remote SURVIVED_DETACH (attached=0). Tests:
parse/attach-cmd/ownership unit + discovery route, session-routes + case-routes green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 55f5ada9db6d01518a4adf6b752e460b5df39524)
This commit is contained in:
Aamer Akhter
2026-07-17 15:49:24 -04:00
parent 7f24a132d0
commit fb013e9de0
11 changed files with 760 additions and 5 deletions
+37
View File
@@ -96,6 +96,43 @@ export interface SessionRemote extends RemoteSshOptions {
port?: number;
remotePath: string;
commands?: Partial<Record<RemoteCommandMode, string>>;
/**
* COD-105 — whether THIS Codeman created the remote tmux session.
*
* - `true` (default for COD-104 launched sessions): we own the remote session;
* an explicit "kill" may propagate a remote `tmux kill-session`.
* - `false` (discovered + attached an existing remote session another Codeman
* created): closing the local tab must DETACH only — we must NEVER issue a
* remote `kill-session`, or we'd nuke work the remote's own Codeman (or
* another instance) still relies on. See `killSession()` gate.
*
* Absent is treated as owned (legacy/COD-104 sessions persisted before this
* field existed were all launched by us).
*/
owned?: boolean;
/**
* COD-105 — for a NON-owned (discovered + attached) session, the EXISTING
* remote tmux session name to `attach -t` (e.g. `codeman-disco1`). It differs
* from this Codeman's deterministic `codeman-<id>` name because the remote
* session was created elsewhere. Only meaningful when `owned === false`.
*/
remoteSessionName?: string;
}
/**
* COD-105 — a `codeman-*` tmux session discovered on a remote host's
* `tmux -L codeman` socket (may have been created by the remote's own Codeman,
* another instance, or this one). Returned by `listRemoteCodemanSessions`.
*/
export interface RemoteSessionInfo {
/** tmux session name (always starts `codeman-`). */
name: string;
/** Whether a client is currently attached to the remote session. */
attached: boolean;
/** tmux `session_created` epoch seconds. */
created: number;
/** Number of windows in the remote session. */
windows: number;
}
/**