fix(telegram): stream replies in-place without duplicate final sends

This commit is contained in:
Ayaan Zaidi
2026-02-15 20:09:10 +05:30
committed by Ayaan Zaidi
parent 8b2a5672be
commit a69e82765f
17 changed files with 575 additions and 210 deletions

View File

@@ -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).