mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 06:27:27 +00:00
fix(telegram): stream replies in-place without duplicate final sends
This commit is contained in:
@@ -21,7 +21,7 @@ title: grammY
|
||||
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `channels.telegram.webhookUrl` + `channels.telegram.webhookSecret` are set (otherwise it long-polls).
|
||||
- **Sessions:** direct chats collapse into the agent main session (`agent:<agentId>:<mainKey>`); groups use `agent:<agentId>:telegram:group:<chatId>`; replies route back to the same channel.
|
||||
- **Config knobs:** `channels.telegram.botToken`, `channels.telegram.dmPolicy`, `channels.telegram.groups` (allowlist + mention defaults), `channels.telegram.allowFrom`, `channels.telegram.groupAllowFrom`, `channels.telegram.groupPolicy`, `channels.telegram.mediaMaxMb`, `channels.telegram.linkPreview`, `channels.telegram.proxy`, `channels.telegram.webhookSecret`, `channels.telegram.webhookUrl`, `channels.telegram.webhookHost`.
|
||||
- **Draft streaming:** optional `channels.telegram.streamMode` uses `sendMessageDraft` in private topic chats (Bot API 9.3+). This is separate from channel block streaming.
|
||||
- **Live stream preview:** optional `channels.telegram.streamMode` sends a temporary message and updates it with `editMessageText`. This is separate from channel block streaming.
|
||||
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
||||
|
||||
Open questions
|
||||
|
||||
@@ -221,23 +221,20 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
## Feature reference
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Draft streaming in Telegram DMs">
|
||||
OpenClaw can stream partial replies with Telegram draft bubbles (`sendMessageDraft`).
|
||||
<Accordion title="Live stream preview (message edits)">
|
||||
OpenClaw can stream partial replies by sending a temporary Telegram message and editing it as text arrives.
|
||||
|
||||
Requirements:
|
||||
Requirement:
|
||||
|
||||
- `channels.telegram.streamMode` is not `"off"` (default: `"partial"`)
|
||||
- private chat
|
||||
- inbound update includes `message_thread_id`
|
||||
- bot topics are enabled (`getMe().has_topics_enabled`)
|
||||
|
||||
Modes:
|
||||
|
||||
- `off`: no draft streaming
|
||||
- `partial`: frequent draft updates from partial text
|
||||
- `block`: chunked draft updates using `channels.telegram.draftChunk`
|
||||
- `off`: no live preview
|
||||
- `partial`: frequent preview updates from partial text
|
||||
- `block`: chunked preview updates using `channels.telegram.draftChunk`
|
||||
|
||||
`draftChunk` defaults for block mode:
|
||||
`draftChunk` defaults for `streamMode: "block"`:
|
||||
|
||||
- `minChars: 200`
|
||||
- `maxChars: 800`
|
||||
@@ -245,13 +242,17 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
|
||||
`maxChars` is clamped by `channels.telegram.textChunkLimit`.
|
||||
|
||||
Draft streaming is DM-only; groups/channels do not use draft bubbles.
|
||||
This works in direct chats and groups/topics.
|
||||
|
||||
If you want early real Telegram messages instead of draft updates, use block streaming (`channels.telegram.blockStreaming: true`).
|
||||
For text-only replies, OpenClaw keeps the same preview message and performs a final edit in place (no second message).
|
||||
|
||||
For complex replies (for example media payloads), OpenClaw falls back to normal final delivery and then cleans up the preview message.
|
||||
|
||||
`streamMode` is separate from block streaming. When block streaming is explicitly enabled for Telegram, OpenClaw skips the preview stream to avoid double-streaming.
|
||||
|
||||
Telegram-only reasoning stream:
|
||||
|
||||
- `/reasoning stream` sends reasoning to the draft bubble while generating
|
||||
- `/reasoning stream` sends reasoning to the live preview while generating
|
||||
- final answer is sent without reasoning text
|
||||
|
||||
</Accordion>
|
||||
@@ -703,7 +704,7 @@ Primary reference:
|
||||
- `channels.telegram.textChunkLimit`: outbound chunk size (chars).
|
||||
- `channels.telegram.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.telegram.linkPreview`: toggle link previews for outbound messages (default: true).
|
||||
- `channels.telegram.streamMode`: `off | partial | block` (draft streaming).
|
||||
- `channels.telegram.streamMode`: `off | partial | block` (live stream preview).
|
||||
- `channels.telegram.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
- `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
||||
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to disabled on Node 22 to avoid Happy Eyeballs timeouts.
|
||||
@@ -727,7 +728,7 @@ Telegram-specific high-signal fields:
|
||||
- access control: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`, `groups.*.topics.*`
|
||||
- command/menu: `commands.native`, `customCommands`
|
||||
- threading/replies: `replyToMode`
|
||||
- streaming: `streamMode`, `draftChunk`, `blockStreaming`
|
||||
- streaming: `streamMode` (preview), `draftChunk`, `blockStreaming`
|
||||
- formatting/delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix`
|
||||
- media/network: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `proxy`
|
||||
- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
summary: "Streaming + chunking behavior (block replies, draft streaming, limits)"
|
||||
summary: "Streaming + chunking behavior (block replies, Telegram preview streaming, limits)"
|
||||
read_when:
|
||||
- Explaining how streaming or chunking works on channels
|
||||
- Changing block streaming or channel chunking behavior
|
||||
- Debugging duplicate/early block replies or draft streaming
|
||||
- Debugging duplicate/early block replies or Telegram preview streaming
|
||||
title: "Streaming and Chunking"
|
||||
---
|
||||
|
||||
@@ -12,9 +12,9 @@ title: "Streaming and Chunking"
|
||||
OpenClaw has two separate “streaming” layers:
|
||||
|
||||
- **Block streaming (channels):** emit completed **blocks** as the assistant writes. These are normal channel messages (not token deltas).
|
||||
- **Token-ish streaming (Telegram only):** update a **draft bubble** with partial text while generating; final message is sent at the end.
|
||||
- **Token-ish streaming (Telegram only):** update a temporary **preview message** with partial text while generating.
|
||||
|
||||
There is **no real token streaming** to external channel messages today. Telegram draft streaming is the only partial-stream surface.
|
||||
There is **no true token-delta streaming** to channel messages today. Telegram preview streaming is the only partial-stream surface.
|
||||
|
||||
## Block streaming (channel messages)
|
||||
|
||||
@@ -99,37 +99,38 @@ This maps to:
|
||||
- **No block streaming:** `blockStreamingDefault: "off"` (only final reply).
|
||||
|
||||
**Channel note:** For non-Telegram channels, block streaming is **off unless**
|
||||
`*.blockStreaming` is explicitly set to `true`. Telegram can stream drafts
|
||||
`*.blockStreaming` is explicitly set to `true`. Telegram can stream a live preview
|
||||
(`channels.telegram.streamMode`) without block replies.
|
||||
|
||||
Config location reminder: the `blockStreaming*` defaults live under
|
||||
`agents.defaults`, not the root config.
|
||||
|
||||
## Telegram draft streaming (token-ish)
|
||||
## Telegram preview streaming (token-ish)
|
||||
|
||||
Telegram is the only channel with draft streaming:
|
||||
Telegram is the only channel with live preview streaming:
|
||||
|
||||
- Uses Bot API `sendMessageDraft` in **private chats with topics**.
|
||||
- Uses Bot API `sendMessage` (first update) + `editMessageText` (subsequent updates).
|
||||
- `channels.telegram.streamMode: "partial" | "block" | "off"`.
|
||||
- `partial`: draft updates with the latest stream text.
|
||||
- `block`: draft updates in chunked blocks (same chunker rules).
|
||||
- `off`: no draft streaming.
|
||||
- Draft chunk config (only for `streamMode: "block"`): `channels.telegram.draftChunk` (defaults: `minChars: 200`, `maxChars: 800`).
|
||||
- Draft streaming is separate from block streaming; block replies are off by default and only enabled by `*.blockStreaming: true` on non-Telegram channels.
|
||||
- Final reply is still a normal message.
|
||||
- `/reasoning stream` writes reasoning into the draft bubble (Telegram only).
|
||||
|
||||
When draft streaming is active, OpenClaw disables block streaming for that reply to avoid double-streaming.
|
||||
- `partial`: preview updates with latest stream text.
|
||||
- `block`: preview updates in chunked blocks (same chunker rules).
|
||||
- `off`: no preview streaming.
|
||||
- Preview chunk config (only for `streamMode: "block"`): `channels.telegram.draftChunk` (defaults: `minChars: 200`, `maxChars: 800`).
|
||||
- Preview streaming is separate from block streaming.
|
||||
- When Telegram block streaming is explicitly enabled, preview streaming is skipped to avoid double-streaming.
|
||||
- Text-only finals are applied by editing the preview message in place.
|
||||
- Non-text/complex finals fall back to normal final message delivery.
|
||||
- `/reasoning stream` writes reasoning into the live preview (Telegram only).
|
||||
|
||||
```
|
||||
Telegram (private + topics)
|
||||
└─ sendMessageDraft (draft bubble)
|
||||
├─ streamMode=partial → update latest text
|
||||
└─ streamMode=block → chunker updates draft
|
||||
└─ final reply → normal message
|
||||
Telegram
|
||||
└─ sendMessage (temporary preview message)
|
||||
├─ streamMode=partial → edit latest text
|
||||
└─ streamMode=block → chunker + edit updates
|
||||
└─ final text-only reply → final edit on same message
|
||||
└─ fallback: cleanup preview + normal final delivery (media/complex)
|
||||
```
|
||||
|
||||
Legend:
|
||||
|
||||
- `sendMessageDraft`: Telegram draft bubble (not a real message).
|
||||
- `final reply`: normal Telegram message send.
|
||||
- `preview message`: temporary Telegram message updated during generation.
|
||||
- `final edit`: in-place edit on the same preview message (text-only).
|
||||
|
||||
@@ -155,7 +155,7 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat
|
||||
|
||||
- Bot token: `channels.telegram.botToken` or `channels.telegram.tokenFile`, with `TELEGRAM_BOT_TOKEN` as fallback for the default account.
|
||||
- `configWrites: false` blocks Telegram-initiated config writes (supergroup ID migrations, `/config set|unset`).
|
||||
- Draft streaming uses Telegram `sendMessageDraft` (requires private chat topics).
|
||||
- Telegram stream previews use `sendMessage` + `editMessageText` (works in direct and group chats).
|
||||
- Retry policy: see [Retry policy](/concepts/retry).
|
||||
|
||||
### Discord
|
||||
|
||||
Reference in New Issue
Block a user