From 9eed092baa50837cfd80366309b3ad9cf4c8aaee Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 12 Apr 2026 08:45:08 +0100 Subject: [PATCH] fix: suppress commentary fallback payloads --- CHANGELOG.md | 1 + .../pi-embedded-runner/run/payloads.test.ts | 60 ++++++++++++++++++- src/agents/pi-embedded-runner/run/payloads.ts | 6 +- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52c66f3880..e5c23359689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai - Doctor: warn when on-disk agent directories still exist under `~/.openclaw/agents//agent` but the matching `agents.list[]` entries are missing from config. (#65113) Thanks @neeravmakwana. - Telegram: route approval button callback queries onto a separate sequentializer lane so plugin approval clicks can resolve immediately instead of deadlocking behind the blocked agent turn. (#64979) Thanks @nk3750. - Agents/Anthropic replay: preserve immutable signed-thinking replay safety across stored and live reruns, keep non-thinking embedded `tool_result` user blocks intact, and drop conflicting preserved tool IDs before validation so retries stop degrading into omitted tool calls. (#65126) Thanks @shakkernerd. +- Telegram/direct sessions: keep commentary-only assistant fallback payloads out of visible direct delivery, so Codex planning chatter cannot leak into Telegram DMs when a run has no `final_answer` text. (#65112) Thanks @vincentkoc. ## 2026.4.11 diff --git a/src/agents/pi-embedded-runner/run/payloads.test.ts b/src/agents/pi-embedded-runner/run/payloads.test.ts index a38ea9ed64d..c2db4ac95be 100644 --- a/src/agents/pi-embedded-runner/run/payloads.test.ts +++ b/src/agents/pi-embedded-runner/run/payloads.test.ts @@ -1,5 +1,10 @@ +import type { AssistantMessage } from "@mariozechner/pi-ai"; import { describe, expect, it } from "vitest"; -import { buildPayloads, expectSingleToolErrorPayload } from "./payloads.test-helpers.js"; +import { + buildPayloads, + expectSinglePayloadText, + expectSingleToolErrorPayload, +} from "./payloads.test-helpers.js"; describe("buildEmbeddedRunPayloads tool-error warnings", () => { function expectNoPayloads(params: Parameters[0]) { @@ -7,6 +12,59 @@ describe("buildEmbeddedRunPayloads tool-error warnings", () => { expect(payloads).toHaveLength(0); } + it("does not fall back to commentary-only assistant text when streamed text was suppressed", () => { + const payloads = buildPayloads({ + lastAssistant: { + role: "assistant", + stopReason: "toolUse", + content: [ + { + type: "text", + text: "Need update cron messages to use finalBrief/briefPath.", + textSignature: JSON.stringify({ + v: 1, + id: "item_commentary", + phase: "commentary", + }), + }, + ], + } as AssistantMessage, + }); + + expect(payloads).toEqual([]); + }); + + it("falls back to final-answer assistant text when streamed text is unavailable", () => { + const payloads = buildPayloads({ + lastAssistant: { + role: "assistant", + stopReason: "stop", + content: [ + { + type: "text", + text: "Need inspect.", + textSignature: JSON.stringify({ + v: 1, + id: "item_commentary", + phase: "commentary", + }), + }, + { + type: "text", + text: "Done.", + textSignature: JSON.stringify({ + v: 1, + id: "item_final", + phase: "final_answer", + }), + }, + ], + } as AssistantMessage, + }); + + expectSinglePayloadText(payloads, "Done."); + }); + it("suppresses exec tool errors when verbose mode is off", () => { expectNoPayloads({ lastToolError: { toolName: "exec", error: "command failed" }, diff --git a/src/agents/pi-embedded-runner/run/payloads.ts b/src/agents/pi-embedded-runner/run/payloads.ts index 260ab37fb4c..6a32bd03129 100644 --- a/src/agents/pi-embedded-runner/run/payloads.ts +++ b/src/agents/pi-embedded-runner/run/payloads.ts @@ -20,8 +20,8 @@ import { } from "../../pi-embedded-helpers.js"; import type { ToolResultFormat } from "../../pi-embedded-subscribe.shared-types.js"; import { - extractAssistantText, extractAssistantThinking, + extractAssistantVisibleText, formatReasoningMessage, } from "../../pi-embedded-utils.js"; import { isExecLikeToolName, type ToolErrorSummary } from "../../tool-error-summary.js"; @@ -217,7 +217,9 @@ export function buildEmbeddedRunPayloads(params: { replyItems.push({ text: reasoningText, isReasoning: true }); } - const fallbackAnswerText = params.lastAssistant ? extractAssistantText(params.lastAssistant) : ""; + const fallbackAnswerText = params.lastAssistant + ? extractAssistantVisibleText(params.lastAssistant) + : ""; const shouldSuppressRawErrorText = (text: string) => { if (!lastAssistantErrored) { return false;