Files
orca/src/shared/tui-agent-startup.test.ts
T
dcfa91919f fix(automations): deliver Hermes prompts via native query (#7862)
* fix(automations): deliver prompt to Hermes TUI via process-ready signal

Hermes's prompt_toolkit TUI never emits the DECSET 2004 bracketed-paste
handshake that the default draft-paste readiness waiter gates on, so the
automation prompt was silently dropped and the agent sat idle (terminal
opens, nothing entered). Add a 'process-ready' DraftPasteReadySignal that
arms the quiet-window on first PTY output and pastes once the TUI settles,
and assign it to the hermes agent config.

Adds a Hermes unit test covering the no-handshake path.

Design for the follow-up PR/issue review loop lives in
docs/design/pr-issue-review-loop.md (not committed; gitignored).

* fix(automations): skip broken process-name fallback for process-ready

CodeRabbit: the process-ready fallback consulted waitForAgentReady, which
compares the foreground process basename via isExpectedAgentProcess. Wrapped
interpreter launches surface as 'python3 .../hermes', so the check can never
confirm readiness and only drops the paste. process-ready readiness is the
PTY-quiet window (handled by waitForAgentDraftInputReady); the process-name
fallback is dead for this signal. Skip it and return false on timeout instead.

* test(automations): assert single paste for Hermes quiet-window path

Guard against a duplicate paste in the no-handshake Hermes path by
asserting sendRuntimePtyInputVerified is called exactly once. Addresses
the CodeRabbit nitpick on PR #7862.

* fix(automations): skip broken process-name fallback on pty-bound paste path

pasteDraftToAgentPtyWhenReady (quick-create/work-item route) still ran the
python3-vs-hermes process-name fallback that can never match for
process-ready agents, burning ~1s and dropping the paste. Mirror the
process-ready guard from pasteDraftWhenAgentReady and surface onTimeout.

Adds regression tests for both the quiet-window happy path and the
fallback-skip on the pty-bound path.

Co-authored-by: Orca <help@stably.ai>

* fix(automations): defer submit Enter until the Hermes TUI is interactive

Live-testing against Hermes v0.18.2 showed the fixed 50ms post-paste Enter
is swallowed: the node ui-tui takes 15s+ to boot, the paste fires ~1.5s in
(process-ready quiet window), and the cooked-mode line discipline turns the
early \r into \n, which the editor treats as newline-insert. The prompt
parked in the input box and the automation never executed.

For process-ready agents, defer the Enter until the TUI signals
interactivity (DECSET 2004 enable) or echoes the pasted content marker-free
(legacy prompt_toolkit), with a 120s best-effort cap. PTY input is FIFO, so
the deferred Enter always lands after the buffered paste text. The paste
echo of the content itself (raw or caret-notation markers adjacent) is
rejected so a cooked-mode echo can't release the Enter early.

Verified end-to-end in the dev app: automation prompt pasted, deferred
Enter released on tui-ready, Hermes submitted and ran the turn.

Co-authored-by: Orca <help@stably.ai>

* fix(automations): use Hermes native startup query

Co-authored-by: Orca <help@stably.ai>

* fix(automations): preserve quoted Hermes queries on Windows

* fix(agents): preserve invalid argument error message

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
2026-07-11 02:35:01 -07:00

857 lines
26 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildAgentDraftLaunchPlan,
buildAgentResumeStartupPlan,
buildAgentStartupPlan,
buildShellCommandFromArgv,
planAgentCliArgsSuffix
} from './tui-agent-startup'
import { TUI_AGENT_CONFIG } from './tui-agent-config'
import { normalizeTuiAgentArgsRecord, resolveTuiAgentLaunchArgs } from './tui-agent-launch-defaults'
import { tokenizeStartupCommand } from './tui-agent-startup-shell'
function unwrapPosixShellScript(command: string | undefined): string {
const tokenized = tokenizeStartupCommand(command ?? '', 'posix')
expect(tokenized.ok).toBe(true)
const wrapper = tokenized.ok ? (tokenized.tokens[2] ?? '') : ''
const encoded = wrapper.match(/printf %b "([\\0-7]+)"/)?.[1]
if (!encoded) {
return wrapper
}
const bytes = [...encoded.matchAll(/\\0([0-7]{3})/g)].map((match) => Number.parseInt(match[1], 8))
return new TextDecoder().decode(new Uint8Array(bytes))
}
function unwrapPowerShellScript(command: string | undefined): string {
const encoded = command?.match(/-EncodedCommand\s+(\S+)/)?.[1]
expect(encoded).toBeDefined()
return Buffer.from(encoded!, 'base64').toString('utf16le')
}
describe('tui agent startup plans', () => {
it.each(['powershell', 'cmd'] as const)(
'keeps the established invalid-quote error on %s',
(shell) => {
expect(planAgentCliArgsSuffix('--model "unterminated', shell)).toEqual({
ok: false,
error: 'CLI arguments are invalid: Unclosed quote in command template.'
})
}
)
it('uses POSIX quoting when the target shell is Linux', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: "fix Bob's branch",
cmdOverrides: {},
platform: 'linux'
})
expect(plan?.launchCommand).toBe("claude 'fix Bob'\\''s branch'")
})
it('uses PowerShell quoting by default when the target shell is Windows', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: 'fix Bob\'s "quoted" branch',
cmdOverrides: {},
platform: 'win32'
})
expect(plan?.launchCommand).toBe("claude 'fix Bob''s \"quoted\" branch'")
})
it('invokes fully quoted argv commands in PowerShell', () => {
expect(buildShellCommandFromArgv(['codex', 'resume', 's1'], 'powershell')).toBe(
"& 'codex' 'resume' 's1'"
)
})
it('uses cmd escaping when requested explicitly', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: 'fix "quoted" & %PATH%',
cmdOverrides: {},
platform: 'win32',
shell: 'cmd'
})
expect(plan?.launchCommand).toBe('claude "fix ^"quoted^" ^& ^%PATH^%"')
})
it('terminates Grok options before a flag-shaped POSIX prompt', () => {
const plan = buildAgentStartupPlan({
agent: 'grok',
prompt: '--version',
cmdOverrides: {},
platform: 'linux'
})
expect(plan?.launchCommand).toBe("grok -- '--version'")
})
it('terminates Grok options before a flag-shaped PowerShell prompt', () => {
const plan = buildAgentStartupPlan({
agent: 'grok',
prompt: '-h',
cmdOverrides: {},
platform: 'win32'
})
expect(plan?.launchCommand).toBe("grok -- '-h'")
})
it('terminates Grok options before a subcommand-shaped cmd prompt', () => {
const plan = buildAgentStartupPlan({
agent: 'grok',
prompt: 'help',
cmdOverrides: {},
platform: 'win32',
shell: 'cmd'
})
expect(plan?.launchCommand).toBe('grok -- "help"')
})
it('places the Grok prompt separator after configured agent arguments', () => {
const plan = buildAgentStartupPlan({
agent: 'grok',
prompt: '--version',
cmdOverrides: {},
agentArgs: '--always-approve',
platform: 'win32'
})
expect(plan?.launchCommand).toBe("grok '--always-approve' -- '--version'")
})
it('does not add the Grok prompt separator to other argv agents', () => {
const plan = buildAgentStartupPlan({
agent: 'codex',
prompt: '--version',
cmdOverrides: {},
platform: 'linux'
})
expect(plan?.launchCommand).toBe("codex '--version'")
})
it.each([
{ platform: 'linux' as const, shell: 'posix' as const },
{ platform: 'win32' as const, shell: 'powershell' as const },
{ platform: 'win32' as const, shell: 'cmd' as const }
])('delivers multiline Hermes queries through a child-only expansion on $shell', (testCase) => {
const prompt = 'first line\nsecond "quoted" line with %PATH%'
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt,
cmdOverrides: {},
agentArgs: '--yolo',
platform: testCase.platform,
shell: testCase.shell
})
expect(plan?.launchCommand).not.toContain(prompt)
expect(plan?.followupPrompt).toBeNull()
expect(plan?.launchConfig.agentCommand).toBe('hermes --tui')
expect(plan?.env?.ORCA_HERMES_STARTUP_QUERY).toBe(prompt)
const script =
testCase.shell === 'posix'
? unwrapPosixShellScript(plan?.launchCommand)
: unwrapPowerShellScript(plan?.launchCommand)
expect(script).toContain("'hermes' 'chat'")
expect(script).toContain('--query=')
expect(testCase.shell === 'posix' ? plan?.launchCommand : script).toContain(
'ORCA_HERMES_STARTUP_QUERY'
)
expect(script).toContain("'--yolo' '--tui'")
expect(script).toContain(
testCase.shell === 'posix'
? '--query=${__orca_hermes_startup_query}'
: 'Remove-Item Env:ORCA_HERMES_STARTUP_QUERY'
)
if (testCase.shell === 'posix') {
expect(plan?.launchCommand).toContain('unset ORCA_HERMES_STARTUP_QUERY')
}
})
it('uses a sh invocation that POSIX-host PowerShell can parse', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: {},
platform: 'linux'
})
expect(plan?.launchCommand).toMatch(/^sh -c /)
expect(plan?.launchCommand).toMatch(/\\0[0-7]{3}/)
expect(plan?.launchCommand).not.toContain("'sh' '-c'")
expect(plan?.launchCommand).not.toContain("'\\''")
})
it('moves Hermes command override flags after the chat subcommand', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run privately',
cmdOverrides: { hermes: 'hermes --tui --provider anthropic' },
agentArgs: '--yolo',
platform: 'linux'
})
const script = unwrapPosixShellScript(plan?.launchCommand)
expect(script).toContain("'--provider' 'anthropic' '--yolo' '--tui'")
expect(plan?.env?.ORCA_HERMES_STARTUP_QUERY).toBe('run privately')
})
it.each([
{
shell: 'powershell' as const,
override: '"C:\\Program Files\\Hermes\\hermes.exe" --tui',
expected: "& 'C:\\Program Files\\Hermes\\hermes.exe' 'chat'"
},
{
shell: 'cmd' as const,
override: 'C:\\Tools\\hermes.exe --tui',
expected: "& 'C:\\Tools\\hermes.exe' 'chat'"
}
])('preserves Windows paths in Hermes command overrides on $shell', (testCase) => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: testCase.override },
platform: 'win32',
shell: testCase.shell
})
expect(unwrapPowerShellScript(plan?.launchCommand)).toContain(testCase.expected)
})
it('removes a configured duplicate chat subcommand', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'hermes --provider copilot chat --tui' },
agentArgs: '--provider copilot chat --yolo',
platform: 'linux'
})
const script = unwrapPosixShellScript(plan?.launchCommand)
expect(script.match(/'chat'/g)).toHaveLength(1)
expect(script).toContain("'--provider' 'copilot' '--yolo'")
})
it('preserves an option value named chat', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'hermes --profile chat --tui' },
platform: 'linux'
})
expect(unwrapPosixShellScript(plan?.launchCommand)).toContain("'--profile' 'chat'")
})
it('keeps Orca ownership of the Hermes startup query and TUI mode', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'automation prompt',
cmdOverrides: { hermes: 'hermes --query override --cli' },
agentArgs: '-q=second-override --query=third-override -qfourth-override --tui',
platform: 'linux'
})
const script = unwrapPosixShellScript(plan?.launchCommand)
expect(script.match(/--query=/g)).toHaveLength(1)
expect(script).not.toContain('override')
expect(script).not.toContain("'--cli'")
expect(script.match(/'--tui'/g)).toHaveLength(1)
expect(plan?.env?.ORCA_HERMES_STARTUP_QUERY).toBe('automation prompt')
})
it('preserves wrapper tokens before the Hermes executable', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'uv run hermes --tui' },
platform: 'linux'
})
expect(unwrapPosixShellScript(plan?.launchCommand)).toContain("'uv' 'run' 'hermes' 'chat'")
})
it('selects the final Hermes executable token in a wrapper', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'sudo -u hermes hermes --tui' },
platform: 'linux'
})
expect(unwrapPosixShellScript(plan?.launchCommand)).toContain(
"'sudo' '-u' 'hermes' 'hermes' 'chat'"
)
})
it('selects the wrapped executable when the wrapper and command both name Hermes', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'sudo -u hermes hermes chat --tui' },
platform: 'linux'
})
const script = unwrapPosixShellScript(plan?.launchCommand)
expect(script).toContain("'sudo' '-u' 'hermes' 'hermes' 'chat'")
expect(script.match(/'chat'/g)).toHaveLength(1)
})
it('does not mistake a Hermes option value for a wrapped executable', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'hermes chat --resume hermes --tui' },
platform: 'linux'
})
const script = unwrapPosixShellScript(plan?.launchCommand)
expect(script.match(/'chat'/g)).toHaveLength(1)
expect(script).toContain("'--resume' 'hermes'")
})
it('preserves POSIX environment-assignment command prefixes', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'HERMES_HOME=/tmp/test uv run hermes --tui' },
platform: 'linux'
})
expect(unwrapPosixShellScript(plan?.launchCommand)).toContain(
"'env' 'HERMES_HOME=/tmp/test' 'uv' 'run' 'hermes' 'chat'"
)
})
it('rejects a Hermes command override with no identifiable executable', () => {
expect(
buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run it',
cmdOverrides: { hermes: 'custom-agent --tui' },
platform: 'linux'
})
).toBeNull()
})
it('rejects Hermes queries that exceed the safe Windows environment limit', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'x'.repeat(24_000),
cmdOverrides: {},
platform: 'win32'
})
expect(plan).toBeNull()
})
it.each(['quote "this"', 'print %PATH%', 'toggle !feature!', 'inspect C:\\repo\\'])(
'keeps a complex cmd Hermes query out of command text: %s',
(prompt) => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt,
cmdOverrides: {},
platform: 'win32',
shell: 'cmd'
})
expect(plan?.launchCommand).not.toContain(prompt)
expect(plan?.env?.ORCA_HERMES_STARTUP_QUERY).toBe(prompt)
}
)
it('uses the Windows remote default shell for SSH Hermes queries', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: 'run remotely',
cmdOverrides: {},
platform: 'win32',
isRemote: true
})
expect(unwrapPowerShellScript(plan?.launchCommand)).toContain(
"& 'hermes' 'chat' \"--query=$orcaHermesNativeQuery\" '--tui'"
)
})
it('measures POSIX Hermes query limits in UTF-8 bytes', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: '界'.repeat(50_000),
cmdOverrides: {},
platform: 'linux'
})
expect(plan).toBeNull()
})
it('keeps empty Hermes launches on the interactive TUI command', () => {
const plan = buildAgentStartupPlan({
agent: 'hermes',
prompt: '',
cmdOverrides: {},
platform: 'linux',
allowEmptyPromptLaunch: true
})
expect(plan?.launchCommand).toBe('hermes --tui')
expect(plan?.followupPrompt).toBeNull()
})
it('does not launch Codex with the Orca profile when agent status hooks are enabled', () => {
const plan = buildAgentStartupPlan({
agent: 'codex',
prompt: 'fix it',
cmdOverrides: {},
platform: 'linux'
})
expect(plan?.launchCommand).toBe("codex 'fix it'")
expect(plan?.startupCommandDelivery).toBe('shell-ready')
})
it('keeps plain empty Codex startup on the fast delivery path', () => {
const plan = buildAgentStartupPlan({
agent: 'codex',
prompt: '',
cmdOverrides: {},
platform: 'linux',
allowEmptyPromptLaunch: true
})
expect(plan).toEqual({
agent: 'codex',
launchCommand: 'codex',
expectedProcess: 'codex',
followupPrompt: null,
launchConfig: { agentCommand: 'codex', agentArgs: '', agentEnv: {} }
})
})
it('launches Claude without Orca settings injection', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: 'fix it',
cmdOverrides: {},
platform: 'linux'
})
expect(plan?.launchCommand).toBe("claude 'fix it'")
expect(plan?.launchCommand).not.toContain('--settings')
})
it('uses the Linux Orca CLI command for Claude Agent Teams launches', () => {
const plan = buildAgentStartupPlan({
agent: 'claude-agent-teams',
prompt: '',
cmdOverrides: {},
platform: 'linux',
allowEmptyPromptLaunch: true
})
expect(plan?.launchCommand).toBe('orca-ide claude-teams')
})
it('uses the plain orca shim for Claude Agent Teams on Linux SSH remotes', () => {
// Why: the SSH relay deploys the CLI shim as `orca` (not the local-only
// `orca-ide` GNOME-screen-reader workaround), so a remote launch must not
// emit `orca-ide claude-teams` — that name is not on the remote PATH and
// `claude-teams` is rejected by the relay's CLI switch (issue #6500).
const plan = buildAgentStartupPlan({
agent: 'claude-agent-teams',
prompt: '',
cmdOverrides: {},
platform: 'linux',
isRemote: true,
allowEmptyPromptLaunch: true
})
expect(plan?.launchCommand).toBe('orca claude-teams')
})
it('keeps the Windows orca.cmd shim for Claude Agent Teams on SSH remotes', () => {
// Why: the Windows remote shim is also `orca.cmd`, matching the local
// win32 override, so remoteness must not alter the Windows command.
const plan = buildAgentStartupPlan({
agent: 'claude-agent-teams',
prompt: '',
cmdOverrides: {},
platform: 'win32',
isRemote: true,
allowEmptyPromptLaunch: true
})
expect(plan?.launchCommand).toBe('orca.cmd claude-teams')
})
it('keeps the Linux orca-ide wrapper for local (non-remote) Claude Agent Teams', () => {
// Why: the `orca-ide` rename is still required for a local Linux desktop
// install (avoids shadowing the GNOME Orca screen reader), so an explicit
// isRemote:false must preserve it.
const plan = buildAgentStartupPlan({
agent: 'claude-agent-teams',
prompt: '',
cmdOverrides: {},
platform: 'linux',
isRemote: false,
allowEmptyPromptLaunch: true
})
expect(plan?.launchCommand).toBe('orca-ide claude-teams')
})
it('launches OpenClaude as a distinct argv agent', () => {
const plan = buildAgentStartupPlan({
agent: 'openclaude',
prompt: 'fix it',
cmdOverrides: {},
platform: 'linux'
})
expect(plan).toEqual({
agent: 'openclaude',
launchCommand: "openclaude 'fix it'",
expectedProcess: 'openclaude',
followupPrompt: null,
launchConfig: { agentCommand: 'openclaude', agentArgs: '', agentEnv: {} }
})
})
it('launches Mistral Vibe through the installed vibe executable', () => {
const plan = buildAgentStartupPlan({
agent: 'mistral-vibe',
prompt: 'fix it',
cmdOverrides: {},
platform: 'linux'
})
expect(plan).toEqual({
agent: 'mistral-vibe',
launchCommand: 'vibe',
expectedProcess: 'vibe',
followupPrompt: 'fix it',
launchConfig: { agentCommand: 'vibe', agentArgs: '', agentEnv: {} }
})
})
it('launches Qwen Code through the installed qwen executable', () => {
const plan = buildAgentStartupPlan({
agent: 'qwen-code',
prompt: 'fix it',
cmdOverrides: {},
platform: 'linux'
})
expect(plan).toEqual({
agent: 'qwen-code',
launchCommand: 'qwen',
expectedProcess: 'qwen',
followupPrompt: 'fix it',
launchConfig: { agentCommand: 'qwen', agentArgs: '', agentEnv: {} }
})
})
it('leaves Claude command overrides untouched', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: 'fix it',
cmdOverrides: { claude: 'claude --dangerously-skip-permissions' },
platform: 'linux'
})
expect(plan?.launchCommand).toBe("claude --dangerously-skip-permissions 'fix it'")
})
it('leaves Codex command overrides untouched', () => {
const plan = buildAgentStartupPlan({
agent: 'codex',
prompt: 'fix it',
cmdOverrides: { codex: 'codex --profile work' },
platform: 'linux'
})
expect(plan?.launchCommand).toBe("codex --profile work 'fix it'")
})
it('builds Windows resume plans that PowerShell can invoke', () => {
const plan = buildAgentResumeStartupPlan({
agent: 'codex',
providerSession: { key: 'session_id', id: 's1' },
cmdOverrides: {},
platform: 'win32'
})
expect(plan?.launchCommand).toBe("codex 'resume' 's1'")
})
it('honors command overrides when building POSIX resume plans', () => {
const plan = buildAgentResumeStartupPlan({
agent: 'codex',
providerSession: { key: 'session_id', id: 's1' },
cmdOverrides: { codex: 'codex --profile work' },
platform: 'linux'
})
expect(plan?.launchCommand).toBe("codex --profile work 'resume' 's1'")
})
it('uses a captured launch command when building resume plans after overrides change', () => {
const plan = buildAgentResumeStartupPlan({
agent: 'codex',
providerSession: { key: 'session_id', id: 's1' },
cmdOverrides: { codex: 'codex --profile changed' },
agentCommand: 'codex --profile captured',
platform: 'linux'
})
expect(plan?.launchCommand).toBe("codex --profile captured 'resume' 's1'")
expect(plan?.launchConfig).toEqual({
agentCommand: 'codex --profile captured',
agentArgs: '',
agentEnv: {}
})
})
it('appends shell-quoted CLI arguments before prompt delivery flags', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: 'fix it',
cmdOverrides: {},
agentArgs: '--model sonnet --add-dir "path with spaces"',
platform: 'linux'
})
expect(plan?.launchCommand).toBe(
"claude '--model' 'sonnet' '--add-dir' 'path with spaces' 'fix it'"
)
})
it('uses PowerShell quoting for CLI arguments on Windows', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: 'fix it',
cmdOverrides: {},
agentArgs: '--model sonnet --name "Bob\'s"',
platform: 'win32'
})
expect(plan?.launchCommand).toBe("claude '--model' 'sonnet' '--name' 'Bob''s' 'fix it'")
})
it('carries agent launch environment defaults into startup plans', () => {
const plan = buildAgentStartupPlan({
agent: 'goose',
prompt: '',
cmdOverrides: {},
agentEnv: { GOOSE_MODE: 'auto' },
platform: 'linux',
allowEmptyPromptLaunch: true
})
expect(plan?.launchCommand).toBe('goose')
expect(plan?.env).toEqual({ GOOSE_MODE: 'auto' })
expect(plan?.launchConfig).toEqual({
agentCommand: 'goose',
agentArgs: '',
agentEnv: { GOOSE_MODE: 'auto' }
})
})
it('captures empty args and env as explicit launch config values', () => {
const plan = buildAgentStartupPlan({
agent: 'claude',
prompt: '',
cmdOverrides: {},
agentArgs: '',
agentEnv: {},
platform: 'linux',
allowEmptyPromptLaunch: true
})
expect(plan?.launchConfig).toEqual({ agentCommand: 'claude', agentArgs: '', agentEnv: {} })
})
it('does not append the unsupported OpenCode TUI skip-permissions arg', () => {
const agentDefaultArgs = normalizeTuiAgentArgsRecord({
opencode: '--dangerously-skip-permissions'
})
const plan = buildAgentStartupPlan({
agent: 'opencode',
prompt: 'fix it',
cmdOverrides: {},
agentArgs: resolveTuiAgentLaunchArgs('opencode', agentDefaultArgs),
platform: 'linux'
})
expect(plan?.launchCommand).toBe("opencode --prompt 'fix it'")
})
it('keeps opencode and mimo-code on the cursor-gated paste draft route', () => {
expect(TUI_AGENT_CONFIG.opencode.draftPasteReadySignal).toBe(
'render-cursor-after-bracketed-paste'
)
expect(TUI_AGENT_CONFIG.opencode.draftPromptFlag).toBeUndefined()
expect(TUI_AGENT_CONFIG.opencode.draftPromptEnvVar).toBeUndefined()
expect(TUI_AGENT_CONFIG['mimo-code'].draftPasteReadySignal).toBe(
'render-cursor-after-bracketed-paste'
)
expect(TUI_AGENT_CONFIG['mimo-code'].draftPromptFlag).toBeUndefined()
expect(TUI_AGENT_CONFIG['mimo-code'].draftPromptEnvVar).toBeUndefined()
// Why: no native draft launch plan means both agents fall through to the
// cursor-gated paste-after-ready route, where the new signal applies.
expect(
buildAgentDraftLaunchPlan({
agent: 'opencode',
draft: 'x',
cmdOverrides: {},
platform: 'darwin'
})
).toBeNull()
expect(
buildAgentDraftLaunchPlan({
agent: 'mimo-code',
draft: 'x',
cmdOverrides: {},
platform: 'darwin'
})
).toBeNull()
})
it('appends Kiro trust defaults to the chat subcommand that accepts them', () => {
const plan = buildAgentStartupPlan({
agent: 'kiro',
prompt: 'fix it',
cmdOverrides: {},
agentArgs: '--trust-all-tools',
platform: 'linux'
})
expect(plan?.launchCommand).toBe("kiro-cli chat --tui '--trust-all-tools'")
})
it('launches Continue through the documented cn binary', () => {
const plan = buildAgentStartupPlan({
agent: 'continue',
prompt: 'fix it',
cmdOverrides: {},
agentArgs: '--allow "*"',
platform: 'linux'
})
expect(plan?.launchCommand).toBe("cn '--allow' '*'")
})
it('clears draft environment variables with the target shell syntax', () => {
expect(
buildAgentDraftLaunchPlan({
agent: 'pi',
draft: 'https://github.com/acme/repo/issues/42',
cmdOverrides: {},
platform: 'win32'
})?.launchCommand
).toBe('pi; Remove-Item Env:ORCA_PI_PREFILL -ErrorAction SilentlyContinue')
expect(
buildAgentDraftLaunchPlan({
agent: 'pi',
draft: 'https://github.com/acme/repo/issues/42',
cmdOverrides: {},
platform: 'win32',
shell: 'cmd'
})?.launchCommand
).toBe('pi & set "ORCA_PI_PREFILL="')
})
it('returns an OMP draft plan with ORCA_OMP_PREFILL (OMP-scoped, not Pi-shared)', () => {
// Why: OMP owns its own managed prefill extension and env var.
// orca-prefill.ts reads ORCA_OMP_PREFILL for OMP launches — see
// src/main/pi/titlebar-extension-service.ts — so a draft plan for OMP
// MUST emit that name. A regression here would either silently drop the
// draft (Pi var ignored by OMP) or honor a stale Pi-PTY draft.
const plan = buildAgentDraftLaunchPlan({
agent: 'omp',
draft: 'fix the omp regression',
cmdOverrides: {},
platform: 'linux'
})
expect(plan).not.toBeNull()
expect(plan?.env).toEqual({ ORCA_OMP_PREFILL: 'fix the omp regression' })
expect(plan?.expectedProcess).toBe('omp')
expect(plan?.launchCommand).toBe('omp; unset ORCA_OMP_PREFILL')
})
it('returns null for oversized Windows flag drafts so callers paste after ready', () => {
expect(
buildAgentDraftLaunchPlan({
agent: 'claude',
draft: 'x'.repeat(25_000),
cmdOverrides: {},
platform: 'win32'
})
).toBeNull()
})
it('returns null for oversized Windows env-var drafts so callers paste after ready', () => {
expect(
buildAgentDraftLaunchPlan({
agent: 'pi',
draft: 'x'.repeat(25_000),
cmdOverrides: {},
platform: 'win32'
})
).toBeNull()
})
it('launches Devin with stdin-after-start prompt delivery', () => {
const plan = buildAgentStartupPlan({
agent: 'devin',
prompt: 'fix the tests',
cmdOverrides: {},
agentArgs: resolveTuiAgentLaunchArgs('devin', null),
platform: 'linux'
})
expect(plan).toEqual({
agent: 'devin',
launchCommand: "devin '--permission-mode' 'bypass'",
expectedProcess: 'devin',
followupPrompt: 'fix the tests',
launchConfig: {
agentCommand: "devin '--permission-mode' 'bypass'",
agentArgs: '--permission-mode bypass',
agentEnv: {}
}
})
})
it('excludes transient draft prompt env from launch config', () => {
const plan = buildAgentDraftLaunchPlan({
agent: 'pi',
draft: 'prefill text',
cmdOverrides: {},
agentEnv: { ORCA_AGENT_MODE: 'managed' },
platform: 'linux'
})
expect(plan?.env).toEqual({ ORCA_AGENT_MODE: 'managed', ORCA_PI_PREFILL: 'prefill text' })
expect(plan?.launchConfig).toEqual({
agentCommand: 'pi',
agentArgs: '',
agentEnv: { ORCA_AGENT_MODE: 'managed' }
})
})
it('appends Devin default permission-mode bypass before stdin prompt delivery', () => {
expect(resolveTuiAgentLaunchArgs('devin', null)).toBe('--permission-mode bypass')
})
})