@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
@@ -3,7 +3,7 @@ import { createInboundDebouncer, resolveInboundDebounceMs, } from "../../auto-re
3
3
  import { danger } from "../../globals.js";
4
4
  import { preflightDiscordMessage } from "./message-handler.preflight.js";
5
5
  import { processDiscordMessage } from "./message-handler.process.js";
6
- import { resolveDiscordMessageText } from "./message-utils.js";
6
+ import { resolveDiscordMessageChannelId, resolveDiscordMessageText } from "./message-utils.js";
7
7
  export function createDiscordMessageHandler(params) {
8
8
  const groupPolicy = params.discordConfig?.groupPolicy ?? "open";
9
9
  const ackReactionScope = params.cfg.messages?.ackReactionScope ?? "group-mentions";
@@ -13,28 +13,37 @@ export function createDiscordMessageHandler(params) {
13
13
  buildKey: (entry) => {
14
14
  const message = entry.data.message;
15
15
  const authorId = entry.data.author?.id;
16
- if (!message || !authorId)
16
+ if (!message || !authorId) {
17
17
  return null;
18
- const channelId = message.channelId;
19
- if (!channelId)
18
+ }
19
+ const channelId = resolveDiscordMessageChannelId({
20
+ message,
21
+ eventChannelId: entry.data.channel_id,
22
+ });
23
+ if (!channelId) {
20
24
  return null;
25
+ }
21
26
  return `discord:${params.accountId}:${channelId}:${authorId}`;
22
27
  },
23
28
  shouldDebounce: (entry) => {
24
29
  const message = entry.data.message;
25
- if (!message)
30
+ if (!message) {
26
31
  return false;
27
- if (message.attachments && message.attachments.length > 0)
32
+ }
33
+ if (message.attachments && message.attachments.length > 0) {
28
34
  return false;
35
+ }
29
36
  const baseText = resolveDiscordMessageText(message, { includeForwarded: false });
30
- if (!baseText.trim())
37
+ if (!baseText.trim()) {
31
38
  return false;
39
+ }
32
40
  return !hasControlCommand(baseText, params.cfg);
33
41
  },
34
42
  onFlush: async (entries) => {
35
43
  const last = entries.at(-1);
36
- if (!last)
44
+ if (!last) {
37
45
  return;
46
+ }
38
47
  if (entries.length === 1) {
39
48
  const ctx = await preflightDiscordMessage({
40
49
  ...params,
@@ -43,8 +52,9 @@ export function createDiscordMessageHandler(params) {
43
52
  data: last.data,
44
53
  client: last.client,
45
54
  });
46
- if (!ctx)
55
+ if (!ctx) {
47
56
  return;
57
+ }
48
58
  await processDiscordMessage(ctx);
49
59
  return;
50
60
  }
@@ -73,8 +83,9 @@ export function createDiscordMessageHandler(params) {
73
83
  data: syntheticData,
74
84
  client: last.client,
75
85
  });
76
- if (!ctx)
86
+ if (!ctx) {
77
87
  return;
88
+ }
78
89
  if (entries.length > 1) {
79
90
  const ids = entries.map((entry) => entry.data.message?.id).filter(Boolean);
80
91
  if (ids.length > 0) {
@@ -3,43 +3,78 @@ import { hasControlCommand } from "../../auto-reply/command-detection.js";
3
3
  import { shouldHandleTextCommands } from "../../auto-reply/commands-registry.js";
4
4
  import { recordPendingHistoryEntryIfEnabled, } from "../../auto-reply/reply/history.js";
5
5
  import { buildMentionRegexes, matchesMentionWithExplicit, } from "../../auto-reply/reply/mentions.js";
6
+ import { formatAllowlistMatchMeta } from "../../channels/allowlist-match.js";
7
+ import { resolveControlCommandGate } from "../../channels/command-gating.js";
8
+ import { logInboundDrop } from "../../channels/logging.js";
9
+ import { resolveMentionGatingWithBypass } from "../../channels/mention-gating.js";
10
+ import { loadConfig } from "../../config/config.js";
6
11
  import { logVerbose, shouldLogVerbose } from "../../globals.js";
7
12
  import { recordChannelActivity } from "../../infra/channel-activity.js";
8
13
  import { enqueueSystemEvent } from "../../infra/system-events.js";
14
+ import { logDebug } from "../../logger.js";
9
15
  import { getChildLogger } from "../../logging.js";
10
16
  import { buildPairingReply } from "../../pairing/pairing-messages.js";
11
17
  import { readChannelAllowFromStore, upsertChannelPairingRequest, } from "../../pairing/pairing-store.js";
12
18
  import { resolveAgentRoute } from "../../routing/resolve-route.js";
13
- import { resolveMentionGatingWithBypass } from "../../channels/mention-gating.js";
14
- import { formatAllowlistMatchMeta } from "../../channels/allowlist-match.js";
19
+ import { fetchPluralKitMessageInfo } from "../pluralkit.js";
15
20
  import { sendMessageDiscord } from "../send.js";
16
- import { resolveControlCommandGate } from "../../channels/command-gating.js";
17
- import { logInboundDrop } from "../../channels/logging.js";
18
- import { allowListMatches, isDiscordGroupAllowedByPolicy, normalizeDiscordAllowList, normalizeDiscordSlug, resolveDiscordAllowListMatch, resolveDiscordChannelConfigWithFallback, resolveDiscordGuildEntry, resolveDiscordShouldRequireMention, resolveDiscordUserAllowed, resolveGroupDmAllow, } from "./allow-list.js";
21
+ import { allowListMatches, isDiscordGroupAllowedByPolicy, normalizeDiscordAllowList, normalizeDiscordSlug, resolveDiscordAllowListMatch, resolveDiscordChannelConfigWithFallback, resolveDiscordGuildEntry, resolveDiscordMemberAccessState, resolveDiscordShouldRequireMention, resolveGroupDmAllow, } from "./allow-list.js";
19
22
  import { formatDiscordUserTag, resolveDiscordSystemLocation, resolveTimestampMs, } from "./format.js";
20
- import { resolveDiscordChannelInfo, resolveDiscordMessageText } from "./message-utils.js";
23
+ import { resolveDiscordChannelInfo, resolveDiscordMessageChannelId, resolveDiscordMessageText, } from "./message-utils.js";
24
+ import { resolveDiscordSenderIdentity, resolveDiscordWebhookId } from "./sender-identity.js";
21
25
  import { resolveDiscordSystemEvent } from "./system-events.js";
22
26
  import { resolveDiscordThreadChannel, resolveDiscordThreadParentInfo } from "./threading.js";
23
27
  export async function preflightDiscordMessage(params) {
24
28
  const logger = getChildLogger({ module: "discord-auto-reply" });
25
29
  const message = params.data.message;
26
30
  const author = params.data.author;
27
- if (!author)
31
+ if (!author) {
32
+ return null;
33
+ }
34
+ const messageChannelId = resolveDiscordMessageChannelId({
35
+ message,
36
+ eventChannelId: params.data.channel_id,
37
+ });
38
+ if (!messageChannelId) {
39
+ logVerbose(`discord: drop message ${message.id} (missing channel id)`);
28
40
  return null;
41
+ }
29
42
  const allowBots = params.discordConfig?.allowBots ?? false;
30
- if (author.bot) {
43
+ if (params.botUserId && author.id === params.botUserId) {
31
44
  // Always ignore own messages to prevent self-reply loops
32
- if (params.botUserId && author.id === params.botUserId)
33
- return null;
34
- if (!allowBots) {
45
+ return null;
46
+ }
47
+ const pluralkitConfig = params.discordConfig?.pluralkit;
48
+ const webhookId = resolveDiscordWebhookId(message);
49
+ const shouldCheckPluralKit = Boolean(pluralkitConfig?.enabled) && !webhookId;
50
+ let pluralkitInfo = null;
51
+ if (shouldCheckPluralKit) {
52
+ try {
53
+ pluralkitInfo = await fetchPluralKitMessageInfo({
54
+ messageId: message.id,
55
+ config: pluralkitConfig,
56
+ });
57
+ }
58
+ catch (err) {
59
+ logVerbose(`discord: pluralkit lookup failed for ${message.id}: ${String(err)}`);
60
+ }
61
+ }
62
+ const sender = resolveDiscordSenderIdentity({
63
+ author,
64
+ member: params.data.member,
65
+ pluralkitInfo,
66
+ });
67
+ if (author.bot) {
68
+ if (!allowBots && !sender.isPluralKit) {
35
69
  logVerbose("discord: drop bot message (allowBots=false)");
36
70
  return null;
37
71
  }
38
72
  }
39
73
  const isGuildMessage = Boolean(params.data.guild_id);
40
- const channelInfo = await resolveDiscordChannelInfo(params.client, message.channelId);
74
+ const channelInfo = await resolveDiscordChannelInfo(params.client, messageChannelId);
41
75
  const isDirectMessage = channelInfo?.type === ChannelType.DM;
42
76
  const isGroupDm = channelInfo?.type === ChannelType.GroupDM;
77
+ logDebug(`[discord-preflight] channelId=${messageChannelId} guild_id=${params.data.guild_id} channelType=${channelInfo?.type} isGuild=${isGuildMessage} isDM=${isDirectMessage} isGroupDm=${isGroupDm}`);
43
78
  if (isGroupDm && !params.groupDmEnabled) {
44
79
  logVerbose("discord: drop group dm (group dms disabled)");
45
80
  return null;
@@ -48,7 +83,7 @@ export async function preflightDiscordMessage(params) {
48
83
  logVerbose("discord: drop dm (dms disabled)");
49
84
  return null;
50
85
  }
51
- const dmPolicy = params.discordConfig?.dm?.policy ?? "pairing";
86
+ const dmPolicy = params.discordConfig?.dmPolicy ?? params.discordConfig?.dm?.policy ?? "pairing";
52
87
  let commandAuthorized = true;
53
88
  if (isDirectMessage) {
54
89
  if (dmPolicy === "disabled") {
@@ -58,14 +93,14 @@ export async function preflightDiscordMessage(params) {
58
93
  if (dmPolicy !== "open") {
59
94
  const storeAllowFrom = await readChannelAllowFromStore("discord").catch(() => []);
60
95
  const effectiveAllowFrom = [...(params.allowFrom ?? []), ...storeAllowFrom];
61
- const allowList = normalizeDiscordAllowList(effectiveAllowFrom, ["discord:", "user:"]);
96
+ const allowList = normalizeDiscordAllowList(effectiveAllowFrom, ["discord:", "user:", "pk:"]);
62
97
  const allowMatch = allowList
63
98
  ? resolveDiscordAllowListMatch({
64
99
  allowList,
65
100
  candidate: {
66
- id: author.id,
67
- name: author.username,
68
- tag: formatDiscordUserTag(author),
101
+ id: sender.id,
102
+ name: sender.name,
103
+ tag: sender.tag,
69
104
  },
70
105
  })
71
106
  : { allowed: false };
@@ -101,7 +136,7 @@ export async function preflightDiscordMessage(params) {
101
136
  }
102
137
  }
103
138
  else {
104
- logVerbose(`Blocked unauthorized discord sender ${author.id} (dmPolicy=${dmPolicy}, ${allowMatchMeta})`);
139
+ logVerbose(`Blocked unauthorized discord sender ${sender.id} (dmPolicy=${dmPolicy}, ${allowMatchMeta})`);
105
140
  }
106
141
  return null;
107
142
  }
@@ -120,15 +155,46 @@ export async function preflightDiscordMessage(params) {
120
155
  accountId: params.accountId,
121
156
  direction: "inbound",
122
157
  });
158
+ // Resolve thread parent early for binding inheritance
159
+ const channelName = channelInfo?.name ??
160
+ ((isGuildMessage || isGroupDm) && message.channel && "name" in message.channel
161
+ ? message.channel.name
162
+ : undefined);
163
+ const earlyThreadChannel = resolveDiscordThreadChannel({
164
+ isGuildMessage,
165
+ message,
166
+ channelInfo,
167
+ messageChannelId,
168
+ });
169
+ let earlyThreadParentId;
170
+ let earlyThreadParentName;
171
+ let earlyThreadParentType;
172
+ if (earlyThreadChannel) {
173
+ const parentInfo = await resolveDiscordThreadParentInfo({
174
+ client: params.client,
175
+ threadChannel: earlyThreadChannel,
176
+ channelInfo,
177
+ });
178
+ earlyThreadParentId = parentInfo.id;
179
+ earlyThreadParentName = parentInfo.name;
180
+ earlyThreadParentType = parentInfo.type;
181
+ }
182
+ // Fresh config for bindings lookup; other routing inputs are payload-derived.
183
+ const memberRoleIds = Array.isArray(params.data.rawMember?.roles)
184
+ ? params.data.rawMember.roles.map((roleId) => String(roleId))
185
+ : [];
123
186
  const route = resolveAgentRoute({
124
- cfg: params.cfg,
187
+ cfg: loadConfig(),
125
188
  channel: "discord",
126
189
  accountId: params.accountId,
127
190
  guildId: params.data.guild_id ?? undefined,
191
+ memberRoleIds,
128
192
  peer: {
129
- kind: isDirectMessage ? "dm" : isGroupDm ? "group" : "channel",
130
- id: isDirectMessage ? author.id : message.channelId,
193
+ kind: isDirectMessage ? "direct" : isGroupDm ? "group" : "channel",
194
+ id: isDirectMessage ? author.id : messageChannelId,
131
195
  },
196
+ // Pass parent peer for thread binding inheritance
197
+ parentPeer: earlyThreadParentId ? { kind: "channel", id: earlyThreadParentId } : undefined,
132
198
  });
133
199
  const mentionRegexes = buildMentionRegexes(params.cfg, route.agentId);
134
200
  const explicitlyMentioned = Boolean(botId && message.mentionedUsers?.some((user) => user.id === botId));
@@ -136,23 +202,6 @@ export async function preflightDiscordMessage(params) {
136
202
  (message.mentionedEveryone ||
137
203
  (message.mentionedUsers?.length ?? 0) > 0 ||
138
204
  (message.mentionedRoles?.length ?? 0) > 0));
139
- const wasMentioned = !isDirectMessage &&
140
- matchesMentionWithExplicit({
141
- text: baseText,
142
- mentionRegexes,
143
- explicit: {
144
- hasAnyMention,
145
- isExplicitlyMentioned: explicitlyMentioned,
146
- canResolveExplicit: Boolean(botId),
147
- },
148
- });
149
- const implicitMention = Boolean(!isDirectMessage &&
150
- botId &&
151
- message.referencedMessage?.author?.id &&
152
- message.referencedMessage.author.id === botId);
153
- if (shouldLogVerbose()) {
154
- logVerbose(`discord: inbound id=${message.id} guild=${message.guild?.id ?? "dm"} channel=${message.channelId} mention=${wasMentioned ? "yes" : "no"} type=${isDirectMessage ? "dm" : isGroupDm ? "group-dm" : "guild"} content=${messageText ? "yes" : "no"}`);
155
- }
156
205
  if (isGuildMessage &&
157
206
  (message.type === MessageType.ChatInputCommand ||
158
207
  message.type === MessageType.ContextMenuCommand)) {
@@ -165,35 +214,20 @@ export async function preflightDiscordMessage(params) {
165
214
  guildEntries: params.guildEntries,
166
215
  })
167
216
  : null;
217
+ logDebug(`[discord-preflight] guild_id=${params.data.guild_id} guild_obj=${!!params.data.guild} guild_obj_id=${params.data.guild?.id} guildInfo=${!!guildInfo} guildEntries=${params.guildEntries ? Object.keys(params.guildEntries).join(",") : "none"}`);
168
218
  if (isGuildMessage &&
169
219
  params.guildEntries &&
170
220
  Object.keys(params.guildEntries).length > 0 &&
171
221
  !guildInfo) {
222
+ logDebug(`[discord-preflight] guild blocked: guild_id=${params.data.guild_id} guildEntries keys=${Object.keys(params.guildEntries).join(",")}`);
172
223
  logVerbose(`Blocked discord guild ${params.data.guild_id ?? "unknown"} (not in discord.guilds)`);
173
224
  return null;
174
225
  }
175
- const channelName = channelInfo?.name ??
176
- ((isGuildMessage || isGroupDm) && message.channel && "name" in message.channel
177
- ? message.channel.name
178
- : undefined);
179
- const threadChannel = resolveDiscordThreadChannel({
180
- isGuildMessage,
181
- message,
182
- channelInfo,
183
- });
184
- let threadParentId;
185
- let threadParentName;
186
- let threadParentType;
187
- if (threadChannel) {
188
- const parentInfo = await resolveDiscordThreadParentInfo({
189
- client: params.client,
190
- threadChannel,
191
- channelInfo,
192
- });
193
- threadParentId = parentInfo.id;
194
- threadParentName = parentInfo.name;
195
- threadParentType = parentInfo.type;
196
- }
226
+ // Reuse early thread resolution from above (for binding inheritance)
227
+ const threadChannel = earlyThreadChannel;
228
+ const threadParentId = earlyThreadParentId;
229
+ const threadParentName = earlyThreadParentName;
230
+ const threadParentType = earlyThreadParentType;
197
231
  const threadName = threadChannel?.name;
198
232
  const configChannelName = threadParentName ?? channelName;
199
233
  const configChannelSlug = configChannelName ? normalizeDiscordSlug(configChannelName) : "";
@@ -207,7 +241,7 @@ export async function preflightDiscordMessage(params) {
207
241
  const channelConfig = isGuildMessage
208
242
  ? resolveDiscordChannelConfigWithFallback({
209
243
  guildInfo,
210
- channelId: message.channelId,
244
+ channelId: messageChannelId,
211
245
  channelName,
212
246
  channelSlug: threadChannelSlug,
213
247
  parentId: threadParentId ?? undefined,
@@ -217,19 +251,27 @@ export async function preflightDiscordMessage(params) {
217
251
  })
218
252
  : null;
219
253
  const channelMatchMeta = formatAllowlistMatchMeta(channelConfig);
254
+ if (shouldLogVerbose()) {
255
+ const channelConfigSummary = channelConfig
256
+ ? `allowed=${channelConfig.allowed} enabled=${channelConfig.enabled ?? "unset"} requireMention=${channelConfig.requireMention ?? "unset"} matchKey=${channelConfig.matchKey ?? "none"} matchSource=${channelConfig.matchSource ?? "none"} users=${channelConfig.users?.length ?? 0} roles=${channelConfig.roles?.length ?? 0} skills=${channelConfig.skills?.length ?? 0}`
257
+ : "none";
258
+ logDebug(`[discord-preflight] channelConfig=${channelConfigSummary} channelMatchMeta=${channelMatchMeta} channelId=${messageChannelId}`);
259
+ }
220
260
  if (isGuildMessage && channelConfig?.enabled === false) {
221
- logVerbose(`Blocked discord channel ${message.channelId} (channel disabled, ${channelMatchMeta})`);
261
+ logDebug(`[discord-preflight] drop: channel disabled`);
262
+ logVerbose(`Blocked discord channel ${messageChannelId} (channel disabled, ${channelMatchMeta})`);
222
263
  return null;
223
264
  }
224
265
  const groupDmAllowed = isGroupDm &&
225
266
  resolveGroupDmAllow({
226
267
  channels: params.groupDmChannels,
227
- channelId: message.channelId,
268
+ channelId: messageChannelId,
228
269
  channelName: displayChannelName,
229
270
  channelSlug: displayChannelSlug,
230
271
  });
231
- if (isGroupDm && !groupDmAllowed)
272
+ if (isGroupDm && !groupDmAllowed) {
232
273
  return null;
274
+ }
233
275
  const channelAllowlistConfigured = Boolean(guildInfo?.channels) && Object.keys(guildInfo?.channels ?? {}).length > 0;
234
276
  const channelAllowed = channelConfig?.allowed !== false;
235
277
  if (isGuildMessage &&
@@ -246,23 +288,25 @@ export async function preflightDiscordMessage(params) {
246
288
  logVerbose(`discord: drop guild message (groupPolicy: allowlist, no channel allowlist, ${channelMatchMeta})`);
247
289
  }
248
290
  else {
249
- logVerbose(`Blocked discord channel ${message.channelId} not in guild channel allowlist (groupPolicy: allowlist, ${channelMatchMeta})`);
291
+ logVerbose(`Blocked discord channel ${messageChannelId} not in guild channel allowlist (groupPolicy: allowlist, ${channelMatchMeta})`);
250
292
  }
251
293
  return null;
252
294
  }
253
295
  if (isGuildMessage && channelConfig?.allowed === false) {
254
- logVerbose(`Blocked discord channel ${message.channelId} not in guild channel allowlist (${channelMatchMeta})`);
296
+ logDebug(`[discord-preflight] drop: channelConfig.allowed===false`);
297
+ logVerbose(`Blocked discord channel ${messageChannelId} not in guild channel allowlist (${channelMatchMeta})`);
255
298
  return null;
256
299
  }
257
300
  if (isGuildMessage) {
258
- logVerbose(`discord: allow channel ${message.channelId} (${channelMatchMeta})`);
301
+ logDebug(`[discord-preflight] pass: channel allowed`);
302
+ logVerbose(`discord: allow channel ${messageChannelId} (${channelMatchMeta})`);
259
303
  }
260
304
  const textForHistory = resolveDiscordMessageText(message, {
261
305
  includeForwarded: true,
262
306
  });
263
307
  const historyEntry = isGuildMessage && params.historyLimit > 0 && textForHistory
264
308
  ? {
265
- sender: params.data.member?.nickname ?? author.globalName ?? author.username ?? author.id,
309
+ sender: sender.label,
266
310
  body: textForHistory,
267
311
  timestamp: resolveTimestampMs(message.timestamp),
268
312
  messageId: message.id,
@@ -277,27 +321,80 @@ export async function preflightDiscordMessage(params) {
277
321
  channelConfig,
278
322
  guildInfo,
279
323
  });
324
+ // Preflight audio transcription for mention detection in guilds
325
+ // This allows voice notes to be checked for mentions before being dropped
326
+ let preflightTranscript;
327
+ const hasAudioAttachment = message.attachments?.some((att) => att.contentType?.startsWith("audio/"));
328
+ const needsPreflightTranscription = !isDirectMessage &&
329
+ shouldRequireMention &&
330
+ hasAudioAttachment &&
331
+ !baseText &&
332
+ mentionRegexes.length > 0;
333
+ if (needsPreflightTranscription) {
334
+ try {
335
+ const { transcribeFirstAudio } = await import("../../media-understanding/audio-preflight.js");
336
+ const audioPaths = message.attachments
337
+ ?.filter((att) => att.contentType?.startsWith("audio/"))
338
+ .map((att) => att.url) ?? [];
339
+ if (audioPaths.length > 0) {
340
+ const tempCtx = {
341
+ MediaUrls: audioPaths,
342
+ MediaTypes: message.attachments
343
+ ?.filter((att) => att.contentType?.startsWith("audio/"))
344
+ .map((att) => att.contentType)
345
+ .filter(Boolean),
346
+ };
347
+ preflightTranscript = await transcribeFirstAudio({
348
+ ctx: tempCtx,
349
+ cfg: params.cfg,
350
+ agentDir: undefined,
351
+ });
352
+ }
353
+ }
354
+ catch (err) {
355
+ logVerbose(`discord: audio preflight transcription failed: ${String(err)}`);
356
+ }
357
+ }
358
+ const wasMentioned = !isDirectMessage &&
359
+ matchesMentionWithExplicit({
360
+ text: baseText,
361
+ mentionRegexes,
362
+ explicit: {
363
+ hasAnyMention,
364
+ isExplicitlyMentioned: explicitlyMentioned,
365
+ canResolveExplicit: Boolean(botId),
366
+ },
367
+ transcript: preflightTranscript,
368
+ });
369
+ const implicitMention = Boolean(!isDirectMessage &&
370
+ botId &&
371
+ message.referencedMessage?.author?.id &&
372
+ message.referencedMessage.author.id === botId);
373
+ if (shouldLogVerbose()) {
374
+ logVerbose(`discord: inbound id=${message.id} guild=${params.data.guild_id ?? "dm"} channel=${messageChannelId} mention=${wasMentioned ? "yes" : "no"} type=${isDirectMessage ? "dm" : isGroupDm ? "group-dm" : "guild"} content=${messageText ? "yes" : "no"}`);
375
+ }
280
376
  const allowTextCommands = shouldHandleTextCommands({
281
377
  cfg: params.cfg,
282
378
  surface: "discord",
283
379
  });
284
380
  const hasControlCommandInMessage = hasControlCommand(baseText, params.cfg);
381
+ const { hasAccessRestrictions, memberAllowed } = resolveDiscordMemberAccessState({
382
+ channelConfig,
383
+ guildInfo,
384
+ memberRoleIds,
385
+ sender,
386
+ });
285
387
  if (!isDirectMessage) {
286
- const ownerAllowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
388
+ const ownerAllowList = normalizeDiscordAllowList(params.allowFrom, [
389
+ "discord:",
390
+ "user:",
391
+ "pk:",
392
+ ]);
287
393
  const ownerOk = ownerAllowList
288
394
  ? allowListMatches(ownerAllowList, {
289
- id: author.id,
290
- name: author.username,
291
- tag: formatDiscordUserTag(author),
292
- })
293
- : false;
294
- const channelUsers = channelConfig?.users ?? guildInfo?.users;
295
- const usersOk = Array.isArray(channelUsers) && channelUsers.length > 0
296
- ? resolveDiscordUserAllowed({
297
- allowList: channelUsers,
298
- userId: author.id,
299
- userName: author.username,
300
- userTag: formatDiscordUserTag(author),
395
+ id: sender.id,
396
+ name: sender.name,
397
+ tag: sender.tag,
301
398
  })
302
399
  : false;
303
400
  const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
@@ -305,7 +402,7 @@ export async function preflightDiscordMessage(params) {
305
402
  useAccessGroups,
306
403
  authorizers: [
307
404
  { configured: ownerAllowList != null, allowed: ownerOk },
308
- { configured: Array.isArray(channelUsers) && channelUsers.length > 0, allowed: usersOk },
405
+ { configured: hasAccessRestrictions, allowed: memberAllowed },
309
406
  ],
310
407
  modeWhenAccessGroupsOff: "configured",
311
408
  allowTextCommands,
@@ -317,7 +414,7 @@ export async function preflightDiscordMessage(params) {
317
414
  log: logVerbose,
318
415
  channel: "discord",
319
416
  reason: "control command (unauthorized)",
320
- target: author.id,
417
+ target: sender.id,
321
418
  });
322
419
  return null;
323
420
  }
@@ -335,55 +432,50 @@ export async function preflightDiscordMessage(params) {
335
432
  commandAuthorized,
336
433
  });
337
434
  const effectiveWasMentioned = mentionGate.effectiveWasMentioned;
435
+ logDebug(`[discord-preflight] shouldRequireMention=${shouldRequireMention} mentionGate.shouldSkip=${mentionGate.shouldSkip} wasMentioned=${wasMentioned}`);
338
436
  if (isGuildMessage && shouldRequireMention) {
339
437
  if (botId && mentionGate.shouldSkip) {
438
+ logDebug(`[discord-preflight] drop: no-mention`);
340
439
  logVerbose(`discord: drop guild message (mention required, botId=${botId})`);
341
440
  logger.info({
342
- channelId: message.channelId,
441
+ channelId: messageChannelId,
343
442
  reason: "no-mention",
344
443
  }, "discord: skipping guild message");
345
444
  recordPendingHistoryEntryIfEnabled({
346
445
  historyMap: params.guildHistories,
347
- historyKey: message.channelId,
446
+ historyKey: messageChannelId,
348
447
  limit: params.historyLimit,
349
448
  entry: historyEntry ?? null,
350
449
  });
351
450
  return null;
352
451
  }
353
452
  }
354
- if (isGuildMessage) {
355
- const channelUsers = channelConfig?.users ?? guildInfo?.users;
356
- if (Array.isArray(channelUsers) && channelUsers.length > 0) {
357
- const userOk = resolveDiscordUserAllowed({
358
- allowList: channelUsers,
359
- userId: author.id,
360
- userName: author.username,
361
- userTag: formatDiscordUserTag(author),
362
- });
363
- if (!userOk) {
364
- logVerbose(`Blocked discord guild sender ${author.id} (not in channel users allowlist)`);
365
- return null;
366
- }
367
- }
453
+ if (isGuildMessage && hasAccessRestrictions && !memberAllowed) {
454
+ logDebug(`[discord-preflight] drop: member not allowed`);
455
+ logVerbose(`Blocked discord guild sender ${sender.id} (not in users/roles allowlist)`);
456
+ return null;
368
457
  }
369
458
  const systemLocation = resolveDiscordSystemLocation({
370
459
  isDirectMessage,
371
460
  isGroupDm,
372
461
  guild: params.data.guild ?? undefined,
373
- channelName: channelName ?? message.channelId,
462
+ channelName: channelName ?? messageChannelId,
374
463
  });
375
464
  const systemText = resolveDiscordSystemEvent(message, systemLocation);
376
465
  if (systemText) {
466
+ logDebug(`[discord-preflight] drop: system event`);
377
467
  enqueueSystemEvent(systemText, {
378
468
  sessionKey: route.sessionKey,
379
- contextKey: `discord:system:${message.channelId}:${message.id}`,
469
+ contextKey: `discord:system:${messageChannelId}:${message.id}`,
380
470
  });
381
471
  return null;
382
472
  }
383
473
  if (!messageText) {
474
+ logDebug(`[discord-preflight] drop: empty content`);
384
475
  logVerbose(`discord: drop message ${message.id} (empty content)`);
385
476
  return null;
386
477
  }
478
+ logDebug(`[discord-preflight] success: route=${route.agentId} sessionKey=${route.sessionKey}`);
387
479
  return {
388
480
  cfg: params.cfg,
389
481
  discordConfig: params.discordConfig,
@@ -401,7 +493,9 @@ export async function preflightDiscordMessage(params) {
401
493
  data: params.data,
402
494
  client: params.client,
403
495
  message,
496
+ messageChannelId,
404
497
  author,
498
+ sender,
405
499
  channelInfo,
406
500
  channelName,
407
501
  isGuildMessage,