Skip to content

fix(telegram): preserve numbered overflow fallback - #63677

Open
ofir5300 wants to merge 2 commits into
NousResearch:mainfrom
ofir5300:fix/telegram-overflow-numbered-fallback
Open

fix(telegram): preserve numbered overflow fallback#63677
ofir5300 wants to merge 2 commits into
NousResearch:mainfrom
ofir5300:fix/telegram-overflow-numbered-fallback

Conversation

@ofir5300

Copy link
Copy Markdown

What does this PR do?

Fixes two related paths where a long Telegram reply could end up truncated or lose its numbered (i/N) overflow chunks:

  1. Rate-limited finalize edit during overflow split. When the final response overflows and _edit_overflow_split tries to finalize the first chunk, a Telegram flood-control error (retry_after) left the streamed preview visible but without a trustworthy (1/N) marker. The adapter previously treated this like a generic edit failure, so downstream logic could accept the unnumbered preview as chunk 1 and only send the tail — producing a reply whose first part carried no numbering and could silently drop content. The adapter now reports a partial overflow with resend_full_final: true, and the stream consumer responds by resending the complete final response as fresh, correctly numbered chunks, deleting the stale preview only after every chunk is confirmed delivered.

  2. Plugin-transformed finals edited past the platform limit. When plugin hooks transform the final response after streaming, gateway/run.py edited the existing streamed message directly via adapter.edit_message. An oversized transformed final could truncate, and the edit path did not preserve topic routing metadata. The call site now goes through a new consumer-owned deliver_transformed_final(), which reuses the fresh-final fallback path: every send stays below the platform limit, thread_id / reply_to_message_id metadata is preserved, chunks are numbered, and a failed delivery leaves the gateway's normal final-send fallback enabled instead of being marked as sent.

The consumer gains a _fallback_resend_full flag driving both paths: continuation text becomes the full final (not just the unseen tail), and multi-chunk sends get (i/N) numbering with a chunk limit that accounts for the suffix.

Related Issue

No existing issue found (searched open issues/PRs for Telegram overflow/truncation). Happy to file one if you prefer tracking it separately — closest existing PRs (#55869, #60127, #52095) address different failure paths (immediate fallback on flood control, mid-stream preview truncation, resend-after-partial-delivery) and none preserve the numbered-chunk contract on a rate-limited finalize edit or route transformed finals through the consumer.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • plugins/platforms/telegram/adapter.py_edit_overflow_split: a retry_after/flood-control failure on the first-chunk finalize edit now returns a retryable partial-overflow SendResult with resend_full_final: true instead of falling into the generic error path.
  • gateway/stream_consumer.py — new deliver_transformed_final() public method; new _fallback_resend_full state honored by _continuation_text and _send_fallback_final (full resend + (i/N) numbering + suffix-aware chunk limit); _send_or_edit recognizes resend_full_final in the adapter's raw_response and clears the delivered-prefix bookkeeping.
  • gateway/run.py — the plugin-transformed-final call site no longer edits the streamed message directly; it delegates to deliver_transformed_final() and only marks already_sent when delivery is confirmed.
  • Tests: tests/gateway/test_telegram_overflow_partial.py (rate-limited finalize edit → full numbered resend, end-to-end consumer recovery), tests/gateway/test_stream_consumer_fresh_final.py (transformed-final success/failure/mid-chunk-failure semantics, metadata preservation, preview deletion rules), tests/gateway/test_duplicate_reply_suppression.py (call-site regression guard).

How to Test

  1. pytest tests/gateway/test_telegram_overflow_partial.py tests/gateway/test_stream_consumer_fresh_final.py tests/gateway/test_duplicate_reply_suppression.py -q — 62 pass.
  2. Repro of path 1: on a live Telegram gateway, trigger a final response longer than 4096 chars while the bot is under flood control (rapid successive long replies); before this change the visible reply could keep an unnumbered first bubble with a missing tail; after, the preview is replaced by a complete (1/N)…(N/N) sequence.
  3. Repro of path 2: enable any plugin that rewrites the final response in post_stream hooks and make it grow the reply past the platform limit; before, the direct edit truncated at the limit; after, delivery arrives as numbered chunks in the original topic/thread.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q — all gateway/telegram tests touched by this change pass (62/62 in the three affected test files). Note: on my machine the full suite has pre-existing environment-dependent failures that reproduce identically on a clean origin/main checkout (verified by diffing failure sets branch vs main); this branch introduces no new failures.
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15 (Apple Silicon)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A (behavior fix; docstrings added on the new method)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no config changes)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — N/A (no platform-specific code)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/telegram Telegram bot adapter P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 13, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracing two real delivery paths. The current main still has the generic Telegram first-chunk overflow failure (plugins/platforms/telegram/adapter.py:4514-4525) and unconditionally marks transformed direct edits as sent (gateway/run.py:20586-20603).

