@poolzin/pool-bot 2026.2.25 → 2026.2.26

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 (506) hide show
  1. package/dist/acp/event-mapper.js +87 -22
  2. package/dist/acp/meta.js +12 -6
  3. package/dist/agents/agent-paths.js +8 -9
  4. package/dist/agents/agent-scope.js +7 -5
  5. package/dist/agents/auth-profiles/oauth.js +148 -64
  6. package/dist/agents/auth-profiles/session-override.js +13 -7
  7. package/dist/agents/bash-tools.exec-host-gateway.js +14 -4
  8. package/dist/agents/bash-tools.exec-runtime.js +2 -25
  9. package/dist/agents/bedrock-discovery.js +3 -1
  10. package/dist/agents/byteplus-models.js +97 -0
  11. package/dist/agents/chutes-oauth.js +1 -0
  12. package/dist/agents/cli-runner/helpers.js +4 -0
  13. package/dist/agents/compaction.js +41 -14
  14. package/dist/agents/doubao-models.js +121 -0
  15. package/dist/agents/failover-error.js +2 -0
  16. package/dist/agents/huggingface-models.js +5 -3
  17. package/dist/agents/live-model-filter.js +5 -0
  18. package/dist/agents/minimax-vlm.js +10 -8
  19. package/dist/agents/model-auth.js +6 -0
  20. package/dist/agents/model-catalog.js +3 -1
  21. package/dist/agents/model-selection.js +7 -1
  22. package/dist/agents/models-config.providers.js +93 -11
  23. package/dist/agents/ollama-stream.js +117 -4
  24. package/dist/agents/opencode-zen-models.js +22 -11
  25. package/dist/agents/pi-embedded-helpers/errors.js +55 -33
  26. package/dist/agents/pi-embedded-helpers/messaging-dedupe.js +10 -5
  27. package/dist/agents/pi-embedded-helpers/thinking.js +10 -5
  28. package/dist/agents/pi-embedded-helpers.js +1 -1
  29. package/dist/agents/pi-embedded-runner/compact.js +29 -7
  30. package/dist/agents/pi-embedded-runner/extensions.js +28 -26
  31. package/dist/agents/pi-embedded-runner/google.js +20 -8
  32. package/dist/agents/pi-embedded-runner/run/attempt.js +95 -36
  33. package/dist/agents/pi-embedded-runner/run.js +71 -12
  34. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +11 -2
  35. package/dist/agents/pi-embedded-runner/session-manager-cache.js +11 -7
  36. package/dist/agents/pi-embedded-runner/system-prompt.js +2 -0
  37. package/dist/agents/pi-embedded-runner/thinking.js +42 -0
  38. package/dist/agents/pi-embedded-runner/tool-name-allowlist.js +19 -0
  39. package/dist/agents/pi-embedded-runner/utils.js +7 -10
  40. package/dist/agents/pi-embedded-subscribe.handlers.lifecycle.js +45 -56
  41. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +2 -2
  42. package/dist/agents/pi-embedded-subscribe.js +9 -4
  43. package/dist/agents/pi-embedded-subscribe.tools.js +68 -14
  44. package/dist/agents/pi-embedded-utils.js +3 -0
  45. package/dist/agents/pi-extensions/compaction-safeguard-runtime.js +4 -20
  46. package/dist/agents/pi-extensions/compaction-safeguard.js +75 -33
  47. package/dist/agents/pi-settings.js +40 -0
  48. package/dist/agents/pi-tools.policy.js +2 -1
  49. package/dist/agents/provider/config-loader.js +1 -1
  50. package/dist/agents/sandbox/browser.js +170 -33
  51. package/dist/agents/sandbox/config-hash.js +14 -27
  52. package/dist/agents/sandbox/config.js +21 -2
  53. package/dist/agents/sandbox/constants.js +2 -0
  54. package/dist/agents/sandbox/docker.js +16 -2
  55. package/dist/agents/sandbox/novnc-auth.js +62 -0
  56. package/dist/agents/sandbox/sanitize-env-vars.js +1 -1
  57. package/dist/agents/sandbox/shared.js +10 -6
  58. package/dist/agents/sandbox-paths.js +24 -11
  59. package/dist/agents/schema/clean-for-gemini.js +132 -85
  60. package/dist/agents/session-slug.js +10 -5
  61. package/dist/agents/session-tool-result-guard-wrapper.js +1 -0
  62. package/dist/agents/session-tool-result-guard.js +3 -1
  63. package/dist/agents/session-transcript-repair.js +40 -6
  64. package/dist/agents/skills/bundled-dir.js +19 -5
  65. package/dist/agents/skills/env-overrides.js +124 -43
  66. package/dist/agents/skills/frontmatter.js +6 -6
  67. package/dist/agents/skills/plugin-skills.js +14 -7
  68. package/dist/agents/skills/workspace.js +1 -0
  69. package/dist/agents/subagent-announce.js +251 -49
  70. package/dist/agents/subagent-lifecycle-events.js +19 -0
  71. package/dist/agents/subagent-registry-cleanup.js +31 -0
  72. package/dist/agents/subagent-registry-completion.js +68 -0
  73. package/dist/agents/subagent-registry-queries.js +117 -0
  74. package/dist/agents/subagent-registry-state.js +46 -0
  75. package/dist/agents/subagent-registry.js +252 -221
  76. package/dist/agents/subagent-registry.store.js +1 -0
  77. package/dist/agents/subagent-registry.types.js +1 -0
  78. package/dist/agents/subagent-spawn.js +195 -7
  79. package/dist/agents/system-prompt.js +22 -6
  80. package/dist/agents/test-helpers/fast-coding-tools.js +1 -18
  81. package/dist/agents/test-helpers/fast-core-tools.js +1 -17
  82. package/dist/agents/timeout.js +18 -6
  83. package/dist/agents/tool-call-id.js +1 -1
  84. package/dist/agents/tool-display-common.js +162 -29
  85. package/dist/agents/tool-images.js +82 -9
  86. package/dist/agents/tool-policy.js +51 -26
  87. package/dist/agents/tools/browser-tool.js +2 -2
  88. package/dist/agents/tools/canvas-tool.js +27 -1
  89. package/dist/agents/tools/common.js +45 -0
  90. package/dist/agents/tools/discord-actions-guild.js +4 -1
  91. package/dist/agents/tools/gateway-tool.js +3 -1
  92. package/dist/agents/tools/nodes-utils.js +1 -10
  93. package/dist/agents/tools/sessions-send-helpers.js +12 -6
  94. package/dist/agents/tools/sessions-spawn-tool.js +8 -2
  95. package/dist/agents/tools/subagents-tool.js +2 -1
  96. package/dist/agents/tools/whatsapp-actions.js +10 -2
  97. package/dist/agents/tools/whatsapp-target-auth.js +18 -0
  98. package/dist/agents/transcript-policy.js +22 -8
  99. package/dist/agents/venice-models.js +11 -3
  100. package/dist/auto-reply/commands-registry.data.js +51 -0
  101. package/dist/auto-reply/commands-registry.js +4 -3
  102. package/dist/auto-reply/group-activation.js +10 -5
  103. package/dist/auto-reply/inbound-debounce.js +10 -5
  104. package/dist/auto-reply/reply/abort.js +1 -1
  105. package/dist/auto-reply/reply/agent-runner-execution.js +4 -1
  106. package/dist/auto-reply/reply/bash-command.js +41 -39
  107. package/dist/auto-reply/reply/command-gates.js +25 -0
  108. package/dist/auto-reply/reply/commands-allowlist.js +111 -72
  109. package/dist/auto-reply/reply/commands-bash.js +6 -5
  110. package/dist/auto-reply/reply/commands-config.js +30 -28
  111. package/dist/auto-reply/reply/commands-core.js +2 -1
  112. package/dist/auto-reply/reply/commands-info.js +1 -0
  113. package/dist/auto-reply/reply/commands-models.js +65 -14
  114. package/dist/auto-reply/reply/commands-session.js +237 -82
  115. package/dist/auto-reply/reply/commands-setunset.js +45 -0
  116. package/dist/auto-reply/reply/commands-subagents/action-agents.js +44 -0
  117. package/dist/auto-reply/reply/commands-subagents/action-focus.js +64 -0
  118. package/dist/auto-reply/reply/commands-subagents/action-help.js +4 -0
  119. package/dist/auto-reply/reply/commands-subagents/action-info.js +45 -0
  120. package/dist/auto-reply/reply/commands-subagents/action-kill.js +60 -0
  121. package/dist/auto-reply/reply/commands-subagents/action-list.js +44 -0
  122. package/dist/auto-reply/reply/commands-subagents/action-log.js +29 -0
  123. package/dist/auto-reply/reply/commands-subagents/action-send.js +119 -0
  124. package/dist/auto-reply/reply/commands-subagents/action-spawn.js +52 -0
  125. package/dist/auto-reply/reply/commands-subagents/action-unfocus.js +30 -0
  126. package/dist/auto-reply/reply/commands-subagents/shared.js +303 -0
  127. package/dist/auto-reply/reply/commands-subagents.js +51 -587
  128. package/dist/auto-reply/reply/commands-tts.js +10 -5
  129. package/dist/auto-reply/reply/config-value.js +10 -5
  130. package/dist/auto-reply/reply/directive-handling.model-picker.js +12 -6
  131. package/dist/auto-reply/reply/directive-handling.persist.js +9 -21
  132. package/dist/auto-reply/reply/directive-handling.shared.js +24 -4
  133. package/dist/auto-reply/reply/followup-runner.js +1 -0
  134. package/dist/auto-reply/reply/get-reply-directives-utils.js +23 -14
  135. package/dist/auto-reply/reply/get-reply-directives.js +17 -28
  136. package/dist/auto-reply/reply/get-reply-inline-actions.js +1 -0
  137. package/dist/auto-reply/reply/get-reply.js +71 -12
  138. package/dist/auto-reply/reply/model-selection.js +80 -39
  139. package/dist/auto-reply/reply/queue/enqueue.js +10 -5
  140. package/dist/auto-reply/reply/queue/state.js +13 -12
  141. package/dist/auto-reply/reply/reply-payloads.js +67 -36
  142. package/dist/auto-reply/reply/reply-reference.js +9 -8
  143. package/dist/auto-reply/reply/route-reply.js +15 -8
  144. package/dist/auto-reply/reply/session-reset-prompt.js +1 -1
  145. package/dist/auto-reply/reply/session.js +22 -6
  146. package/dist/auto-reply/reply/strip-inbound-meta.js +147 -0
  147. package/dist/auto-reply/reply/subagents-utils.js +56 -30
  148. package/dist/auto-reply/reply/typing.js +46 -21
  149. package/dist/auto-reply/send-policy.js +14 -7
  150. package/dist/auto-reply/status.js +140 -16
  151. package/dist/auto-reply/templating.js +10 -5
  152. package/dist/auto-reply/thinking.js +7 -16
  153. package/dist/auto-reply/tokens.js +21 -5
  154. package/dist/browser/bridge-server.js +36 -20
  155. package/dist/browser/cdp.helpers.js +7 -14
  156. package/dist/browser/cdp.js +35 -15
  157. package/dist/browser/chrome.profile-decoration.js +7 -4
  158. package/dist/browser/config.js +4 -0
  159. package/dist/browser/extension-relay-auth.js +55 -0
  160. package/dist/browser/extension-relay.js +74 -29
  161. package/dist/browser/navigation-guard.js +9 -1
  162. package/dist/browser/paths.js +77 -0
  163. package/dist/browser/profiles.js +13 -8
  164. package/dist/browser/pw-ai-module.js +10 -5
  165. package/dist/browser/pw-session.js +76 -39
  166. package/dist/browser/pw-tools-core.interactions.js +14 -7
  167. package/dist/browser/pw-tools-core.state.js +12 -6
  168. package/dist/browser/routes/agent.act.js +2 -2
  169. package/dist/browser/server-context.js +7 -0
  170. package/dist/build-info.json +3 -3
  171. package/dist/channels/allow-from.js +2 -1
  172. package/dist/channels/allowlists/resolve-utils.js +43 -19
  173. package/dist/channels/channel-config.js +14 -7
  174. package/dist/channels/draft-stream-loop.js +7 -0
  175. package/dist/channels/model-overrides.js +82 -0
  176. package/dist/channels/plugins/normalize/imessage.js +14 -7
  177. package/dist/channels/plugins/normalize/slack.js +10 -5
  178. package/dist/channels/plugins/normalize/telegram.js +14 -7
  179. package/dist/channels/plugins/outbound/discord.js +80 -8
  180. package/dist/channels/plugins/outbound/signal.js +11 -11
  181. package/dist/channels/plugins/setup-helpers.js +10 -5
  182. package/dist/channels/sender-label.js +14 -7
  183. package/dist/channels/session.js +4 -2
  184. package/dist/channels/status-reactions.js +297 -0
  185. package/dist/cli/banner.js +1 -1
  186. package/dist/cli/browser-cli-actions-input/register.files-downloads.js +65 -56
  187. package/dist/cli/cli-name.js +11 -11
  188. package/dist/cli/cli-utils.js +13 -3
  189. package/dist/cli/command-format.js +1 -1
  190. package/dist/cli/config-cli.js +1 -1
  191. package/dist/cli/daemon-cli/lifecycle-core.js +31 -19
  192. package/dist/cli/daemon-cli/lifecycle.js +64 -2
  193. package/dist/cli/daemon-cli/restart-health.js +126 -0
  194. package/dist/cli/daemon-cli/status.gather.js +9 -13
  195. package/dist/cli/daemon-cli/status.print.js +2 -10
  196. package/dist/cli/deps.js +27 -22
  197. package/dist/cli/gateway-cli/run-loop.js +23 -5
  198. package/dist/cli/node-cli/register.js +14 -5
  199. package/dist/cli/nodes-media-utils.js +7 -2
  200. package/dist/cli/outbound-send-deps.js +2 -9
  201. package/dist/cli/outbound-send-mapping.js +11 -0
  202. package/dist/cli/pairing-cli.js +40 -14
  203. package/dist/cli/plugins-cli.js +34 -41
  204. package/dist/cli/ports.js +11 -10
  205. package/dist/cli/program/command-registry.js +2 -11
  206. package/dist/cli/program/command-tree.js +16 -0
  207. package/dist/cli/program/preaction.js +13 -9
  208. package/dist/cli/program/register.configure.js +3 -18
  209. package/dist/cli/program/register.maintenance.js +2 -2
  210. package/dist/cli/program/register.onboard.js +2 -0
  211. package/dist/cli/program/register.status-health-sessions.js +16 -17
  212. package/dist/cli/program/register.subclis.js +93 -52
  213. package/dist/cli/route.js +11 -7
  214. package/dist/cli/system-cli.js +36 -46
  215. package/dist/cli/update-cli/shared.js +22 -9
  216. package/dist/cli/update-cli/update-command.js +89 -14
  217. package/dist/cli/update-cli/wizard.js +6 -12
  218. package/dist/commands/agent/run-context.js +18 -5
  219. package/dist/commands/agent/session-store.js +17 -4
  220. package/dist/commands/agent.js +22 -2
  221. package/dist/commands/agents.bindings.js +14 -7
  222. package/dist/commands/agents.commands.add.js +13 -9
  223. package/dist/commands/agents.commands.identity.js +12 -6
  224. package/dist/commands/agents.commands.list.js +11 -6
  225. package/dist/commands/agents.config.js +8 -10
  226. package/dist/commands/agents.providers.js +12 -6
  227. package/dist/commands/auth-choice-options.js +103 -75
  228. package/dist/commands/auth-choice.apply.byteplus.js +55 -0
  229. package/dist/commands/auth-choice.apply.js +4 -0
  230. package/dist/commands/auth-choice.apply.minimax.js +61 -13
  231. package/dist/commands/auth-choice.apply.openai.js +3 -1
  232. package/dist/commands/auth-choice.apply.volcengine.js +55 -0
  233. package/dist/commands/auth-choice.preferred-provider.js +2 -0
  234. package/dist/commands/channels/remove.js +13 -6
  235. package/dist/commands/channels/shared.js +4 -14
  236. package/dist/commands/configure.commands.js +14 -0
  237. package/dist/commands/configure.gateway.js +2 -4
  238. package/dist/commands/configure.js +1 -1
  239. package/dist/commands/configure.shared.js +11 -0
  240. package/dist/commands/daemon-install-helpers.js +2 -2
  241. package/dist/commands/dashboard.js +12 -10
  242. package/dist/commands/docs.js +14 -8
  243. package/dist/commands/doctor-config-flow.js +11 -9
  244. package/dist/commands/doctor-legacy-config.js +281 -0
  245. package/dist/commands/doctor-state-integrity.js +99 -23
  246. package/dist/commands/doctor-update.js +12 -9
  247. package/dist/commands/models/list.list-command.js +7 -5
  248. package/dist/commands/models/set-image.js +2 -21
  249. package/dist/commands/node-daemon-install-helpers.js +10 -8
  250. package/dist/commands/onboard-auth.config-minimax.js +54 -80
  251. package/dist/commands/onboard-auth.config-opencode.js +2 -18
  252. package/dist/commands/onboard-auth.credentials.js +90 -13
  253. package/dist/commands/onboard-auth.js +1 -1
  254. package/dist/commands/onboard-auth.models.js +6 -5
  255. package/dist/commands/onboard-hooks.js +1 -1
  256. package/dist/commands/onboard-non-interactive/api-keys.js +14 -7
  257. package/dist/commands/onboard-non-interactive/local/auth-choice.js +64 -49
  258. package/dist/commands/onboard-provider-auth-flags.js +14 -0
  259. package/dist/commands/onboard-remote.js +14 -7
  260. package/dist/commands/onboard.js +11 -13
  261. package/dist/commands/sandbox-display.js +6 -5
  262. package/dist/commands/status-all/diagnosis.js +14 -10
  263. package/dist/commands/status-all/format.js +1 -0
  264. package/dist/commands/status.gateway-probe.js +1 -16
  265. package/dist/commands/systemd-linger.js +12 -6
  266. package/dist/config/agent-limits.js +2 -0
  267. package/dist/config/commands.js +30 -16
  268. package/dist/config/config-paths.js +9 -11
  269. package/dist/config/defaults.js +22 -2
  270. package/dist/config/discord-preview-streaming.js +104 -0
  271. package/dist/config/env-vars.js +37 -8
  272. package/dist/config/includes.js +4 -0
  273. package/dist/config/io.js +97 -12
  274. package/dist/config/legacy.migrations.part-1.js +189 -78
  275. package/dist/config/legacy.shared.js +3 -1
  276. package/dist/config/merge-patch.js +4 -0
  277. package/dist/config/prototype-keys.js +4 -0
  278. package/dist/config/schema.help.js +44 -7
  279. package/dist/config/schema.labels.js +38 -6
  280. package/dist/config/sessions/delivery-info.js +10 -3
  281. package/dist/config/sessions/main-session.js +10 -5
  282. package/dist/config/sessions/session-file.js +33 -0
  283. package/dist/config/sessions/session-key.js +10 -5
  284. package/dist/config/sessions/store.js +1 -1
  285. package/dist/config/sessions.js +1 -0
  286. package/dist/config/zod-schema.agent-runtime.js +11 -0
  287. package/dist/config/zod-schema.js +148 -13
  288. package/dist/config/zod-schema.providers-core.js +78 -4
  289. package/dist/config/zod-schema.providers.js +6 -1
  290. package/dist/config/zod-schema.session.js +41 -2
  291. package/dist/cron/run-log.js +3 -0
  292. package/dist/cron/schedule.js +21 -10
  293. package/dist/cron/service/ops.js +35 -21
  294. package/dist/cron/service/timer.js +116 -16
  295. package/dist/cron/stagger.js +3 -1
  296. package/dist/discord/api.js +12 -6
  297. package/dist/discord/draft-chunking.js +22 -0
  298. package/dist/discord/draft-stream.js +124 -0
  299. package/dist/discord/monitor/agent-components.js +1 -1
  300. package/dist/discord/monitor/commands.js +5 -0
  301. package/dist/discord/monitor/gateway-plugin.js +2 -1
  302. package/dist/discord/monitor/listeners.js +37 -27
  303. package/dist/discord/monitor/message-handler.js +4 -1
  304. package/dist/discord/monitor/message-handler.preflight.js +65 -8
  305. package/dist/discord/monitor/message-handler.process.js +246 -217
  306. package/dist/discord/monitor/message-utils.js +143 -6
  307. package/dist/discord/monitor/model-picker-preferences.js +143 -0
  308. package/dist/discord/monitor/model-picker.js +651 -0
  309. package/dist/discord/monitor/native-command.js +573 -16
  310. package/dist/discord/monitor/provider.allowlist.js +223 -0
  311. package/dist/discord/monitor/provider.js +275 -347
  312. package/dist/discord/monitor/provider.lifecycle.js +100 -0
  313. package/dist/discord/monitor/reply-delivery.js +123 -16
  314. package/dist/discord/monitor/thread-bindings.discord-api.js +215 -0
  315. package/dist/discord/monitor/thread-bindings.js +4 -0
  316. package/dist/discord/monitor/thread-bindings.lifecycle.js +177 -0
  317. package/dist/discord/monitor/thread-bindings.manager.js +423 -0
  318. package/dist/discord/monitor/thread-bindings.messages.js +55 -0
  319. package/dist/discord/monitor/thread-bindings.state.js +358 -0
  320. package/dist/discord/monitor/thread-bindings.types.js +6 -0
  321. package/dist/discord/resolve-users.js +33 -21
  322. package/dist/discord/send.channels.js +15 -0
  323. package/dist/discord/send.js +3 -2
  324. package/dist/discord/send.outbound.js +82 -26
  325. package/dist/discord/send.permissions.js +83 -30
  326. package/dist/discord/send.reactions.js +8 -4
  327. package/dist/discord/token.js +10 -5
  328. package/dist/discord/voice/command.js +263 -0
  329. package/dist/discord/voice/manager.js +531 -0
  330. package/dist/gateway/auth.js +34 -10
  331. package/dist/gateway/call.js +4 -16
  332. package/dist/gateway/client.js +28 -4
  333. package/dist/gateway/config-reload.js +3 -4
  334. package/dist/gateway/control-ui.js +219 -96
  335. package/dist/gateway/hooks-mapping.js +88 -38
  336. package/dist/gateway/http-auth-helpers.js +3 -2
  337. package/dist/gateway/http-endpoint-helpers.js +1 -0
  338. package/dist/gateway/net.js +54 -12
  339. package/dist/gateway/node-invoke-system-run-approval.js +14 -35
  340. package/dist/gateway/node-registry.js +10 -5
  341. package/dist/gateway/openai-http.js +1 -0
  342. package/dist/gateway/openresponses-http.js +1 -0
  343. package/dist/gateway/origin-check.js +1 -18
  344. package/dist/gateway/protocol/index.js +4 -3
  345. package/dist/gateway/protocol/schema/cron.js +1 -0
  346. package/dist/gateway/protocol/schema/devices.js +1 -0
  347. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -1
  348. package/dist/gateway/protocol/schema/sessions.js +6 -0
  349. package/dist/gateway/role-policy.js +17 -0
  350. package/dist/gateway/server/ws-connection/connect-policy.js +37 -0
  351. package/dist/gateway/server/ws-connection/message-handler.js +175 -148
  352. package/dist/gateway/server-chat.js +83 -25
  353. package/dist/gateway/server-constants.js +10 -9
  354. package/dist/gateway/server-cron.js +1 -0
  355. package/dist/gateway/server-http.js +16 -7
  356. package/dist/gateway/server-maintenance.js +20 -5
  357. package/dist/gateway/server-methods/chat.js +10 -6
  358. package/dist/gateway/server-methods/config.js +12 -14
  359. package/dist/gateway/server-methods/devices.js +17 -3
  360. package/dist/gateway/server-methods/models.js +11 -1
  361. package/dist/gateway/server-methods/sessions.js +64 -8
  362. package/dist/gateway/server-methods/usage.js +162 -75
  363. package/dist/gateway/server-node-events.js +29 -0
  364. package/dist/gateway/server-runtime-config.js +34 -13
  365. package/dist/gateway/server-startup-memory.js +17 -11
  366. package/dist/gateway/session-utils.fs.js +32 -34
  367. package/dist/gateway/sessions-resolve.js +17 -5
  368. package/dist/gateway/test-helpers.openai-mock.js +14 -7
  369. package/dist/gateway/tools-invoke-http.js +21 -10
  370. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +3 -1
  371. package/dist/hooks/bundled/command-logger/handler.js +7 -2
  372. package/dist/hooks/bundled/session-memory/handler.js +6 -5
  373. package/dist/hooks/frontmatter.js +6 -6
  374. package/dist/hooks/gmail-watcher.js +11 -6
  375. package/dist/hooks/internal-hooks.js +11 -1
  376. package/dist/hooks/llm-slug-generator.js +4 -1
  377. package/dist/hooks/workspace.js +47 -17
  378. package/dist/imessage/accounts.js +9 -20
  379. package/dist/imessage/monitor/inbound-processing.js +2 -1
  380. package/dist/infra/archive.js +174 -73
  381. package/dist/infra/control-ui-assets.js +14 -6
  382. package/dist/infra/device-pairing.js +108 -29
  383. package/dist/infra/env.js +10 -5
  384. package/dist/infra/exec-approvals-allowlist.js +122 -0
  385. package/dist/infra/exec-approvals-analysis.js +34 -3
  386. package/dist/infra/exec-approvals.js +5 -17
  387. package/dist/infra/exec-safe-bin-policy.js +53 -45
  388. package/dist/infra/fs-safe.js +71 -39
  389. package/dist/infra/gateway-lock.js +6 -2
  390. package/dist/infra/heartbeat-wake.js +6 -12
  391. package/dist/infra/host-env-security-policy.json +19 -0
  392. package/dist/infra/host-env-security.js +66 -0
  393. package/dist/infra/net/ssrf.js +131 -38
  394. package/dist/infra/outbound/bound-delivery-router.js +88 -0
  395. package/dist/infra/outbound/channel-selection.js +12 -6
  396. package/dist/infra/outbound/envelope.js +1 -1
  397. package/dist/infra/outbound/format.js +12 -6
  398. package/dist/infra/outbound/payloads.js +14 -7
  399. package/dist/infra/outbound/session-binding-service.js +123 -0
  400. package/dist/infra/path-guards.js +25 -0
  401. package/dist/infra/provider-usage.fetch.codex.js +7 -15
  402. package/dist/infra/provider-usage.fetch.gemini.js +14 -11
  403. package/dist/infra/provider-usage.fetch.shared.js +30 -1
  404. package/dist/infra/provider-usage.fetch.zai.js +10 -9
  405. package/dist/infra/retry-policy.js +4 -2
  406. package/dist/infra/retry.js +9 -5
  407. package/dist/infra/session-cost-usage.js +107 -59
  408. package/dist/infra/session-maintenance-warning.js +3 -1
  409. package/dist/infra/shell-env.js +98 -34
  410. package/dist/infra/ssh-config.js +12 -6
  411. package/dist/infra/system-run-command.js +49 -4
  412. package/dist/infra/update-channels.js +10 -5
  413. package/dist/line/accounts.js +5 -7
  414. package/dist/line/bot-access.js +8 -20
  415. package/dist/line/bot-handlers.js +3 -1
  416. package/dist/link-understanding/detect.js +15 -7
  417. package/dist/media/constants.js +15 -6
  418. package/dist/media/image-ops.js +7 -0
  419. package/dist/media/local-roots.js +3 -2
  420. package/dist/media-understanding/apply.js +4 -1
  421. package/dist/media-understanding/concurrency.js +8 -20
  422. package/dist/memory/backend-config.js +45 -6
  423. package/dist/memory/embeddings.js +10 -4
  424. package/dist/memory/fs-utils.js +23 -0
  425. package/dist/memory/manager-search.js +12 -6
  426. package/dist/memory/manager-sync-ops.js +12 -2
  427. package/dist/memory/qmd-manager.js +466 -53
  428. package/dist/memory/query-expansion.js +167 -3
  429. package/dist/memory/status-format.js +10 -5
  430. package/dist/memory/sync-memory-files.js +1 -1
  431. package/dist/node-host/invoke-system-run.js +281 -0
  432. package/dist/node-host/invoke.js +55 -337
  433. package/dist/pairing/pairing-store.js +22 -0
  434. package/dist/plugin-sdk/allow-from.js +1 -1
  435. package/dist/plugin-sdk/command-auth.js +3 -1
  436. package/dist/plugin-sdk/index.js +6 -3
  437. package/dist/plugin-sdk/webhook-targets.js +32 -0
  438. package/dist/plugins/bundled-dir.js +9 -6
  439. package/dist/plugins/hooks.js +50 -0
  440. package/dist/plugins/install.js +28 -16
  441. package/dist/plugins/runtime.js +3 -17
  442. package/dist/plugins/update.js +78 -12
  443. package/dist/process/spawn-utils.js +14 -7
  444. package/dist/providers/github-copilot-token.js +11 -6
  445. package/dist/providers/qwen-portal-oauth.js +14 -6
  446. package/dist/routing/account-id.js +30 -0
  447. package/dist/routing/resolve-route.js +3 -7
  448. package/dist/routing/session-key.js +2 -16
  449. package/dist/security/audit-channel.js +93 -2
  450. package/dist/security/audit-extra.async.js +159 -5
  451. package/dist/security/audit-extra.js +1 -1
  452. package/dist/security/audit-extra.sync.js +85 -6
  453. package/dist/security/audit.js +40 -4
  454. package/dist/security/dm-policy-shared.js +44 -0
  455. package/dist/security/external-content.js +26 -6
  456. package/dist/shared/entry-status.js +6 -0
  457. package/dist/shared/frontmatter.js +5 -5
  458. package/dist/shared/node-match.js +11 -4
  459. package/dist/shared/operator-scope-compat.js +8 -3
  460. package/dist/signal/accounts.js +7 -20
  461. package/dist/signal/monitor/event-handler.js +3 -1
  462. package/dist/slack/accounts.js +6 -19
  463. package/dist/slack/actions.js +11 -3
  464. package/dist/slack/monitor/auth.js +1 -1
  465. package/dist/slack/monitor/message-handler/dispatch.js +50 -29
  466. package/dist/slack/monitor/replies.js +15 -7
  467. package/dist/slack/monitor/slash.js +22 -13
  468. package/dist/slack/resolve-channels.js +10 -5
  469. package/dist/slack/send.js +102 -12
  470. package/dist/slack/stream-mode.js +10 -0
  471. package/dist/slack/streaming.js +4 -2
  472. package/dist/telegram/accounts.js +19 -14
  473. package/dist/telegram/bot/helpers.js +3 -5
  474. package/dist/telegram/bot-access.js +35 -36
  475. package/dist/telegram/bot-handlers.js +120 -148
  476. package/dist/telegram/bot-message-context.js +68 -9
  477. package/dist/telegram/bot-message-dispatch.js +155 -90
  478. package/dist/telegram/bot-native-commands.js +16 -0
  479. package/dist/telegram/draft-stream.js +14 -1
  480. package/dist/telegram/inline-buttons.js +5 -15
  481. package/dist/telegram/monitor.js +11 -7
  482. package/dist/telegram/network-config.js +19 -7
  483. package/dist/telegram/send.js +3 -2
  484. package/dist/telegram/sent-message-cache.js +5 -6
  485. package/dist/telegram/status-reaction-variants.js +208 -0
  486. package/dist/telegram/sticker-cache.js +11 -9
  487. package/dist/terminal/theme.js +12 -12
  488. package/dist/tts/tts.js +80 -567
  489. package/dist/tui/components/chat-log.js +41 -8
  490. package/dist/tui/theme/theme.js +10 -12
  491. package/dist/tui/tui-local-shell.js +16 -6
  492. package/dist/tui/tui.js +58 -6
  493. package/dist/utils/account-id.js +2 -4
  494. package/dist/utils/boolean.js +10 -5
  495. package/dist/utils/directive-tags.js +11 -0
  496. package/dist/utils/queue-helpers.js +67 -12
  497. package/dist/web/auto-reply/deliver-reply.js +8 -4
  498. package/dist/web/auto-reply/mentions.js +10 -5
  499. package/dist/web/auto-reply/monitor/group-members.js +14 -7
  500. package/dist/web/auto-reply/monitor/process-message.js +45 -24
  501. package/dist/web/inbound/access-control.js +5 -2
  502. package/dist/web/login-qr.js +12 -6
  503. package/dist/web/media.js +123 -16
  504. package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
  505. package/extensions/bluebubbles/src/monitor.ts +208 -1950
  506. package/package.json +1 -1
