@poolzin/pool-bot 2026.3.25 → 2026.3.26

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 +10 -276
  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,508 @@
1
+ import { GrammyError, InputFile } from "grammy";
2
+ import { chunkMarkdownTextWithMode } from "../../../../src/auto-reply/chunk.js";
3
+ import { danger, logVerbose } from "../../../../src/globals.js";
4
+ import { fireAndForgetHook } from "../../../../src/hooks/fire-and-forget.js";
5
+ import { createInternalHookEvent, triggerInternalHook, } from "../../../../src/hooks/internal-hooks.js";
6
+ import { buildCanonicalSentMessageHookContext, toInternalMessageSentContext, toPluginMessageContext, toPluginMessageSentEvent, } from "../../../../src/hooks/message-hook-mappers.js";
7
+ import { formatErrorMessage } from "../../../../src/infra/errors.js";
8
+ import { buildOutboundMediaLoadOptions } from "../../../../src/media/load-options.js";
9
+ import { isGifMedia, kindFromMime } from "../../../../src/media/mime.js";
10
+ import { getGlobalHookRunner } from "../../../../src/plugins/hook-runner-global.js";
11
+ import { loadWebMedia } from "../../../whatsapp/src/media.js";
12
+ import { splitTelegramCaption } from "../caption.js";
13
+ import { markdownToTelegramChunks, markdownToTelegramHtml, renderTelegramHtmlText, wrapFileReferencesInHtml, } from "../format.js";
14
+ import { buildInlineKeyboard } from "../send.js";
15
+ import { resolveTelegramVoiceSend } from "../voice.js";
16
+ import { buildTelegramSendParams, sendTelegramText, sendTelegramWithThreadFallback, } from "./delivery.send.js";
17
+ import { resolveTelegramReplyId } from "./helpers.js";
18
+ import { markReplyApplied, resolveReplyToForSend, sendChunkedTelegramReplyText, } from "./reply-threading.js";
19
+ const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/;
20
+ const CAPTION_TOO_LONG_RE = /caption is too long/i;
21
+ function buildChunkTextResolver(params) {
22
+ return (markdown) => {
23
+ const markdownChunks = params.chunkMode === "newline"
24
+ ? chunkMarkdownTextWithMode(markdown, params.textLimit, params.chunkMode)
25
+ : [markdown];
26
+ const chunks = [];
27
+ for (const chunk of markdownChunks) {
28
+ const nested = markdownToTelegramChunks(chunk, params.textLimit, {
29
+ tableMode: params.tableMode,
30
+ });
31
+ if (!nested.length && chunk) {
32
+ chunks.push({
33
+ html: wrapFileReferencesInHtml(markdownToTelegramHtml(chunk, { tableMode: params.tableMode, wrapFileRefs: false })),
34
+ text: chunk,
35
+ });
36
+ continue;
37
+ }
38
+ chunks.push(...nested);
39
+ }
40
+ return chunks;
41
+ };
42
+ }
43
+ function markDelivered(progress) {
44
+ progress.hasDelivered = true;
45
+ progress.deliveredCount += 1;
46
+ }
47
+ async function deliverTextReply(params) {
48
+ let firstDeliveredMessageId;
49
+ await sendChunkedTelegramReplyText({
50
+ chunks: params.chunkText(params.replyText),
51
+ progress: params.progress,
52
+ replyToId: params.replyToId,
53
+ replyToMode: params.replyToMode,
54
+ replyMarkup: params.replyMarkup,
55
+ replyQuoteText: params.replyQuoteText,
56
+ markDelivered,
57
+ sendChunk: async ({ chunk, replyToMessageId, replyMarkup, replyQuoteText }) => {
58
+ const messageId = await sendTelegramText(params.bot, params.chatId, chunk.html, params.runtime, {
59
+ replyToMessageId,
60
+ replyQuoteText,
61
+ thread: params.thread,
62
+ textMode: "html",
63
+ plainText: chunk.text,
64
+ linkPreview: params.linkPreview,
65
+ replyMarkup,
66
+ });
67
+ if (firstDeliveredMessageId == null) {
68
+ firstDeliveredMessageId = messageId;
69
+ }
70
+ },
71
+ });
72
+ return firstDeliveredMessageId;
73
+ }
74
+ async function sendPendingFollowUpText(params) {
75
+ await sendChunkedTelegramReplyText({
76
+ chunks: params.chunkText(params.text),
77
+ progress: params.progress,
78
+ replyToId: params.replyToId,
79
+ replyToMode: params.replyToMode,
80
+ replyMarkup: params.replyMarkup,
81
+ markDelivered,
82
+ sendChunk: async ({ chunk, replyToMessageId, replyMarkup }) => {
83
+ await sendTelegramText(params.bot, params.chatId, chunk.html, params.runtime, {
84
+ replyToMessageId,
85
+ thread: params.thread,
86
+ textMode: "html",
87
+ plainText: chunk.text,
88
+ linkPreview: params.linkPreview,
89
+ replyMarkup,
90
+ });
91
+ },
92
+ });
93
+ }
94
+ function isVoiceMessagesForbidden(err) {
95
+ if (err instanceof GrammyError) {
96
+ return VOICE_FORBIDDEN_RE.test(err.description);
97
+ }
98
+ return VOICE_FORBIDDEN_RE.test(formatErrorMessage(err));
99
+ }
100
+ function isCaptionTooLong(err) {
101
+ if (err instanceof GrammyError) {
102
+ return CAPTION_TOO_LONG_RE.test(err.description);
103
+ }
104
+ return CAPTION_TOO_LONG_RE.test(formatErrorMessage(err));
105
+ }
106
+ async function sendTelegramVoiceFallbackText(opts) {
107
+ let firstDeliveredMessageId;
108
+ const chunks = opts.chunkText(opts.text);
109
+ let appliedReplyTo = false;
110
+ for (let i = 0; i < chunks.length; i += 1) {
111
+ const chunk = chunks[i];
112
+ // Only apply reply reference, quote text, and buttons to the first chunk.
113
+ const replyToForChunk = !appliedReplyTo ? opts.replyToId : undefined;
114
+ const messageId = await sendTelegramText(opts.bot, opts.chatId, chunk.html, opts.runtime, {
115
+ replyToMessageId: replyToForChunk,
116
+ replyQuoteText: !appliedReplyTo ? opts.replyQuoteText : undefined,
117
+ thread: opts.thread,
118
+ textMode: "html",
119
+ plainText: chunk.text,
120
+ linkPreview: opts.linkPreview,
121
+ replyMarkup: !appliedReplyTo ? opts.replyMarkup : undefined,
122
+ });
123
+ if (firstDeliveredMessageId == null) {
124
+ firstDeliveredMessageId = messageId;
125
+ }
126
+ if (replyToForChunk) {
127
+ appliedReplyTo = true;
128
+ }
129
+ }
130
+ return firstDeliveredMessageId;
131
+ }
132
+ async function deliverMediaReply(params) {
133
+ let firstDeliveredMessageId;
134
+ let first = true;
135
+ let pendingFollowUpText;
136
+ for (const mediaUrl of params.mediaList) {
137
+ const isFirstMedia = first;
138
+ const media = await loadWebMedia(mediaUrl, buildOutboundMediaLoadOptions({ mediaLocalRoots: params.mediaLocalRoots }));
139
+ const kind = kindFromMime(media.contentType ?? undefined);
140
+ const isGif = isGifMedia({
141
+ contentType: media.contentType,
142
+ fileName: media.fileName,
143
+ });
144
+ const fileName = media.fileName ?? (isGif ? "animation.gif" : "file");
145
+ const file = new InputFile(media.buffer, fileName);
146
+ const { caption, followUpText } = splitTelegramCaption(isFirstMedia ? (params.reply.text ?? undefined) : undefined);
147
+ const htmlCaption = caption
148
+ ? renderTelegramHtmlText(caption, { tableMode: params.tableMode })
149
+ : undefined;
150
+ if (followUpText) {
151
+ pendingFollowUpText = followUpText;
152
+ }
153
+ first = false;
154
+ const replyToMessageId = resolveReplyToForSend({
155
+ replyToId: params.replyToId,
156
+ replyToMode: params.replyToMode,
157
+ progress: params.progress,
158
+ });
159
+ const shouldAttachButtonsToMedia = isFirstMedia && params.replyMarkup && !followUpText;
160
+ const mediaParams = {
161
+ caption: htmlCaption,
162
+ ...(htmlCaption ? { parse_mode: "HTML" } : {}),
163
+ ...(shouldAttachButtonsToMedia ? { reply_markup: params.replyMarkup } : {}),
164
+ ...buildTelegramSendParams({
165
+ replyToMessageId,
166
+ thread: params.thread,
167
+ }),
168
+ };
169
+ if (isGif) {
170
+ const result = await sendTelegramWithThreadFallback({
171
+ operation: "sendAnimation",
172
+ runtime: params.runtime,
173
+ thread: params.thread,
174
+ requestParams: mediaParams,
175
+ send: (effectiveParams) => params.bot.api.sendAnimation(params.chatId, file, { ...effectiveParams }),
176
+ });
177
+ if (firstDeliveredMessageId == null) {
178
+ firstDeliveredMessageId = result.message_id;
179
+ }
180
+ markDelivered(params.progress);
181
+ }
182
+ else if (kind === "image") {
183
+ const result = await sendTelegramWithThreadFallback({
184
+ operation: "sendPhoto",
185
+ runtime: params.runtime,
186
+ thread: params.thread,
187
+ requestParams: mediaParams,
188
+ send: (effectiveParams) => params.bot.api.sendPhoto(params.chatId, file, { ...effectiveParams }),
189
+ });
190
+ if (firstDeliveredMessageId == null) {
191
+ firstDeliveredMessageId = result.message_id;
192
+ }
193
+ markDelivered(params.progress);
194
+ }
195
+ else if (kind === "video") {
196
+ const result = await sendTelegramWithThreadFallback({
197
+ operation: "sendVideo",
198
+ runtime: params.runtime,
199
+ thread: params.thread,
200
+ requestParams: mediaParams,
201
+ send: (effectiveParams) => params.bot.api.sendVideo(params.chatId, file, { ...effectiveParams }),
202
+ });
203
+ if (firstDeliveredMessageId == null) {
204
+ firstDeliveredMessageId = result.message_id;
205
+ }
206
+ markDelivered(params.progress);
207
+ }
208
+ else if (kind === "audio") {
209
+ const { useVoice } = resolveTelegramVoiceSend({
210
+ wantsVoice: params.reply.audioAsVoice === true,
211
+ contentType: media.contentType,
212
+ fileName,
213
+ logFallback: logVerbose,
214
+ });
215
+ if (useVoice) {
216
+ const sendVoiceMedia = async (requestParams, shouldLog) => {
217
+ const result = await sendTelegramWithThreadFallback({
218
+ operation: "sendVoice",
219
+ runtime: params.runtime,
220
+ thread: params.thread,
221
+ requestParams,
222
+ shouldLog,
223
+ send: (effectiveParams) => params.bot.api.sendVoice(params.chatId, file, { ...effectiveParams }),
224
+ });
225
+ if (firstDeliveredMessageId == null) {
226
+ firstDeliveredMessageId = result.message_id;
227
+ }
228
+ markDelivered(params.progress);
229
+ };
230
+ await params.onVoiceRecording?.();
231
+ try {
232
+ await sendVoiceMedia(mediaParams, (err) => !isVoiceMessagesForbidden(err));
233
+ }
234
+ catch (voiceErr) {
235
+ if (isVoiceMessagesForbidden(voiceErr)) {
236
+ const fallbackText = params.reply.text;
237
+ if (!fallbackText || !fallbackText.trim()) {
238
+ throw voiceErr;
239
+ }
240
+ logVerbose("telegram sendVoice forbidden (recipient has voice messages blocked in privacy settings); falling back to text");
241
+ const voiceFallbackReplyTo = resolveReplyToForSend({
242
+ replyToId: params.replyToId,
243
+ replyToMode: params.replyToMode,
244
+ progress: params.progress,
245
+ });
246
+ const fallbackMessageId = await sendTelegramVoiceFallbackText({
247
+ bot: params.bot,
248
+ chatId: params.chatId,
249
+ runtime: params.runtime,
250
+ text: fallbackText,
251
+ chunkText: params.chunkText,
252
+ replyToId: voiceFallbackReplyTo,
253
+ thread: params.thread,
254
+ linkPreview: params.linkPreview,
255
+ replyMarkup: params.replyMarkup,
256
+ replyQuoteText: params.replyQuoteText,
257
+ });
258
+ if (firstDeliveredMessageId == null) {
259
+ firstDeliveredMessageId = fallbackMessageId;
260
+ }
261
+ markReplyApplied(params.progress, voiceFallbackReplyTo);
262
+ markDelivered(params.progress);
263
+ continue;
264
+ }
265
+ if (isCaptionTooLong(voiceErr)) {
266
+ logVerbose("telegram sendVoice caption too long; resending voice without caption + text separately");
267
+ const noCaptionParams = { ...mediaParams };
268
+ delete noCaptionParams.caption;
269
+ delete noCaptionParams.parse_mode;
270
+ await sendVoiceMedia(noCaptionParams);
271
+ const fallbackText = params.reply.text;
272
+ if (fallbackText?.trim()) {
273
+ await sendTelegramVoiceFallbackText({
274
+ bot: params.bot,
275
+ chatId: params.chatId,
276
+ runtime: params.runtime,
277
+ text: fallbackText,
278
+ chunkText: params.chunkText,
279
+ replyToId: undefined,
280
+ thread: params.thread,
281
+ linkPreview: params.linkPreview,
282
+ replyMarkup: params.replyMarkup,
283
+ });
284
+ }
285
+ markReplyApplied(params.progress, replyToMessageId);
286
+ continue;
287
+ }
288
+ throw voiceErr;
289
+ }
290
+ }
291
+ else {
292
+ const result = await sendTelegramWithThreadFallback({
293
+ operation: "sendAudio",
294
+ runtime: params.runtime,
295
+ thread: params.thread,
296
+ requestParams: mediaParams,
297
+ send: (effectiveParams) => params.bot.api.sendAudio(params.chatId, file, { ...effectiveParams }),
298
+ });
299
+ if (firstDeliveredMessageId == null) {
300
+ firstDeliveredMessageId = result.message_id;
301
+ }
302
+ markDelivered(params.progress);
303
+ }
304
+ }
305
+ else {
306
+ const result = await sendTelegramWithThreadFallback({
307
+ operation: "sendDocument",
308
+ runtime: params.runtime,
309
+ thread: params.thread,
310
+ requestParams: mediaParams,
311
+ send: (effectiveParams) => params.bot.api.sendDocument(params.chatId, file, { ...effectiveParams }),
312
+ });
313
+ if (firstDeliveredMessageId == null) {
314
+ firstDeliveredMessageId = result.message_id;
315
+ }
316
+ markDelivered(params.progress);
317
+ }
318
+ markReplyApplied(params.progress, replyToMessageId);
319
+ if (pendingFollowUpText && isFirstMedia) {
320
+ await sendPendingFollowUpText({
321
+ bot: params.bot,
322
+ chatId: params.chatId,
323
+ runtime: params.runtime,
324
+ thread: params.thread,
325
+ chunkText: params.chunkText,
326
+ text: pendingFollowUpText,
327
+ replyMarkup: params.replyMarkup,
328
+ linkPreview: params.linkPreview,
329
+ replyToId: params.replyToId,
330
+ replyToMode: params.replyToMode,
331
+ progress: params.progress,
332
+ });
333
+ pendingFollowUpText = undefined;
334
+ }
335
+ }
336
+ return firstDeliveredMessageId;
337
+ }
338
+ async function maybePinFirstDeliveredMessage(params) {
339
+ if (!params.shouldPin || typeof params.firstDeliveredMessageId !== "number") {
340
+ return;
341
+ }
342
+ try {
343
+ await params.bot.api.pinChatMessage(params.chatId, params.firstDeliveredMessageId, {
344
+ disable_notification: true,
345
+ });
346
+ }
347
+ catch (err) {
348
+ logVerbose(`telegram pinChatMessage failed chat=${params.chatId} message=${params.firstDeliveredMessageId}: ${formatErrorMessage(err)}`);
349
+ }
350
+ }
351
+ function emitMessageSentHooks(params) {
352
+ if (!params.enabled && !params.sessionKeyForInternalHooks) {
353
+ return;
354
+ }
355
+ const canonical = buildCanonicalSentMessageHookContext({
356
+ to: params.chatId,
357
+ content: params.content,
358
+ success: params.success,
359
+ error: params.error,
360
+ channelId: "telegram",
361
+ accountId: params.accountId,
362
+ conversationId: params.chatId,
363
+ messageId: typeof params.messageId === "number" ? String(params.messageId) : undefined,
364
+ isGroup: params.isGroup,
365
+ groupId: params.groupId,
366
+ });
367
+ if (params.enabled) {
368
+ fireAndForgetHook(Promise.resolve(params.hookRunner.runMessageSent(toPluginMessageSentEvent(canonical), toPluginMessageContext(canonical))), "telegram: message_sent plugin hook failed");
369
+ }
370
+ if (!params.sessionKeyForInternalHooks) {
371
+ return;
372
+ }
373
+ fireAndForgetHook(triggerInternalHook(createInternalHookEvent("message", "sent", params.sessionKeyForInternalHooks, toInternalMessageSentContext(canonical))), "telegram: message:sent internal hook failed");
374
+ }
375
+ export async function deliverReplies(params) {
376
+ const progress = {
377
+ hasReplied: false,
378
+ hasDelivered: false,
379
+ deliveredCount: 0,
380
+ };
381
+ const hookRunner = getGlobalHookRunner();
382
+ const hasMessageSendingHooks = hookRunner?.hasHooks("message_sending") ?? false;
383
+ const hasMessageSentHooks = hookRunner?.hasHooks("message_sent") ?? false;
384
+ const chunkText = buildChunkTextResolver({
385
+ textLimit: params.textLimit,
386
+ chunkMode: params.chunkMode ?? "length",
387
+ tableMode: params.tableMode,
388
+ });
389
+ for (const originalReply of params.replies) {
390
+ let reply = originalReply;
391
+ const mediaList = reply?.mediaUrls?.length
392
+ ? reply.mediaUrls
393
+ : reply?.mediaUrl
394
+ ? [reply.mediaUrl]
395
+ : [];
396
+ const hasMedia = mediaList.length > 0;
397
+ if (!reply?.text && !hasMedia) {
398
+ if (reply?.audioAsVoice) {
399
+ logVerbose("telegram reply has audioAsVoice without media/text; skipping");
400
+ continue;
401
+ }
402
+ params.runtime.error?.(danger("reply missing text/media"));
403
+ continue;
404
+ }
405
+ const rawContent = reply.text || "";
406
+ if (hasMessageSendingHooks) {
407
+ const hookResult = await hookRunner?.runMessageSending({
408
+ to: params.chatId,
409
+ content: rawContent,
410
+ metadata: {
411
+ channel: "telegram",
412
+ mediaUrls: mediaList,
413
+ threadId: params.thread?.id,
414
+ },
415
+ }, {
416
+ channelId: "telegram",
417
+ accountId: params.accountId,
418
+ conversationId: params.chatId,
419
+ });
420
+ if (hookResult?.cancel) {
421
+ continue;
422
+ }
423
+ if (typeof hookResult?.content === "string" && hookResult.content !== rawContent) {
424
+ reply = { ...reply, text: hookResult.content };
425
+ }
426
+ }
427
+ const contentForSentHook = reply.text || "";
428
+ try {
429
+ const deliveredCountBeforeReply = progress.deliveredCount;
430
+ const replyToId = params.replyToMode === "off" ? undefined : resolveTelegramReplyId(reply.replyToId);
431
+ const telegramData = reply.channelData?.telegram;
432
+ const shouldPinFirstMessage = telegramData?.pin === true;
433
+ const replyMarkup = buildInlineKeyboard(telegramData?.buttons);
434
+ let firstDeliveredMessageId;
435
+ if (mediaList.length === 0) {
436
+ firstDeliveredMessageId = await deliverTextReply({
437
+ bot: params.bot,
438
+ chatId: params.chatId,
439
+ runtime: params.runtime,
440
+ thread: params.thread,
441
+ chunkText,
442
+ replyText: reply.text || "",
443
+ replyMarkup,
444
+ replyQuoteText: params.replyQuoteText,
445
+ linkPreview: params.linkPreview,
446
+ replyToId,
447
+ replyToMode: params.replyToMode,
448
+ progress,
449
+ });
450
+ }
451
+ else {
452
+ firstDeliveredMessageId = await deliverMediaReply({
453
+ reply,
454
+ mediaList,
455
+ bot: params.bot,
456
+ chatId: params.chatId,
457
+ runtime: params.runtime,
458
+ thread: params.thread,
459
+ tableMode: params.tableMode,
460
+ mediaLocalRoots: params.mediaLocalRoots,
461
+ chunkText,
462
+ onVoiceRecording: params.onVoiceRecording,
463
+ linkPreview: params.linkPreview,
464
+ replyQuoteText: params.replyQuoteText,
465
+ replyMarkup,
466
+ replyToId,
467
+ replyToMode: params.replyToMode,
468
+ progress,
469
+ });
470
+ }
471
+ await maybePinFirstDeliveredMessage({
472
+ shouldPin: shouldPinFirstMessage,
473
+ bot: params.bot,
474
+ chatId: params.chatId,
475
+ runtime: params.runtime,
476
+ firstDeliveredMessageId,
477
+ });
478
+ emitMessageSentHooks({
479
+ hookRunner,
480
+ enabled: hasMessageSentHooks,
481
+ sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
482
+ chatId: params.chatId,
483
+ accountId: params.accountId,
484
+ content: contentForSentHook,
485
+ success: progress.deliveredCount > deliveredCountBeforeReply,
486
+ messageId: firstDeliveredMessageId,
487
+ isGroup: params.mirrorIsGroup,
488
+ groupId: params.mirrorGroupId,
489
+ });
490
+ }
491
+ catch (error) {
492
+ emitMessageSentHooks({
493
+ hookRunner,
494
+ enabled: hasMessageSentHooks,
495
+ sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
496
+ chatId: params.chatId,
497
+ accountId: params.accountId,
498
+ content: contentForSentHook,
499
+ success: false,
500
+ error: error instanceof Error ? error.message : String(error),
501
+ isGroup: params.mirrorIsGroup,
502
+ groupId: params.mirrorGroupId,
503
+ });
504
+ throw error;
505
+ }
506
+ }
507
+ return { delivered: progress.hasDelivered };
508
+ }
@@ -0,0 +1,86 @@
1
+ import { logVerbose } from "../../../src/globals.js";
2
+ import { issuePairingChallenge } from "../../../src/pairing/pairing-challenge.js";
3
+ import { upsertChannelPairingRequest } from "../../../src/pairing/pairing-store.js";
4
+ import { withTelegramApiErrorLogging } from "./api-logging.js";
5
+ import { resolveSenderAllowMatch } from "./bot-access.js";
6
+ function resolveTelegramSenderIdentity(msg, chatId) {
7
+ const from = msg.from;
8
+ const userId = from?.id != null ? String(from.id) : null;
9
+ return {
10
+ username: from?.username ?? "",
11
+ userId,
12
+ candidateId: userId ?? String(chatId),
13
+ firstName: from?.first_name,
14
+ lastName: from?.last_name,
15
+ };
16
+ }
17
+ export async function enforceTelegramDmAccess(params) {
18
+ const { isGroup, dmPolicy, msg, chatId, effectiveDmAllow, accountId, bot, logger } = params;
19
+ if (isGroup) {
20
+ return true;
21
+ }
22
+ if (dmPolicy === "disabled") {
23
+ return false;
24
+ }
25
+ if (dmPolicy === "open") {
26
+ return true;
27
+ }
28
+ const sender = resolveTelegramSenderIdentity(msg, chatId);
29
+ const allowMatch = resolveSenderAllowMatch({
30
+ allow: effectiveDmAllow,
31
+ senderId: sender.candidateId,
32
+ senderUsername: sender.username,
33
+ });
34
+ const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${allowMatch.matchSource ?? "none"}`;
35
+ const allowed = effectiveDmAllow.hasWildcard || (effectiveDmAllow.hasEntries && allowMatch.allowed);
36
+ if (allowed) {
37
+ return true;
38
+ }
39
+ if (dmPolicy === "pairing") {
40
+ try {
41
+ const telegramUserId = sender.userId ?? sender.candidateId;
42
+ await issuePairingChallenge({
43
+ channel: "telegram",
44
+ senderId: telegramUserId,
45
+ senderIdLine: `Your Telegram user id: ${telegramUserId}`,
46
+ meta: {
47
+ username: sender.username || undefined,
48
+ firstName: sender.firstName,
49
+ lastName: sender.lastName,
50
+ },
51
+ upsertPairingRequest: async ({ id, meta }) => await upsertChannelPairingRequest({
52
+ channel: "telegram",
53
+ id,
54
+ accountId,
55
+ meta,
56
+ }),
57
+ onCreated: () => {
58
+ logger.info({
59
+ chatId: String(chatId),
60
+ senderUserId: sender.userId ?? undefined,
61
+ username: sender.username || undefined,
62
+ firstName: sender.firstName,
63
+ lastName: sender.lastName,
64
+ matchKey: allowMatch.matchKey ?? "none",
65
+ matchSource: allowMatch.matchSource ?? "none",
66
+ }, "telegram pairing request");
67
+ },
68
+ sendPairingReply: async (text) => {
69
+ await withTelegramApiErrorLogging({
70
+ operation: "sendMessage",
71
+ fn: () => bot.api.sendMessage(chatId, text),
72
+ });
73
+ },
74
+ onReplyError: (err) => {
75
+ logVerbose(`telegram pairing reply failed for chat ${chatId}: ${String(err)}`);
76
+ },
77
+ });
78
+ }
79
+ catch (err) {
80
+ logVerbose(`telegram pairing reply failed for chat ${chatId}: ${String(err)}`);
81
+ }
82
+ return false;
83
+ }
84
+ logVerbose(`Blocked unauthorized telegram sender ${sender.candidateId} (dmPolicy=${dmPolicy}, ${allowMatchMeta})`);
85
+ return false;
86
+ }
@@ -0,0 +1,62 @@
1
+ import { vi } from "vitest";
2
+ export function createTestDraftStream(params) {
3
+ let messageId = params?.messageId;
4
+ let previewRevision = 0;
5
+ let lastDeliveredText = "";
6
+ return {
7
+ update: vi.fn().mockImplementation((text) => {
8
+ previewRevision += 1;
9
+ lastDeliveredText = text.trimEnd();
10
+ params?.onUpdate?.(text);
11
+ }),
12
+ flush: vi.fn().mockResolvedValue(undefined),
13
+ messageId: vi.fn().mockImplementation(() => messageId),
14
+ previewMode: vi.fn().mockReturnValue(params?.previewMode ?? "message"),
15
+ previewRevision: vi.fn().mockImplementation(() => previewRevision),
16
+ lastDeliveredText: vi.fn().mockImplementation(() => lastDeliveredText),
17
+ clear: vi.fn().mockResolvedValue(undefined),
18
+ stop: vi.fn().mockImplementation(async () => {
19
+ await params?.onStop?.();
20
+ }),
21
+ materialize: vi.fn().mockImplementation(async () => messageId),
22
+ forceNewMessage: vi.fn().mockImplementation(() => {
23
+ if (params?.clearMessageIdOnForceNew) {
24
+ messageId = undefined;
25
+ }
26
+ }),
27
+ sendMayHaveLanded: vi.fn().mockReturnValue(false),
28
+ setMessageId: (value) => {
29
+ messageId = value;
30
+ },
31
+ };
32
+ }
33
+ export function createSequencedTestDraftStream(startMessageId = 1001) {
34
+ let activeMessageId;
35
+ let nextMessageId = startMessageId;
36
+ let previewRevision = 0;
37
+ let lastDeliveredText = "";
38
+ return {
39
+ update: vi.fn().mockImplementation((text) => {
40
+ if (activeMessageId == null) {
41
+ activeMessageId = nextMessageId++;
42
+ }
43
+ previewRevision += 1;
44
+ lastDeliveredText = text.trimEnd();
45
+ }),
46
+ flush: vi.fn().mockResolvedValue(undefined),
47
+ messageId: vi.fn().mockImplementation(() => activeMessageId),
48
+ previewMode: vi.fn().mockReturnValue("message"),
49
+ previewRevision: vi.fn().mockImplementation(() => previewRevision),
50
+ lastDeliveredText: vi.fn().mockImplementation(() => lastDeliveredText),
51
+ clear: vi.fn().mockResolvedValue(undefined),
52
+ stop: vi.fn().mockResolvedValue(undefined),
53
+ materialize: vi.fn().mockImplementation(async () => activeMessageId),
54
+ forceNewMessage: vi.fn().mockImplementation(() => {
55
+ activeMessageId = undefined;
56
+ }),
57
+ sendMayHaveLanded: vi.fn().mockReturnValue(false),
58
+ setMessageId: (value) => {
59
+ activeMessageId = value;
60
+ },
61
+ };
62
+ }