diff --git a/src/renderer/src/lib/launch-agent-in-new-tab-web-runtime.test.ts b/src/renderer/src/lib/launch-agent-in-new-tab-web-runtime.test.ts index d3638c3b71a..171f5988747 100644 --- a/src/renderer/src/lib/launch-agent-in-new-tab-web-runtime.test.ts +++ b/src/renderer/src/lib/launch-agent-in-new-tab-web-runtime.test.ts @@ -93,8 +93,7 @@ describe('launchAgentInNewTab paired web runtime', () => { activate: true, agentSessionKind: 'fresh', agent: 'claude', - // Why: an unconfigured client still resolves the default explicitly, so - // the host applies this client's launch defaults rather than its own. + // Why: the client resolves its default explicitly so the host cannot apply its own. agentArgs: '--dangerously-skip-permissions', viewMode: 'terminal' }) diff --git a/src/renderer/src/lib/launch-agent-in-new-tab.ts b/src/renderer/src/lib/launch-agent-in-new-tab.ts index 71e591155fe..0c45a11c544 100644 --- a/src/renderer/src/lib/launch-agent-in-new-tab.ts +++ b/src/renderer/src/lib/launch-agent-in-new-tab.ts @@ -157,11 +157,8 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI promptDelivery, pastePromptAfterReady: pasteDraftAfterLaunch, submitPastedPrompt, - // Why: the raw param is undefined on ordinary UI launches, and omitting it - // lets a headless host apply its own defaults — so a client configured for - // Manual gets the host's YOLO default (#15373). Send the client-resolved - // value, empty string included: the host treats present-but-empty as an - // explicit no-args override. + // Why: the raw param is undefined on ordinary UI launches, so omission let a headless host apply its + // own YOLO default (#15373). Send the resolved value — present-but-empty is an explicit no-args override. agentArgs: effectiveAgentArgs, // Why: omission means terminal locally, but would let a paired host apply // its own default; send the client's resolved terminal choice explicitly. @@ -195,6 +192,7 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI ...(startupPlan.env ? { env: startupPlan.env } : {}), launchConfig: startupPlan.launchConfig, launchAgent: agent, + // Why: the local launch command already bakes in the resolved args, so this override only records explicit caller intent. ...(agentArgs !== undefined ? { agentArgsOverride: agentArgs } : {}), ...(startupPlan.sessionOptions ? { sessionOptions: startupPlan.sessionOptions } : {}), ...(startupPlan.startupCommandDelivery diff --git a/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts b/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts index 13ec579a2f7..843cfa8e607 100644 --- a/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts +++ b/src/renderer/src/runtime/web-runtime-session-terminal-host-authority.test.ts @@ -325,6 +325,65 @@ describe('createWebRuntimeSessionTerminal', () => { ) }) + it('keeps an explicit empty agentArgs in the structured create payload', async () => { + // Why (#15373): Manual is stored as an empty string; dropping it here would let the host re-resolve YOLO. + const runtimeCall = vi.fn(async (request: { method: string }) => { + if (request.method === 'status.get') { + return { + id: 'status', + ok: true, + result: { + runtimeId: 'runtime-1', + graphStatus: 'ready', + runtimeProtocolVersion: 3, + minCompatibleRuntimeClientVersion: 2, + capabilities: ['agent-session.host-authority.v1'] + } + } + } + if (request.method === 'terminal.createAgentSession') { + return { + id: 'agent-session', + ok: true, + result: { + terminal: { + handle: 'term-manual', + worktreeId: WORKTREE_ID, + tabId: 'host-tab-manual', + paneKey: `host-tab-manual:${FOCUS_LEAF_ID}` + }, + disposition: 'created' + } + } + } + return { id: 'list', ok: true, result: makeSnapshot() } + }) + vi.stubGlobal('window', { + api: { runtimeEnvironments: { call: runtimeCall } } + }) + + await expect( + createWebRuntimeSessionTerminal({ + worktreeId: WORKTREE_ID, + agent: 'claude', + agentSessionKind: 'fresh', + agentArgs: '', + activate: true + }) + ).resolves.toEqual({ status: 'created' }) + + const createRequest = runtimeCall.mock.calls.find( + ([request]) => request.method === 'terminal.createAgentSession' + )?.[0] as { params: Record } | undefined + expect(createRequest?.params).toEqual({ + clientOperationId: expect.stringMatching(/^\d{13}-[0-9a-f]{32}$/), + worktree: `id:${WORKTREE_ID}`, + agent: 'claude', + agentArgs: '', + presentation: 'background' + }) + }) + it.each(['session.tabs.move', 'session.tabs.list'] as const)( 'treats %s failure after host creation as accepted so callers do not duplicate the agent', async (failedMethod) => {