fix(agents): suppress delivered messaging fallback

This commit is contained in:
Peter Steinberger
2026-04-25 06:55:42 +01:00
parent 47a4124dc3
commit 18ffa81564
3 changed files with 39 additions and 7 deletions

View File

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

View File

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

View File

@@ -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({