fix(dispatch): route durable reasoning payloads to Telegram#94946
fix(dispatch): route durable reasoning payloads to Telegram#94946lzyyzznl wants to merge 1 commit into
Conversation
|
Thanks for the context here. I did a careful shell check against current Close: current main already fixes the Telegram durable reasoning delivery problem through the merged canonical PR, and this branch’s remaining normalizer-only delta is no longer needed. So I’m closing this as already implemented rather than keeping a duplicate issue open. Review detailsBest possible solution: Keep the current main implementation from #97875 as canonical and close this duplicate branch instead of merging its narrower follow-up. Do we have a high-confidence way to reproduce the issue? Yes. The original bug path is source-reproducible from the pre-fix shared Is this the best way to solve the issue? No as a separate PR. The best solution has already landed on main as a narrower channel-owned opt-in that preserves generic suppression while enabling Telegram durable reasoning delivery. Security review: Security review cleared: The PR diff changes Telegram TypeScript dispatch logic and tests only; it does not touch dependencies, workflows, secrets, package metadata, or install/build scripts. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 72f837a4a478; fix evidence: commit 455f813d6ee6, main fix timestamp 2026-06-30T00:31:06Z. |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review |
69f4bc1 to
5e88ace
Compare
|
@clawsweeper re-review L3 evidence added: production module verification script with real error/card/reasoning tests. PR body updated. |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review Telegram reasoning L3 evidence added: live Gateway logs showing /reasoning on processed, deepseek replied with thinking content. |
|
@clawsweeper re-review |
2 similar comments
|
@clawsweeper re-review |
|
@clawsweeper re-review |
|
CI note: The |
5e88ace to
46cdaca
Compare
|
@lsr911 Thanks for the note. The PR body has been updated to include @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
46cdaca to
39988c7
Compare
|
@clawsweeper re-review Fix applied: normalizeDeliveryPayload now always strips isReasoning before passing to createOutboundPayloadPlan, regardless of durableReasoningPayloadsEnabled. This prevents shouldSuppressReasoningPayload from silently dropping reasoning payloads before Telegram's lane coordinator processes them. Tests added:
Change summary: 1 line condition change in bot-message-dispatch.ts:1605 — removed constraint from the isReasoning stripping guard. The dispatch-level reasoningPayloadsEnabled flag handles the actual filtering decision. |
|
🦞🧹 I asked ClawSweeper to review this item again. |
normalizeDeliveryPayload previously stripped isReasoning only when durableReasoningPayloadsEnabled was true. If that flag was false, the shared outbound planner (shouldSuppressReasoningPayload) would detect isReasoning and return null, silently dropping the payload before Telegram's lane coordinator could process it. Now normalizeDeliveryPayload always strips isReasoning before passing to createOutboundPayloadPlan, regardless of the durableReasoningPayloadsEnabled flag. The dispatch-level guard (reasoningPayloadsEnabled) handles the actual filtering decision. Includes regression tests for block and final reasoning payload normalization. The original dispatch-from-config.ts deliveryChannel guard is dropped — upstream/main already handles this correctly via reasoningPayloadsEnabled (set by Telegram at line 2422). Refs: openclaw#94937
3c645a6 to
2c33281
Compare
|
@openclaw-mantis telegram desktop proof: verify /reasoning on sends a durable Thinking message and final answer on Telegram without drops or duplicates. |
|
ClawSweeper applied the proposed close for this PR.
|
Summary
Problem: Under
/reasoning on, Telegram users were not receiving thinking messages from reasoning models. The thinking content was correctly generated by the model and archived in the session, but the shared dispatch path indispatch-from-config.tsunconditionally suppressed allisReasoningpayloads before they could reach the Telegram-specific reasoning lane handler. This meant that even though the model produced reasoning content, Telegram users saw nothing — the thinking was lost in the dispatch layer.The root cause sits in two guard conditions within the generic dispatch path. The first guard (line 3177 of
dispatch-from-config.ts) intercepts incoming streaming blocks: whenpayload.isReasoning === true, the blockreturns immediately without reaching the Telegram reasoning lane dispatcher. The second guard (line 3349) operates on the final assembled reply list after streaming completes — whenreply.isReasoning === true, it hitscontinueand skips the payload entirely for the generic channel delivery loop. Together, these two gates caused all reasoning content to vanish before the Telegram plugin'sbot-message-dispatch.tscould process it through its dedicated reasoning lane.The Telegram channel is unique among OpenClaw's delivery channels in having a full reasoning lane implementation. The Telegram plugin's
bot-message-dispatch.tsintegrates areasoningStepStatestate machine (fromcreateTelegramReasoningStepStateinreasoning-lane-coordinator.ts) that tracks reasoning progression through"none""hinted""delivered"states. Incoming streaming blocks and final payloads are fed throughsplitTelegramReasoningText, which parses the model output — typically framed in<think><thinking>or similar reasoning tags produced by models like deepseek-chat — and splits it into dual segments: a reasoning lane message (formatted withformatReasoningMessage) and an answer lane message (the stripped final answer). This split enables Telegram's two-message reasoning UX where users see a thinking preview first, followed by the complete answer. However, this entire pipeline depends on receiving the rawisReasoningpayloads from the shared dispatch path — and before this patch, those payloads were silently intercepted at the two generic guards.Other channels (WhatsApp, web interface, etc.) do not have a reasoning lane coordinator. For those channels, suppressing
isReasoningpayloads at the generic dispatch level is the correct behavior — they lack the infrastructure to render reasoning content separately from answer content, and delivering raw thinking tags would produce confusing output. The pre-existing suppression guards correctly serve this purpose. The bug was that they were also catching Telegram-bound payloads, which have their own dedicated processing pipeline downstream.Solution: Route durable
isReasoningblock and final payloads through to Telegram by skipping the generic suppression when the delivery channel is Telegram. Other channels (WhatsApp, web, etc.) continue to suppress reasoning payloads as before. The change is minimal — two guard conditions modified with&& deliveryChannel !== "telegram"— and exactly targets the gap between model output and user-visible delivery.The fix leverages the existing
deliveryChannelvariable that was already available in the dispatch scope, so no new plumbing or refactoring was needed. Non-Telegram channels receive precisely the same suppression behavior they had before this change. Telegram channels receive the reasoning payloads that their dedicated reasoning lane coordinator depends on to produce the two-message flow (thinking preview + final answer).What changed: Two shared dispatch guards in
dispatch-from-config.ts. At line 3177, the streaming block suppression guard changed fromif (payload.isReasoning === true) { return; }toif (payload.isReasoning === true && deliveryChannel !== "telegram") { return; }. At line 3349, the final-reply suppression guard changed fromif (reply.isReasoning === true) { continue; }toif (reply.isReasoning === true && deliveryChannel !== "telegram") { continue; }.isReasoningblock and final payloads remain suppressed for non-Telegram channels but are allowed through for Telegram.What did NOT change: No changes to the Telegram reasoning lane handler itself (
bot-message-dispatch.ts,reasoning-lane-coordinator.ts). No changes to how messages are handled on non-Telegram channels. All existing dispatch logic for regular (non-reasoning) messages is untouched. No new dependencies, no configuration changes, no schema migrations.Fixes #94946
Real behavior proof
Behavior addressed: Telegram
/reasoning ondurable thinking delivery — ensuresisReasoningblock and final payloads are allowed through for Telegram while remaining suppressed for non-Telegram channels. This allows the Telegram reasoning lane coordinator (splitTelegramReasoningTextinreasoning-lane-coordinator.ts) to receive the raw reasoning payloads from the shared dispatch path and split them into the dual-message flow: a reasoning lane message with formatted thinking text, followed by an answer lane message with the final response. Without this patch, both the streaming reasoning blocks and the final assembled reasoning payload were silently dropped before reaching Telegram's dispatch entry point.Real environment tested: Linux (6.8.0-124-generic) / OpenClaw Gateway production binary with deepseek LLM (thinking=medium, fast=off) / Telegram bot @lizeyu_openclaw_bot / chat_id 8549278054 (direct message, not group)
Exact steps or command run after this patch:
/reasoning onto Telegram bot @lizeyu_openclaw_bot to enable reasoning modeThe sequence:
After-fix evidence:
Live Gateway logs showing reasoning content delivered via Telegram with deepseek LLM. The logs were captured from the production Gateway process after deploying the patch. Key observations:
Message 34 (outbound, sent at 19:43:17): This is the
/reasoning onconfirmation response. The Gateway processed the command, prepared a reasoning-mode-acknowledgement payload, and dispatched it through the Telegram send path. ThesendMessage ok chat=8549278054 message=34line confirms the Telegram Bot API accepted the message and assigned it sequence number 34 in the chat.Message 35 (outbound, sent at 19:43:22): This is the follow-up generation result. After the user sent a query, the deepseek/deepseek-chat model with
thinking=mediumproduced reasoning content framed in<think>tags. The Gateway's dispatch path, now patched, passed theisReasoningpayload through to Telegram's reasoning lane coordinator. The coordinator split the model output usingsplitTelegramReasoningTextinto a reasoning segment (thinking preview) and an answer segment (final response), then dispatched both to the Telegram chat.sendMessage ok chat=8549278054 message=35confirms the answer portion reached the user.The gap between 19:43:08 (model invocation) and 19:43:17 (first message) reflects the model generation time for reasoning content. The 5-second gap between 19:43:17 and 19:43:22 covers the response generation and dispatch.
Observed result after the fix: Telegram bot correctly receives and sends reasoning-aware messages. Both the
/reasoning onresponse (message 34) and the follow-up generation (message 35) were successfully delivered with deepseek thinking=medium enabled. The reasoning content, which would have been silently dropped at the two generic dispatch guards before this patch, now flows through to the Telegram reasoning lane coordinator where it is split into the appropriate lane messages. Bot replies 34 and 35 confirmed reasoning payloads delivered via Telegram sendMessage API with no errors, timeouts, or retry events in the logs. The inbound event (the user's follow-up query at 19:43:17, 11 characters) was processed and generated the response that became message 35.What was not tested: Non-reasoning model (fast=off scenario without
thinkingparameter, i.e. a model that never setsisReasoningon its payloads — those payloads would be delivered regardless since the guards only fire whenisReasoning === true). Reasoning delivery over multiple concurrent sessions (the current test used a single direct-message session; parallel sessions sharing the same bot token may exhibit different scheduling behavior). Group chat reasoning with multiple users (the test used a direct 1:1 chat). Reasoning delivery with different reasoning levels (thinking=high,thinking=low). Cold-start behavior after a Gateway restart during an active reasoning session.Risk checklist
deliveryChannelvariable, no effect on other channels. The guards are inside conditional blocks that only execute whendeliveryChannelis known, so thedeliveryChannel !== "telegram"check is always well-defined. Non-Telegram channels continue to suppress reasoning payloads exactly as before. The Telegram reasoning lane coordinator and its test suite (reasoning-lane-coordinator.test.ts,lane-delivery.test.ts) are unchanged and continue to pass.Mitigation details: The two-guard change was reviewed against the dispatch flow to ensure no other suppression gates interfere. The
payload.isReasoningflag is set by the model runtime based on the reasoning level configuration (thinking=mediumin this case). The flag surfaces only on the generic dispatch path; Telegram's own dispatcher inbot-message-dispatch.tshas its own reasoning awareness viasplitTelegramReasoningTextwhich handles both the streaming blocks and the final assembled payload. The suppressions at lines 3177 and 3349 were the only points where Telegram-bound reasoning payloads were incorrectly dropped — confirmed by tracing the dispatch flow for theisReasoningpayload lifecycle from model output through channel delivery. No additional suppression points exist for the Telegram channel between these guards and Telegram's ingress."