@poolzin/pool-bot 2026.2.21 → 2026.2.22

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 (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Slack native text streaming helpers.
3
+ *
4
+ * Uses the Slack SDK's `ChatStreamer` (via `client.chatStream()`) to stream
5
+ * text responses word-by-word in a single updating message, matching Slack's
6
+ * "Agents & AI Apps" streaming UX.
7
+ *
8
+ * @see https://docs.slack.dev/ai/developing-ai-apps#streaming
9
+ * @see https://docs.slack.dev/reference/methods/chat.startStream
10
+ * @see https://docs.slack.dev/reference/methods/chat.appendStream
11
+ * @see https://docs.slack.dev/reference/methods/chat.stopStream
12
+ */
13
+ import { logVerbose } from "../globals.js";
14
+ // ---------------------------------------------------------------------------
15
+ // Stream lifecycle
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Start a new Slack text stream.
19
+ *
20
+ * Returns a {@link SlackStreamSession} that should be passed to
21
+ * {@link appendSlackStream} and {@link stopSlackStream}.
22
+ *
23
+ * The first chunk of text can optionally be included via `text`.
24
+ */
25
+ export async function startSlackStream(params) {
26
+ const { client, channel, threadTs, text } = params;
27
+ logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}`);
28
+ const streamer = client.chatStream({
29
+ channel,
30
+ thread_ts: threadTs,
31
+ });
32
+ const session = {
33
+ streamer,
34
+ channel,
35
+ threadTs,
36
+ stopped: false,
37
+ };
38
+ // If initial text is provided, send it as the first append which will
39
+ // trigger the ChatStreamer to call chat.startStream under the hood.
40
+ if (text) {
41
+ await streamer.append({ markdown_text: text });
42
+ logVerbose(`slack-stream: appended initial text (${text.length} chars)`);
43
+ }
44
+ return session;
45
+ }
46
+ /**
47
+ * Append markdown text to an active Slack stream.
48
+ */
49
+ export async function appendSlackStream(params) {
50
+ const { session, text } = params;
51
+ if (session.stopped) {
52
+ logVerbose("slack-stream: attempted to append to a stopped stream, ignoring");
53
+ return;
54
+ }
55
+ if (!text) {
56
+ return;
57
+ }
58
+ await session.streamer.append({ markdown_text: text });
59
+ logVerbose(`slack-stream: appended ${text.length} chars`);
60
+ }
61
+ /**
62
+ * Stop (finalize) a Slack stream.
63
+ *
64
+ * After calling this the stream message becomes a normal Slack message.
65
+ * Optionally include final text to append before stopping.
66
+ */
67
+ export async function stopSlackStream(params) {
68
+ const { session, text } = params;
69
+ if (session.stopped) {
70
+ logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
71
+ return;
72
+ }
73
+ session.stopped = true;
74
+ logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}${text ? ` (final text: ${text.length} chars)` : ""}`);
75
+ await session.streamer.stop(text ? { markdown_text: text } : undefined);
76
+ logVerbose("slack-stream: stream stopped");
77
+ }
@@ -3,18 +3,20 @@ import { listBoundAccountIds, resolveDefaultAgentBoundAccountId } from "../routi
3
3
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
4
4
  import { resolveTelegramToken } from "./token.js";
