diff --git a/CHANGELOG.md b/CHANGELOG.md index 943dc80d273..244db768b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Signal: preserve sender attachment filenames and resolve missing MIME types from those filenames, so Linux `signal-cli` voice notes without `contentType` still enter audio transcription. Fixes #48614. Thanks @mindfury. +- Telegram/agents: suppress the phantom "Agent couldn't generate a response" fallback after a reply was already delivered through the messaging tool on clean non-error terminal turns. (#70623) Thanks @chinar-amrutkar. - Dashboard/security: avoid writing tokenized Control UI URLs or SSH hints to runtime logs, keeping gateway bearer fragments out of console-captured logs readable through `logs.tail`. (#70029) Thanks @Ziy1-Tan. - Providers/OpenRouter: treat DeepSeek refs as cache-TTL eligible without injecting Anthropic cache-control markers, aligning context pruning with OpenRouter-managed prompt caching. (#51983) Thanks @QuinnH496. - Discord/cron: deliver text-only isolated cron and heartbeat announce output from the canonical final assistant text once, avoiding duplicate Discord posts when streamed block payloads and the final answer contain the same content. Fixes #71406. Thanks @alexgross21. diff --git a/src/agents/pi-embedded-runner/run.incomplete-turn.test.ts b/src/agents/pi-embedded-runner/run.incomplete-turn.test.ts index 7cbf577b7c3..f308e388780 100644 --- a/src/agents/pi-embedded-runner/run.incomplete-turn.test.ts +++ b/src/agents/pi-embedded-runner/run.incomplete-turn.test.ts @@ -822,6 +822,33 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => { expect(incompleteTurnText).toBeNull(); }); + it("suppresses the incomplete-turn warning when a messaging tool delivered before end_turn", () => { + const incompleteTurnText = resolveIncompleteTurnPayloadText({ + payloadCount: 0, + aborted: false, + timedOut: false, + attempt: makeAttemptResult({ + assistantTexts: [], + didSendViaMessagingTool: true, + lastAssistant: { + role: "assistant", + stopReason: "end_turn", + provider: "google", + model: "gemini-2.5-pro", + content: [ + { + type: "thinking", + thinking: "internal reasoning", + thinkingSignature: JSON.stringify({ id: "rs_messaging_end_turn", type: "reasoning" }), + }, + ], + } as unknown as EmbeddedRunAttemptResult["lastAssistant"], + }), + }); + + expect(incompleteTurnText).toBeNull(); + }); + it("still surfaces the incomplete-turn warning after a messaging tool when the provider signalled an error", () => { const incompleteTurnText = resolveIncompleteTurnPayloadText({ payloadCount: 0, diff --git a/src/agents/pi-embedded-runner/run/incomplete-turn.ts b/src/agents/pi-embedded-runner/run/incomplete-turn.ts index 0e8dba07f82..1050a5bfa1c 100644 --- a/src/agents/pi-embedded-runner/run/incomplete-turn.ts +++ b/src/agents/pi-embedded-runner/run/incomplete-turn.ts @@ -191,13 +191,17 @@ export function resolveIncompleteTurnPayloadText(params: { const stopReason = params.attempt.lastAssistant?.stopReason; // If the assistant already delivered user-visible content via a messaging - // tool during this turn and ended cleanly (stopReason=stop), do not surface - // an incomplete-turn warning. The user has received the reply; a follow-up - // "couldn't generate a response" bubble is a false positive. Provider-side - // failures (stopReason=error, toolUse interruption) still fall through to - // the normal incomplete-turn paths below; tool-error cases are already - // handled by the lastToolError early return above. - if (params.attempt.didSendViaMessagingTool && stopReason === "stop") { + // tool during this turn and did not end in a hard error/interrupted tool-use + // state, do not surface an incomplete-turn warning. The user has received the + // reply; a follow-up "couldn't generate a response" bubble is a false positive. + // Provider-side failures and interrupted tool-use still fall through to the + // normal incomplete-turn paths below; tool-error cases are already handled by + // the lastToolError early return above. + if ( + params.attempt.didSendViaMessagingTool && + stopReason !== "error" && + stopReason !== "toolUse" + ) { return null; } const incompleteTerminalAssistant = isIncompleteTerminalAssistantTurn({