From 1f06dbd04ceddfc98980886d88173b94487d12e6 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:35:03 -0700 Subject: [PATCH] fix(agent): recover blank streamed replies from final answer --- .../pi-embedded-runner/run/payloads.test.ts | 23 +++++++++++++++++++ src/agents/pi-embedded-runner/run/payloads.ts | 10 ++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/agents/pi-embedded-runner/run/payloads.test.ts b/src/agents/pi-embedded-runner/run/payloads.test.ts index 0674e7e5116..a414add27ce 100644 --- a/src/agents/pi-embedded-runner/run/payloads.test.ts +++ b/src/agents/pi-embedded-runner/run/payloads.test.ts @@ -65,6 +65,29 @@ describe("buildEmbeddedRunPayloads tool-error warnings", () => { expectSinglePayloadText(payloads, "Done."); }); + it("falls back to final-answer assistant text when streamed text only contains blanks", () => { + const payloads = buildPayloads({ + assistantTexts: [" "], + lastAssistant: { + role: "assistant", + stopReason: "stop", + content: [ + { + type: "text", + text: "Fixed.", + textSignature: JSON.stringify({ + v: 1, + id: "item_final", + phase: "final_answer", + }), + }, + ], + } as AssistantMessage, + }); + + expectSinglePayloadText(payloads, "Fixed."); + }); + 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 d0ecf81ae90..2d68b3b23dc 100644 --- a/src/agents/pi-embedded-runner/run/payloads.ts +++ b/src/agents/pi-embedded-runner/run/payloads.ts @@ -293,20 +293,22 @@ export function buildEmbeddedRunPayloads(params: { const parsed = parseReplyDirectives(text); return (parsed.mediaUrls?.length ?? 0) > 0 || parsed.audioAsVoice; }); - const normalizedAssistantTexts = normalizeTextForComparison(params.assistantTexts.join("\n\n")); + const nonEmptyAssistantTexts = params.assistantTexts.filter((text) => text.trim().length > 0); + const normalizedAssistantTexts = normalizeTextForComparison(nonEmptyAssistantTexts.join("\n\n")); const normalizedRawAnswerText = normalizeTextForComparison(rawAnswerDirectiveState?.text ?? ""); const shouldPreferRawAnswerText = rawAnswerHasMedia && - (!params.assistantTexts.length || + (!nonEmptyAssistantTexts.length || (!assistantTextsHaveMedia && normalizedAssistantTexts.length > 0 && normalizedAssistantTexts === normalizedRawAnswerText)); + const hasAssistantTextPayload = nonEmptyAssistantTexts.length > 0; const answerTexts = suppressAssistantArtifacts ? [] : (shouldPreferRawAnswerText && fallbackRawAnswerText ? [fallbackRawAnswerText] - : params.assistantTexts.length - ? params.assistantTexts + : hasAssistantTextPayload + ? nonEmptyAssistantTexts : fallbackAnswerText ? [fallbackAnswerText] : []