@poolzin/pool-bot 2026.1.39 → 2026.2.1

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 (511) hide show
  1. package/assets/chrome-extension/README.md +3 -3
  2. package/assets/chrome-extension/background.js +5 -5
  3. package/assets/chrome-extension/manifest.json +3 -3
  4. package/assets/chrome-extension/options.html +4 -4
  5. package/assets/chrome-extension/options.js +1 -1
  6. package/dist/acp/client.js +3 -3
  7. package/dist/acp/types.js +1 -1
  8. package/dist/agents/agent-paths.js +3 -3
  9. package/dist/agents/auth-profiles/paths.js +3 -3
  10. package/dist/agents/bash-tools.exec.js +76 -25
  11. package/dist/agents/cli-runner/helpers.js +10 -12
  12. package/dist/agents/cli-runner.js +2 -2
  13. package/dist/agents/cloudflare-ai-gateway.js +31 -0
  14. package/dist/agents/compaction.js +16 -2
  15. package/dist/agents/context-window-guard.js +13 -10
  16. package/dist/agents/context.js +4 -4
  17. package/dist/agents/docs-path.js +1 -1
  18. package/dist/agents/identity.js +47 -7
  19. package/dist/agents/memory-search.js +25 -8
  20. package/dist/agents/minimax-vlm.js +1 -1
  21. package/dist/agents/model-auth.js +12 -1
  22. package/dist/agents/model-catalog.js +4 -4
  23. package/dist/agents/model-selection.js +31 -4
  24. package/dist/agents/models-config.js +3 -3
  25. package/dist/agents/models-config.providers.js +147 -39
  26. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  27. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  28. package/dist/agents/pi-embedded-helpers/openai.js +1 -1
  29. package/dist/agents/pi-embedded-helpers.js +1 -1
  30. package/dist/agents/pi-embedded-runner/compact.js +9 -8
  31. package/dist/agents/pi-embedded-runner/model.js +63 -4
  32. package/dist/agents/pi-embedded-runner/run/attempt.js +27 -17
  33. package/dist/agents/pi-embedded-runner/run.js +203 -50
  34. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  35. package/dist/agents/pi-embedded-runner/tool-result-truncation.js +275 -0
  36. package/dist/agents/pi-embedded-runner/utils.js +1 -1
  37. package/dist/agents/pi-embedded-subscribe.js +118 -29
  38. package/dist/agents/pi-model-discovery.js +10 -0
  39. package/dist/agents/pi-tool-definition-adapter.js +50 -9
  40. package/dist/agents/pi-tools.before-tool-call.js +67 -0
  41. package/dist/agents/pi-tools.js +20 -10
  42. package/dist/agents/pi-tools.read.js +2 -2
  43. package/dist/agents/poolbot-tools.js +15 -10
  44. package/dist/agents/sandbox-paths.js +31 -0
  45. package/dist/agents/session-file-repair.js +83 -0
  46. package/dist/agents/session-tool-result-guard.js +94 -15
  47. package/dist/agents/session-transcript-repair.js +68 -0
  48. package/dist/agents/shell-utils.js +51 -0
  49. package/dist/agents/skills/bundled-context.js +23 -0
  50. package/dist/agents/skills/bundled-dir.js +41 -7
  51. package/dist/agents/skills/frontmatter.js +1 -1
  52. package/dist/agents/skills/workspace.js +2 -2
  53. package/dist/agents/skills-install.js +60 -23
  54. package/dist/agents/subagent-announce.js +79 -34
  55. package/dist/agents/system-prompt.js +28 -4
  56. package/dist/agents/together-models.js +127 -0
  57. package/dist/agents/tool-images.js +1 -1
  58. package/dist/agents/tool-policy.conformance.js +14 -0
  59. package/dist/agents/tool-policy.js +25 -1
  60. package/dist/agents/tools/browser-tool.js +3 -3
  61. package/dist/agents/tools/cron-tool.js +166 -19
  62. package/dist/agents/tools/discord-actions-presence.js +78 -0
  63. package/dist/agents/tools/image-tool.js +2 -2
  64. package/dist/agents/tools/memory-tool.js +93 -5
  65. package/dist/agents/tools/message-tool.js +56 -2
  66. package/dist/agents/tools/sessions-history-tool.js +69 -1
  67. package/dist/agents/tools/web-search.js +211 -42
  68. package/dist/agents/usage.js +23 -1
  69. package/dist/agents/workspace-run.js +67 -0
  70. package/dist/agents/workspace-templates.js +44 -0
  71. package/dist/auto-reply/command-auth.js +121 -6
  72. package/dist/auto-reply/commands-registry.data.js +1 -1
  73. package/dist/auto-reply/envelope.js +50 -72
  74. package/dist/auto-reply/reply/commands-compact.js +1 -0
  75. package/dist/auto-reply/reply/commands-context-report.js +3 -2
  76. package/dist/auto-reply/reply/commands-context.js +1 -0
  77. package/dist/auto-reply/reply/commands-models.js +107 -60
  78. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  79. package/dist/auto-reply/reply/commands-session.js +2 -2
  80. package/dist/auto-reply/reply/get-reply-run.js +16 -5
  81. package/dist/auto-reply/reply/groups.js +1 -1
  82. package/dist/auto-reply/reply/inbound-context.js +9 -1
  83. package/dist/auto-reply/reply/inbound-meta.js +130 -0
  84. package/dist/auto-reply/reply/model-selection.js +3 -3
  85. package/dist/auto-reply/reply/untrusted-context.js +15 -0
  86. package/dist/auto-reply/status.js +1 -1
  87. package/dist/auto-reply/thinking.js +88 -43
  88. package/dist/browser/bridge-server.js +13 -0
  89. package/dist/browser/cdp.helpers.js +38 -24
  90. package/dist/browser/client-fetch.js +51 -8
  91. package/dist/browser/config.js +2 -11
  92. package/dist/browser/extension-relay.js +104 -43
  93. package/dist/browser/pw-ai.js +1 -1
  94. package/dist/browser/pw-session.js +143 -8
  95. package/dist/browser/pw-tools-core.interactions.js +125 -27
  96. package/dist/browser/pw-tools-core.responses.js +1 -1
  97. package/dist/browser/pw-tools-core.state.js +1 -1
  98. package/dist/browser/routes/agent.act.js +86 -41
  99. package/dist/browser/routes/dispatcher.js +4 -4
  100. package/dist/browser/screenshot.js +1 -1
  101. package/dist/browser/server-context.js +2 -2
  102. package/dist/browser/server.js +13 -0
  103. package/dist/build-info.json +3 -3
  104. package/dist/canvas-host/a2ui.js +3 -3
  105. package/dist/channels/plugins/catalog.js +2 -2
  106. package/dist/channels/plugins/onboarding/imessage.js +1 -1
  107. package/dist/channels/plugins/onboarding/signal.js +1 -1
  108. package/dist/channels/plugins/onboarding/slack.js +4 -4
  109. package/dist/channels/plugins/onboarding/whatsapp.js +3 -3
  110. package/dist/channels/plugins/pairing-message.js +1 -1
  111. package/dist/channels/reply-prefix.js +8 -1
  112. package/dist/cli/browser-cli-extension.js +2 -2
  113. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  114. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  115. package/dist/cli/cron-cli/shared.js +56 -41
  116. package/dist/cli/dns-cli.js +26 -14
  117. package/dist/cli/docs-cli.js +1 -1
  118. package/dist/cli/gateway-cli/dev.js +1 -1
  119. package/dist/cli/gateway-cli/register.js +37 -19
  120. package/dist/cli/memory-cli.js +30 -20
  121. package/dist/cli/nodes-cli/register.canvas.js +1 -1
  122. package/dist/cli/parse-bytes.js +37 -0
  123. package/dist/cli/plugins-cli.js +1 -1
  124. package/dist/cli/run-main.js +2 -2
  125. package/dist/cli/security-cli.js +1 -1
  126. package/dist/cli/tagline.js +1 -1
  127. package/dist/cli/update-cli.js +173 -52
  128. package/dist/cli/webhooks-cli.js +5 -5
  129. package/dist/commands/agent.js +1 -0
  130. package/dist/commands/agents.commands.add.js +1 -1
  131. package/dist/commands/auth-choice.apply.api-providers.js +305 -17
  132. package/dist/commands/auth-choice.apply.js +4 -1
  133. package/dist/commands/auth-choice.apply.plugin-provider.js +2 -2
  134. package/dist/commands/auth-choice.apply.xai.js +63 -0
  135. package/dist/commands/auth-choice.preferred-provider.js +7 -1
  136. package/dist/commands/configure.wizard.js +1 -1
  137. package/dist/commands/dashboard.js +1 -1
  138. package/dist/commands/docs.js +1 -1
  139. package/dist/commands/doctor-config-flow.js +61 -5
  140. package/dist/commands/doctor-gateway-services.js +3 -3
  141. package/dist/commands/doctor-state-migrations.js +1 -1
  142. package/dist/commands/doctor-update.js +3 -3
  143. package/dist/commands/doctor.js +1 -1
  144. package/dist/commands/health.js +1 -1
  145. package/dist/commands/model-allowlist.js +29 -0
  146. package/dist/commands/model-picker.js +2 -1
  147. package/dist/commands/models/list.probe.js +2 -2
  148. package/dist/commands/models/list.registry.js +4 -4
  149. package/dist/commands/models/list.status-command.js +44 -24
  150. package/dist/commands/models/shared.js +15 -0
  151. package/dist/commands/onboard-auth.config-core.js +366 -28
  152. package/dist/commands/onboard-auth.credentials.js +71 -9
  153. package/dist/commands/onboard-auth.js +3 -3
  154. package/dist/commands/onboard-auth.models.js +26 -24
  155. package/dist/commands/onboard-custom.js +384 -0
  156. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  157. package/dist/commands/onboard-non-interactive/local/auth-choice.js +146 -9
  158. package/dist/commands/onboard-skills.js +63 -38
  159. package/dist/commands/openai-model-default.js +41 -0
  160. package/dist/commands/status-all/report-lines.js +1 -1
  161. package/dist/commands/status.command.js +1 -1
  162. package/dist/commands/uninstall.js +3 -3
  163. package/dist/compat/legacy-names.js +1 -1
  164. package/dist/config/defaults.js +3 -2
  165. package/dist/config/io.js +3 -3
  166. package/dist/config/paths.js +136 -35
  167. package/dist/config/plugin-auto-enable.js +21 -5
  168. package/dist/config/redact-snapshot.js +153 -0
  169. package/dist/config/schema.field-metadata.js +590 -0
  170. package/dist/config/schema.js +3 -3
  171. package/dist/config/sessions/store.js +291 -23
  172. package/dist/config/types.memory.js +1 -0
  173. package/dist/config/version.js +4 -4
  174. package/dist/config/zod-schema.agent-defaults.js +3 -0
  175. package/dist/config/zod-schema.agent-runtime.js +13 -2
  176. package/dist/config/zod-schema.providers-core.js +142 -0
  177. package/dist/config/zod-schema.session.js +3 -0
  178. package/dist/cron/delivery.js +57 -0
  179. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  180. package/dist/cron/isolated-agent/helpers.js +22 -5
  181. package/dist/cron/isolated-agent/run.js +171 -63
  182. package/dist/cron/isolated-agent/session.js +2 -0
  183. package/dist/cron/normalize.js +356 -28
  184. package/dist/cron/parse.js +10 -5
  185. package/dist/cron/run-log.js +35 -10
  186. package/dist/cron/schedule.js +41 -6
  187. package/dist/cron/service/jobs.js +208 -35
  188. package/dist/cron/service/ops.js +72 -16
  189. package/dist/cron/service/state.js +2 -0
  190. package/dist/cron/service/store.js +386 -14
  191. package/dist/cron/service/timer.js +390 -147
  192. package/dist/cron/session-reaper.js +86 -0
  193. package/dist/cron/store.js +23 -8
  194. package/dist/cron/validate-timestamp.js +43 -0
  195. package/dist/daemon/constants.js +7 -7
  196. package/dist/daemon/inspect.js +6 -6
  197. package/dist/daemon/systemd-unit.js +1 -1
  198. package/dist/discord/monitor/agent-components.js +438 -0
  199. package/dist/discord/monitor/allow-list.js +28 -5
  200. package/dist/discord/monitor/gateway-registry.js +29 -0
  201. package/dist/discord/monitor/native-command.js +44 -23
  202. package/dist/discord/monitor/sender-identity.js +45 -0
  203. package/dist/discord/pluralkit.js +27 -0
  204. package/dist/discord/send.outbound.js +92 -5
  205. package/dist/discord/send.shared.js +60 -23
  206. package/dist/discord/targets.js +84 -1
  207. package/dist/entry.js +15 -9
  208. package/dist/extensionAPI.js +8 -0
  209. package/dist/gateway/control-ui.js +8 -1
  210. package/dist/gateway/hooks-mapping.js +3 -0
  211. package/dist/gateway/hooks.js +65 -0
  212. package/dist/gateway/live-image-probe.js +1 -66
  213. package/dist/gateway/net.js +96 -31
  214. package/dist/gateway/node-command-policy.js +50 -15
  215. package/dist/gateway/openai-http.js +2 -2
  216. package/dist/gateway/openresponses-http.js +4 -4
  217. package/dist/gateway/origin-check.js +56 -0
  218. package/dist/gateway/protocol/client-info.js +9 -0
  219. package/dist/gateway/protocol/index.js +9 -2
  220. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  221. package/dist/gateway/protocol/schema/cron.js +22 -10
  222. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  223. package/dist/gateway/protocol/schema/sessions.js +12 -0
  224. package/dist/gateway/server/hooks.js +1 -1
  225. package/dist/gateway/server-broadcast.js +26 -9
  226. package/dist/gateway/server-chat.js +112 -23
  227. package/dist/gateway/server-discovery-runtime.js +10 -2
  228. package/dist/gateway/server-discovery.js +2 -2
  229. package/dist/gateway/server-http.js +110 -12
  230. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  231. package/dist/gateway/server-methods/agents.js +321 -2
  232. package/dist/gateway/server-methods/usage.js +559 -16
  233. package/dist/gateway/server-runtime-state.js +22 -8
  234. package/dist/gateway/server-startup-memory.js +16 -0
  235. package/dist/gateway/server.impl.js +7 -3
  236. package/dist/gateway/session-utils.fs.js +23 -25
  237. package/dist/gateway/session-utils.js +20 -10
  238. package/dist/gateway/sessions-patch.js +7 -22
  239. package/dist/gateway/test-helpers.server.js +35 -2
  240. package/dist/hooks/frontmatter.js +1 -1
  241. package/dist/hooks/hooks-status.js +1 -1
  242. package/dist/hooks/install.js +2 -2
  243. package/dist/hooks/loader.js +1 -1
  244. package/dist/hooks/workspace.js +3 -3
  245. package/dist/imessage/constants.js +2 -0
  246. package/dist/imessage/monitor/deliver.js +4 -1
  247. package/dist/imessage/monitor/monitor-provider.js +51 -1
  248. package/dist/index.js +2 -2
  249. package/dist/infra/bonjour-discovery.js +131 -70
  250. package/dist/infra/bonjour.js +3 -3
  251. package/dist/infra/control-ui-assets.js +134 -12
  252. package/dist/infra/errors.js +12 -0
  253. package/dist/infra/exec-approvals.js +266 -57
  254. package/dist/infra/format-time/format-datetime.js +79 -0
  255. package/dist/infra/format-time/format-duration.js +81 -0
  256. package/dist/infra/format-time/format-relative.js +80 -0
  257. package/dist/infra/heartbeat-runner.js +140 -49
  258. package/dist/infra/home-dir.js +54 -0
  259. package/dist/infra/net/fetch-guard.js +122 -0
  260. package/dist/infra/net/ssrf.js +65 -29
  261. package/dist/infra/outbound/abort.js +14 -0
  262. package/dist/infra/outbound/message-action-runner.js +77 -13
  263. package/dist/infra/outbound/outbound-session.js +143 -37
  264. package/dist/infra/path-env.js +3 -3
  265. package/dist/infra/poolbot-root.js +43 -1
  266. package/dist/infra/provider-usage.fetch.minimax.js +1 -1
  267. package/dist/infra/restart.js +1 -1
  268. package/dist/infra/session-cost-usage.js +631 -41
  269. package/dist/infra/state-migrations.js +317 -47
  270. package/dist/infra/tailscale.js +1 -1
  271. package/dist/infra/update-global.js +35 -0
  272. package/dist/infra/update-runner.js +149 -43
  273. package/dist/infra/warning-filter.js +65 -0
  274. package/dist/infra/widearea-dns.js +30 -9
  275. package/dist/logging/redact-identifier.js +12 -0
  276. package/dist/macos/relay.js +2 -2
  277. package/dist/media/fetch.js +81 -58
  278. package/dist/media/input-files.js +1 -1
  279. package/dist/media/mime.js +4 -0
  280. package/dist/media/png-encode.js +74 -0
  281. package/dist/media-understanding/apply.js +403 -3
  282. package/dist/media-understanding/attachments.js +38 -27
  283. package/dist/media-understanding/defaults.js +16 -0
  284. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  285. package/dist/media-understanding/providers/google/audio.js +24 -17
  286. package/dist/media-understanding/providers/google/video.js +24 -17
  287. package/dist/media-understanding/providers/image.js +4 -4
  288. package/dist/media-understanding/providers/index.js +4 -1
  289. package/dist/media-understanding/providers/openai/audio.js +22 -14
  290. package/dist/media-understanding/providers/shared.js +16 -11
  291. package/dist/media-understanding/providers/zai/index.js +6 -0
  292. package/dist/media-understanding/runner.js +158 -90
  293. package/dist/memory/backend-config.js +207 -0
  294. package/dist/memory/batch-voyage.js +277 -0
  295. package/dist/memory/embeddings-voyage.js +75 -0
  296. package/dist/memory/embeddings.js +29 -17
  297. package/dist/memory/internal.js +101 -18
  298. package/dist/memory/manager.js +155 -48
  299. package/dist/memory/search-manager.js +173 -0
  300. package/dist/memory/session-files.js +9 -3
  301. package/dist/memory/types.js +1 -0
  302. package/dist/node-host/runner.js +36 -26
  303. package/dist/node-host/with-timeout.js +27 -0
  304. package/dist/pairing/pairing-messages.js +1 -1
  305. package/dist/plugins/commands.js +5 -1
  306. package/dist/plugins/config-state.js +86 -7
  307. package/dist/plugins/discovery.js +1 -1
  308. package/dist/plugins/install.js +2 -2
  309. package/dist/plugins/source-display.js +51 -0
  310. package/dist/plugins/update.js +1 -1
  311. package/dist/process/exec.js +20 -2
  312. package/dist/routing/resolve-route.js +12 -0
  313. package/dist/routing/session-key.js +15 -0
  314. package/dist/runtime.js +2 -0
  315. package/dist/security/audit-extra.async.js +601 -0
  316. package/dist/security/audit-extra.js +2 -830
  317. package/dist/security/audit-extra.sync.js +505 -0
  318. package/dist/security/audit.js +2 -2
  319. package/dist/security/channel-metadata.js +34 -0
  320. package/dist/security/external-content.js +88 -6
  321. package/dist/security/skill-scanner.js +330 -0
  322. package/dist/sessions/session-key-utils.js +7 -0
  323. package/dist/shared/text/reasoning-tags.js +52 -7
  324. package/dist/signal/monitor/event-handler.js +80 -1
  325. package/dist/slack/monitor/media.js +85 -15
  326. package/dist/tailscale/detect.js +145 -0
  327. package/dist/telegram/bot/helpers.js +109 -28
  328. package/dist/telegram/bot-handlers.js +144 -3
  329. package/dist/telegram/bot-message-context.js +38 -11
  330. package/dist/telegram/bot-message-dispatch.js +48 -15
  331. package/dist/telegram/bot-native-commands.js +86 -29
  332. package/dist/telegram/bot.js +30 -29
  333. package/dist/telegram/model-buttons.js +163 -0
  334. package/dist/telegram/monitor.js +110 -85
  335. package/dist/telegram/send.js +129 -47
  336. package/dist/terminal/restore.js +45 -0
  337. package/dist/test-helpers/state-dir-env.js +16 -0
  338. package/dist/test-helpers/workspace.js +11 -0
  339. package/dist/test-utils/channel-plugins.js +82 -0
  340. package/dist/test-utils/ports.js +73 -0
  341. package/dist/tts/tts.js +12 -6
  342. package/dist/tui/tui-session-actions.js +166 -54
  343. package/dist/utils/fetch-timeout.js +20 -0
  344. package/dist/utils/normalize-secret-input.js +19 -0
  345. package/dist/utils/shell-argv.js +61 -0
  346. package/dist/utils/transcript-tools.js +58 -0
  347. package/dist/utils.js +55 -14
  348. package/dist/version.js +42 -5
  349. package/dist/web/qr-image.js +1 -61
  350. package/dist/wizard/onboarding.finalize.js +7 -7
  351. package/dist/wizard/onboarding.js +3 -3
  352. package/docs/RELEASE_WORKFOTS_COMPARISON.md +3 -3
  353. package/docs/_config.yml +2 -2
  354. package/docs/_layouts/default.html +9 -9
  355. package/docs/concepts/typebox.md +1 -1
  356. package/docs/docs.json +1 -1
  357. package/docs/northflank.mdx +7 -7
  358. package/docs/railway.mdx +3 -3
  359. package/docs/render.mdx +5 -5
  360. package/docs/start/lore.md +2 -2
  361. package/extensions/bluebubbles/index.ts +2 -2
  362. package/extensions/bluebubbles/package.json +1 -1
  363. package/extensions/bluebubbles/src/accounts.ts +8 -8
  364. package/extensions/bluebubbles/src/actions.test.ts +22 -22
  365. package/extensions/bluebubbles/src/actions.ts +5 -5
  366. package/extensions/bluebubbles/src/attachments.ts +2 -2
  367. package/extensions/bluebubbles/src/channel.ts +16 -16
  368. package/extensions/bluebubbles/src/chat.ts +2 -2
  369. package/extensions/bluebubbles/src/media-send.ts +2 -2
  370. package/extensions/bluebubbles/src/monitor.test.ts +46 -46
  371. package/extensions/bluebubbles/src/monitor.ts +5 -5
  372. package/extensions/bluebubbles/src/onboarding.ts +7 -7
  373. package/extensions/bluebubbles/src/reactions.ts +2 -2
  374. package/extensions/bluebubbles/src/send.ts +2 -2
  375. package/extensions/copilot-proxy/README.md +1 -1
  376. package/extensions/copilot-proxy/package.json +1 -1
  377. package/extensions/diagnostics-otel/index.ts +2 -2
  378. package/extensions/diagnostics-otel/package.json +1 -1
  379. package/extensions/diagnostics-otel/src/service.ts +3 -3
  380. package/extensions/discord/index.ts +2 -2
  381. package/extensions/discord/package.json +1 -1
  382. package/extensions/google-antigravity-auth/README.md +1 -1
  383. package/extensions/google-antigravity-auth/index.ts +1 -1
  384. package/extensions/google-antigravity-auth/package.json +1 -1
  385. package/extensions/google-gemini-cli-auth/README.md +1 -1
  386. package/extensions/google-gemini-cli-auth/oauth.ts +1 -1
  387. package/extensions/google-gemini-cli-auth/package.json +1 -1
  388. package/extensions/googlechat/index.ts +3 -3
  389. package/extensions/googlechat/package.json +1 -1
  390. package/extensions/googlechat/src/accounts.ts +8 -8
  391. package/extensions/googlechat/src/actions.ts +6 -6
  392. package/extensions/googlechat/src/channel.ts +21 -21
  393. package/extensions/googlechat/src/monitor.ts +8 -8
  394. package/extensions/googlechat/src/onboarding.ts +10 -10
  395. package/extensions/imessage/index.ts +2 -2
  396. package/extensions/imessage/package.json +1 -1
  397. package/extensions/line/index.ts +2 -2
  398. package/extensions/line/package.json +1 -1
  399. package/extensions/line/src/card-command.ts +2 -2
  400. package/extensions/line/src/channel.logout.test.ts +4 -4
  401. package/extensions/line/src/channel.sendPayload.test.ts +8 -8
  402. package/extensions/line/src/channel.ts +3 -3
  403. package/extensions/llm-task/README.md +3 -3
  404. package/extensions/llm-task/index.ts +2 -2
  405. package/extensions/llm-task/package.json +1 -1
  406. package/extensions/llm-task/src/llm-task-tool.ts +4 -4
  407. package/extensions/lobster/README.md +6 -6
  408. package/extensions/lobster/index.ts +2 -2
  409. package/extensions/lobster/src/lobster-tool.test.ts +4 -4
  410. package/extensions/lobster/src/lobster-tool.ts +2 -2
  411. package/extensions/matrix/index.ts +2 -2
  412. package/extensions/matrix/package.json +1 -1
  413. package/extensions/matrix/src/matrix/client/config.ts +1 -1
  414. package/extensions/matrix/src/matrix/monitor/handler.ts +1 -1
  415. package/extensions/matrix/src/onboarding.ts +1 -1
  416. package/extensions/mattermost/index.ts +2 -2
  417. package/extensions/mattermost/package.json +1 -1
  418. package/extensions/mattermost/src/mattermost/accounts.ts +8 -8
  419. package/extensions/mattermost/src/mattermost/monitor-helpers.ts +5 -5
  420. package/extensions/mattermost/src/mattermost/monitor.ts +2 -2
  421. package/extensions/mattermost/src/onboarding-helpers.ts +3 -3
  422. package/extensions/mattermost/src/onboarding.ts +2 -2
  423. package/extensions/memory-core/index.ts +2 -2
  424. package/extensions/memory-core/package.json +1 -1
  425. package/extensions/memory-lancedb/index.ts +3 -3
  426. package/extensions/memory-lancedb/package.json +1 -1
  427. package/extensions/msteams/index.ts +2 -2
  428. package/extensions/msteams/package.json +1 -1
  429. package/extensions/msteams/src/channel.directory.test.ts +2 -2
  430. package/extensions/msteams/src/channel.ts +2 -2
  431. package/extensions/msteams/src/graph-upload.ts +4 -4
  432. package/extensions/msteams/src/monitor-handler.ts +2 -2
  433. package/extensions/msteams/src/monitor.ts +2 -2
  434. package/extensions/msteams/src/onboarding.ts +9 -9
  435. package/extensions/msteams/src/reply-dispatcher.ts +2 -2
  436. package/extensions/msteams/src/send-context.ts +2 -2
  437. package/extensions/msteams/src/send.ts +4 -4
  438. package/extensions/nextcloud-talk/index.ts +2 -2
  439. package/extensions/nextcloud-talk/package.json +1 -1
  440. package/extensions/nextcloud-talk/src/channel.ts +7 -7
  441. package/extensions/nextcloud-talk/src/inbound.ts +7 -7
  442. package/extensions/nextcloud-talk/src/onboarding.ts +1 -1
  443. package/extensions/nostr/README.md +2 -2
  444. package/extensions/nostr/index.ts +5 -5
  445. package/extensions/nostr/package.json +1 -1
  446. package/extensions/nostr/src/types.ts +4 -4
  447. package/extensions/open-prose/index.ts +2 -2
  448. package/extensions/qwen-portal-auth/README.md +1 -1
  449. package/extensions/signal/index.ts +2 -2
  450. package/extensions/signal/package.json +1 -1
  451. package/extensions/slack/index.ts +2 -2
  452. package/extensions/slack/package.json +1 -1
  453. package/extensions/telegram/index.ts +2 -2
  454. package/extensions/telegram/package.json +1 -1
  455. package/extensions/telegram/src/channel.ts +2 -2
  456. package/extensions/tlon/README.md +2 -2
  457. package/extensions/tlon/index.ts +2 -2
  458. package/extensions/tlon/package.json +1 -1
  459. package/extensions/tlon/src/channel.ts +13 -13
  460. package/extensions/tlon/src/monitor/index.ts +3 -3
  461. package/extensions/tlon/src/onboarding.ts +3 -3
  462. package/extensions/tlon/src/types.ts +3 -3
  463. package/extensions/twitch/README.md +1 -1
  464. package/extensions/twitch/index.ts +2 -2
  465. package/extensions/twitch/package.json +1 -1
  466. package/extensions/twitch/src/config.ts +3 -3
  467. package/extensions/twitch/src/monitor.ts +3 -3
  468. package/extensions/twitch/src/onboarding.ts +9 -9
  469. package/extensions/twitch/src/outbound.test.ts +2 -2
  470. package/extensions/twitch/src/plugin.test.ts +2 -2
  471. package/extensions/twitch/src/plugin.ts +8 -8
  472. package/extensions/twitch/src/send.test.ts +2 -2
  473. package/extensions/twitch/src/send.ts +4 -4
  474. package/extensions/twitch/src/token.test.ts +8 -8
  475. package/extensions/twitch/src/token.ts +3 -3
  476. package/extensions/twitch/src/twitch-client.ts +3 -3
  477. package/extensions/twitch/src/types.ts +3 -3
  478. package/extensions/twitch/src/utils/markdown.ts +1 -1
  479. package/extensions/voice-call/README.md +3 -3
  480. package/extensions/voice-call/package.json +1 -1
  481. package/extensions/voice-call/src/core-bridge.ts +2 -2
  482. package/extensions/voice-call/src/response-generator.ts +1 -1
  483. package/extensions/whatsapp/index.ts +2 -2
  484. package/extensions/whatsapp/package.json +1 -1
  485. package/extensions/zalo/README.md +1 -1
  486. package/extensions/zalo/index.ts +2 -2
  487. package/extensions/zalo/package.json +1 -1
  488. package/extensions/zalo/src/accounts.ts +8 -8
  489. package/extensions/zalo/src/actions.ts +4 -4
  490. package/extensions/zalo/src/channel.directory.test.ts +2 -2
  491. package/extensions/zalo/src/channel.ts +18 -18
  492. package/extensions/zalo/src/monitor.ts +9 -9
  493. package/extensions/zalo/src/monitor.webhook.test.ts +2 -2
  494. package/extensions/zalo/src/onboarding.ts +24 -24
  495. package/extensions/zalo/src/send.ts +2 -2
  496. package/extensions/zalouser/README.md +2 -2
  497. package/extensions/zalouser/index.ts +2 -2
  498. package/extensions/zalouser/package.json +1 -1
  499. package/extensions/zalouser/src/accounts.ts +9 -9
  500. package/extensions/zalouser/src/channel.ts +24 -24
  501. package/extensions/zalouser/src/monitor.ts +4 -4
  502. package/extensions/zalouser/src/onboarding.ts +28 -28
  503. package/package.json +13 -251
  504. package/skills/nano-banana-pro/scripts/generate_image.py +1 -1
  505. package/skills/tmux/scripts/find-sessions.sh +1 -1
  506. package/CHANGELOG.md +0 -102
  507. package/README-header.png +0 -0
  508. package/git-hooks/pre-commit +0 -4
  509. package/scripts/format-staged.js +0 -148
  510. package/scripts/postinstall.js +0 -300
  511. package/scripts/setup-git-hooks.js +0 -96
