入站和出站 Telegram 消息如何被路由、预览、确认和投递¶
运行时行为¶
- Telegram 消息处理在网关进程内运行。
- 路由是确定性的:Telegram 入站消息回复回 Telegram(模型不会选择通道)。
- 入站消息会规范化为共享通道信封,其中包含回复元数据、媒体占位符,以及网关观察到的回复的持久化回复链上下文。
- 群组会话按群组 ID 隔离。论坛主题会追加
:topic:<threadId>。 - 当机器人加入允许的群组或超级群组时,它会发布一条基于可用房间元数据的介绍:群组标题、描述和置顶消息。Telegram Bot API 无法读取机器人加入之前的群组消息,因此介绍绝不会声称使用先前的聊天历史。介绍默认启用,绝不在私聊中运行,可以通过
channels.telegram.joinIntro: false禁用,或通过channels.telegram.accounts.<accountId>.joinIntro按账户覆盖。参见 群组加入介绍 了解每房间一次的行为和不可信内容处理。 - 私信消息可以携带
message_thread_id;OpenClaw 会为回复保留它。仅当 TelegramgetMe报告该机器人的has_topics_enabled: true时,私信主题会话才会拆分;否则私信保持在扁平会话中。 - 长轮询在隔离的工作进程中运行。更新会保存到持久队列中,并按每个聊天和主题的顺序进行处理。
- 多账户启动限制了并发的
getMe探测,因此大型机器人集群不会一次性扇出所有账户探测。 - 每个网关进程守护长轮询,因此同一时间只有一个活动的轮询器可以使用一个机器人令牌。持续的
getUpdates409 冲突表明另一个 OpenClaw 网关、脚本或外部轮询器正在使用同一令牌。 - 如果在 120 秒内没有完成
getUpdates活跃性检查,轮询看门狗就会重启。 - Telegram Bot API 不支持已读回执(
sendReadReceipts不适用)。
Note
升级说明:Telegram 的默认预览在 2026.8.1 中已更改。 当 channels.telegram.streaming 未设置时,Telegram 在回合期间保留一个可编辑的状态草稿(代理的当前状态及其工具行),并将最终答案作为普通消息发送。它之前会将答案文本本身流式传输到预览中。不会有任何配置失效,也无需运行 doctor --fix;要保持先前的行为,请设置:
Note
channels.telegram.dm.threadReplies 和 channels.telegram.direct.<chatId>.threadReplies 已被移除。升级后,如果您的配置仍然包含这些键,请运行 openclaw doctor --fix。私信主题路由现在遵循 Telegram 的 getMe.has_topics_enabled(由 BotFather 线程模式控制):启用主题的机器人在 Telegram 发送 message_thread_id 时使用线程范围的私信会话;其他私信保持在扁平会话中。
对 replyToMode、streaming 和 textChunkLimit 的更改会在无需重新连接 Telegram 的情况下应用于下一个组装的回合,包括账户覆盖。
活动回合会保留其捕获的投递设置。
入站文本批处理¶
Telegram 默认会将来自同一发送者的快速文本消息批量合并为一个代理回合。普通文本等待 300ms 的静默窗口;至少 4000 个字符的非转发消息允许最多 1500ms 以等待可能的长粘贴续文。
- 短文本和长文本共享一个批次,因此简短介绍、长粘贴和简短跟进可以作为一个回合到达。消息 ID 不需要连续。
- 批次按机器人账户、发送者、聊天和主题保持隔离。回复元数据和源消息 ID 会被保留。
messages.inbound.byChannel.telegram覆盖messages.inbound.debounceMs,后者覆盖 300ms 的普通文本默认值。显式的0会禁用普通突发批处理,但保留自动长粘贴组装。- 控制命令绕过批处理并立即分发。停止/中止命令会取消其目标对话的待处理文本。
- 转发消息使用单独的 80ms 收集窗口,但共享发送者的分发队列,因此它们不能超越较早的文本。
普通文本批次限制为 12 条消息和 50,000 个字符。它们的收集截止时间为自第一条消息起 7.5 秒;如果配置的静默窗口更长,则使用该窗口。批次刷新后到达的消息无法加入该批次。这种启发式方法不保证 Telegram 会将每个粘贴片段一起投递。有关热重载行为,请参阅 入站防抖。
消息行为¶
实时流预览(消息编辑)
OpenClaw 在私聊、群组和主题中实时流式传输部分回复:发送一条预览消息,然后反复执行 `editMessageText`,就地完成。
- `channels.telegram.streaming` 为 `off | partial | block | progress`(默认:`progress`);设置 `mode: "partial"` 可将答案文本流式传输到预览中,而不是状态草稿
- 简短的最初答案预览会进行防抖,然后在有界延迟后具体化(如果运行仍然活动)
- `progress` 保留一个可编辑的状态草稿,当答案活动先于工具进度到达时显示稳定的状态标签,在完成时清除它,并将最终答案作为普通消息发送。默认情况下草稿是安静的:状态标题、评论、计划里程碑和审批请求。中间工具失败和非零命令退出会被隐藏;终端任务错误仍使用正常的错误投递。`streaming.progress.toolProgress: true` 会添加滚动的工具日志,包括工具失败。
- `streaming.preview.toolProgress` 控制工具/进度更新是否在 `partial` 和 `block` 模式下重用同一编辑后的预览消息(默认:预览流式传输活动时为 `true`)
- `streaming.preview.commandText` 控制这些行内的命令/执行细节:`status`(默认,仅工具标签)或 `raw`(显式命令文本)
- 默认情况下,已完成的助手前言会更新状态标题;新的前言会保留先前可读的状态直到其完成
- `streaming.progress.commentary`(默认:`false`)将这些前言显示为交错的评论行,而不是标题;评论在计划步骤旁边保持可见
- 成功的后台进程轮询和内部等待不会进入进度日志;失败仍遵循所选的工具进度策略,`/verbose` 保留诊断摘要
- 会检测遗留的 `channels.telegram.streamMode`、布尔值 `streaming` 和已退役的原生草稿预览键;运行 `openclaw doctor --fix` 以迁移它们
工具进度行是工具运行时显示的简短状态更新(命令执行、文件读取、计划更新、补丁摘要、应用服务器模式下的 Codex 前言/评论)。partial 和 block 预览默认显示这些状态;progress 草稿仅在 streaming.progress.toolProgress: true 时显示。压缩状态遵循相同的设置,并会在压缩开始时立即出现,包括在首个模型输出之前。
保留答案预览编辑,但隐藏工具进度行:
{
"channels": {
"telegram": {
"streaming": {
"mode": "partial",
"preview": { "toolProgress": false }
}
}
}
}
保持工具进度可见,但隐藏命令/执行文本:
{
"channels": {
"telegram": {
"streaming": {
"mode": "partial",
"preview": { "commandText": "status" }
}
}
}
}
progress 模式可以显示工具日志,而无需将最终答案编辑到该消息中。设置 toolProgress: true 即可启用,并将命令文本策略放在 streaming.progress 下:
{
"channels": {
"telegram": {
"streaming": {
"mode": "progress",
"progress": {
"toolProgress": true,
"commandText": "status"
}
}
}
}
}
streaming.mode: "off" 禁用预览编辑,并抑制通用的工具/进度消息,而不是将其作为独立状态消息发送;审批提示、媒体和错误仍通过正常最终投递路由。streaming.preview.toolProgress: false 仅保留答案预览编辑。
Note
选中的引用回复是例外。当 replyToMode 为 first、all 或 batched,且入站消息包含选中的引用文本时,OpenClaw 会通过 Telegram 的原生引用回复路径发送最终答案,并跳过该轮次的草稿预览。没有选中引用文本的当前消息回复仍然流式输出。启用回复线程后,它们的预览会携带自动引用摘录,并在原位置完成最终答案时保留这些摘录。当工具进度可见性比原生引用回复更重要时,请将 replyToMode 设置为 "off"。要保留原生引用回复并隐藏工具进度行,请在 progress 模式下使用 streaming.progress.toolProgress: false,或在 partial 和 block 模式下使用 streaming.preview.toolProgress: false。
对于纯文本回复:短预览会在原位置进行最终编辑;拆分为多条消息的长最终回复会将预览复用为第一块,然后仅发送剩余部分;progress 模式的最终回复会清除状态草稿并使用正常最终投递;如果在确认完成前最终编辑失败,OpenClaw 会回退到正常最终投递并清理过时的预览。对于复杂回复(媒体负载),OpenClaw 始终回退到正常最终投递并清理预览。
预览流式输出和块流式输出是互斥的。显式的非 off 预览模式会覆盖继承的 agents.defaults.blockStreamingDefault: "on";显式的 streaming.block.enabled: true 会覆盖预览。如果某一轮无法使用预览,继承的块投递仍然适用。
推理:/reasoning stream 在生成时将推理流式输出到实时预览中,然后在最终投递后删除推理预览(使用 /reasoning on 保持其可见)。最终答案发送时不包含推理文本。
原生命令与自定义命令
Telegram 的命令菜单在启动时通过 setMyCommands 注册。commands.native: "auto" 为 Telegram 启用原生命令。
添加自定义命令菜单条目:
{
channels: {
telegram: {
customCommands: [
{ command: "backup", description: "Git backup" },
{ command: "generate", description: "Create an image" },
],
},
},
}
规则:名称会被规范化(去除开头的 /,转为小写);有效模式为 a-z、0-9、_,长度 1-32;自定义命令不能覆盖原生命令;冲突/重复项会被跳过并记录日志。
当 Telegram 菜单限制需要裁剪时,配置的自定义命令优先,除非省略的按技能条目由前置的 /skill 回退项替换。
自定义命令仅是菜单条目——它们不会自动实现行为。插件/技能命令即使在 Telegram 菜单中未显示,输入时仍然可以工作。如果原生命令被禁用,内置命令会被移除;自定义/插件命令如果已配置,仍可能注册。
常见设置失败:
setMyCommands failed并带有BOT_COMMANDS_TOO_MUCH,在裁剪重试后表示菜单仍然溢出;减少插件/技能/自定义命令,或禁用channels.telegram.commands.native。deleteWebhook、deleteMyCommands或setMyCommands在直接 Bot API curl 命令正常工作时却失败并返回404: Not Found,通常意味着channels.telegram.apiRoot被设置为完整的/bot<TOKEN>端点。apiRoot必须是 Bot API 根路径;openclaw doctor --fix会移除意外添加的尾部/bot<TOKEN>。getMe returned 401表示 Telegram 拒绝了配置的机器人令牌。使用当前的 BotFather 令牌更新botToken、tokenFile或TELEGRAM_BOT_TOKEN(默认账户);OpenClaw 会在轮询之前停止,因此这不会被报告为 webhook 清理失败。setMyCommands failed并出现网络/获取错误,通常意味着到api.telegram.org的出站 DNS/HTTPS 被阻止。
设备配对命令(device-pair 插件)¶
安装后:
/pair生成设置代码- 将代码粘贴到 iOS 应用中
/pair pending列出待处理请求(包括角色/作用域)- 批准:
/pair approve <requestId>、/pair approve(仅待处理请求)或/pair approve latest
如果设备以更改后的身份验证详情(角色、权限范围、公钥)重试,则之前的待处理请求会被新的 requestId 取代;批准前请重新运行 /pair pending。
更多详情:[配对](../pairing.md#pair-via-telegram).
回复线程标签
生成输出中的显式回复线程标签:
[[reply_to_current]]— 回复触发消息[[reply_to:<id>]]— 回复特定消息 ID
channels.telegram.replyToMode:off(默认)、first、all。
当启用回复线程且原始文本/说明可用时,OpenClaw 会自动添加原生引用摘录。Telegram 将原生引用文本限制为 1024 个 UTF-16 码元;更长的消息将从开头引用,如果 Telegram 拒绝该引用,则回退为普通回复。
off 仅禁用隐式回复线程;显式 [[reply_to_*]] 标签仍会被遵循。
确认表情回应
`ackReaction` 会在 OpenClaw 处理入站消息时发送一个确认表情。`messages.ackReactionScope` 决定*何时*发送。
**表情解析顺序:**
- `channels.telegram.accounts.<accountId>.ackReaction`
- `channels.telegram.ackReaction`
- `messages.ackReaction`
- 代理身份表情回退(`agents.entries.*.identity.emoji`,否则为 "👀")
Telegram 期望一个 Unicode 表情(例如 "👀");使用 `""` 可为某个渠道或账户禁用该反应。
**作用域(`messages.ackReactionScope`,默认 `"group-mentions"`;无 Telegram 账户或 Telegram 渠道覆盖):**
`all`(私聊 + 群组,包括环境房间事件)、`direct`(仅私聊)、`group-all`(除环境房间事件外的所有群消息,不含私聊)、`group-mentions`(群组中机器人被提及时;**不含私聊** — 默认)、`off` / `none`(禁用)。
Note
默认作用域(group-mentions)不会在私聊或环境房间事件中触发确认表情。私聊请使用 direct 或 all;只有 all 会确认环境房间事件。更改遵循热重载,并应用于后续消息。每个组装的回合保留其捕获的值。
保留的群组历史
Telegram 群组和论坛主题使用近期自动上下文窗口以及显式历史读取。当 `requireMention: true` 时,被允许的未提及消息会被记录,而不会启动代理回合。后续被指向的回合会收到近期上下文,代理在需要更早讨论时可以使用 `message(action="read")`。
- `channels.telegram.historyLimit` 或 `messages.groupChat.historyLimit` 限制自动观察消息窗口(默认 50,硬性上限 200)。`0` 禁用自动历史注入,而不是禁用记录或显式读取。JSON 整数最大值(`9007199254740991`)会选择 50 条消息的默认值。
- 自动上下文在应用主题和发送者权限之前,会检查一个有界的近期片段。当其他主题或被排除的发送者主导该片段时,繁忙的群组可能提供的消息少于配置数量。代理可以通过显式历史读取向前翻页。
- 历史读取始终限定在已授权的账户、聊天和主题内。它们使用 Telegram 消息 ID 进行引用和分页;省略主题时,不得将主题范围的读取扩展到整个群组。
- 代理读取默认每页 50 条消息,最多 100 条。使用返回的 `oldestMessageId` 配合 `before` 读取更早的消息,使用 `after` 读取更新的消息,或使用 `messageId` 进行精确引用。这些读取需要代理已认证的当前群组/主题;它们不会获取 Telegram 服务器历史。
- `/new` 和 `/reset` 重置自动会话上下文,而不是保留的对话。显式历史读取可以检索被允许的更早讨论。
- 现有的 SQLite 插件状态表拥有保留的群消息。成功持久化的记录在 Gateway 重启后仍然存在,并且不会因消息数量而被逐出。私聊缓存行为仍保持有界。
- 首次使用时,现有的群缓存记录会原子地迁移到保留存储中。没有历史准入来源的旧记录(包括嵌入的回复祖先)仍可在现有深度和可见性限制内作为显式回复上下文使用;它们不会被当作经过验证的对话存档。
- 历史仅包含 OpenClaw 收到并被允许记录的消息。它无法恢复此功能之前被逐出的消息、Telegram 未投递的消息,或机器人加入之前的消息。媒体引用不保证附件字节会无限期可用。
无需单独的观察设置。请保持 Telegram [群组可见性](setup.md#privacy-mode-and-group-visibility) 启用,以便机器人接收普通消息。启用历史不会更改显式 `requireMention: false` 的激活方式。
Warning
旧版 OpenClaw 版本应用不同的缓存和插件配额规则。不要将它们用于扩展的保留历史。降级需要兼容的更新前备份;此功能不会添加数据库版本围栏。
限制和 CLI 目标
- `channels.telegram.textChunkLimit` 默认 4000;`streaming.chunkMode="newline"` 在按长度拆分之前优先选择段落边界(空行)。
- `channels.telegram.mediaMaxMb`(默认 100)限制入站和出站媒体大小。
- 当入站附件无法下载且消息继续传递给代理时,其正文会包含 `[media unavailable: ...]` 通知。超大通知会包含有效大小限制;部分相册会包含失败和总附件数量。这也适用于被允许的渠道帖子,即使其单独的聊天警告被抑制。
- 自动群组上下文使用 `channels.telegram.historyLimit` 或 `messages.groupChat.historyLimit`(默认 50);`0` 禁用自动窗口,而不是保留历史。
- 当网关已观察到父消息时,回复/引用/转发的补充上下文会归一化为一个选定的对话上下文窗口;观察消息缓存位于 OpenClaw SQLite 插件状态中。要导入六月前的缓存 sidecar 文件,请[通过 `2026.9.5` 升级](../../install/updating.md#upgrading-very-old-versions),并先运行其 Doctor。Telegram 每个更新仅包含一个浅层 `reply_to_message`,因此早于缓存的链仅限于该负载。
- Telegram 允许列表主要控制谁可以触发代理,而不是完整的补充上下文脱敏边界。
- 私聊历史:`channels.telegram.dmHistoryLimit`、`channels.telegram.dms["<user_id>"].historyLimit`。自动观察私聊上下文默认为 10 条消息,上限为 200;`0` 禁用该额外上下文,JSON 整数最大值会选择默认值。
- 这些渠道/账户/私聊字段还控制**用户回合**中嵌入的会话转录修剪,而不是观察消息。该独立限制器将 `0` 视为不修剪,并保留其现有逐出缓冲。Doctor 会保留有效的已保存限制(包括 JSON 整数最大值),因此不会悄悄更改转录上下文。
CLI 和 message-tool 发送目标接受数字聊天 ID、用户名或论坛主题目标:
```bash
openclaw message send --channel telegram --target 123456789 --message "hi"
openclaw message send --channel telegram --target @name --message "hi"
openclaw message send --channel telegram --target -1001234567890:topic:42 --message "hi topic"
```
投票使用 `openclaw message poll`,并支持论坛主题:
```bash
openclaw message poll --channel telegram --target 123456789 \
--poll-question "Ship it?" --poll-option "Yes" --poll-option "No"
openclaw message poll --channel telegram --target -1001234567890:topic:42 \
--poll-question "Pick a time" --poll-option "10am" --poll-option "2pm" \
--poll-duration-seconds 300 --poll-public
```
仅限 Telegram 的投票参数:`--poll-duration-seconds`(5-604800;最长七天)、`--poll-anonymous`、`--poll-public`、`--thread-id`(或 `:topic:` 目标)。`--poll-option` 可重复 2-12 次(Telegram 的选项上限)。
Telegram 发送还支持 `--presentation`,其中包含 `buttons` 块用于内联键盘(当 `channels.telegram.capabilities.inlineButtons` 允许时)、`--pin` 或 `--delivery '{"pin":true}'` 以在机器人可以在该聊天中置顶消息时请求置顶投递,以及 `--force-document` 以将出站图片、GIF 和视频作为文档发送,而不是作为压缩/动画/视频上传发送。
操作门控:`channels.telegram.actions.sendMessage=false` 会禁用所有出站消息,包括投票;`channels.telegram.actions.poll=false` 会禁用投票创建,同时保留普通发送功能。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw