@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,17 @@
1
+ import { isNodeRoleMethod } from "./method-scopes.js";
2
+ export const GATEWAY_ROLES = ["operator", "node"];
3
+ export function parseGatewayRole(roleRaw) {
4
+ if (roleRaw === "operator" || roleRaw === "node") {
5
+ return roleRaw;
6
+ }
7
+ return null;
8
+ }
9
+ export function roleCanSkipDeviceIdentity(role, sharedAuthOk) {
10
+ return role === "operator" && sharedAuthOk;
11
+ }
12
+ export function isRoleAuthorizedForMethod(role, method) {
13
+ if (isNodeRoleMethod(method)) {
14
+ return role === "node";
15
+ }
16
+ return role === "operator";
17
+ }
@@ -0,0 +1,37 @@
1
+ import { roleCanSkipDeviceIdentity } from "../../role-policy.js";
2
+ export function resolveControlUiAuthPolicy(params) {
3
+ const allowInsecureAuthConfigured = params.isControlUi && params.controlUiConfig?.allowInsecureAuth === true;
4
+ const dangerouslyDisableDeviceAuth = params.isControlUi && params.controlUiConfig?.dangerouslyDisableDeviceAuth === true;
5
+ return {
6
+ allowInsecureAuthConfigured,
7
+ dangerouslyDisableDeviceAuth,
8
+ // `allowInsecureAuth` must not bypass secure-context/device-auth requirements.
9
+ allowBypass: dangerouslyDisableDeviceAuth,
10
+ device: dangerouslyDisableDeviceAuth ? null : params.deviceRaw,
11
+ };
12
+ }
13
+ export function shouldSkipControlUiPairing(policy, sharedAuthOk) {
14
+ return policy.allowBypass && sharedAuthOk;
15
+ }
16
+ export function evaluateMissingDeviceIdentity(params) {
17
+ if (params.hasDeviceIdentity) {
18
+ return { kind: "allow" };
19
+ }
20
+ if (params.isControlUi && !params.controlUiAuthPolicy.allowBypass) {
21
+ // Allow localhost Control UI connections when allowInsecureAuth is configured.
22
+ // Localhost has no network interception risk, and browser SubtleCrypto
23
+ // (needed for device identity) is unavailable in insecure HTTP contexts.
24
+ // Remote connections are still rejected to preserve the MitM protection
25
+ // that the security fix (#20684) intended.
26
+ if (!params.controlUiAuthPolicy.allowInsecureAuthConfigured || !params.isLocalClient) {
27
+ return { kind: "reject-control-ui-insecure-auth" };
28
+ }
29
+ }
30
+ if (roleCanSkipDeviceIdentity(params.role, params.sharedAuthOk)) {
31
+ return { kind: "allow" };
32
+ }
33
+ if (!params.authOk && params.hasSharedAuth) {
34
+ return { kind: "reject-unauthorized" };
35
+ }
36
+ return { kind: "reject-device-required" };
37
+ }
@@ -7,17 +7,20 @@ import { recordRemoteNodeInfo, refreshRemoteNodeBins } from "../../../infra/skil
7
7
  import { upsertPresence } from "../../../infra/system-presence.js";
8
8
  import { loadVoiceWakeConfig } from "../../../infra/voicewake.js";
9
9
  import { rawDataToString } from "../../../infra/ws.js";
10
+ import { roleScopesAllow } from "../../../shared/operator-scope-compat.js";
10
11
  import { isGatewayCliClient, isWebchatClient } from "../../../utils/message-channel.js";
11
12
  import { resolveRuntimeServiceVersion } from "../../../version.js";
12
13
  import { AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET, } from "../../auth-rate-limit.js";
13
- import { authorizeGatewayConnect, isLocalDirectRequest } from "../../auth.js";
14
+ import { authorizeHttpGatewayConnect, authorizeWsControlUiGatewayConnect, isLocalDirectRequest, } from "../../auth.js";
15
+ import { buildCanvasScopedHostUrl, CANVAS_CAPABILITY_TTL_MS, mintCanvasCapabilityToken, } from "../../canvas-capability.js";
14
16
  import { buildDeviceAuthPayload } from "../../device-auth.js";
