@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,357 @@
1
+ import { isRecoverableTelegramNetworkError, isSafeToRetrySendError, isTelegramClientRejection, } from "./network-errors.js";
2
+ const MESSAGE_NOT_MODIFIED_RE = /400:\s*Bad Request:\s*message is not modified|MESSAGE_NOT_MODIFIED/i;
3
+ const MESSAGE_NOT_FOUND_RE = /400:\s*Bad Request:\s*message to edit not found|MESSAGE_ID_INVALID|message can't be edited/i;
4
+ function extractErrorText(err) {
5
+ return typeof err === "string"
6
+ ? err
7
+ : err instanceof Error
8
+ ? err.message
9
+ : typeof err === "object" && err && "description" in err
10
+ ? typeof err.description === "string"
11
+ ? err.description
12
+ : ""
13
+ : "";
14
+ }
15
+ function isMessageNotModifiedError(err) {
16
+ return MESSAGE_NOT_MODIFIED_RE.test(extractErrorText(err));
17
+ }
18
+ /**
19
+ * Returns true when Telegram rejects an edit because the target message can no
20
+ * longer be resolved or edited. The caller still needs preview context to
21
+ * decide whether to retain a different visible preview or fall back to send.
22
+ */
23
+ function isMissingPreviewMessageError(err) {
24
+ return MESSAGE_NOT_FOUND_RE.test(extractErrorText(err));
25
+ }
26
+ function shouldSkipRegressivePreviewUpdate(args) {
27
+ const currentPreviewText = args.currentPreviewText;
28
+ if (currentPreviewText === undefined) {
29
+ return false;
30
+ }
31
+ return (currentPreviewText.startsWith(args.text) &&
32
+ args.text.length < currentPreviewText.length &&
33
+ (args.skipRegressive === "always" || args.hadPreviewMessage));
34
+ }
35
+ function resolvePreviewTarget(params) {
36
+ const lanePreviewMessageId = params.lane.stream?.messageId();
37
+ const previewMessageId = typeof params.previewMessageIdOverride === "number"
38
+ ? params.previewMessageIdOverride
39
+ : lanePreviewMessageId;
40
+ const hadPreviewMessage = typeof params.previewMessageIdOverride === "number" || typeof lanePreviewMessageId === "number";
41
+ return {
42
+ hadPreviewMessage,
43
+ previewMessageId: typeof previewMessageId === "number" ? previewMessageId : undefined,
44
+ stopCreatesFirstPreview: params.stopBeforeEdit && !hadPreviewMessage && params.context === "final",
45
+ };
46
+ }
47
+ export function createLaneTextDeliverer(params) {
48
+ const getLanePreviewText = (lane) => lane.lastPartialText;
49
+ const markActivePreviewComplete = (laneName) => {
50
+ params.activePreviewLifecycleByLane[laneName] = "complete";
51
+ params.retainPreviewOnCleanupByLane[laneName] = true;
52
+ };
53
+ const isDraftPreviewLane = (lane) => lane.stream?.previewMode?.() === "draft";
54
+ const canMaterializeDraftFinal = (lane, previewButtons) => {
55
+ const hasPreviewButtons = Boolean(previewButtons && previewButtons.length > 0);
56
+ return (isDraftPreviewLane(lane) &&
57
+ !hasPreviewButtons &&
58
+ typeof lane.stream?.materialize === "function");
59
+ };
60
+ const tryMaterializeDraftPreviewForFinal = async (args) => {
61
+ const stream = args.lane.stream;
62
+ if (!stream || !isDraftPreviewLane(args.lane)) {
63
+ return false;
64
+ }
65
+ // Draft previews have no message_id to edit; materialize the final text
66
+ // into a real message and treat that as the finalized delivery.
67
+ stream.update(args.text);
68
+ const materializedMessageId = await stream.materialize?.();
69
+ if (typeof materializedMessageId !== "number") {
70
+ params.log(`telegram: ${args.laneName} draft preview materialize produced no message id; falling back to standard send`);
71
+ return false;
72
+ }
73
+ args.lane.lastPartialText = args.text;
74
+ params.markDelivered();
75
+ return true;
76
+ };
77
+ const tryEditPreviewMessage = async (args) => {
78
+ try {
79
+ await params.editPreview({
80
+ laneName: args.laneName,
81
+ messageId: args.messageId,
82
+ text: args.text,
83
+ previewButtons: args.previewButtons,
84
+ context: args.context,
85
+ });
86
+ if (args.updateLaneSnapshot) {
87
+ args.lane.lastPartialText = args.text;
88
+ }
89
+ params.markDelivered();
90
+ return "edited";
91
+ }
92
+ catch (err) {
93
+ if (isMessageNotModifiedError(err)) {
94
+ params.log(`telegram: ${args.laneName} preview ${args.context} edit returned "message is not modified"; treating as delivered`);
95
+ params.markDelivered();
96
+ return "edited";
97
+ }
98
+ if (args.context === "final") {
99
+ if (args.finalTextAlreadyLanded) {
100
+ params.log(`telegram: ${args.laneName} preview final edit failed after stop flush; keeping existing preview (${String(err)})`);
101
+ params.markDelivered();
102
+ return "retained";
103
+ }
104
+ if (isSafeToRetrySendError(err)) {
105
+ params.log(`telegram: ${args.laneName} preview final edit failed before reaching Telegram; falling back to standard send (${String(err)})`);
106
+ return "fallback";
107
+ }
108
+ if (isMissingPreviewMessageError(err)) {
109
+ if (args.retainAlternatePreviewOnMissingTarget) {
110
+ params.log(`telegram: ${args.laneName} preview final edit target missing; keeping alternate preview without fallback (${String(err)})`);
111
+ params.markDelivered();
112
+ return "retained";
113
+ }
114
+ params.log(`telegram: ${args.laneName} preview final edit target missing with no alternate preview; falling back to standard send (${String(err)})`);
115
+ return "fallback";
116
+ }
117
+ if (isRecoverableTelegramNetworkError(err, { allowMessageMatch: true })) {
118
+ params.log(`telegram: ${args.laneName} preview final edit may have landed despite network error; keeping existing preview (${String(err)})`);
119
+ params.markDelivered();
120
+ return "retained";
121
+ }
122
+ if (isTelegramClientRejection(err)) {
123
+ params.log(`telegram: ${args.laneName} preview final edit rejected by Telegram (client error); falling back to standard send (${String(err)})`);
124
+ return "fallback";
125
+ }
126
+ // Default: ambiguous error — prefer incomplete over duplicate
127
+ params.log(`telegram: ${args.laneName} preview final edit failed with ambiguous error; keeping existing preview to avoid duplicate (${String(err)})`);
128
+ params.markDelivered();
129
+ return "retained";
130
+ }
131
+ params.log(`telegram: ${args.laneName} preview ${args.context} edit failed; falling back to standard send (${String(err)})`);
132
+ return "fallback";
133
+ }
134
+ };
135
+ const tryUpdatePreviewForLane = async ({ lane, laneName, text, previewButtons, stopBeforeEdit = false, updateLaneSnapshot = false, skipRegressive, context, previewMessageId: previewMessageIdOverride, previewTextSnapshot, }) => {
136
+ const editPreview = (messageId, finalTextAlreadyLanded, retainAlternatePreviewOnMissingTarget) => tryEditPreviewMessage({
137
+ laneName,
138
+ messageId,
139
+ text,
140
+ context,
141
+ previewButtons,
142
+ updateLaneSnapshot,
143
+ lane,
144
+ finalTextAlreadyLanded,
145
+ retainAlternatePreviewOnMissingTarget,
146
+ });
147
+ const finalizePreview = (previewMessageId, finalTextAlreadyLanded, hadPreviewMessage, retainAlternatePreviewOnMissingTarget = false) => {
148
+ const currentPreviewText = previewTextSnapshot ?? getLanePreviewText(lane);
149
+ const shouldSkipRegressive = shouldSkipRegressivePreviewUpdate({
150
+ currentPreviewText,
151
+ text,
152
+ skipRegressive,
153
+ hadPreviewMessage,
154
+ });
155
+ if (shouldSkipRegressive) {
156
+ params.markDelivered();
157
+ return "edited";
158
+ }
159
+ return editPreview(previewMessageId, finalTextAlreadyLanded, retainAlternatePreviewOnMissingTarget);
160
+ };
161
+ if (!lane.stream) {
162
+ return "fallback";
163
+ }
164
+ const previewTargetBeforeStop = resolvePreviewTarget({
165
+ lane,
166
+ previewMessageIdOverride,
167
+ stopBeforeEdit,
168
+ context,
169
+ });
170
+ if (previewTargetBeforeStop.stopCreatesFirstPreview) {
171
+ // Final stop() can create the first visible preview message.
172
+ // Prime pending text so the stop flush sends the final text snapshot.
173
+ lane.stream.update(text);
174
+ await params.stopDraftLane(lane);
175
+ const previewTargetAfterStop = resolvePreviewTarget({
176
+ lane,
177
+ stopBeforeEdit: false,
178
+ context,
179
+ });
180
+ if (typeof previewTargetAfterStop.previewMessageId !== "number") {
181
+ return "fallback";
182
+ }
183
+ return finalizePreview(previewTargetAfterStop.previewMessageId, true, false);
184
+ }
185
+ if (stopBeforeEdit) {
186
+ await params.stopDraftLane(lane);
187
+ }
188
+ const previewTargetAfterStop = resolvePreviewTarget({
189
+ lane,
190
+ previewMessageIdOverride,
191
+ stopBeforeEdit: false,
192
+ context,
193
+ });
194
+ if (typeof previewTargetAfterStop.previewMessageId !== "number") {
195
+ // Only retain for final delivery when a prior preview is already visible
196
+ // to the user — otherwise falling back is safer than silence. For updates,
197
+ // always fall back so the caller can attempt sendPayload without stale
198
+ // markDelivered() state.
199
+ if (context === "final" && lane.hasStreamedMessage && lane.stream?.sendMayHaveLanded?.()) {
200
+ params.log(`telegram: ${laneName} preview send may have landed despite missing message id; keeping to avoid duplicate`);
201
+ params.markDelivered();
202
+ return "retained";
203
+ }
204
+ return "fallback";
205
+ }
206
+ const activePreviewMessageId = lane.stream?.messageId();
207
+ return finalizePreview(previewTargetAfterStop.previewMessageId, false, previewTargetAfterStop.hadPreviewMessage, typeof activePreviewMessageId === "number" &&
208
+ activePreviewMessageId !== previewTargetAfterStop.previewMessageId);
209
+ };
210
+ const consumeArchivedAnswerPreviewForFinal = async ({ lane, text, payload, previewButtons, canEditViaPreview, }) => {
211
+ const archivedPreview = params.archivedAnswerPreviews.shift();
212
+ if (!archivedPreview) {
213
+ return undefined;
214
+ }
215
+ if (canEditViaPreview) {
216
+ const finalized = await tryUpdatePreviewForLane({
217
+ lane,
218
+ laneName: "answer",
219
+ text,
220
+ previewButtons,
221
+ stopBeforeEdit: false,
222
+ skipRegressive: "existingOnly",
223
+ context: "final",
224
+ previewMessageId: archivedPreview.messageId,
225
+ previewTextSnapshot: archivedPreview.textSnapshot,
226
+ });
227
+ if (finalized === "edited") {
228
+ return "preview-finalized";
229
+ }
230
+ if (finalized === "retained") {
231
+ params.retainPreviewOnCleanupByLane.answer = true;
232
+ return "preview-retained";
233
+ }
234
+ }
235
+ // Send the replacement message first, then clean up the old preview.
236
+ // This avoids the visual "disappear then reappear" flash.
237
+ const delivered = await params.sendPayload(params.applyTextToPayload(payload, text));
238
+ // Once this archived preview is consumed by a fallback final send, delete it
239
+ // regardless of deleteIfUnused. That flag only applies to unconsumed boundaries.
240
+ if (delivered || archivedPreview.deleteIfUnused !== false) {
241
+ try {
242
+ await params.deletePreviewMessage(archivedPreview.messageId);
243
+ }
244
+ catch (err) {
245
+ params.log(`telegram: archived answer preview cleanup failed (${archivedPreview.messageId}): ${String(err)}`);
246
+ }
247
+ }
248
+ return delivered ? "sent" : "skipped";
249
+ };
250
+ return async ({ laneName, text, payload, infoKind, previewButtons, allowPreviewUpdateForNonFinal = false, }) => {
251
+ const lane = params.lanes[laneName];
252
+ const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
253
+ const canEditViaPreview = !hasMedia && text.length > 0 && text.length <= params.draftMaxChars && !payload.isError;
254
+ if (infoKind === "final") {
255
+ // Transient previews must decide cleanup retention per final attempt.
256
+ // Completed previews intentionally stay retained so later extra payloads
257
+ // do not clear the already-finalized message.
258
+ if (params.activePreviewLifecycleByLane[laneName] === "transient") {
259
+ params.retainPreviewOnCleanupByLane[laneName] = false;
260
+ }
261
+ if (laneName === "answer") {
262
+ const archivedResult = await consumeArchivedAnswerPreviewForFinal({
263
+ lane,
264
+ text,
265
+ payload,
266
+ previewButtons,
267
+ canEditViaPreview,
268
+ });
269
+ if (archivedResult) {
270
+ return archivedResult;
271
+ }
272
+ }
273
+ if (canEditViaPreview && params.activePreviewLifecycleByLane[laneName] === "transient") {
274
+ await params.flushDraftLane(lane);
275
+ if (laneName === "answer") {
276
+ const archivedResultAfterFlush = await consumeArchivedAnswerPreviewForFinal({
277
+ lane,
278
+ text,
279
+ payload,
280
+ previewButtons,
281
+ canEditViaPreview,
282
+ });
283
+ if (archivedResultAfterFlush) {
284
+ return archivedResultAfterFlush;
285
+ }
286
+ }
287
+ if (canMaterializeDraftFinal(lane, previewButtons)) {
288
+ const materialized = await tryMaterializeDraftPreviewForFinal({
289
+ lane,
290
+ laneName,
291
+ text,
292
+ });
293
+ if (materialized) {
294
+ markActivePreviewComplete(laneName);
295
+ return "preview-finalized";
296
+ }
297
+ }
298
+ const finalized = await tryUpdatePreviewForLane({
299
+ lane,
300
+ laneName,
301
+ text,
302
+ previewButtons,
303
+ stopBeforeEdit: true,
304
+ skipRegressive: "existingOnly",
305
+ context: "final",
306
+ });
307
+ if (finalized === "edited") {
308
+ markActivePreviewComplete(laneName);
309
+ return "preview-finalized";
310
+ }
311
+ if (finalized === "retained") {
312
+ markActivePreviewComplete(laneName);
313
+ return "preview-retained";
314
+ }
315
+ }
316
+ else if (!hasMedia && !payload.isError && text.length > params.draftMaxChars) {
317
+ params.log(`telegram: preview final too long for edit (${text.length} > ${params.draftMaxChars}); falling back to standard send`);
318
+ }
319
+ await params.stopDraftLane(lane);
320
+ const delivered = await params.sendPayload(params.applyTextToPayload(payload, text));
321
+ return delivered ? "sent" : "skipped";
322
+ }
323
+ if (allowPreviewUpdateForNonFinal && canEditViaPreview) {
324
+ if (isDraftPreviewLane(lane)) {
325
+ // DM draft flow has no message_id to edit; updates are sent via sendMessageDraft.
326
+ // Only mark as updated when the draft flush actually emits an update.
327
+ const previewRevisionBeforeFlush = lane.stream?.previewRevision?.() ?? 0;
328
+ lane.stream?.update(text);
329
+ await params.flushDraftLane(lane);
330
+ const previewUpdated = (lane.stream?.previewRevision?.() ?? 0) > previewRevisionBeforeFlush;
331
+ if (!previewUpdated) {
332
+ params.log(`telegram: ${laneName} draft preview update not emitted; falling back to standard send`);
333
+ const delivered = await params.sendPayload(params.applyTextToPayload(payload, text));
334
+ return delivered ? "sent" : "skipped";
335
+ }
336
+ lane.lastPartialText = text;
337
+ params.markDelivered();
338
+ return "preview-updated";
339
+ }
340
+ const updated = await tryUpdatePreviewForLane({
341
+ lane,
342
+ laneName,
343
+ text,
344
+ previewButtons,
345
+ stopBeforeEdit: false,
346
+ updateLaneSnapshot: true,
347
+ skipRegressive: "always",
348
+ context: "update",
349
+ });
350
+ if (updated === "edited") {
351
+ return "preview-updated";
352
+ }
353
+ }
354
+ const delivered = await params.sendPayload(params.applyTextToPayload(payload, text));
355
+ return delivered ? "sent" : "skipped";
356
+ };
357
+ }
@@ -0,0 +1,2 @@
1
+ export { createLaneTextDeliverer, } from "./lane-delivery-text-deliverer.js";
2
+ export { createLaneDeliveryStateTracker, } from "./lane-delivery-state.js";
@@ -0,0 +1,37 @@
1
+ import { normalizeTelegramLookupTarget, parseTelegramTarget } from "./targets.js";
2
+ const TELEGRAM_PREFIX_RE = /^(telegram|tg):/i;
3
+ function normalizeTelegramTargetBody(raw) {
4
+ const trimmed = raw.trim();
5
+ if (!trimmed) {
6
+ return undefined;
7
+ }
8
+ const prefixStripped = trimmed.replace(TELEGRAM_PREFIX_RE, "").trim();
9
+ if (!prefixStripped) {
10
+ return undefined;
11
+ }
12
+ const parsed = parseTelegramTarget(trimmed);
13
+ const normalizedChatId = normalizeTelegramLookupTarget(parsed.chatId);
14
+ if (!normalizedChatId) {
15
+ return undefined;
16
+ }
17
+ const keepLegacyGroupPrefix = /^group:/i.test(prefixStripped);
18
+ const hasTopicSuffix = /:topic:\d+$/i.test(prefixStripped);
19
+ const chatSegment = keepLegacyGroupPrefix ? `group:${normalizedChatId}` : normalizedChatId;
20
+ if (parsed.messageThreadId == null) {
21
+ return chatSegment;
22
+ }
23
+ const threadSuffix = hasTopicSuffix
24
+ ? `:topic:${parsed.messageThreadId}`
25
+ : `:${parsed.messageThreadId}`;
26
+ return `${chatSegment}${threadSuffix}`;
27
+ }
28
+ export function normalizeTelegramMessagingTarget(raw) {
29
+ const normalizedBody = normalizeTelegramTargetBody(raw);
30
+ if (!normalizedBody) {
31
+ return undefined;
32
+ }
33
+ return `telegram:${normalizedBody}`.toLowerCase();
34
+ }
35
+ export function looksLikeTelegramTargetId(raw) {
36
+ return normalizeTelegramTargetBody(raw) !== undefined;
37
+ }
@@ -0,0 +1,192 @@
1
+ import { applySingleTokenPromptResult, patchChannelConfigForAccount, promptSingleChannelSecretInput, promptResolvedAllowFrom, resolveAccountIdForConfigure, resolveOnboardingAccountId, setChannelDmPolicyWithAllowFrom, setOnboardingChannelEnabled, splitOnboardingEntries, } from "../../../src/channels/plugins/onboarding/helpers.js";
2
+ import { formatCliCommand } from "../../../src/cli/command-format.js";
3
+ import { hasConfiguredSecretInput } from "../../../src/config/types.secrets.js";
4
+ import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js";
5
+ import { formatDocsLink } from "../../../src/terminal/links.js";
6
+ import { inspectTelegramAccount } from "./account-inspect.js";
7
+ import { listTelegramAccountIds, resolveDefaultTelegramAccountId, resolveTelegramAccount, } from "./accounts.js";
8
+ import { fetchTelegramChatId } from "./api-fetch.js";
9
+ const channel = "telegram";
10
+ async function noteTelegramTokenHelp(prompter) {
11
+ await prompter.note([
12
+ "1) Open Telegram and chat with @BotFather",
13
+ "2) Run /newbot (or /mybots)",
14
+ "3) Copy the token (looks like 123456:ABC...)",
15
+ "Tip: you can also set TELEGRAM_BOT_TOKEN in your env.",
16
+ `Docs: ${formatDocsLink("/telegram")}`,
17
+ "Website: https://openclaw.ai",
18
+ ].join("\n"), "Telegram bot token");
19
+ }
20
+ async function noteTelegramUserIdHelp(prompter) {
21
+ await prompter.note([
22
+ `1) DM your bot, then read from.id in \`${formatCliCommand("openclaw logs --follow")}\` (safest)`,
23
+ "2) Or call https://api.telegram.org/bot<bot_token>/getUpdates and read message.from.id",
24
+ "3) Third-party: DM @userinfobot or @getidsbot",
25
+ `Docs: ${formatDocsLink("/telegram")}`,
26
+ "Website: https://openclaw.ai",
27
+ ].join("\n"), "Telegram user id");
28
+ }
29
+ export function normalizeTelegramAllowFromInput(raw) {
30
+ return raw
31
+ .trim()
32
+ .replace(/^(telegram|tg):/i, "")
33
+ .trim();
34
+ }
35
+ export function parseTelegramAllowFromId(raw) {
36
+ const stripped = normalizeTelegramAllowFromInput(raw);
37
+ return /^\d+$/.test(stripped) ? stripped : null;
38
+ }
39
+ async function promptTelegramAllowFrom(params) {
40
+ const { cfg, prompter, accountId } = params;
41
+ const resolved = resolveTelegramAccount({ cfg, accountId });
42
+ const existingAllowFrom = resolved.config.allowFrom ?? [];
43
+ await noteTelegramUserIdHelp(prompter);
44
+ const token = params.tokenOverride?.trim() || resolved.token;
45
+ if (!token) {
46
+ await prompter.note("Telegram token missing; username lookup is unavailable.", "Telegram");
47
+ }
48
+ const unique = await promptResolvedAllowFrom({
49
+ prompter,
50
+ existing: existingAllowFrom,
51
+ token,
52
+ message: "Telegram allowFrom (numeric sender id; @username resolves to id)",
53
+ placeholder: "@username",
54
+ label: "Telegram allowlist",
55
+ parseInputs: splitOnboardingEntries,
56
+ parseId: parseTelegramAllowFromId,
57
+ invalidWithoutTokenNote: "Telegram token missing; use numeric sender ids (usernames require a bot token).",
58
+ resolveEntries: async ({ token: tokenValue, entries }) => {
59
+ const results = await Promise.all(entries.map(async (entry) => {
60
+ const numericId = parseTelegramAllowFromId(entry);
61
+ if (numericId) {
62
+ return { input: entry, resolved: true, id: numericId };
63
+ }
64
+ const stripped = normalizeTelegramAllowFromInput(entry);
65
+ if (!stripped) {
66
+ return { input: entry, resolved: false, id: null };
67
+ }
68
+ const username = stripped.startsWith("@") ? stripped : `@${stripped}`;
69
+ const id = await fetchTelegramChatId({ token: tokenValue, chatId: username });
70
+ return { input: entry, resolved: Boolean(id), id };
71
+ }));
72
+ return results;
73
+ },
74
+ });
75
+ return patchChannelConfigForAccount({
76
+ cfg,
77
+ channel: "telegram",
78
+ accountId,
79
+ patch: { dmPolicy: "allowlist", allowFrom: unique },
80
+ });
81
+ }
82
+ async function promptTelegramAllowFromForAccount(params) {
83
+ const accountId = resolveOnboardingAccountId({
84
+ accountId: params.accountId,
85
+ defaultAccountId: resolveDefaultTelegramAccountId(params.cfg),
86
+ });
87
+ return promptTelegramAllowFrom({
88
+ cfg: params.cfg,
89
+ prompter: params.prompter,
90
+ accountId,
91
+ });
92
+ }
93
+ const dmPolicy = {
94
+ label: "Telegram",
95
+ channel,
96
+ policyKey: "channels.telegram.dmPolicy",
97
+ allowFromKey: "channels.telegram.allowFrom",
98
+ getCurrent: (cfg) => cfg.channels?.telegram?.dmPolicy ?? "pairing",
99
+ setPolicy: (cfg, policy) => setChannelDmPolicyWithAllowFrom({
100
+ cfg,
101
+ channel: "telegram",
102
+ dmPolicy: policy,
103
+ }),
104
+ promptAllowFrom: promptTelegramAllowFromForAccount,
105
+ };
106
+ export const telegramOnboardingAdapter = {
107
+ channel,
108
+ getStatus: async ({ cfg }) => {
109
+ const configured = listTelegramAccountIds(cfg).some((accountId) => {
110
+ const account = inspectTelegramAccount({ cfg, accountId });
111
+ return account.configured;
112
+ });
113
+ return {
114
+ channel,
115
+ configured,
116
+ statusLines: [`Telegram: ${configured ? "configured" : "needs token"}`],
117
+ selectionHint: configured ? "recommended · configured" : "recommended · newcomer-friendly",
118
+ quickstartScore: configured ? 1 : 10,
119
+ };
120
+ },
121
+ configure: async ({ cfg, prompter, options, accountOverrides, shouldPromptAccountIds, forceAllowFrom, }) => {
122
+ const defaultTelegramAccountId = resolveDefaultTelegramAccountId(cfg);
123
+ const telegramAccountId = await resolveAccountIdForConfigure({
124
+ cfg,
125
+ prompter,
126
+ label: "Telegram",
127
+ accountOverride: accountOverrides.telegram,
128
+ shouldPromptAccountIds,
129
+ listAccountIds: listTelegramAccountIds,
130
+ defaultAccountId: defaultTelegramAccountId,
131
+ });
132
+ let next = cfg;
133
+ const resolvedAccount = resolveTelegramAccount({
134
+ cfg: next,
135
+ accountId: telegramAccountId,
136
+ });
137
+ const hasConfiguredBotToken = hasConfiguredSecretInput(resolvedAccount.config.botToken);
138
+ const hasConfigToken = hasConfiguredBotToken || Boolean(resolvedAccount.config.tokenFile?.trim());
139
+ const accountConfigured = Boolean(resolvedAccount.token) || hasConfigToken;
140
+ const allowEnv = telegramAccountId === DEFAULT_ACCOUNT_ID;
141
+ const canUseEnv = allowEnv && !hasConfigToken && Boolean(process.env.TELEGRAM_BOT_TOKEN?.trim());
142
+ if (!accountConfigured) {
143
+ await noteTelegramTokenHelp(prompter);
144
+ }
145
+ const tokenResult = await promptSingleChannelSecretInput({
146
+ cfg: next,
147
+ prompter,
148
+ providerHint: "telegram",
149
+ credentialLabel: "Telegram bot token",
150
+ secretInputMode: options?.secretInputMode,
151
+ accountConfigured,
152
+ canUseEnv,
153
+ hasConfigToken,
154
+ envPrompt: "TELEGRAM_BOT_TOKEN detected. Use env var?",
155
+ keepPrompt: "Telegram token already configured. Keep it?",
156
+ inputPrompt: "Enter Telegram bot token",
157
+ preferredEnvVar: allowEnv ? "TELEGRAM_BOT_TOKEN" : undefined,
158
+ });
159
+ let resolvedTokenForAllowFrom;
160
+ if (tokenResult.action === "use-env") {
161
+ next = applySingleTokenPromptResult({
162
+ cfg: next,
163
+ channel: "telegram",
164
+ accountId: telegramAccountId,
165
+ tokenPatchKey: "botToken",
166
+ tokenResult: { useEnv: true, token: null },
167
+ });
168
+ resolvedTokenForAllowFrom = process.env.TELEGRAM_BOT_TOKEN?.trim() || undefined;
169
+ }
170
+ else if (tokenResult.action === "set") {
171
+ next = applySingleTokenPromptResult({
172
+ cfg: next,
173
+ channel: "telegram",
174
+ accountId: telegramAccountId,
175
+ tokenPatchKey: "botToken",
176
+ tokenResult: { useEnv: false, token: tokenResult.value },
177
+ });
178
+ resolvedTokenForAllowFrom = tokenResult.resolvedValue;
179
+ }
180
+ if (forceAllowFrom) {
181
+ next = await promptTelegramAllowFrom({
182
+ cfg: next,
183
+ prompter,
184
+ accountId: telegramAccountId,
185
+ tokenOverride: resolvedTokenForAllowFrom,
186
+ });
187
+ }
188
+ return { cfg: next, accountId: telegramAccountId };
189
+ },
190
+ dmPolicy,
191
+ disable: (cfg) => setOnboardingChannelEnabled(cfg, channel, false),
192
+ };