diff --git a/CHANGELOG.md b/CHANGELOG.md index f6350be7ffb..e2b39d81d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -357,6 +357,7 @@ Docs: https://docs.openclaw.ai - Agents/failover: rotate auth profiles before deferred cooldown marking on rate-limit failures, so file-lock contention cannot stall profile failover. Fixes #57281. (#57283) Thanks @jeremyknows. - Gateway/sessions: when `session.dmScope: "main"` is configured, route a bare webchat `/new` against the agent's main session (`sessions.create` with `emitCommandHooks=true`) to an in-place reset instead of creating a parallel `dashboard:` child, matching `/new` behavior on Telegram/Discord. Fixes #77434. (#71170) Thanks @statxc. - Scripts/UI/Windows: launch `.cmd` and `.bat` UI runners through the shared cmd.exe escaping path with shell mode disabled, avoiding Node.js v24 DEP0190 warnings while preserving argument boundaries. (#62910) Thanks @nandanadileep. +- Agents/CLI runner: disable supervisor stdout/stderr capture for prepared CLI runs while keeping bounded diagnostics and incremental JSONL output parsing, preventing long CLI output from being retained in memory. (#79617) Thanks @samzong. - Telegram: treat a DM binding that carries the chat id in both `conversationId` and `parentConversationId` as a direct conversation instead of a topic, so reverse delivery for Telegram DMs is not misrouted through a topic-shaped target. (#79700) Thanks @TSHOGX. ## 2026.5.7 diff --git a/src/agents/cli-runner.spawn.test.ts b/src/agents/cli-runner.spawn.test.ts index 4a9e3129884..6d4b8d23890 100644 --- a/src/agents/cli-runner.spawn.test.ts +++ b/src/agents/cli-runner.spawn.test.ts @@ -768,27 +768,19 @@ describe("runCliAgent spawn path", () => { event: { type: "content_block_delta", delta: { type: "text_delta", text: " world" } }, }) + "\n", ); + input.onStdout?.( + JSON.stringify({ + type: "result", + session_id: "session-123", + result: "Hello world", + }) + "\n", + ); return createManagedRun({ reason: "exit", exitCode: 0, exitSignal: null, durationMs: 50, - stdout: [ - JSON.stringify({ type: "init", session_id: "session-123" }), - JSON.stringify({ - type: "stream_event", - event: { type: "content_block_delta", delta: { type: "text_delta", text: "Hello" } }, - }), - JSON.stringify({ - type: "stream_event", - event: { type: "content_block_delta", delta: { type: "text_delta", text: " world" } }, - }), - JSON.stringify({ - type: "result", - session_id: "session-123", - result: "Hello world", - }), - ].join("\n"), + stdout: "", stderr: "", timedOut: false, noOutputTimedOut: false, diff --git a/src/agents/cli-runner/execute.supervisor-capture.test.ts b/src/agents/cli-runner/execute.supervisor-capture.test.ts index bd302267f34..2c0ccc94452 100644 --- a/src/agents/cli-runner/execute.supervisor-capture.test.ts +++ b/src/agents/cli-runner/execute.supervisor-capture.test.ts @@ -150,6 +150,52 @@ describe("executePreparedCliRun supervisor output capture", () => { expect(result.sessionId).toBe("session-jsonl-large"); }); + it("parses oversized resume JSONL output from the effective resume output mode", async () => { + const largeToolEvent = `${JSON.stringify({ + type: "stream_event", + event: { + type: "content_block_delta", + delta: { type: "tool_delta", text: "x".repeat(2 * 1024 * 1024) }, + }, + })}\n`; + const resultEvent = `${JSON.stringify({ + type: "result", + session_id: "resume-jsonl-session", + result: "resumed answer", + })}\n`; + const context = buildPreparedCliRunContext({ + output: "text", + provider: "resume-jsonl-cli", + }); + Object.assign(context.preparedBackend.backend, { + jsonlDialect: "claude-stream-json" as const, + resumeArgs: ["resume", "{sessionId}"], + resumeOutput: "jsonl" as const, + sessionMode: "existing" as const, + }); + + supervisorSpawnMock.mockImplementationOnce(async (...args: unknown[]) => { + const input = args[0] as SupervisorSpawnInput; + input.onStdout?.(largeToolEvent); + input.onStdout?.(resultEvent); + return createManagedRun({ + reason: "exit", + exitCode: 0, + exitSignal: null, + durationMs: 50, + stdout: input.captureOutput === false ? "" : `${largeToolEvent}${resultEvent}`, + stderr: "", + timedOut: false, + noOutputTimedOut: false, + }); + }); + + const result = await executePreparedCliRun(context, "resume-jsonl-session"); + + expect(result.text).toBe("resumed answer"); + expect(result.sessionId).toBe("resume-jsonl-session"); + }); + it("classifies failed stdout from the retained parse buffer before the diagnostic tail", async () => { const errorPrefix = `${JSON.stringify({ type: "result", diff --git a/src/agents/cli-runner/execute.ts b/src/agents/cli-runner/execute.ts index 0f07f7949b1..777cc7c8325 100644 --- a/src/agents/cli-runner/execute.ts +++ b/src/agents/cli-runner/execute.ts @@ -442,7 +442,8 @@ export async function executePreparedCliRun( useResume, trigger: params.trigger, }); - const hasJsonlOutput = backend.output === "jsonl"; + const outputMode = useResume ? (backend.resumeOutput ?? backend.output) : backend.output; + const hasJsonlOutput = outputMode === "jsonl"; if (shouldUseClaudeLiveSession(context)) { if (!hasJsonlOutput) { throw new Error("Claude live session requires JSONL streaming parser"); @@ -688,7 +689,6 @@ export async function executePreparedCliRun( }); } - const outputMode = useResume ? (backend.resumeOutput ?? backend.output) : backend.output; const streamedJsonlOutput = outputMode === "jsonl" ? (streamingParser?.getOutput() ?? null) : null;