15
- import { isLoopbackAddress, isTrustedProxyAddress, resolveGatewayClientIp } from "../../net.js";
17
+ import { isLoopbackAddress, isTrustedProxyAddress, resolveClientIp } from "../../net.js";
16
18
  import { resolveHostName } from "../../net.js";
17
19
  import { resolveNodeCommandAllowlist } from "../../node-command-policy.js";
18
20
  import { checkBrowserOrigin } from "../../origin-check.js";
19
21
  import { GATEWAY_CLIENT_IDS } from "../../protocol/client-info.js";
20
22
  import { ErrorCodes, errorShape, formatValidationErrors, PROTOCOL_VERSION, validateConnectParams, validateRequestFrame, } from "../../protocol/index.js";
23
+ import { parseGatewayRole } from "../../role-policy.js";
21
24
  import { MAX_BUFFERED_BYTES, MAX_PAYLOAD_BYTES, TICK_INTERVAL_MS } from "../../server-constants.js";
22
25
  import { handleGatewayRequest } from "../../server-methods.js";
23
26
  import { formatError } from "../../server-utils.js";
@@ -25,12 +28,20 @@ import { formatForLog, logWs } from "../../ws-log.js";
25
28
  import { truncateCloseReason } from "../close-reason.js";
26
29
  import { buildGatewaySnapshot, getHealthCache, getHealthVersion, incrementPresenceVersion, refreshGatewayHealthSnapshot, } from "../health-state.js";
27
30
  import { formatGatewayAuthFailureMessage } from "./auth-messages.js";
31
+ import { evaluateMissingDeviceIdentity, resolveControlUiAuthPolicy, shouldSkipControlUiPairing, } from "./connect-policy.js";
28
32
  const DEVICE_SIGNATURE_SKEW_MS = 10 * 60 * 1000;
