@poolzin/pool-bot 2026.3.25 → 2026.3.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/dist/agents/model-fallback.js +5 -4
  2. package/dist/agents/tools/common.js +16 -201
  3. package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
  4. package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
  5. package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
  6. package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
  7. package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
  8. package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
  9. package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
  10. package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
  11. package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
  12. package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
  13. package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
  14. package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
  15. package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
  16. package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
  17. package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
  18. package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
  19. package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
  20. package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
  21. package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
  22. package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
  23. package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
  24. package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
  25. package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
  26. package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
  27. package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
  28. package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
  29. package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
  30. package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
  31. package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
  32. package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
  33. package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
  34. package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
  35. package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
  36. package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
  37. package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
  38. package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
  39. package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
  40. package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
  41. package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
  42. package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
  43. package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
  44. package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
  45. package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
  46. package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
  47. package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
  48. package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
  49. package/dist/build-info.json +3 -3
  50. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  51. package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
  52. package/dist/canvas-host/a2ui/index.html +1 -307
  53. package/dist/channels/channels/directory-config.js +185 -0
  54. package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
  55. package/dist/channels/channels/discord/handle-action.js +165 -0
  56. package/dist/channels/channels/discord.js +413 -0
  57. package/dist/channels/channels/dock.js +436 -0
  58. package/dist/channels/channels/index.js +51 -0
  59. package/dist/channels/channels/plugins/outbound/discord.js +101 -0
  60. package/dist/channels/channels/whatsapp.js +17 -0
  61. package/dist/channels/plugins/types.js +1 -1
  62. package/dist/channels/run-state-machine.js +7 -0
  63. package/dist/commands/models/auth.js +47 -1
  64. package/dist/commands-subagents/action-agents.js +44 -0
  65. package/dist/commands-subagents/action-focus.js +64 -0
  66. package/dist/commands-subagents/action-help.js +4 -0
  67. package/dist/commands-subagents/action-info.js +45 -0
  68. package/dist/commands-subagents/action-kill.js +60 -0
  69. package/dist/commands-subagents/action-list.js +44 -0
  70. package/dist/commands-subagents/action-log.js +29 -0
  71. package/dist/commands-subagents/action-send.js +119 -0
  72. package/dist/commands-subagents/action-spawn.js +52 -0
  73. package/dist/commands-subagents/action-unfocus.js +30 -0
  74. package/dist/commands-subagents/shared.js +303 -0
  75. package/dist/config/config.js +1 -8
  76. package/dist/config/types.secrets.js +61 -0
  77. package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
  78. package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
  79. package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
  80. package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
  81. package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
  82. package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
  83. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
  84. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
  85. package/dist/control-ui/index.html +1 -1
  86. package/dist/gateway/method-scopes.js +9 -1
  87. package/dist/gateway/node-pending-work.js +142 -0
  88. package/dist/gateway/protocol/index.js +5 -1
  89. package/dist/gateway/protocol/schema/nodes.js +18 -0
  90. package/dist/gateway/server-methods/nodes-pending.js +96 -0
  91. package/dist/gateway/server-methods-list.js +4 -0
  92. package/dist/gateway/server-methods.js +2 -0
  93. package/dist/imessage/channel.js +253 -0
  94. package/dist/imessage/monitor/echo-cache.js +70 -0
  95. package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
  96. package/dist/imessage/monitor/reflection-guard.js +50 -0
  97. package/dist/imessage/monitor/sanitize-outbound.js +25 -0
  98. package/dist/imessage/monitor/self-chat-cache.js +75 -0
  99. package/dist/imessage/runtime.js +3 -0
  100. package/dist/infra/exec-approval-reply.js +7 -0
  101. package/dist/infra/tmp-openclaw-dir.js +84 -0
  102. package/dist/pairing/pairing-challenge.js +15 -0
  103. package/dist/plugin-sdk/account-id.d.ts +1 -0
  104. package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
  105. package/dist/plugin-sdk/allow-from.d.ts +27 -0
  106. package/dist/plugin-sdk/command-auth.d.ts +25 -0
  107. package/dist/plugin-sdk/command-auth.js +3 -1
  108. package/dist/plugin-sdk/config-paths.d.ts +6 -0
  109. package/dist/plugin-sdk/file-lock.d.ts +16 -0
  110. package/dist/plugin-sdk/index.d.ts +428 -0
  111. package/dist/plugin-sdk/index.js +237 -103
  112. package/dist/plugin-sdk/json-store.d.ts +5 -0
  113. package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
  114. package/dist/plugin-sdk/onboarding.d.ts +11 -0
  115. package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
  116. package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
  117. package/dist/plugin-sdk/status-helpers.d.ts +25 -0
  118. package/dist/plugin-sdk/temp-path.d.ts +12 -0
  119. package/dist/plugin-sdk/text-chunking.d.ts +1 -0
  120. package/dist/plugin-sdk/tool-send.d.ts +4 -0
  121. package/dist/plugin-sdk/webhook-path.d.ts +6 -0
  122. package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
  123. package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
  124. package/dist/plugin-sdk-internal/accounts.js +6 -0
  125. package/dist/plugin-sdk-internal/discord.js +23 -0
  126. package/dist/plugin-sdk-internal/imessage.js +13 -0
  127. package/dist/plugin-sdk-internal/setup.js +9 -0
  128. package/dist/plugin-sdk-internal/signal.js +13 -0
  129. package/dist/plugin-sdk-internal/slack.js +22 -0
  130. package/dist/plugin-sdk-internal/telegram.js +32 -0
  131. package/dist/plugin-sdk-internal/whatsapp.js +29 -0
  132. package/dist/routing/session-key.js +4 -185
  133. package/dist/shared/pid-alive.js +2 -61
  134. package/dist/shared/process-scoped-map.js +5 -7
  135. package/dist/signal/channel.js +264 -0
  136. package/dist/signal/monitor/access-policy.js +60 -0
  137. package/dist/signal/runtime.js +3 -0
  138. package/dist/slack/account-inspect.js +135 -0
  139. package/dist/slack/blocks-input.js +7 -38
  140. package/dist/slack/channel.js +394 -0
  141. package/dist/slack/interactive-replies.js +28 -0
  142. package/dist/slack/monitor/channel-type.js +31 -0
  143. package/dist/slack/monitor/dm-auth.js +49 -0
  144. package/dist/slack/monitor/events/interactions.modal.js +137 -0
  145. package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
  146. package/dist/slack/monitor/events/system-event-context.js +29 -0
  147. package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
  148. package/dist/slack/monitor/external-arg-menu-store.js +46 -0
  149. package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
  150. package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
  151. package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
  152. package/dist/slack/monitor/reconnect-policy.js +78 -0
  153. package/dist/slack/monitor/slash-commands.runtime.js +1 -0
  154. package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
  155. package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
  156. package/dist/slack/resolve-allowlist-common.js +36 -0
  157. package/dist/slack/runtime.js +3 -0
  158. package/dist/slack/sent-thread-cache.js +61 -0
  159. package/dist/slack/truncate.js +10 -0
  160. package/dist/telegram/account-inspect.js +175 -0
  161. package/dist/telegram/allow-from.js +10 -0
  162. package/dist/telegram/api-fetch.js +18 -0
  163. package/dist/telegram/approval-buttons.js +30 -0
  164. package/dist/telegram/audit-membership-runtime.js +61 -0
  165. package/dist/telegram/bot/delivery.replies.js +508 -0
  166. package/dist/telegram/bot/delivery.resolve-media.js +227 -0
  167. package/dist/telegram/bot/delivery.send.js +132 -0
  168. package/dist/telegram/bot/reply-threading.js +46 -0
  169. package/dist/telegram/bot-message-context.body.js +186 -0
  170. package/dist/telegram/bot-message-context.session.js +207 -0
  171. package/dist/telegram/bot-message-context.types.js +1 -0
  172. package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
  173. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  174. package/dist/telegram/bot.media.test-utils.js +81 -0
  175. package/dist/telegram/channel-actions.js +225 -0
  176. package/dist/telegram/channel.js +515 -0
  177. package/dist/telegram/conversation-route.js +107 -0
  178. package/dist/telegram/delivery.js +2 -0
  179. package/dist/telegram/delivery.replies.js +508 -0
  180. package/dist/telegram/dm-access.js +86 -0
  181. package/dist/telegram/draft-stream.test-helpers.js +62 -0
  182. package/dist/telegram/exec-approvals-handler.js +281 -0
  183. package/dist/telegram/exec-approvals.js +62 -0
  184. package/dist/telegram/forum-service-message.js +22 -0
  185. package/dist/telegram/group-config-helpers.js +10 -0
  186. package/dist/telegram/lane-delivery-state.js +19 -0
  187. package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
  188. package/dist/telegram/lane-delivery.js +2 -0
  189. package/dist/telegram/normalize.js +37 -0
  190. package/dist/telegram/onboarding.js +192 -0
  191. package/dist/telegram/outbound-adapter.js +100 -0
  192. package/dist/telegram/polling-session.js +275 -0
  193. package/dist/telegram/runtime.js +3 -0
  194. package/dist/telegram/sendchataction-401-backoff.js +71 -0
  195. package/dist/telegram/sequential-key.js +46 -0
  196. package/dist/telegram/status-issues.js +105 -0
  197. package/dist/telegram/target-writeback.js +165 -0
  198. package/dist/telegram/thread-bindings.js +560 -0
  199. package/dist/utils.js +32 -257
  200. package/dist/wizard/prompts.js +5 -5
  201. package/extensions/feishu/src/policy.ts +1 -1
  202. package/extensions/firecrawl/index.test.ts +82 -0
  203. package/extensions/firecrawl/index.ts +20 -0
  204. package/extensions/firecrawl/openclaw.plugin.json +8 -0
  205. package/extensions/firecrawl/package.json +12 -0
  206. package/extensions/firecrawl/src/config.ts +159 -0
  207. package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
  208. package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
  209. package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
  210. package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
  211. package/package.json +1 -1
  212. package/dist/.buildstamp +0 -1
  213. package/dist/acp/bindings-store.js +0 -209
  214. package/dist/acp/control-plane/runtime-cache.js +0 -54
  215. package/dist/acp/control-plane/runtime-options.js +0 -215
  216. package/dist/acp/control-plane/session-actor-queue.js +0 -36
  217. package/dist/acp/index.js +0 -2
  218. package/dist/acp/runtime/errors.js +0 -47
  219. package/dist/acp/runtime/registry.js +0 -86
  220. package/dist/acp/secret-file.js +0 -22
  221. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
  222. package/dist/agents/bash-process-registry.test-helpers.js +0 -29
  223. package/dist/agents/bash-tools.exec-approval-request.js +0 -20
  224. package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
  225. package/dist/agents/bash-tools.exec-host-node.js +0 -235
  226. package/dist/agents/checkpoint-manager.js +0 -290
  227. package/dist/agents/claude-cli-runner.js +0 -3
  228. package/dist/agents/error-classifier.js +0 -251
  229. package/dist/agents/live-model-filter.js +0 -84
  230. package/dist/agents/nvidia-models.js +0 -228
  231. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
  232. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
  233. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
  234. package/dist/agents/provider/config-loader.js +0 -76
  235. package/dist/agents/provider/index.js +0 -15
  236. package/dist/agents/provider/models-dev.js +0 -129
  237. package/dist/agents/provider/session-binding.js +0 -376
  238. package/dist/agents/queued-file-writer.js +0 -22
  239. package/dist/agents/skills/bundled-context.js +0 -23
  240. package/dist/agents/skills/security.js +0 -211
  241. package/dist/agents/skills/tools-dir.js +0 -9
  242. package/dist/agents/skills-install-download.js +0 -290
  243. package/dist/agents/skills-install-output.js +0 -30
  244. package/dist/agents/skills-install.download-test-utils.js +0 -36
  245. package/dist/agents/skills.test-helpers.js +0 -13
  246. package/dist/agents/subagent-announce-reliability.js +0 -160
  247. package/dist/agents/subagent-registry.mocks.shared.js +0 -12
  248. package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
  249. package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
  250. package/dist/agents/test-helpers/fast-core-tools.js +0 -8
  251. package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
  252. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
  253. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
  254. package/dist/agents/tool-display-common.js +0 -915
  255. package/dist/agents/tool-policy-shared.js +0 -108
  256. package/dist/agents/tool-policy.conformance.js +0 -14
  257. package/dist/agents/tool-result-truncation.js +0 -299
  258. package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
  259. package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
  260. package/dist/agents/tools/discord-actions-presence.js +0 -78
  261. package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
  262. package/dist/discord/discord-improvements.js +0 -167
  263. package/dist/discord/index.js +0 -2
  264. package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
  265. package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
  266. package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
  267. package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
  268. package/dist/whatsapp/normalize.js +0 -66
  269. package/dist/whatsapp/resolve-outbound-target.js +0 -42
  270. /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
  271. /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
