diff --git a/src/term-commands/serve.ts b/src/term-commands/serve.ts index deb25eeb5..dafed1157 100644 --- a/src/term-commands/serve.ts +++ b/src/term-commands/serve.ts @@ -110,39 +110,6 @@ function isTmuxServerRunning(socket: string, conf: string): boolean { } } -/** Start the genie agent tmux server and create sessions for all agents */ -function startAgentTmuxServer(agents: string[]): void { - const conf = genieTmuxConf(); - - // Start server with a bootstrap session (tmux needs at least one) - if (!isTmuxServerRunning(GENIE_SOCKET, conf)) { - execSync(genieTmux('new-session -d -s _bootstrap'), { stdio: 'ignore' }); - } - - // Create a session per agent - for (const agent of agents) { - try { - execSync(genieTmux(`has-session -t '${agent}'`), { stdio: 'ignore' }); - } catch { - // Session doesn't exist — create it - try { - execSync(genieTmux(`new-session -d -s '${agent}'`), { stdio: 'ignore' }); - } catch { - // race or naming conflict - } - } - } - - // Kill the bootstrap session if we have real agent sessions - if (agents.length > 0) { - try { - execSync(genieTmux('kill-session -t _bootstrap'), { stdio: 'ignore' }); - } catch { - // already gone or was the only session - } - } -} - const NAV_WIDTH = 30; const KEY_TABLE = 'genie-tui'; @@ -170,32 +137,22 @@ function applyTuiStyle(): void { /** Set up keybindings in a dedicated key table for TUI */ function setupTuiKeybindings(): void { - try { - execSync( - tuiTmux(`bind-key -T ${KEY_TABLE} Tab select-pane -t ${TUI_SESSION}:0.1 \\; switch-client -T ${KEY_TABLE}`), - { stdio: 'ignore' }, - ); - execSync( - tuiTmux( - `bind-key -T ${KEY_TABLE} C-b if-shell "[ $(tmux -L ${TUI_SOCKET} display-message -p '#\\{pane_width\\}' -t ${TUI_SESSION}:0.0) -gt 5 ]" "resize-pane -t ${TUI_SESSION}:0.0 -x 0" "resize-pane -t ${TUI_SESSION}:0.0 -x ${NAV_WIDTH}" \\; switch-client -T ${KEY_TABLE}`, - ), - { stdio: 'ignore' }, - ); - execSync( - tuiTmux(`bind-key -T ${KEY_TABLE} C-t send-keys -t ${TUI_SESSION}:0.1 C-b c \\; switch-client -T ${KEY_TABLE}`), - { stdio: 'ignore' }, - ); - execSync(tuiTmux(`bind-key -T ${KEY_TABLE} C-q run-shell "tmux -L ${TUI_SOCKET} kill-session -t ${TUI_SESSION}"`), { - stdio: 'ignore', - }); - execSync( - tuiTmux(`bind-key -T ${KEY_TABLE} 'C-\\\\' run-shell "tmux -L ${TUI_SOCKET} kill-session -t ${TUI_SESSION}"`), - { stdio: 'ignore' }, - ); - execSync(tuiTmux(`set-hook -t ${TUI_SESSION} client-session-changed "switch-client -T ${KEY_TABLE}"`), { - stdio: 'ignore', - }); - } catch {} + const bindings = [ + // Tab: toggle focus between left nav (pane 0) and right terminal (pane 1) + `bind-key -T ${KEY_TABLE} Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${TUI_SESSION}:0.1" "select-pane -t ${TUI_SESSION}:0.0" \\; switch-client -T ${KEY_TABLE}`, + // Ctrl+B: toggle sidebar width (collapse/expand) + `bind-key -T ${KEY_TABLE} C-b if-shell "[ $(tmux -L ${TUI_SOCKET} display-message -p '#\\{pane_width\\}' -t ${TUI_SESSION}:0.0) -gt 5 ]" "resize-pane -t ${TUI_SESSION}:0.0 -x 0" "resize-pane -t ${TUI_SESSION}:0.0 -x ${NAV_WIDTH}" \\; switch-client -T ${KEY_TABLE}`, + // Ctrl+Q: detach from TUI (don't kill — serve owns the session) + `bind-key -T ${KEY_TABLE} C-q detach-client`, + `bind-key -T ${KEY_TABLE} 'C-\\\\' detach-client`, + // Activate key table on session attach + `set-hook -t ${TUI_SESSION} client-session-changed "switch-client -T ${KEY_TABLE}"`, + ]; + for (const cmd of bindings) { + try { + execSync(tuiTmux(cmd), { stdio: 'ignore' }); + } catch {} + } } /** @@ -343,18 +300,6 @@ export function ensureTuiSession(workspaceRoot?: string): void { // Workspace agent scanning // ============================================================================ -/** Scan for agents from workspace.json and filesystem */ -function discoverAgents(): string[] { - try { - const { findWorkspace, scanAgents } = require('../lib/workspace.js') as typeof import('../lib/workspace.js'); - const ws = findWorkspace(); - if (!ws) return []; - return scanAgents(ws.root); - } catch { - return []; - } -} - // ============================================================================ // Service management // ============================================================================ @@ -421,12 +366,14 @@ async function startForeground(): Promise { console.error(` pgserve failed: ${msg}`); } - // 2. Start genie agent tmux server + agent sessions - const agents = discoverAgents(); - console.log(` Starting tmux -L ${GENIE_SOCKET} server...`); - startAgentTmuxServer(agents); + // 2. Report agent tmux server state (don't create empty sessions — + // sessions are created on-demand by `genie spawn`). const sessions = listAgentSessions(); - console.log(` Agent sessions: ${sessions.length > 0 ? sessions.join(', ') : '(none)'}`); + if (sessions.length > 0) { + console.log(` Agent server (-L ${GENIE_SOCKET}): ${sessions.length} sessions`); + } else { + console.log(` Agent server (-L ${GENIE_SOCKET}): no sessions yet (created on first spawn)`); + } // 2b. Sync agent directory + start watcher handles.agentWatcher = await startAgentSync(); diff --git a/src/tui/components/Nav.tsx b/src/tui/components/Nav.tsx index 6b0d50b79..d04099be7 100644 --- a/src/tui/components/Nav.tsx +++ b/src/tui/components/Nav.tsx @@ -162,10 +162,11 @@ export function Nav({ onTmuxSessionSelect, workspaceRoot, initialAgent }: NavPro const node = flatNodes[selectedIndex]?.node; if (!node) return; - // Stopped agent: spawn it and attach to the session - if (node.type === 'agent' && node.wsAgentState === 'stopped') { - spawnAgent(node.label); - // Attach immediately — ensureAgentSession in tmux.ts creates the session if needed + // Agent node: spawn if not running, then attach + if (node.type === 'agent') { + if (node.wsAgentState !== 'running') { + spawnAgent(node.label); + } const target = getSessionTarget(node); if (target) onTmuxSessionSelect(target.sessionName, target.windowIndex); return;