Problems

  • gateway/run.py:20085-20100 changes transformed-final delivery to fresh sends, but the unchanged integration test at tests/gateway/test_run_progress_topics.py:1043-1071 still requires the transformed text in adapter.edits. That assertion will fail on this head; update it to the new delivery contract.
  • tests/gateway/test_duplicate_reply_suppression.py:72-80 inspects GatewayRunner source text. AGENTS.md:1370-1381 bans source-reading tests; this does not exercise the send/failure behavior.

Suggested changes

  • Replace the source-shape assertion with a _run_with_agent regression covering confirmed fresh delivery and failed delivery leaving already_sent unset.
  • Update the existing transformed-final integration test to assert fresh-send content and preserved metadata.

Automated hermes-sweeper review.

Comment thread gateway/run.py
_sc, "deliver_transformed_final", None
)
if callable(_deliver_transformed):
_delivery_result = _deliver_transformed(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes transformed finals from edits to fresh sends, but the unchanged tests/gateway/test_run_progress_topics.py:1043-1071 still requires the appended text in adapter.edits. Update that integration test to assert the new fresh-send contract, including the failed-delivery already_sent outcome.

@@ -67,6 +69,17 @@ def _make_event(text="hello", chat_id="c1", user_id="u1"):
)


def test_transformed_final_callsite_requires_confirmed_fresh_delivery():
"""Production must not treat a failed transformed edit as final delivery."""
src = inspect.getsource(GatewayRunner._run_agent_inner)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inspect.getsource() makes this a source-shape test, which AGENTS.md explicitly bans. Replace it with an integration test that drives the transformed-final path and observes confirmed delivery versus failure.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 16, 2026
@ofir5300

Copy link
Copy Markdown
Author

@teknium1 Implemented both requested test-contract changes in b31da89aa:

  • Removed the prohibited inspect.getsource() source-shape test.
  • Updated the existing _run_with_agent transformed-final integration path to assert a confirmed fresh send, preserved topic metadata, and no transformed edit.
  • Added a failed-delivery integration case that observes the transformed send attempt, preserves the transformed final_response, and verifies already_sent remains unset so the normal final-send fallback stays enabled.

Verification:

  • 329 passed across test_run_progress_topics.py, duplicate suppression, fresh-final, overflow-partial, stream-consumer, and Telegram formatting suites.
  • 329 passed on a temporary merge with current origin/main (569b912d7).
  • ruff, compileall, git diff --check, and added-line security scan passed.

No production behavior was expanded in this follow-up; it replaces the source-reading assertion with behavioral coverage and aligns the pre-existing integration test with the PR's confirmed fresh-delivery contract.

@ofir5300
ofir5300 requested a review from teknium1 July 16, 2026 14:16
@ofir5300
ofir5300 force-pushed the fix/telegram-overflow-numbered-fallback branch from b31da89 to 8c554df Compare July 26, 2026 14:13
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

47 PRs address or reference this Telegram long-message complex, spanning legacy MarkdownV2 formatting, streamed overflow and flood recovery, draft behavior, and the standalone sender. The current open work separates into #63677’s numbered full-resend/transformed-final recovery, several salvageable streaming edge cases, and #74040’s recorded best fix for the standalone chunk-indicator defect.

Related pull requests

Duplicates

#1477 is a duplicate of merged #1478; #42766 and the validated parts of #43463/#43590 were salvaged into #43761; #48663, #48718, #50408, and #51266 are superseded by merged #51736; #48517 was salvaged into #50010; #74028, #74055, and #74107 overlap #74040, which is the recorded best fix for #74004.

Suggested consolidation

Keep #63677 open with a salvage path: retain its full numbered resend after a rate-limited first-chunk finalize and consumer-owned transformed-final delivery, then re-run the updated behavioral tests against current main. Keep #74040 open as the recorded standalone best fix; despite the keep_open reviews on #74028, #74055, and #74107, their diffs respectively omit required fence parity/tests, modify the wrong chunk list amid unrelated work, or affect HTML and bundle unrelated changes, so they can be closed as duplicates of #74040.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I42765(["issue #42765 (closed)"])
    P63677["PR #63677 (open)"]
    P63677 -.->|partial| I42765
    class I42765 closed
    class P63677 open
    class P63677 target
    click I42765 "https://github.com/NousResearch/hermes-agent/issues/42765"
    click P63677 "https://github.com/NousResearch/hermes-agent/pull/63677"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 47 pull requests and 8 issues in this complex. Each diff was read against this issue; Assessment working set: 582 kB of PR diffs, 127 kB of issue/PR text, 79 kB of discussion (90 comments), 61 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants