fix(telegram): move preview-streamed dedup to channel layer (#80520)#83161
fix(telegram): move preview-streamed dedup to channel layer (#80520)#83161Elarwei001 wants to merge 3 commits into
Conversation
|
Codex review: needs changes before merge. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. The related issue and PR body give a concrete live Telegram path, and the remaining review finding is source-reproducible from the dedupe branch using lastPartialText without flushing or checking lastDeliveredText. PR rating Rank-up moves:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. PR egg What is this egg doing here?
Real behavior proof Mantis proof suggestion Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge Security Review findings
Review detailsBest possible solution: Keep the dedupe in the Telegram channel layer, but only suppress the final after the answer preview stream is flushed and the delivered preview text is known to match; otherwise fall through to normal final delivery. Do we have a high-confidence way to reproduce the issue? Yes. The related issue and PR body give a concrete live Telegram path, and the remaining review finding is source-reproducible from the dedupe branch using lastPartialText without flushing or checking lastDeliveredText. Is this the best way to solve the issue? No, not yet. Moving dedupe to Telegram is the right owner boundary, but the implementation needs to honor the existing stream finalization contract before suppressing the durable final. Label justifications:
Full review comments:
Overall correctness: patch is incorrect Acceptance criteria:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against f07c87405c30. |
5565c46 to
46f7783
Compare
|
Addressed the latest ClawSweeper finding.
Local verification: @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
…enclaw#80520) The previous core-layer dedup (PR openclaw#82625 / commit bd51d8f, reverted in the preceding commit) suppressed preview-streamed final payloads in src/auto-reply/reply/agent-runner-payloads.ts but had no path to also signal lane.finalized on the channel side. When every final was suppressed (e.g., embedded harness + reasoning-model paragraph-split delivery), the Telegram lane stayed unfinalized and the end-of-turn clearUnfinalizedStream cleanup deleted the preview message — users saw the reply briefly and then watched it disappear (openclaw#80520). This change moves the dedup to the channel layer so the "skip duplicate send" and "mark lane finalized" steps happen atomically: - Adds extensions/telegram/src/preview-dedup.ts with the same normalization and per-block dedup semantics that the previous core-layer suppressPreviewStreamedPayloads used. Helpers are unit-tested in preview-dedup.test.ts (13 cases). - In extensions/telegram/src/bot-message-dispatch.ts, the dispatcher's deliver callback now checks each text-only final against the answer lane's lastPartialText (whole + per-block normalized). On match, the lane is marked finalized in the same step that skips the send, keeping preview/finalize state coupled. - Guards the dedup with a previewMessageId check: only suppresses when the preview actually has an established Telegram message. If the preview never landed, the final is still delivered through sendPayload so the user receives something. This avoids an additional latent issue in the core-layer dedup, which had no channel state at all. - Payloads with media keep their existing path through lane-delivery-text-deliverer.ts; the hasMedia branch there already strips duplicate captions while keeping the media. Adds 4 integration tests in bot-message-dispatch.test.ts covering the dedup path, the previewMessageId guard, multi-block deliveries, and error preservation. No existing tests modified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46f7783 to
630ac27
Compare
Summary
Fixes the Telegram "reply appears briefly then disappears" regression reported in #80520. The previous core-layer dedup (#82625 / commit
bd51d8f2dd) suppressed final payloads matching preview-streamed text but had no path to also signal the channel'slane.finalizedstate. When every final was suppressed (e.g., embedded harness + reasoning-model paragraph-split deliveries), the lane stayed unfinalized and the end-of-turn cleanup deleted the preview message.This PR moves the dedup from the channel-agnostic core to the Telegram channel layer so the "skip duplicate send" and "mark lane finalized" steps happen atomically.
Commits
Revert "Deduplicate preview-streamed final replies (Deduplicate Telegram partial preview final replies #82625)" — undoes the previous dedup, which could not observe channel state.
fix(telegram): move preview-streamed final dedup to channel layer (Telegram messages silently dropped, no sendMessage logged #80520) — equivalent dedup at the channel layer:
extensions/telegram/src/preview-dedup.tshelpers (whole + per-block normalization, exact-match check). 13 unit tests inpreview-dedup.test.ts.bot-message-dispatch.ts, the dispatcher'sdelivercallback checks each text-only final against the answer lane'slastPartialText. On match:answerLane.finalized = true,deliveryState.markDelivered(), skip the send.previewMessageIdcheck — only fires when the preview actually has an established Telegram message. If the preview never landed (e.g., transient failure), the final still flows throughsendPayloadso the user receives something. The previous core-layer dedup had no access to this state.lane-delivery-text-deliverer.ts'shasMediabranch, which already handles caption stripping.Real behavior proof
Behavior addressed: Telegram "reply appears briefly then disappears" regression for embedded-harness + reasoning-model +
streaming.mode=partialpaths (#80520).Real environment tested: macOS, managed gateway service running this branch's
dist/. Live Telegram bot configured withmodel.primary = zai/glm-5.1(reasoning model) via the embedded harness withstreaming.mode=partial.Exact steps or command run after this patch:
git checkout dev/telegram-preview-dedup && pnpm build(build the patcheddist/).openclaw gateway stop && openclaw gateway start(run a live gateway on the patched build).pnpm test extensions/telegramagainst the patched branch — 118 files, 1735 tests pass, including 4 new integration cases for the channel-layer dedup, thepreviewMessageIdguard, multi-block deliveries, and error preservation.Evidence after fix:
Redacted live
gateway.logexcerpt covering the 80-char inbound prompt and the subsequent long-reply turn on the live gateway (chat id and bot handle redacted as<chat>and<bot>; nothing else inside this turn was logged):The absence of any outbound
[telegram]line is the load-bearing observation from the live gateway: the 1297-char, 10-paragraph reply was delivered entirely through the existing preview-streamingeditMessageTextpath on the already-established preview message id. Onmainwithout this PR, the previoussuppressPreviewStreamedPayloadswould have dropped each paragraph's final payload at the runner layer andclearUnfinalizedStreamwould have then deleted the preview message — the regression described in #80520.Redacted live session jsonl excerpt for the same turn (assistant payload metadata as written by the running gateway; reply text and tool inputs/outputs omitted):
{ "type": "message", "message": { "role": "assistant", "provider": "zai", "model": "glm-5.1", "api": "openai-completions", "stopReason": "stop", "usage": { "input": 10445, "output": 585, "cacheRead": 130176, "totalTokens": 141206, "cost": { "input": 0.012534, "output": 0.00234, "cacheRead": 0.031242, "total": 0.046116 } }, "content": [ { "type": "text", "text": "<redacted: ~1297-char markdown reply with 10 paragraph blocks separated by blank lines>" } ] } }The same turn also wrote a
provider=openclaw, model=delivery-mirrormirror entry ~11 s later with the samecontent[0].textvalue, confirming the channel side accepted the reply as delivered and finalized the lane — the cleanup did not fire and the preview message remained intact in the Telegram chat.Before this PR on
main(the parent of56b09263ef, before revertingbd51d8f2dd), the same setup repeatedly produced a preview message that flashed into the chat and disappeared without any visible final, matching the report on #80520. Revertingbd51d8f2ddalone restored delivery (with paragraph duplicates); this PR adds the channel-layer dedup that also removes the duplicates.Observed result after fix:
What was not tested:
Test plan
pnpm test extensions/telegram— 1735/1735 passzai/glm-5.1embedded harness: short reply + 10-paragraph URL summaryCloses #80520.
🤖 Generated with Claude Code