mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
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:
@@ -9,7 +9,7 @@ import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import type { ApiResponse, CaseInfo } from '../../types.js';
|
||||
import type { ApiResponse, CaseInfo, RemoteSessionInfo } from '../../types.js';
|
||||
import { ApiErrorCode, createErrorResponse, getErrorMessage } from '../../types.js';
|
||||
import {
|
||||
CreateCaseSchema,
|
||||
@@ -26,6 +26,7 @@ import type { EventPort, ConfigPort } from '../ports/index.js';
|
||||
import { dataPath, getDataDir } from '../../config/instance.js';
|
||||
import {
|
||||
checkRemoteTmuxAvailable,
|
||||
listRemoteCodemanSessions,
|
||||
readRemoteCases,
|
||||
readRemoteHosts,
|
||||
remoteDisplayPath,
|
||||
@@ -167,6 +168,23 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config
|
||||
|
||||
app.get('/api/remote-hosts', async () => readRemoteHosts(CODEMAN_CONFIG_DIR));
|
||||
|
||||
// COD-105 — discover `codeman-*` tmux sessions already running on a remote
|
||||
// host (created by the remote's own Codeman, another instance, or this one)
|
||||
// so the operator can attach to one this Codeman didn't launch. Explicit
|
||||
// trigger only (Decision A): the frontend calls this on a "Discover" click,
|
||||
// never automatically on host select. listRemoteCodemanSessions never throws
|
||||
// (returns [] on unreachable/no-tmux/no-sessions) and is ssh-guarded under test.
|
||||
app.get(
|
||||
'/api/remote-hosts/:hostId/sessions',
|
||||
async (req): Promise<ApiResponse<{ sessions: RemoteSessionInfo[] }>> => {
|
||||
const { hostId } = req.params as { hostId: string };
|
||||
const host = (await readRemoteHosts(CODEMAN_CONFIG_DIR)).find((item) => item.id === hostId);
|
||||
if (!host) return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Remote host not found');
|
||||
const sessions = await listRemoteCodemanSessions(host);
|
||||
return { success: true, data: { sessions } };
|
||||
}
|
||||
);
|
||||
|
||||
app.post('/api/remote-hosts', async (req): Promise<ApiResponse<{ host: unknown }>> => {
|
||||
const host = parseBody(RemoteHostSchema, req.body);
|
||||
const hosts = await readRemoteHosts(CODEMAN_CONFIG_DIR);
|
||||
|
||||
Reference in New Issue
Block a user