@poolzin/pool-bot 2026.2.21 → 2026.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -1,17 +1,17 @@
1
1
  import { inspect } from "node:util";
2
- import { Client } from "@buape/carbon";
3
- import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway";
2
+ import { Client, ReadyListener, } from "@buape/carbon";
4
3
  import { Routes } from "discord-api-types/v10";
5
4
  import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
6
5
  import { listNativeCommandSpecsForConfig } from "../../auto-reply/commands-registry.js";
7
6
  import { listSkillCommandsForAgents } from "../../auto-reply/skill-commands.js";
8
- import { mergeAllowlist, summarizeMapping } from "../../channels/allowlists/resolve-utils.js";
7
+ import { addAllowlistUserEntriesFromConfigEntry, buildAllowlistResolutionSummary, mergeAllowlist, resolveAllowlistIdAdditions, patchAllowlistUsersInConfigEntries, summarizeMapping, } from "../../channels/allowlists/resolve-utils.js";
9
8
  import { isNativeCommandsExplicitlyDisabled, resolveNativeCommandsEnabled, resolveNativeSkillsEnabled, } from "../../config/commands.js";
10
9
  import { loadConfig } from "../../config/config.js";
11
10
  import { danger, logVerbose, shouldLogVerbose, warn } from "../../globals.js";
12
11
  import { formatErrorMessage } from "../../infra/errors.js";
13
12
  import { createDiscordRetryRunner } from "../../infra/retry-policy.js";
14
13
  import { createSubsystemLogger } from "../../logging/subsystem.js";
14
+ import { createNonExitingRuntime } from "../../runtime.js";
15
15
  import { resolveDiscordAccount } from "../accounts.js";
16
16
  import { attachDiscordGatewayLogging } from "../gateway-logging.js";
17
17
  import { getDiscordGatewayEmitter, waitForDiscordGatewayStop } from "../monitor.gateway.js";
@@ -19,28 +19,53 @@ import { fetchDiscordApplicationId } from "../probe.js";
19
19
  import { resolveDiscordChannelAllowlist } from "../resolve-channels.js";
20
20
  import { resolveDiscordUserAllowlist } from "../resolve-users.js";
21
21
  import { normalizeDiscordToken } from "../token.js";
22
+ import { createAgentComponentButton, createAgentSelectMenu, createDiscordComponentButton, createDiscordComponentChannelSelect, createDiscordComponentMentionableSelect, createDiscordComponentModal, createDiscordComponentRoleSelect, createDiscordComponentStringSelect, createDiscordComponentUserSelect, } from "./agent-components.js";
23
+ import { createExecApprovalButton, DiscordExecApprovalHandler } from "./exec-approvals.js";
24
+ import { createDiscordGatewayPlugin } from "./gateway-plugin.js";
25
+ import { registerGateway, unregisterGateway } from "./gateway-registry.js";
22
26
  import { DiscordMessageListener, DiscordPresenceListener, DiscordReactionListener, DiscordReactionRemoveListener, registerDiscordListener, } from "./listeners.js";
23
27
  import { createDiscordMessageHandler } from "./message-handler.js";
24
28
  import { createDiscordCommandArgFallbackButton, createDiscordNativeCommand, } from "./native-command.js";
25
- import { createExecApprovalButton, DiscordExecApprovalHandler } from "./exec-approvals.js";
29
+ import { resolveDiscordPresenceUpdate } from "./presence.js";
30
+ import { resolveDiscordRestFetch } from "./rest-fetch.js";
26
31
  function summarizeAllowList(list) {
27
- if (!list || list.length === 0)
32
+ if (!list || list.length === 0) {
28
33
  return "any";
34
+ }
29
35
  const sample = list.slice(0, 4).map((entry) => String(entry));
30
36
  const suffix = list.length > sample.length ? ` (+${list.length - sample.length})` : "";
31
37
  return `${sample.join(", ")}${suffix}`;
32
38
  }
