hermes-agent/gateway/platforms
NivOO5 4ed293b38e feat(telegram): native draft streaming via sendMessageDraft (Bot API 9.5+)
Adds Telegram's native streaming-draft API as a streaming transport so DM
replies render with smooth animated previews as tokens arrive, dropping
the per-edit jitter of the legacy editMessageText polling path.

Adapter contract (gateway/platforms/base.py):
  - supports_draft_streaming(chat_type, metadata) -> bool. Default False.
    Telegram returns True only for DMs and only when the bound python-
    telegram-bot version exposes Bot.send_message_draft (PTB 22.6+).
  - send_draft(chat_id, draft_id, content, metadata) -> SendResult.
    Default raises NotImplementedError. Telegram delegates to PTB's
    send_message_draft. Drafts have no message_id (Bot API contract);
    SendResult.message_id is None on success.

Telegram adapter (gateway/platforms/telegram.py):
  - supports_draft_streaming gates on chat_type='dm' AND PTB capability.
  - send_draft trims to MAX_MESSAGE_LENGTH using utf16_len, threads
    message_thread_id through metadata, and routes failures back as
    SendResult(success=False, error=...) so the consumer can fall back.

Stream consumer (gateway/stream_consumer.py):
  - StreamConsumerConfig gains transport ('auto'|'draft'|'edit'|'off')
    and chat_type fields.
  - run() resolves _use_draft_streaming once via a probe at the top of
    the run, allocating a fresh class-wide draft_id_counter so each
    response animates as its own preview (no animation collision across
    consecutive responses to the same chat).
  - _send_or_edit gains a pre-edit branch: when drafts are active AND
    not finalizing AND no edit-path message_id is established, the
    frame routes through _send_draft_frame instead of edit_message.
    Drafts intentionally do NOT set _already_sent so the gateway's
    final sendMessage path still fires — drafts have no message_id and
    the user needs a real message in their chat history.
  - _reset_segment_state bumps the draft_id when the consumer is in
    draft mode so each text block after a tool boundary animates as a
    fresh preview below the tool-progress bubble (avoids the inter-
    tool-call leak openclaw documented in their #32535).
  - Per-response fallback: any send_draft failure (transient network,
    server reject, capability gap) flips _use_draft_streaming to False
    for the rest of the run, gracefully returning to the edit path.

Gateway config (gateway/config.py):
  - StreamingConfig.transport default flips edit -> auto. The auto path
    is identical to edit on every chat type that doesn't currently
    support drafts (groups, supergroups, forum topics, every non-
    Telegram platform), so the default is backwards-compatible for
    non-DM users.

Lifecycle model (Telegram Bot API 9.5):
  1. sendMessageDraft(chat_id, draft_id, text='') opens the bubble.
  2. Repeated sendMessageDraft calls with the SAME draft_id animate
     the preview as text grows.
  3. Drafts have no message_id and cannot be edited or deleted.
  4. When the response finishes the gateway's normal sendMessage path
     delivers the final answer; the draft preview clears naturally on
     the client and the user sees a real message in their history.

Inspired by PR #3412 by @NivOO5. Re-authored against current main
(stream_consumer.py is now ~4x larger than at #3412's branch base, with
new _NEW_SEGMENT/_COMMENTARY/finalize/_on_new_message machinery the
original PR didn't account for) but the design call (DM-only, edit-
fallback, transport=auto|draft|edit|off) is faithful to the original
proposal, with two improvements baked in:

  1. Per-response draft_id (monotonic counter, not a time hash) — no
     collision risk across consecutive responses on the same chat.
  2. Tool-boundary draft_id bump — prevents the inter-tool-call leak
     openclaw hit during their rollout (their #32535).

Closes #21439 (duplicate feature request).
2026-05-10 20:02:50 -07:00
..
qqbot feat(qqbot): wire native tool-approval UX via inline keyboards 2026-05-07 07:48:15 -07:00
__init__.py perf(gateway): defer QQAdapter and YuanbaoAdapter imports via PEP 562 (#22790) 2026-05-09 13:17:48 -07:00
_http_client_limits.py fix(gateway): tighten httpx keepalive and close whatsapp typing-response leak (#18451) 2026-05-02 02:23:37 -07:00
ADDING_A_PLATFORM.md feat(gateway): add LINE Messaging API platform plugin (#23197) 2026-05-10 06:40:46 -07:00
api_server.py fix(api-server): emit length/error finish_reason for truncation/failure (#22775) 2026-05-09 12:48:08 -07:00
base.py feat(telegram): native draft streaming via sendMessageDraft (Bot API 9.5+) 2026-05-10 20:02:50 -07:00
bluebubbles.py fix(gateway): tighten httpx keepalive and close whatsapp typing-response leak (#18451) 2026-05-02 02:23:37 -07:00
dingtalk.py fix(dingtalk): align override signatures with base + guard Optional[error] in tests 2026-05-09 11:11:10 -07:00
discord.py feat(session): make /handoff actually transfer the session live 2026-05-10 13:06:25 -07:00
email.py fix(email): use real hermes version in IMAP ID command 2026-05-09 13:35:50 -07:00
feishu.py fix(gateway): stream consumer first message drops thread context 2026-05-10 15:20:40 -07:00
feishu_comment.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00
feishu_comment_rules.py fix(feishu-comment): use get_hermes_home(); drop dead asyncio wrapper; AUTHOR_MAP 2026-04-17 19:04:11 -07:00
helpers.py fix(gateway): ensure deterministic thread eviction in helpers 2026-05-05 10:13:55 -07:00
homeassistant.py fix(gateway): correct ws scheme conversion for https urls 2026-05-03 03:54:03 -07:00
matrix.py feat(gateway): add allowed_{chats,channels,rooms} whitelist to Telegram, Mattermost, Matrix, DingTalk 2026-05-07 06:54:29 -07:00
mattermost.py feat(gateway): add allowed_{chats,channels,rooms} whitelist to Telegram, Mattermost, Matrix, DingTalk 2026-05-07 06:54:29 -07:00
msgraph_webhook.py fix(msgraph_webhook): harden auth surface + IP allowlisting + response hygiene 2026-05-08 10:29:58 -07:00
signal.py fix(signal): skip reactions for unauthorized senders 2026-05-04 01:38:21 -07:00
signal_rate_limit.py feat(gateway/signal): add support for multiple images sending 2026-04-30 04:28:08 -07:00
slack.py feat(session): make /handoff actually transfer the session live 2026-05-10 13:06:25 -07:00
sms.py test(sms): use clear=True in test_missing_phone_number_is_non_retryable 2026-05-04 05:25:09 -07:00
telegram.py feat(telegram): native draft streaming via sendMessageDraft (Bot API 9.5+) 2026-05-10 20:02:50 -07:00
telegram_network.py fix(gateway): keep DoH-confirmed Telegram IPs that match system DNS (#14520) 2026-05-05 04:42:59 -07:00
webhook.py fix(webhook): widen INSECURE_NO_AUTH loopback check + tests + docs 2026-05-07 07:38:43 -07:00
wecom.py fix(gateway): use monotonic deadlines in QR onboarding flows 2026-05-07 05:09:39 -07:00
wecom_callback.py fix(gateway): tighten httpx keepalive and close whatsapp typing-response leak (#18451) 2026-05-02 02:23:37 -07:00
wecom_crypto.py feat(gateway): add WeCom callback-mode adapter for self-built apps 2026-04-11 15:22:49 -07:00
weixin.py fix(weixin): wrap long copy-unfriendly lines 2026-05-07 06:08:06 -07:00
whatsapp.py feat(cross-platform): psutil for PID/process management + Windows footgun checker 2026-05-08 14:27:40 -07:00
yuanbao.py fix(yuanbao): enforce owner identity check on group slash commands 2026-04-30 23:57:55 -07:00
yuanbao_media.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00
yuanbao_proto.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00
yuanbao_sticker.py yuanbao platform (#16298) 2026-04-26 18:50:49 -07:00