Files
hermes-agent/plugins
teknium1 595f3a289c fix(telegram): split replies resume from the refused chunk, never re-send the head; per-chat send order + flood cooldown
A reply past 4,096 chars goes out as several sendMessage calls. When chunk 2 was
refused by flood control (RetryAfter past the 5s inline cap) send() returned the
bare flood_control result, so _send_with_retry re-sent the WHOLE payload after
the wait: the user saw chunk 1 twice (reporter: 4 messages, 686 duplicated words),
and paths without a ledger row lost the tail outright.

- send() now reports a mid-split refusal through the existing partial_overflow
  contract (the key _edit_overflow_split already sets and the stream consumer
  reads): delivered_chunks / total_chunks / last_message_id, plus
  undelivered_chunks + delivered_message_ids ONLY when non-delivery is certain
  (flood cap, Bot API rejection, connect/pool timeout) — an ambiguous TimedOut may
  have reached Telegram and is never resumed from.
- BasePlatformAdapter._send_with_retry resumes from the remainder via a new
  _resume_partial_send hook (default None = keep the partial failure, never
  re-send the head; the plain-text fallback is skipped for partials too). The
  Telegram override sends the leftover formatted chunks, continuing the id sequence.
- Per-chat FIFO send gate, reentrant per asyncio task (media paths nest), held
  only around the API calls — never across the reconnect wait — on send() and the
  media funnel, so concurrent replies to one chat no longer interleave chunks.
- A flood refusal arms a per-chat cooldown (mirrors the sendChatAction cooldown,
  capped 300s); sends inside the window fail closed locally with the same
  flood_control:<s> result and no API call, so ledger recognition and redelivery
  timing are unchanged.
- Edit path: log "refusing (retry_after Ns > cap)" after the cap check instead of
  "waiting Ns" followed by no wait.

Live against a local fake Telegram Bot API with a fake token (RetryAfter=7 on
chunk 2 of a 3-chunk reply): before 4 messages / 324 duplicated words; after 3
messages, 853/853 words, 0 duplicated, 0 lost. Two concurrent 3-chunk sends:
before 9 source switches, after 1. Five sends inside a refused window: before 5
API calls, after 0.

Fixes #114396

Co-authored-by: AStrnbrg <45151087+AStrnbrg@users.noreply.github.com>
Co-authored-by: whyyagswhy <166958865+whyyagswhy@users.noreply.github.com>
2026-09-18 10:17:23 -07:00
..