From 4932ed84a70e83f38cb370db84ef880076995cc5 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 21 Aug 2026 22:59:59 -0700 Subject: [PATCH] test(runtime): pin the empty agentArgs override at the wire boundary The PR's launch tests mock web-runtime-session wholesale, so nothing caught a future `args.agentArgs || args.launchConfig?.agentArgs` refactor silently dropping Manual's empty string. Assert the exact terminal.createAgentSession params instead. Also trims the new WHY comments to repo length and notes why the queued local startup keeps using the raw param. --- ...aunch-agent-in-new-tab-web-runtime.test.ts | 3 +- .../src/lib/launch-agent-in-new-tab.ts | 8 +-- ...me-session-terminal-host-authority.test.ts | 59 +++++++++++++++++++ 3 files changed, 63 insertions(+), 7 deletions(-) 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) => {