Skip to content

fix(telegram): move preview-streamed dedup to channel layer (#80520)#83161

Open
Elarwei001 wants to merge 3 commits into
openclaw:mainfrom
Elarwei001:dev/telegram-preview-dedup
Open

fix(telegram): move preview-streamed dedup to channel layer (#80520)#83161
Elarwei001 wants to merge 3 commits into
openclaw:mainfrom
Elarwei001:dev/telegram-preview-dedup

Conversation

@Elarwei001

@Elarwei001 Elarwei001 commented May 17, 2026

Copy link
Copy Markdown
Contributor

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's lane.finalized state. 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

  1. Revert "Deduplicate preview-streamed final replies (Deduplicate Telegram partial preview final replies #82625)" — undoes the previous dedup, which could not observe channel state.

  2. fix(telegram): move preview-streamed final dedup to channel layer (Telegram messages silently dropped, no sendMessage logged #80520) — equivalent dedup at the channel layer:

    • New extensions/telegram/src/preview-dedup.ts helpers (whole + per-block normalization, exact-match check). 13 unit tests in preview-dedup.test.ts.
    • In bot-message-dispatch.ts, the dispatcher's deliver callback checks each text-only final against the answer lane's lastPartialText. On match: answerLane.finalized = true, deliveryState.markDelivered(), skip the send.
    • Guards with a previewMessageId check — only fires when the preview actually has an established Telegram message. If the preview never landed (e.g., transient failure), the final still flows through sendPayload so the user receives something. The previous core-layer dedup had no access to this state.
    • Media + caption payloads keep their existing path through lane-delivery-text-deliverer.ts's hasMedia branch, which already handles caption stripping.

Real behavior proof

Behavior addressed: Telegram "reply appears briefly then disappears" regression for embedded-harness + reasoning-model + streaming.mode=partial paths (#80520).

Real environment tested: macOS, managed gateway service running this branch's dist/. Live Telegram bot configured with model.primary = zai/glm-5.1 (reasoning model) via the embedded harness with streaming.mode=partial.

Exact steps or command run after this patch:

  1. git checkout dev/telegram-preview-dedup && pnpm build (build the patched dist/).
  2. openclaw gateway stop && openclaw gateway start (run a live gateway on the patched build).
  3. Sent a short (3-char) conversational message to the live agent through the Telegram client.
  4. Sent an 80-char prompt asking the live agent to fetch and summarize an external URL; the model produced a 10-paragraph, ~1297-char markdown summary — the paragraph-split delivery shape this regression originally manifested on.
  5. (Supplemental) Ran pnpm test extensions/telegram against the patched branch — 118 files, 1735 tests pass, including 4 new integration cases for the channel-layer dedup, the previewMessageId guard, multi-block deliveries, and error preservation.

Evidence after fix:

Redacted live gateway.log excerpt 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):

[telegram] Inbound message <chat> -> @<bot> (direct, 80 chars)
(no other [telegram] outbound line for this account between the inbound
 and 3 minutes later — no sendMessage, no deliverReplies, no
 editMessageTelegram from the channel adapter for this turn)

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-streaming editMessageText path on the already-established preview message id. On main without this PR, the previous suppressPreviewStreamedPayloads would have dropped each paragraph's final payload at the runner layer and clearUnfinalizedStream would 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-mirror mirror entry ~11 s later with the same content[0].text value, 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 of 56b09263ef, before reverting bd51d8f2dd), the same setup repeatedly produced a preview message that flashed into the chat and disappeared without any visible final, matching the report on #80520. Reverting bd51d8f2dd alone restored delivery (with paragraph duplicates); this PR adds the channel-layer dedup that also removes the duplicates.

Observed result after fix:

  • The 10-paragraph, ~1297-char live reply lands as a single Telegram chat bubble that contains every paragraph (not 10 separate messages) and stays in the chat permanently.
  • No flashing/disappearing during or after streaming.
  • No paragraph-split duplicates.
  • Short conversational replies behave the same way (single message, stable).

What was not tested:

  • Slack, MSTeams, Mattermost (the analogous dispatchers in those channels were not modified by this PR; whether they need similar treatment is follow-up work).
  • Codex-harness paths (different code path, not affected by this PR).
  • Live multi-instance / concurrent-turn scenarios.

Test plan

  • pnpm test extensions/telegram — 1735/1735 pass
  • Manual Telegram E2E with zai/glm-5.1 embedded harness: short reply + 10-paragraph URL summary
  • Maintainer to confirm whether Slack/MSTeams dispatchers need analogous treatment in a follow-up

Closes #80520.

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: M triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 17, 2026
@clawsweeper

clawsweeper Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Summary
The PR adds Telegram-channel preview-final deduplication with helper tests and dispatcher coverage for matching text finals, multi-block finals, transcript mirroring, preview-message-id fallback, and inline buttons.

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
Overall: 🧂 unranked krab
Proof: 🦞 diamond lobster
Patch quality: 🧂 unranked krab
Summary: Strong live-log proof, but the patch is not quality-ready until the dedupe path verifies the preview was actually finalized before suppressing the final.

Rank-up moves:

  • Flush/stop the answer draft stream and verify delivered preview text before marking the deduped final delivered.
  • Add a dispatcher regression where the preview message id exists but the latest partial has not been delivered, and assert the final still sends.
  • Run the focused Telegram dispatcher, preview-dedup, and lane-delivery tests after the repair.
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
🔥 Warming up: proof, findings, or rank-up moves are still in progress.

       .-.
    .-'   '-.
   /  .- -.  \
  |  /     \  |
   \  '._.'  /
    '-.___.-'
What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • How to hatch it: reach status: 👀 ready for maintainer look or status: 🚀 automerge armed; that usually means sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

Real behavior proof
Sufficient (logs): The PR body includes redacted live Telegram gateway/session logs and observed stable after-fix behavior for the affected partial-streaming path.

Mantis proof suggestion
A short native Telegram recording would materially show the preview remains visible and no paragraph duplicate finals appear. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

telegram desktop proof: verify a partial-streamed Telegram reply remains visible after finalization, avoids paragraph duplicate finals, and preserves inline buttons on a matching final.

Risk before merge
Why this matters: - As written, a matching final can be marked delivered before the pending preview edit has flushed or before delivered preview text is verified, so a stale or failed edit could suppress the fallback final reply.

  • The PR intentionally changes Telegram final-send suppression, so maintainers should treat this as message-delivery-sensitive even with green unit tests.

Maintainer options:

  1. Flush and verify before suppression (recommended)
    Update the dedupe path to stop/flush the answer draft stream and only mark delivered when the stream reports matching delivered preview text; otherwise let the normal final send path run.
  2. Accept the stale-preview risk
    Maintainers could land with the supplied live logs and monitor the Telegram path, but that accepts a remaining race where a final is suppressed before the preview edit is proven visible.
  3. Pause for visual transport proof
    If maintainers want higher confidence after the repair, request a short Telegram Desktop proof that shows the preview remains visible, duplicates do not appear, and buttons are preserved.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Update the Telegram preview-final dedupe path to stop/flush the answer draft stream and only suppress the final after the stream reports matching delivered preview text; otherwise fall through to normal final delivery. Add focused dispatcher coverage for a pending or stale preview where the final must still send.

Next step before merge
There is a narrow, file-local repair for the blocking message-delivery finding in the Telegram dispatcher tests and implementation.

Security
Cleared: The diff only touches Telegram channel TypeScript and tests; it does not change dependencies, workflows, permissions, credential handling, or package scripts.

Review findings

  • [P1] Flush the preview before suppressing the final — extensions/telegram/src/bot-message-dispatch.ts:1372
Review details

Best 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:

  • P1: The PR targets a user-visible Telegram regression where generated replies can disappear or fail to reach the chat.
  • merge-risk: 🚨 message-delivery: The diff changes when Telegram final payloads are suppressed instead of sent, which can affect visible reply delivery.

Full review comments:

  • [P1] Flush the preview before suppressing the final — extensions/telegram/src/bot-message-dispatch.ts:1372
    This branch returns before deliverLaneText can stop/flush the answer stream. lastPartialText is updated synchronously, while the actual Telegram edit only updates lastDeliveredText after send/edit success; if the final arrives while that edit is still pending or the edit fails, this code marks the turn delivered and suppresses the fallback final, leaving an older or incomplete preview. Flush/stop the stream and verify the delivered preview text matches before returning; otherwise fall through to the normal final send path.
    Confidence: 0.87

Overall correctness: patch is incorrect
Overall confidence: 0.86

Acceptance criteria:

  • node scripts/run-vitest.mjs run --config test/vitest/vitest.extension-telegram.config.ts extensions/telegram/src/bot-message-dispatch.test.ts extensions/telegram/src/preview-dedup.test.ts extensions/telegram/src/lane-delivery.test.ts
  • git diff --check

What I checked:

Likely related people:

  • steipete: Recent Telegram preview/final-delivery work includes transcript-backed final recovery and plugin message-lifecycle migration touching the same lane-delivery surface. (role: recent area contributor; confidence: high; commits: 549a0ea31310, 05eda57b3c72, ee72ce8cf7b7; files: extensions/telegram/src/lane-delivery-text-deliverer.ts, extensions/telegram/src/bot-message-dispatch.ts, src/plugin-sdk/channel-streaming.ts)
  • obviyus: Recent commits in the Telegram dispatcher and lane-delivery path cover native draft progress, transcript-backed final replies, and removal of native draft preview transport. (role: recent Telegram streaming contributor; confidence: high; commits: 890139f99826, 20c3580394e7, 2a4dd8925323; files: extensions/telegram/src/bot-message-dispatch.ts, extensions/telegram/src/lane-delivery-text-deliverer.ts, extensions/telegram/src/draft-stream.ts)
  • vincentkoc: Authored the long Telegram final preview reuse work that established part of the preview-finalization behavior this PR now extends. (role: preview-finalization feature contributor; confidence: medium; commits: e03fe1e28965; files: extensions/telegram/src/lane-delivery-text-deliverer.ts, extensions/telegram/src/lane-delivery.test.ts)
  • Patrick-Erichsen: Most recent current-main Telegram progress-preview commit touched bot-message-dispatch and related tests near the streaming/finalization path. (role: recent adjacent contributor; confidence: medium; commits: d60ab485114a; files: extensions/telegram/src/bot-message-dispatch.ts, extensions/telegram/src/bot-message-dispatch.test.ts)
  • giodl73-repo: Authored the merged core-layer preview-streamed final suppression that this PR is replacing after the disappearing-preview regression. (role: prior suppression contributor; confidence: medium; commits: bd51d8f2dd97; files: src/auto-reply/reply/agent-runner-payloads.ts, src/auto-reply/reply/agent-runner.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against f07c87405c30.

@clawsweeper clawsweeper Bot added mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P1 High-priority user-facing bug, regression, or broken workflow. impact:message-loss Channel message delivery can be lost, duplicated, or misrouted. labels May 17, 2026
@obviyus obviyus added mantis: telegram-visible-proof Mantis should capture Telegram visible proof. and removed mantis: telegram-visible-proof Mantis should capture Telegram visible proof. labels May 17, 2026
@obviyus obviyus temporarily deployed to qa-live-shared May 17, 2026 15:57 — with GitHub Actions Inactive
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. impact:session-state Session, memory, transcript, context, or agent state can drift or corrupt. labels May 17, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 17, 2026
@openclaw openclaw deleted a comment from openclaw-mantis Bot May 18, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. and removed impact:session-state Session, memory, transcript, context, or agent state can drift or corrupt. impact:message-loss Channel message delivery can be lost, duplicated, or misrouted. labels May 18, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 18, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 18, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 18, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 18, 2026
@Elarwei001 Elarwei001 force-pushed the dev/telegram-preview-dedup branch from 5565c46 to 46f7783 Compare May 18, 2026 14:24
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 18, 2026
@Elarwei001

Copy link
Copy Markdown
Contributor Author

Addressed the latest ClawSweeper finding.

  • Rebased the branch onto current main.
  • Preserved Telegram inline/interactive buttons on the preview-finalized dedupe path by editing the existing preview message before suppressing the duplicate final send.
  • Added regression coverage for button-bearing finals whose text already streamed into the preview.

Local verification:

node scripts/run-vitest.mjs run --config test/vitest/vitest.extension-telegram.config.ts extensions/telegram/src/bot-message-dispatch.test.ts extensions/telegram/src/preview-dedup.test.ts extensions/telegram/src/lane-delivery.test.ts

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels May 18, 2026
Elarwei001 and others added 3 commits May 19, 2026 21:08
…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>
@Elarwei001 Elarwei001 force-pushed the dev/telegram-preview-dedup branch from 46f7783 to 630ac27 Compare May 19, 2026 13:11
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram mantis: telegram-visible-proof Mantis should capture Telegram visible proof. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: M status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram messages silently dropped, no sendMessage logged

2 participants