5
5
  const debugAccounts = (...args) => {
6
- if (isTruthyEnvValue(process.env.POOLBOT_DEBUG_TELEGRAM_ACCOUNTS || process.env.CLAWDBOT_DEBUG_TELEGRAM_ACCOUNTS)) {
6
+ if (isTruthyEnvValue(process.env.POOLBOT_DEBUG_TELEGRAM_ACCOUNTS)) {
7
7
  console.warn("[telegram:accounts]", ...args);
8
8
  }
9
9
  };
10
10
  function listConfiguredAccountIds(cfg) {
11
11
  const accounts = cfg.channels?.telegram?.accounts;
12
- if (!accounts || typeof accounts !== "object")
12
+ if (!accounts || typeof accounts !== "object") {
13
13
  return [];
14
+ }
14
15
  const ids = new Set();
15
16
  for (const key of Object.keys(accounts)) {
16
- if (!key)
17
+ if (!key) {
17
18
  continue;
19
+ }
18
20
  ids.add(normalizeAccountId(key));
19
21
  }
20
22
  return [...ids];
@@ -22,26 +24,31 @@ function listConfiguredAccountIds(cfg) {
22
24
  export function listTelegramAccountIds(cfg) {
23
25
  const ids = Array.from(new Set([...listConfiguredAccountIds(cfg), ...listBoundAccountIds(cfg, "telegram")]));
24
26
  debugAccounts("listTelegramAccountIds", ids);
25
- if (ids.length === 0)
27
+ if (ids.length === 0) {
26
28
  return [DEFAULT_ACCOUNT_ID];
27
- return ids.sort((a, b) => a.localeCompare(b));
29
+ }
30
+ return ids.toSorted((a, b) => a.localeCompare(b));
28
31
  }
29
32
  export function resolveDefaultTelegramAccountId(cfg) {
30
33
  const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram");
31
- if (boundDefault)
34
+ if (boundDefault) {
32
35
  return boundDefault;
36
+ }
33
37
  const ids = listTelegramAccountIds(cfg);
34
- if (ids.includes(DEFAULT_ACCOUNT_ID))
38
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
35
39
  return DEFAULT_ACCOUNT_ID;
40
+ }
36
41
  return ids[0] ?? DEFAULT_ACCOUNT_ID;
37
42
  }
38
43
  function resolveAccountConfig(cfg, accountId) {
39
44
  const accounts = cfg.channels?.telegram?.accounts;
40
- if (!accounts || typeof accounts !== "object")
45
+ if (!accounts || typeof accounts !== "object") {
41
46
  return undefined;
47
+ }
42
48
  const direct = accounts[accountId];
43
- if (direct)
49
+ if (direct) {
44
50
  return direct;
51
+ }
45
52
  const normalized = normalizeAccountId(accountId);
46
53
  const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
47
54
  return matchKey ? accounts[matchKey] : undefined;
@@ -52,6 +59,22 @@ function mergeTelegramAccountConfig(cfg, accountId) {
52
59
  const account = resolveAccountConfig(cfg, accountId) ?? {};
53
60
  return { ...base, ...account };
54
61
  }
62
+ export function createTelegramActionGate(params) {
63
+ const accountId = normalizeAccountId(params.accountId);
64
+ const baseActions = params.cfg.channels?.telegram?.actions;
65
+ const accountActions = resolveAccountConfig(params.cfg, accountId)?.actions;
66
+ return (key, defaultValue = true) => {
67
+ const accountValue = accountActions?.[key];
68
+ if (accountValue !== undefined) {
69
+ return accountValue;
70
+ }
71
+ const baseValue = baseActions?.[key];
72
+ if (baseValue !== undefined) {
73
+ return baseValue;
74
+ }
75
+ return defaultValue;
76
+ };
77
+ }
55
78
  export function resolveTelegramAccount(params) {
56
79
  const hasExplicitAccountId = Boolean(params.accountId?.trim());
57
80
  const baseEnabled = params.cfg.channels?.telegram?.enabled !== false;
@@ -76,19 +99,23 @@ export function resolveTelegramAccount(params) {
76
99
  };
77
100
  const normalized = normalizeAccountId(params.accountId);
78
101
  const primary = resolve(normalized);
79
- if (hasExplicitAccountId)
102
+ if (hasExplicitAccountId) {
80
103
  return primary;
81
- if (primary.tokenSource !== "none")
104
+ }
105
+ if (primary.tokenSource !== "none") {
82
106
  return primary;
107
+ }
83
108
  // If accountId is omitted, prefer a configured account token over failing on
84
109
  // the implicit "default" account. This keeps env-based setups working while
85
110
  // making config-only tokens work for things like heartbeats.
86
111
  const fallbackId = resolveDefaultTelegramAccountId(params.cfg);
87
- if (fallbackId === primary.accountId)
112
+ if (fallbackId === primary.accountId) {
88
113
  return primary;
114
+ }
89
115
  const fallback = resolve(fallbackId);
90
- if (fallback.tokenSource === "none")
116
+ if (fallback.tokenSource === "none") {
91
117
  return primary;
118
+ }
92
119
  return fallback;
93
120
  }
94
121
  export function listEnabledTelegramAccounts(cfg) {
@@ -4,5 +4,8 @@ export function resolveTelegramAllowedUpdates() {
4
4
  if (!updates.includes("message_reaction")) {
5
5
  updates.push("message_reaction");
6
6
  }
7
+ if (!updates.includes("channel_post")) {
8
+ updates.push("channel_post");
9
+ }
7
10
  return updates;
8
11
  }
@@ -1,26 +1,31 @@
1
1
  import { GrammyError, InputFile } from "grammy";
2
- import { markdownToTelegramChunks, markdownToTelegramHtml, renderTelegramHtmlText, } from "../format.js";
3
- import { withTelegramApiErrorLogging } from "../api-logging.js";
4
2
  import { chunkMarkdownTextWithMode } from "../../auto-reply/chunk.js";
5
- import { splitTelegramCaption } from "../caption.js";
6
- import { danger, logVerbose } from "../../globals.js";
3
+ import { danger, logVerbose, warn } from "../../globals.js";
7
4
  import { formatErrorMessage } from "../../infra/errors.js";
5
+ import { retryAsync } from "../../infra/retry.js";
8
6
  import { mediaKindFromMime } from "../../media/constants.js";
9
7
  import { fetchRemoteMedia } from "../../media/fetch.js";
10
8
  import { isGifMedia } from "../../media/mime.js";
11
9
  import { saveMediaBuffer } from "../../media/store.js";
12
10
  import { loadWebMedia } from "../../web/media.js";
11
+ import { withTelegramApiErrorLogging } from "../api-logging.js";
12
+ import { splitTelegramCaption } from "../caption.js";
13
+ import { markdownToTelegramChunks, markdownToTelegramHtml, renderTelegramHtmlText, wrapFileReferencesInHtml, } from "../format.js";
13
14
  import { buildInlineKeyboard } from "../send.js";
14
- import { resolveTelegramVoiceSend } from "../voice.js";
15
- import { buildTelegramThreadParams, resolveTelegramReplyId } from "./helpers.js";
16
15
  import { cacheSticker, getCachedSticker } from "../sticker-cache.js";
16
+ import { resolveTelegramVoiceSend } from "../voice.js";
17
+ import { buildTelegramThreadParams, resolveTelegramMediaPlaceholder, resolveTelegramReplyId, } from "./helpers.js";
17
18
  const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
18
19
  const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/;
20
+ const FILE_TOO_BIG_RE = /file is too big/i;
19
21
  export async function deliverReplies(params) {
20
- const { replies, chatId, runtime, bot, replyToMode, textLimit, messageThreadId, linkPreview } = params;
22
+ const { replies, chatId, runtime, bot, replyToMode, textLimit, thread, linkPreview, replyQuoteText, } = params;
21
23
  const chunkMode = params.chunkMode ?? "length";
22
- const threadParams = buildTelegramThreadParams(messageThreadId);
23
24
  let hasReplied = false;
25
+ let hasDelivered = false;
26
+ const markDelivered = () => {
27
+ hasDelivered = true;
28
+ };
24
29
  const chunkText = (markdown) => {
25
30
  const markdownChunks = chunkMode === "newline"
26
31
  ? chunkMarkdownTextWithMode(markdown, textLimit, chunkMode)
@@ -30,7 +35,7 @@ export async function deliverReplies(params) {
30
35
  const nested = markdownToTelegramChunks(chunk, textLimit, { tableMode: params.tableMode });
31
36
  if (!nested.length && chunk) {
32
37
  chunks.push({
33
- html: markdownToTelegramHtml(chunk, { tableMode: params.tableMode }),
38
+ html: wrapFileReferencesInHtml(markdownToTelegramHtml(chunk, { tableMode: params.tableMode, wrapFileRefs: false })),
34
39
  text: chunk,
35
40
  });
36
41
  continue;
@@ -50,6 +55,7 @@ export async function deliverReplies(params) {
50
55
  continue;
51
56
  }
52
57
  const replyToId = replyToMode === "off" ? undefined : resolveTelegramReplyId(reply.replyToId);
58
+ const replyToMessageIdForPayload = replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined;
53
59
  const mediaList = reply.mediaUrls?.length
54
60
  ? reply.mediaUrls
55
61
  : reply.mediaUrl
@@ -59,23 +65,28 @@ export async function deliverReplies(params) {
59
65
  const replyMarkup = buildInlineKeyboard(telegramData?.buttons);
60
66
  if (mediaList.length === 0) {
61
67
  const chunks = chunkText(reply.text || "");
68
+ let sentTextChunk = false;
62
69
  for (let i = 0; i < chunks.length; i += 1) {
63
70
  const chunk = chunks[i];
64
- if (!chunk)
71
+ if (!chunk) {
65
72
  continue;
73
+ }
66
74
  // Only attach buttons to the first chunk.
67
75
  const shouldAttachButtons = i === 0 && replyMarkup;
68
76
  await sendTelegramText(bot, chatId, chunk.html, runtime, {
69
- replyToMessageId: replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined,
70
- messageThreadId,
77
+ replyToMessageId: replyToMessageIdForPayload,
78
+ replyQuoteText,
79
+ thread,
71
80
  textMode: "html",
72
81
  plainText: chunk.text,
73
82
  linkPreview,
74
83
  replyMarkup: shouldAttachButtons ? replyMarkup : undefined,
75
84
  });
76
- if (replyToId && !hasReplied) {
77
- hasReplied = true;
78
- }
85
+ sentTextChunk = true;
86
+ markDelivered();
87
+ }
88
+ if (replyToMessageIdForPayload && !hasReplied && sentTextChunk) {
89
+ hasReplied = true;
79
90
  }
80
91
  continue;
81
92
  }
@@ -86,7 +97,9 @@ export async function deliverReplies(params) {
86
97
  let pendingFollowUpText;
87
98
  for (const mediaUrl of mediaList) {
88
99
  const isFirstMedia = first;
89
- const media = await loadWebMedia(mediaUrl);
100
+ const media = await loadWebMedia(mediaUrl, {
101
+ localRoots: params.mediaLocalRoots,
102
+ });
90
103
  const kind = mediaKindFromMime(media.contentType ?? undefined);
91
104
  const isGif = isGifMedia({
92
105
  contentType: media.contentType,
@@ -103,23 +116,24 @@ export async function deliverReplies(params) {
103
116
  pendingFollowUpText = followUpText;
104
117
  }
105
118
  first = false;
106
- const replyToMessageId = replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined;
119
+ const replyToMessageId = replyToMessageIdForPayload;
107
120
  const shouldAttachButtonsToMedia = isFirstMedia && replyMarkup && !followUpText;
108
121
  const mediaParams = {
109
122
  caption: htmlCaption,
110
- reply_to_message_id: replyToMessageId,
111
123
  ...(htmlCaption ? { parse_mode: "HTML" } : {}),
112
124
  ...(shouldAttachButtonsToMedia ? { reply_markup: replyMarkup } : {}),
125
+ ...buildTelegramSendParams({
126
+ replyToMessageId,
127
+ thread,
128
+ }),
113
129
  };
114
- if (threadParams) {
115
- mediaParams.message_thread_id = threadParams.message_thread_id;
116
- }
117
130
  if (isGif) {
118
131
  await withTelegramApiErrorLogging({
119
132
  operation: "sendAnimation",
120
133
  runtime,
121
134
  fn: () => bot.api.sendAnimation(chatId, file, { ...mediaParams }),
122
135
  });
136
+ markDelivered();
123
137
  }
124
138
  else if (kind === "image") {
125
139
  await withTelegramApiErrorLogging({
@@ -127,6 +141,7 @@ export async function deliverReplies(params) {
127
141
  runtime,
128
142
  fn: () => bot.api.sendPhoto(chatId, file, { ...mediaParams }),
129
143
  });
144
+ markDelivered();
130
145
  }
131
146
  else if (kind === "video") {
132
147
  await withTelegramApiErrorLogging({
@@ -134,6 +149,7 @@ export async function deliverReplies(params) {
134
149
  runtime,
135
150
  fn: () => bot.api.sendVideo(chatId, file, { ...mediaParams }),
136
151
  });
152
+ markDelivered();
137
153
  }
138
154
  else if (kind === "audio") {
139
155
  const { useVoice } = resolveTelegramVoiceSend({
@@ -153,6 +169,7 @@ export async function deliverReplies(params) {
153
169
  shouldLog: (err) => !isVoiceMessagesForbidden(err),
154
170
  fn: () => bot.api.sendVoice(chatId, file, { ...mediaParams }),
155
171
  });
172
+ markDelivered();
156
173
  }
157
174
  catch (voiceErr) {
158
175
  // Fall back to text if voice messages are forbidden in this chat.
@@ -164,19 +181,22 @@ export async function deliverReplies(params) {
164
181
  throw voiceErr;
165
182
  }
166
183
  logVerbose("telegram sendVoice forbidden (recipient has voice messages blocked in privacy settings); falling back to text");
167
- hasReplied = await sendTelegramVoiceFallbackText({
184
+ await sendTelegramVoiceFallbackText({
168
185
  bot,
169
186
  chatId,
170
187
  runtime,
171
188
  text: fallbackText,
172
189
  chunkText,
173
- replyToId,
174
- replyToMode,
175
- hasReplied,
176
- messageThreadId,
190
+ replyToId: replyToMessageIdForPayload,
191
+ thread,
177
192
  linkPreview,
178
193
  replyMarkup,
194
+ replyQuoteText,
179
195
  });
196
+ if (replyToMessageIdForPayload && !hasReplied) {
197
+ hasReplied = true;
198
+ }
199
+ markDelivered();
180
200
  // Skip this media item; continue with next.
181
201
  continue;
182
202
  }
@@ -190,6 +210,7 @@ export async function deliverReplies(params) {
190
210
  runtime,
191
211
  fn: () => bot.api.sendAudio(chatId, file, { ...mediaParams }),
192
212
  });
213
+ markDelivered();
193
214
  }
194
215
  }
195
216
  else {
@@ -198,6 +219,7 @@ export async function deliverReplies(params) {
198
219
  runtime,
199
220
  fn: () => bot.api.sendDocument(chatId, file, { ...mediaParams }),
200
221
  });
222
+ markDelivered();
201
223
  }
202
224
  if (replyToId && !hasReplied) {
203
225
  hasReplied = true;
@@ -208,26 +230,37 @@ export async function deliverReplies(params) {
208
230
  const chunks = chunkText(pendingFollowUpText);
209
231
  for (let i = 0; i < chunks.length; i += 1) {
210
232
  const chunk = chunks[i];
211
- const replyToMessageIdFollowup = replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined;
212
233
  await sendTelegramText(bot, chatId, chunk.html, runtime, {
213
- replyToMessageId: replyToMessageIdFollowup,
214
- messageThreadId,
234
+ replyToMessageId: replyToMessageIdForPayload,
235
+ thread,
215
236
  textMode: "html",
216
237
  plainText: chunk.text,
217
238
  linkPreview,
218
239
  replyMarkup: i === 0 ? replyMarkup : undefined,
219
240
  });
220
- if (replyToId && !hasReplied) {
221
- hasReplied = true;
222
- }
241
+ markDelivered();
223
242
  }
224
243
  pendingFollowUpText = undefined;
225
244
  }
245
+ if (replyToMessageIdForPayload && !hasReplied) {
246
+ hasReplied = true;
247
+ }
226
248
  }
227
249
  }
250
+ return { delivered: hasDelivered };
228
251
  }
229
252
  export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
230
253
  const msg = ctx.message;
254
+ const downloadAndSaveTelegramFile = async (filePath, fetchImpl) => {
255
+ const url = `https://api.telegram.org/file/bot${token}/${filePath}`;
256
+ const fetched = await fetchRemoteMedia({
257
+ url,
258
+ fetchImpl,
259
+ filePathHint: filePath,
260
+ });
261
+ const originalName = fetched.fileName ?? filePath;
262
+ return saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", maxBytes, originalName);
263
+ };
231
264
  // Handle stickers separately - only static stickers (WEBP) are supported
232
265
  if (msg.sticker) {
233
266
  const sticker = msg.sticker;
@@ -236,8 +269,9 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
236
269
  logVerbose("telegram: skipping animated/video sticker (only static stickers supported)");
237
270
  return null;
238
271
  }
239
- if (!sticker.file_id)
272
+ if (!sticker.file_id) {
240
273
  return null;
274
+ }
241
275
  try {
242
276
  const file = await ctx.getFile();
243
277
  if (!file.file_path) {
@@ -249,13 +283,7 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
249
283
  logVerbose("telegram: fetch not available for sticker download");
250
284
  return null;
251
285
  }
252
- const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`;
253
- const fetched = await fetchRemoteMedia({
254
- url,
255
- fetchImpl,
256
- filePathHint: file.file_path,
257
- });
258
- const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", maxBytes);
286
+ const saved = await downloadAndSaveTelegramFile(file.file_path, fetchImpl);
259
287
  // Check sticker cache for existing description
260
288
  const cached = sticker.file_unique_id ? getCachedSticker(sticker.file_unique_id) : null;
261
289
  if (cached) {
@@ -303,10 +331,38 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
303
331
  return null;
304
332
  }
305
333
  }
306
- const m = msg.photo?.[msg.photo.length - 1] ?? msg.video ?? msg.document ?? msg.audio ?? msg.voice;
307
- if (!m?.file_id)
334
+ const m = msg.photo?.[msg.photo.length - 1] ??
335
+ msg.video ??
336
+ msg.video_note ??
337
+ msg.document ??
338
+ msg.audio ??
339
+ msg.voice;
340
+ if (!m?.file_id) {
308
341
  return null;
309
- const file = await ctx.getFile();
342
+ }
343
+ let file;
344
+ try {
345
+ file = await retryAsync(() => ctx.getFile(), {
346
+ attempts: 3,
347
+ minDelayMs: 1000,
348
+ maxDelayMs: 4000,
349
+ jitter: 0.2,
350
+ label: "telegram:getFile",
351
+ shouldRetry: isRetryableGetFileError,
352
+ onRetry: ({ attempt, maxAttempts }) => logVerbose(`telegram: getFile retry ${attempt}/${maxAttempts}`),
353
+ });
354
+ }
355
+ catch (err) {
356
+ // Handle "file is too big" separately - Telegram Bot API has a 20MB download limit
357
+ if (isFileTooBigError(err)) {
358
+ logVerbose(warn("telegram: getFile failed - file exceeds Telegram Bot API 20MB limit; skipping attachment"));
359
+ return null;
360
+ }
361
+ // All retries exhausted — return null so the message still reaches the agent
362
+ // with a type-based placeholder (e.g. <media:audio>) instead of being dropped.
363
+ logVerbose(`telegram: getFile failed after retries: ${String(err)}`);
364
+ return null;
365
+ }
310
366
  if (!file.file_path) {
311
367
  throw new Error("Telegram getFile returned no file_path");
312
368
  }
@@ -314,20 +370,8 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
314
370
  if (!fetchImpl) {
315
371
  throw new Error("fetch is not available; set channels.telegram.proxy in config");
316
372
  }
317
- const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`;
318
- const fetched = await fetchRemoteMedia({
319
- url,
320
- fetchImpl,
321
- filePathHint: file.file_path,
322
- });
323
- const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", maxBytes);
324
- let placeholder = "<media:document>";
325
- if (msg.photo)
326
- placeholder = "<media:image>";
327
- else if (msg.video)
328
- placeholder = "<media:video>";
329
- else if (msg.audio || msg.voice)
330
- placeholder = "<media:audio>";
373
+ const saved = await downloadAndSaveTelegramFile(file.file_path, fetchImpl);
374
+ const placeholder = resolveTelegramMediaPlaceholder(msg) ?? "<media:document>";
331
375
  return { path: saved.path, contentType: saved.contentType, placeholder };
332
376
  }
333
377
  function isVoiceMessagesForbidden(err) {
@@ -336,27 +380,46 @@ function isVoiceMessagesForbidden(err) {
336
380
  }
337
381
  return VOICE_FORBIDDEN_RE.test(formatErrorMessage(err));
338
382
  }
383
+ /**
384
+ * Returns true if the error is Telegram's "file is too big" error.
385
+ * This happens when trying to download files >20MB via the Bot API.
386
+ * Unlike network errors, this is a permanent error and should not be retried.
387
+ */
388
+ function isFileTooBigError(err) {
389
+ if (err instanceof GrammyError) {
390
+ return FILE_TOO_BIG_RE.test(err.description);
391
+ }
392
+ return FILE_TOO_BIG_RE.test(formatErrorMessage(err));
393
+ }
394
+ /**
395
+ * Returns true if the error is a transient network error that should be retried.
396
+ * Returns false for permanent errors like "file is too big" (400 Bad Request).
397
+ */
398
+ function isRetryableGetFileError(err) {
399
+ // Don't retry "file is too big" - it's a permanent 400 error
400
+ if (isFileTooBigError(err)) {
401
+ return false;
402
+ }
403
+ // Retry all other errors (network issues, timeouts, etc.)
404
+ return true;
405
+ }
339
406
  async function sendTelegramVoiceFallbackText(opts) {
340
407
  const chunks = opts.chunkText(opts.text);
341
- let hasReplied = opts.hasReplied;
342
408
  for (let i = 0; i < chunks.length; i += 1) {
343
409
  const chunk = chunks[i];
344
410
  await sendTelegramText(opts.bot, opts.chatId, chunk.html, opts.runtime, {
345
- replyToMessageId: opts.replyToId && (opts.replyToMode === "all" || !hasReplied) ? opts.replyToId : undefined,
346
- messageThreadId: opts.messageThreadId,
411
+ replyToMessageId: opts.replyToId,
412
+ replyQuoteText: opts.replyQuoteText,
413
+ thread: opts.thread,
347
414
  textMode: "html",
348
415
  plainText: chunk.text,
349
416
  linkPreview: opts.linkPreview,
350
417
  replyMarkup: i === 0 ? opts.replyMarkup : undefined,
351
418
  });
352
- if (opts.replyToId && !hasReplied) {
353
- hasReplied = true;
354
- }
355
419
  }
356
- return hasReplied;
357
420
  }
358
421
  function buildTelegramSendParams(opts) {
359
- const threadParams = buildTelegramThreadParams(opts?.messageThreadId);
422
+ const threadParams = buildTelegramThreadParams(opts?.thread);
360
423
  const params = {};
361
424
  if (opts?.replyToMessageId) {
362
425
  params.reply_to_message_id = opts.replyToMessageId;
@@ -369,7 +432,7 @@ function buildTelegramSendParams(opts) {
369
432
  async function sendTelegramText(bot, chatId, text, runtime, opts) {
370
433
  const baseParams = buildTelegramSendParams({
371
434
  replyToMessageId: opts?.replyToMessageId,
372
- messageThreadId: opts?.messageThreadId,
435
+ thread: opts?.thread,
373
436
  });
374
437
  // Add link_preview_options when link preview is disabled.
375
438
  const linkPreviewEnabled = opts?.linkPreview ?? true;