29
33
  export function attachGatewayWsMessageHandler(params) {
30
34
  const { socket, upgradeReq, connId, remoteAddr, forwardedFor, realIp, requestHost, requestOrigin, requestUserAgent, canvasHostUrl, connectNonce, resolvedAuth, rateLimiter, gatewayMethods, events, extraHandlers, buildRequestContext, send, close, isClosed, clearHandshakeTimer, getClient, setClient, setHandshakeState, setCloseCause, setLastFrameMeta, logGateway, logHealth, logWsControl, } = params;
31
35
  const configSnapshot = loadConfig();
32
36
  const trustedProxies = configSnapshot.gateway?.trustedProxies ?? [];
33
- const clientIp = resolveGatewayClientIp({ remoteAddr, forwardedFor, realIp, trustedProxies });
37
+ const allowRealIpFallback = configSnapshot.gateway?.allowRealIpFallback === true;
38
+ const clientIp = resolveClientIp({
39
+ remoteAddr,
40
+ forwardedFor,
41
+ realIp,
42
+ trustedProxies,
43
+ allowRealIpFallback,
44
+ });
34
45
  // If proxy headers are present but the remote address isn't trusted, don't treat
35
46
  // the connection as local. This prevents auth bypass when running behind a reverse
36
47
  // proxy without proper configuration - the proxy's loopback connection would otherwise
@@ -42,7 +53,7 @@ export function attachGatewayWsMessageHandler(params) {
42
53
  const hostIsLocal = hostName === "localhost" || hostName === "127.0.0.1" || hostName === "::1";
43
54
  const hostIsTailscaleServe = hostName.endsWith(".ts.net");
44
55
  const hostIsLocalish = hostIsLocal || hostIsTailscaleServe;
45
- const isLocalClient = isLocalDirectRequest(upgradeReq, trustedProxies);
56
+ const isLocalClient = isLocalDirectRequest(upgradeReq, trustedProxies, allowRealIpFallback);
46
57
  const reportedClientIp = isLocalClient || hasUntrustedProxyHeaders
47
58
  ? undefined
48
59
  : clientIp && !isLoopbackAddress(clientIp)
@@ -162,7 +173,7 @@ export function attachGatewayWsMessageHandler(params) {
162
173
  return;
163
174
  }
164
175
  const roleRaw = connectParams.role ?? "operator";
165
- const role = roleRaw === "operator" || roleRaw === "node" ? roleRaw : null;
176
+ const role = parseGatewayRole(roleRaw);
166
177
  if (!role) {
167
178
  markHandshakeFailure("invalid-role", {
168
179
  role: roleRaw,
@@ -202,52 +213,64 @@ export function attachGatewayWsMessageHandler(params) {
202
213
  const hasTokenAuth = Boolean(connectParams.auth?.token);
203
214
  const hasPasswordAuth = Boolean(connectParams.auth?.password);
204
215
  const hasSharedAuth = hasTokenAuth || hasPasswordAuth;
205
- const allowInsecureControlUi = isControlUi && configSnapshot.gateway?.controlUi?.allowInsecureAuth === true;
206
- const disableControlUiDeviceAuth = isControlUi && configSnapshot.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true;
207
- const allowControlUiBypass = allowInsecureControlUi || disableControlUiDeviceAuth;
208
- const device = disableControlUiDeviceAuth ? null : deviceRaw;
209
- const hasDeviceTokenCandidate = Boolean(connectParams.auth?.token && device);
210
- let authResult = await authorizeGatewayConnect({
211
- auth: resolvedAuth,
212
- connectAuth: connectParams.auth,
213
- req: upgradeReq,
214
- trustedProxies,
215
- rateLimiter: hasDeviceTokenCandidate ? undefined : rateLimiter,
216
- clientIp,
217
- rateLimitScope: AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
216
+ const controlUiAuthPolicy = resolveControlUiAuthPolicy({
217
+ isControlUi,
218
+ controlUiConfig: configSnapshot.gateway?.controlUi,
219
+ deviceRaw,
218
220
  });
219
- if (hasDeviceTokenCandidate &&
220
- authResult.ok &&
221
- rateLimiter &&
222
- (authResult.method === "token" || authResult.method === "password")) {
223
- const sharedRateCheck = rateLimiter.check(clientIp, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET);
224
- if (!sharedRateCheck.allowed) {
225
- authResult = {
226
- ok: false,
227
- reason: "rate_limited",
228
- rateLimited: true,
229
- retryAfterMs: sharedRateCheck.retryAfterMs,
230
- };
231
- }
232
- else {
233
- rateLimiter.reset(clientIp, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET);
234
- }
235
- }
236
- let authOk = authResult.ok;
237
- let authMethod = authResult.method ?? (resolvedAuth.mode === "password" ? "password" : "token");
238
- const sharedAuthResult = hasSharedAuth
239
- ? await authorizeGatewayConnect({
240
- auth: { ...resolvedAuth, allowTailscale: false },
221
+ const device = controlUiAuthPolicy.device;
222
+ const resolveAuthState = async () => {
223
+ const hasDeviceTokenCandidate = Boolean(connectParams.auth?.token && device);
224
+ let nextAuthResult = await authorizeWsControlUiGatewayConnect({
225
+ auth: resolvedAuth,
241
226
  connectAuth: connectParams.auth,
242
227
  req: upgradeReq,
243
228
  trustedProxies,
244
- // Shared-auth probe only; rate-limit side effects are handled in
245
- // the primary auth flow (or deferred for device-token candidates).
229
+ allowRealIpFallback,
230
+ rateLimiter: hasDeviceTokenCandidate ? undefined : rateLimiter,
231
+ clientIp,
246
232
  rateLimitScope: AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
247
- })
248
- : null;
249
- const sharedAuthOk = sharedAuthResult?.ok === true &&
250
- (sharedAuthResult.method === "token" || sharedAuthResult.method === "password");
233
+ });
234
+ if (hasDeviceTokenCandidate &&
235
+ nextAuthResult.ok &&
236
+ rateLimiter &&
237
+ (nextAuthResult.method === "token" || nextAuthResult.method === "password")) {
238
+ const sharedRateCheck = rateLimiter.check(clientIp, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET);
239
+ if (!sharedRateCheck.allowed) {
240
+ nextAuthResult = {
241
+ ok: false,
242
+ reason: "rate_limited",
243
+ rateLimited: true,
244
+ retryAfterMs: sharedRateCheck.retryAfterMs,
245
+ };
246
+ }
247
+ else {
248
+ rateLimiter.reset(clientIp, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET);
249
+ }
250
+ }
251
+ const nextAuthMethod = nextAuthResult.method ?? (resolvedAuth.mode === "password" ? "password" : "token");
252
+ const sharedAuthResult = hasSharedAuth
253
+ ? await authorizeHttpGatewayConnect({
254
+ auth: { ...resolvedAuth, allowTailscale: false },
255
+ connectAuth: connectParams.auth,
256
+ req: upgradeReq,
257
+ trustedProxies,
258
+ allowRealIpFallback,
259
+ // Shared-auth probe only; rate-limit side effects are handled in
260
+ // the primary auth flow (or deferred for device-token candidates).
261
+ rateLimitScope: AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
262
+ })
263
+ : null;
264
+ const nextSharedAuthOk = sharedAuthResult?.ok === true &&
265
+ (sharedAuthResult.method === "token" || sharedAuthResult.method === "password");
266
+ return {
267
+ authResult: nextAuthResult,
268
+ authOk: nextAuthResult.ok,
269
+ authMethod: nextAuthMethod,
270
+ sharedAuthOk: nextSharedAuthOk,
271
+ };
272
+ };
273
+ let { authResult, authOk, authMethod, sharedAuthOk } = await resolveAuthState();
251
274
  const rejectUnauthorized = (failedAuth) => {
252
275
  markHandshakeFailure("unauthorized", {
253
276
  authMode: resolvedAuth.mode,
@@ -274,37 +297,55 @@ export function attachGatewayWsMessageHandler(params) {
274
297
  sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, authMessage);
275
298
  close(1008, truncateCloseReason(authMessage));
276
299
  };
277
- if (!device) {
278
- if (scopes.length > 0 && !allowControlUiBypass) {
300
+ const clearUnboundScopes = () => {
301
+ if (scopes.length > 0 && !controlUiAuthPolicy.allowBypass) {
279
302
  scopes = [];
280
303
  connectParams.scopes = scopes;
281
304
  }
282
- const canSkipDevice = sharedAuthOk;
283
- if (isControlUi && !allowControlUiBypass) {
284
- const errorMessage = "control ui requires HTTPS or localhost (secure context)";
285
- markHandshakeFailure("control-ui-insecure-auth");
305
+ };
306
+ const handleMissingDeviceIdentity = () => {
307
+ if (!device) {
308
+ clearUnboundScopes();
309
+ }
310
+ const decision = evaluateMissingDeviceIdentity({
311
+ hasDeviceIdentity: Boolean(device),
312
+ role,
313
+ isControlUi,
314
+ controlUiAuthPolicy,
315
+ sharedAuthOk,
316
+ authOk,
317
+ hasSharedAuth,
318
+ isLocalClient,
319
+ });
320
+ if (decision.kind === "allow") {
321
+ return true;
322
+ }
323
+ if (decision.kind === "reject-control-ui-insecure-auth") {
324
+ const errorMessage = "control ui requires device identity (use HTTPS or localhost secure context)";
325
+ markHandshakeFailure("control-ui-insecure-auth", {
326
+ insecureAuthConfigured: controlUiAuthPolicy.allowInsecureAuthConfigured,
327
+ });
286
328
  sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, errorMessage);
287
329
  close(1008, errorMessage);
288
- return;
330
+ return false;
289
331
  }
290
- // Allow shared-secret authenticated connections (e.g., control-ui) to skip device identity
291
- if (!canSkipDevice) {
292
- if (!authOk && hasSharedAuth) {
293
- rejectUnauthorized(authResult);
294
- return;
295
- }
296
- markHandshakeFailure("device-required");
297
- sendHandshakeErrorResponse(ErrorCodes.NOT_PAIRED, "device identity required");
298
- close(1008, "device identity required");
299
- return;
332
+ if (decision.kind === "reject-unauthorized") {
333
+ rejectUnauthorized(authResult);
334
+ return false;
300
335
  }
336
+ markHandshakeFailure("device-required");
337
+ sendHandshakeErrorResponse(ErrorCodes.NOT_PAIRED, "device identity required");
338
+ close(1008, "device identity required");
339
+ return false;
340
+ };
341
+ if (!handleMissingDeviceIdentity()) {
342
+ return;
301
343
  }
302
344
  if (device) {
303
- const derivedId = deriveDeviceIdFromPublicKey(device.publicKey);
304
- if (!derivedId || derivedId !== device.id) {
345
+ const rejectDeviceAuthInvalid = (reason, message) => {
305
346
  setHandshakeState("failed");
306
347
  setCloseCause("device-auth-invalid", {
307
- reason: "device-id-mismatch",
348
+ reason,
308
349
  client: connectParams.client.id,
309
350
  deviceId: device.id,
310
351
  });
@@ -312,61 +353,29 @@ export function attachGatewayWsMessageHandler(params) {
312
353
  type: "res",
313
354
  id: frame.id,
314
355
  ok: false,
315
- error: errorShape(ErrorCodes.INVALID_REQUEST, "device identity mismatch"),
356
+ error: errorShape(ErrorCodes.INVALID_REQUEST, message),
316
357
  });
317
- close(1008, "device identity mismatch");
358
+ close(1008, message);
359
+ };
360
+ const derivedId = deriveDeviceIdFromPublicKey(device.publicKey);
361
+ if (!derivedId || derivedId !== device.id) {
362
+ rejectDeviceAuthInvalid("device-id-mismatch", "device identity mismatch");
318
363
  return;
319
364
  }
320
365
  const signedAt = device.signedAt;
321
366
  if (typeof signedAt !== "number" ||
322
367
  Math.abs(Date.now() - signedAt) > DEVICE_SIGNATURE_SKEW_MS) {
323
- setHandshakeState("failed");
324
- setCloseCause("device-auth-invalid", {
325
- reason: "device-signature-stale",
326
- client: connectParams.client.id,
327
- deviceId: device.id,
328
- });
329
- send({
330
- type: "res",
331
- id: frame.id,
332
- ok: false,
333
- error: errorShape(ErrorCodes.INVALID_REQUEST, "device signature expired"),
334
- });
335
- close(1008, "device signature expired");
368
+ rejectDeviceAuthInvalid("device-signature-stale", "device signature expired");
336
369
  return;
337
370
  }
338
371
  const nonceRequired = !isLocalClient;
339
372
  const providedNonce = typeof device.nonce === "string" ? device.nonce.trim() : "";
340
373
  if (nonceRequired && !providedNonce) {
341
- setHandshakeState("failed");
342
- setCloseCause("device-auth-invalid", {
343
- reason: "device-nonce-missing",
344
- client: connectParams.client.id,
345
- deviceId: device.id,
346
- });
347
- send({
348
- type: "res",
349
- id: frame.id,
350
- ok: false,
351
- error: errorShape(ErrorCodes.INVALID_REQUEST, "device nonce required"),
352
- });
353
- close(1008, "device nonce required");
374
+ rejectDeviceAuthInvalid("device-nonce-missing", "device nonce required");
354
375
  return;
355
376
  }
356
377
  if (providedNonce && providedNonce !== connectNonce) {
357
- setHandshakeState("failed");
358
- setCloseCause("device-auth-invalid", {
359
- reason: "device-nonce-mismatch",
360
- client: connectParams.client.id,
361
- deviceId: device.id,
362
- });
363
- send({
364
- type: "res",
365
- id: frame.id,
366
- ok: false,
367
- error: errorShape(ErrorCodes.INVALID_REQUEST, "device nonce mismatch"),
368
- });
369
- close(1008, "device nonce mismatch");
378
+ rejectDeviceAuthInvalid("device-nonce-mismatch", "device nonce mismatch");
370
379
  return;
371
380
  }
372
381
  const payload = buildDeviceAuthPayload({
@@ -380,21 +389,7 @@ export function attachGatewayWsMessageHandler(params) {
380
389
  nonce: providedNonce || undefined,
381
390
  version: providedNonce ? "v2" : "v1",
382
391
  });
383
- const rejectDeviceSignatureInvalid = () => {
384
- setHandshakeState("failed");
385
- setCloseCause("device-auth-invalid", {
386
- reason: "device-signature",
387
- client: connectParams.client.id,
388
- deviceId: device.id,
389
- });
390
- send({
391
- type: "res",
392
- id: frame.id,
393
- ok: false,
394
- error: errorShape(ErrorCodes.INVALID_REQUEST, "device signature invalid"),
395
- });
396
- close(1008, "device signature invalid");
397
- };
392
+ const rejectDeviceSignatureInvalid = () => rejectDeviceAuthInvalid("device-signature", "device signature invalid");
398
393
  const signatureOk = verifyDeviceSignature(device.publicKey, payload, device.signature);
399
394
  const allowLegacy = !nonceRequired && !providedNonce;
400
395
  if (!signatureOk && allowLegacy) {
@@ -422,19 +417,7 @@ export function attachGatewayWsMessageHandler(params) {
422
417
  }
423
418
  devicePublicKey = normalizeDevicePublicKeyBase64Url(device.publicKey);
424
419
  if (!devicePublicKey) {
425
- setHandshakeState("failed");
426
- setCloseCause("device-auth-invalid", {
427
- reason: "device-public-key",
428
- client: connectParams.client.id,
429
- deviceId: device.id,
430
- });
431
- send({
432
- type: "res",
433
- id: frame.id,
434
- ok: false,
435
- error: errorShape(ErrorCodes.INVALID_REQUEST, "device public key invalid"),
436
- });
437
- close(1008, "device public key invalid");
420
+ rejectDeviceAuthInvalid("device-public-key", "device public key invalid");
438
421
  return;
439
422
  }
440
423
  }
@@ -472,9 +455,28 @@ export function attachGatewayWsMessageHandler(params) {
472
455
  rejectUnauthorized(authResult);
473
456
  return;
474
457
  }
475
- const skipPairing = allowControlUiBypass && sharedAuthOk;
458
+ const skipPairing = shouldSkipControlUiPairing(controlUiAuthPolicy, sharedAuthOk);
476
459
  if (device && devicePublicKey && !skipPairing) {
477
- const requirePairing = async (reason, _paired) => {
460
+ const formatAuditList = (items) => {
461
+ if (!items || items.length === 0) {
462
+ return "<none>";
463
+ }
464
+ const out = new Set();
465
+ for (const item of items) {
466
+ const trimmed = item.trim();
467
+ if (trimmed) {
468
+ out.add(trimmed);
469
+ }
470
+ }
471
+ if (out.size === 0) {
472
+ return "<none>";
473
+ }
474
+ return [...out].toSorted().join(",");
475
+ };
476
+ const logUpgradeAudit = (reason, currentRoles, currentScopes) => {
477
+ logGateway.warn(`security audit: device access upgrade requested reason=${reason} device=${device.id} ip=${reportedClientIp ?? "unknown-ip"} auth=${authMethod} roleFrom=${formatAuditList(currentRoles)} roleTo=${role} scopesFrom=${formatAuditList(currentScopes)} scopesTo=${formatAuditList(scopes)} client=${connectParams.client.id} conn=${connId}`);
478
+ };
479
+ const requirePairing = async (reason) => {
478
480
  const pairing = await requestDevicePairing({
479
481
  deviceId: device.id,
480
482
  publicKey: devicePublicKey,
@@ -485,7 +487,7 @@ export function attachGatewayWsMessageHandler(params) {
485
487
  role,
486
488
  scopes,
487
489
  remoteIp: reportedClientIp,
488
- silent: isLocalClient,
490
+ silent: isLocalClient && reason === "not-paired",
489
491
  });
490
492
  const context = buildRequestContext();
491
493
  if (pairing.request.silent === true) {
@@ -532,32 +534,48 @@ export function attachGatewayWsMessageHandler(params) {
532
534
  }
533
535
  }
534
536
  else {
535
- const allowedRoles = new Set(Array.isArray(paired.roles) ? paired.roles : paired.role ? [paired.role] : []);
537
+ const pairedRoles = Array.isArray(paired.roles)
538
+ ? paired.roles
539
+ : paired.role
540
+ ? [paired.role]
541
+ : [];
542
+ const pairedScopes = Array.isArray(paired.scopes)
543
+ ? paired.scopes
544
+ : Array.isArray(paired.approvedScopes)
545
+ ? paired.approvedScopes
546
+ : [];
547
+ const allowedRoles = new Set(pairedRoles);
536
548
  if (allowedRoles.size === 0) {
537
- const ok = await requirePairing("role-upgrade", paired);
549
+ logUpgradeAudit("role-upgrade", pairedRoles, pairedScopes);
550
+ const ok = await requirePairing("role-upgrade");
538
551
  if (!ok) {
539
552
  return;
540
553
  }
541
554
  }
542
555
  else if (!allowedRoles.has(role)) {
543
- const ok = await requirePairing("role-upgrade", paired);
556
+ logUpgradeAudit("role-upgrade", pairedRoles, pairedScopes);
557
+ const ok = await requirePairing("role-upgrade");
544
558
  if (!ok) {
545
559
  return;
546
560
  }
547
561
  }
548
- const pairedScopes = Array.isArray(paired.scopes) ? paired.scopes : [];
549
562
  if (scopes.length > 0) {
550
563
  if (pairedScopes.length === 0) {
551
- const ok = await requirePairing("scope-upgrade", paired);
564
+ logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
565
+ const ok = await requirePairing("scope-upgrade");
552
566
  if (!ok) {
553
567
  return;
554
568
  }
555
569
  }
556
570
  else {
557
- const allowedScopes = new Set(pairedScopes);
558
- const missingScope = scopes.find((scope) => !allowedScopes.has(scope));
559
- if (missingScope) {
560
- const ok = await requirePairing("scope-upgrade", paired);
571
+ const scopesAllowed = roleScopesAllow({
572
+ role,
573
+ requestedScopes: scopes,
574
+ allowedScopes: pairedScopes,
575
+ });
576
+ if (!scopesAllowed) {
577
+ logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
578
+ const ok = await requirePairing("scope-upgrade");
561
579
  if (!ok) {
562
580
  return;
563
581
  }
@@ -630,6 +648,13 @@ export function attachGatewayWsMessageHandler(params) {
630
648
  snapshot.health = cachedHealth;
631
649
  snapshot.stateVersion.health = getHealthVersion();
632
650
  }
651
+ const canvasCapability = role === "node" && canvasHostUrl ? mintCanvasCapabilityToken() : undefined;
652
+ const canvasCapabilityExpiresAtMs = canvasCapability
653
+ ? Date.now() + CANVAS_CAPABILITY_TTL_MS
654
+ : undefined;
655
+ const scopedCanvasHostUrl = canvasHostUrl && canvasCapability
656
+ ? (buildCanvasScopedHostUrl(canvasHostUrl, canvasCapability) ?? canvasHostUrl)
657
+ : canvasHostUrl;
633
658
  const helloOk = {
634
659
  type: "hello-ok",
635
660
  protocol: PROTOCOL_VERSION,
@@ -641,7 +666,7 @@ export function attachGatewayWsMessageHandler(params) {
641
666
  },
642
667
  features: { methods: gatewayMethods, events },
643
668
  snapshot,
644
- canvasHostUrl,
669
+ canvasHostUrl: scopedCanvasHostUrl,
645
670
  auth: deviceToken
646
671
  ? {
647
672
  deviceToken: deviceToken.token,
@@ -663,6 +688,8 @@ export function attachGatewayWsMessageHandler(params) {
663
688
  connId,
664
689
  presenceKey,
665
690
  clientIp: reportedClientIp,
691
+ canvasCapability,
692
+ canvasCapabilityExpiresAtMs,
666
693
  };
667
694
  setClient(nextClient);
668
695
  setHandshakeState("connected");