
OpenClaw Telegram 插件可靠性工程指南从持久化入站到流式出站的维护者契约【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw本指南围绕 extensions/telegram/AGENTS.md 展开系统解析 OpenClaw 内置 Telegram 通道插件在消息可靠性、流式回答、上下文授权与交互面设计上的维护者决策与评审不变量。读完你将掌握核心持久的 ingress drain 如何与 Telegram 特有的轮询/Webhook 传输协同、为什么“先落盘再应答”是防丢消息的根基、以及任何改动如何通过 crash-window 与真实 Telegram 探测验证。extensions/telegram/AGENTS.md是仓库维护者为 Telegram 插件划定的“评审绑定不变量”——它不是实现细节的随记而是有意为之的架构决策。文档基于 Telegram Bot API 10.3 验证2026-08-24所有改动都应在此框架内进行。一、文档定位先理解插件边界在深入 Telegram 具体规则前必须同时阅读 extensions/AGENTS.md 中的插件边界规则。其核心结论是extensions/下的所有内置插件都遵循与第三方插件相同的边界约束——生产代码只允许从openclaw/plugin-sdk/*与插件自身的局部 barrel如api.ts、runtime-api.ts导入禁止直接导入src/**、src/channels/**或其它扩展的src/**插件运行时依赖归属插件自身package.json运行时不会自动安装依赖安装/更新/doctor 才是修复点插件可用性来自 manifest 所有权 定向激活不允许依赖 import 时的全局注册副作用。从文件结构看Telegram 插件是仓库中代码量最大的通道插件之一extensions/telegram/src 下按bot/grammY 中间件与派发、miniapp/Telegram Mini App、telegram-ingress-*持久化入站、bot-message-dispatch.*出站派发等维度组织并配套大量*.test.ts单测与*.runtime.test.ts运行时集成测试。这与本文档“行为改动需要真实证明、可靠性改动需要崩溃窗口证明”的评审标准互为表里。二、核心 drain 契约Telegram 不重复实现的东西2.1 契约归属文档明确指出入站持久化 drain 的核心契约不属于 Telegram 插件而是核心仓core的资产归属 src/channels/message/ingress-drain.ts以及 claim-owner、retry-policy 两个配套模块。Telegram 插件只是在核心 drain 之上做传输适配。对应的证据文件src/channels/message/ingress-drain.test.tssrc/channels/message/ingress-claim-owner.test.tssrc/channels/message/ingress-retry-policy.test.ts2.2 六条核心契约逐条解读结合 ingress-drain.ts 源码可以印证每一条契约的落地方式1. 完成行通过complete()墓碑化绝不delete()代码中completeClaimWithRetry负责写墓碑tombstonecreateIngressWriter提供的三个原语是completeClaimWithRetry/releaseClaim/failClaim。墓碑化保证了崩溃恢复时能看到“该事件已处理”的事实而物理删除则无法区分“从未处理”与“已处理但丢了记录”。2. 在“回合接管turn adoption”时 complete而不是在 settle 时源码生命周期中onAdopted回调里执行state.phase adopted; clearStallTimer(state); await completeClaimWithRetry(...)ingress-drain.ts。也就是说一旦事件被某个回合正式收养立即落墓碑并释放 lane而不是等整个回合结束。deferral延迟交接期间 claim 仍然被持有watchdog 保持武装watchdog 超时走共享的重试处置。3. 统一的重试策略attempt 下限 age 门槛默认 8 次 / 24 小时src/channels/message/ingress-retry-policy.ts 定义了四个默认值参数默认值含义maxAttempts8最大尝试次数attempt 下限deadLetterMinAgeMs24h死信最小年龄门槛baseMs1 000指数退避初始延迟factor 2无抖动maxMs3 * 60_000退避封顶关键语义在shouldDeadLetterRetryableIngressEvent死信必须同时满足 attempt ≥ maxAttempts 且 now - receivedAt ≥ 24h。超限但未到龄的事件会继续以封顶延迟重试直到年龄满足——这是为了避免刚收到的事件因瞬时故障被过早丢进死信。4. 派发/延迟期间以claimLeaseMs / 3为周期做 claim 续租心跳armClaimRefresh中intervalMs Math.max(1, Math.floor(claimLeaseMs / 3))ingress-drain.ts心跳一直持续到墓碑提交为止包含 complete 重试的 wedge 窗口。若refreshClaim返回 false说明 claim token 已被其它 owner 接管则触发markLeaseReclaimed的 guillotine 封闭后续onAdopted会抛IngressAdoptionLostError且不允许再 release/fail 他人持有的 claim。5. 瞬时失败绝不静默 complete——通过 disposition 走 release/failapplyFailureDisposition是唯一的失败出口GatewayDrainingError直接 release 且不消耗失败预算否则调用resolveIngressFailureDisposition得到fail死信或release保留待重试两种处置。6. supersede 只作用于收养前pre-adoptionsupersedeActiveIfNeeded只对未收养的工作生效收养后中断属于核心的 reply-run registry / queue interrupt 职责Telegram 侧不干预。2.3 崩溃窗口语义为什么“complete at adoption”是安全的源码注释点明了顺序敏感点“adoption 的 tombstone 重试 wedge 期间dispatch 副作用已经发生此时不能 release claim否则重放风险”。因此代码在墓碑重试期间把 phase 先置为adopted即使 tombstone 写入失败也保持 claim 持有避免同一事件被双发。这正是文档“Never silently complete on transient failure”背后的工程动机。三、Telegram 自有的传输与通道策略核心 drain 是通用骨架Telegram 插件在其上落实传输层策略主要证据集中在 extensions/telegram/src/telegram-ingress-spool.ts、telegram-ingress-worker.ts、telegram-ingress-drain-factory.ts。3.1 两种传输都必须“先持久化后应答”Durable-before-ack轮询Pollingingress worker 只有在父进程把 spool 提交writeTelegramSpooledUpdate并确认后才推进本地 offset。从 telegram-ingress-worker.ts 的消息协议可以看到 parent ↔ worker 之间存在显式的spool-ack命令ok: true/falseworker 仅在ok: true后才继续推进 offset。Webhook只有 spool 写入成功后才返回 HTTP 200写入失败返回非 200这本身就是 Telegram 的 redelivery 契约。为什么这是防丢消息的根基Telegram 的 getUpdates 以 offset 推进确认消息已消费Webhook 以 200 确认已接收。如果先应答后落盘进程在应答与落盘之间崩溃消息就永久丢失且 Telegram 不再重投。先落盘后应答把“至少一次投递”建立在本地持久化之上。3.2 update_id ↔ 事件 ID 编码与 lane 推导telegram-ingress-spool.ts提供resolveTelegramUpdateId必须是非负安全整数与telegramQueueEventIdupdate_id用 0 左填充到 16 位作为事件 ID保证事件 ID 稳定且可按顺序排序。lane 推导走getTelegramSequentialKeysequential-key形成 per-chat/per-topic 的串行化通道这部分逻辑必须留在 spool/lane 推导层不允许在别处另起炉灶。3.3 轮询与 Webhook 共用同一套 drain两种传输最终都走createTelegramTransportIngressDrain(...).drainOnce()即“先入队再泵一次 drain”禁止任何私有 claim 循环。telegram-ingress-drain-factory.ts的注释点明设计意图“一个 monitor 同时服务 polling webhookchannel 侧追加、共享 claim → dispatch 带 turnAdoptionLifecycle → 在收养时 complete”。回调callback_query的应答在onDurableAdmission落盘提交之后、claim 之前执行避免 callback 应答抹掉 Telegram 的重投路径。3.4 停滞超时OPENCLAW_TELEGRAM_SPOOLED_HANDLER_TIMEOUT_MS环境变量OPENCLAW_TELEGRAM_SPOOLED_HANDLER_TIMEOUT_MS映射到adoptionStallTimeoutMs默认5 分钟DEFAULT_INGRESS_ADOPTION_STALL_MS 5 * 60 * 1000。解析逻辑在 telegram-ingress-drain.ts优先取显式配置其次取环境变量最后回退默认值且经过clampPositiveTimerTimeoutMs校验。停滞 watchdog 在 claim→adoption 之间触发超时后走共享重试处置而非直接静默完成。3.5 不可重试分类器与 supersede 谓词不可重试分类器telegram-ingress-non-retryable.ts负责把“缺 harness、dispatch-dedupe 回滚”等场景判为不可重试直接 fail死信而非 release。supersede 谓词telegram-ingress-supersede.ts规定——只有文本消息、看起来已授权的显式命令以及待处理的 ambient room_event可以 supersede 未收养的同 lane 工作普通消息永远不能 supersede。room_event如“进入会话”的系统事件共享 sequential lane因此后续用户回合可以在收养前 supersede 它已收养的用户回合则绝不被触碰核心 drain 的 supersede 仅限收养前。3.6 禁止每消息全量存储写文档明确热路径上的 SQLite 写入必须是**逐条目per-entry**的禁止每次发送/读取都重写整份缓存。历史教训是 sent-message-cache 回归——重写缓存会让事件循环停顿而这个停顿会伪装成轮询停滞难以排查。3.7 传输错误分类getUpdates worker 的本地重试规则Bot API5xx与429本地重试并遵循parameters.retry_after401/404保持致命fatal409必须传播给父会话parent session由父会话负责 webhook-conflict 恢复解析 Bot API 错误体要防御性处理错误码在error_code而非.code且非 2xx 响应体不一定是 JSON例如 502 可能返回 HTML 页面。3.8 发送漏斗对等性Send funnel parity持久化漏斗send.ts与流式漏斗bot/delivery.*必须同样优雅降级触发场景降级行为富文本实体 400回退为纯文本caption 解析 400回退为纯 captionquote-not-found 400回退为传统回复legacy reply新增的恢复逻辑必须进共享谓词send-error-predicates.ts、reply-parameters.ts绝不只修其中一个漏斗——否则两个漏斗行为漂移同一消息在不同路径下表现不一致。3.9 出站洪峰等待与 webhook 安全顺序出站洪峰等待遵循retry_after封顶值为TELEGRAM_OUTBOUND_RETRY_AFTER_CAP_MS见 extensions/telegram/src/retry-after.ts值为 60_000ms不允许把 Telegram 发送重新钳制到通用通道重试上限。Webhook 安全顺序先校验 secret header常量时间比较、单 header 强制、401 时关闭连接然后才做请求限流限流预算只统计认证失败的尝试从而保证 Telegram 自身的投递永远不会被限流误伤。所有插件拥有的 undici 传输必须在所有退出路径关闭轮询会话、webhook 关闭与启动失败、probe-cache 驱逐。四、流式回答Streaming的维护者决策4.1 为什么禁用 sendMessageDraftTelegram 的 draft草稿只是私聊中 30 秒的临时预览最终投递仍需独立的sendMessage。OpenClaw 的流式实现采用sendMessage建立消息 editMessageText持续编辑 原地定稿用户看到的是一个持续存在的答案气泡而不是草稿闪烁。4.2 只拥有一个可见预览消息流式过程只允许一个可见的预览消息向前编辑它除非最终编辑真的失败否则不额外发送一条最终气泡。这与 4.1 结合保证用户在聊天里看到稳定单一的回答。4.3 保留首预览防抖如果 provider 发送 token 级增量应把增量合并为累积预览文本而不是移除防抖。防抖存在的意义是避免高频 delta 打爆 Bot API 调用配额。4.4 在 Telegram 层尊重 Telegram 限制文本超过4096 字符时链式拆分为续写消息投票保持当前 Bot API 的12 选项上限。这些限制必须在 Telegram 层处理不能指望上层语义自动适配。五、Telegram API 所有权5.1 优先 grammY 原生能力当 grammY 原语与 Telegram 原生 helper 已经能直接建模所需行为时优先复用 grammY禁止自造重复的 Bot API 包装。这与扩展边界哲学一致核心已拥有的能力不应在通道层重复实现。5.2 节流是 bot-token 作用域的所有使用同一 token 的 Telegram API 客户端共享同一个 grammYapiThrottler()实例。多实例节流会导致同一个 bot 的请求配额被重复计算超出 429 防护的设计意图。5.3 话题topic语义的两个硬规则不要在没有话题元数据的情况下静默重试失败的话题发送——投到错误表面wrong surface的成功比响亮的 Telegram 报错更糟糕DM 话题与论坛话题是两回事direct_messages_topic_id与message_thread_id不可互换。六、上下文与授权6.1 回复上下文的来源边界回复reply上下文只能来自OpenClaw 观察到的消息。虽然 Bot API 的 update 暴露reply_to_message但 Bot API没有任意getMessage(chat, id)的后期回填hydration路径——也就是说不能事后按 chatid 任意取回旧消息补上下文。因此回复链必须在接收时完整捕获。6.2 本地上下文优先于陈旧回复祖先提示词中当前本地聊天的上下文必须压过陈旧的回复祖先链。很久以前被回复的消息不应看起来像当前活跃对话。这是防止模型“跑题到旧话题”的关键提示工程约束。6.3 群组历史窗口永远开启、滚动前进群组的历史窗口永远开启并以historyLimit为界。文档明确禁止重引入 prompt-history 门控模式——那次回归曾让 ambient rooms房间事件类会话失明。历史窗口是滚动的用“自你上次回复以来”的自条目水位self-entry watermark选取视图禁止重引入破坏性清空。原因很实在room_event 不持久化到 session清掉的上下文无法恢复。6.4 授权模型的三个要点配对pairing仅限 DM。群组与话题的授权必须走显式配置的 allowlistTelegram allowlist 使用数字 sender ID。用户名是可选的、可变的不能作为 Bot API 中可靠的任意用户查询键群组与频道的可见回复由策略控制普通房间回复保持私密除非配置messages.groupChat.visibleReplies: automatic或 agent 显式调用message.send。七、交互面Interactive Surfaces原生回调保持结构化审批approval、原生命令native command、插件、单选select、多选multiselect回调不得作为原始回调文本透传回调值必须逐字节保真包括env|prod这类带分隔符的值——解析端依赖分隔符还原语义原生斜杠命令保持快路径fast-pathable在完整 workspace 与 agent-turn 初始化之前就能路由执行保证常用命令的低延迟响应。八、评审标准什么改动需要什么证明文档把证明要求分成两类这是所有 Telegram 相关 PR 的验收门槛改动类型覆盖范围证明要求行为改动传输transport、流式streaming、话题topics、回调callbacks、授权authorization、回复上下文reply context真实 Telegram 证明优先 bot-to-bot QA 通道或等效的真实 Telegram 探测禁止仅凭合成synthetic验证可靠性改动spool、drain、retry、ack、offset 路径crash-window 或 restart-replay 测试证明而非仅 happy-path 测试仓库内大量*.runtime.test.ts、*.e2e.test.ts与webhook.test.ts如对OPENCLAW_TELEGRAM_SPOOLED_HANDLER_TIMEOUT_MS的 env stub 验证正是这一标准的具体执行形态用重启重放与崩溃窗口测试来钉死持久化不变量。九、快速核对清单改任何extensions/telegram/下的代码前逐条自检是否触碰了核心 drain 契约complete-at-adoption、tombstone、claim 心跳、统一重试若是先读 src/channels/message/ingress-drain.ts确认没有在 Telegram 层重复实现入站是否仍满足“先持久化后应答”polling offset 在 spool 确认后推进webhook 在 spool 写成功后 200lane 推导与 update_id 编码是否仍在 spool/sequential-key 层失败是否走了 dispositionrelease/fail而非静默 complete流式是否仍单预览消息 原地定稿未重引入sendMessageDraft发送漏斗的两个路径send.ts与bot/delivery.*是否共享同一降级谓词授权是否用数字 sender ID、配对是否仅 DM、群组可见回复是否受策略控制回调值是否结构化且逐字节保真测试是否匹配评审标准行为改动有真实 Telegram 证明可靠性改动有 crash-window/restart-replay 证明这份清单直接映射本文档的全部不变量对每一项的深入实现都可以在extensions/telegram/src与其配套测试中找到对应代码证据。【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考