33
39
  function summarizeGuilds(entries) {
34
- if (!entries || Object.keys(entries).length === 0)
40
+ if (!entries || Object.keys(entries).length === 0) {
35
41
  return "any";
42
+ }
36
43
  const keys = Object.keys(entries);
37
44
  const sample = keys.slice(0, 4);
38
45
  const suffix = keys.length > sample.length ? ` (+${keys.length - sample.length})` : "";
39
46
  return `${sample.join(", ")}${suffix}`;
40
47
  }
48
+ function dedupeSkillCommandsForDiscord(skillCommands) {
49
+ const seen = new Set();
50
+ const deduped = [];
51
+ for (const command of skillCommands) {
52
+ const key = command.skillName.trim().toLowerCase();
53
+ if (!key) {
54
+ deduped.push(command);
55
+ continue;
56
+ }
57
+ if (seen.has(key)) {
58
+ continue;
59
+ }
60
+ seen.add(key);
61
+ deduped.push(command);
62
+ }
63
+ return deduped;
64
+ }
41
65
  async function deployDiscordCommands(params) {
42
- if (!params.enabled)
66
+ if (!params.enabled) {
43
67
  return;
68
+ }
44
69
  const runWithRetry = createDiscordRetryRunner({ verbose: shouldLogVerbose() });
45
70
  try {
46
71
  await runWithRetry(() => params.client.handleDeployRequest(), "command deploy");
@@ -51,14 +76,16 @@ async function deployDiscordCommands(params) {
51
76
  }
52
77
  }
53
78
  function formatDiscordDeployErrorDetails(err) {
54
- if (!err || typeof err !== "object")
79
+ if (!err || typeof err !== "object") {
55
80
  return "";
81
+ }
56
82
  const status = err.status;
57
83
  const discordCode = err.discordCode;
58
84
  const rawBody = err.rawBody;
59
85
  const details = [];
60
- if (typeof status === "number")
86
+ if (typeof status === "number") {
61
87
  details.push(`status=${status}`);
88
+ }
62
89
  if (typeof discordCode === "number" || typeof discordCode === "string") {
63
90
  details.push(`code=${discordCode}`);
64
91
  }
@@ -79,21 +106,6 @@ function formatDiscordDeployErrorDetails(err) {
79
106
  }
80
107
  return details.length > 0 ? ` (${details.join(", ")})` : "";
81
108
  }
82
- function resolveDiscordGatewayIntents(intentsConfig) {
83
- let intents = GatewayIntents.Guilds |
84
- GatewayIntents.GuildMessages |
85
- GatewayIntents.MessageContent |
86
- GatewayIntents.DirectMessages |
87
- GatewayIntents.GuildMessageReactions |
88
- GatewayIntents.DirectMessageReactions;
89
- if (intentsConfig?.presence) {
90
- intents |= GatewayIntents.GuildPresences;
91
- }
92
- if (intentsConfig?.guildMembers) {
93
- intents |= GatewayIntents.GuildMembers;
94
- }
95
- return intents;
96
- }
97
109
  export async function monitorDiscordProvider(opts = {}) {
98
110
  const cfg = opts.config ?? loadConfig();
99
111
  const account = resolveDiscordAccount({
@@ -104,14 +116,9 @@ export async function monitorDiscordProvider(opts = {}) {
104
116
  if (!token) {
105
117
  throw new Error(`Discord bot token missing for account "${account.accountId}" (set discord.accounts.${account.accountId}.token or DISCORD_BOT_TOKEN for default).`);
106
118
  }
107
- const runtime = opts.runtime ?? {
108
- log: console.log,
109
- error: console.error,
110
- exit: (code) => {
111
- throw new Error(`exit ${code}`);
112
- },
113
- };
119
+ const runtime = opts.runtime ?? createNonExitingRuntime();
114
120
  const discordCfg = account.config;
121
+ const discordRestFetch = resolveDiscordRestFetch(discordCfg.proxy, runtime);
115
122
  const dmConfig = discordCfg.dm;
116
123
  let guildEntries = discordCfg.guilds;
117
124
  const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
@@ -122,7 +129,7 @@ export async function monitorDiscordProvider(opts = {}) {
122
129
  groupPolicy === "open") {
123
130
  runtime.log?.(warn('discord: groupPolicy defaults to "open" when channels.discord is missing; set channels.discord.groupPolicy (or channels.defaults.groupPolicy) or add channels.discord.guilds to restrict access.'));
124
131
  }
125
- let allowFrom = dmConfig?.allowFrom;
132
+ let allowFrom = discordCfg.allowFrom ?? dmConfig?.allowFrom;
126
133
  const mediaMaxBytes = (opts.mediaMaxMb ?? discordCfg.mediaMaxMb ?? 8) * 1024 * 1024;
127
134
  const textLimit = resolveTextChunkLimit(cfg, "discord", account.accountId, {
128
135
  fallbackLimit: 2000,
@@ -130,7 +137,7 @@ export async function monitorDiscordProvider(opts = {}) {
130
137
  const historyLimit = Math.max(0, opts.historyLimit ?? discordCfg.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? 20);
131
138
  const replyToMode = opts.replyToMode ?? discordCfg.replyToMode ?? "off";
132
139
  const dmEnabled = dmConfig?.enabled ?? true;
133
- const dmPolicy = dmConfig?.policy ?? "pairing";
140
+ const dmPolicy = discordCfg.dmPolicy ?? dmConfig?.policy ?? "pairing";
134
141
  const groupDmEnabled = dmConfig?.groupEnabled ?? false;
135
142
  const groupDmChannels = dmConfig?.groupChannels;
136
143
  const nativeEnabled = resolveNativeCommandsEnabled({
@@ -155,12 +162,14 @@ export async function monitorDiscordProvider(opts = {}) {
155
162
  try {
156
163
  const entries = [];
157
164
  for (const [guildKey, guildCfg] of Object.entries(guildEntries)) {
158
- if (guildKey === "*")
165
+ if (guildKey === "*") {
159
166
  continue;
167
+ }
160
168
  const channels = guildCfg?.channels ?? {};
161
169
  const channelKeys = Object.keys(channels).filter((key) => key !== "*");
162
170
  if (channelKeys.length === 0) {
163
- entries.push({ input: guildKey, guildKey });
171
+ const input = /^\d+$/.test(guildKey) ? `guild:${guildKey}` : guildKey;
172
+ entries.push({ input, guildKey });
164
173
  continue;
165
174
  }
166
175
  for (const channelKey of channelKeys) {
@@ -175,14 +184,16 @@ export async function monitorDiscordProvider(opts = {}) {
175
184
  const resolved = await resolveDiscordChannelAllowlist({
176
185
  token,
177
186
  entries: entries.map((entry) => entry.input),
187
+ fetcher: discordRestFetch,
178
188
  });
179
189
  const nextGuilds = { ...guildEntries };
180
190
  const mapping = [];
181
191
  const unresolved = [];
182
192
  for (const entry of resolved) {
183
193
  const source = entries.find((item) => item.input === entry.input);
184
- if (!source)
194
+ if (!source) {
185
195
  continue;
196
+ }
186
197
  const sourceGuild = guildEntries?.[source.guildKey] ?? {};
187
198
  if (!entry.resolved || !entry.guildId) {
188
199
  unresolved.push(entry.input);
@@ -225,19 +236,9 @@ export async function monitorDiscordProvider(opts = {}) {
225
236
  const resolvedUsers = await resolveDiscordUserAllowlist({
226
237
  token,
227
238
  entries: allowEntries.map((entry) => String(entry)),
239
+ fetcher: discordRestFetch,
228
240
  });
229
- const mapping = [];
230
- const unresolved = [];
231
- const additions = [];
232
- for (const entry of resolvedUsers) {
233
- if (entry.resolved && entry.id) {
234
- mapping.push(`${entry.input}→${entry.id}`);
235
- additions.push(entry.id);
236
- }
237
- else {
238
- unresolved.push(entry.input);
239
- }
240
- }
241
+ const { mapping, unresolved, additions } = buildAllowlistResolutionSummary(resolvedUsers);
241
242
  allowFrom = mergeAllowlist({ existing: allowFrom, additions });
242
243
  summarizeMapping("discord users", mapping, unresolved, runtime);
243
244
  }
@@ -248,28 +249,13 @@ export async function monitorDiscordProvider(opts = {}) {
248
249
  if (guildEntries && Object.keys(guildEntries).length > 0) {
249
250
  const userEntries = new Set();
250
251
  for (const guild of Object.values(guildEntries)) {
251
- if (!guild || typeof guild !== "object")
252
+ if (!guild || typeof guild !== "object") {
252
253
  continue;
253
- const users = guild.users;
254
- if (Array.isArray(users)) {
255
- for (const entry of users) {
256
- const trimmed = String(entry).trim();
257
- if (trimmed && trimmed !== "*")
258
- userEntries.add(trimmed);
259
- }
260
254
  }
255
+ addAllowlistUserEntriesFromConfigEntry(userEntries, guild);
261
256
  const channels = guild.channels ?? {};
262
257
  for (const channel of Object.values(channels)) {
263
- if (!channel || typeof channel !== "object")
264
- continue;
265
- const channelUsers = channel.users;
266
- if (!Array.isArray(channelUsers))
267
- continue;
268
- for (const entry of channelUsers) {
269
- const trimmed = String(entry).trim();
270
- if (trimmed && trimmed !== "*")
271
- userEntries.add(trimmed);
272
- }
258
+ addAllowlistUserEntriesFromConfigEntry(userEntries, channel);
273
259
  }
274
260
  }
275
261
  if (userEntries.size > 0) {
@@ -277,52 +263,26 @@ export async function monitorDiscordProvider(opts = {}) {
277
263
  const resolvedUsers = await resolveDiscordUserAllowlist({
278
264
  token,
279
265
  entries: Array.from(userEntries),
266
+ fetcher: discordRestFetch,
280
267
  });
281
- const resolvedMap = new Map(resolvedUsers.map((entry) => [entry.input, entry]));
282
- const mapping = resolvedUsers
283
- .filter((entry) => entry.resolved && entry.id)
284
- .map((entry) => `${entry.input}→${entry.id}`);
285
- const unresolved = resolvedUsers
286
- .filter((entry) => !entry.resolved)
287
- .map((entry) => entry.input);
268
+ const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers);
288
269
  const nextGuilds = { ...guildEntries };
289
270
  for (const [guildKey, guildConfig] of Object.entries(guildEntries ?? {})) {
290
- if (!guildConfig || typeof guildConfig !== "object")
271
+ if (!guildConfig || typeof guildConfig !== "object") {
291
272
  continue;
273
+ }
292
274
  const nextGuild = { ...guildConfig };
293
275
  const users = guildConfig.users;
294
276
  if (Array.isArray(users) && users.length > 0) {
295
- const additions = [];
296
- for (const entry of users) {
297
- const trimmed = String(entry).trim();
298
- const resolved = resolvedMap.get(trimmed);
299
- if (resolved?.resolved && resolved.id)
300
- additions.push(resolved.id);
301
- }
277
+ const additions = resolveAllowlistIdAdditions({ existing: users, resolvedMap });
302
278
  nextGuild.users = mergeAllowlist({ existing: users, additions });
303
279
  }
304
280
  const channels = guildConfig.channels ?? {};
305
281
  if (channels && typeof channels === "object") {
306
- const nextChannels = { ...channels };
307
- for (const [channelKey, channelConfig] of Object.entries(channels)) {
308
- if (!channelConfig || typeof channelConfig !== "object")
309
- continue;
310
- const channelUsers = channelConfig.users;
311
- if (!Array.isArray(channelUsers) || channelUsers.length === 0)
312
- continue;
313
- const additions = [];
314
- for (const entry of channelUsers) {
315
- const trimmed = String(entry).trim();
316
- const resolved = resolvedMap.get(trimmed);
317
- if (resolved?.resolved && resolved.id)
318
- additions.push(resolved.id);
319
- }
320
- nextChannels[channelKey] = {
321
- ...channelConfig,
322
- users: mergeAllowlist({ existing: channelUsers, additions }),
323
- };
324
- }
325
- nextGuild.channels = nextChannels;
282
+ nextGuild.channels = patchAllowlistUsersInConfigEntries({
283
+ entries: channels,
284
+ resolvedMap,
285
+ });
326
286
  }
327
287
  nextGuilds[guildKey] = nextGuild;
328
288
  }
@@ -338,12 +298,14 @@ export async function monitorDiscordProvider(opts = {}) {
338
298
  if (shouldLogVerbose()) {
339
299
  logVerbose(`discord: config dm=${dmEnabled ? "on" : "off"} dmPolicy=${dmPolicy} allowFrom=${summarizeAllowList(allowFrom)} groupDm=${groupDmEnabled ? "on" : "off"} groupDmChannels=${summarizeAllowList(groupDmChannels)} groupPolicy=${groupPolicy} guilds=${summarizeGuilds(guildEntries)} historyLimit=${historyLimit} mediaMaxMb=${Math.round(mediaMaxBytes / (1024 * 1024))} native=${nativeEnabled ? "on" : "off"} nativeSkills=${nativeSkillsEnabled ? "on" : "off"} accessGroups=${useAccessGroups ? "on" : "off"}`);
340
300
  }
341
- const applicationId = await fetchDiscordApplicationId(token, 4000);
301
+ const applicationId = await fetchDiscordApplicationId(token, 4000, discordRestFetch);
342
302
  if (!applicationId) {
343
303
  throw new Error("Failed to resolve Discord application id");
344
304
  }
345
305
  const maxDiscordCommands = 100;
346
- let skillCommands = nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
306
+ let skillCommands = nativeEnabled && nativeSkillsEnabled
307
+ ? dedupeSkillCommandsForDiscord(listSkillCommandsForAgents({ cfg }))
308
+ : [];
347
309
  let commandSpecs = nativeEnabled
348
310
  ? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "discord" })
349
311
  : [];
@@ -375,6 +337,8 @@ export async function monitorDiscordProvider(opts = {}) {
375
337
  runtime,
376
338
  })
377
339
  : null;
340
+ const agentComponentsConfig = discordCfg.agentComponents ?? {};
341
+ const agentComponentsEnabled = agentComponentsConfig.enabled ?? true;
378
342
  const components = [
379
343
  createDiscordCommandArgFallbackButton({
380
344
  cfg,
@@ -383,9 +347,44 @@ export async function monitorDiscordProvider(opts = {}) {
383
347
  sessionPrefix,
384
348
  }),
385
349
  ];
350
+ const modals = [];
386
351
  if (execApprovalsHandler) {
387
352
  components.push(createExecApprovalButton({ handler: execApprovalsHandler }));
388
353
  }
354
+ if (agentComponentsEnabled) {
355
+ const componentContext = {
356
+ cfg,
357
+ discordConfig: discordCfg,
358
+ accountId: account.accountId,
359
+ guildEntries,
360
+ allowFrom,
361
+ dmPolicy,
362
+ runtime,
363
+ token,
364
+ };
365
+ components.push(createAgentComponentButton(componentContext));
366
+ components.push(createAgentSelectMenu(componentContext));
367
+ components.push(createDiscordComponentButton(componentContext));
368
+ components.push(createDiscordComponentStringSelect(componentContext));
369
+ components.push(createDiscordComponentUserSelect(componentContext));
370
+ components.push(createDiscordComponentRoleSelect(componentContext));
371
+ components.push(createDiscordComponentMentionableSelect(componentContext));
372
+ components.push(createDiscordComponentChannelSelect(componentContext));
373
+ modals.push(createDiscordComponentModal(componentContext));
374
+ }
375
+ class DiscordStatusReadyListener extends ReadyListener {
376
+ async handle(_data, client) {
377
+ const gateway = client.getPlugin("gateway");
378
+ if (!gateway) {
379
+ return;
380
+ }
381
+ const presence = resolveDiscordPresenceUpdate(discordCfg);
382
+ if (!presence) {
383
+ return;
384
+ }
385
+ gateway.updatePresence(presence);
386
+ }
387
+ }
389
388
  const client = new Client({
390
389
  baseUrl: "http://localhost",
391
390
  deploySecret: "a",
@@ -395,17 +394,10 @@ export async function monitorDiscordProvider(opts = {}) {
395
394
  autoDeploy: false,
396
395
  }, {
397
396
  commands,
398
- listeners: [],
397
+ listeners: [new DiscordStatusReadyListener()],
399
398
  components,
400
- }, [
401
- new GatewayPlugin({
402
- reconnect: {
403
- maxAttempts: Number.POSITIVE_INFINITY,
404
- },
405
- intents: resolveDiscordGatewayIntents(discordCfg.intents),
406
- autoInteractions: true,
407
- }),
408
- ]);
399
+ modals,
400
+ }, [createDiscordGatewayPlugin({ discordConfig: discordCfg, runtime })]);
409
401
  await deployDiscordCommands({ client, runtime, enabled: nativeEnabled });
410
402
  const logger = createSubsystemLogger("discord/monitor");
411
403
  const guildHistories = new Map();
@@ -469,6 +461,9 @@ export async function monitorDiscordProvider(opts = {}) {
469
461
  await execApprovalsHandler.start();
470
462
  }
471
463
  const gateway = client.getPlugin("gateway");
464
+ if (gateway) {
465
+ registerGateway(account.accountId, gateway);
466
+ }
472
467
  const gatewayEmitter = getDiscordGatewayEmitter(gateway);
473
468
  const stopGatewayLogging = attachDiscordGatewayLogging({
474
469
  emitter: gatewayEmitter,
@@ -476,8 +471,9 @@ export async function monitorDiscordProvider(opts = {}) {
476
471
  });
477
472
  const abortSignal = opts.abortSignal;
478
473
  const onAbort = () => {
479
- if (!gateway)
474
+ if (!gateway) {
480
475
  return;
476
+ }
481
477
  // Carbon emits an error when maxAttempts is 0; keep a one-shot listener to avoid
482
478
  // an unhandled error after we tear down listeners during abort.
483
479
  gatewayEmitter?.once("error", () => { });
@@ -495,10 +491,12 @@ export async function monitorDiscordProvider(opts = {}) {
495
491
  let helloTimeoutId;
496
492
  const onGatewayDebug = (msg) => {
497
493
  const message = String(msg);
498
- if (!message.includes("WebSocket connection opened"))
494
+ if (!message.includes("WebSocket connection opened")) {
499
495
  return;
500
- if (helloTimeoutId)
496
+ }
497
+ if (helloTimeoutId) {
501
498
  clearTimeout(helloTimeoutId);
499
+ }
502
500
  helloTimeoutId = setTimeout(() => {
503
501
  if (!gateway?.isConnected) {
504
502
  runtime.log?.(danger(`connection stalled: no HELLO received within ${HELLO_TIMEOUT_MS}ms, forcing reconnect`));
@@ -528,9 +526,11 @@ export async function monitorDiscordProvider(opts = {}) {
528
526
  });
529
527
  }
530
528
  finally {
529
+ unregisterGateway(account.accountId);
531
530
  stopGatewayLogging();
532
- if (helloTimeoutId)
531
+ if (helloTimeoutId) {
533
532
  clearTimeout(helloTimeoutId);
533
+ }
534
534
  gatewayEmitter?.removeListener("debug", onGatewayDebug);
535
535
  abortSignal?.removeEventListener("abort", onAbort);
536
536
  if (execApprovalsHandler) {
@@ -549,3 +549,8 @@ async function clearDiscordNativeCommands(params) {
549
549
  params.runtime.error?.(danger(`discord: failed to clear native commands: ${String(err)}`));
550
550
  }
551
551
  }
552
+ export const __testing = {
553
+ createDiscordGatewayPlugin,
554
+ dedupeSkillCommandsForDiscord,
555
+ resolveDiscordRestFetch,
556
+ };
@@ -1,27 +1,31 @@
1
- import { formatAgentEnvelope } from "../../auto-reply/envelope.js";
2
- import { formatDiscordUserTag, resolveTimestampMs } from "./format.js";
3
- export function resolveReplyContext(message, resolveDiscordMessageText, options) {
1
+ import { resolveTimestampMs } from "./format.js";
2
+ import { resolveDiscordSenderIdentity } from "./sender-identity.js";
3
+ export function resolveReplyContext(message, resolveDiscordMessageText) {
4
4
  const referenced = message.referencedMessage;
5
- if (!referenced?.author)
5
+ if (!referenced?.author) {
6
6
  return null;
7
+ }
7
8
  const referencedText = resolveDiscordMessageText(referenced, {
8
9
  includeForwarded: true,
9
10
  });
10
- if (!referencedText)
11
+ if (!referencedText) {
11
12
  return null;
12
- const fromLabel = referenced.author ? buildDirectLabel(referenced.author) : "Unknown";
13
- const body = `${referencedText}\n[discord message id: ${referenced.id} channel: ${referenced.channelId} from: ${formatDiscordUserTag(referenced.author)} user id:${referenced.author?.id ?? "unknown"}]`;
14
- return formatAgentEnvelope({
15
- channel: "Discord",
16
- from: fromLabel,
17
- timestamp: resolveTimestampMs(referenced.timestamp),
18
- body,
19
- envelope: options?.envelope,
13
+ }
14
+ const sender = resolveDiscordSenderIdentity({
15
+ author: referenced.author,
16
+ pluralkitInfo: null,
20
17
  });
18
+ return {
19
+ id: referenced.id,
20
+ channelId: referenced.channelId,
21
+ sender: sender.tag ?? sender.label ?? "unknown",
22
+ body: referencedText,
23
+ timestamp: resolveTimestampMs(referenced.timestamp),
24
+ };
21
25
  }
22
- export function buildDirectLabel(author) {
23
- const username = formatDiscordUserTag(author);
24
- return `${username} user id:${author.id}`;
26
+ export function buildDirectLabel(author, tagOverride) {
27
+ const username = tagOverride?.trim() || resolveDiscordSenderIdentity({ author, pluralkitInfo: null }).tag;
28
+ return `${username ?? "unknown"} user id:${author.id}`;
25
29
  }
26
30
  export function buildGuildLabel(params) {
27
31
  const { guild, channelName, channelId } = params;
@@ -1,6 +1,6 @@
1
1
  import { convertMarkdownTables } from "../../markdown/tables.js";
2
2
  import { chunkDiscordTextWithMode } from "../chunk.js";
3
- import { sendMessageDiscord } from "../send.js";
3
+ import { sendMessageDiscord, sendVoiceMessageDiscord } from "../send.js";
4
4
  export async function deliverDiscordReply(params) {
5
5
  const chunkLimit = Math.min(params.textLimit, 2000);
6
6
  for (const payload of params.replies) {
@@ -8,36 +8,67 @@ export async function deliverDiscordReply(params) {
8
8
  const rawText = payload.text ?? "";
9
9
  const tableMode = params.tableMode ?? "code";
10
10
  const text = convertMarkdownTables(rawText, tableMode);
11
- if (!text && mediaList.length === 0)
11
+ if (!text && mediaList.length === 0) {
12
12
  continue;
13
+ }
13
14
  const replyTo = params.replyToId?.trim() || undefined;
14
15
  if (mediaList.length === 0) {
15
- let isFirstChunk = true;
16
16
  const mode = params.chunkMode ?? "length";
17
17
  const chunks = chunkDiscordTextWithMode(text, {
18
18
  maxChars: chunkLimit,
19
19
  maxLines: params.maxLinesPerMessage,
20
20
  chunkMode: mode,
21
21
  });
22
- if (!chunks.length && text)
22
+ if (!chunks.length && text) {
23
23
  chunks.push(text);
24
+ }
24
25
  for (const chunk of chunks) {
25
26
  const trimmed = chunk.trim();
26
- if (!trimmed)
27
+ if (!trimmed) {
27
28
  continue;
29
+ }
28
30
  await sendMessageDiscord(params.target, trimmed, {
29
31
  token: params.token,
30
32
  rest: params.rest,
31
33
  accountId: params.accountId,
32
- replyTo: isFirstChunk ? replyTo : undefined,
34
+ replyTo,
33
35
  });
34
- isFirstChunk = false;
35
36
  }
36
37
  continue;
37
38
  }
38
39
  const firstMedia = mediaList[0];
39
- if (!firstMedia)
40
+ if (!firstMedia) {
40
41
  continue;
42
+ }
43
+ // Voice message path: audioAsVoice flag routes through sendVoiceMessageDiscord
44
+ if (payload.audioAsVoice) {
45
+ await sendVoiceMessageDiscord(params.target, firstMedia, {
46
+ token: params.token,
47
+ rest: params.rest,
48
+ accountId: params.accountId,
49
+ replyTo,
50
+ });
51
+ // Voice messages cannot include text; send remaining text separately if present
52
+ if (text.trim()) {
53
+ await sendMessageDiscord(params.target, text, {
54
+ token: params.token,
55
+ rest: params.rest,
56
+ accountId: params.accountId,
57
+ replyTo,
58
+ });
59
+ }
60
+ // Additional media items are sent as regular attachments (voice is single-file only)
61
+ for (const extra of mediaList.slice(1)) {
62
+ await sendMessageDiscord(params.target, "", {
63
+ token: params.token,
64
+ rest: params.rest,
65
+ mediaUrl: extra,
66
+ accountId: params.accountId,
67
+ replyTo,
68
+ });
69
+ }
70
+ continue;
71
+ }
41
72
  await sendMessageDiscord(params.target, text, {
42
73
  token: params.token,
43
74
  rest: params.rest,
@@ -51,6 +82,7 @@ export async function deliverDiscordReply(params) {
51
82
  rest: params.rest,
52
83
  mediaUrl: extra,
53
84
  accountId: params.accountId,
85
+ replyTo,
54
86
  });
55
87
  }
56
88
  }
@@ -0,0 +1,22 @@
1
+ import { ProxyAgent, fetch as undiciFetch } from "undici";
2
+ import { danger } from "../../globals.js";
3
+ import { wrapFetchWithAbortSignal } from "../../infra/fetch.js";
4
+ export function resolveDiscordRestFetch(proxyUrl, runtime) {
5
+ const proxy = proxyUrl?.trim();
6
+ if (!proxy) {
7
+ return fetch;
8
+ }
9
+ try {
10
+ const agent = new ProxyAgent(proxy);
11
+ const fetcher = ((input, init) => undiciFetch(input, {
12
+ ...init,
13
+ dispatcher: agent,
14
+ }));
15
+ runtime.log?.("discord: rest proxy enabled");
16
+ return wrapFetchWithAbortSignal(fetcher);
17
+ }
18
+ catch (err) {
19
+ runtime.error?.(danger(`discord: invalid rest proxy: ${String(err)}`));
20
+ return fetch;
21
+ }
22
+ }