From 23fc08b4e939169f0f6bc80d6e2a92e88699258b Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 2 Oct 2026 20:56:53 -0700 Subject: [PATCH] fix(opencode): pass run prompts as positional messages Preserve run flags and use the existing shell quoting and run-command detector to append the initial message after --, reusing an existing separator. TUI launches retain their version-selected prompt transport and draft behavior. Original run-order work: @coelho-doti (#13065, tracked in #17551). --- src/shared/flag-prompt-startup.ts | 50 +++++++++++++++++++ src/shared/opencode-startup-prompt.test.ts | 57 +++++++++++++++++++++- src/shared/tui-agent-startup.ts | 21 ++++---- 3 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 src/shared/flag-prompt-startup.ts diff --git a/src/shared/flag-prompt-startup.ts b/src/shared/flag-prompt-startup.ts new file mode 100644 index 00000000000..28dd70cf0bd --- /dev/null +++ b/src/shared/flag-prompt-startup.ts @@ -0,0 +1,50 @@ +import type { AgentStartupPlan } from './tui-agent-startup' +import type { SleepingAgentLaunchConfig } from './agent-session-resume' +import type { SessionOptionValue } from './native-chat-session-options' +import type { TuiAgent } from './tui-agent' +import { TUI_AGENT_CONFIG } from './tui-agent-config' +import { tokenizeStartupCommand, type AgentStartupShell } from './tui-agent-startup-shell' +import { isOpenCodeRunCommand } from './opencode-headless-command' +import { openCodeStartupPromptEnv } from './opencode-startup-prompt' + +export function appliedSessionOptionProps(values: Record) { + return Object.keys(values).length > 0 ? { sessionOptions: { ...values } } : {} +} + +export function buildFlagPromptStartupPlan(args: { + agent: TuiAgent + launchCommand: string + quotedPrompt: string + prompt: string + shell: AgentStartupShell + launchConfig: SleepingAgentLaunchConfig + sessionOptions: Record + agentEnv: Record | null | undefined +}): AgentStartupPlan { + const parsed = tokenizeStartupCommand(args.launchCommand, args.shell) + const isOpenCodeRun = + (args.agent === 'opencode' || args.agent === 'opencode2') && + parsed.ok && + isOpenCodeRunCommand(parsed.tokens) + // OpenCode run takes a positional message; --prompt belongs to its TUI. + const promptSuffix = isOpenCodeRun + ? parsed.tokens.includes('--') + ? ` ${args.quotedPrompt}` + : ` -- ${args.quotedPrompt}` + : ` --prompt ${args.quotedPrompt}` + return { + agent: args.agent, + launchCommand: `${args.launchCommand}${promptSuffix}`, + expectedProcess: TUI_AGENT_CONFIG[args.agent].expectedProcess, + followupPrompt: null, + launchConfig: args.launchConfig, + ...appliedSessionOptionProps(args.sessionOptions), + ...openCodeStartupPromptEnv( + args.agent, + args.launchCommand, + args.shell, + args.prompt, + args.agentEnv + ) + } +} diff --git a/src/shared/opencode-startup-prompt.test.ts b/src/shared/opencode-startup-prompt.test.ts index e4aab26d065..d438028ba96 100644 --- a/src/shared/opencode-startup-prompt.test.ts +++ b/src/shared/opencode-startup-prompt.test.ts @@ -6,6 +6,7 @@ import { OPENCODE_STARTUP_PROMPT_BODY_ENV, OPENCODE_STARTUP_PROMPT_SHELL_ENV } from './opencode-startup-prompt' +import { tokenizeStartupCommand } from './tui-agent-startup-shell' describe('native OpenCode startup submission intent', () => { it('binds the exact trimmed native prompt to host-selectable transport', () => { @@ -33,9 +34,63 @@ describe('native OpenCode startup submission intent', () => { platform: 'linux' }) expect(plan?.env).toBeUndefined() - expect(plan?.launchCommand).toContain('run --prompt') + expect(plan?.launchCommand).toContain("run -- 'task'") }) + it.each(['posix', 'powershell', 'cmd'] as const)( + 'keeps run and its flags before a positional message in %s', + (shell) => { + for (const agent of ['opencode', 'opencode2'] as const) { + const prompt = '--literal task with unicode é' + const plan = buildAgentStartupPlan({ + agent, + prompt, + cmdOverrides: { [agent]: 'opencode --log-level debug run --standalone' }, + platform: shell === 'posix' ? 'linux' : 'win32', + shell, + agentEnv: { CUSTOM_CONFIG: 'kept' } + }) + expect(plan).not.toBeNull() + const parsed = tokenizeStartupCommand(plan?.launchCommand ?? '', shell) + expect(parsed.ok).toBe(true) + if (!parsed.ok) { + throw new Error(parsed.error) + } + expect(parsed.tokens).toEqual([ + 'opencode', + '--log-level', + 'debug', + 'run', + '--standalone', + '--', + prompt + ]) + expect(plan?.env).toEqual({ CUSTOM_CONFIG: 'kept' }) + expect(plan?.followupPrompt).toBeNull() + } + } + ) + + it.each(['posix', 'powershell', 'cmd'] as const)( + 'reuses an existing run message separator in %s', + (shell) => { + const plan = buildAgentStartupPlan({ + agent: 'opencode', + prompt: '--literal task', + cmdOverrides: { opencode: 'opencode run --standalone --' }, + platform: shell === 'posix' ? 'linux' : 'win32', + shell + }) + const parsed = tokenizeStartupCommand(plan?.launchCommand ?? '', shell) + expect(parsed.ok).toBe(true) + if (!parsed.ok) { + throw new Error(parsed.error) + } + expect(parsed.tokens).toEqual(['opencode', 'run', '--standalone', '--', '--literal task']) + expect(plan?.env).toBeUndefined() + } + ) + it('never gives an editable draft or empty launch an automatic submission intent', () => { const args = { agent: 'opencode' as const, cmdOverrides: {}, platform: 'linux' as const } expect( diff --git a/src/shared/tui-agent-startup.ts b/src/shared/tui-agent-startup.ts index ce8974a8cc3..8c56c1d9d95 100644 --- a/src/shared/tui-agent-startup.ts +++ b/src/shared/tui-agent-startup.ts @@ -17,7 +17,7 @@ import { inlineAgentDraftFitsPlatform } from './agent-draft-platform-limit' import type { TuiAgent } from './tui-agent' import type { SessionOptionValue } from './native-chat-session-options' import { resolveAgentLaunchCommand } from './tui-agent-launch-command' -import { openCodeStartupPromptEnv } from './opencode-startup-prompt' +import { appliedSessionOptionProps, buildFlagPromptStartupPlan } from './flag-prompt-startup' export { buildAgentResumeStartupPlan } from './tui-agent-resume-startup' @@ -36,10 +36,6 @@ export type AgentStartupPlan = { sessionOptions?: Record } -function appliedSessionOptionProps(values: Record) { - return Object.keys(values).length > 0 ? { sessionOptions: { ...values } } : {} -} - export function buildAgentStartupPlan(args: { agent: TuiAgent prompt: string @@ -117,15 +113,16 @@ export function buildAgentStartupPlan(args: { } if (config.promptInjectionMode === 'flag-prompt') { - return { + return buildFlagPromptStartupPlan({ agent, - launchCommand: `${launchCommand} --prompt ${quotedPrompt}`, - expectedProcess: config.expectedProcess, - followupPrompt: null, + launchCommand, + quotedPrompt, + prompt: trimmedPrompt, + shell, launchConfig, - ...appliedSessionOptionProps(baseCommand.appliedSessionOptions), - ...openCodeStartupPromptEnv(agent, launchCommand, shell, trimmedPrompt, args.agentEnv) - } + sessionOptions: baseCommand.appliedSessionOptions, + agentEnv: args.agentEnv + }) } if (config.promptInjectionMode === 'hermes-query') {