fix(telegram): restore reply context for replies to rich messages - #46569
fix(telegram): restore reply context for replies to rich messages#46569GodsBoy wants to merge 1 commit into
Conversation
When telegram.extra.rich_messages is enabled, final replies are sent via sendRichMessage, which carries no legacy text field. A user replying to such a message arrives with reply_to_message.text and .caption empty, so the gateway loses reply context and injects no "[Replying to: ...]" pointer. This was most visible on replies to rich cron announcements. Add a bounded, TTL-expiring outbound reply-context cache on the Telegram adapter, keyed by (chat_id, message_id) and populated on both the rich and legacy send paths. When an inbound reply has no native quote and no text/caption, _build_message_event falls back to the cached snippet. The existing gateway injection logic then works unchanged. This restores parity with legacy text replies, which already round-trip through reply_to_message.text. The cache is in-memory only and bounded three ways (entry count, per-entry chars, and a 48h TTL); it stores only the bot's own outbound content and surfaces it only on an explicit user reply, so it adds no stale-task-revival surface beyond the existing legacy-reply behavior (see NousResearch#26714).
Code Review — LGTM ✅Scope: Telegram adapter outbound reply-context cache for rich message reply parity. What was checked:
No issues found. The implementation is thorough and the test coverage is excellent. |
|
This seems like the right minimal fix for the rich-message regression. One question: should cron deliveries eventually store a durable mapping from Telegram The in-memory outbound cache handles the common case where a user replies soon after delivery, but cron messages are a bit special:
A small durable bridge like:
would let the gateway inject a lightweight pointer on reply, and the agent could look up the full cron run only when needed. Not blocking this PR. The cache fix is clean and restores parity for rich messages. Just wondering if cron deliveries deserve the durable mapping as a follow-up. |
|
Closing this as implemented on The rich-message reply-context regression is now covered by the mainline implementation using Verified:
The branch here is now redundant and dirty against current |
What does this PR do?
When
telegram.extra.rich_messages: trueis enabled (Bot API 10.1sendRichMessage, added in #44829), final replies are sent with the agent text in arich_messagepayload and no legacytextfield. A user replying to one of those messages arrives withreply_to_message.textand.captionempty, so_build_message_eventsetsreply_to_text=Noneandgateway/run.pyinjects no[Replying to: "..."]pointer. The agent loses the context of what the user is replying to. This is most visible on replies to rich cron announcements (Cronjob Response: ...).It worked before rich messages because the legacy
sendMessagepath always carried a realtextfield that round-tripped intoreply_to_message.text.This restores that context with a bounded outbound reply-context cache, at parity with legacy text replies. It does not disable rich messages and adds no new stale-task-revival surface beyond the existing legacy-reply behavior (reference-only semantics for quoted cron text remain the injection layer's responsibility, related: #26714).
Related Issue
Fixes #46568
Type of Change
Changes Made
gateway/platforms/telegram.py: add a bounded, TTL-expiring outbound reply-context cache (_outbound_context_cache,_remember_outbound_context,_prune_outbound_context,_lookup_outbound_context) keyed by(chat_id, message_id); register delivered content on both the rich (_try_send_rich) and legacy chunked (send) paths; fall back to the cached snippet in_build_message_eventonly when there is no native quote and notext/caption(precedence preserved, keeps the [Bug]: Telegram native partial quotes are expanded to the full replied-to message #22619 quote-narrowing behavior).tests/gateway/test_telegram_rich_reply_context.py: new regression suite (cache helpers, send-path registration, inbound precedence, TTL/eviction, empty/failed-send guards, end-to-end rich cron-reply scenario).The cache is in-memory only and bounded three independent ways: 1024 entries, 2000 chars per entry, and a 48h TTL. It stores only the bot's own outbound content and surfaces it only on an explicit user reply. Cron deliveries benefit automatically because live delivery already routes through
adapter.send; no scheduler change was needed.How to Test
telegram.extra.rich_messages: trueand run the bot.sendRichMessage).[Replying to: "..."]context. After: the reply context is restored.Automated:
scripts/run_tests.sh tests/gateway/test_telegram_rich_reply_context.py(21 tests).scripts/run_tests.sh tests/gateway/test_telegram_rich_messages.py tests/gateway/test_telegram_reply_quote.py tests/gateway/test_telegram_reply_mode.py tests/gateway/test_reply_to_injection.py tests/gateway/test_telegram_thread_fallback.py(no regressions).Checklist
Code
fix(telegram): ...)scripts/run_tests.sh(CI-parity) and all tests passscripts/run_tests.shand full CI (6 test shards green). Note: live end-to-end verification on a real Telegram bot was not run in my environment; the one edge that needs live confirmation is called out under Residual findings below.Documentation & Housekeeping
docs/, docstrings) — N/A (no user-facing docs affected)cli-config.yaml.exampleif I added/changed config keys — N/A (cache bounds are internal constants by design, no new config key)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Atime,collections.OrderedDict), no platform-specific primitivesScreenshots / Logs
Backend gateway change, no UI. All CI checks green on the first run (6 test shards, both nix, both arch builds, e2e,
check-attribution,ruff + ty diff).Residual findings
Surfaced by an internal review pass; both are low severity and do not affect correctness.
reply_to_message.text, so cached legacy entries are never consulted on the fallback path, yet they occupy slots in the shared 1024-entry cache and can evict rich entries under sustained load before a reply arrives. Output stays correct; only effective retention degrades. Options: register only on the rich path, scope the cap per-chat, or document the tradeoff.message.quote.textfor native-quote replies to asendRichMessagemessage, the cache fallback would inject the full cached body instead of the user's selected substring, regressing the [Bug]: Telegram native partial quotes are expanded to the full replied-to message #22619 quote-narrowing protection. Needs verification on live Telegram of whetherquote.textround-trips for rich-message replies; if it does not, suppress full-body cache injection on quoted rich replies.Advisory: the cache is in-memory only, so it is lost on gateway restart and not shared across processes (a reply after a restart, or on a different instance, falls back to no context). Reasonable for a first cut given the 48h TTL window.