@@ -0,0 +1,531 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import { ChannelType, ReadyListener } from "@buape/carbon";
6
+ import { AudioPlayerStatus, EndBehaviorType, VoiceConnectionStatus, createAudioPlayer, createAudioResource, entersState, joinVoiceChannel, } from "@discordjs/voice";
7
+ import { resolveAgentDir } from "../../agents/agent-scope.js";
8
+ import { agentCommand } from "../../commands/agent.js";
9
+ import { logVerbose, shouldLogVerbose } from "../../globals.js";
10
+ import { formatErrorMessage } from "../../infra/errors.js";
11
+ import { resolvePreferredPoolbotTmpDir } from "../../infra/tmp-poolbot-dir.js";
12
+ import { createSubsystemLogger } from "../../logging/subsystem.js";
13
+ import { buildProviderRegistry, createMediaAttachmentCache, normalizeMediaAttachments, runCapability, } from "../../media-understanding/runner.js";
14
+ import { resolveAgentRoute } from "../../routing/resolve-route.js";
15
+ import { parseTtsDirectives } from "../../tts/tts-core.js";
16
+ import { resolveTtsConfig, textToSpeech } from "../../tts/tts.js";
17
+ const require = createRequire(import.meta.url);
18
+ const SAMPLE_RATE = 48_000;
19
+ const CHANNELS = 2;
20
+ const BIT_DEPTH = 16;
21
+ const MIN_SEGMENT_SECONDS = 0.35;
22
+ const SILENCE_DURATION_MS = 1_000;
23
+ const PLAYBACK_READY_TIMEOUT_MS = 15_000;
24
+ const SPEAKING_READY_TIMEOUT_MS = 60_000;
25
+ const logger = createSubsystemLogger("discord/voice");
26
+ const logVoiceVerbose = (message) => {
27
+ logVerbose(`discord voice: ${message}`);
28
+ };
29
+ function mergeTtsConfig(base, override) {
30
+ if (!override) {
31
+ return base;
32
+ }
33
+ return {
34
+ ...base,
35
+ ...override,
36
+ modelOverrides: {
37
+ ...base.modelOverrides,
38
+ ...override.modelOverrides,
39
+ },
40
+ elevenlabs: {
41
+ ...base.elevenlabs,
42
+ ...override.elevenlabs,
43
+ voiceSettings: {
44
+ ...base.elevenlabs?.voiceSettings,
45
+ ...override.elevenlabs?.voiceSettings,
46
+ },
47
+ },
48
+ openai: {
49
+ ...base.openai,
50
+ ...override.openai,
51
+ },
52
+ edge: {
53
+ ...base.edge,
54
+ ...override.edge,
55
+ },
56
+ };
57
+ }
58
+ function resolveVoiceTtsConfig(params) {
59
+ if (!params.override) {
60
+ return { cfg: params.cfg, resolved: resolveTtsConfig(params.cfg) };
61
+ }
62
+ const base = params.cfg.messages?.tts ?? {};
63
+ const merged = mergeTtsConfig(base, params.override);
64
+ const messages = params.cfg.messages ?? {};
65
+ const cfg = {
66
+ ...params.cfg,
67
+ messages: {
68
+ ...messages,
69
+ tts: merged,
70
+ },
71
+ };
72
+ return { cfg, resolved: resolveTtsConfig(cfg) };
73
+ }
74
+ function buildWavBuffer(pcm) {
75
+ const blockAlign = (CHANNELS * BIT_DEPTH) / 8;
76
+ const byteRate = SAMPLE_RATE * blockAlign;
77
+ const header = Buffer.alloc(44);
78
+ header.write("RIFF", 0);
79
+ header.writeUInt32LE(36 + pcm.length, 4);
80
+ header.write("WAVE", 8);
81
+ header.write("fmt ", 12);
82
+ header.writeUInt32LE(16, 16);
83
+ header.writeUInt16LE(1, 20);
84
+ header.writeUInt16LE(CHANNELS, 22);
85
+ header.writeUInt32LE(SAMPLE_RATE, 24);
86
+ header.writeUInt32LE(byteRate, 28);
87
+ header.writeUInt16LE(blockAlign, 32);
88
+ header.writeUInt16LE(BIT_DEPTH, 34);
89
+ header.write("data", 36);
90
+ header.writeUInt32LE(pcm.length, 40);
91
+ return Buffer.concat([header, pcm]);
92
+ }
93
+ function createOpusDecoder() {
94
+ try {
95
+ const OpusScript = require("opusscript");
96
+ const decoder = new OpusScript(SAMPLE_RATE, CHANNELS, OpusScript.Application.AUDIO);
97
+ return { decoder, name: "opusscript" };
98
+ }
99
+ catch (err) {
100
+ logger.warn(`discord voice: opusscript init failed: ${formatErrorMessage(err)}`);
101
+ }
102
+ try {
103
+ const { OpusEncoder } = require("@discordjs/opus");
104
+ const decoder = new OpusEncoder(SAMPLE_RATE, CHANNELS);
105
+ return { decoder, name: "@discordjs/opus" };
106
+ }
107
+ catch (err) {
108
+ logger.warn(`discord voice: opus decoder init failed: ${formatErrorMessage(err)}`);
109
+ }
110
+ return null;
111
+ }
112
+ async function decodeOpusStream(stream) {
113
+ const selected = createOpusDecoder();
114
+ if (!selected) {
115
+ return Buffer.alloc(0);
116
+ }
117
+ logVoiceVerbose(`opus decoder: ${selected.name}`);
118
+ const chunks = [];
119
+ try {
120
+ for await (const chunk of stream) {
121
+ if (!chunk || !(chunk instanceof Buffer) || chunk.length === 0) {
122
+ continue;
123
+ }
124
+ const decoded = selected.decoder.decode(chunk);
125
+ if (decoded && decoded.length > 0) {
126
+ chunks.push(Buffer.from(decoded));
127
+ }
128
+ }
129
+ }
130
+ catch (err) {
131
+ if (shouldLogVerbose()) {
132
+ logVerbose(`discord voice: opus decode failed: ${formatErrorMessage(err)}`);
133
+ }
134
+ }
135
+ return chunks.length > 0 ? Buffer.concat(chunks) : Buffer.alloc(0);
136
+ }
137
+ function estimateDurationSeconds(pcm) {
138
+ const bytesPerSample = (BIT_DEPTH / 8) * CHANNELS;
139
+ if (bytesPerSample <= 0) {
140
+ return 0;
141
+ }
142
+ return pcm.length / (bytesPerSample * SAMPLE_RATE);
143
+ }
144
+ async function writeWavFile(pcm) {
145
+ const tempDir = await fs.mkdtemp(path.join(resolvePreferredPoolbotTmpDir(), "discord-voice-"));
146
+ const filePath = path.join(tempDir, `segment-${randomUUID()}.wav`);
147
+ const wav = buildWavBuffer(pcm);
148
+ await fs.writeFile(filePath, wav);
149
+ scheduleTempCleanup(tempDir);
150
+ return { path: filePath, durationSeconds: estimateDurationSeconds(pcm) };
151
+ }
152
+ function scheduleTempCleanup(tempDir, delayMs = 30 * 60 * 1000) {
153
+ const timer = setTimeout(() => {
154
+ fs.rm(tempDir, { recursive: true, force: true }).catch((err) => {
155
+ if (shouldLogVerbose()) {
156
+ logVerbose(`discord voice: temp cleanup failed for ${tempDir}: ${formatErrorMessage(err)}`);
157
+ }
158
+ });
159
+ }, delayMs);
160
+ timer.unref();
161
+ }
162
+ async function transcribeAudio(params) {
163
+ const ctx = {
164
+ MediaPath: params.filePath,
165
+ MediaType: "audio/wav",
166
+ };
167
+ const attachments = normalizeMediaAttachments(ctx);
168
+ if (attachments.length === 0) {
169
+ return undefined;
170
+ }
171
+ const cache = createMediaAttachmentCache(attachments);
172
+ const providerRegistry = buildProviderRegistry();
173
+ try {
174
+ const result = await runCapability({
175
+ capability: "audio",
176
+ cfg: params.cfg,
177
+ ctx,
178
+ attachments: cache,
179
+ media: attachments,
180
+ agentDir: resolveAgentDir(params.cfg, params.agentId),
181
+ providerRegistry,
182
+ config: params.cfg.tools?.media?.audio,
183
+ });
184
+ const output = result.outputs.find((entry) => entry.kind === "audio.transcription");
185
+ const text = output?.text?.trim();
186
+ return text || undefined;
187
+ }
188
+ finally {
189
+ await cache.cleanup();
190
+ }
191
+ }
192
+ export class DiscordVoiceManager {
193
+ params;
194
+ sessions = new Map();
195
+ botUserId;
196
+ voiceEnabled;
197
+ autoJoinTask = null;
198
+ constructor(params) {
199
+ this.params = params;
200
+ this.botUserId = params.botUserId;
201
+ this.voiceEnabled = params.discordConfig.voice?.enabled !== false;
202
+ }
203
+ setBotUserId(id) {
204
+ if (id) {
205
+ this.botUserId = id;
206
+ }
207
+ }
208
+ isEnabled() {
209
+ return this.voiceEnabled;
210
+ }
211
+ async autoJoin() {
212
+ if (!this.voiceEnabled) {
213
+ return;
214
+ }
215
+ if (this.autoJoinTask) {
216
+ return this.autoJoinTask;
217
+ }
218
+ this.autoJoinTask = (async () => {
219
+ const entries = this.params.discordConfig.voice?.autoJoin ?? [];
220
+ logVoiceVerbose(`autoJoin: ${entries.length} entries`);
221
+ const seenGuilds = new Set();
222
+ for (const entry of entries) {
223
+ const guildId = entry.guildId.trim();
224
+ if (!guildId) {
225
+ continue;
226
+ }
227
+ if (seenGuilds.has(guildId)) {
228
+ logger.warn(`discord voice: autoJoin has multiple entries for guild ${guildId}; skipping`);
229
+ continue;
230
+ }
231
+ seenGuilds.add(guildId);
232
+ logVoiceVerbose(`autoJoin: joining guild ${guildId} channel ${entry.channelId}`);
233
+ await this.join({
234
+ guildId: entry.guildId,
235
+ channelId: entry.channelId,
236
+ });
237
+ }
238
+ })().finally(() => {
239
+ this.autoJoinTask = null;
240
+ });
241
+ return this.autoJoinTask;
242
+ }
243
+ status() {
244
+ return Array.from(this.sessions.values()).map((session) => ({
245
+ ok: true,
246
+ message: `connected: guild ${session.guildId} channel ${session.channelId}`,
247
+ guildId: session.guildId,
248
+ channelId: session.channelId,
249
+ }));
250
+ }
251
+ async join(params) {
252
+ if (!this.voiceEnabled) {
253
+ return {
254
+ ok: false,
255
+ message: "Discord voice is disabled (channels.discord.voice.enabled).",
256
+ };
257
+ }
258
+ const guildId = params.guildId.trim();
259
+ const channelId = params.channelId.trim();
260
+ if (!guildId || !channelId) {
261
+ return { ok: false, message: "Missing guildId or channelId." };
262
+ }
263
+ logVoiceVerbose(`join requested: guild ${guildId} channel ${channelId}`);
264
+ const existing = this.sessions.get(guildId);
265
+ if (existing && existing.channelId === channelId) {
266
+ logVoiceVerbose(`join: already connected to guild ${guildId} channel ${channelId}`);
267
+ return { ok: true, message: `Already connected to <#${channelId}>.`, guildId, channelId };
268
+ }
269
+ if (existing) {
270
+ logVoiceVerbose(`join: replacing existing session for guild ${guildId}`);
271
+ await this.leave({ guildId });
272
+ }
273
+ const channelInfo = await this.params.client.fetchChannel(channelId).catch(() => null);
274
+ if (!channelInfo || ("type" in channelInfo && !isVoiceChannel(channelInfo.type))) {
275
+ return { ok: false, message: `Channel ${channelId} is not a voice channel.` };
276
+ }
277
+ const channelGuildId = "guildId" in channelInfo ? channelInfo.guildId : undefined;
278
+ if (channelGuildId && channelGuildId !== guildId) {
279
+ return { ok: false, message: "Voice channel is not in this guild." };
280
+ }
281
+ const voicePlugin = this.params.client.getPlugin("voice");
282
+ if (!voicePlugin) {
283
+ return { ok: false, message: "Discord voice plugin is not available." };
284
+ }
285
+ const adapterCreator = voicePlugin.getGatewayAdapterCreator(guildId);
286
+ const connection = joinVoiceChannel({
287
+ channelId,
288
+ guildId,
289
+ adapterCreator,
290
+ selfDeaf: false,
291
+ selfMute: false,
292
+ });
293
+ try {
294
+ await entersState(connection, VoiceConnectionStatus.Ready, PLAYBACK_READY_TIMEOUT_MS);
295
+ logVoiceVerbose(`join: connected to guild ${guildId} channel ${channelId}`);
296
+ }
297
+ catch (err) {
298
+ connection.destroy();
299
+ return { ok: false, message: `Failed to join voice channel: ${formatErrorMessage(err)}` };
300
+ }
301
+ const sessionChannelId = channelInfo?.id ?? channelId;
302
+ // Use the voice channel id as the session channel so text chat in the voice channel
303
+ // shares the same session as spoken audio.
304
+ if (sessionChannelId !== channelId) {
305
+ logVoiceVerbose(`join: using session channel ${sessionChannelId} for voice channel ${channelId}`);
306
+ }
307
+ const route = resolveAgentRoute({
308
+ cfg: this.params.cfg,
309
+ channel: "discord",
310
+ accountId: this.params.accountId,
311
+ guildId,
312
+ peer: { kind: "channel", id: sessionChannelId },
313
+ });
314
+ const player = createAudioPlayer();
315
+ connection.subscribe(player);
316
+ const entry = {
317
+ guildId,
318
+ channelId,
319
+ sessionChannelId,
320
+ route,
321
+ connection,
322
+ player,
323
+ playbackQueue: Promise.resolve(),
324
+ processingQueue: Promise.resolve(),
325
+ activeSpeakers: new Set(),
326
+ stop: () => {
327
+ player.stop();
328
+ connection.destroy();
329
+ },
330
+ };
331
+ const speakingHandler = (userId) => {
332
+ void this.handleSpeakingStart(entry, userId).catch((err) => {
333
+ logger.warn(`discord voice: capture failed: ${formatErrorMessage(err)}`);
334
+ });
335
+ };
336
+ connection.receiver.speaking.on("start", speakingHandler);
337
+ connection.on(VoiceConnectionStatus.Disconnected, async () => {
338
+ try {
339
+ await Promise.race([
340
+ entersState(connection, VoiceConnectionStatus.Signalling, 5_000),
341
+ entersState(connection, VoiceConnectionStatus.Connecting, 5_000),
342
+ ]);
343
+ }
344
+ catch {
345
+ this.sessions.delete(guildId);
346
+ connection.destroy();
347
+ }
348
+ });
349
+ connection.on(VoiceConnectionStatus.Destroyed, () => {
350
+ this.sessions.delete(guildId);
351
+ });
352
+ player.on("error", (err) => {
353
+ logger.warn(`discord voice: playback error: ${formatErrorMessage(err)}`);
354
+ });
355
+ this.sessions.set(guildId, entry);
356
+ return {
357
+ ok: true,
358
+ message: `Joined <#${channelId}>.`,
359
+ guildId,
360
+ channelId,
361
+ };
362
+ }
363
+ async leave(params) {
364
+ const guildId = params.guildId.trim();
365
+ logVoiceVerbose(`leave requested: guild ${guildId} channel ${params.channelId ?? "current"}`);
366
+ const entry = this.sessions.get(guildId);
367
+ if (!entry) {
368
+ return { ok: false, message: "Not connected to a voice channel." };
369
+ }
370
+ if (params.channelId && params.channelId !== entry.channelId) {
371
+ return { ok: false, message: "Not connected to that voice channel." };
372
+ }
373
+ entry.stop();
374
+ this.sessions.delete(guildId);
375
+ logVoiceVerbose(`leave: disconnected from guild ${guildId} channel ${entry.channelId}`);
376
+ return {
377
+ ok: true,
378
+ message: `Left <#${entry.channelId}>.`,
379
+ guildId,
380
+ channelId: entry.channelId,
381
+ };
382
+ }
383
+ async destroy() {
384
+ for (const entry of this.sessions.values()) {
385
+ entry.stop();
386
+ }
387
+ this.sessions.clear();
388
+ }
389
+ enqueueProcessing(entry, task) {
390
+ entry.processingQueue = entry.processingQueue
391
+ .then(task)
392
+ .catch((err) => logger.warn(`discord voice: processing failed: ${formatErrorMessage(err)}`));
393
+ }
394
+ enqueuePlayback(entry, task) {
395
+ entry.playbackQueue = entry.playbackQueue
396
+ .then(task)
397
+ .catch((err) => logger.warn(`discord voice: playback failed: ${formatErrorMessage(err)}`));
398
+ }
399
+ async handleSpeakingStart(entry, userId) {
400
+ if (!userId || entry.activeSpeakers.has(userId)) {
401
+ return;
402
+ }
403
+ if (this.botUserId && userId === this.botUserId) {
404
+ return;
405
+ }
406
+ entry.activeSpeakers.add(userId);
407
+ logVoiceVerbose(`capture start: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
408
+ if (entry.player.state.status === AudioPlayerStatus.Playing) {
409
+ entry.player.stop(true);
410
+ }
411
+ const stream = entry.connection.receiver.subscribe(userId, {
412
+ end: {
413
+ behavior: EndBehaviorType.AfterSilence,
414
+ duration: SILENCE_DURATION_MS,
415
+ },
416
+ });
417
+ stream.on("error", (err) => {
418
+ logger.warn(`discord voice: receive error: ${formatErrorMessage(err)}`);
419
+ });
420
+ try {
421
+ const pcm = await decodeOpusStream(stream);
422
+ if (pcm.length === 0) {
423
+ logVoiceVerbose(`capture empty: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
424
+ return;
425
+ }
426
+ const { path: wavPath, durationSeconds } = await writeWavFile(pcm);
427
+ if (durationSeconds < MIN_SEGMENT_SECONDS) {
428
+ logVoiceVerbose(`capture too short (${durationSeconds.toFixed(2)}s): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
429
+ return;
430
+ }
431
+ logVoiceVerbose(`capture ready (${durationSeconds.toFixed(2)}s): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
432
+ this.enqueueProcessing(entry, async () => {
433
+ await this.processSegment({ entry, wavPath, userId, durationSeconds });
434
+ });
435
+ }
436
+ finally {
437
+ entry.activeSpeakers.delete(userId);
438
+ }
439
+ }
440
+ async processSegment(params) {
441
+ const { entry, wavPath, userId, durationSeconds } = params;
442
+ logVoiceVerbose(`segment processing (${durationSeconds.toFixed(2)}s): guild ${entry.guildId} channel ${entry.channelId}`);
443
+ const transcript = await transcribeAudio({
444
+ cfg: this.params.cfg,
445
+ agentId: entry.route.agentId,
446
+ filePath: wavPath,
447
+ });
448
+ if (!transcript) {
449
+ logVoiceVerbose(`transcription empty: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
450
+ return;
451
+ }
452
+ logVoiceVerbose(`transcription ok (${transcript.length} chars): guild ${entry.guildId} channel ${entry.channelId}`);
453
+ const speakerLabel = await this.resolveSpeakerLabel(entry.guildId, userId);
454
+ const prompt = speakerLabel ? `${speakerLabel}: ${transcript}` : transcript;
455
+ const result = await agentCommand({
456
+ message: prompt,
457
+ sessionKey: entry.route.sessionKey,
458
+ agentId: entry.route.agentId,
459
+ messageChannel: "discord",
460
+ deliver: false,
461
+ }, this.params.runtime);
462
+ const replyText = (result.payloads ?? [])
463
+ .map((payload) => payload.text)
464
+ .filter((text) => typeof text === "string" && text.trim())
465
+ .join("\n")
466
+ .trim();
467
+ if (!replyText) {
468
+ logVoiceVerbose(`reply empty: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
469
+ return;
470
+ }
471
+ logVoiceVerbose(`reply ok (${replyText.length} chars): guild ${entry.guildId} channel ${entry.channelId}`);
472
+ const { cfg: ttsCfg, resolved: ttsConfig } = resolveVoiceTtsConfig({
473
+ cfg: this.params.cfg,
474
+ override: this.params.discordConfig.voice?.tts,
475
+ });
476
+ const directive = parseTtsDirectives(replyText, ttsConfig.modelOverrides);
477
+ const speakText = directive.overrides.ttsText ?? directive.cleanedText.trim();
478
+ if (!speakText) {
479
+ logVoiceVerbose(`tts skipped (empty): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
480
+ return;
481
+ }
482
+ const ttsResult = await textToSpeech({
483
+ text: speakText,
484
+ cfg: ttsCfg,
485
+ channel: "discord",
486
+ overrides: directive.overrides,
487
+ });
488
+ if (!ttsResult.success || !ttsResult.audioPath) {
489
+ logger.warn(`discord voice: TTS failed: ${ttsResult.error ?? "unknown error"}`);
490
+ return;
491
+ }
492
+ const audioPath = ttsResult.audioPath;
493
+ logVoiceVerbose(`tts ok (${speakText.length} chars): guild ${entry.guildId} channel ${entry.channelId}`);
494
+ this.enqueuePlayback(entry, async () => {
495
+ logVoiceVerbose(`playback start: guild ${entry.guildId} channel ${entry.channelId} file ${path.basename(audioPath)}`);
496
+ const resource = createAudioResource(audioPath);
497
+ entry.player.play(resource);
498
+ await entersState(entry.player, AudioPlayerStatus.Playing, PLAYBACK_READY_TIMEOUT_MS).catch(() => undefined);
499
+ await entersState(entry.player, AudioPlayerStatus.Idle, SPEAKING_READY_TIMEOUT_MS).catch(() => undefined);
500
+ logVoiceVerbose(`playback done: guild ${entry.guildId} channel ${entry.channelId}`);
501
+ });
502
+ }
503
+ async resolveSpeakerLabel(guildId, userId) {
504
+ try {
505
+ const member = await this.params.client.fetchMember(guildId, userId);
506
+ return member.nickname ?? member.user?.globalName ?? member.user?.username ?? userId;
507
+ }
508
+ catch {
509
+ try {
510
+ const user = await this.params.client.fetchUser(userId);
511
+ return user.globalName ?? user.username ?? userId;
512
+ }
513
+ catch {
514
+ return userId;
515
+ }
516
+ }
517
+ }
518
+ }
519
+ export class DiscordVoiceReadyListener extends ReadyListener {
520
+ manager;
521
+ constructor(manager) {
522
+ super();
523
+ this.manager = manager;
524
+ }
525
+ async handle() {
526
+ await this.manager.autoJoin();
527
+ }
528
+ }
529
+ function isVoiceChannel(type) {
530
+ return type === ChannelType.GuildVoice || type === ChannelType.GuildStageVoice;
531
+ }
@@ -1,36 +1,41 @@
1
1
  import { readTailscaleWhoisIdentity } from "../infra/tailscale.js";
2
2
  import { safeEqualSecret } from "../security/secret-equal.js";
3
3
  import { AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET, } from "./auth-rate-limit.js";
4
- import { isLoopbackAddress, isTrustedProxyAddress, resolveHostName, parseForwardedForClientIp, resolveGatewayClientIp, } from "./net.js";
4
+ import { isLoopbackAddress, isTrustedProxyAddress, resolveHostName, resolveClientIp, } from "./net.js";
5
5
  function normalizeLogin(login) {
6
6
  return login.trim().toLowerCase();
7
7
  }
8
8
  function headerValue(value) {
9
9
  return Array.isArray(value) ? value[0] : value;
10
10
  }
11
+ const TAILSCALE_TRUSTED_PROXIES = ["127.0.0.1", "::1"];
11
12
  function resolveTailscaleClientIp(req) {
12
13
  if (!req) {
13
14
  return undefined;
14
15
  }
15
- const forwardedFor = headerValue(req.headers?.["x-forwarded-for"]);
16
- return forwardedFor ? parseForwardedForClientIp(forwardedFor) : undefined;
16
+ return resolveClientIp({
17
+ remoteAddr: req.socket?.remoteAddress ?? "",
18
+ forwardedFor: headerValue(req.headers?.["x-forwarded-for"]),
19
+ trustedProxies: [...TAILSCALE_TRUSTED_PROXIES],
20
+ });
17
21
  }
18
- function resolveRequestClientIp(req, trustedProxies) {
22
+ function resolveRequestClientIp(req, trustedProxies, allowRealIpFallback = false) {
19
23
  if (!req) {
20
24
  return undefined;
21
25
  }
22
- return resolveGatewayClientIp({
26
+ return resolveClientIp({
23
27
  remoteAddr: req.socket?.remoteAddress ?? "",
24
28
  forwardedFor: headerValue(req.headers?.["x-forwarded-for"]),
25
29
  realIp: headerValue(req.headers?.["x-real-ip"]),
26
30
  trustedProxies,
31
+ allowRealIpFallback,
27
32
  });
28
33
  }
29
- export function isLocalDirectRequest(req, trustedProxies) {
34
+ export function isLocalDirectRequest(req, trustedProxies, allowRealIpFallback = false) {
30
35
  if (!req) {
31
36
  return false;
32
37
  }
33
- const clientIp = resolveRequestClientIp(req, trustedProxies) ?? "";
38
+ const clientIp = resolveRequestClientIp(req, trustedProxies, allowRealIpFallback) ?? "";
34
39
  if (!isLoopbackAddress(clientIp)) {
35
40
  return false;
36
41
  }
@@ -214,10 +219,15 @@ function authorizeTrustedProxy(params) {
214
219
  }
215
220
  return { user };
216
221
  }
222
+ function shouldAllowTailscaleHeaderAuth(authSurface) {
223
+ return authSurface === "ws-control-ui";
224
+ }
217
225
  export async function authorizeGatewayConnect(params) {
218
226
  const { auth, connectAuth, req, trustedProxies } = params;
219
227
  const tailscaleWhois = params.tailscaleWhois ?? readTailscaleWhoisIdentity;
220
- const localDirect = isLocalDirectRequest(req, trustedProxies);
228
+ const authSurface = params.authSurface ?? "http";
229
+ const allowTailscaleHeaderAuth = shouldAllowTailscaleHeaderAuth(authSurface);
230
+ const localDirect = isLocalDirectRequest(req, trustedProxies, params.allowRealIpFallback === true);
221
231
  if (auth.mode === "trusted-proxy") {
222
232
  if (!auth.trustedProxy) {
223
233
  return { ok: false, reason: "trusted_proxy_config_missing" };
@@ -239,7 +249,9 @@ export async function authorizeGatewayConnect(params) {
239
249
  return { ok: true, method: "none" };
240
250
  }
241
251
  const limiter = params.rateLimiter;
242
- const ip = params.clientIp ?? resolveRequestClientIp(req, trustedProxies) ?? req?.socket?.remoteAddress;
252
+ const ip = params.clientIp ??
253
+ resolveRequestClientIp(req, trustedProxies, params.allowRealIpFallback === true) ??
254
+ req?.socket?.remoteAddress;
243
255
  const rateLimitScope = params.rateLimitScope ?? AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET;
244
256
  if (limiter) {
245
257
  const rlCheck = limiter.check(ip, rateLimitScope);
@@ -252,7 +264,7 @@ export async function authorizeGatewayConnect(params) {
252
264
  };
253
265
  }
254
266
  }
255
- if (auth.allowTailscale && !localDirect) {
267
+ if (allowTailscaleHeaderAuth && auth.allowTailscale && !localDirect) {
256
268
  const tailscaleCheck = await resolveVerifiedTailscaleUser({
257
269
  req,
258
270
  tailscaleWhois,
@@ -300,3 +312,15 @@ export async function authorizeGatewayConnect(params) {
300
312
  limiter?.recordFailure(ip, rateLimitScope);
301
313
  return { ok: false, reason: "unauthorized" };
302
314
  }
315
+ export async function authorizeHttpGatewayConnect(params) {
316
+ return authorizeGatewayConnect({
317
+ ...params,
318
+ authSurface: "http",
319
+ });
320
+ }
321
+ export async function authorizeWsControlUiGatewayConnect(params) {
322
+ return authorizeGatewayConnect({
323
+ ...params,
324
+ authSurface: "ws-control-ui",
325
+ });
326
+ }
@@ -1,12 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { loadConfig, resolveConfigPath, resolveGatewayPort, resolveStateDir, } from "../config/config.js";
3
3
  import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js";
4
- import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
5
4
  import { loadGatewayTlsRuntime } from "../infra/tls/gateway.js";
6
5
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, } from "../utils/message-channel.js";
7
6
  import { GatewayClient } from "./client.js";
8
7
  import { CLI_DEFAULT_OPERATOR_SCOPES, resolveLeastPrivilegeOperatorScopesForMethod, } from "./method-scopes.js";
9
- import { isSecureWebSocketUrl, pickPrimaryLanIPv4 } from "./net.js";
8
+ import { isSecureWebSocketUrl } from "./net.js";
10
9
  import { PROTOCOL_VERSION } from "./protocol/index.js";
11
10
  export function resolveExplicitGatewayAuth(opts) {
12
11
  const token = typeof opts?.token === "string" && opts.token.trim().length > 0 ? opts.token.trim() : undefined;
@@ -38,17 +37,10 @@ export function buildGatewayConnectionDetails(options = {}) {
38
37
  const remote = isRemoteMode ? config.gateway?.remote : undefined;
39
38
  const tlsEnabled = config.gateway?.tls?.enabled === true;
40
39
  const localPort = resolveGatewayPort(config);
41
- const tailnetIPv4 = pickPrimaryTailnetIPv4();
42
40
  const bindMode = config.gateway?.bind ?? "loopback";
43
- const preferTailnet = bindMode === "tailnet" && !!tailnetIPv4;
44
- const preferLan = bindMode === "lan";
45
- const lanIPv4 = preferLan ? pickPrimaryLanIPv4() : undefined;
46
41
  const scheme = tlsEnabled ? "wss" : "ws";
47
- const localUrl = preferTailnet && tailnetIPv4
48
- ? `${scheme}://${tailnetIPv4}:${localPort}`
49
- : preferLan && lanIPv4
50
- ? `${scheme}://${lanIPv4}:${localPort}`
51
- : `${scheme}://127.0.0.1:${localPort}`;
42
+ // Self-connections should always target loopback; bind mode only controls listener exposure.
43
+ const localUrl = `${scheme}://127.0.0.1:${localPort}`;
52
44
  const urlOverride = typeof options.url === "string" && options.url.trim().length > 0
53
45
  ? options.url.trim()
54
46
  : undefined;
@@ -61,11 +53,7 @@ export function buildGatewayConnectionDetails(options = {}) {
61
53
  ? "config gateway.remote.url"
62
54
  : remoteMisconfigured
63
55
  ? "missing gateway.remote.url (fallback local)"
64
- : preferTailnet && tailnetIPv4
65
- ? `local tailnet ${tailnetIPv4}`
66
- : preferLan && lanIPv4
67
- ? `local lan ${lanIPv4}`
68
- : "local loopback";
56
+ : "local loopback";
69
57
  const remoteFallbackNote = remoteMisconfigured
70
58
  ? "Warn: gateway.mode=remote but gateway.remote.url is missing; set gateway.remote.url or switch gateway.mode=local."
71
59
  : undefined;