Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 23 additions & 76 deletions src/term-commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Silently catching and ignoring errors can make debugging difficult if a tmux command fails unexpectedly. Consider logging the error at a debug level or adding a comment explaining why the error is intentionally ignored here.

    } catch (error) {
      // Consider logging the error here for debugging purposes.
    }

}
}

/**
Expand Down Expand Up @@ -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
// ============================================================================
Expand Down Expand Up @@ -421,12 +366,14 @@ async function startForeground(): Promise<void> {
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();
Comment on lines +369 to 371

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Start agent tmux server with config before on-demand sessions

This change stops initializing tmux -L genie via genieTmuxConf() at serve startup, so a fresh run with no sessions now relies on later on-demand creation. In that flow, the TUI attach path calls ensureAgentSession in src/tui/tmux.ts, which creates the first server with bare tmux -L genie new-session (no -f), so ~/.genie/tmux.conf/the shipped config is never loaded for that server process. The result is inconsistent tmux behavior (status/options/keybindings) until the server is manually killed and restarted with config.

Useful? React with 👍 / 👎.

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();
Expand Down
9 changes: 5 additions & 4 deletions src/tui/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading