@poolzin/pool-bot 2026.2.21 → 2026.2.23

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 (378) hide show
  1. package/CHANGELOG.md +25 -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/device-pair/index.ts +2 -2
  326. package/extensions/diagnostics-otel/package.json +1 -1
  327. package/extensions/discord/package.json +1 -1
  328. package/extensions/feishu/package.json +1 -1
  329. package/extensions/google-antigravity-auth/package.json +1 -1
  330. package/extensions/google-gemini-cli-auth/package.json +1 -1
  331. package/extensions/googlechat/package.json +1 -1
  332. package/extensions/imessage/package.json +1 -1
  333. package/extensions/irc/package.json +1 -1
  334. package/extensions/irc/src/accounts.ts +1 -1
  335. package/extensions/irc/src/onboarding.ts +4 -4
  336. package/extensions/line/package.json +1 -1
  337. package/extensions/llm-task/package.json +1 -1
  338. package/extensions/lobster/package.json +1 -1
  339. package/extensions/matrix/CHANGELOG.md +10 -0
  340. package/extensions/matrix/package.json +1 -1
  341. package/extensions/mattermost/package.json +1 -1
  342. package/extensions/memory-core/package.json +1 -1
  343. package/extensions/memory-lancedb/package.json +1 -1
  344. package/extensions/minimax-portal-auth/package.json +1 -1
  345. package/extensions/msteams/CHANGELOG.md +10 -0
  346. package/extensions/msteams/package.json +1 -1
  347. package/extensions/nextcloud-talk/package.json +1 -1
  348. package/extensions/nostr/CHANGELOG.md +10 -0
  349. package/extensions/nostr/package.json +1 -1
  350. package/extensions/open-prose/package.json +1 -1
  351. package/extensions/openai-codex-auth/package.json +1 -1
  352. package/extensions/signal/package.json +1 -1
  353. package/extensions/slack/package.json +1 -1
  354. package/extensions/telegram/package.json +1 -1
  355. package/extensions/tlon/package.json +1 -1
  356. package/extensions/twitch/CHANGELOG.md +10 -0
  357. package/extensions/twitch/package.json +1 -1
  358. package/extensions/voice-call/CHANGELOG.md +10 -0
  359. package/extensions/voice-call/package.json +1 -1
  360. package/extensions/whatsapp/package.json +1 -1
  361. package/extensions/zalo/CHANGELOG.md +10 -0
  362. package/extensions/zalo/package.json +1 -1
  363. package/extensions/zalouser/CHANGELOG.md +10 -0
  364. package/extensions/zalouser/package.json +1 -1
  365. package/package.json +1 -1
  366. package/skills/apple-reminders/SKILL.md +100 -49
  367. package/skills/coding-agent/SKILL.md +34 -28
  368. package/skills/github/SKILL.md +131 -16
  369. package/skills/imsg/SKILL.md +112 -15
  370. package/skills/openhue/SKILL.md +101 -19
  371. package/skills/tmux/SKILL.md +111 -79
  372. package/skills/weather/SKILL.md +88 -25
  373. package/dist/agents/openclaw-tools.js +0 -151
  374. package/dist/agents/tool-security.js +0 -96
  375. package/dist/gateway/url-validation.js +0 -94
  376. package/dist/infra/openclaw-root.js +0 -109
  377. package/dist/infra/tmp-openclaw-dir.js +0 -81
  378. package/dist/media/path-sanitization.js +0 -78
@@ -1,12 +1,6 @@
1
+ import { normalizeHostname } from "../../infra/net/hostname.js";
1
2
  import { fetchRemoteMedia } from "../../media/fetch.js";
2
3
  import { saveMediaBuffer } from "../../media/store.js";
