From 3d88e8f5ce41648da855e68e5b32631cabaa792a Mon Sep 17 00:00:00 2001 From: "njzjz-bot (driven by OpenClaw (model: gpt-5.2))[bot]" <48687836+njzjz-bot@users.noreply.github.com> Date: Sun, 15 Mar 2026 10:19:13 +0000 Subject: [PATCH] fix(sessions): ignore streamTo for runtime=subagent \nThis prevents schema-following models from breaking subagent spawns when the tool schema includes streamTo.\n\nAuthored by OpenClaw (model: gpt-5.2) --- src/agents/tools/sessions-spawn-tool.test.ts | 10 ++++------ src/agents/tools/sessions-spawn-tool.ts | 15 +++++++-------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/agents/tools/sessions-spawn-tool.test.ts b/src/agents/tools/sessions-spawn-tool.test.ts index 4fe106a7ebd..ab9c94a1693 100644 --- a/src/agents/tools/sessions-spawn-tool.test.ts +++ b/src/agents/tools/sessions-spawn-tool.test.ts @@ -224,24 +224,22 @@ describe("sessions_spawn tool", () => { expect(hoisted.spawnSubagentDirectMock).not.toHaveBeenCalled(); }); - it('rejects streamTo when runtime is not "acp"', async () => { + it('ignores streamTo when runtime is not "acp" (schema-following models may include it)', async () => { const tool = createSessionsSpawnTool({ agentSessionKey: "agent:main:main", }); - const result = await tool.execute("call-3b", { + const result = await tool.execute("call-streamto-ignore", { runtime: "subagent", task: "analyze file", streamTo: "parent", }); expect(result.details).toMatchObject({ - status: "error", + status: "accepted", }); - const details = result.details as { error?: string }; - expect(details.error).toContain("streamTo is only supported for runtime=acp"); + expect(hoisted.spawnSubagentDirectMock).toHaveBeenCalled(); expect(hoisted.spawnAcpDirectMock).not.toHaveBeenCalled(); - expect(hoisted.spawnSubagentDirectMock).not.toHaveBeenCalled(); }); it("keeps attachment content schema unconstrained for llama.cpp grammar safety", () => { diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index b735084d2b0..833e2bf0249 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -41,7 +41,9 @@ const SessionsSpawnToolSchema = Type.Object({ mode: optionalStringEnum(SUBAGENT_SPAWN_MODES), cleanup: optionalStringEnum(["delete", "keep"] as const), sandbox: optionalStringEnum(SESSIONS_SPAWN_SANDBOX_MODES), - streamTo: optionalStringEnum(ACP_SPAWN_STREAM_TARGETS), + streamTo: optionalStringEnum(ACP_SPAWN_STREAM_TARGETS, { + description: 'ACP-only. Ignored when runtime != "acp".', + }), // Inline attachments (snapshot-by-value). // NOTE: Attachment contents are redacted from transcript persistence by sanitizeToolCallInputs. @@ -105,7 +107,8 @@ export function createSessionsSpawnTool( const cleanup = params.cleanup === "keep" || params.cleanup === "delete" ? params.cleanup : "keep"; const sandbox = params.sandbox === "require" ? "require" : "inherit"; - const streamTo = params.streamTo === "parent" ? "parent" : undefined; + // Only relevant for ACP. For runtime=subagent, ignore it (schema-following models may still send it). + const streamTo = runtime === "acp" && params.streamTo === "parent" ? "parent" : undefined; // Back-compat: older callers used timeoutSeconds for this tool. const timeoutSecondsCandidate = typeof params.runTimeoutSeconds === "number" @@ -127,12 +130,8 @@ export function createSessionsSpawnTool( }>) : undefined; - if (streamTo && runtime !== "acp") { - return jsonResult({ - status: "error", - error: `streamTo is only supported for runtime=acp; got runtime=${runtime}`, - }); - } + // NOTE: streamTo is ACP-only. For runtime=subagent we intentionally ignore it, + // because schema-following models may include it if it is present in the tool schema. if (resumeSessionId && runtime !== "acp") { return jsonResult({