diff --git a/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/proposal.md b/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/proposal.md new file mode 100644 index 0000000..4f2d162 --- /dev/null +++ b/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/proposal.md @@ -0,0 +1,16 @@ +## Why + +Multi-agent `gx agents start` already creates isolated `agent/*` lanes, but the recent launcher panel still points operators back through cockpit/tmux for terminal panes. The requested operator flow needs the safety model to stay unchanged while opening the created lanes in a Kitty window by default. + +## What Changes + +- Add a Kitty-backed terminal launcher for multi-agent starts. +- Add `--terminal kitty|none` with `GUARDEX_AGENT_TERMINAL` defaulting to `kitty`. +- Keep branch/worktree creation, lock claiming, and PR finish flow unchanged. +- Update launcher panel terminal copy to Kitty-first language. + +## Impact + +- Multi-agent starts can open one Kitty session after all lanes are created. +- Missing Kitty reports a recovery command and session file path instead of failing lane creation. +- `--terminal none` keeps the old no-terminal behavior. diff --git a/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/specs/agents-multi-launcher/spec.md b/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/specs/agents-multi-launcher/spec.md new file mode 100644 index 0000000..74284a3 --- /dev/null +++ b/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/specs/agents-multi-launcher/spec.md @@ -0,0 +1,24 @@ +## ADDED Requirements + +### Requirement: Kitty external terminal launcher + +`gx agents start` SHALL use `kitty` as the default external terminal launcher for multi-agent starts while preserving the existing branch, worktree, lock, and PR-only finish safety model. + +#### Scenario: Multi-agent start launches Kitty after lanes exist + +- **WHEN** an operator starts more than one agent lane with `gx agents start "fix auth tests" --panel --codex-accounts 3 --base main` +- **THEN** Guardex SHALL create each `agent/*` lane before terminal launch +- **AND** SHALL write a Kitty session file containing each lane worktree and launch command +- **AND** SHALL launch one Kitty window from that session file. + +#### Scenario: Terminal launch disabled + +- **WHEN** an operator passes `--terminal none` +- **THEN** Guardex SHALL create the requested lanes +- **AND** SHALL skip external terminal launch. + +#### Scenario: Kitty unavailable + +- **WHEN** Kitty is not available on PATH +- **THEN** Guardex SHALL keep created lanes and session metadata intact +- **AND** SHALL print the Kitty session file path and recovery command. diff --git a/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/tasks.md b/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/tasks.md new file mode 100644 index 0000000..cd50eac --- /dev/null +++ b/openspec/changes/agent-codex-kitty-default-agent-terminal-2026-04-30-13-14/tasks.md @@ -0,0 +1,34 @@ +## 1. Spec + +- [x] Record Kitty-first multi-agent terminal launcher scope. + +## 2. Tests + +- [x] Cover default `kitty` parsing. +- [x] Cover `--terminal none` skipping terminal launch. +- [x] Cover missing Kitty recovery output. +- [x] Cover launcher panel copy update. + +## 3. Implementation + +- [x] Add `src/agents/terminal.js`. +- [x] Wire `gx agents start` multi-lane success into Kitty launch after lane creation. +- [x] Add parser support for `--terminal`. +- [x] Update panel text from tmux-focused wording to Kitty-first wording. + +## 4. Verification + +- [x] Run focused Node tests for parser, launcher, panel. +- [x] Run `openspec validate --specs`. + +Evidence: + +- `node --test test/cli-args-dispatch.test.js test/agents-start.test.js test/agents-selection-panel.test.js test/agents-start-dry-run.test.js` -> 30 pass. +- `openspec validate agent-codex-kitty-default-agent-terminal-2026-04-30-13-14 --strict` -> valid. +- `openspec validate --specs` -> no spec items found. +- `npm test` -> 423 pass, 9 fail, 1 skip; failures were pre-existing-looking baseline mismatches outside this touched scope (`test/agents-launch.test.js`, `test/agents-lifecycle.test.js`, `test/agents-sessions.test.js`, `test/cockpit-command.test.js`). + +## 5. Cleanup + +- [x] Commit changes. +- [ ] Finish via PR, wait for merge, cleanup, and record `MERGED` evidence. diff --git a/src/agents/selection-panel.js b/src/agents/selection-panel.js index 248a0d5..583bea0 100644 --- a/src/agents/selection-panel.js +++ b/src/agents/selection-panel.js @@ -11,9 +11,9 @@ const DEFAULT_PANEL_HEIGHT = 30; const SIDEBAR_WIDTH = 36; const PANEL_ACTIONS = [ ['n', 'New agent', 'create an agent pane in this repo'], - ['t', 'Terminal', 'open a shell pane from gx cockpit'], + ['t', 'Terminal', 'open Kitty agent terminal'], ['p', 'Project', 'create pane in another project'], - ['Alt+Shift+M', 'Pane menu', 'act on the focused tmux pane'], + ['Alt+Shift+M', 'Pane menu', 'act on the selected pane'], ['j/k', 'Jump', 'move between panes in the list'], ['m', 'Menu', 'open pane context actions'], ['x', 'Close', 'close selected pane'], @@ -27,10 +27,10 @@ const PANEL_ACTIONS = [ const PANEL_SHORTCUT_MESSAGES = { '?': 'Shortcut map is shown on the right.', - t: 'Terminal panes are managed in gx cockpit; open cockpit, then press t.', + t: 'Kitty agent terminals open after multi-agent launch; pass --terminal none to skip.', p: 'Project panes are managed in gx cockpit; open cockpit, then press p.', m: 'Pane menu is available in gx cockpit with m or Alt+Shift+M.', - 'alt-shift-m': 'Pane menu is available in gx cockpit for the focused tmux pane.', + 'alt-shift-m': 'Pane menu is available in gx cockpit for the selected pane.', x: 'Close is available from gx cockpit pane menu.', b: 'Child worktrees are available from gx cockpit pane menu.', f: 'File browser is available from gx cockpit pane menu.', @@ -422,7 +422,7 @@ function renderSidebarRows(options, selections, definitions, width, height) { '─'.repeat(width), ' [l]ogs [p]rojects [s]ettings', ' Press [?] for keyboard shortcuts', - ' Tip: live panes: gx cockpit', + ' Tip: multi-agent terminals: Kitty', '', ); diff --git a/src/agents/start.js b/src/agents/start.js index 0fb4290..c5c4e99 100644 --- a/src/agents/start.js +++ b/src/agents/start.js @@ -21,6 +21,7 @@ const { listAgentSessions, updateAgentSession, } = require('./sessions'); +const { launchAgentTerminal } = require('./terminal'); function sanitizeSlug(value, fallback = 'task') { const slug = String(value || '') @@ -473,7 +474,7 @@ function startSingleAgentLane(repoRoot, options, deps = {}) { const session = writeAgentSession(repoRoot, options, metadata, 'active'); stdout = appendSessionId(stdout, session); if (options.claims.length === 0) { - return { status: 0, stdout, stderr }; + return { status: 0, stdout, stderr, session }; } if (!metadata.branch || !metadata.worktreePath) { @@ -492,7 +493,7 @@ function startSingleAgentLane(repoRoot, options, deps = {}) { stdout += String(claimResult.stdout || ''); stderr += String(claimResult.stderr || ''); if (!isSpawnFailure(claimResult) && claimResult.status === 0) { - return { status: 0, stdout, stderr }; + return { status: 0, stdout, stderr, session }; } if (isSpawnFailure(claimResult)) { @@ -520,6 +521,7 @@ function startAgentLane(repoRoot, options, deps = {}) { selections: normalizeAgentSelections(options), }); let stderr = ''; + const sessions = []; for (const launchOption of launchOptions) { const result = startSingleAgentLane(repoRoot, launchOption, deps); @@ -532,12 +534,24 @@ function startAgentLane(repoRoot, options, deps = {}) { stderr, }; } + if (result.session) { + sessions.push(result.session); + } } + const terminalResult = launchAgentTerminal(repoRoot, sessions, { + terminal: options.terminal, + runner: deps.terminalRunner, + kittyBin: deps.kittyBin, + }); + stdout += String(terminalResult.stdout || ''); + stderr += String(terminalResult.stderr || ''); + return { status: 0, stdout, stderr, + terminal: terminalResult, }; } diff --git a/src/agents/terminal.js b/src/agents/terminal.js new file mode 100644 index 0000000..0d9aa8d --- /dev/null +++ b/src/agents/terminal.js @@ -0,0 +1,140 @@ +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); + +const { TOOL_NAME } = require('../context'); +const { run } = require('../core/runtime'); +const { shellQuote } = require('./launch'); + +const DEFAULT_AGENT_TERMINAL = 'kitty'; +const SUPPORTED_AGENT_TERMINALS = new Set(['kitty', 'none']); + +function normalizeAgentTerminal(value) { + const terminal = String(value || DEFAULT_AGENT_TERMINAL).trim().toLowerCase(); + return terminal || DEFAULT_AGENT_TERMINAL; +} + +function sanitizeFileSegment(value) { + return String(value || 'agents') + .replace(/[^a-zA-Z0-9._-]+/g, '__') + .replace(/^_+|_+$/g, '') + .slice(0, 120) || 'agents'; +} + +function terminalSessionDir(repoRoot) { + return path.join(repoRoot, '.guardex', 'agents', 'terminals'); +} + +function terminalSessionFilePath(repoRoot, sessions, terminal = DEFAULT_AGENT_TERMINAL) { + const firstSession = sessions[0] || {}; + const sessionId = sanitizeFileSegment(firstSession.id || firstSession.branch || 'agents'); + return path.join(terminalSessionDir(repoRoot), `${sessionId}-${sessions.length}.${terminal}-session`); +} + +function sessionTitle(session, index) { + const branch = String(session.branch || session.id || `agent-${index + 1}`); + const leaf = branch.split('/').filter(Boolean).pop() || branch; + return `${index + 1}: ${session.agent || 'agent'} ${leaf}`; +} + +function buildKittySession(sessions) { + const lines = ['# Generated by gx agents start.']; + sessions.forEach((session, index) => { + const title = sessionTitle(session, index); + lines.push( + '', + `new_tab ${shellQuote(title)}`, + `cd ${shellQuote(session.worktreePath)}`, + `launch --title ${shellQuote(title)} sh -lc ${shellQuote(session.launchCommand)}`, + ); + }); + return `${lines.join('\n')}\n`; +} + +function writeKittySessionFile(repoRoot, sessions) { + const filePath = terminalSessionFilePath(repoRoot, sessions, 'kitty'); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, buildKittySession(sessions), { encoding: 'utf8', mode: 0o600 }); + return filePath; +} + +function recoveryLines(sessionFilePath, reason) { + const detail = reason ? `: ${reason}` : '.'; + return [ + `[${TOOL_NAME}] Kitty terminal not launched${detail}`, + `[${TOOL_NAME}] Kitty session file: ${sessionFilePath}`, + `[${TOOL_NAME}] Recovery: kitty --detach --session ${shellQuote(sessionFilePath)}`, + `[${TOOL_NAME}] Agent lanes are intact; run the recovery command when Kitty is available.`, + '', + ].join('\n'); +} + +function resultReason(result, fallback) { + if (result?.error?.message) return result.error.message; + if (typeof result?.status === 'number') return `${fallback} exited ${result.status}`; + return fallback; +} + +function launchAgentTerminal(repoRoot, sessions, options = {}) { + const terminal = normalizeAgentTerminal(options.terminal); + if (terminal === 'none' || !Array.isArray(sessions) || sessions.length === 0) { + return { status: 'skipped', stdout: '', stderr: '', sessionFilePath: '' }; + } + if (!SUPPORTED_AGENT_TERMINALS.has(terminal)) { + return { + status: 'unsupported', + stdout: '', + stderr: `[${TOOL_NAME}] Unsupported agent terminal '${terminal}'. Supported terminals: kitty, none.\n`, + sessionFilePath: '', + }; + } + + const sessionFilePath = writeKittySessionFile(repoRoot, sessions); + const runner = options.runner || run; + if (typeof runner !== 'function') { + return { + status: 'missing', + stdout: '', + stderr: recoveryLines(sessionFilePath, 'terminal runner unavailable'), + sessionFilePath, + }; + } + const kittyBin = options.kittyBin || process.env.GUARDEX_KITTY_BIN || 'kitty'; + const probe = runner(kittyBin, ['--version'], { cwd: repoRoot, stdio: 'pipe' }); + if (probe?.error || probe?.status !== 0) { + return { + status: 'missing', + stdout: '', + stderr: recoveryLines(sessionFilePath, resultReason(probe, `${kittyBin} --version`)), + sessionFilePath, + }; + } + + const launch = runner(kittyBin, ['--detach', '--session', sessionFilePath], { cwd: repoRoot, stdio: 'ignore' }); + if (launch?.error || launch?.status !== 0) { + return { + status: 'failed', + stdout: '', + stderr: recoveryLines(sessionFilePath, resultReason(launch, `${kittyBin} --detach`)), + sessionFilePath, + }; + } + + return { + status: 'launched', + stdout: `[${TOOL_NAME}] Kitty agent terminal: ${sessionFilePath}\n`, + stderr: '', + sessionFilePath, + }; +} + +module.exports = { + DEFAULT_AGENT_TERMINAL, + buildKittySession, + launchAgentTerminal, + normalizeAgentTerminal, + recoveryLines, + terminalSessionFilePath, + writeKittySessionFile, +}; diff --git a/src/cli/args.js b/src/cli/args.js index d639c23..07ce734 100644 --- a/src/cli/args.js +++ b/src/cli/args.js @@ -275,6 +275,7 @@ function parseAgentsArgs(rawArgs) { count: 1, agentSelectionSpecs: [], panel: false, + terminal: process.env.GUARDEX_AGENT_TERMINAL || 'kitty', dryRun: false, reviewIntervalSeconds: 30, cleanupIntervalSeconds: 60, @@ -287,6 +288,7 @@ function parseAgentsArgs(rawArgs) { finishArgs: [], metadata: {}, }; + let terminalProvided = false; for (let index = 0; index < rest.length; index += 1) { const arg = rest[index]; @@ -451,6 +453,16 @@ function parseAgentsArgs(rawArgs) { options.panel = true; continue; } + if (arg === '--terminal') { + const next = rest[index + 1]; + if (!next || next.startsWith('-')) { + throw new Error('--terminal requires kitty or none'); + } + options.terminal = next; + terminalProvided = true; + index += 1; + continue; + } if (arg === '--base') { const next = rest[index + 1]; if (!next || next.startsWith('-')) { @@ -501,10 +513,10 @@ function parseAgentsArgs(rawArgs) { throw new Error('--pid is only supported with `gx agents stop`'); } if ( - (options.task || options.agent || options.base || options.claims.length > 0 || Object.keys(options.metadata).length > 0) && + (options.task || options.agent || options.base || options.claims.length > 0 || Object.keys(options.metadata).length > 0 || terminalProvided) && options.subcommand !== 'start' ) { - throw new Error('--task, --agent, --agents, --count, --base, --claim, --meta, and --panel are only supported with `gx agents start`'); + throw new Error('--task, --agent, --agents, --count, --base, --claim, --meta, --terminal, and --panel are only supported with `gx agents start`'); } if ( (options.agentSelectionSpecs.length > 0 || options.count !== 1 || options.panel) && diff --git a/test/agents-selection-panel.test.js b/test/agents-selection-panel.test.js index a49a9c1..7889c72 100644 --- a/test/agents-selection-panel.test.js +++ b/test/agents-selection-panel.test.js @@ -49,6 +49,7 @@ test('renderAgentSelectionPanel shows a dmux-style GitGuardex shell', () => { assert.match(output, /gitguardex/); assert.match(output, /\[n\] launch/); assert.match(output, /\[t\] terminal/); + assert.match(output, /multi-agent terminals: Kitty/); assert.match(output, /Alt\+Shift\+M/); assert.match(output, /Files/); assert.match(output, /Selected: 3\/10/); @@ -124,10 +125,10 @@ test('interactive panel keys move focus, toggle agents, and adjust codex account assert.equal(countForAgent(selectionsFromPanelState(state), 'codex'), 2); const terminalHelp = applyAgentSelectionKey(state, 't'); assert.equal(terminalHelp.action, 'render'); - assert.match(terminalHelp.state.message, /Terminal panes are managed in gx cockpit/); + assert.match(terminalHelp.state.message, /Kitty agent terminals open after multi-agent launch/); const paneMenuHelp = applyAgentSelectionKey(state, '\u001bM'); assert.equal(paneMenuHelp.action, 'render'); - assert.match(paneMenuHelp.state.message, /Pane menu is available in gx cockpit/); + assert.match(paneMenuHelp.state.message, /selected pane/); assert.equal(applyAgentSelectionKey(state, 'n').action, 'launch'); assert.equal(applyAgentSelectionKey(state, '\r').action, 'launch'); assert.equal(applyAgentSelectionKey(state, '\u001b').action, 'cancel'); diff --git a/test/agents-start.test.js b/test/agents-start.test.js index 4b563e3..8e5eb87 100644 --- a/test/agents-start.test.js +++ b/test/agents-start.test.js @@ -1,6 +1,9 @@ const test = require('node:test'); const assert = require('node:assert/strict'); const Module = require('node:module'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); function loadStartWithMocks({ runPackageAsset, @@ -12,10 +15,12 @@ function loadStartWithMocks({ const startPath = require.resolve('../src/agents/start'); const runtimePath = require.resolve('../src/core/runtime'); const sessionsPath = require.resolve('../src/agents/sessions'); + const terminalPath = require.resolve('../src/agents/terminal'); const gitPath = require.resolve('../src/git'); const originalLoad = Module._load; delete require.cache[startPath]; + delete require.cache[terminalPath]; Module._load = function mockLoad(request, parent, isMain) { const resolved = Module._resolveFilename(request, parent, isMain); if (resolved === runtimePath) { @@ -35,6 +40,7 @@ function loadStartWithMocks({ } finally { Module._load = originalLoad; delete require.cache[startPath]; + delete require.cache[terminalPath]; } } @@ -226,9 +232,11 @@ test('agents start output includes canonical session id', () => { test('agents start launches repeated codex accounts with unique branch tasks', () => { const runCalls = []; const created = []; + const terminalCalls = []; + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-agents-start-')); const branches = [ - ['agent/codex/fix-auth-codex-01', '/repo/.omx/agent-worktrees/repo__codex__fix-auth-codex-01'], - ['agent/codex/fix-auth-codex-02', '/repo/.omx/agent-worktrees/repo__codex__fix-auth-codex-02'], + ['agent/codex/fix-auth-codex-01', path.join(repoRoot, '.omx/agent-worktrees/repo__codex__fix-auth-codex-01')], + ['agent/codex/fix-auth-codex-02', path.join(repoRoot, '.omx/agent-worktrees/repo__codex__fix-auth-codex-02')], ]; const start = loadStartWithMocks({ runPackageAsset(assetKey, args, options) { @@ -246,23 +254,120 @@ test('agents start launches repeated codex accounts with unique branch tasks', ( currentBranchName: () => 'main', }); - const result = start.startAgentLane('/repo', { + const result = start.startAgentLane(repoRoot, { task: 'fix auth', agent: 'codex', count: 2, base: 'main', claims: [], + }, { + terminalRunner(cmd, args, options) { + terminalCalls.push({ cmd, args, options }); + return { status: 0, stdout: args[0] === '--version' ? 'kitty 0.36\n' : '', stderr: '' }; + }, }); assert.equal(result.status, 0); assert.match(result.stdout, /Selected: 2\/10/); + assert.match(result.stdout, /Kitty agent terminal:/); assert.deepEqual(runCalls.map((call) => call.args), [ ['--task', 'fix auth codex 01', '--agent', 'codex', '--base', 'main'], ['--task', 'fix auth codex 02', '--agent', 'codex', '--base', 'main'], ]); + assert.deepEqual(terminalCalls.map((call) => call.args[0]), ['--version', '--detach']); + const sessionFile = terminalCalls[1].args[2]; + assert.match(sessionFile, /\.guardex\/agents\/terminals\/agent__codex__fix-auth-codex-01-2\.kitty-session$/); + const sessionBody = fs.readFileSync(sessionFile, 'utf8'); + assert.match(sessionBody, /new_tab '1: codex fix-auth-codex-01'/); + assert.match(sessionBody, /launch --title '2: codex fix-auth-codex-02' sh -lc 'cd/); assert.deepEqual(created.map((entry) => entry.payload.task), ['fix auth', 'fix auth']); assert.deepEqual(created.map((entry) => entry.payload.branch), [ 'agent/codex/fix-auth-codex-01', 'agent/codex/fix-auth-codex-02', ]); }); + +test('agents start --terminal none skips multi-agent terminal launch', () => { + const runCalls = []; + const terminalCalls = []; + const branches = [ + ['agent/codex/fix-auth-codex-01', '/repo/.omx/agent-worktrees/repo__codex__fix-auth-codex-01'], + ['agent/codex/fix-auth-codex-02', '/repo/.omx/agent-worktrees/repo__codex__fix-auth-codex-02'], + ]; + const start = loadStartWithMocks({ + runPackageAsset(assetKey, args, options) { + runCalls.push({ assetKey, args, options }); + const branchIndex = runCalls.filter((call) => call.assetKey === 'branchStart').length - 1; + return { status: 0, stdout: branchStartOutput(branches[branchIndex][0], branches[branchIndex][1]), stderr: '' }; + }, + createAgentSession(repoRoot, payload) { + return payload; + }, + updateAgentSession() { + throw new Error('unexpected update'); + }, + currentBranchName: () => 'main', + }); + + const result = start.startAgentLane('/repo', { + task: 'fix auth', + agent: 'codex', + count: 2, + base: 'main', + claims: [], + terminal: 'none', + }, { + terminalRunner(cmd, args, options) { + terminalCalls.push({ cmd, args, options }); + return { status: 0, stdout: '', stderr: '' }; + }, + }); + + assert.equal(result.status, 0); + assert.equal(terminalCalls.length, 0); + assert.doesNotMatch(result.stdout, /Kitty agent terminal:/); +}); + +test('agents start keeps lanes intact and prints Kitty recovery when terminal is missing', () => { + const runCalls = []; + const terminalCalls = []; + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-agents-missing-kitty-')); + const branches = [ + ['agent/codex/fix-auth-codex-01', path.join(repoRoot, '.omx/agent-worktrees/repo__codex__fix-auth-codex-01')], + ['agent/codex/fix-auth-codex-02', path.join(repoRoot, '.omx/agent-worktrees/repo__codex__fix-auth-codex-02')], + ]; + const start = loadStartWithMocks({ + runPackageAsset(assetKey, args, options) { + runCalls.push({ assetKey, args, options }); + const branchIndex = runCalls.filter((call) => call.assetKey === 'branchStart').length - 1; + return { status: 0, stdout: branchStartOutput(branches[branchIndex][0], branches[branchIndex][1]), stderr: '' }; + }, + createAgentSession(repoRootArg, payload) { + return payload; + }, + updateAgentSession() { + throw new Error('unexpected update'); + }, + currentBranchName: () => 'main', + }); + + const result = start.startAgentLane(repoRoot, { + task: 'fix auth', + agent: 'codex', + count: 2, + base: 'main', + claims: [], + }, { + terminalRunner(cmd, args, options) { + terminalCalls.push({ cmd, args, options }); + return { status: 127, stdout: '', stderr: '', error: new Error('spawn kitty ENOENT') }; + }, + }); + + assert.equal(result.status, 0); + assert.equal(terminalCalls.length, 1); + assert.match(result.stderr, /Kitty terminal not launched: spawn kitty ENOENT/); + assert.match(result.stderr, /Kitty session file:/); + assert.match(result.stderr, /Recovery: kitty --detach --session/); + assert.equal(fs.existsSync(result.terminal.sessionFilePath), true); +}); diff --git a/test/cli-args-dispatch.test.js b/test/cli-args-dispatch.test.js index 8620e33..f2fe738 100644 --- a/test/cli-args-dispatch.test.js +++ b/test/cli-args-dispatch.test.js @@ -113,6 +113,7 @@ test('parseAgentsArgs applies interval overrides and validates the subcommand', count: 1, agentSelectionSpecs: [], panel: false, + terminal: 'kitty', dryRun: false, reviewIntervalSeconds: 15, cleanupIntervalSeconds: 45, @@ -170,8 +171,21 @@ test('parseAgentsArgs applies interval overrides and validates the subcommand', assert.equal(panelOptions.panel, true); assert.equal(panelOptions.agent, 'codex'); assert.equal(panelOptions.count, 3); + assert.equal(panelOptions.terminal, 'kitty'); assert.deepEqual(panelOptions.agentSelectionSpecs, ['codex:2,claude']); + const noTerminalOptions = parseAgentsArgs([ + 'start', + 'fix auth tests', + '--panel', + '--codex-accounts', + '3', + '--terminal', + 'none', + ]); + + assert.equal(noTerminalOptions.terminal, 'none'); + const emptyPanelOptions = parseAgentsArgs([ 'start', '--panel',