@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,39 +1,256 @@
1
1
  import { ChannelType } from "@buape/carbon";
2
2
  import { resolveAckReaction, resolveHumanDelayConfig } from "../../agents/identity.js";
3
- import { removeAckReactionAfterReply, shouldAckReaction as shouldAckReactionGate, } from "../../channels/ack-reactions.js";
4
- import { logTypingFailure, logAckFailure } from "../../channels/logging.js";
5
- import { createReplyPrefixContext } from "../../channels/reply-prefix.js";
6
- import { createTypingCallbacks } from "../../channels/typing.js";
7
- import { formatInboundEnvelope, formatThreadStarterEnvelope, resolveEnvelopeFormatOptions, } from "../../auto-reply/envelope.js";
3
+ import { resolveChunkMode } from "../../auto-reply/chunk.js";
8
4
  import { dispatchInboundMessage } from "../../auto-reply/dispatch.js";
5
+ import { formatInboundEnvelope, resolveEnvelopeFormatOptions } from "../../auto-reply/envelope.js";
9
6
  import { buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, } from "../../auto-reply/reply/history.js";
10
7
  import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
11
8
  import { createReplyDispatcherWithTyping } from "../../auto-reply/reply/reply-dispatcher.js";
9
+ import { shouldAckReaction as shouldAckReactionGate } from "../../channels/ack-reactions.js";
10
+ import { logTypingFailure, logAckFailure } from "../../channels/logging.js";
11
+ import { createReplyPrefixOptions } from "../../channels/reply-prefix.js";
12
12
  import { recordInboundSession } from "../../channels/session.js";
13
- import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js";
14
- import { resolveChunkMode } from "../../auto-reply/chunk.js";
13
+ import { createTypingCallbacks } from "../../channels/typing.js";
15
14
  import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
15
+ import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js";
16
16
  import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
17
17
  import { buildAgentSessionKey } from "../../routing/resolve-route.js";
18
18
  import { resolveThreadSessionKeys } from "../../routing/session-key.js";
19
+ import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
19
20
  import { truncateUtf16Safe } from "../../utils.js";
20
21
  import { reactMessageDiscord, removeReactionDiscord } from "../send.js";
21
- import { normalizeDiscordSlug } from "./allow-list.js";
22
- import { formatDiscordUserTag, resolveTimestampMs } from "./format.js";
23
- import { buildDiscordMediaPayload, resolveDiscordMessageText, resolveMediaList, } from "./message-utils.js";
22
+ import { normalizeDiscordSlug, resolveDiscordOwnerAllowFrom } from "./allow-list.js";
23
+ import { resolveTimestampMs } from "./format.js";
24
+ import { buildDiscordMediaPayload, resolveDiscordMessageText, resolveForwardedMediaList, resolveMediaList, } from "./message-utils.js";
24
25
  import { buildDirectLabel, buildGuildLabel, resolveReplyContext } from "./reply-context.js";
25
26
  import { deliverDiscordReply } from "./reply-delivery.js";
26
27
  import { resolveDiscordAutoThreadReplyPlan, resolveDiscordThreadStarter } from "./threading.js";
27
28
  import { sendTyping } from "./typing.js";
