mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 20:06:06 +00:00
fix(agents): clamp subagent spawn thinking overrides
Fixes #92412. Subagent spawns that request an unsupported explicit thinking level now clamp through the existing provider/model thinking fallback instead of hard-failing after the orchestrator has already received an accepted ack. The exception is limited to trusted subagent spawn runs by requiring both the subagent lane and a subagent-shaped session key, so interactive and non-subagent explicit `--thinking` validation still fails loudly. Verification: - `node scripts/run-vitest.mjs src/agents/agent-command.live-model-switch.test.ts --maxWorkers=1` - `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main` - Testbox-through-Crabbox `tbx_01kv2wt0nqavsmnvzzzy2antrc`: `OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed` - GitHub PR checks clean on `c71186863337d9dfb9a18e5349ebef634a7d5ccd`
This commit is contained in:
@@ -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"));
|
||||
|
||||
@@ -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)}.`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user