fix(telegram): avoid rich final duplicate previews (#46206)
This commit is contained in:
@@ -1486,7 +1486,7 @@ streaming:
|
||||
edit_interval: 0.3 # Seconds between message edits
|
||||
buffer_threshold: 40 # Characters before forcing an edit flush
|
||||
cursor: " ▉" # Cursor shown during streaming
|
||||
fresh_final_after_seconds: 60 # Send fresh final (Telegram) when preview is this old; 0 = always edit in place
|
||||
fresh_final_after_seconds: 0 # Opt in to fresh final (Telegram) when preview is this old
|
||||
```
|
||||
|
||||
When enabled, the bot sends a message on the first token, then progressively edits it as more tokens arrive. Platforms that don't support message editing (Signal, Email, Home Assistant) are auto-detected on the first attempt — streaming is gracefully disabled for that session with no flood of messages.
|
||||
@@ -1495,7 +1495,7 @@ For separate natural mid-turn assistant updates without progressive token editin
|
||||
|
||||
**Overflow handling:** If the streamed text exceeds the platform's message length limit (~4096 chars), the current message is finalized and a new one starts automatically.
|
||||
|
||||
**Fresh final (Telegram):** Telegram's `editMessageText` preserves the original message timestamp, so a long-running streamed reply would keep the first-token timestamp even after completion. When `fresh_final_after_seconds > 0` (default `60`), the completed reply is delivered as a brand-new message (with the stale preview best-effort deleted) so Telegram's visible timestamp reflects completion time. Short previews still finalize in place. Set to `0` to always edit in place.
|
||||
**Fresh final (Telegram):** Telegram's `editMessageText` preserves the original message timestamp, so a long-running streamed reply would keep the first-token timestamp even after completion. Set `fresh_final_after_seconds > 0` to opt in to delivering old previews as brand-new final messages with best-effort preview deletion. The default is `0`, which always finalizes streamed replies in place and avoids the brief duplicate-message/delete sequence on clients that show both operations.
|
||||
|
||||
:::note Per-platform streaming defaults
|
||||
The master `streaming.enabled` switch is `false` by default — nothing streams until you flip it. Once enabled, streaming is decided **per platform**: Telegram ships with `display.platforms.telegram.streaming: true` (streams) and Discord with `display.platforms.discord.streaming: false` (does not). So after enabling streaming, Telegram streams out of the box and Discord stays on whole-message replies until you change its toggle. You can adjust these per-platform switches from the dashboard's **Channels** toggles or directly in `~/.hermes/config.yaml`.
|
||||
|
||||
@@ -900,7 +900,7 @@ gateway:
|
||||
|
||||
## Rendering: Rich Messages, Tables and Link Previews
|
||||
|
||||
**Rich Messages (Bot API 10.1).** Final replies are sent with Telegram's native [`sendRichMessage`](https://core.telegram.org/bots/api#sendrichmessage) using the agent's **raw markdown**, so tables, task lists, headings, nested blockquotes, collapsible `<details>`, footnotes/references, math/formulas, underline, sub/superscript, marked text, and anchors render natively — no client-side flattening. In DMs the live streaming preview also uses `sendRichMessageDraft`, so the animated draft matches the final rich message.
|
||||
**Rich Messages (Bot API 10.1).** When opted in, final replies are sent with Telegram's native [`sendRichMessage`](https://core.telegram.org/bots/api#sendrichmessage) using the agent's **raw markdown**, so tables, task lists, headings, nested blockquotes, collapsible `<details>`, footnotes/references, math/formulas, underline, sub/superscript, marked text, and anchors render natively — no client-side flattening. In DMs the live streaming preview also uses `sendRichMessageDraft`, so the animated draft matches the final rich message.
|
||||
|
||||
The rich path is skipped automatically when content exceeds the 32,768-byte rich text limit, and any rejection from Telegram (unsupported endpoint on an older `python-telegram-bot`, parser error, oversized blocks/columns) **transparently falls back** to the MarkdownV2 path — your message is never lost. Transient/network errors are *not* silently re-sent (no duplicate final message).
|
||||
|
||||
@@ -909,17 +909,17 @@ The rich path is skipped automatically when content exceeds the 32,768-byte rich
|
||||
- **Small tables** are flattened into **row-group bullets** — each row becomes a readable bulleted list under the column headings. Good for 2–4 columns and short cells.
|
||||
- **Larger or wider tables** fall back to a **fenced code block** with aligned columns so nothing collapses.
|
||||
|
||||
There's nothing to configure for the API fallback — the adapter picks the right rendering per message. If a Telegram client accepts but cannot render rich messages (for example, a watch client that shows them as opaque media blocks), opt out and force the MarkdownV2 path:
|
||||
Rich messages are disabled by default because some Telegram clients accept the Bot API payload but render it poorly. To opt in for clients that handle rich messages well:
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
platforms:
|
||||
telegram:
|
||||
extra:
|
||||
rich_messages: false
|
||||
rich_messages: true
|
||||
```
|
||||
|
||||
Rich messages are enabled by default (`rich_messages: true`). This setting is for client-rendering compatibility; Hermes already falls back automatically when Telegram rejects the rich API call. If you only want the legacy "always code-block" table behavior while keeping rich messages enabled, disable table normalization by setting `telegram.pretty_tables: false` in `config.yaml` (default: `true`).
|
||||
This setting is for client-rendering compatibility; Hermes already falls back automatically when Telegram rejects the rich API call. If you only want the legacy "always code-block" table behavior while keeping rich messages enabled, disable table normalization by setting `telegram.pretty_tables: false` in `config.yaml` (default: `true`).
|
||||
|
||||
**Link previews.** Telegram auto-generates link previews for URLs in bot messages. If you'd rather suppress those (long `/tools` output, agent reply that mentions ten links, etc.):
|
||||
|
||||
|
||||
+2
-2
@@ -1302,7 +1302,7 @@ streaming:
|
||||
edit_interval: 0.3 # 消息编辑之间的秒数
|
||||
buffer_threshold: 40 # 强制编辑刷新前的字符数
|
||||
cursor: " ▉" # 流式传输期间显示的光标
|
||||
fresh_final_after_seconds: 60 # 当预览超过此时间时发送新的最终消息(Telegram);0 = 始终就地编辑
|
||||
fresh_final_after_seconds: 0 # 预览超过此时间时选择发送新的最终消息(Telegram)
|
||||
```
|
||||
|
||||
启用后,bot 在第一个 token 时发送消息,然后随着更多 token 到来渐进式编辑它。不支持消息编辑的平台(Signal、Email、Home Assistant)在第一次尝试时自动检测 —— 该会话的流式传输被优雅地禁用,不会产生大量消息。
|
||||
@@ -1311,7 +1311,7 @@ streaming:
|
||||
|
||||
**溢出处理:** 如果流式传输的文本超过平台的消息长度限制(约 4096 字符),当前消息被最终化,新消息自动开始。
|
||||
|
||||
**新的最终消息(Telegram):** Telegram 的 `editMessageText` 保留原始消息时间戳,因此长时间运行的流式回复即使在完成后也会保留第一个 token 的时间戳。当 `fresh_final_after_seconds > 0`(默认 `60`)时,完成的回复作为全新消息传递(尽力删除旧预览),以便 Telegram 的可见时间戳反映完成时间。短预览仍然就地最终化。设置为 `0` 以始终就地编辑。
|
||||
**新的最终消息(Telegram):** Telegram 的 `editMessageText` 保留原始消息时间戳,因此长时间运行的流式回复即使在完成后也会保留第一个 token 的时间戳。设置 `fresh_final_after_seconds > 0` 可选择将旧预览作为全新的最终消息传递,并尽力删除旧预览。默认值为 `0`,始终就地最终化流式回复,避免某些客户端短暂显示重复消息再删除其中一条。
|
||||
|
||||
:::note
|
||||
主开关 `streaming.enabled` 默认为 `false`——在你启用之前不会有任何流式传输。启用后,是否流式传输按**平台**决定:Telegram 默认带有 `display.platforms.telegram.streaming: true`(流式传输),Discord 为 `display.platforms.discord.streaming: false`(不流式传输)。因此启用流式传输后,Telegram 开箱即用地流式传输,Discord 在你修改其开关之前仍使用整条消息回复。你可以在仪表盘的 **Channels** 开关中或直接在 `~/.hermes/config.yaml` 中调整这些按平台的开关。
|
||||
|
||||
+4
-4
@@ -877,7 +877,7 @@ gateway:
|
||||
|
||||
## 渲染:富消息、表格和链接预览
|
||||
|
||||
**富消息(Bot API 10.1)。** 最终回复通过 Telegram 原生的 [`sendRichMessage`](https://core.telegram.org/bots/api#sendrichmessage) 发送,使用 Agent 的**原始 markdown**,因此表格、任务列表、标题、嵌套引用块、可折叠的 `<details>`、脚注/引用、数学公式、下划线、上下标、高亮文本和锚点都能原生渲染——无需客户端展平。在私聊中,实时流式预览也使用 `sendRichMessageDraft`,因此动画草稿与最终的富消息保持一致。
|
||||
**富消息(Bot API 10.1)。** 选择启用后,最终回复通过 Telegram 原生的 [`sendRichMessage`](https://core.telegram.org/bots/api#sendrichmessage) 发送,使用 Agent 的**原始 markdown**,因此表格、任务列表、标题、嵌套引用块、可折叠的 `<details>`、脚注/引用、数学公式、下划线、上下标、高亮文本和锚点都能原生渲染——无需客户端展平。在私聊中,实时流式预览也使用 `sendRichMessageDraft`,因此动画草稿与最终的富消息保持一致。
|
||||
|
||||
当内容超过 32,768 字节的富文本上限时,富消息路径会自动跳过;Telegram 的任何拒绝(较旧 `python-telegram-bot` 不支持该端点、解析错误、块/列过多)都会**透明回退**到 MarkdownV2 路径——消息绝不会丢失。瞬时/网络错误**不会**被静默重发(不会产生重复的最终消息)。
|
||||
|
||||
@@ -886,17 +886,17 @@ gateway:
|
||||
- **小表格**被展平为**行组项目符号**——每行在列标题下变为可读的项目符号列表。适合 2-4 列和短单元格。
|
||||
- **较大或较宽的表格**回退为带对齐列的**围栏代码块**,以防内容折叠。
|
||||
|
||||
API 回退无需配置——适配器会为每条消息选择正确的渲染方式。如果某个 Telegram 客户端能接收但不能渲染富消息(例如手表客户端把它们显示为不透明媒体块),可以选择退出并强制使用 MarkdownV2 路径:
|
||||
富消息默认关闭,因为一些 Telegram 客户端能接收 Bot API 载荷但渲染效果很差。若你的客户端能良好处理富消息,可以选择启用:
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
platforms:
|
||||
telegram:
|
||||
extra:
|
||||
rich_messages: false
|
||||
rich_messages: true
|
||||
```
|
||||
|
||||
富消息默认启用(`rich_messages: true`)。这个设置用于客户端渲染兼容性;当 Telegram 拒绝富消息 API 调用时,Hermes 已经会自动回退。如果你只是想在保持富消息启用的同时恢复旧版「始终使用代码块」表格行为,可在 `config.yaml` 中设置 `telegram.pretty_tables: false` 禁用表格规范化(默认:`true`)。
|
||||
这个设置用于客户端渲染兼容性;当 Telegram 拒绝富消息 API 调用时,Hermes 已经会自动回退。如果你只是想在保持富消息启用的同时恢复旧版「始终使用代码块」表格行为,可在 `config.yaml` 中设置 `telegram.pretty_tables: false` 禁用表格规范化(默认:`true`)。
|
||||
|
||||
**链接预览。** Telegram 会为机器人消息中的 URL 自动生成链接预览。如果你希望抑制这些预览(长 `/tools` 输出、提及十个链接的 Agent 回复等):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user