fix: suppress commentary fallback payloads

This commit is contained in:
Peter Steinberger
2026-04-12 08:45:08 +01:00
parent eba501c303
commit 9eed092baa
3 changed files with 64 additions and 3 deletions

View File

@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
- Doctor: warn when on-disk agent directories still exist under `~/.openclaw/agents/<id>/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

View File

@@ -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<typeof buildPayloads>[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" },

View File

@@ -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;