@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
@@ -1,23 +1,31 @@
1
+ import crypto from "node:crypto";
1
2
  import { startBrowserBridgeServer, stopBrowserBridgeServer } from "../../browser/bridge-server.js";
2
3
  import { resolveProfile } from "../../browser/config.js";
3
- import { DEFAULT_BROWSER_EVALUATE_ENABLED, DEFAULT_CLAWD_BROWSER_COLOR, } from "../../browser/constants.js";
4
+ import { DEFAULT_BROWSER_EVALUATE_ENABLED, DEFAULT_CLAWD_BROWSER_COLOR, DEFAULT_CLAWD_BROWSER_PROFILE_NAME, } from "../../browser/constants.js";
5
+ import { defaultRuntime } from "../../runtime.js";
4
6
  import { BROWSER_BRIDGES } from "./browser-bridges.js";
5
- import { DEFAULT_SANDBOX_BROWSER_IMAGE, SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants.js";
6
- import { buildSandboxCreateArgs, dockerContainerState, execDocker, readDockerPort, } from "./docker.js";
7
- import { updateBrowserRegistry } from "./registry.js";
8
- import { slugifySessionKey } from "./shared.js";
7
+ import { computeSandboxBrowserConfigHash } from "./config-hash.js";
8
+ import { resolveSandboxBrowserDockerCreateConfig } from "./config.js";
9
+ import { DEFAULT_SANDBOX_BROWSER_IMAGE, SANDBOX_AGENT_WORKSPACE_MOUNT, SANDBOX_BROWSER_SECURITY_HASH_EPOCH, } from "./constants.js";
10
+ import { buildSandboxCreateArgs, dockerContainerState, execDocker, readDockerContainerEnvVar, readDockerContainerLabel, readDockerPort, } from "./docker.js";
11
+ import { buildNoVncDirectUrl, buildNoVncObserverTokenUrl, consumeNoVncObserverToken, generateNoVncPassword, isNoVncEnabled, NOVNC_PASSWORD_ENV_KEY, issueNoVncObserverToken, } from "./novnc-auth.js";
12
+ import { readBrowserRegistry, updateBrowserRegistry } from "./registry.js";
13
+ import { resolveSandboxAgentId, slugifySessionKey } from "./shared.js";
9
14
  import { isToolAllowed } from "./tool-policy.js";
15
+ const HOT_BROWSER_WINDOW_MS = 5 * 60 * 1000;
16
+ const CDP_SOURCE_RANGE_ENV_KEY = "POOLBOT_BROWSER_CDP_SOURCE_RANGE";
10
17
  async function waitForSandboxCdp(params) {
11
18
  const deadline = Date.now() + Math.max(0, params.timeoutMs);
12
19
  const url = `http://127.0.0.1:${params.cdpPort}/json/version`;
13
20
  while (Date.now() < deadline) {
14
21
  try {
15
22
  const ctrl = new AbortController();
16
- const t = setTimeout(() => ctrl.abort(), 1000);
23
+ const t = setTimeout(ctrl.abort.bind(ctrl), 1000);
17
24
  try {
18
25
  const res = await fetch(url, { signal: ctrl.signal });
19
- if (res.ok)
26
+ if (res.ok) {
20
27
  return true;
28
+ }
21
29
  }
22
30
  finally {
23
31
  clearTimeout(t);
@@ -46,9 +54,13 @@ function buildSandboxBrowserResolvedConfig(params) {
46
54
  headless: params.headless,
47
55
  noSandbox: false,
48
56
  attachOnly: true,
49
- defaultProfile: "clawd",
57
+ defaultProfile: DEFAULT_CLAWD_BROWSER_PROFILE_NAME,
58
+ extraArgs: [],
50
59
  profiles: {
51
- clawd: { cdpPort: params.cdpPort, color: DEFAULT_CLAWD_BROWSER_COLOR },
60
+ [DEFAULT_CLAWD_BROWSER_PROFILE_NAME]: {
61
+ cdpPort: params.cdpPort,
62
+ color: DEFAULT_CLAWD_BROWSER_COLOR,
63
+ },
52
64
  },
53
65
  };
54
66
  }
@@ -56,26 +68,115 @@ async function ensureSandboxBrowserImage(image) {
56
68
  const result = await execDocker(["image", "inspect", image], {
57
69
  allowFailure: true,
58
70
  });
59
- if (result.code === 0)
71
+ if (result.code === 0) {
60
72
  return;
73
+ }
61
74
  throw new Error(`Sandbox browser image not found: ${image}. Build it with scripts/sandbox-browser-setup.sh.`);
62
75
  }
76
+ async function ensureDockerNetwork(network) {
77
+ const normalized = network.trim().toLowerCase();
78
+ if (!normalized ||
79
+ normalized === "bridge" ||
80
+ normalized === "none" ||
81
+ normalized.startsWith("container:")) {
82
+ return;
83
+ }
84
+ const inspect = await execDocker(["network", "inspect", network], { allowFailure: true });
85
+ if (inspect.code === 0) {
86
+ return;
87
+ }
88
+ await execDocker(["network", "create", "--driver", "bridge", network]);
89
+ }
63
90
  export async function ensureSandboxBrowser(params) {
64
- if (!params.cfg.browser.enabled)
91
+ if (!params.cfg.browser.enabled) {
65
92
  return null;
66
- if (!isToolAllowed(params.cfg.tools, "browser"))
93
+ }
94
+ if (!isToolAllowed(params.cfg.tools, "browser")) {
67
95
  return null;
96
+ }
68
97
  const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(params.scopeKey);
69
98
  const name = `${params.cfg.browser.containerPrefix}${slug}`;
70
99
  const containerName = name.slice(0, 63);
71
100
  const state = await dockerContainerState(containerName);
72
- if (!state.exists) {
73
- await ensureSandboxBrowserImage(params.cfg.browser.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE);
101
+ const browserImage = params.cfg.browser.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE;
102
+ const cdpSourceRange = params.cfg.browser.cdpSourceRange?.trim() || undefined;
103
+ const browserDockerCfg = resolveSandboxBrowserDockerCreateConfig({
104
+ docker: params.cfg.docker,
105
+ browser: { ...params.cfg.browser, image: browserImage },
106
+ });
107
+ const expectedHash = computeSandboxBrowserConfigHash({
108
+ docker: browserDockerCfg,
109
+ browser: {
110
+ cdpPort: params.cfg.browser.cdpPort,
111
+ vncPort: params.cfg.browser.vncPort,
112
+ noVncPort: params.cfg.browser.noVncPort,
113
+ headless: params.cfg.browser.headless,
114
+ enableNoVnc: params.cfg.browser.enableNoVnc,
115
+ cdpSourceRange,
116
+ },
117
+ securityEpoch: SANDBOX_BROWSER_SECURITY_HASH_EPOCH,
118
+ workspaceAccess: params.cfg.workspaceAccess,
119
+ workspaceDir: params.workspaceDir,
120
+ agentWorkspaceDir: params.agentWorkspaceDir,
121
+ });
122
+ const now = Date.now();
123
+ let hasContainer = state.exists;
124
+ let running = state.running;
125
+ let currentHash = null;
126
+ let hashMismatch = false;
127
+ const noVncEnabled = isNoVncEnabled(params.cfg.browser);
128
+ let noVncPassword;
129
+ if (hasContainer) {
130
+ if (noVncEnabled) {
131
+ noVncPassword =
132
+ (await readDockerContainerEnvVar(containerName, NOVNC_PASSWORD_ENV_KEY)) ?? undefined;
133
+ }
134
+ const registry = await readBrowserRegistry();
135
+ const registryEntry = registry.entries.find((entry) => entry.containerName === containerName);
136
+ currentHash = await readDockerContainerLabel(containerName, "poolbot.configHash");
137
+ hashMismatch = !currentHash || currentHash !== expectedHash;
138
+ if (!currentHash) {
139
+ currentHash = registryEntry?.configHash ?? null;
140
+ hashMismatch = !currentHash || currentHash !== expectedHash;
141
+ }
142
+ if (hashMismatch) {
143
+ const lastUsedAtMs = registryEntry?.lastUsedAtMs;
144
+ const isHot = running && (typeof lastUsedAtMs !== "number" || now - lastUsedAtMs < HOT_BROWSER_WINDOW_MS);
145
+ if (isHot) {
146
+ const hint = (() => {
147
+ if (params.cfg.scope === "session") {
148
+ return `poolbot sandbox recreate --browser --session ${params.scopeKey}`;
149
+ }
150
+ if (params.cfg.scope === "agent") {
151
+ const agentId = resolveSandboxAgentId(params.scopeKey) ?? "main";
152
+ return `poolbot sandbox recreate --browser --agent ${agentId}`;
153
+ }
154
+ return "poolbot sandbox recreate --browser --all";
155
+ })();
156
+ defaultRuntime.log(`Sandbox browser config changed for ${containerName} (recently used). Recreate to apply: ${hint}`);
157
+ }
158
+ else {
159
+ await execDocker(["rm", "-f", containerName], { allowFailure: true });
160
+ hasContainer = false;
161
+ running = false;
162
+ }
163
+ }
164
+ }
165
+ if (!hasContainer) {
166
+ if (noVncEnabled) {
167
+ noVncPassword = generateNoVncPassword();
168
+ }
169
+ await ensureDockerNetwork(browserDockerCfg.network);
170
+ await ensureSandboxBrowserImage(browserImage);
74
171
  const args = buildSandboxCreateArgs({
75
172
  name: containerName,
76
- cfg: params.cfg.docker,
173
+ cfg: browserDockerCfg,
77
174
  scopeKey: params.scopeKey,
78
- labels: { "poolbot.sandboxBrowser": "1" },
175
+ labels: {
176
+ "poolbot.sandboxBrowser": "1",
177
+ "poolbot.browserConfigEpoch": SANDBOX_BROWSER_SECURITY_HASH_EPOCH,
178
+ },
179
+ configHash: expectedHash,
79
180
  });
80
181
  const mainMountSuffix = params.cfg.workspaceAccess === "ro" && params.workspaceDir === params.agentWorkspaceDir
81
182
  ? ":ro"
@@ -86,48 +187,75 @@ export async function ensureSandboxBrowser(params) {
86
187
  args.push("-v", `${params.agentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`);
87
188
  }
88
189
  args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
89
- if (params.cfg.browser.enableNoVnc && !params.cfg.browser.headless) {
190
+ if (noVncEnabled) {
90
191
  args.push("-p", `127.0.0.1::${params.cfg.browser.noVncPort}`);
91
192
  }
92
193
  args.push("-e", `POOLBOT_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`);
93
- args.push("-e", `CLAWDBOT_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`);
94
194
  args.push("-e", `POOLBOT_BROWSER_ENABLE_NOVNC=${params.cfg.browser.enableNoVnc ? "1" : "0"}`);
95
- args.push("-e", `CLAWDBOT_BROWSER_ENABLE_NOVNC=${params.cfg.browser.enableNoVnc ? "1" : "0"}`);
96
195
  args.push("-e", `POOLBOT_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
97
- args.push("-e", `CLAWDBOT_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
196
+ if (cdpSourceRange) {
197
+ args.push("-e", `${CDP_SOURCE_RANGE_ENV_KEY}=${cdpSourceRange}`);
198
+ }
98
199
  args.push("-e", `POOLBOT_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
99
- args.push("-e", `CLAWDBOT_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
100
200
  args.push("-e", `POOLBOT_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`);
101
- args.push("-e", `CLAWDBOT_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`);
102
- args.push(params.cfg.browser.image);
201
+ if (noVncEnabled && noVncPassword) {
202
+ args.push("-e", `${NOVNC_PASSWORD_ENV_KEY}=${noVncPassword}`);
203
+ }
204
+ args.push(browserImage);
103
205
  await execDocker(args);
104
206
  await execDocker(["start", containerName]);
105
207
  }
106
- else if (!state.running) {
208
+ else if (!running) {
107
209
  await execDocker(["start", containerName]);
108
210
  }
109
211
  const mappedCdp = await readDockerPort(containerName, params.cfg.browser.cdpPort);
110
212
  if (!mappedCdp) {
111
213
  throw new Error(`Failed to resolve CDP port mapping for ${containerName}.`);
112
214
  }
113
- const mappedNoVnc = params.cfg.browser.enableNoVnc && !params.cfg.browser.headless
215
+ const mappedNoVnc = noVncEnabled
114
216
  ? await readDockerPort(containerName, params.cfg.browser.noVncPort)
115
217
  : null;
218
+ if (noVncEnabled && !noVncPassword) {
219
+ noVncPassword =
220
+ (await readDockerContainerEnvVar(containerName, NOVNC_PASSWORD_ENV_KEY)) ?? undefined;
221
+ }
116
222
  const existing = BROWSER_BRIDGES.get(params.scopeKey);
117
- const existingProfile = existing ? resolveProfile(existing.bridge.state.resolved, "clawd") : null;
223
+ const existingProfile = existing
224
+ ? resolveProfile(existing.bridge.state.resolved, DEFAULT_CLAWD_BROWSER_PROFILE_NAME)
225
+ : null;
226
+ let desiredAuthToken = params.bridgeAuth?.token?.trim() || undefined;
227
+ let desiredAuthPassword = params.bridgeAuth?.password?.trim() || undefined;
228
+ if (!desiredAuthToken && !desiredAuthPassword) {
229
+ // Always require auth for the sandbox bridge server, even if gateway auth
230
+ // mode doesn't produce a shared secret (e.g. trusted-proxy).
231
+ // Keep it stable across calls by reusing the existing bridge auth.
232
+ desiredAuthToken = existing?.authToken;
233
+ desiredAuthPassword = existing?.authPassword;
234
+ if (!desiredAuthToken && !desiredAuthPassword) {
235
+ desiredAuthToken = crypto.randomBytes(24).toString("hex");
236
+ }
237
+ }
118
238
  const shouldReuse = existing && existing.containerName === containerName && existingProfile?.cdpPort === mappedCdp;
239
+ const authMatches = !existing ||
240
+ (existing.authToken === desiredAuthToken && existing.authPassword === desiredAuthPassword);
119
241
  if (existing && !shouldReuse) {
120
242
  await stopBrowserBridgeServer(existing.bridge.server).catch(() => undefined);
121
243
  BROWSER_BRIDGES.delete(params.scopeKey);
122
244
  }
245
+ if (existing && shouldReuse && !authMatches) {
246
+ await stopBrowserBridgeServer(existing.bridge.server).catch(() => undefined);
247
+ BROWSER_BRIDGES.delete(params.scopeKey);
248
+ }
123
249
  const bridge = (() => {
124
- if (shouldReuse && existing)
250
+ if (shouldReuse && authMatches && existing) {
125
251
  return existing.bridge;
252
+ }
126
253
  return null;
127
254
  })();
128
255
  const ensureBridge = async () => {
129
- if (bridge)
256
+ if (bridge) {
130
257
  return bridge;
258
+ }
131
259
  const onEnsureAttachTarget = params.cfg.browser.autoStart
132
260
  ? async () => {
133
261
  const state = await dockerContainerState(containerName);
@@ -150,28 +278,37 @@ export async function ensureSandboxBrowser(params) {
150
278
  headless: params.cfg.browser.headless,
151
279
  evaluateEnabled: params.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED,
152
280
  }),
281
+ authToken: desiredAuthToken,
282
+ authPassword: desiredAuthPassword,
153
283
  onEnsureAttachTarget,
284
+ resolveSandboxNoVncToken: consumeNoVncObserverToken,
154
285
  });
155
286
  };
156
287
  const resolvedBridge = await ensureBridge();
157
- if (!shouldReuse) {
288
+ if (!shouldReuse || !authMatches) {
158
289
  BROWSER_BRIDGES.set(params.scopeKey, {
159
290
  bridge: resolvedBridge,
160
291
  containerName,
292
+ authToken: desiredAuthToken,
293
+ authPassword: desiredAuthPassword,
161
294
  });
162
295
  }
163
- const now = Date.now();
164
296
  await updateBrowserRegistry({
165
297
  containerName,
166
298
  sessionKey: params.scopeKey,
167
299
  createdAtMs: now,
168
300
  lastUsedAtMs: now,
169
- image: params.cfg.browser.image,
301
+ image: browserImage,
302
+ configHash: hashMismatch && running ? (currentHash ?? undefined) : expectedHash,
170
303
  cdpPort: mappedCdp,
171
304
  noVncPort: mappedNoVnc ?? undefined,
172
305
  });
173
- const noVncUrl = mappedNoVnc && params.cfg.browser.enableNoVnc && !params.cfg.browser.headless
174
- ? `http://127.0.0.1:${mappedNoVnc}/vnc.html?autoconnect=1&resize=remote`
306
+ const noVncUrl = mappedNoVnc && noVncEnabled
307
+ ? (() => {
308
+ const directUrl = buildNoVncDirectUrl(mappedNoVnc, noVncPassword);
309
+ const token = issueNoVncObserverToken({ url: directUrl });
310
+ return buildNoVncObserverTokenUrl(resolvedBridge.baseUrl, token);
311
+ })()
175
312
  : undefined;
176
313
  return {
177
314
  bridgeUrl: resolvedBridge.baseUrl,
@@ -1,45 +1,32 @@
1
- import crypto from "node:crypto";
2
- function isPrimitive(value) {
3
- return value === null || (typeof value !== "object" && typeof value !== "function");
4
- }
1
+ import { hashTextSha256 } from "./hash.js";
5
2
  function normalizeForHash(value) {
6
- if (value === undefined)
3
+ if (value === undefined) {
7
4
  return undefined;
5
+ }
8
6
  if (Array.isArray(value)) {
9
- const normalized = value
10
- .map(normalizeForHash)
11
- .filter((item) => item !== undefined);
12
- const primitives = normalized.filter(isPrimitive);
13
- if (primitives.length === normalized.length) {
14
- return [...primitives].sort((a, b) => primitiveToString(a).localeCompare(primitiveToString(b)));
15
- }
16
- return normalized;
7
+ return value.map(normalizeForHash).filter((item) => item !== undefined);
17
8
  }
18
9
  if (value && typeof value === "object") {
19
- const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
10
+ const entries = Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b));
20
11
  const normalized = {};
21
12
  for (const [key, entryValue] of entries) {
22
13
  const next = normalizeForHash(entryValue);
23
- if (next !== undefined)
14
+ if (next !== undefined) {
24
15
  normalized[key] = next;
16
+ }
25
17
  }
26
18
  return normalized;
27
19
  }
28
20
  return value;
29
21
  }
30
- function primitiveToString(value) {
31
- if (value === null)
32
- return "null";
33
- if (typeof value === "string")
34
- return value;
35
- if (typeof value === "number")
36
- return String(value);
37
- if (typeof value === "boolean")
38
- return value ? "true" : "false";
39
- return JSON.stringify(value);
40
- }
41
22
  export function computeSandboxConfigHash(input) {
23
+ return computeHash(input);
24
+ }
25
+ export function computeSandboxBrowserConfigHash(input) {
26
+ return computeHash(input);
27
+ }
28
+ function computeHash(input) {
42
29
  const payload = normalizeForHash(input);
43
30
  const raw = JSON.stringify(payload);
44
- return crypto.createHash("sha1").update(raw).digest("hex");
31
+ return hashTextSha256(raw);
45
32
  }
@@ -1,9 +1,22 @@
1
1
  import { resolveAgentConfig } from "../agent-scope.js";
2
- import { DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS, DEFAULT_SANDBOX_BROWSER_CDP_PORT, DEFAULT_SANDBOX_BROWSER_IMAGE, DEFAULT_SANDBOX_BROWSER_NOVNC_PORT, DEFAULT_SANDBOX_BROWSER_PREFIX, DEFAULT_SANDBOX_BROWSER_VNC_PORT, DEFAULT_SANDBOX_CONTAINER_PREFIX, DEFAULT_SANDBOX_IDLE_HOURS, DEFAULT_SANDBOX_IMAGE, DEFAULT_SANDBOX_MAX_AGE_DAYS, DEFAULT_SANDBOX_WORKDIR, DEFAULT_SANDBOX_WORKSPACE_ROOT, } from "./constants.js";
2
+ import { DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS, DEFAULT_SANDBOX_BROWSER_CDP_PORT, DEFAULT_SANDBOX_BROWSER_IMAGE, DEFAULT_SANDBOX_BROWSER_NETWORK, DEFAULT_SANDBOX_BROWSER_NOVNC_PORT, DEFAULT_SANDBOX_BROWSER_PREFIX, DEFAULT_SANDBOX_BROWSER_VNC_PORT, DEFAULT_SANDBOX_CONTAINER_PREFIX, DEFAULT_SANDBOX_IDLE_HOURS, DEFAULT_SANDBOX_IMAGE, DEFAULT_SANDBOX_MAX_AGE_DAYS, DEFAULT_SANDBOX_WORKDIR, DEFAULT_SANDBOX_WORKSPACE_ROOT, } from "./constants.js";
3
3
  import { resolveSandboxToolPolicyForAgent } from "./tool-policy.js";
4
+ export function resolveSandboxBrowserDockerCreateConfig(params) {
5
+ const browserNetwork = params.browser.network.trim();
6
+ const base = {
7
+ ...params.docker,
8
+ // Browser container needs network access for Chrome, downloads, etc.
9
+ network: browserNetwork || DEFAULT_SANDBOX_BROWSER_NETWORK,
10
+ // For hashing and consistency, treat browser image as the docker image even though we
11
+ // pass it separately as the final `docker create` argument.
12
+ image: params.browser.image,
13
+ };
14
+ return params.browser.binds !== undefined ? { ...base, binds: params.browser.binds } : base;
15
+ }
4
16
  export function resolveSandboxScope(params) {
5
- if (params.scope)
17
+ if (params.scope) {
6
18
  return params.scope;
19
+ }
7
20
  if (typeof params.perSession === "boolean") {
8
21
  return params.perSession ? "session" : "shared";
9
22
  }
@@ -47,13 +60,18 @@ export function resolveSandboxDockerConfig(params) {
47
60
  export function resolveSandboxBrowserConfig(params) {
48
61
  const agentBrowser = params.scope === "shared" ? undefined : params.agentBrowser;
49
62
  const globalBrowser = params.globalBrowser;
63
+ const binds = [...(globalBrowser?.binds ?? []), ...(agentBrowser?.binds ?? [])];
64
+ // Treat `binds: []` as an explicit override, so it can disable `docker.binds` for the browser container.
65
+ const bindsConfigured = globalBrowser?.binds !== undefined || agentBrowser?.binds !== undefined;
50
66
  return {
51
67
  enabled: agentBrowser?.enabled ?? globalBrowser?.enabled ?? false,
52
68
  image: agentBrowser?.image ?? globalBrowser?.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE,
53
69
  containerPrefix: agentBrowser?.containerPrefix ??
54
70
  globalBrowser?.containerPrefix ??
55
71
  DEFAULT_SANDBOX_BROWSER_PREFIX,
72
+ network: agentBrowser?.network ?? globalBrowser?.network ?? DEFAULT_SANDBOX_BROWSER_NETWORK,
56
73
  cdpPort: agentBrowser?.cdpPort ?? globalBrowser?.cdpPort ?? DEFAULT_SANDBOX_BROWSER_CDP_PORT,
74
+ cdpSourceRange: agentBrowser?.cdpSourceRange ?? globalBrowser?.cdpSourceRange,
57
75
  vncPort: agentBrowser?.vncPort ?? globalBrowser?.vncPort ?? DEFAULT_SANDBOX_BROWSER_VNC_PORT,
58
76
  noVncPort: agentBrowser?.noVncPort ?? globalBrowser?.noVncPort ?? DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
59
77
  headless: agentBrowser?.headless ?? globalBrowser?.headless ?? false,
@@ -63,6 +81,7 @@ export function resolveSandboxBrowserConfig(params) {
63
81
  autoStartTimeoutMs: agentBrowser?.autoStartTimeoutMs ??
64
82
  globalBrowser?.autoStartTimeoutMs ??
65
83
  DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS,
84
+ binds: bindsConfigured ? binds : undefined,
66
85
  };
67
86
  }
68
87
  export function resolveSandboxPruneConfig(params) {
@@ -43,3 +43,5 @@ const resolvedSandboxStateDir = STATE_DIR ?? path.join(os.homedir(), ".poolbot")
43
43
  export const SANDBOX_STATE_DIR = path.join(resolvedSandboxStateDir, "sandbox");
44
44
  export const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
45
45
  export const SANDBOX_BROWSER_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "browsers.json");
46
+ export const SANDBOX_BROWSER_SECURITY_HASH_EPOCH = "2026-02-21-novnc-auth-default";
47
+ export const DEFAULT_SANDBOX_BROWSER_NETWORK = "poolbot-sandbox-browser";
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { createSubsystemLogger } from "../../logging/subsystem.js";
2
3
  import { sanitizeEnvVars } from "./sanitize-env-vars.js";
3
4
  function createAbortError() {
4
5
  const err = new Error("Aborted");
@@ -82,6 +83,7 @@ import { DEFAULT_SANDBOX_IMAGE, SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constant
82
83
  import { readRegistry, updateRegistry } from "./registry.js";
83
84
  import { resolveSandboxAgentId, resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
84
85
  import { validateSandboxSecurity } from "./validate-sandbox-security.js";
86
+ const log = createSubsystemLogger("docker");
85
87
  const HOT_CONTAINER_WINDOW_MS = 5 * 60 * 1000;
86
88
  export async function execDocker(args, opts) {
87
89
  const result = await execDockerRaw(args, opts);
@@ -102,6 +104,18 @@ export async function readDockerContainerLabel(containerName, label) {
102
104
  }
103
105
  return raw;
104
106
  }
107
+ export async function readDockerContainerEnvVar(containerName, envVar) {
108
+ const result = await execDocker(["inspect", "-f", "{{range .Config.Env}}{{println .}}{{end}}", containerName], { allowFailure: true });
109
+ if (result.code !== 0) {
110
+ return null;
111
+ }
112
+ for (const line of result.stdout.split(/\r?\n/)) {
113
+ if (line.startsWith(`${envVar}=`)) {
114
+ return line.slice(envVar.length + 1);
115
+ }
116
+ }
117
+ return null;
118
+ }
105
119
  export async function readDockerPort(containerName, port) {
106
120
  const result = await execDocker(["port", containerName, `${port}/tcp`], {
107
121
  allowFailure: true,
@@ -212,10 +226,10 @@ export function buildSandboxCreateArgs(params) {
212
226
  }
213
227
  const envSanitization = sanitizeEnvVars(params.cfg.env ?? {});
214
228
  if (envSanitization.blocked.length > 0) {
215
- console.warn("[Security] Blocked sensitive environment variables:", envSanitization.blocked.join(", "));
229
+ log.warn(`Blocked sensitive environment variables: ${envSanitization.blocked.join(", ")}`);
216
230
  }
217
231
  if (envSanitization.warnings.length > 0) {
218
- console.warn("[Security] Suspicious environment variables:", envSanitization.warnings);
232
+ log.warn(`Suspicious environment variables: ${envSanitization.warnings.join(", ")}`);
219
233
  }
220
234
  for (const [key, value] of Object.entries(envSanitization.allowed)) {
221
235
  args.push("--env", `${key}=${value}`);
@@ -0,0 +1,62 @@
1
+ import crypto from "node:crypto";
2
+ export const NOVNC_PASSWORD_ENV_KEY = "POOLBOT_BROWSER_NOVNC_PASSWORD";
3
+ const NOVNC_TOKEN_TTL_MS = 5 * 60 * 1000;
4
+ const NO_VNC_OBSERVER_TOKENS = new Map();
5
+ function pruneExpiredNoVncObserverTokens(now) {
6
+ for (const [token, entry] of NO_VNC_OBSERVER_TOKENS) {
7
+ if (entry.expiresAt <= now) {
8
+ NO_VNC_OBSERVER_TOKENS.delete(token);
9
+ }
10
+ }
11
+ }
12
+ export function isNoVncEnabled(params) {
13
+ return params.enableNoVnc && !params.headless;
14
+ }
15
+ export function generateNoVncPassword() {
16
+ // VNC auth uses an 8-char password max.
17
+ return crypto.randomBytes(4).toString("hex");
18
+ }
19
+ export function buildNoVncDirectUrl(port, password) {
20
+ const query = new URLSearchParams({
21
+ autoconnect: "1",
22
+ resize: "remote",
23
+ });
24
+ if (password?.trim()) {
25
+ query.set("password", password);
26
+ }
27
+ return `http://127.0.0.1:${port}/vnc.html?${query.toString()}`;
28
+ }
29
+ export function issueNoVncObserverToken(params) {
30
+ const now = params.nowMs ?? Date.now();
31
+ pruneExpiredNoVncObserverTokens(now);
32
+ const token = crypto.randomBytes(24).toString("hex");
33
+ NO_VNC_OBSERVER_TOKENS.set(token, {
34
+ url: params.url,
35
+ expiresAt: now + Math.max(1, params.ttlMs ?? NOVNC_TOKEN_TTL_MS),
36
+ });
37
+ return token;
38
+ }
39
+ export function consumeNoVncObserverToken(token, nowMs) {
40
+ const now = nowMs ?? Date.now();
41
+ pruneExpiredNoVncObserverTokens(now);
42
+ const normalized = token.trim();
43
+ if (!normalized) {
44
+ return null;
45
+ }
46
+ const entry = NO_VNC_OBSERVER_TOKENS.get(normalized);
47
+ if (!entry) {
48
+ return null;
49
+ }
50
+ NO_VNC_OBSERVER_TOKENS.delete(normalized);
51
+ if (entry.expiresAt <= now) {
52
+ return null;
53
+ }
54
+ return entry.url;
55
+ }
56
+ export function buildNoVncObserverTokenUrl(baseUrl, token) {
57
+ const query = new URLSearchParams({ token });
58
+ return `${baseUrl}/sandbox/novnc?${query.toString()}`;
59
+ }
60
+ export function resetNoVncObserverTokensForTests() {
61
+ NO_VNC_OBSERVER_TOKENS.clear();
62
+ }
@@ -28,7 +28,7 @@ const ALLOWED_ENV_VAR_PATTERNS = [
28
28
  /^TZ$/i,
29
29
  /^NODE_ENV$/i,
30
30
  ];
31
- function validateEnvVarValue(value) {
31
+ export function validateEnvVarValue(value) {
32
32
  if (value.includes("\0")) {
33
33
  return "Contains null bytes";
34
34
  }
@@ -1,11 +1,11 @@
1
- import crypto from "node:crypto";
2
1
  import path from "node:path";
3
2
  import { normalizeAgentId } from "../../routing/session-key.js";
4
3
  import { resolveUserPath } from "../../utils.js";
5
4
  import { resolveAgentIdFromSessionKey } from "../agent-scope.js";
5
+ import { hashTextSha256 } from "./hash.js";
6
6
  export function slugifySessionKey(value) {
7
7
  const trimmed = value.trim() || "session";
8
- const hash = crypto.createHash("sha1").update(trimmed).digest("hex").slice(0, 8);
8
+ const hash = hashTextSha256(trimmed).slice(0, 8);
9
9
  const safe = trimmed
10
10
  .toLowerCase()
11
11
  .replace(/[^a-z0-9._-]+/g, "-")
@@ -20,19 +20,23 @@ export function resolveSandboxWorkspaceDir(root, sessionKey) {
20
20
  }
21
21
  export function resolveSandboxScopeKey(scope, sessionKey) {
22
22
  const trimmed = sessionKey.trim() || "main";
23
- if (scope === "shared")
23
+ if (scope === "shared") {
24
24
  return "shared";
25
- if (scope === "session")
25
+ }
26
+ if (scope === "session") {
26
27
  return trimmed;
28
+ }
27
29
  const agentId = resolveAgentIdFromSessionKey(trimmed);
28
30
  return `agent:${agentId}`;
29
31
  }
30
32
  export function resolveSandboxAgentId(scopeKey) {
31
33
  const trimmed = scopeKey.trim();
32
- if (!trimmed || trimmed === "shared")
34
+ if (!trimmed || trimmed === "shared") {
33
35
  return undefined;
36
+ }
34
37
  const parts = trimmed.split(":").filter(Boolean);
35
- if (parts[0] === "agent" && parts[1])
38
+ if (parts[0] === "agent" && parts[1]) {
36
39
  return normalizeAgentId(parts[1]);
40
+ }
37
41
  return resolveAgentIdFromSessionKey(trimmed);
38
42
  }
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { isNotFoundPathError, isPathInside } from "../infra/path-guards.js";
5
6
  const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
6
7
  const HTTP_URL_RE = /^https?:\/\//i;
7
8
  const DATA_URL_RE = /^data:/i;
@@ -70,12 +71,32 @@ export async function resolveSandboxedMediaSource(params) {
70
71
  throw new Error(`Invalid file:// URL for sandboxed media: ${raw}`);
71
72
  }
72
73
  }
73
- const resolved = await assertSandboxPath({
74
+ const tmpMediaPath = await resolveAllowedTmpMediaPath({
75
+ candidate,
76
+ sandboxRoot: params.sandboxRoot,
77
+ });
78
+ if (tmpMediaPath) {
79
+ return tmpMediaPath;
80
+ }
81
+ const sandboxResult = await assertSandboxPath({
74
82
  filePath: candidate,
75
83
  cwd: params.sandboxRoot,
76
84
  root: params.sandboxRoot,
77
85
  });
78
- return resolved.resolved;
86
+ return sandboxResult.resolved;
87
+ }
88
+ async function resolveAllowedTmpMediaPath(params) {
89
+ const candidateIsAbsolute = path.isAbsolute(expandPath(params.candidate));
90
+ if (!candidateIsAbsolute) {
91
+ return undefined;
92
+ }
93
+ const resolved = path.resolve(resolveSandboxInputPath(params.candidate, params.sandboxRoot));
94
+ const tmpDir = path.resolve(os.tmpdir());
95
+ if (!isPathInside(tmpDir, resolved)) {
96
+ return undefined;
97
+ }
98
+ await assertNoSymlinkEscape(path.relative(tmpDir, resolved), tmpDir);
99
+ return resolved;
79
100
  }
80
101
  async function assertNoSymlinkEscape(relative, root, options) {
81
102
  if (!relative) {
@@ -104,8 +125,7 @@ async function assertNoSymlinkEscape(relative, root, options) {
104
125
  }
105
126
  }
106
127
  catch (err) {
107
- const anyErr = err;
108
- if (anyErr.code === "ENOENT") {
128
+ if (isNotFoundPathError(err)) {
109
129
  return;
110
130
  }
111
131
  throw err;
@@ -120,13 +140,6 @@ async function tryRealpath(value) {
120
140
  return path.resolve(value);
121
141
  }
122
142
  }
123
- function isPathInside(root, target) {
124
- const relative = path.relative(root, target);
125
- if (!relative || relative === "") {
126
- return true;
127
- }
128
- return !(relative.startsWith("..") || path.isAbsolute(relative));
129
- }
130
143
  function shortPath(value) {
131
144
  if (value.startsWith(os.homedir())) {
132
145
  return `~${value.slice(os.homedir().length)}`;