@@ -68,7 +68,7 @@ export function resolveDiscordAllowListMatch(params) {
68
68
  return { allowed: false };
69
69
  }
70
70
  export function resolveDiscordUserAllowed(params) {
71
- const allowList = normalizeDiscordAllowList(params.allowList, ["discord:", "user:"]);
71
+ const allowList = normalizeDiscordAllowList(params.allowList, ["discord:", "user:", "pk:"]);
72
72
  if (!allowList)
73
73
  return true;
74
74
  return allowListMatches(allowList, {
@@ -80,7 +80,7 @@ export function resolveDiscordUserAllowed(params) {
80
80
  export function resolveDiscordCommandAuthorized(params) {
81
81
  if (!params.isDirectMessage)
82
82
  return true;
83
- const allowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]);
83
+ const allowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:", "pk:"]);
84
84
  if (!allowList)
85
85
  return true;
86
86
  return allowListMatches(allowList, {
@@ -89,6 +89,28 @@ export function resolveDiscordCommandAuthorized(params) {
89
89
  tag: formatDiscordUserTag(params.author),
90
90
  });
91
91
  }
92
+ export function resolveDiscordOwnerAllowFrom(params) {
93
+ const rawAllowList = params.channelConfig?.users ?? params.guildInfo?.users;
94
+ if (!Array.isArray(rawAllowList) || rawAllowList.length === 0) {
95
+ return undefined;
96
+ }
97
+ const allowList = normalizeDiscordAllowList(rawAllowList, ["discord:", "user:", "pk:"]);
98
+ if (!allowList) {
99
+ return undefined;
100
+ }
101
+ const match = resolveDiscordAllowListMatch({
102
+ allowList,
103
+ candidate: {
104
+ id: params.sender.id,
105
+ name: params.sender.name,
106
+ tag: params.sender.tag,
107
+ },
108
+ });
109
+ if (!match.allowed || !match.matchKey || match.matchKey === "*") {
110
+ return undefined;
111
+ }
112
+ return [match.matchKey];
113
+ }
92
114
  export function resolveDiscordGuildEntry(params) {
93
115
  const guild = params.guild;
94
116
  const entries = params.guildEntries;
@@ -128,6 +150,7 @@ function resolveDiscordChannelConfigEntry(entry) {
128
150
  enabled: entry.enabled,
129
151
  users: entry.users,
130
152
  systemPrompt: entry.systemPrompt,
153
+ includeThreadStarter: entry.includeThreadStarter,
131
154
  autoThread: entry.autoThread,
132
155
  };
133
156
  return resolved;
@@ -199,13 +222,13 @@ export function resolveGroupDmAllow(params) {
199
222
  const { channels, channelId, channelName, channelSlug } = params;
200
223
  if (!channels || channels.length === 0)
201
224
  return true;
202
- const allowList = channels.map((entry) => normalizeDiscordSlug(String(entry)));
225
+ const allowList = new Set(channels.map((entry) => normalizeDiscordSlug(String(entry))));
203
226
  const candidates = [
204
227
  normalizeDiscordSlug(channelId),
205
228
  channelSlug,
206
229
  channelName ? normalizeDiscordSlug(channelName) : "",
207
230
  ].filter(Boolean);
208
- return allowList.includes("*") || candidates.some((candidate) => allowList.includes(candidate));
231
+ return allowList.has("*") || candidates.some((candidate) => allowList.has(candidate));
209
232
  }
210
233
  export function shouldEmitDiscordReactionNotification(params) {
211
234
  const mode = params.mode ?? "own";
@@ -217,7 +240,7 @@ export function shouldEmitDiscordReactionNotification(params) {
217
240
  return Boolean(params.botId && params.messageAuthorId === params.botId);
218
241
  }
219
242
  if (mode === "allowlist") {
220
- const list = normalizeDiscordAllowList(params.allowlist, ["discord:", "user:"]);
243
+ const list = normalizeDiscordAllowList(params.allowlist, ["discord:", "user:", "pk:"]);
221
244
  if (!list)
222
245
  return false;
223
246
  return allowListMatches(list, {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Module-level registry of active Discord GatewayPlugin instances.
3
+ * Bridges the gap between agent tool handlers (which only have REST access)
4
+ * and the gateway WebSocket (needed for operations like updatePresence).
5
+ * Follows the same pattern as presence-cache.ts.
6
+ */
7
+ const gatewayRegistry = new Map();
8
+ // Sentinel key for the default (unnamed) account. Uses a prefix that cannot
9
+ // collide with user-configured account IDs.
10
+ const DEFAULT_ACCOUNT_KEY = "\0__default__";
11
+ function resolveAccountKey(accountId) {
12
+ return accountId ?? DEFAULT_ACCOUNT_KEY;
13
+ }
14
+ /** Register a GatewayPlugin instance for an account. */
15
+ export function registerGateway(accountId, gateway) {
16
+ gatewayRegistry.set(resolveAccountKey(accountId), gateway);
17
+ }
18
+ /** Unregister a GatewayPlugin instance for an account. */
19
+ export function unregisterGateway(accountId) {
20
+ gatewayRegistry.delete(resolveAccountKey(accountId));
21
+ }
22
+ /** Get the GatewayPlugin for an account. Returns undefined if not registered. */
23
+ export function getGateway(accountId) {
24
+ return gatewayRegistry.get(resolveAccountKey(accountId));
25
+ }
26
+ /** Clear all registered gateways (for testing). */
27
+ export function clearGateways() {
28
+ gatewayRegistry.clear();
29
+ }
@@ -1,6 +1,6 @@
1
1
  import { Button, ChannelType, Command, Row, } from "@buape/carbon";
2
2
  import { ApplicationCommandOptionType, ButtonStyle } from "discord-api-types/v10";
3
- import { resolveEffectiveMessagesConfig, resolveHumanDelayConfig } from "../../agents/identity.js";
3
+ import { resolveHumanDelayConfig } from "../../agents/identity.js";
4
4
  import { resolveChunkMode, resolveTextChunkLimit } from "../../auto-reply/chunk.js";
5
5
  import { buildCommandTextFromArgs, findCommandByNativeName, listChatCommands, parseCommandArgs, resolveCommandArgChoices, resolveCommandArgMenu, serializeCommandArgs, } from "../../auto-reply/commands-registry.js";
6
6
  import { dispatchReplyWithDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
@@ -11,8 +11,10 @@ import { resolveAgentRoute } from "../../routing/resolve-route.js";
11
11
  import { loadWebMedia } from "../../web/media.js";
12
12
  import { chunkDiscordTextWithMode } from "../chunk.js";
13
13
  import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
14
- import { allowListMatches, isDiscordGroupAllowedByPolicy, normalizeDiscordAllowList, normalizeDiscordSlug, resolveDiscordChannelConfigWithFallback, resolveDiscordGuildEntry, resolveDiscordUserAllowed, } from "./allow-list.js";
15
- import { formatDiscordUserTag } from "./format.js";
14
+ import { createReplyPrefixContext } from "../../channels/reply-prefix.js";
15
+ import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
16
+ import { allowListMatches, isDiscordGroupAllowedByPolicy, normalizeDiscordAllowList, normalizeDiscordSlug, resolveDiscordChannelConfigWithFallback, resolveDiscordGuildEntry, resolveDiscordOwnerAllowFrom, resolveDiscordUserAllowed, } from "./allow-list.js";
17
+ import { resolveDiscordSenderIdentity } from "./sender-identity.js";
16
18
  import { resolveDiscordChannelInfo } from "./message-utils.js";
17
19
  import { resolveDiscordThreadParentInfo } from "./threading.js";
18
20
  function buildDiscordCommandOptions(params) {
@@ -357,6 +359,7 @@ async function dispatchDiscordCommandInteraction(params) {
357
359
  const user = interaction.user;
358
360
  if (!user)
359
361
  return;
362
+ const sender = resolveDiscordSenderIdentity({ author: user, pluralkitInfo: null });
360
363
  const channel = interaction.channel;
361
364
  const channelType = channel?.type;
362
365
  const isDirectMessage = channelType === ChannelType.DM;
@@ -370,12 +373,13 @@ async function dispatchDiscordCommandInteraction(params) {
370
373
  const ownerAllowList = normalizeDiscordAllowList(discordConfig?.dm?.allowFrom ?? [], [
371
374
  "discord:",
372
375
  "user:",
376
+ "pk:",
373
377
  ]);
374
378
  const ownerOk = ownerAllowList && user
375
379
  ? allowListMatches(ownerAllowList, {
376
- id: user.id,
377
- name: user.username,
378
- tag: formatDiscordUserTag(user),
380
+ id: sender.id,
381
+ name: sender.name,
382
+ tag: sender.tag,
379
383
  })
380
384
  : false;
381
385
  const guildInfo = resolveDiscordGuildEntry({
@@ -447,12 +451,12 @@ async function dispatchDiscordCommandInteraction(params) {
447
451
  if (dmPolicy !== "open") {
448
452
  const storeAllowFrom = await readChannelAllowFromStore("discord").catch(() => []);
449
453
  const effectiveAllowFrom = [...(discordConfig?.dm?.allowFrom ?? []), ...storeAllowFrom];
450
- const allowList = normalizeDiscordAllowList(effectiveAllowFrom, ["discord:", "user:"]);
454
+ const allowList = normalizeDiscordAllowList(effectiveAllowFrom, ["discord:", "user:", "pk:"]);
451
455
  const permitted = allowList
452
456
  ? allowListMatches(allowList, {
453
- id: user.id,
454
- name: user.username,
455
- tag: formatDiscordUserTag(user),
457
+ id: sender.id,
458
+ name: sender.name,
459
+ tag: sender.tag,
456
460
  })
457
461
  : false;
458
462
  if (!permitted) {
@@ -462,8 +466,8 @@ async function dispatchDiscordCommandInteraction(params) {
462
466
  channel: "discord",
463
467
  id: user.id,
464
468
  meta: {
465
- tag: formatDiscordUserTag(user),
466
- name: user.username ?? undefined,
469
+ tag: sender.tag,
470
+ name: sender.name ?? undefined,
467
471
  },
468
472
  });
469
473
  if (created) {
@@ -488,9 +492,9 @@ async function dispatchDiscordCommandInteraction(params) {
488
492
  const userOk = hasUserAllowlist
489
493
  ? resolveDiscordUserAllowed({
490
494
  allowList: channelUsers,
491
- userId: user.id,
492
- userName: user.username,
493
- userTag: formatDiscordUserTag(user),
495
+ userId: sender.id,
496
+ userName: sender.name,
497
+ userTag: sender.tag,
494
498
  })
495
499
  : false;
496
500
  const authorizers = useAccessGroups
@@ -555,10 +559,17 @@ async function dispatchDiscordCommandInteraction(params) {
555
559
  kind: isDirectMessage ? "dm" : isGroupDm ? "group" : "channel",
556
560
  id: isDirectMessage ? user.id : channelId,
557
561
  },
562
+ parentPeer: threadParentId ? { kind: "channel", id: threadParentId } : undefined,
558
563
  });
559
564
  const conversationLabel = isDirectMessage ? (user.globalName ?? user.username) : channelId;
565
+ const ownerAllowFrom = resolveDiscordOwnerAllowFrom({
566
+ channelConfig,
567
+ guildInfo,
568
+ sender: { id: sender.id, name: sender.name, tag: sender.tag },
569
+ });
560
570
  const ctxPayload = finalizeInboundContext({
561
571
  Body: prompt,
572
+ BodyForAgent: prompt,
562
573
  RawBody: prompt,
563
574
  CommandBody: prompt,
564
575
  CommandArgs: commandArgs,
@@ -576,19 +587,25 @@ async function dispatchDiscordCommandInteraction(params) {
576
587
  GroupSubject: isGuild ? interaction.guild?.name : undefined,
577
588
  GroupSystemPrompt: isGuild
578
589
  ? (() => {
579
- const channelTopic = channel && "topic" in channel ? (channel.topic ?? undefined) : undefined;
580
- const channelDescription = channelTopic?.trim();
581
- const systemPromptParts = [
582
- channelDescription ? `Channel topic: ${channelDescription}` : null,
583
- channelConfig?.systemPrompt?.trim() || null,
584
- ].filter((entry) => Boolean(entry));
590
+ const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter((entry) => Boolean(entry));
585
591
  return systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
586
592
  })()
587
593
  : undefined,
594
+ UntrustedContext: isGuild
595
+ ? (() => {
596
+ const channelTopic = channel && "topic" in channel ? (channel.topic ?? undefined) : undefined;
597
+ const untrustedChannelMetadata = buildUntrustedChannelMetadata({
598
+ source: "discord",
599
+ label: "Discord channel topic",
600
+ entries: [channelTopic],
601
+ });
602
+ return untrustedChannelMetadata ? [untrustedChannelMetadata] : undefined;
603
+ })()
604
+ : undefined,
588
605
  SenderName: user.globalName ?? user.username,
589
606
  SenderId: user.id,
590
607
  SenderUsername: user.username,
591
- SenderTag: formatDiscordUserTag(user),
608
+ SenderTag: sender.tag,
592
609
  Provider: "discord",
593
610
  Surface: "discord",
594
611
  WasMentioned: true,
@@ -596,13 +613,16 @@ async function dispatchDiscordCommandInteraction(params) {
596
613
  Timestamp: Date.now(),
597
614
  CommandAuthorized: commandAuthorized,
598
615
  CommandSource: "native",
616
+ OwnerAllowFrom: ownerAllowFrom,
599
617
  });
618
+ const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
600
619
  let didReply = false;
601
620
  await dispatchReplyWithDispatcher({
602
621
  ctx: ctxPayload,
603
622
  cfg,
604
623
  dispatcherOptions: {
605
- responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
624
+ responsePrefix: prefixContext.responsePrefix,
625
+ responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
606
626
  humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
607
627
  deliver: async (payload) => {
608
628
  try {
@@ -635,6 +655,7 @@ async function dispatchDiscordCommandInteraction(params) {
635
655
  disableBlockStreaming: typeof discordConfig?.blockStreaming === "boolean"
636
656
  ? !discordConfig.blockStreaming
637
657
  : undefined,
658
+ onModelSelected: prefixContext.onModelSelected,
638
659
  },
639
660
  });
640
661
  }
@@ -0,0 +1,45 @@
1
+ import { formatDiscordUserTag } from "./format.js";
2
+ export function resolveDiscordWebhookId(message) {
3
+ const candidate = message.webhookId ?? message.webhook_id;
4
+ return typeof candidate === "string" && candidate.trim() ? candidate.trim() : null;
5
+ }
6
+ export function resolveDiscordSenderIdentity(params) {
7
+ const pkInfo = params.pluralkitInfo ?? null;
8
+ const pkMember = pkInfo?.member ?? undefined;
9
+ const pkSystem = pkInfo?.system ?? undefined;
10
+ const memberId = pkMember?.id?.trim();
11
+ const memberNameRaw = pkMember?.display_name ?? pkMember?.name ?? "";
12
+ const memberName = memberNameRaw?.trim();
13
+ if (memberId && memberName) {
14
+ const systemName = pkSystem?.name?.trim();
15
+ const label = systemName ? `${memberName} (PK:${systemName})` : `${memberName} (PK)`;
16
+ return {
17
+ id: memberId,
18
+ name: memberName,
19
+ tag: pkMember?.name?.trim() || undefined,
20
+ label,
21
+ isPluralKit: true,
22
+ pluralkit: {
23
+ memberId,
24
+ memberName,
25
+ systemId: pkSystem?.id?.trim() || undefined,
26
+ systemName,
27
+ },
28
+ };
29
+ }
30
+ const senderTag = formatDiscordUserTag(params.author);
31
+ const senderDisplay = params.member?.nickname ?? params.author.globalName ?? params.author.username;
32
+ const senderLabel = senderDisplay && senderTag && senderDisplay !== senderTag
33
+ ? `${senderDisplay} (${senderTag})`
34
+ : (senderDisplay ?? senderTag ?? params.author.id);
35
+ return {
36
+ id: params.author.id,
37
+ name: params.author.username ?? undefined,
38
+ tag: senderTag,
39
+ label: senderLabel,
40
+ isPluralKit: false,
41
+ };
42
+ }
43
+ export function resolveDiscordSenderLabel(params) {
44
+ return resolveDiscordSenderIdentity(params).label;
45
+ }
@@ -0,0 +1,27 @@
1
+ import { resolveFetch } from "../infra/fetch.js";
2
+ const PLURALKIT_API_BASE = "https://api.pluralkit.me/v2";
3
+ export async function fetchPluralKitMessageInfo(params) {
4
+ if (!params.config?.enabled) {
5
+ return null;
6
+ }
7
+ const fetchImpl = resolveFetch(params.fetcher);
8
+ if (!fetchImpl) {
9
+ return null;
10
+ }
11
+ const headers = {};
12
+ if (params.config.token?.trim()) {
13
+ headers.Authorization = params.config.token.trim();
14
+ }
15
+ const res = await fetchImpl(`${PLURALKIT_API_BASE}/messages/${params.messageId}`, {
16
+ headers,
17
+ });
18
+ if (res.status === 404) {
19
+ return null;
20
+ }
21
+ if (!res.ok) {
22
+ const text = await res.text().catch(() => "");
23
+ const detail = text.trim() ? `: ${text.trim()}` : "";
24
+ throw new Error(`PluralKit API failed (${res.status})${detail}`);
25
+ }
26
+ return (await res.json());
27
+ }
@@ -1,11 +1,25 @@
1
- import { Routes } from "discord-api-types/v10";
1
+ import { ChannelType, Routes } from "discord-api-types/v10";
2
2
  import { resolveChunkMode } from "../auto-reply/chunk.js";
3
3
  import { loadConfig } from "../config/config.js";
4
4
  import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
5
5
  import { recordChannelActivity } from "../infra/channel-activity.js";
6
6
  import { convertMarkdownTables } from "../markdown/tables.js";
7
7
  import { resolveDiscordAccount } from "./accounts.js";
8
- import { buildDiscordSendError, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds, parseRecipient, resolveChannelId, sendDiscordMedia, sendDiscordText, } from "./send.shared.js";
8
+ import { buildDiscordSendError, buildDiscordTextChunks, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds, parseAndResolveRecipient, resolveChannelId, sendDiscordMedia, sendDiscordText, } from "./send.shared.js";
9
+ /** Discord thread names are capped at 100 characters. */
10
+ const DISCORD_THREAD_NAME_LIMIT = 100;
11
+ /** Derive a thread title from the first non-empty line of the message text. */
12
+ function deriveForumThreadName(text) {
13
+ const firstLine = text
14
+ .split("\n")
15
+ .find((l) => l.trim())
16
+ ?.trim() ?? "";
17
+ return firstLine.slice(0, DISCORD_THREAD_NAME_LIMIT) || new Date().toISOString().slice(0, 16);
18
+ }
19
+ /** Forum/Media channels cannot receive regular messages; detect them here. */
20
+ function isForumLikeType(channelType) {
21
+ return channelType === ChannelType.GuildForum || channelType === ChannelType.GuildMedia;
22
+ }
9
23
  export async function sendMessageDiscord(to, text, opts = {}) {
10
24
  const cfg = loadConfig();
11
25
  const accountInfo = resolveDiscordAccount({
@@ -20,8 +34,81 @@ export async function sendMessageDiscord(to, text, opts = {}) {
20
34
  const chunkMode = resolveChunkMode(cfg, "discord", accountInfo.accountId);
21
35
  const textWithTables = convertMarkdownTables(text ?? "", tableMode);
22
36
  const { token, rest, request } = createDiscordClient(opts, cfg);
23
- const recipient = parseRecipient(to);
37
+ const recipient = await parseAndResolveRecipient(to, opts.accountId);
24
38
  const { channelId } = await resolveChannelId(rest, recipient, request);
39
+ // Forum/Media channels reject POST /messages; auto-create a thread post instead.
40
+ let channelType;
41
+ try {
42
+ const channel = (await rest.get(Routes.channel(channelId)));
43
+ channelType = channel?.type;
44
+ }
45
+ catch {
46
+ // If we can't fetch the channel, fall through to the normal send path.
47
+ }
48
+ if (isForumLikeType(channelType)) {
49
+ const threadName = deriveForumThreadName(textWithTables);
50
+ const chunks = buildDiscordTextChunks(textWithTables, {
51
+ maxLinesPerMessage: accountInfo.config.maxLinesPerMessage,
52
+ chunkMode,
53
+ });
54
+ const starterContent = chunks[0]?.trim() ? chunks[0] : threadName;
55
+ const starterEmbeds = opts.embeds?.length ? opts.embeds : undefined;
56
+ let threadRes;
57
+ try {
58
+ threadRes = (await request(() => rest.post(Routes.threads(channelId), {
59
+ body: {
60
+ name: threadName,
61
+ message: {
62
+ content: starterContent,
63
+ ...(starterEmbeds ? { embeds: starterEmbeds } : {}),
64
+ },
65
+ },
66
+ }), "forum-thread"));
67
+ }
68
+ catch (err) {
69
+ throw await buildDiscordSendError(err, {
70
+ channelId,
71
+ rest,
72
+ token,
73
+ hasMedia: Boolean(opts.mediaUrl),
74
+ });
75
+ }
76
+ const threadId = threadRes.id;
77
+ const messageId = threadRes.message?.id ?? threadId;
78
+ const resultChannelId = threadRes.message?.channel_id ?? threadId;
79
+ const remainingChunks = chunks.slice(1);
80
+ try {
81
+ if (opts.mediaUrl) {
82
+ const [mediaCaption, ...afterMediaChunks] = remainingChunks;
83
+ await sendDiscordMedia(rest, threadId, mediaCaption ?? "", opts.mediaUrl, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
84
+ for (const chunk of afterMediaChunks) {
85
+ await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
86
+ }
87
+ }
88
+ else {
89
+ for (const chunk of remainingChunks) {
90
+ await sendDiscordText(rest, threadId, chunk, undefined, request, accountInfo.config.maxLinesPerMessage, undefined, chunkMode);
91
+ }
92
+ }
93
+ }
94
+ catch (err) {
95
+ throw await buildDiscordSendError(err, {
96
+ channelId: threadId,
97
+ rest,
98
+ token,
99
+ hasMedia: Boolean(opts.mediaUrl),
100
+ });
101
+ }
102
+ recordChannelActivity({
103
+ channel: "discord",
104
+ accountId: accountInfo.accountId,
105
+ direction: "outbound",
106
+ });
107
+ return {
108
+ messageId: messageId ? String(messageId) : "unknown",
109
+ channelId: String(resultChannelId ?? channelId),
110
+ };
111
+ }
25
112
  let result;
26
113
  try {
27
114
  if (opts.mediaUrl) {
@@ -52,7 +139,7 @@ export async function sendMessageDiscord(to, text, opts = {}) {
52
139
  export async function sendStickerDiscord(to, stickerIds, opts = {}) {
53
140
  const cfg = loadConfig();
54
141
  const { rest, request } = createDiscordClient(opts, cfg);
55
- const recipient = parseRecipient(to);
142
+ const recipient = await parseAndResolveRecipient(to, opts.accountId);
56
143
  const { channelId } = await resolveChannelId(rest, recipient, request);
57
144
  const content = opts.content?.trim();
58
145
  const stickers = normalizeStickerIds(stickerIds);
@@ -70,7 +157,7 @@ export async function sendStickerDiscord(to, stickerIds, opts = {}) {
70
157
  export async function sendPollDiscord(to, poll, opts = {}) {
71
158
  const cfg = loadConfig();
72
159
  const { rest, request } = createDiscordClient(opts, cfg);
73
- const recipient = parseRecipient(to);
160
+ const recipient = await parseAndResolveRecipient(to, opts.accountId);
74
161
  const { channelId } = await resolveChannelId(rest, recipient, request);
75
162
  const content = opts.content?.trim();
76
163
  const payload = normalizeDiscordPollInput(poll);
@@ -9,7 +9,7 @@ import { resolveDiscordAccount } from "./accounts.js";
9
9
  import { chunkDiscordTextWithMode } from "./chunk.js";
10
10
  import { fetchChannelPermissionsDiscord, isThreadChannelType } from "./send.permissions.js";
11
11
  import { DiscordSendError } from "./send.types.js";
12
- import { parseDiscordTarget } from "./targets.js";
12
+ import { parseDiscordTarget, resolveDiscordTarget } from "./targets.js";
13
13
  import { normalizeDiscordToken } from "./token.js";
14
14
  const DISCORD_TEXT_LIMIT = 2000;
15
15
  const DISCORD_MAX_STICKERS = 3;
@@ -19,8 +19,9 @@ const DISCORD_MISSING_PERMISSIONS = 50013;
19
19
  const DISCORD_CANNOT_DM = 50007;
20
20
  function resolveToken(params) {
21
21
  const explicit = normalizeDiscordToken(params.explicit);
22
- if (explicit)
22
+ if (explicit) {
23
23
  return explicit;
24
+ }
24
25
  const fallback = normalizeDiscordToken(params.fallbackToken);
25
26
  if (!fallback) {
26
27
  throw new Error(`Discord bot token missing for account "${params.accountId}" (set discord.accounts.${params.accountId}.token or DISCORD_BOT_TOKEN for default).`);
@@ -68,6 +69,37 @@ function parseRecipient(raw) {
68
69
  }
69
70
  return { kind: target.kind, id: target.id };
70
71
  }
72
+ /**
73
+ * Parse and resolve Discord recipient, including username lookup.
74
+ * This enables sending DMs by username (e.g., "john.doe") by querying
75
+ * the Discord directory to resolve usernames to user IDs.
76
+ *
77
+ * @param raw - The recipient string (username, ID, or known format)
78
+ * @param accountId - Discord account ID to use for directory lookup
79
+ * @returns Parsed DiscordRecipient with resolved user ID if applicable
80
+ */
81
+ export async function parseAndResolveRecipient(raw, accountId) {
82
+ const cfg = loadConfig();
83
+ const accountInfo = resolveDiscordAccount({ cfg, accountId });
84
+ // First try to resolve using directory lookup (handles usernames)
85
+ const trimmed = raw.trim();
86
+ const parseOptions = {
87
+ ambiguousMessage: `Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
88
+ };
89
+ const resolved = await resolveDiscordTarget(raw, {
90
+ cfg,
91
+ accountId: accountInfo.accountId,
92
+ }, parseOptions);
93
+ if (resolved) {
94
+ return { kind: resolved.kind, id: resolved.id };
95
+ }
96
+ // Fallback to standard parsing (for channels, etc.)
97
+ const parsed = parseDiscordTarget(raw, parseOptions);
98
+ if (!parsed) {
99
+ throw new Error("Recipient is required for Discord sends");
100
+ }
101
+ return { kind: parsed.kind, id: parsed.id };
102
+ }
71
103
  function normalizeStickerIds(raw) {
72
104
  const ids = raw.map((entry) => entry.trim()).filter(Boolean);
73
105
  if (ids.length === 0) {
@@ -102,29 +134,33 @@ function normalizeDiscordPollInput(input) {
102
134
  };
103
135
  }
104
136
  function getDiscordErrorCode(err) {
105
- if (!err || typeof err !== "object")
137
+ if (!err || typeof err !== "object") {
106
138
  return undefined;
139
+ }
107
140
  const candidate = "code" in err && err.code !== undefined
108
141
  ? err.code
109
142
  : "rawError" in err && err.rawError && typeof err.rawError === "object"
110
143
  ? err.rawError.code
111
144
  : undefined;
112
- if (typeof candidate === "number")
145
+ if (typeof candidate === "number") {
113
146
  return candidate;
147
+ }
114
148
  if (typeof candidate === "string" && /^\d+$/.test(candidate)) {
115
149
  return Number(candidate);
116
150
  }
117
151
  return undefined;
118
152
  }
119
153
  async function buildDiscordSendError(err, ctx) {
120
- if (err instanceof DiscordSendError)
154
+ if (err instanceof DiscordSendError) {
121
155
  return err;
156
+ }
122
157
  const code = getDiscordErrorCode(err);
123
158
  if (code === DISCORD_CANNOT_DM) {
124
159
  return new DiscordSendError("discord dm failed: user blocks dms or privacy settings disallow it", { kind: "dm-blocked" });
125
160
  }
126
- if (code !== DISCORD_MISSING_PERMISSIONS)
161
+ if (code !== DISCORD_MISSING_PERMISSIONS) {
127
162
  return err;
163
+ }
128
164
  let missing = [];
129
165
  try {
130
166
  const permissions = await fetchChannelPermissionsDiscord(ctx.channelId, {
@@ -165,18 +201,26 @@ async function resolveChannelId(rest, recipient, request) {
165
201
  }
166
202
  return { channelId: dmChannel.id, dm: true };
167
203
  }
204
+ export function buildDiscordTextChunks(text, opts = {}) {
205
+ if (!text) {
206
+ return [];
207
+ }
208
+ const chunks = chunkDiscordTextWithMode(text, {
209
+ maxChars: opts.maxChars ?? DISCORD_TEXT_LIMIT,
210
+ maxLines: opts.maxLinesPerMessage,
211
+ chunkMode: opts.chunkMode,
212
+ });
213
+ if (!chunks.length && text) {
214
+ chunks.push(text);
215
+ }
216
+ return chunks;
217
+ }
168
218
  async function sendDiscordText(rest, channelId, text, replyTo, request, maxLinesPerMessage, embeds, chunkMode) {
169
219
  if (!text.trim()) {
170
220
  throw new Error("Message must be non-empty for Discord sends");
171
221
  }
172
222
  const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined;
173
- const chunks = chunkDiscordTextWithMode(text, {
174
- maxChars: DISCORD_TEXT_LIMIT,
175
- maxLines: maxLinesPerMessage,
176
- chunkMode,
177
- });
178
- if (!chunks.length && text)
179
- chunks.push(text);
223
+ const chunks = buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode });
180
224
  if (chunks.length === 1) {
181
225
  const res = (await request(() => rest.post(Routes.channelMessages(channelId), {
182
226
  body: {
@@ -206,15 +250,7 @@ async function sendDiscordText(rest, channelId, text, replyTo, request, maxLines
206
250
  }
207
251
  async function sendDiscordMedia(rest, channelId, text, mediaUrl, replyTo, request, maxLinesPerMessage, embeds, chunkMode) {
208
252
  const media = await loadWebMedia(mediaUrl);
209
- const chunks = text
210
- ? chunkDiscordTextWithMode(text, {
211
- maxChars: DISCORD_TEXT_LIMIT,
212
- maxLines: maxLinesPerMessage,
213
- chunkMode,
214
- })
215
- : [];
216
- if (!chunks.length && text)
217
- chunks.push(text);
253
+ const chunks = text ? buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode }) : [];
218
254
  const caption = chunks[0] ?? "";
219
255
  const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined;
220
256
  const res = (await request(() => rest.post(Routes.channelMessages(channelId), {
@@ -231,8 +267,9 @@ async function sendDiscordMedia(rest, channelId, text, mediaUrl, replyTo, reques
231
267
  },
232
268
  }), "media"));
233
269
  for (const chunk of chunks.slice(1)) {
234
- if (!chunk.trim())
270
+ if (!chunk.trim()) {
235
271
  continue;
272
+ }
236
273
  await sendDiscordText(rest, channelId, chunk, undefined, request, maxLinesPerMessage, undefined, chunkMode);
237
274
  }
238
275
  return res;