3
- function normalizeHostname(hostname) {
4
- const normalized = hostname.trim().toLowerCase().replace(/\.$/, "");
5
- if (normalized.startsWith("[") && normalized.endsWith("]")) {
6
- return normalized.slice(1, -1);
7
- }
8
- return normalized;
9
- }
10
4
  function isSlackHostname(hostname) {
11
5
  const normalized = normalizeHostname(hostname);
12
6
  if (!normalized) {
@@ -94,12 +88,72 @@ export async function fetchWithSlackAuth(url, token) {
94
88
  // (Slack's CDN URLs are pre-signed and don't need it)
95
89
  return fetch(resolvedUrl.toString(), { redirect: "follow" });
96
90
  }
91
+ /**
92
+ * Slack voice messages (audio clips, huddle recordings) carry a `subtype` of
93
+ * `"slack_audio"` but are served with a `video/*` MIME type (e.g. `video/mp4`,
94
+ * `video/webm`). Override the primary type to `audio/` so the
95
+ * media-understanding pipeline routes them to transcription.
96
+ */
97
+ function resolveSlackMediaMimetype(file, fetchedContentType) {
98
+ const mime = fetchedContentType ?? file.mimetype;
99
+ if (file.subtype === "slack_audio" && mime?.startsWith("video/")) {
100
+ return mime.replace("video/", "audio/");
101
+ }
102
+ return mime;
103
+ }
104
+ const MAX_SLACK_MEDIA_FILES = 8;
105
+ const MAX_SLACK_MEDIA_CONCURRENCY = 3;
106
+ const MAX_SLACK_FORWARDED_ATTACHMENTS = 8;
107
+ function isForwardedSlackAttachment(attachment) {
108
+ // Narrow this parser to Slack's explicit "shared/forwarded" attachment payloads.
109
+ return attachment.is_share === true;
110
+ }
111
+ function resolveForwardedAttachmentImageUrl(attachment) {
112
+ const rawUrl = attachment.image_url?.trim();
113
+ if (!rawUrl) {
114
+ return null;
115
+ }
116
+ try {
117
+ const parsed = new URL(rawUrl);
118
+ if (parsed.protocol !== "https:" || !isSlackHostname(parsed.hostname)) {
119
+ return null;
120
+ }
121
+ return parsed.toString();
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }
127
+ async function mapLimit(items, limit, fn) {
128
+ if (items.length === 0) {
129
+ return [];
130
+ }
131
+ const results = [];
132
+ results.length = items.length;
133
+ let nextIndex = 0;
134
+ const workerCount = Math.max(1, Math.min(limit, items.length));
135
+ await Promise.all(Array.from({ length: workerCount }, async () => {
136
+ while (true) {
137
+ const idx = nextIndex++;
138
+ if (idx >= items.length) {
139
+ return;
140
+ }
141
+ results[idx] = await fn(items[idx]);
142
+ }
143
+ }));
144
+ return results;
145
+ }
146
+ /**
147
+ * Downloads all files attached to a Slack message and returns them as an array.
148
+ * Returns `null` when no files could be downloaded.
149
+ */
97
150
  export async function resolveSlackMedia(params) {
98
151
  const files = params.files ?? [];
99
- for (const file of files) {
152
+ const limitedFiles = files.length > MAX_SLACK_MEDIA_FILES ? files.slice(0, MAX_SLACK_MEDIA_FILES) : files;
153
+ const resolved = await mapLimit(limitedFiles, MAX_SLACK_MEDIA_CONCURRENCY, async (file) => {
100
154
  const url = file.url_private_download ?? file.url_private;
101
155
  if (!url) {
102
- continue;
156
+ return null;
103
157
  }
104
158
  try {
105
159
  // Note: fetchRemoteMedia calls fetchImpl(url) with the URL string today and
@@ -113,28 +167,116 @@ export async function resolveSlackMedia(params) {
113
167
  maxBytes: params.maxBytes,
114
168
  });
115
169
  if (fetched.buffer.byteLength > params.maxBytes) {
116
- continue;
170
+ return null;
117
171
  }
118
- const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType ?? file.mimetype, "inbound", params.maxBytes);
172
+ const effectiveMime = resolveSlackMediaMimetype(file, fetched.contentType);
173
+ const saved = await saveMediaBuffer(fetched.buffer, effectiveMime, "inbound", params.maxBytes);
119
174
  const label = fetched.fileName ?? file.name;
175
+ const contentType = effectiveMime ?? saved.contentType;
120
176
  return {
121
177
  path: saved.path,
122
- contentType: saved.contentType,
178
+ ...(contentType ? { contentType } : {}),
123
179
  placeholder: label ? `[Slack file: ${label}]` : "[Slack file]",
124
180
  };
125
181
  }
126
182
  catch {
127
- // Ignore download failures and fall through to the next file.
183
+ return null;
184
+ }
185
+ });
186
+ const results = resolved.filter((entry) => Boolean(entry));
187
+ return results.length > 0 ? results : null;
188
+ }
189
+ /** Extracts text and media from forwarded-message attachments. Returns null when empty. */
190
+ export async function resolveSlackAttachmentContent(params) {
191
+ const attachments = params.attachments;
192
+ if (!attachments || attachments.length === 0) {
193
+ return null;
194
+ }
195
+ const forwardedAttachments = attachments
196
+ .filter((attachment) => isForwardedSlackAttachment(attachment))
197
+ .slice(0, MAX_SLACK_FORWARDED_ATTACHMENTS);
198
+ if (forwardedAttachments.length === 0) {
199
+ return null;
200
+ }
201
+ const textBlocks = [];
202
+ const allMedia = [];
203
+ for (const att of forwardedAttachments) {
204
+ const text = att.text?.trim() || att.fallback?.trim();
205
+ if (text) {
206
+ const author = att.author_name;
207
+ const heading = author ? `[Forwarded message from ${author}]` : "[Forwarded message]";
208
+ textBlocks.push(`${heading}\n${text}`);
209
+ }
210
+ const imageUrl = resolveForwardedAttachmentImageUrl(att);
211
+ if (imageUrl) {
212
+ try {
213
+ const fetched = await fetchRemoteMedia({
214
+ url: imageUrl,
215
+ maxBytes: params.maxBytes,
216
+ });
217
+ if (fetched.buffer.byteLength <= params.maxBytes) {
218
+ const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", params.maxBytes);
219
+ const label = fetched.fileName ?? "forwarded image";
220
+ allMedia.push({
221
+ path: saved.path,
222
+ contentType: fetched.contentType ?? saved.contentType,
223
+ placeholder: `[Forwarded image: ${label}]`,
224
+ });
225
+ }
226
+ }
227
+ catch {
228
+ // Skip images that fail to download
229
+ }
128
230
  }
231
+ if (att.files && att.files.length > 0) {
232
+ const fileMedia = await resolveSlackMedia({
233
+ files: att.files,
234
+ token: params.token,
235
+ maxBytes: params.maxBytes,
236
+ });
237
+ if (fileMedia) {
238
+ allMedia.push(...fileMedia);
239
+ }
240
+ }
241
+ }
242
+ const combinedText = textBlocks.join("\n\n");
243
+ if (!combinedText && allMedia.length === 0) {
244
+ return null;
129
245
  }
130
- return null;
246
+ return { text: combinedText, media: allMedia };
131
247
  }
132
248
  const THREAD_STARTER_CACHE = new Map();
249
+ const THREAD_STARTER_CACHE_TTL_MS = 6 * 60 * 60_000;
250
+ const THREAD_STARTER_CACHE_MAX = 2000;
251
+ function evictThreadStarterCache() {
252
+ const now = Date.now();
253
+ for (const [cacheKey, entry] of THREAD_STARTER_CACHE.entries()) {
254
+ if (now - entry.cachedAt > THREAD_STARTER_CACHE_TTL_MS) {
255
+ THREAD_STARTER_CACHE.delete(cacheKey);
256
+ }
257
+ }
258
+ if (THREAD_STARTER_CACHE.size <= THREAD_STARTER_CACHE_MAX) {
259
+ return;
260
+ }
261
+ const excess = THREAD_STARTER_CACHE.size - THREAD_STARTER_CACHE_MAX;
262
+ let removed = 0;
263
+ for (const cacheKey of THREAD_STARTER_CACHE.keys()) {
264
+ THREAD_STARTER_CACHE.delete(cacheKey);
265
+ removed += 1;
266
+ if (removed >= excess) {
267
+ break;
268
+ }
269
+ }
270
+ }
133
271
  export async function resolveSlackThreadStarter(params) {
272
+ evictThreadStarterCache();
134
273
  const cacheKey = `${params.channelId}:${params.threadTs}`;
135
274
  const cached = THREAD_STARTER_CACHE.get(cacheKey);
275
+ if (cached && Date.now() - cached.cachedAt <= THREAD_STARTER_CACHE_TTL_MS) {
276
+ return cached.value;
277
+ }
136
278
  if (cached) {
137
- return cached;
279
+ THREAD_STARTER_CACHE.delete(cacheKey);
138
280
  }
139
281
  try {
140
282
  const response = (await params.client.conversations.replies({
@@ -154,10 +296,76 @@ export async function resolveSlackThreadStarter(params) {
154
296
  ts: message.ts,
155
297
  files: message.files,
156
298
  };
157
- THREAD_STARTER_CACHE.set(cacheKey, starter);
299
+ if (THREAD_STARTER_CACHE.has(cacheKey)) {
300
+ THREAD_STARTER_CACHE.delete(cacheKey);
301
+ }
302
+ THREAD_STARTER_CACHE.set(cacheKey, {
303
+ value: starter,
304
+ cachedAt: Date.now(),
305
+ });
306
+ evictThreadStarterCache();
158
307
  return starter;
159
308
  }
160
309
  catch {
161
310
  return null;
162
311
  }
163
312
  }
313
+ export function resetSlackThreadStarterCacheForTest() {
314
+ THREAD_STARTER_CACHE.clear();
315
+ }
316
+ /**
317
+ * Fetches the most recent messages in a Slack thread (excluding the current message).
318
+ * Used to populate thread context when a new thread session starts.
319
+ *
320
+ * Uses cursor pagination and keeps only the latest N retained messages so long threads
321
+ * still produce up-to-date context without unbounded memory growth.
322
+ */
323
+ export async function resolveSlackThreadHistory(params) {
324
+ const maxMessages = params.limit ?? 20;
325
+ if (!Number.isFinite(maxMessages) || maxMessages <= 0) {
326
+ return [];
327
+ }
328
+ // Slack recommends no more than 200 per page.
329
+ const fetchLimit = 200;
330
+ const retained = [];
331
+ let cursor;
332
+ try {
333
+ do {
334
+ const response = (await params.client.conversations.replies({
335
+ channel: params.channelId,
336
+ ts: params.threadTs,
337
+ limit: fetchLimit,
338
+ inclusive: true,
339
+ ...(cursor ? { cursor } : {}),
340
+ }));
341
+ for (const msg of response.messages ?? []) {
342
+ // Keep messages with text OR file attachments
343
+ if (!msg.text?.trim() && !msg.files?.length) {
344
+ continue;
345
+ }
346
+ if (params.currentMessageTs && msg.ts === params.currentMessageTs) {
347
+ continue;
348
+ }
349
+ retained.push(msg);
350
+ if (retained.length > maxMessages) {
351
+ retained.shift();
352
+ }
353
+ }
354
+ const next = response.response_metadata?.next_cursor;
355
+ cursor = typeof next === "string" && next.trim().length > 0 ? next.trim() : undefined;
356
+ } while (cursor);
357
+ return retained.map((msg) => ({
358
+ // For file-only messages, create a placeholder showing attached filenames
359
+ text: msg.text?.trim()
360
+ ? msg.text
361
+ : `[attached: ${msg.files?.map((f) => f.name ?? "file").join(", ")}]`,
362
+ userId: msg.user,
363
+ botId: msg.bot_id,
364
+ ts: msg.ts,
365
+ files: msg.files,
366
+ }));
367
+ }
368
+ catch {
369
+ return [];
370
+ }
371
+ }
@@ -1,16 +1,43 @@
1
1
  import { resolveHumanDelayConfig } from "../../../agents/identity.js";
2
2
  import { dispatchInboundMessage } from "../../../auto-reply/dispatch.js";
3
3
  import { clearHistoryEntriesIfEnabled } from "../../../auto-reply/reply/history.js";
4
+ import { createReplyDispatcherWithTyping } from "../../../auto-reply/reply/reply-dispatcher.js";
4
5
  import { removeAckReactionAfterReply } from "../../../channels/ack-reactions.js";
5
6
  import { logAckFailure, logTypingFailure } from "../../../channels/logging.js";
6
- import { createReplyPrefixContext } from "../../../channels/reply-prefix.js";
7
+ import { createReplyPrefixOptions } from "../../../channels/reply-prefix.js";
7
8
  import { createTypingCallbacks } from "../../../channels/typing.js";
8
- import { createReplyDispatcherWithTyping } from "../../../auto-reply/reply/reply-dispatcher.js";
9
9
  import { resolveStorePath, updateLastRoute } from "../../../config/sessions.js";
10
10
  import { danger, logVerbose, shouldLogVerbose } from "../../../globals.js";
11
11
  import { removeSlackReaction } from "../../actions.js";
12
+ import { createSlackDraftStream } from "../../draft-stream.js";
13
+ import { applyAppendOnlyStreamUpdate, buildStatusFinalPreviewText, resolveSlackStreamMode, } from "../../stream-mode.js";
14
+ import { appendSlackStream, startSlackStream, stopSlackStream } from "../../streaming.js";
12
15
  import { resolveSlackThreadTargets } from "../../threading.js";
13
- import { createSlackReplyDeliveryPlan, deliverReplies } from "../replies.js";
16
+ import { createSlackReplyDeliveryPlan, deliverReplies, resolveSlackThreadTs } from "../replies.js";
17
+ function hasMedia(payload) {
18
+ return Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
19
+ }
20
+ export function isSlackStreamingEnabled(streaming) {
21
+ return streaming !== false;
22
+ }
23
+ export function resolveSlackStreamingThreadHint(params) {
24
+ return resolveSlackThreadTs({
25
+ replyToMode: params.replyToMode,
26
+ incomingThreadTs: params.incomingThreadTs,
27
+ messageTs: params.messageTs,
28
+ hasReplied: false,
29
+ });
30
+ }
31
+ function shouldUseStreaming(params) {
32
+ if (!params.streamingEnabled) {
33
+ return false;
34
+ }
35
+ if (!params.threadTs) {
36
+ logVerbose("slack-stream: streaming disabled — no reply thread target available");
37
+ return false;
38
+ }
39
+ return true;
40
+ }
14
41
  export async function dispatchPreparedSlackMessage(prepared) {
15
42
  const { ctx, account, message, route } = prepared;
16
43
  const cfg = ctx.cfg;
@@ -58,8 +85,9 @@ export async function dispatchPreparedSlackMessage(prepared) {
58
85
  });
59
86
  },
60
87
  stop: async () => {
61
- if (!didSetStatus)
88
+ if (!didSetStatus) {
62
89
  return;
90
+ }
63
91
  didSetStatus = false;
64
92
  await ctx.setSlackThreadStatus({
65
93
  channelId: message.channel,
@@ -86,12 +114,129 @@ export async function dispatchPreparedSlackMessage(prepared) {
86
114
  });
87
115
  },
88
116
  });
89
- const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
117
+ const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
118
+ cfg,
119
+ agentId: route.agentId,
120
+ channel: "slack",
121
+ accountId: route.accountId,
122
+ });
123
+ const streamingEnabled = isSlackStreamingEnabled(account.config.streaming);
124
+ const streamThreadHint = resolveSlackStreamingThreadHint({
125
+ replyToMode: ctx.replyToMode,
126
+ incomingThreadTs,
127
+ messageTs,
128
+ });
129
+ const useStreaming = shouldUseStreaming({
130
+ streamingEnabled,
131
+ threadTs: streamThreadHint,
132
+ });
133
+ let streamSession = null;
134
+ let streamFailed = false;
135
+ const deliverNormally = async (payload, forcedThreadTs) => {
136
+ const replyThreadTs = forcedThreadTs ?? replyPlan.nextThreadTs();
137
+ await deliverReplies({
138
+ replies: [payload],
139
+ target: prepared.replyTarget,
140
+ token: ctx.botToken,
141
+ accountId: account.accountId,
142
+ runtime,
143
+ textLimit: ctx.textLimit,
144
+ replyThreadTs,
145
+ });
146
+ replyPlan.markSent();
147
+ };
148
+ const deliverWithStreaming = async (payload) => {
149
+ if (streamFailed || hasMedia(payload) || !payload.text?.trim()) {
150
+ await deliverNormally(payload, streamSession?.threadTs);
151
+ return;
152
+ }
153
+ const text = payload.text.trim();
154
+ let plannedThreadTs;
155
+ try {
156
+ if (!streamSession) {
157
+ const streamThreadTs = replyPlan.nextThreadTs();
158
+ plannedThreadTs = streamThreadTs;
159
+ if (!streamThreadTs) {
160
+ logVerbose("slack-stream: no reply thread target for stream start, falling back to normal delivery");
161
+ streamFailed = true;
162
+ await deliverNormally(payload);
163
+ return;
164
+ }
165
+ streamSession = await startSlackStream({
166
+ client: ctx.app.client,
167
+ channel: message.channel,
168
+ threadTs: streamThreadTs,
169
+ text,
170
+ });
171
+ replyPlan.markSent();
172
+ return;
173
+ }
174
+ await appendSlackStream({
175
+ session: streamSession,
176
+ text: "\n" + text,
177
+ });
178
+ }
179
+ catch (err) {
180
+ runtime.error?.(danger(`slack-stream: streaming API call failed: ${String(err)}, falling back`));
181
+ streamFailed = true;
182
+ await deliverNormally(payload, streamSession?.threadTs ?? plannedThreadTs);
183
+ }
184
+ };
90
185
  const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
91
- responsePrefix: prefixContext.responsePrefix,
92
- responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
186
+ ...prefixOptions,
93
187
  humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
94
188
  deliver: async (payload) => {
189
+ if (useStreaming) {
190
+ await deliverWithStreaming(payload);
191
+ return;
192
+ }
193
+ const mediaCount = payload.mediaUrls?.length ?? (payload.mediaUrl ? 1 : 0);
194
+ const draftMessageId = draftStream?.messageId();
195
+ const draftChannelId = draftStream?.channelId();
196
+ const finalText = payload.text;
197
+ const canFinalizeViaPreviewEdit = streamMode !== "status_final" &&
198
+ mediaCount === 0 &&
199
+ !payload.isError &&
200
+ typeof finalText === "string" &&
201
+ finalText.trim().length > 0 &&
202
+ typeof draftMessageId === "string" &&
203
+ typeof draftChannelId === "string";
204
+ if (canFinalizeViaPreviewEdit) {
205
+ draftStream?.stop();
206
+ try {
207
+ await ctx.app.client.chat.update({
208
+ token: ctx.botToken,
209
+ channel: draftChannelId,
210
+ ts: draftMessageId,
211
+ text: finalText.trim(),
212
+ });
213
+ return;
214
+ }
215
+ catch (err) {
216
+ logVerbose(`slack: preview final edit failed; falling back to standard send (${String(err)})`);
217
+ }
218
+ }
219
+ else if (streamMode === "status_final" && hasStreamedMessage) {
220
+ try {
221
+ const statusChannelId = draftStream?.channelId();
222
+ const statusMessageId = draftStream?.messageId();
223
+ if (statusChannelId && statusMessageId) {
224
+ await ctx.app.client.chat.update({
225
+ token: ctx.botToken,
226
+ channel: statusChannelId,
227
+ ts: statusMessageId,
228
+ text: "Status: complete. Final answer posted below.",
229
+ });
230
+ }
231
+ }
232
+ catch (err) {
233
+ logVerbose(`slack: status_final completion update failed (${String(err)})`);
234
+ }
235
+ }
236
+ else if (mediaCount > 0) {
237
+ await draftStream?.clear();
238
+ hasStreamedMessage = false;
239
+ }
95
240
  const replyThreadTs = replyPlan.nextThreadTs();
96
241
  await deliverReplies({
97
242
  replies: [payload],
@@ -111,6 +256,53 @@ export async function dispatchPreparedSlackMessage(prepared) {
111
256
  onReplyStart: typingCallbacks.onReplyStart,
112
257
  onIdle: typingCallbacks.onIdle,
113
258
  });
259
+ const draftStream = createSlackDraftStream({
260
+ target: prepared.replyTarget,
261
+ token: ctx.botToken,
262
+ accountId: account.accountId,
263
+ maxChars: Math.min(ctx.textLimit, 4000),
264
+ resolveThreadTs: () => replyPlan.nextThreadTs(),
265
+ onMessageSent: () => replyPlan.markSent(),
266
+ log: logVerbose,
267
+ warn: logVerbose,
268
+ });
269
+ let hasStreamedMessage = false;
270
+ const streamMode = resolveSlackStreamMode(account.config.streamMode);
271
+ let appendRenderedText = "";
272
+ let appendSourceText = "";
273
+ let statusUpdateCount = 0;
274
+ const updateDraftFromPartial = (text) => {
275
+ const trimmed = text?.trimEnd();
276
+ if (!trimmed) {
277
+ return;
278
+ }
279
+ if (streamMode === "append") {
280
+ const next = applyAppendOnlyStreamUpdate({
281
+ incoming: trimmed,
282
+ rendered: appendRenderedText,
283
+ source: appendSourceText,
284
+ });
285
+ appendRenderedText = next.rendered;
286
+ appendSourceText = next.source;
287
+ if (!next.changed) {
288
+ return;
289
+ }
290
+ draftStream.update(next.rendered);
291
+ hasStreamedMessage = true;
292
+ return;
293
+ }
294
+ if (streamMode === "status_final") {
295
+ statusUpdateCount += 1;
296
+ if (statusUpdateCount > 1 && statusUpdateCount % 4 !== 0) {
297
+ return;
298
+ }
299
+ draftStream.update(buildStatusFinalPreviewText(statusUpdateCount));
300
+ hasStreamedMessage = true;
301
+ return;
302
+ }
303
+ draftStream.update(trimmed);
304
+ hasStreamedMessage = true;
305
+ };
114
306
  const { queuedFinal, counts } = await dispatchInboundMessage({
115
307
  ctx: prepared.ctxPayload,
116
308
  cfg,
@@ -119,17 +311,59 @@ export async function dispatchPreparedSlackMessage(prepared) {
119
311
  ...replyOptions,
120
312
  skillFilter: prepared.channelConfig?.skills,
121
313
  hasRepliedRef,
122
- disableBlockStreaming: typeof account.config.blockStreaming === "boolean"
123
- ? !account.config.blockStreaming
124
- : undefined,
125
- onModelSelected: (ctx) => {
126
- prefixContext.onModelSelected(ctx);
127
- },
314
+ disableBlockStreaming: useStreaming
315
+ ? false
316
+ : typeof account.config.blockStreaming === "boolean"
317
+ ? !account.config.blockStreaming
318
+ : undefined,
319
+ onModelSelected,
320
+ onPartialReply: useStreaming
321
+ ? undefined
322
+ : async (payload) => {
323
+ updateDraftFromPartial(payload.text);
324
+ },
325
+ onAssistantMessageStart: useStreaming
326
+ ? undefined
327
+ : async () => {
328
+ if (hasStreamedMessage) {
329
+ draftStream.forceNewMessage();
330
+ hasStreamedMessage = false;
331
+ appendRenderedText = "";
332
+ appendSourceText = "";
333
+ statusUpdateCount = 0;
334
+ }
335
+ },
336
+ onReasoningEnd: useStreaming
337
+ ? undefined
338
+ : async () => {
339
+ if (hasStreamedMessage) {
340
+ draftStream.forceNewMessage();
341
+ hasStreamedMessage = false;
342
+ appendRenderedText = "";
343
+ appendSourceText = "";
344
+ statusUpdateCount = 0;
345
+ }
346
+ },
128
347
  },
129
348
  });
349
+ await draftStream.flush();
350
+ draftStream.stop();
130
351
  markDispatchIdle();
352
+ // -----------------------------------------------------------------------
353
+ // Finalize the stream if one was started
354
+ // -----------------------------------------------------------------------
355
+ const finalStream = streamSession;
356
+ if (finalStream && !finalStream.stopped) {
357
+ try {
358
+ await stopSlackStream({ session: finalStream });
359
+ }
360
+ catch (err) {
361
+ runtime.error?.(danger(`slack-stream: failed to stop stream: ${String(err)}`));
362
+ }
363
+ }
131
364
  const anyReplyDelivered = queuedFinal || (counts.block ?? 0) > 0 || (counts.final ?? 0) > 0;
132
365
  if (!anyReplyDelivered) {
366
+ await draftStream.clear();
133
367
  if (prepared.isRoomish) {
134
368
  clearHistoryEntriesIfEnabled({
135
369
  historyMap: ctx.channelHistories,