diff --git a/src/agents/agent-command.live-model-switch.test.ts b/src/agents/agent-command.live-model-switch.test.ts index f2bb4a15de5e..40caa771afdd 100644 --- a/src/agents/agent-command.live-model-switch.test.ts +++ b/src/agents/agent-command.live-model-switch.test.ts @@ -328,7 +328,6 @@ vi.mock("../routing/session-key.js", async () => { ); return { ...actual, - isSubagentSessionKey: () => false, normalizeAgentId: (id: string) => id, normalizeMainKey: (key?: string | null) => key?.trim() || "main", }; @@ -1340,6 +1339,75 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { }); }); + it("clamps unsupported explicit thinking for subagent spawns instead of throwing", async () => { + setupSingleAttemptFallback(); + state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("anthropic", "claude-fable-5")); + state.resolvedSessionKeyMock = "agent:planner:subagent:00000000-0000-4000-8000-000000000000"; + state.isThinkingLevelSupportedMock.mockReturnValue(false); + state.resolveSupportedThinkingLevelMock.mockReturnValue("high"); + + await agentCommand({ + message: "hello", + sessionKey: state.resolvedSessionKeyMock, + thinking: "xhigh", + lane: "subagent", + }); + + expect(state.resolveSupportedThinkingLevelMock).toHaveBeenCalled(); + expectRecordFields(mockCallArg(state.runAgentAttemptMock), { + resolvedThinkLevel: "high", + }); + }); + + it("rejects unsupported explicit thinking for interactive subagent-key runs", async () => { + setupSingleAttemptFallback(); + state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("anthropic", "claude-fable-5")); + state.resolvedSessionKeyMock = "agent:planner:subagent:00000000-0000-4000-8000-000000000000"; + state.isThinkingLevelSupportedMock.mockReturnValue(false); + + await expect( + agentCommand({ + message: "hello", + sessionKey: state.resolvedSessionKeyMock, + thinking: "xhigh", + }), + ).rejects.toThrow(/is not supported/u); + expect(state.runAgentAttemptMock).not.toHaveBeenCalled(); + }); + + it("rejects unsupported explicit thinking for non-subagent sessions on the subagent lane", async () => { + setupSingleAttemptFallback(); + state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("anthropic", "claude-fable-5")); + state.resolvedSessionKeyMock = "agent:main:main"; + state.isThinkingLevelSupportedMock.mockReturnValue(false); + + await expect( + agentCommand({ + message: "hello", + sessionKey: state.resolvedSessionKeyMock, + thinking: "xhigh", + lane: "subagent", + }), + ).rejects.toThrow(/is not supported/u); + expect(state.runAgentAttemptMock).not.toHaveBeenCalled(); + }); + + it("rejects unsupported explicit thinking for direct interactive runs", async () => { + setupSingleAttemptFallback(); + state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("anthropic", "claude-fable-5")); + state.resolvedSessionKeyMock = "agent:main:main"; + state.isThinkingLevelSupportedMock.mockReturnValue(false); + + await expect( + agentCommand({ + message: "hello", + to: "+1234567890", + thinking: "xhigh", + }), + ).rejects.toThrow(/is not supported/u); + expect(state.runAgentAttemptMock).not.toHaveBeenCalled(); + }); + it("skips the initial session touch after gateway ingress already persisted activity", async () => { setupSingleAttemptFallback(); state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("openai", "gpt-5.4")); diff --git a/src/agents/agent-command.ts b/src/agents/agent-command.ts index 70b8b494e39e..c87697470502 100644 --- a/src/agents/agent-command.ts +++ b/src/agents/agent-command.ts @@ -819,6 +819,7 @@ async function prepareAgentCommandExecution(opts: AgentCommandOpts, runtime: Run manifestMetadataSnapshot, modelManifestContext, runId, + isSubagentLane, acpManager, acpResolution, }; @@ -859,6 +860,7 @@ async function agentCommandInternal( cwd, agentDir, runId, + isSubagentLane, acpManager, acpResolution, pluginsEnabled, @@ -1597,7 +1599,11 @@ async function agentCommandInternal( }) ) { const explicitThink = Boolean(thinkOnce || thinkOverride); - if (explicitThink) { + const isSubagentSpawnRun = isSubagentLane && isSubagentSessionKey(sessionKey); + // Spawn-lane subagents are fire-and-forget; the orchestrator already got + // an "accepted" ack, so throwing here strands the run and half-fails fan-outs. + // Clamp like the embedded runner; interactive --thinking keeps the throw. + if (explicitThink && !isSubagentSpawnRun) { throw new Error( `Thinking level "${resolvedThinkLevel}" is not supported for ${provider}/${model}. Use one of: ${formatThinkingLevels(provider, model, ", ", thinkingCatalog)}.`, );