@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
@@ -2,11 +2,13 @@ import { randomUUID } from "node:crypto";
2
2
  import { WebSocket } from "ws";
3
3
  import { clearDeviceAuthToken, loadDeviceAuthToken, storeDeviceAuthToken, } from "../infra/device-auth-store.js";
4
4
  import { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload, } from "../infra/device-identity.js";
5
+ import { clearDevicePairing } from "../infra/device-pairing.js";
5
6
  import { normalizeFingerprint } from "../infra/tls/fingerprint.js";
6
7
  import { rawDataToString } from "../infra/ws.js";
7
8
  import { logDebug, logError } from "../logger.js";
8
9
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, } from "../utils/message-channel.js";
9
10
  import { buildDeviceAuthPayload } from "./device-auth.js";
11
+ import { isSecureWebSocketUrl } from "./net.js";
10
12
  import { PROTOCOL_VERSION, validateEventFrame, validateRequestFrame, validateResponseFrame, } from "./protocol/index.js";
11
13
  export const GATEWAY_CLOSE_CODE_HINTS = {
12
14
  1000: "normal closure",
@@ -46,6 +48,24 @@ export class GatewayClient {
46
48
  this.opts.onConnectError?.(new Error("gateway tls fingerprint requires wss:// gateway url"));
47
49
  return;
48
50
  }
51
+ // Security check: block ALL plaintext ws:// to non-loopback addresses (CWE-319, CVSS 9.8)
52
+ // This protects both credentials AND chat/conversation data from MITM attacks.
53
+ // Device tokens may be loaded later in sendConnect(), so we block regardless of hasCredentials.
54
+ if (!isSecureWebSocketUrl(url)) {
55
+ // Safe hostname extraction - avoid throwing on malformed URLs in error path
56
+ let displayHost = url;
57
+ try {
58
+ displayHost = new URL(url).hostname || url;
59
+ }
60
+ catch {
61
+ // Use raw URL if parsing fails
62
+ }
63
+ const error = new Error(`SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. ` +
64
+ "Both credentials and chat data would be exposed to network interception. " +
65
+ "Use wss:// for the gateway URL, or connect via SSH tunnel to localhost.");
66
+ this.opts.onConnectError?.(error);
67
+ return;
68
+ }
49
69
  // Allow node screen snapshots and other large responses.
50
70
  const wsOptions = {
51
71
  maxPayload: 25 * 1024 * 1024,
@@ -87,17 +107,21 @@ export class GatewayClient {
87
107
  this.ws.on("close", (code, reason) => {
88
108
  const reasonText = rawDataToString(reason);
89
109
  this.ws = null;
90
- // If closed due to device token mismatch, clear the stored token so next attempt can get a fresh one
110
+ // If closed due to device token mismatch, clear the stored token and pairing so next attempt can get a fresh one
91
111
  if (code === 1008 &&
92
112
  reasonText.toLowerCase().includes("device token mismatch") &&
93
113
  this.opts.deviceIdentity) {
114
+ const deviceId = this.opts.deviceIdentity.deviceId;
94
115
  const role = this.opts.role ?? "operator";
95
116
  try {
96
- clearDeviceAuthToken({ deviceId: this.opts.deviceIdentity.deviceId, role });
97
- logDebug(`cleared stale device-auth token for device ${this.opts.deviceIdentity.deviceId}`);
117
+ clearDeviceAuthToken({ deviceId, role });
118
+ void clearDevicePairing(deviceId).catch((err) => {
119
+ logDebug(`failed clearing stale device pairing for device ${deviceId}: ${String(err)}`);
120
+ });
121
+ logDebug(`cleared stale device-auth token for device ${deviceId}`);
98
122
  }
99
123
  catch (err) {
100
- logDebug(`failed clearing stale device-auth token for device ${this.opts.deviceIdentity.deviceId}: ${String(err)}`);
124
+ logDebug(`failed clearing stale device-auth token for device ${deviceId}: ${String(err)}`);
101
125
  }
102
126
  }
103
127
  this.flushPendingErrors(new Error(`gateway closed (${code}): ${reasonText}`));
@@ -1,3 +1,4 @@
1
+ import { isDeepStrictEqual } from "node:util";
1
2
  import chokidar from "chokidar";
2
3
  import { listChannelPlugins } from "../channels/plugins/index.js";
3
4
  import { getActivePluginRegistry } from "../plugins/runtime.js";
@@ -102,10 +103,8 @@ export function diffConfigPaths(prev, next, prefix = "") {
102
103
  }
103
104
  return paths;
104
105
  }
105
- if (Array.isArray(prev) && Array.isArray(next)) {
106
- if (prev.length === next.length && prev.every((val, idx) => val === next[idx])) {
107
- return [];
108
- }
106
+ if (isDeepStrictEqual(prev, next)) {
107
+ return [];
109
108
  }
110
109
  return [prefix || "<root>"];
111
110
  }
@@ -1,35 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { fileURLToPath } from "node:url";
3
+ import { resolveControlUiRootSync } from "../infra/control-ui-assets.js";
4
+ import { isWithinDir } from "../infra/path-safety.js";
4
5
  import { DEFAULT_ASSISTANT_IDENTITY, resolveAssistantIdentity } from "./assistant-identity.js";
6
+ import { CONTROL_UI_BOOTSTRAP_CONFIG_PATH, } from "./control-ui-contract.js";
7
+ import { buildControlUiCspHeader } from "./control-ui-csp.js";
5
8
  import { buildControlUiAvatarUrl, CONTROL_UI_AVATAR_PREFIX, normalizeControlUiBasePath, resolveAssistantAvatarUrl, } from "./control-ui-shared.js";
6
9
  const ROOT_PREFIX = "/";
7
- function resolveControlUiRoot() {
8
- const here = path.dirname(fileURLToPath(import.meta.url));
9
- const execDir = (() => {
10
- try {
11
- return path.dirname(fs.realpathSync(process.execPath));
12
- }
13
- catch {
14
- return null;
15
- }
16
- })();
17
- const candidates = [
18
- // Packaged app: control-ui lives alongside the executable.
19
- execDir ? path.resolve(execDir, "control-ui") : null,
20
- // Running from dist: dist/gateway/control-ui.js -> dist/control-ui
21
- path.resolve(here, "../control-ui"),
22
- // Running from source: src/gateway/control-ui.ts -> dist/control-ui
23
- path.resolve(here, "../../dist/control-ui"),
24
- // Fallback to cwd (dev)
25
- path.resolve(process.cwd(), "dist", "control-ui"),
26
- ].filter((dir) => Boolean(dir));
27
- for (const dir of candidates) {
28
- if (fs.existsSync(path.join(dir, "index.html")))
29
- return dir;
30
- }
31
- return null;
32
- }
33
10
  function contentTypeForExt(ext) {
34
11
  switch (ext) {
35
12
  case ".html":
@@ -60,6 +37,33 @@ function contentTypeForExt(ext) {
60
37
  return "application/octet-stream";
61
38
  }
62
39
  }
40
+ /**
41
+ * Extensions recognised as static assets. Missing files with these extensions
42
+ * return 404 instead of the SPA index.html fallback. `.html` is intentionally
43
+ * excluded — actual HTML files on disk are served earlier, and missing `.html`
44
+ * paths should fall through to the SPA router (client-side routers may use
45
+ * `.html`-suffixed routes).
46
+ */
47
+ const STATIC_ASSET_EXTENSIONS = new Set([
48
+ ".js",
49
+ ".css",
50
+ ".json",
51
+ ".map",
52
+ ".svg",
53
+ ".png",
54
+ ".jpg",
55
+ ".jpeg",
56
+ ".gif",
57
+ ".webp",
58
+ ".ico",
59
+ ".txt",
60
+ ]);
61
+ function applyControlUiSecurityHeaders(res) {
62
+ res.setHeader("X-Frame-Options", "DENY");
63
+ res.setHeader("Content-Security-Policy", buildControlUiCspHeader());
64
+ res.setHeader("X-Content-Type-Options", "nosniff");
65
+ res.setHeader("Referrer-Policy", "no-referrer");
66
+ }
63
67
  function sendJson(res, status, body) {
64
68
  res.statusCode = status;
65
69
  res.setHeader("Content-Type", "application/json; charset=utf-8");
@@ -71,18 +75,22 @@ function isValidAgentId(agentId) {
71
75
  }
72
76
  export function handleControlUiAvatarRequest(req, res, opts) {
73
77
  const urlRaw = req.url;
74
- if (!urlRaw)
78
+ if (!urlRaw) {
75
79
  return false;
76
- if (req.method !== "GET" && req.method !== "HEAD")
80
+ }
81
+ if (req.method !== "GET" && req.method !== "HEAD") {
77
82
  return false;
83
+ }
78
84
  const url = new URL(urlRaw, "http://localhost");
79
85
  const basePath = normalizeControlUiBasePath(opts.basePath);
80
86
  const pathname = url.pathname;
81
87
  const pathWithBase = basePath
82
88
  ? `${basePath}${CONTROL_UI_AVATAR_PREFIX}/`
83
89
  : `${CONTROL_UI_AVATAR_PREFIX}/`;
84
- if (!pathname.startsWith(pathWithBase))
90
+ if (!pathname.startsWith(pathWithBase)) {
85
91
  return false;
92
+ }
93
+ applyControlUiSecurityHeaders(res);
86
94
  const agentIdParts = pathname.slice(pathWithBase.length).split("/").filter(Boolean);
87
95
  const agentId = agentIdParts[0] ?? "";
88
96
  if (agentIdParts.length !== 1 || !agentId || !isValidAgentId(agentId)) {
@@ -119,66 +127,93 @@ function respondNotFound(res) {
119
127
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
120
128
  res.end("Not Found");
121
129
  }
122
- function serveFile(res, filePath) {
130
+ function setStaticFileHeaders(res, filePath) {
123
131
  const ext = path.extname(filePath).toLowerCase();
124
132
  res.setHeader("Content-Type", contentTypeForExt(ext));
125
133
  // Static UI should never be cached aggressively while iterating; allow the
126
134
  // browser to revalidate.
127
135
  res.setHeader("Cache-Control", "no-cache");
136
+ }
137
+ function serveFile(res, filePath) {
138
+ setStaticFileHeaders(res, filePath);
128
139
  res.end(fs.readFileSync(filePath));
129
140
  }
130
- function injectControlUiConfig(html, opts) {
131
- const { basePath, assistantName, assistantAvatar } = opts;
132
- const script = `<script>` +
133
- `window.__CLAWDBOT_CONTROL_UI_BASE_PATH__=${JSON.stringify(basePath)};` +
134
- `window.__CLAWDBOT_ASSISTANT_NAME__=${JSON.stringify(assistantName ?? DEFAULT_ASSISTANT_IDENTITY.name)};` +
135
- `window.__CLAWDBOT_ASSISTANT_AVATAR__=${JSON.stringify(assistantAvatar ?? DEFAULT_ASSISTANT_IDENTITY.avatar)};` +
136
- `</script>`;
137
- // Check if already injected
138
- if (html.includes("__CLAWDBOT_ASSISTANT_NAME__"))
139
- return html;
140
- const headClose = html.indexOf("</head>");
141
- if (headClose !== -1) {
142
- return `${html.slice(0, headClose)}${script}${html.slice(headClose)}`;
143
- }
144
- return `${script}${html}`;
141
+ function serveResolvedFile(res, filePath, body) {
142
+ setStaticFileHeaders(res, filePath);
143
+ res.end(body);
145
144
  }
146
- function serveIndexHtml(res, indexPath, opts) {
147
- const { basePath, config, agentId } = opts;
148
- const identity = config
149
- ? resolveAssistantIdentity({ cfg: config, agentId })
150
- : DEFAULT_ASSISTANT_IDENTITY;
151
- const resolvedAgentId = typeof identity.agentId === "string"
152
- ? identity.agentId
153
- : agentId;
154
- const avatarValue = resolveAssistantAvatarUrl({
155
- avatar: identity.avatar,
156
- agentId: resolvedAgentId,
157
- basePath,
158
- }) ?? identity.avatar;
145
+ function serveResolvedIndexHtml(res, body) {
159
146
  res.setHeader("Content-Type", "text/html; charset=utf-8");
160
147
  res.setHeader("Cache-Control", "no-cache");
161
- const raw = fs.readFileSync(indexPath, "utf8");
162
- res.end(injectControlUiConfig(raw, {
163
- basePath,
164
- assistantName: identity.name,
165
- assistantAvatar: avatarValue,
166
- }));
148
+ res.end(body);
149
+ }
150
+ function isContainedPath(baseDir, targetPath) {
151
+ const relative = path.relative(baseDir, targetPath);
152
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
153
+ }
154
+ function isExpectedSafePathError(error) {
155
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
156
+ return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
157
+ }
158
+ function areSameFileIdentity(preOpen, opened) {
159
+ return preOpen.dev === opened.dev && preOpen.ino === opened.ino;
160
+ }
161
+ function resolveSafeControlUiFile(rootReal, filePath) {
162
+ let fd = null;
163
+ try {
164
+ const fileReal = fs.realpathSync(filePath);
165
+ if (!isContainedPath(rootReal, fileReal)) {
166
+ return null;
167
+ }
168
+ const preOpenStat = fs.lstatSync(fileReal);
169
+ if (!preOpenStat.isFile()) {
170
+ return null;
171
+ }
172
+ const openFlags = fs.constants.O_RDONLY |
173
+ (typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0);
174
+ fd = fs.openSync(fileReal, openFlags);
175
+ const openedStat = fs.fstatSync(fd);
176
+ // Compare inode identity so swaps between validation and open are rejected.
177
+ if (!openedStat.isFile() || !areSameFileIdentity(preOpenStat, openedStat)) {
178
+ return null;
179
+ }
180
+ const resolved = { path: fileReal, fd };
181
+ fd = null;
182
+ return resolved;
183
+ }
184
+ catch (error) {
185
+ if (isExpectedSafePathError(error)) {
186
+ return null;
187
+ }
188
+ throw error;
189
+ }
190
+ finally {
191
+ if (fd !== null) {
192
+ fs.closeSync(fd);
193
+ }
194
+ }
167
195
  }
168
196
  function isSafeRelativePath(relPath) {
169
- if (!relPath)
197
+ if (!relPath) {
170
198
  return false;
199
+ }
171
200
  const normalized = path.posix.normalize(relPath);
172
- if (normalized.startsWith("../") || normalized === "..")
201
+ if (path.posix.isAbsolute(normalized) || path.win32.isAbsolute(normalized)) {
173
202
  return false;
174
- if (normalized.includes("\0"))
203
+ }
204
+ if (normalized.startsWith("../") || normalized === "..") {
205
+ return false;
206
+ }
207
+ if (normalized.includes("\0")) {
175
208
  return false;
209
+ }
176
210
  return true;
177
211
  }
178
212
  export function handleControlUiHttpRequest(req, res, opts) {
179
213
  const urlRaw = req.url;
180
- if (!urlRaw)
214
+ if (!urlRaw) {
181
215
  return false;
216
+ }
182
217
  if (req.method !== "GET" && req.method !== "HEAD") {
183
218
  res.statusCode = 405;
184
219
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
@@ -190,41 +225,104 @@ export function handleControlUiHttpRequest(req, res, opts) {
190
225
  const pathname = url.pathname;
191
226
  if (!basePath) {
192
227
  if (pathname === "/ui" || pathname.startsWith("/ui/")) {
228
+ applyControlUiSecurityHeaders(res);
193
229
  respondNotFound(res);
194
230
  return true;
195
231
  }
196
232
  }
197
233
  if (basePath) {
198
234
  if (pathname === basePath) {
235
+ applyControlUiSecurityHeaders(res);
199
236
  res.statusCode = 302;
200
237
  res.setHeader("Location", `${basePath}/${url.search}`);
201
238
  res.end();
202
239
  return true;
203
240
  }
204
- if (!pathname.startsWith(`${basePath}/`))
241
+ if (!pathname.startsWith(`${basePath}/`)) {
205
242
  return false;
243
+ }
206
244
  }
207
- const root = (() => {
208
- if (opts?.root) {
209
- if (opts.root.kind === "resolved")
210
- return opts.root.path;
211
- return null;
245
+ applyControlUiSecurityHeaders(res);
246
+ const bootstrapConfigPath = basePath
247
+ ? `${basePath}${CONTROL_UI_BOOTSTRAP_CONFIG_PATH}`
248
+ : CONTROL_UI_BOOTSTRAP_CONFIG_PATH;
249
+ if (pathname === bootstrapConfigPath) {
250
+ const config = opts?.config;
251
+ const identity = config
252
+ ? resolveAssistantIdentity({ cfg: config, agentId: opts?.agentId })
253
+ : DEFAULT_ASSISTANT_IDENTITY;
254
+ const avatarValue = resolveAssistantAvatarUrl({
255
+ avatar: identity.avatar,
256
+ agentId: identity.agentId,
257
+ basePath,
258
+ });
259
+ if (req.method === "HEAD") {
260
+ res.statusCode = 200;
261
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
262
+ res.setHeader("Cache-Control", "no-cache");
263
+ res.end();
264
+ return true;
212
265
  }
213
- return resolveControlUiRoot();
214
- })();
266
+ sendJson(res, 200, {
267
+ basePath,
268
+ assistantName: identity.name,
269
+ assistantAvatar: avatarValue ?? identity.avatar,
270
+ assistantAgentId: identity.agentId,
271
+ });
272
+ return true;
273
+ }
274
+ const rootState = opts?.root;
275
+ if (rootState?.kind === "invalid") {
276
+ res.statusCode = 503;
277
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
278
+ res.end(`Control UI assets not found at ${rootState.path}. Build them with \`pnpm ui:build\` (auto-installs UI deps), or update gateway.controlUi.root.`);
279
+ return true;
280
+ }
281
+ if (rootState?.kind === "missing") {
282
+ res.statusCode = 503;
283
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
284
+ res.end("Control UI assets not found. Build them with `pnpm ui:build` (auto-installs UI deps), or run `pnpm ui:dev` during development.");
285
+ return true;
286
+ }
287
+ const root = rootState?.kind === "resolved"
288
+ ? rootState.path
289
+ : resolveControlUiRootSync({
290
+ moduleUrl: import.meta.url,
291
+ argv1: process.argv[1],
292
+ cwd: process.cwd(),
293
+ });
215
294
  if (!root) {
216
295
  res.statusCode = 503;
217
296
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
218
297
  res.end("Control UI assets not found. Build them with `pnpm ui:build` (auto-installs UI deps), or run `pnpm ui:dev` during development.");
219
298
  return true;
220
299
  }
300
+ const rootReal = (() => {
301
+ try {
302
+ return fs.realpathSync(root);
303
+ }
304
+ catch (error) {
305
+ if (isExpectedSafePathError(error)) {
306
+ return null;
307
+ }
308
+ throw error;
309
+ }
310
+ })();
311
+ if (!rootReal) {
312
+ res.statusCode = 503;
313
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
314
+ res.end("Control UI assets not found. Build them with `pnpm ui:build` (auto-installs UI deps), or run `pnpm ui:dev` during development.");
315
+ return true;
316
+ }
221
317
  const uiPath = basePath && pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) : pathname;
222
318
  const rel = (() => {
223
- if (uiPath === ROOT_PREFIX)
319
+ if (uiPath === ROOT_PREFIX) {
224
320
  return "";
321
+ }
225
322
  const assetsIndex = uiPath.indexOf("/assets/");
226
- if (assetsIndex >= 0)
323
+ if (assetsIndex >= 0) {
227
324
  return uiPath.slice(assetsIndex + 1);
325
+ }
228
326
  return uiPath.slice(1);
229
327
  })();
230
328
  const requested = rel && !rel.endsWith("/") ? rel : `${rel}index.html`;
@@ -233,32 +331,57 @@ export function handleControlUiHttpRequest(req, res, opts) {
233
331
  respondNotFound(res);
234
332
  return true;
235
333
  }
236
- const filePath = path.join(root, fileRel);
237
- if (!filePath.startsWith(root)) {
334
+ const filePath = path.resolve(root, fileRel);
335
+ if (!isWithinDir(root, filePath)) {
238
336
  respondNotFound(res);
239
337
  return true;
240
338
  }
241
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
242
- if (path.basename(filePath) === "index.html") {
243
- serveIndexHtml(res, filePath, {
244
- basePath,
245
- config: opts?.config,
246
- agentId: opts?.agentId,
247
- });
339
+ const safeFile = resolveSafeControlUiFile(rootReal, filePath);
340
+ if (safeFile) {
341
+ try {
342
+ if (req.method === "HEAD") {
343
+ res.statusCode = 200;
344
+ setStaticFileHeaders(res, safeFile.path);
345
+ res.end();
346
+ return true;
347
+ }
348
+ if (path.basename(safeFile.path) === "index.html") {
349
+ serveResolvedIndexHtml(res, fs.readFileSync(safeFile.fd, "utf8"));
350
+ return true;
351
+ }
352
+ serveResolvedFile(res, safeFile.path, fs.readFileSync(safeFile.fd));
248
353
  return true;
249
354
  }
250
- serveFile(res, filePath);
355
+ finally {
356
+ fs.closeSync(safeFile.fd);
357
+ }
358
+ }
359
+ // If the requested path looks like a static asset (known extension), return
360
+ // 404 rather than falling through to the SPA index.html fallback. We check
361
+ // against the same set of extensions that contentTypeForExt() recognises so
362
+ // that dotted SPA routes (e.g. /user/jane.doe, /v2.0) still get the
363
+ // client-side router fallback.
364
+ if (STATIC_ASSET_EXTENSIONS.has(path.extname(fileRel).toLowerCase())) {
365
+ respondNotFound(res);
251
366
  return true;
252
367
  }
253
368
  // SPA fallback (client-side router): serve index.html for unknown paths.
254
369
  const indexPath = path.join(root, "index.html");
255
- if (fs.existsSync(indexPath)) {
256
- serveIndexHtml(res, indexPath, {
257
- basePath,
258
- config: opts?.config,
259
- agentId: opts?.agentId,
260
- });
261
- return true;
370
+ const safeIndex = resolveSafeControlUiFile(rootReal, indexPath);
371
+ if (safeIndex) {
372
+ try {
373
+ if (req.method === "HEAD") {
374
+ res.statusCode = 200;
375
+ setStaticFileHeaders(res, safeIndex.path);
376
+ res.end();
377
+ return true;
378
+ }
379
+ serveResolvedIndexHtml(res, fs.readFileSync(safeIndex.fd, "utf8"));
380
+ return true;
381
+ }
382
+ finally {
383
+ fs.closeSync(safeIndex.fd);
384
+ }
262
385
  }
263
386
  respondNotFound(res);
264
387
  return true;