29
+ const DISCORD_STATUS_THINKING_EMOJI = "🧠";
30
+ const DISCORD_STATUS_TOOL_EMOJI = "🛠️";
31
+ const DISCORD_STATUS_CODING_EMOJI = "💻";
32
+ const DISCORD_STATUS_WEB_EMOJI = "🌐";
33
+ const DISCORD_STATUS_DONE_EMOJI = "✅";
34
+ const DISCORD_STATUS_ERROR_EMOJI = "❌";
35
+ const DISCORD_STATUS_STALL_SOFT_EMOJI = "⏳";
36
+ const DISCORD_STATUS_STALL_HARD_EMOJI = "⚠️";
37
+ const DISCORD_STATUS_DONE_HOLD_MS = 1500;
38
+ const DISCORD_STATUS_ERROR_HOLD_MS = 2500;
39
+ const DISCORD_STATUS_DEBOUNCE_MS = 700;
40
+ const DISCORD_STATUS_STALL_SOFT_MS = 10_000;
41
+ const DISCORD_STATUS_STALL_HARD_MS = 30_000;
42
+ const CODING_STATUS_TOOL_TOKENS = [
43
+ "exec",
44
+ "process",
45
+ "read",
46
+ "write",
47
+ "edit",
48
+ "session_status",
49
+ "bash",
50
+ ];
51
+ const WEB_STATUS_TOOL_TOKENS = ["web_search", "web-search", "web_fetch", "web-fetch", "browser"];
52
+ function resolveToolStatusEmoji(toolName) {
53
+ const normalized = toolName?.trim().toLowerCase() ?? "";
54
+ if (!normalized) {
55
+ return DISCORD_STATUS_TOOL_EMOJI;
56
+ }
57
+ if (WEB_STATUS_TOOL_TOKENS.some((token) => normalized.includes(token))) {
58
+ return DISCORD_STATUS_WEB_EMOJI;
59
+ }
60
+ if (CODING_STATUS_TOOL_TOKENS.some((token) => normalized.includes(token))) {
61
+ return DISCORD_STATUS_CODING_EMOJI;
62
+ }
63
+ return DISCORD_STATUS_TOOL_EMOJI;
64
+ }
65
+ function sleep(ms) {
66
+ return new Promise((resolve) => {
67
+ setTimeout(resolve, ms);
68
+ });
69
+ }
70
+ function createDiscordStatusReactionController(params) {
71
+ let activeEmoji = null;
72
+ let chain = Promise.resolve();
73
+ let pendingEmoji = null;
74
+ let pendingTimer = null;
75
+ let finished = false;
76
+ let softStallTimer = null;
77
+ let hardStallTimer = null;
78
+ const enqueue = (work) => {
79
+ chain = chain.then(work).catch((err) => {
80
+ logAckFailure({
81
+ log: logVerbose,
82
+ channel: "discord",
83
+ target: `${params.channelId}/${params.messageId}`,
84
+ error: err,
85
+ });
86
+ });
87
+ return chain;
88
+ };
89
+ const clearStallTimers = () => {
90
+ if (softStallTimer) {
91
+ clearTimeout(softStallTimer);
92
+ softStallTimer = null;
93
+ }
94
+ if (hardStallTimer) {
95
+ clearTimeout(hardStallTimer);
96
+ hardStallTimer = null;
97
+ }
98
+ };
99
+ const clearPendingDebounce = () => {
100
+ if (pendingTimer) {
101
+ clearTimeout(pendingTimer);
102
+ pendingTimer = null;
103
+ }
104
+ pendingEmoji = null;
105
+ };
106
+ const applyEmoji = (emoji) => enqueue(async () => {
107
+ if (!params.enabled || !emoji || activeEmoji === emoji) {
108
+ return;
109
+ }
110
+ const previousEmoji = activeEmoji;
111
+ await reactMessageDiscord(params.channelId, params.messageId, emoji, {
112
+ rest: params.rest,
113
+ });
114
+ activeEmoji = emoji;
115
+ if (previousEmoji && previousEmoji !== emoji) {
116
+ await removeReactionDiscord(params.channelId, params.messageId, previousEmoji, {
117
+ rest: params.rest,
118
+ });
119
+ }
120
+ });
121
+ const requestEmoji = (emoji, options) => {
122
+ if (!params.enabled || !emoji) {
123
+ return Promise.resolve();
124
+ }
125
+ if (options?.immediate) {
126
+ clearPendingDebounce();
127
+ return applyEmoji(emoji);
128
+ }
129
+ pendingEmoji = emoji;
130
+ if (pendingTimer) {
131
+ clearTimeout(pendingTimer);
132
+ }
133
+ pendingTimer = setTimeout(() => {
134
+ pendingTimer = null;
135
+ const emojiToApply = pendingEmoji;
136
+ pendingEmoji = null;
137
+ if (!emojiToApply || emojiToApply === activeEmoji) {
138
+ return;
139
+ }
140
+ void applyEmoji(emojiToApply);
141
+ }, DISCORD_STATUS_DEBOUNCE_MS);
142
+ return Promise.resolve();
143
+ };
144
+ const scheduleStallTimers = () => {
145
+ if (!params.enabled || finished) {
146
+ return;
147
+ }
148
+ clearStallTimers();
149
+ softStallTimer = setTimeout(() => {
150
+ if (finished) {
151
+ return;
152
+ }
153
+ void requestEmoji(DISCORD_STATUS_STALL_SOFT_EMOJI, { immediate: true });
154
+ }, DISCORD_STATUS_STALL_SOFT_MS);
155
+ hardStallTimer = setTimeout(() => {
156
+ if (finished) {
157
+ return;
158
+ }
159
+ void requestEmoji(DISCORD_STATUS_STALL_HARD_EMOJI, { immediate: true });
160
+ }, DISCORD_STATUS_STALL_HARD_MS);
161
+ };
162
+ const setPhase = (emoji) => {
163
+ if (!params.enabled || finished) {
164
+ return Promise.resolve();
165
+ }
166
+ scheduleStallTimers();
167
+ return requestEmoji(emoji);
168
+ };
169
+ const setTerminal = async (emoji) => {
170
+ if (!params.enabled) {
171
+ return;
172
+ }
173
+ finished = true;
174
+ clearStallTimers();
175
+ await requestEmoji(emoji, { immediate: true });
176
+ };
177
+ const clear = async () => {
178
+ if (!params.enabled) {
179
+ return;
180
+ }
181
+ finished = true;
182
+ clearStallTimers();
183
+ clearPendingDebounce();
184
+ await enqueue(async () => {
185
+ const cleanupCandidates = new Set([
186
+ params.initialEmoji,
187
+ activeEmoji ?? "",
188
+ DISCORD_STATUS_THINKING_EMOJI,
189
+ DISCORD_STATUS_TOOL_EMOJI,
190
+ DISCORD_STATUS_CODING_EMOJI,
191
+ DISCORD_STATUS_WEB_EMOJI,
192
+ DISCORD_STATUS_DONE_EMOJI,
193
+ DISCORD_STATUS_ERROR_EMOJI,
194
+ DISCORD_STATUS_STALL_SOFT_EMOJI,
195
+ DISCORD_STATUS_STALL_HARD_EMOJI,
196
+ ]);
197
+ activeEmoji = null;
198
+ for (const emoji of cleanupCandidates) {
199
+ if (!emoji) {
200
+ continue;
201
+ }
202
+ try {
203
+ await removeReactionDiscord(params.channelId, params.messageId, emoji, {
204
+ rest: params.rest,
205
+ });
206
+ }
207
+ catch (err) {
208
+ logAckFailure({
209
+ log: logVerbose,
210
+ channel: "discord",
211
+ target: `${params.channelId}/${params.messageId}`,
212
+ error: err,
213
+ });
214
+ }
215
+ }
216
+ });
217
+ };
218
+ const restoreInitial = async () => {
219
+ if (!params.enabled) {
220
+ return;
221
+ }
222
+ finished = true;
223
+ clearStallTimers();
224
+ clearPendingDebounce();
225
+ await requestEmoji(params.initialEmoji, { immediate: true });
226
+ };
227
+ return {
228
+ setQueued: () => {
229
+ scheduleStallTimers();
230
+ return requestEmoji(params.initialEmoji, { immediate: true });
231
+ },
232
+ setThinking: () => setPhase(DISCORD_STATUS_THINKING_EMOJI),
233
+ setTool: (toolName) => setPhase(resolveToolStatusEmoji(toolName)),
234
+ setDone: () => setTerminal(DISCORD_STATUS_DONE_EMOJI),
235
+ setError: () => setTerminal(DISCORD_STATUS_ERROR_EMOJI),
236
+ clear,
237
+ restoreInitial,
238
+ };
239
+ }
28
240
  export async function processDiscordMessage(ctx) {
29
- const { cfg, discordConfig, accountId, token, runtime, guildHistories, historyLimit, mediaMaxBytes, textLimit, replyToMode, ackReactionScope, message, author, data, client, channelInfo, channelName, isGuildMessage, isDirectMessage, isGroupDm, baseText, messageText, shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, threadChannel, threadParentId, threadParentName, threadParentType, threadName, displayChannelSlug, guildInfo, guildSlug, channelConfig, baseSessionKey, route, commandAuthorized, } = ctx;
241
+ const { cfg, discordConfig, accountId, token, runtime, guildHistories, historyLimit, mediaMaxBytes, textLimit, replyToMode, ackReactionScope, message, author, sender, data, client, channelInfo, channelName, messageChannelId, isGuildMessage, isDirectMessage, isGroupDm, baseText, messageText, shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, threadChannel, threadParentId, threadParentName, threadParentType, threadName, displayChannelSlug, guildInfo, guildSlug, channelConfig, baseSessionKey, route, commandAuthorized, } = ctx;
30
242
  const mediaList = await resolveMediaList(message, mediaMaxBytes);
243
+ const forwardedMediaList = await resolveForwardedMediaList(message, mediaMaxBytes);
244
+ mediaList.push(...forwardedMediaList);
31
245
  const text = messageText;
32
246
  if (!text) {
33
247
  logVerbose(`discord: drop message ${message.id} (empty content)`);
34
248
  return;
35
249
  }
36
- const ackReaction = resolveAckReaction(cfg, route.agentId);
250
+ const ackReaction = resolveAckReaction(cfg, route.agentId, {
251
+ channel: "discord",
252
+ accountId,
253
+ });
37
254
  const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
38
255
  const shouldAckReaction = () => Boolean(ackReaction &&
39
256
  shouldAckReactionGate({
@@ -46,26 +263,25 @@ export async function processDiscordMessage(ctx) {
46
263
  effectiveWasMentioned,
47
264
  shouldBypassMention,
48
265
  }));
49
- const ackReactionPromise = shouldAckReaction()
50
- ? reactMessageDiscord(message.channelId, message.id, ackReaction, {
51
- rest: client.rest,
52
- }).then(() => true, (err) => {
53
- logVerbose(`discord react failed for channel ${message.channelId}: ${String(err)}`);
54
- return false;
55
- })
56
- : null;
266
+ const statusReactionsEnabled = shouldAckReaction();
267
+ const statusReactions = createDiscordStatusReactionController({
268
+ enabled: statusReactionsEnabled,
269
+ channelId: messageChannelId,
270
+ messageId: message.id,
271
+ initialEmoji: ackReaction,
272
+ rest: client.rest,
273
+ });
274
+ if (statusReactionsEnabled) {
275
+ void statusReactions.setQueued();
276
+ }
57
277
  const fromLabel = isDirectMessage
58
278
  ? buildDirectLabel(author)
59
279
  : buildGuildLabel({
60
280
  guild: data.guild ?? undefined,
61
- channelName: channelName ?? message.channelId,
62
- channelId: message.channelId,
281
+ channelName: channelName ?? messageChannelId,
282
+ channelId: messageChannelId,
63
283
  });
64
- const senderTag = formatDiscordUserTag(author);
65
- const senderDisplay = data.member?.nickname ?? author.globalName ?? author.username;
66
- const senderLabel = senderDisplay && senderTag && senderDisplay !== senderTag
67
- ? `${senderDisplay} (${senderTag})`
68
- : (senderDisplay ?? senderTag ?? author.id);
284
+ const senderLabel = sender.label;
69
285
  const isForumParent = threadParentType === ChannelType.GuildForum || threadParentType === ChannelType.GuildMedia;
70
286
  const forumParentSlug = isForumParent && threadParentName ? normalizeDiscordSlug(threadParentName) : "";
71
287
  const threadChannelId = threadChannel?.id;
@@ -73,12 +289,27 @@ export async function processDiscordMessage(ctx) {
73
289
  const forumContextLine = isForumStarter ? `[Forum parent: #${forumParentSlug}]` : null;
74
290
  const groupChannel = isGuildMessage && displayChannelSlug ? `#${displayChannelSlug}` : undefined;
75
291
  const groupSubject = isDirectMessage ? undefined : groupChannel;
76
- const channelDescription = channelInfo?.topic?.trim();
77
- const systemPromptParts = [
78
- channelDescription ? `Channel topic: ${channelDescription}` : null,
79
- channelConfig?.systemPrompt?.trim() || null,
80
- ].filter((entry) => Boolean(entry));
292
+ const untrustedChannelMetadata = isGuildMessage
293
+ ? buildUntrustedChannelMetadata({
294
+ source: "discord",
295
+ label: "Discord channel topic",
296
+ entries: [channelInfo?.topic],
297
+ })
298
+ : undefined;
299
+ const senderName = sender.isPluralKit
300
+ ? (sender.name ?? author.username)
301
+ : (data.member?.nickname ?? author.globalName ?? author.username);
302
+ const senderUsername = sender.isPluralKit
303
+ ? (sender.tag ?? sender.name ?? author.username)
304
+ : author.username;
305
+ const senderTag = sender.tag;
306
+ const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter((entry) => Boolean(entry));
81
307
  const groupSystemPrompt = systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
308
+ const ownerAllowFrom = resolveDiscordOwnerAllowFrom({
309
+ channelConfig,
310
+ guildInfo,
311
+ sender: { id: sender.id, name: sender.name, tag: sender.tag },
312
+ });
82
313
  const storePath = resolveStorePath(cfg.session?.store, {
83
314
  agentId: route.agentId,
84
315
  });
@@ -101,26 +332,21 @@ export async function processDiscordMessage(ctx) {
101
332
  if (shouldIncludeChannelHistory) {
102
333
  combinedBody = buildPendingHistoryContextFromMap({
103
334
  historyMap: guildHistories,
104
- historyKey: message.channelId,
335
+ historyKey: messageChannelId,
105
336
  limit: historyLimit,
106
337
  currentMessage: combinedBody,
107
338
  formatEntry: (entry) => formatInboundEnvelope({
108
339
  channel: "Discord",
109
340
  from: fromLabel,
110
341
  timestamp: entry.timestamp,
111
- body: `${entry.body} [id:${entry.messageId ?? "unknown"} channel:${message.channelId}]`,
342
+ body: `${entry.body} [id:${entry.messageId ?? "unknown"} channel:${messageChannelId}]`,
112
343
  chatType: "channel",
113
344
  senderLabel: entry.sender,
114
345
  envelope: envelopeOptions,
115
346
  }),
116
347
  });
117
348
  }
118
- const replyContext = resolveReplyContext(message, resolveDiscordMessageText, {
119
- envelope: envelopeOptions,
120
- });
121
- if (replyContext) {
122
- combinedBody = `[Replied message - for context]\n${replyContext}\n\n${combinedBody}`;
123
- }
349
+ const replyContext = resolveReplyContext(message, resolveDiscordMessageText);
124
350
  if (forumContextLine) {
125
351
  combinedBody = `${combinedBody}\n${forumContextLine}`;
126
352
  }
@@ -128,22 +354,19 @@ export async function processDiscordMessage(ctx) {
128
354
  let threadLabel;
129
355
  let parentSessionKey;
130
356
  if (threadChannel) {
131
- const starter = await resolveDiscordThreadStarter({
132
- channel: threadChannel,
133
- client,
134
- parentId: threadParentId,
135
- parentType: threadParentType,
136
- resolveTimestampMs,
137
- });
138
- if (starter?.text) {
139
- const starterEnvelope = formatThreadStarterEnvelope({
140
- channel: "Discord",
141
- author: starter.author,
142
- timestamp: starter.timestamp,
143
- body: starter.text,
144
- envelope: envelopeOptions,
357
+ const includeThreadStarter = channelConfig?.includeThreadStarter !== false;
358
+ if (includeThreadStarter) {
359
+ const starter = await resolveDiscordThreadStarter({
360
+ channel: threadChannel,
361
+ client,
362
+ parentId: threadParentId,
363
+ parentType: threadParentType,
364
+ resolveTimestampMs,
145
365
  });
146
- threadStarterBody = starterEnvelope;
366
+ if (starter?.text) {
367
+ // Keep thread starter as raw text; metadata is provided out-of-band in the system prompt.
368
+ threadStarterBody = starter.text;
369
+ }
147
370
  }
148
371
  const parentName = threadParentName ?? "parent";
149
372
  threadLabel = threadName
@@ -160,16 +383,18 @@ export async function processDiscordMessage(ctx) {
160
383
  const mediaPayload = buildDiscordMediaPayload(mediaList);
161
384
  const threadKeys = resolveThreadSessionKeys({
162
385
  baseSessionKey,
163
- threadId: threadChannel ? message.channelId : undefined,
386
+ threadId: threadChannel ? messageChannelId : undefined,
164
387
  parentSessionKey,
165
388
  useSuffix: false,
166
389
  });
167
390
  const replyPlan = await resolveDiscordAutoThreadReplyPlan({
168
391
  client,
169
392
  message,
393
+ messageChannelId,
170
394
  isGuildMessage,
171
395
  channelConfig,
172
396
  threadChannel,
397
+ channelType: channelInfo?.type,
173
398
  baseText: baseText ?? "",
174
399
  combinedBody,
175
400
  replyToMode,
@@ -182,14 +407,25 @@ export async function processDiscordMessage(ctx) {
182
407
  const autoThreadContext = replyPlan.autoThreadContext;
183
408
  const effectiveFrom = isDirectMessage
184
409
  ? `discord:${author.id}`
185
- : (autoThreadContext?.From ?? `discord:channel:${message.channelId}`);
410
+ : (autoThreadContext?.From ?? `discord:channel:${messageChannelId}`);
186
411
  const effectiveTo = autoThreadContext?.To ?? replyTarget;
187
412
  if (!effectiveTo) {
188
413
  runtime.error?.(danger("discord: missing reply target"));
189
414
  return;
190
415
  }
416
+ // Keep DM routes user-addressed so follow-up sends resolve direct session keys.
417
+ const lastRouteTo = isDirectMessage ? `user:${author.id}` : effectiveTo;
418
+ const inboundHistory = shouldIncludeChannelHistory && historyLimit > 0
419
+ ? (guildHistories.get(messageChannelId) ?? []).map((entry) => ({
420
+ sender: entry.sender,
421
+ body: entry.body,
422
+ timestamp: entry.timestamp,
423
+ }))
424
+ : undefined;
191
425
  const ctxPayload = finalizeInboundContext({
192
426
  Body: combinedBody,
427
+ BodyForAgent: baseText ?? text,
428
+ InboundHistory: inboundHistory,
193
429
  RawBody: baseText,
194
430
  CommandBody: baseText,
195
431
  From: effectiveFrom,
@@ -198,18 +434,23 @@ export async function processDiscordMessage(ctx) {
198
434
  AccountId: route.accountId,
199
435
  ChatType: isDirectMessage ? "direct" : "channel",
200
436
  ConversationLabel: fromLabel,
201
- SenderName: data.member?.nickname ?? author.globalName ?? author.username,
202
- SenderId: author.id,
203
- SenderUsername: author.username,
204
- SenderTag: formatDiscordUserTag(author),
437
+ SenderName: senderName,
438
+ SenderId: sender.id,
439
+ SenderUsername: senderUsername,
440
+ SenderTag: senderTag,
205
441
  GroupSubject: groupSubject,
206
442
  GroupChannel: groupChannel,
443
+ UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined,
207
444
  GroupSystemPrompt: isGuildMessage ? groupSystemPrompt : undefined,
208
445
  GroupSpace: isGuildMessage ? (guildInfo?.id ?? guildSlug) || undefined : undefined,
446
+ OwnerAllowFrom: ownerAllowFrom,
209
447
  Provider: "discord",
210
448
  Surface: "discord",
211
449
  WasMentioned: effectiveWasMentioned,
212
450
  MessageSid: message.id,
451
+ ReplyToId: replyContext?.id,
452
+ ReplyToBody: replyContext?.body,
453
+ ReplyToSender: replyContext?.sender,
213
454
  ParentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
214
455
  ThreadStarterBody: threadStarterBody,
215
456
  ThreadLabel: threadLabel,
@@ -221,38 +462,52 @@ export async function processDiscordMessage(ctx) {
221
462
  OriginatingChannel: "discord",
222
463
  OriginatingTo: autoThreadContext?.OriginatingTo ?? replyTarget,
223
464
  });
465
+ const persistedSessionKey = ctxPayload.SessionKey ?? route.sessionKey;
224
466
  await recordInboundSession({
225
467
  storePath,
226
- sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
468
+ sessionKey: persistedSessionKey,
227
469
  ctx: ctxPayload,
228
- updateLastRoute: isDirectMessage
229
- ? {
230
- sessionKey: route.mainSessionKey,
231
- channel: "discord",
232
- to: `user:${author.id}`,
233
- accountId: route.accountId,
234
- }
235
- : undefined,
470
+ updateLastRoute: {
471
+ sessionKey: persistedSessionKey,
472
+ channel: "discord",
473
+ to: lastRouteTo,
474
+ accountId: route.accountId,
475
+ },
236
476
  onRecordError: (err) => {
237
477
  logVerbose(`discord: failed updating session meta: ${String(err)}`);
238
478
  },
239
479
  });
240
480
  if (shouldLogVerbose()) {
241
481
  const preview = truncateUtf16Safe(combinedBody, 200).replace(/\n/g, "\\n");
242
- logVerbose(`discord inbound: channel=${message.channelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`);
482
+ logVerbose(`discord inbound: channel=${messageChannelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`);
243
483
  }
244
484
  const typingChannelId = deliverTarget.startsWith("channel:")
245
485
  ? deliverTarget.slice("channel:".length)
246
- : message.channelId;
247
- const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
486
+ : messageChannelId;
487
+ const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
488
+ cfg,
489
+ agentId: route.agentId,
490
+ channel: "discord",
491
+ accountId: route.accountId,
492
+ });
248
493
  const tableMode = resolveMarkdownTableMode({
249
494
  cfg,
250
495
  channel: "discord",
251
496
  accountId,
252
497
  });
498
+ const typingCallbacks = createTypingCallbacks({
499
+ start: () => sendTyping({ client, channelId: typingChannelId }),
500
+ onStartError: (err) => {
501
+ logTypingFailure({
502
+ log: logVerbose,
503
+ channel: "discord",
504
+ target: typingChannelId,
505
+ error: err,
506
+ });
507
+ },
508
+ });
253
509
  const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
254
- responsePrefix: prefixContext.responsePrefix,
255
- responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
510
+ ...prefixOptions,
256
511
  humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
257
512
  deliver: async (payload) => {
258
513
  const replyToId = replyReference.use();
@@ -274,70 +529,76 @@ export async function processDiscordMessage(ctx) {
274
529
  onError: (err, info) => {
275
530
  runtime.error?.(danger(`discord ${info.kind} reply failed: ${String(err)}`));
276
531
  },
277
- onReplyStart: createTypingCallbacks({
278
- start: () => sendTyping({ client, channelId: typingChannelId }),
279
- onStartError: (err) => {
280
- logTypingFailure({
281
- log: logVerbose,
282
- channel: "discord",
283
- target: typingChannelId,
284
- error: err,
285
- });
286
- },
287
- }).onReplyStart,
288
- });
289
- const { queuedFinal, counts } = await dispatchInboundMessage({
290
- ctx: ctxPayload,
291
- cfg,
292
- dispatcher,
293
- replyOptions: {
294
- ...replyOptions,
295
- skillFilter: channelConfig?.skills,
296
- disableBlockStreaming: typeof discordConfig?.blockStreaming === "boolean"
297
- ? !discordConfig.blockStreaming
298
- : undefined,
299
- onModelSelected: (ctx) => {
300
- prefixContext.onModelSelected(ctx);
301
- },
532
+ onReplyStart: async () => {
533
+ await typingCallbacks.onReplyStart();
534
+ await statusReactions.setThinking();
302
535
  },
303
536
  });
304
- markDispatchIdle();
305
- if (!queuedFinal) {
537
+ let dispatchResult = null;
538
+ let dispatchError = false;
539
+ try {
540
+ dispatchResult = await dispatchInboundMessage({
541
+ ctx: ctxPayload,
542
+ cfg,
543
+ dispatcher,
544
+ replyOptions: {
545
+ ...replyOptions,
546
+ skillFilter: channelConfig?.skills,
547
+ disableBlockStreaming: typeof discordConfig?.blockStreaming === "boolean"
548
+ ? !discordConfig.blockStreaming
549
+ : undefined,
550
+ onModelSelected,
551
+ onReasoningStream: async () => {
552
+ await statusReactions.setThinking();
553
+ },
554
+ onToolStart: async (payload) => {
555
+ await statusReactions.setTool(payload.name);
556
+ },
557
+ },
558
+ });
559
+ }
560
+ catch (err) {
561
+ dispatchError = true;
562
+ throw err;
563
+ }
564
+ finally {
565
+ markDispatchIdle();
566
+ if (statusReactionsEnabled) {
567
+ if (dispatchError) {
568
+ await statusReactions.setError();
569
+ }
570
+ else {
571
+ await statusReactions.setDone();
572
+ }
573
+ if (removeAckAfterReply) {
574
+ void (async () => {
575
+ await sleep(dispatchError ? DISCORD_STATUS_ERROR_HOLD_MS : DISCORD_STATUS_DONE_HOLD_MS);
576
+ await statusReactions.clear();
577
+ })();
578
+ }
579
+ else {
580
+ void statusReactions.restoreInitial();
581
+ }
582
+ }
583
+ }
584
+ if (!dispatchResult?.queuedFinal) {
306
585
  if (isGuildMessage) {
307
586
  clearHistoryEntriesIfEnabled({
308
587
  historyMap: guildHistories,
309
- historyKey: message.channelId,
588
+ historyKey: messageChannelId,
310
589
  limit: historyLimit,
311
590
  });
312
591
  }
313
592
  return;
314
593
  }
315
594
  if (shouldLogVerbose()) {
316
- const finalCount = counts.final;
595
+ const finalCount = dispatchResult.counts.final;
317
596
  logVerbose(`discord: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`);
318
597
  }
319
- removeAckReactionAfterReply({
320
- removeAfterReply: removeAckAfterReply,
321
- ackReactionPromise,
322
- ackReactionValue: ackReaction,
323
- remove: async () => {
324
- await removeReactionDiscord(message.channelId, message.id, ackReaction, {
325
- rest: client.rest,
326
- });
327
- },
328
- onError: (err) => {
329
- logAckFailure({
330
- log: logVerbose,
331
- channel: "discord",
332
- target: `${message.channelId}/${message.id}`,
333
- error: err,
334
- });
335
- },
336
- });
337
598
  if (isGuildMessage) {
338
599
  clearHistoryEntriesIfEnabled({
339
600
  historyMap: guildHistories,
340
- historyKey: message.channelId,
601
+ historyKey: messageChannelId,
341
602
  limit: historyLimit,
342
603
  });
343
604
  }