@@ -0,0 +1,100 @@
1
+ import { resolvePayloadMediaUrls, sendPayloadMediaSequence, } from "../../../src/channels/plugins/outbound/direct-text-media.js";
2
+ import { resolveOutboundSendDep, } from "../../../src/infra/outbound/send-deps.js";
3
+ import { markdownToTelegramHtmlChunks } from "./format.js";
4
+ import { parseTelegramReplyToMessageId, parseTelegramThreadId } from "./outbound-params.js";
5
+ import { sendMessageTelegram } from "./send.js";
6
+ function resolveTelegramSendContext(params) {
7
+ const send = resolveOutboundSendDep(params.deps, "telegram") ?? sendMessageTelegram;
8
+ return {
9
+ send,
10
+ baseOpts: {
11
+ verbose: false,
12
+ textMode: "html",
13
+ cfg: params.cfg,
14
+ messageThreadId: parseTelegramThreadId(params.threadId),
15
+ replyToMessageId: parseTelegramReplyToMessageId(params.replyToId),
16
+ accountId: params.accountId ?? undefined,
17
+ },
18
+ };
19
+ }
20
+ export async function sendTelegramPayloadMessages(params) {
21
+ const telegramData = params.payload.channelData?.telegram;
22
+ const quoteText = typeof telegramData?.quoteText === "string" ? telegramData.quoteText : undefined;
23
+ const text = params.payload.text ?? "";
24
+ const mediaUrls = resolvePayloadMediaUrls(params.payload);
25
+ const payloadOpts = {
26
+ ...params.baseOpts,
27
+ quoteText,
28
+ };
29
+ if (mediaUrls.length === 0) {
30
+ return await params.send(params.to, text, {
31
+ ...payloadOpts,
32
+ buttons: telegramData?.buttons,
33
+ });
34
+ }
35
+ // Telegram allows reply_markup on media; attach buttons only to the first send.
36
+ const finalResult = await sendPayloadMediaSequence({
37
+ text,
38
+ mediaUrls,
39
+ send: async ({ text, mediaUrl, isFirst }) => await params.send(params.to, text, {
40
+ ...payloadOpts,
41
+ mediaUrl,
42
+ ...(isFirst ? { buttons: telegramData?.buttons } : {}),
43
+ }),
44
+ });
45
+ return finalResult ?? { messageId: "unknown", chatId: params.to };
46
+ }
47
+ export const telegramOutbound = {
48
+ deliveryMode: "direct",
49
+ chunker: markdownToTelegramHtmlChunks,
50
+ chunkerMode: "markdown",
51
+ textChunkLimit: 4000,
52
+ sendText: async ({ cfg, to, text, accountId, deps, replyToId, threadId }) => {
53
+ const { send, baseOpts } = resolveTelegramSendContext({
54
+ cfg,
55
+ deps,
56
+ accountId,
57
+ replyToId,
58
+ threadId,
59
+ });
60
+ const result = await send(to, text, {
61
+ ...baseOpts,
62
+ });
63
+ return { channel: "telegram", ...result };
64
+ },
65
+ sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, accountId, deps, replyToId, threadId, forceDocument, }) => {
66
+ const { send, baseOpts } = resolveTelegramSendContext({
67
+ cfg,
68
+ deps,
69
+ accountId,
70
+ replyToId,
71
+ threadId,
72
+ });
73
+ const result = await send(to, text, {
74
+ ...baseOpts,
75
+ mediaUrl,
76
+ mediaLocalRoots,
77
+ forceDocument: forceDocument ?? false,
78
+ });
79
+ return { channel: "telegram", ...result };
80
+ },
81
+ sendPayload: async ({ cfg, to, payload, mediaLocalRoots, accountId, deps, replyToId, threadId, }) => {
82
+ const { send, baseOpts } = resolveTelegramSendContext({
83
+ cfg,
84
+ deps,
85
+ accountId,
86
+ replyToId,
87
+ threadId,
88
+ });
89
+ const result = await sendTelegramPayloadMessages({
90
+ send,
91
+ to,
92
+ payload,
93
+ baseOpts: {
94
+ ...baseOpts,
95
+ mediaLocalRoots,
96
+ },
97
+ });
98
+ return { channel: "telegram", ...result };
99
+ },
100
+ };
@@ -0,0 +1,275 @@
1
+ import { run } from "@grammyjs/runner";
2
+ import { computeBackoff, sleepWithAbort } from "../../../src/infra/backoff.js";
3
+ import { formatErrorMessage } from "../../../src/infra/errors.js";
4
+ import { formatDurationPrecise } from "../../../src/infra/format-time/format-duration.ts";
5
+ import { withTelegramApiErrorLogging } from "./api-logging.js";
6
+ import { createTelegramBot } from "./bot.js";
7
+ import { isRecoverableTelegramNetworkError } from "./network-errors.js";
8
+ const TELEGRAM_POLL_RESTART_POLICY = {
9
+ initialMs: 2000,
10
+ maxMs: 30_000,
11
+ factor: 1.8,
12
+ jitter: 0.25,
13
+ };
14
+ const POLL_STALL_THRESHOLD_MS = 90_000;
15
+ const POLL_WATCHDOG_INTERVAL_MS = 30_000;
16
+ const POLL_STOP_GRACE_MS = 15_000;
17
+ const waitForGracefulStop = async (stop) => {
18
+ let timer;
19
+ try {
20
+ await Promise.race([
21
+ stop(),
22
+ new Promise((resolve) => {
23
+ timer = setTimeout(resolve, POLL_STOP_GRACE_MS);
24
+ timer.unref?.();
25
+ }),
26
+ ]);
27
+ }
28
+ finally {
29
+ if (timer) {
30
+ clearTimeout(timer);
31
+ }
32
+ }
33
+ };
34
+ export class TelegramPollingSession {
35
+ opts;
36
+ #restartAttempts = 0;
37
+ #webhookCleared = false;
38
+ #forceRestarted = false;
39
+ #activeRunner;
40
+ #activeFetchAbort;
41
+ constructor(opts) {
42
+ this.opts = opts;
43
+ }
44
+ get activeRunner() {
45
+ return this.#activeRunner;
46
+ }
47
+ markForceRestarted() {
48
+ this.#forceRestarted = true;
49
+ }
50
+ abortActiveFetch() {
51
+ this.#activeFetchAbort?.abort();
52
+ }
53
+ async runUntilAbort() {
54
+ while (!this.opts.abortSignal?.aborted) {
55
+ const bot = await this.#createPollingBot();
56
+ if (!bot) {
57
+ continue;
58
+ }
59
+ const cleanupState = await this.#ensureWebhookCleanup(bot);
60
+ if (cleanupState === "retry") {
61
+ continue;
62
+ }
63
+ if (cleanupState === "exit") {
64
+ return;
65
+ }
66
+ const state = await this.#runPollingCycle(bot);
67
+ if (state === "exit") {
68
+ return;
69
+ }
70
+ }
71
+ }
72
+ async #waitBeforeRestart(buildLine) {
73
+ this.#restartAttempts += 1;
74
+ const delayMs = computeBackoff(TELEGRAM_POLL_RESTART_POLICY, this.#restartAttempts);
75
+ const delay = formatDurationPrecise(delayMs);
76
+ this.opts.log(buildLine(delay));
77
+ try {
78
+ await sleepWithAbort(delayMs, this.opts.abortSignal);
79
+ }
80
+ catch (sleepErr) {
81
+ if (this.opts.abortSignal?.aborted) {
82
+ return false;
83
+ }
84
+ throw sleepErr;
85
+ }
86
+ return true;
87
+ }
88
+ async #waitBeforeRetryOnRecoverableSetupError(err, logPrefix) {
89
+ if (this.opts.abortSignal?.aborted) {
90
+ return false;
91
+ }
92
+ if (!isRecoverableTelegramNetworkError(err, { context: "unknown" })) {
93
+ throw err;
94
+ }
95
+ return this.#waitBeforeRestart((delay) => `${logPrefix}: ${formatErrorMessage(err)}; retrying in ${delay}.`);
96
+ }
97
+ async #createPollingBot() {
98
+ const fetchAbortController = new AbortController();
99
+ this.#activeFetchAbort = fetchAbortController;
100
+ try {
101
+ return createTelegramBot({
102
+ token: this.opts.token,
103
+ runtime: this.opts.runtime,
104
+ proxyFetch: this.opts.proxyFetch,
105
+ config: this.opts.config,
106
+ accountId: this.opts.accountId,
107
+ fetchAbortSignal: fetchAbortController.signal,
108
+ updateOffset: {
109
+ lastUpdateId: this.opts.getLastUpdateId(),
110
+ onUpdateId: this.opts.persistUpdateId,
111
+ },
112
+ });
113
+ }
114
+ catch (err) {
115
+ await this.#waitBeforeRetryOnRecoverableSetupError(err, "Telegram setup network error");
116
+ if (this.#activeFetchAbort === fetchAbortController) {
117
+ this.#activeFetchAbort = undefined;
118
+ }
119
+ return undefined;
120
+ }
121
+ }
122
+ async #ensureWebhookCleanup(bot) {
123
+ if (this.#webhookCleared) {
124
+ return "ready";
125
+ }
126
+ try {
127
+ await withTelegramApiErrorLogging({
128
+ operation: "deleteWebhook",
129
+ runtime: this.opts.runtime,
130
+ fn: () => bot.api.deleteWebhook({ drop_pending_updates: false }),
131
+ });
132
+ this.#webhookCleared = true;
133
+ return "ready";
134
+ }
135
+ catch (err) {
136
+ const shouldRetry = await this.#waitBeforeRetryOnRecoverableSetupError(err, "Telegram webhook cleanup failed");
137
+ return shouldRetry ? "retry" : "exit";
138
+ }
139
+ }
140
+ async #confirmPersistedOffset(bot) {
141
+ const lastUpdateId = this.opts.getLastUpdateId();
142
+ if (lastUpdateId === null || lastUpdateId >= Number.MAX_SAFE_INTEGER) {
143
+ return;
144
+ }
145
+ try {
146
+ await bot.api.getUpdates({ offset: lastUpdateId + 1, limit: 1, timeout: 0 });
147
+ }
148
+ catch {
149
+ // Non-fatal: runner middleware still skips duplicates via shouldSkipUpdate.
150
+ }
151
+ }
152
+ async #runPollingCycle(bot) {
153
+ await this.#confirmPersistedOffset(bot);
154
+ let lastGetUpdatesAt = Date.now();
155
+ bot.api.config.use((prev, method, payload, signal) => {
156
+ if (method === "getUpdates") {
157
+ lastGetUpdatesAt = Date.now();
158
+ }
159
+ return prev(method, payload, signal);
160
+ });
161
+ const runner = run(bot, this.opts.runnerOptions);
162
+ this.#activeRunner = runner;
163
+ const fetchAbortController = this.#activeFetchAbort;
164
+ let stopPromise;
165
+ let stalledRestart = false;
166
+ let forceCycleTimer;
167
+ let forceCycleResolve;
168
+ const forceCyclePromise = new Promise((resolve) => {
169
+ forceCycleResolve = resolve;
170
+ });
171
+ const stopRunner = () => {
172
+ fetchAbortController?.abort();
173
+ stopPromise ??= Promise.resolve(runner.stop())
174
+ .then(() => undefined)
175
+ .catch(() => {
176
+ // Runner may already be stopped by abort/retry paths.
177
+ });
178
+ return stopPromise;
179
+ };
180
+ const stopBot = () => {
181
+ return Promise.resolve(bot.stop())
182
+ .then(() => undefined)
183
+ .catch(() => {
184
+ // Bot may already be stopped by runner stop/abort paths.
185
+ });
186
+ };
187
+ const stopOnAbort = () => {
188
+ if (this.opts.abortSignal?.aborted) {
189
+ void stopRunner();
190
+ }
191
+ };
192
+ const watchdog = setInterval(() => {
193
+ if (this.opts.abortSignal?.aborted) {
194
+ return;
195
+ }
196
+ const elapsed = Date.now() - lastGetUpdatesAt;
197
+ if (elapsed > POLL_STALL_THRESHOLD_MS && runner.isRunning()) {
198
+ stalledRestart = true;
199
+ this.opts.log(`[telegram] Polling stall detected (no getUpdates for ${formatDurationPrecise(elapsed)}); forcing restart.`);
200
+ void stopRunner();
201
+ void stopBot();
202
+ if (!forceCycleTimer) {
203
+ forceCycleTimer = setTimeout(() => {
204
+ if (this.opts.abortSignal?.aborted) {
205
+ return;
206
+ }
207
+ this.opts.log(`[telegram] Polling runner stop timed out after ${formatDurationPrecise(POLL_STOP_GRACE_MS)}; forcing restart cycle.`);
208
+ forceCycleResolve?.();
209
+ }, POLL_STOP_GRACE_MS);
210
+ }
211
+ }
212
+ }, POLL_WATCHDOG_INTERVAL_MS);
213
+ this.opts.abortSignal?.addEventListener("abort", stopOnAbort, { once: true });
214
+ try {
215
+ await Promise.race([runner.task(), forceCyclePromise]);
216
+ if (this.opts.abortSignal?.aborted) {
217
+ return "exit";
218
+ }
219
+ const reason = stalledRestart
220
+ ? "polling stall detected"
221
+ : this.#forceRestarted
222
+ ? "unhandled network error"
223
+ : "runner stopped (maxRetryTime exceeded or graceful stop)";
224
+ this.#forceRestarted = false;
225
+ const shouldRestart = await this.#waitBeforeRestart((delay) => `Telegram polling runner stopped (${reason}); restarting in ${delay}.`);
226
+ return shouldRestart ? "continue" : "exit";
227
+ }
228
+ catch (err) {
229
+ this.#forceRestarted = false;
230
+ if (this.opts.abortSignal?.aborted) {
231
+ throw err;
232
+ }
233
+ const isConflict = isGetUpdatesConflict(err);
234
+ if (isConflict) {
235
+ this.#webhookCleared = false;
236
+ }
237
+ const isRecoverable = isRecoverableTelegramNetworkError(err, { context: "polling" });
238
+ if (!isConflict && !isRecoverable) {
239
+ throw err;
240
+ }
241
+ const reason = isConflict ? "getUpdates conflict" : "network error";
242
+ const errMsg = formatErrorMessage(err);
243
+ const shouldRestart = await this.#waitBeforeRestart((delay) => `Telegram ${reason}: ${errMsg}; retrying in ${delay}.`);
244
+ return shouldRestart ? "continue" : "exit";
245
+ }
246
+ finally {
247
+ clearInterval(watchdog);
248
+ if (forceCycleTimer) {
249
+ clearTimeout(forceCycleTimer);
250
+ }
251
+ this.opts.abortSignal?.removeEventListener("abort", stopOnAbort);
252
+ await waitForGracefulStop(stopRunner);
253
+ await waitForGracefulStop(stopBot);
254
+ this.#activeRunner = undefined;
255
+ if (this.#activeFetchAbort === fetchAbortController) {
256
+ this.#activeFetchAbort = undefined;
257
+ }
258
+ }
259
+ }
260
+ }
261
+ const isGetUpdatesConflict = (err) => {
262
+ if (!err || typeof err !== "object") {
263
+ return false;
264
+ }
265
+ const typed = err;
266
+ const errorCode = typed.error_code ?? typed.errorCode;
267
+ if (errorCode !== 409) {
268
+ return false;
269
+ }
270
+ const haystack = [typed.method, typed.description, typed.message]
271
+ .filter((value) => typeof value === "string")
272
+ .join(" ")
273
+ .toLowerCase();
274
+ return haystack.includes("getupdates");
275
+ };
@@ -0,0 +1,3 @@
1
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
2
+ const { setRuntime: setTelegramRuntime, getRuntime: getTelegramRuntime } = createPluginRuntimeStore("Telegram runtime not initialized");
3
+ export { getTelegramRuntime, setTelegramRuntime };
@@ -0,0 +1,71 @@
1
+ import { computeBackoff, sleepWithAbort } from "../../../src/infra/backoff.js";
2
+ const BACKOFF_POLICY = {
3
+ initialMs: 1000,
4
+ maxMs: 300_000, // 5 minutes
5
+ factor: 2,
6
+ jitter: 0.1,
7
+ };
8
+ function is401Error(error) {
9
+ if (!error) {
10
+ return false;
11
+ }
12
+ const message = error instanceof Error ? error.message : JSON.stringify(error);
13
+ return message.includes("401") || message.toLowerCase().includes("unauthorized");
14
+ }
15
+ /**
16
+ * Creates a GLOBAL (per-account) handler for sendChatAction that tracks 401 errors
17
+ * across all message contexts. This prevents the infinite loop that caused Telegram
18
+ * to delete bots (issue #27092).
19
+ *
20
+ * When a 401 occurs, exponential backoff is applied (1s → 2s → 4s → ... → 5min).
21
+ * After maxConsecutive401 failures (default 10), all sendChatAction calls are
22
+ * suspended until reset() is called.
23
+ */
24
+ export function createTelegramSendChatActionHandler({ sendChatActionFn, logger, maxConsecutive401 = 10, }) {
25
+ let consecutive401Failures = 0;
26
+ let suspended = false;
27
+ const reset = () => {
28
+ consecutive401Failures = 0;
29
+ suspended = false;
30
+ };
31
+ const sendChatAction = async (chatId, action, threadParams) => {
32
+ if (suspended) {
33
+ return;
34
+ }
35
+ if (consecutive401Failures > 0) {
36
+ const backoffMs = computeBackoff(BACKOFF_POLICY, consecutive401Failures);
37
+ logger(`sendChatAction backoff: waiting ${backoffMs}ms before retry ` +
38
+ `(failure ${consecutive401Failures}/${maxConsecutive401})`);
39
+ await sleepWithAbort(backoffMs);
40
+ }
41
+ try {
42
+ await sendChatActionFn(chatId, action, threadParams);
43
+ // Success: reset failure counter
44
+ if (consecutive401Failures > 0) {
45
+ logger(`sendChatAction recovered after ${consecutive401Failures} consecutive 401 failures`);
46
+ consecutive401Failures = 0;
47
+ }
48
+ }
49
+ catch (error) {
50
+ if (is401Error(error)) {
51
+ consecutive401Failures++;
52
+ if (consecutive401Failures >= maxConsecutive401) {
53
+ suspended = true;
54
+ logger(`CRITICAL: sendChatAction suspended after ${consecutive401Failures} consecutive 401 errors. ` +
55
+ `Bot token is likely invalid. Telegram may DELETE the bot if requests continue. ` +
56
+ `Replace the token and restart: openclaw channels restart telegram`);
57
+ }
58
+ else {
59
+ logger(`sendChatAction 401 error (${consecutive401Failures}/${maxConsecutive401}). ` +
60
+ `Retrying with exponential backoff.`);
61
+ }
62
+ }
63
+ throw error;
64
+ }
65
+ };
66
+ return {
67
+ sendChatAction,
68
+ isSuspended: () => suspended,
69
+ reset,
70
+ };
71
+ }
@@ -0,0 +1,46 @@
1
+ import { isAbortRequestText } from "../../../src/auto-reply/reply/abort.js";
2
+ import { isBtwRequestText } from "../../../src/auto-reply/reply/btw-command.js";
3
+ import { resolveTelegramForumThreadId } from "./bot/helpers.js";
4
+ export function getTelegramSequentialKey(ctx) {
5
+ const reaction = ctx.update?.message_reaction;
6
+ if (reaction?.chat?.id) {
7
+ return `telegram:${reaction.chat.id}`;
8
+ }
9
+ const msg = ctx.message ??
10
+ ctx.channelPost ??
11
+ ctx.editedChannelPost ??
12
+ ctx.update?.message ??
13
+ ctx.update?.edited_message ??
14
+ ctx.update?.channel_post ??
15
+ ctx.update?.edited_channel_post ??
16
+ ctx.update?.callback_query?.message;
17
+ const chatId = msg?.chat?.id ?? ctx.chat?.id;
18
+ const rawText = msg?.text ?? msg?.caption;
19
+ const botUsername = ctx.me?.username;
20
+ if (isAbortRequestText(rawText, botUsername ? { botUsername } : undefined)) {
21
+ if (typeof chatId === "number") {
22
+ return `telegram:${chatId}:control`;
23
+ }
24
+ return "telegram:control";
25
+ }
26
+ if (isBtwRequestText(rawText, botUsername ? { botUsername } : undefined)) {
27
+ const messageId = msg?.message_id;
28
+ if (typeof chatId === "number" && typeof messageId === "number") {
29
+ return `telegram:${chatId}:btw:${messageId}`;
30
+ }
31
+ if (typeof chatId === "number") {
32
+ return `telegram:${chatId}:btw`;
33
+ }
34
+ return "telegram:btw";
35
+ }
36
+ const isGroup = msg?.chat?.type === "group" || msg?.chat?.type === "supergroup";
37
+ const messageThreadId = msg?.message_thread_id;
38
+ const isForum = msg?.chat?.is_forum;
39
+ const threadId = isGroup
40
+ ? resolveTelegramForumThreadId({ isForum, messageThreadId })
41
+ : messageThreadId;
42
+ if (typeof chatId === "number") {
43
+ return threadId != null ? `telegram:${chatId}:topic:${threadId}` : `telegram:${chatId}`;
44
+ }
45
+ return "telegram:unknown";
46
+ }
@@ -0,0 +1,105 @@
1
+ import { appendMatchMetadata, asString, isRecord, resolveEnabledConfiguredAccountId, } from "../../../src/channels/plugins/status-issues/shared.js";
2
+ function readTelegramAccountStatus(value) {
3
+ if (!isRecord(value)) {
4
+ return null;
5
+ }
6
+ return {
7
+ accountId: value.accountId,
8
+ enabled: value.enabled,
9
+ configured: value.configured,
10
+ allowUnmentionedGroups: value.allowUnmentionedGroups,
11
+ audit: value.audit,
12
+ };
13
+ }
14
+ function readTelegramGroupMembershipAuditSummary(value) {
15
+ if (!isRecord(value)) {
16
+ return {};
17
+ }
18
+ const unresolvedGroups = typeof value.unresolvedGroups === "number" && Number.isFinite(value.unresolvedGroups)
19
+ ? value.unresolvedGroups
20
+ : undefined;
21
+ const hasWildcardUnmentionedGroups = typeof value.hasWildcardUnmentionedGroups === "boolean"
22
+ ? value.hasWildcardUnmentionedGroups
23
+ : undefined;
24
+ const groupsRaw = value.groups;
25
+ const groups = Array.isArray(groupsRaw)
26
+ ? groupsRaw
27
+ .map((entry) => {
28
+ if (!isRecord(entry)) {
29
+ return null;
30
+ }
31
+ const chatId = asString(entry.chatId);
32
+ if (!chatId) {
33
+ return null;
34
+ }
35
+ const ok = typeof entry.ok === "boolean" ? entry.ok : undefined;
36
+ const status = asString(entry.status) ?? null;
37
+ const error = asString(entry.error) ?? null;
38
+ const matchKey = asString(entry.matchKey) ?? undefined;
39
+ const matchSource = asString(entry.matchSource) ?? undefined;
40
+ return { chatId, ok, status, error, matchKey, matchSource };
41
+ })
42
+ .filter(Boolean)
43
+ : undefined;
44
+ return { unresolvedGroups, hasWildcardUnmentionedGroups, groups };
45
+ }
46
+ export function collectTelegramStatusIssues(accounts) {
47
+ const issues = [];
48
+ for (const entry of accounts) {
49
+ const account = readTelegramAccountStatus(entry);
50
+ if (!account) {
51
+ continue;
52
+ }
53
+ const accountId = resolveEnabledConfiguredAccountId(account);
54
+ if (!accountId) {
55
+ continue;
56
+ }
57
+ if (account.allowUnmentionedGroups === true) {
58
+ issues.push({
59
+ channel: "telegram",
60
+ accountId,
61
+ kind: "config",
62
+ message: "Config allows unmentioned group messages (requireMention=false). Telegram Bot API privacy mode will block most group messages unless disabled.",
63
+ fix: "In BotFather run /setprivacy → Disable for this bot (then restart the gateway).",
64
+ });
65
+ }
66
+ const audit = readTelegramGroupMembershipAuditSummary(account.audit);
67
+ if (audit.hasWildcardUnmentionedGroups === true) {
68
+ issues.push({
69
+ channel: "telegram",
70
+ accountId,
71
+ kind: "config",
72
+ message: 'Telegram groups config uses "*" with requireMention=false; membership probing is not possible without explicit group IDs.',
73
+ fix: "Add explicit numeric group ids under channels.telegram.groups (or per-account groups) to enable probing.",
74
+ });
75
+ }
76
+ if (audit.unresolvedGroups && audit.unresolvedGroups > 0) {
77
+ issues.push({
78
+ channel: "telegram",
79
+ accountId,
80
+ kind: "config",
81
+ message: `Some configured Telegram groups are not numeric IDs (unresolvedGroups=${audit.unresolvedGroups}). Membership probe can only check numeric group IDs.`,
82
+ fix: "Use numeric chat IDs (e.g. -100...) as keys in channels.telegram.groups for requireMention=false groups.",
83
+ });
84
+ }
85
+ for (const group of audit.groups ?? []) {
86
+ if (group.ok === true) {
87
+ continue;
88
+ }
89
+ const status = group.status ? ` status=${group.status}` : "";
90
+ const err = group.error ? `: ${group.error}` : "";
91
+ const baseMessage = `Group ${group.chatId} not reachable by bot.${status}${err}`;
92
+ issues.push({
93
+ channel: "telegram",
94
+ accountId,
95
+ kind: "runtime",
96
+ message: appendMatchMetadata(baseMessage, {
97
+ matchKey: group.matchKey,
98
+ matchSource: group.matchSource,
99
+ }),
100
+ fix: "Invite the bot to the group, then DM the bot once (/start) and restart the gateway.",
101
+ });
102
+ }
103
+ }
104
+ return issues;
105
+ }