diff --git a/src/agents/pi-embedded-utils.test.ts b/src/agents/pi-embedded-utils.test.ts index ab84a375d94..18b2bd6a7bc 100644 --- a/src/agents/pi-embedded-utils.test.ts +++ b/src/agents/pi-embedded-utils.test.ts @@ -329,6 +329,24 @@ Arguments: { "command": "git status", "timeout": 120000 }`, expect(result).toBe(""); }); + it("strips leaked assistant function-call preludes while preserving user text", () => { + const msg = makeAssistantMessage({ + role: "assistant", + content: [ + { + type: "text", + text: 'assistant function callrecipient{The task "setup openclaw" has been successfully added to Things. Let me know if you need further assistance!', + }, + ], + timestamp: Date.now(), + }); + + const result = extractAssistantText(msg); + expect(result).toBe( + 'The task "setup openclaw" has been successfully added to Things. Let me know if you need further assistance!', + ); + }); + it("strips multiple downgraded tool calls", () => { const msg = makeAssistantMessage({ role: "assistant", diff --git a/src/agents/pi-embedded-utils.ts b/src/agents/pi-embedded-utils.ts index 375df11654d..f021b9f3ecb 100644 --- a/src/agents/pi-embedded-utils.ts +++ b/src/agents/pi-embedded-utils.ts @@ -69,9 +69,8 @@ export function stripDowngradedToolCallText(text: string): string { if (!text) { return text; } - if (!/\[Tool (?:Call|Result)/i.test(text) && !/\[Historical context/i.test(text)) { - return text; - } + const stripLeakedFunctionCallPrelude = (input: string): string => + input.replace(/^\s*assistant\s+function\s+call\s*([A-Za-z0-9_.:/-]+)\s*\{\s*/i, ""); const consumeJsonish = ( input: string, @@ -157,6 +156,11 @@ export function stripDowngradedToolCallText(text: string): string { return end; }; + let cleaned = stripLeakedFunctionCallPrelude(text); + if (!/\[Tool (?:Call|Result)/i.test(cleaned) && !/\[Historical context/i.test(cleaned)) { + return cleaned.trim(); + } + const stripToolCalls = (input: string): string => { const markerRe = /\[Tool Call:[^\]]*\]/gi; let result = ""; @@ -213,7 +217,7 @@ export function stripDowngradedToolCallText(text: string): string { }; // Remove [Tool Call: name (ID: ...)] blocks and their Arguments. - let cleaned = stripToolCalls(text); + cleaned = stripToolCalls(cleaned); // Remove [Tool Result for ID ...] blocks and their content. cleaned = cleaned.replace(/\[Tool Result for ID[^\]]*\]\n?[\s\S]*?(?=\n*\[Tool |\n*$)/gi, "");