@poolzin/pool-bot 2026.2.24 → 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 (646) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/acp/client.js +207 -18
  3. package/dist/acp/event-mapper.js +87 -22
  4. package/dist/acp/meta.js +12 -6
  5. package/dist/acp/secret-file.js +22 -0
  6. package/dist/agents/agent-paths.js +8 -9
  7. package/dist/agents/agent-scope.js +17 -5
  8. package/dist/agents/auth-profiles/oauth.js +148 -64
  9. package/dist/agents/auth-profiles/session-override.js +13 -7
  10. package/dist/agents/bash-process-registry.test-helpers.js +29 -0
  11. package/dist/agents/bash-tools.exec-approval-request.js +20 -0
  12. package/dist/agents/bash-tools.exec-host-gateway.js +240 -0
  13. package/dist/agents/bash-tools.exec-host-node.js +235 -0
  14. package/dist/agents/bash-tools.exec-runtime.js +2 -25
  15. package/dist/agents/bash-tools.exec-types.js +1 -0
  16. package/dist/agents/bash-tools.process.js +224 -218
  17. package/dist/agents/bedrock-discovery.js +3 -1
  18. package/dist/agents/byteplus-models.js +97 -0
  19. package/dist/agents/chutes-oauth.js +1 -0
  20. package/dist/agents/cli-runner/helpers.js +4 -0
  21. package/dist/agents/compaction.js +41 -14
  22. package/dist/agents/content-blocks.js +16 -0
  23. package/dist/agents/doubao-models.js +121 -0
  24. package/dist/agents/failover-error.js +2 -0
  25. package/dist/agents/huggingface-models.js +5 -3
  26. package/dist/agents/live-model-filter.js +5 -0
  27. package/dist/agents/minimax-vlm.js +10 -8
  28. package/dist/agents/model-auth.js +6 -0
  29. package/dist/agents/model-catalog.js +3 -1
  30. package/dist/agents/model-fallback.js +96 -101
  31. package/dist/agents/model-selection.js +7 -1
  32. package/dist/agents/models-config.providers.js +364 -165
  33. package/dist/agents/ollama-stream.js +117 -4
  34. package/dist/agents/opencode-zen-models.js +22 -11
  35. package/dist/agents/pi-embedded-helpers/errors.js +55 -33
  36. package/dist/agents/pi-embedded-helpers/messaging-dedupe.js +10 -5
  37. package/dist/agents/pi-embedded-helpers/thinking.js +10 -5
  38. package/dist/agents/pi-embedded-helpers.js +1 -1
  39. package/dist/agents/pi-embedded-payloads.js +1 -0
  40. package/dist/agents/pi-embedded-runner/compact.js +29 -7
  41. package/dist/agents/pi-embedded-runner/extensions.js +28 -26
  42. package/dist/agents/pi-embedded-runner/google.js +20 -8
  43. package/dist/agents/pi-embedded-runner/run/attempt.js +95 -36
  44. package/dist/agents/pi-embedded-runner/run.js +71 -12
  45. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
  46. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +11 -2
  47. package/dist/agents/pi-embedded-runner/session-manager-cache.js +11 -7
  48. package/dist/agents/pi-embedded-runner/system-prompt.js +2 -0
  49. package/dist/agents/pi-embedded-runner/thinking.js +42 -0
  50. package/dist/agents/pi-embedded-runner/tool-name-allowlist.js +19 -0
  51. package/dist/agents/pi-embedded-runner/utils.js +7 -10
  52. package/dist/agents/pi-embedded-subscribe.handlers.lifecycle.js +45 -56
  53. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +2 -2
  54. package/dist/agents/pi-embedded-subscribe.js +9 -4
  55. package/dist/agents/pi-embedded-subscribe.tools.js +68 -14
  56. package/dist/agents/pi-embedded-utils.js +3 -0
  57. package/dist/agents/pi-extensions/compaction-safeguard-runtime.js +4 -20
  58. package/dist/agents/pi-extensions/compaction-safeguard.js +75 -33
  59. package/dist/agents/pi-settings.js +40 -0
  60. package/dist/agents/pi-tools.policy.js +2 -1
  61. package/dist/agents/provider/config-loader.js +1 -1
  62. package/dist/agents/sandbox/browser.js +170 -33
  63. package/dist/agents/sandbox/config-hash.js +14 -27
  64. package/dist/agents/sandbox/config.js +21 -2
  65. package/dist/agents/sandbox/constants.js +2 -0
  66. package/dist/agents/sandbox/docker.js +16 -2
  67. package/dist/agents/sandbox/novnc-auth.js +62 -0
  68. package/dist/agents/sandbox/sanitize-env-vars.js +1 -1
  69. package/dist/agents/sandbox/shared.js +10 -6
  70. package/dist/agents/sandbox-paths.js +24 -11
  71. package/dist/agents/schema/clean-for-gemini.js +132 -85
  72. package/dist/agents/session-slug.js +10 -5
  73. package/dist/agents/session-tool-result-guard-wrapper.js +1 -0
  74. package/dist/agents/session-tool-result-guard.js +3 -1
  75. package/dist/agents/session-transcript-repair.js +40 -6
  76. package/dist/agents/skills/bundled-dir.js +19 -5
  77. package/dist/agents/skills/env-overrides.js +124 -43
  78. package/dist/agents/skills/frontmatter.js +6 -6
  79. package/dist/agents/skills/plugin-skills.js +14 -7
  80. package/dist/agents/skills/workspace.js +1 -0
  81. package/dist/agents/skills.test-helpers.js +13 -0
  82. package/dist/agents/stable-stringify.js +12 -0
  83. package/dist/agents/subagent-announce.js +251 -49
  84. package/dist/agents/subagent-lifecycle-events.js +19 -0
  85. package/dist/agents/subagent-registry-cleanup.js +31 -0
  86. package/dist/agents/subagent-registry-completion.js +68 -0
  87. package/dist/agents/subagent-registry-queries.js +117 -0
  88. package/dist/agents/subagent-registry-state.js +46 -0
  89. package/dist/agents/subagent-registry.js +252 -221
  90. package/dist/agents/subagent-registry.mocks.shared.js +12 -0
  91. package/dist/agents/subagent-registry.store.js +1 -0
  92. package/dist/agents/subagent-registry.types.js +1 -0
  93. package/dist/agents/subagent-spawn.js +195 -7
  94. package/dist/agents/system-prompt.js +22 -6
  95. package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
  96. package/dist/agents/test-helpers/fast-coding-tools.js +1 -18
  97. package/dist/agents/test-helpers/fast-core-tools.js +1 -17
  98. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
  99. package/dist/agents/timeout.js +18 -6
  100. package/dist/agents/tool-call-id.js +1 -1
  101. package/dist/agents/tool-display-common.js +162 -29
  102. package/dist/agents/tool-images.js +82 -9
  103. package/dist/agents/tool-policy-shared.js +108 -0
  104. package/dist/agents/tool-policy.js +51 -26
  105. package/dist/agents/tools/browser-tool.js +160 -54
  106. package/dist/agents/tools/canvas-tool.js +27 -1
  107. package/dist/agents/tools/common.js +45 -0
  108. package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
  109. package/dist/agents/tools/discord-actions-guild.js +4 -1
  110. package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
  111. package/dist/agents/tools/gateway-tool.js +3 -1
  112. package/dist/agents/tools/image-tool.js +214 -99
  113. package/dist/agents/tools/nodes-utils.js +1 -10
  114. package/dist/agents/tools/sessions-history-tool.js +140 -108
  115. package/dist/agents/tools/sessions-send-helpers.js +12 -6
  116. package/dist/agents/tools/sessions-spawn-tool.js +8 -2
  117. package/dist/agents/tools/subagents-tool.js +2 -1
  118. package/dist/agents/tools/whatsapp-actions.js +10 -2
  119. package/dist/agents/tools/whatsapp-target-auth.js +18 -0
  120. package/dist/agents/transcript-policy.js +22 -8
  121. package/dist/agents/venice-models.js +11 -3
  122. package/dist/agents/workspace.js +222 -46
  123. package/dist/auto-reply/commands-registry.data.js +51 -0
  124. package/dist/auto-reply/commands-registry.js +19 -21
  125. package/dist/auto-reply/fallback-state.js +114 -0
  126. package/dist/auto-reply/group-activation.js +10 -5
  127. package/dist/auto-reply/inbound-debounce.js +10 -5
  128. package/dist/auto-reply/model-runtime.js +68 -0
  129. package/dist/auto-reply/reply/abort.js +1 -1
  130. package/dist/auto-reply/reply/agent-runner-execution.js +40 -5
  131. package/dist/auto-reply/reply/agent-runner.js +165 -39
  132. package/dist/auto-reply/reply/bash-command.js +41 -39
  133. package/dist/auto-reply/reply/command-gates.js +25 -0
  134. package/dist/auto-reply/reply/commands-allowlist.js +111 -72
  135. package/dist/auto-reply/reply/commands-bash.js +6 -5
  136. package/dist/auto-reply/reply/commands-config.js +30 -28
  137. package/dist/auto-reply/reply/commands-core.js +2 -1
  138. package/dist/auto-reply/reply/commands-info.js +1 -0
  139. package/dist/auto-reply/reply/commands-models.js +65 -14
  140. package/dist/auto-reply/reply/commands-session.js +237 -82
  141. package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
  142. package/dist/auto-reply/reply/commands-setunset.js +45 -0
  143. package/dist/auto-reply/reply/commands-subagents/action-agents.js +44 -0
  144. package/dist/auto-reply/reply/commands-subagents/action-focus.js +64 -0
  145. package/dist/auto-reply/reply/commands-subagents/action-help.js +4 -0
  146. package/dist/auto-reply/reply/commands-subagents/action-info.js +45 -0
  147. package/dist/auto-reply/reply/commands-subagents/action-kill.js +60 -0
  148. package/dist/auto-reply/reply/commands-subagents/action-list.js +44 -0
  149. package/dist/auto-reply/reply/commands-subagents/action-log.js +29 -0
  150. package/dist/auto-reply/reply/commands-subagents/action-send.js +119 -0
  151. package/dist/auto-reply/reply/commands-subagents/action-spawn.js +52 -0
  152. package/dist/auto-reply/reply/commands-subagents/action-unfocus.js +30 -0
  153. package/dist/auto-reply/reply/commands-subagents/shared.js +303 -0
  154. package/dist/auto-reply/reply/commands-subagents.js +51 -587
  155. package/dist/auto-reply/reply/commands-tts.js +10 -5
  156. package/dist/auto-reply/reply/config-value.js +10 -5
  157. package/dist/auto-reply/reply/directive-handling.model-picker.js +12 -6
  158. package/dist/auto-reply/reply/directive-handling.persist.js +9 -21
  159. package/dist/auto-reply/reply/directive-handling.shared.js +24 -4
  160. package/dist/auto-reply/reply/followup-runner.js +1 -0
  161. package/dist/auto-reply/reply/get-reply-directives-utils.js +23 -14
  162. package/dist/auto-reply/reply/get-reply-directives.js +17 -28
  163. package/dist/auto-reply/reply/get-reply-inline-actions.js +1 -0
  164. package/dist/auto-reply/reply/get-reply.js +71 -12
  165. package/dist/auto-reply/reply/model-selection.js +80 -39
  166. package/dist/auto-reply/reply/queue/enqueue.js +10 -5
  167. package/dist/auto-reply/reply/queue/state.js +13 -12
  168. package/dist/auto-reply/reply/reply-payloads.js +67 -36
  169. package/dist/auto-reply/reply/reply-reference.js +9 -8
  170. package/dist/auto-reply/reply/route-reply.js +15 -8
  171. package/dist/auto-reply/reply/session-reset-prompt.js +1 -1
  172. package/dist/auto-reply/reply/session.js +22 -6
  173. package/dist/auto-reply/reply/strip-inbound-meta.js +147 -0
  174. package/dist/auto-reply/reply/subagents-utils.js +56 -30
  175. package/dist/auto-reply/reply/typing.js +46 -21
  176. package/dist/auto-reply/send-policy.js +14 -7
  177. package/dist/auto-reply/status.js +140 -16
  178. package/dist/auto-reply/templating.js +10 -5
  179. package/dist/auto-reply/thinking.js +7 -16
  180. package/dist/auto-reply/tokens.js +21 -5
  181. package/dist/browser/bridge-server.js +36 -20
  182. package/dist/browser/cdp.helpers.js +7 -14
  183. package/dist/browser/cdp.js +35 -15
  184. package/dist/browser/chrome.profile-decoration.js +7 -4
  185. package/dist/browser/config.js +30 -0
  186. package/dist/browser/extension-relay-auth.js +55 -0
  187. package/dist/browser/extension-relay.js +74 -29
  188. package/dist/browser/navigation-guard.js +39 -0
  189. package/dist/browser/paths.js +77 -0
  190. package/dist/browser/profiles.js +13 -8
  191. package/dist/browser/pw-ai-module.js +10 -5
  192. package/dist/browser/pw-session.js +76 -39
  193. package/dist/browser/pw-tools-core.interactions.js +14 -7
  194. package/dist/browser/pw-tools-core.state.js +12 -6
  195. package/dist/browser/routes/agent.act.js +431 -424
  196. package/dist/browser/routes/agent.shared.js +47 -3
  197. package/dist/browser/routes/agent.snapshot.js +122 -116
  198. package/dist/browser/routes/agent.storage.js +303 -297
  199. package/dist/browser/routes/tabs.js +154 -100
  200. package/dist/browser/server-context.js +7 -0
  201. package/dist/browser/server-lifecycle.js +37 -0
  202. package/dist/build-info.json +3 -3
  203. package/dist/channels/allow-from.js +26 -0
  204. package/dist/channels/allowlists/resolve-utils.js +43 -19
  205. package/dist/channels/channel-config.js +14 -7
  206. package/dist/channels/draft-stream-loop.js +7 -0
  207. package/dist/channels/model-overrides.js +82 -0
  208. package/dist/channels/plugins/account-action-gate.js +13 -0
  209. package/dist/channels/plugins/message-actions.js +10 -0
  210. package/dist/channels/plugins/normalize/imessage.js +14 -7
  211. package/dist/channels/plugins/normalize/slack.js +10 -5
  212. package/dist/channels/plugins/normalize/telegram.js +14 -7
  213. package/dist/channels/plugins/outbound/discord.js +80 -8
  214. package/dist/channels/plugins/outbound/signal.js +11 -11
  215. package/dist/channels/plugins/setup-helpers.js +10 -5
  216. package/dist/channels/sender-label.js +14 -7
  217. package/dist/channels/session.js +4 -2
  218. package/dist/channels/status-reactions.js +297 -0
  219. package/dist/channels/telegram/api.js +18 -0
  220. package/dist/cli/argv.js +84 -21
  221. package/dist/cli/banner.js +3 -2
  222. package/dist/cli/browser-cli-actions-input/register.files-downloads.js +65 -56
  223. package/dist/cli/cli-name.js +11 -11
  224. package/dist/cli/cli-utils.js +13 -3
  225. package/dist/cli/command-format.js +1 -1
  226. package/dist/cli/config-cli.js +1 -1
  227. package/dist/cli/daemon-cli/lifecycle-core.js +31 -19
  228. package/dist/cli/daemon-cli/lifecycle.js +64 -2
  229. package/dist/cli/daemon-cli/restart-health.js +126 -0
  230. package/dist/cli/daemon-cli/status.gather.js +9 -13
  231. package/dist/cli/daemon-cli/status.print.js +2 -10
  232. package/dist/cli/deps.js +27 -22
  233. package/dist/cli/exec-approvals-cli.js +92 -124
  234. package/dist/cli/gateway-cli/run-loop.js +23 -5
  235. package/dist/cli/memory-cli.js +158 -61
  236. package/dist/cli/node-cli/register.js +14 -5
  237. package/dist/cli/nodes-cli/register.push.js +63 -0
  238. package/dist/cli/nodes-media-utils.js +26 -0
  239. package/dist/cli/outbound-send-deps.js +2 -9
  240. package/dist/cli/outbound-send-mapping.js +11 -0
  241. package/dist/cli/pairing-cli.js +40 -14
  242. package/dist/cli/plugins-cli.js +250 -73
  243. package/dist/cli/ports.js +11 -10
  244. package/dist/cli/program/build-program.js +3 -1
  245. package/dist/cli/program/command-registry.js +214 -136
  246. package/dist/cli/program/command-tree.js +16 -0
  247. package/dist/cli/program/help.js +43 -12
  248. package/dist/cli/program/preaction.js +13 -9
  249. package/dist/cli/program/register.configure.js +3 -18
  250. package/dist/cli/program/register.maintenance.js +2 -2
  251. package/dist/cli/program/register.onboard.js +2 -0
  252. package/dist/cli/program/register.status-health-sessions.js +16 -17
  253. package/dist/cli/program/register.subclis.js +93 -52
  254. package/dist/cli/route.js +12 -8
  255. package/dist/cli/system-cli.js +36 -46
  256. package/dist/cli/test-runtime-capture.js +24 -0
  257. package/dist/cli/update-cli/shared.js +22 -9
  258. package/dist/cli/update-cli/update-command.js +89 -14
  259. package/dist/cli/update-cli/wizard.js +6 -12
  260. package/dist/commands/agent/run-context.js +18 -5
  261. package/dist/commands/agent/session-store.js +17 -4
  262. package/dist/commands/agent.js +185 -89
  263. package/dist/commands/agents.bindings.js +14 -7
  264. package/dist/commands/agents.commands.add.js +13 -9
  265. package/dist/commands/agents.commands.identity.js +12 -6
  266. package/dist/commands/agents.commands.list.js +11 -6
  267. package/dist/commands/agents.config.js +8 -10
  268. package/dist/commands/agents.providers.js +12 -6
  269. package/dist/commands/auth-choice-options.js +103 -75
  270. package/dist/commands/auth-choice.apply.byteplus.js +55 -0
  271. package/dist/commands/auth-choice.apply.js +4 -0
  272. package/dist/commands/auth-choice.apply.minimax.js +61 -13
  273. package/dist/commands/auth-choice.apply.openai.js +3 -1
  274. package/dist/commands/auth-choice.apply.volcengine.js +55 -0
  275. package/dist/commands/auth-choice.preferred-provider.js +2 -0
  276. package/dist/commands/channels/remove.js +13 -6
  277. package/dist/commands/channels/shared.js +4 -14
  278. package/dist/commands/channels.mock-harness.js +23 -0
  279. package/dist/commands/configure.commands.js +14 -0
  280. package/dist/commands/configure.gateway.js +2 -4
  281. package/dist/commands/configure.js +1 -1
  282. package/dist/commands/configure.shared.js +11 -0
  283. package/dist/commands/daemon-install-helpers.js +2 -2
  284. package/dist/commands/daemon-install-runtime-warning.js +11 -0
  285. package/dist/commands/dashboard.js +12 -10
  286. package/dist/commands/docs.js +14 -8
  287. package/dist/commands/doctor-config-flow.js +11 -9
  288. package/dist/commands/doctor-legacy-config.js +281 -0
  289. package/dist/commands/doctor-state-integrity.js +99 -23
  290. package/dist/commands/doctor-update.js +12 -9
  291. package/dist/commands/models/list.list-command.js +7 -5
  292. package/dist/commands/models/set-image.js +2 -21
  293. package/dist/commands/node-daemon-install-helpers.js +10 -8
  294. package/dist/commands/onboard-auth.config-minimax.js +54 -80
  295. package/dist/commands/onboard-auth.config-opencode.js +2 -18
  296. package/dist/commands/onboard-auth.credentials.js +90 -13
  297. package/dist/commands/onboard-auth.js +1 -1
  298. package/dist/commands/onboard-auth.models.js +6 -5
  299. package/dist/commands/onboard-hooks.js +1 -1
  300. package/dist/commands/onboard-non-interactive/api-keys.js +14 -7
  301. package/dist/commands/onboard-non-interactive/local/auth-choice.js +64 -49
  302. package/dist/commands/onboard-provider-auth-flags.js +14 -0
  303. package/dist/commands/onboard-remote.js +14 -7
  304. package/dist/commands/onboard.js +11 -13
  305. package/dist/commands/sandbox-display.js +6 -5
  306. package/dist/commands/sessions.test-helpers.js +61 -0
  307. package/dist/commands/status-all/diagnosis.js +14 -10
  308. package/dist/commands/status-all/format.js +1 -0
  309. package/dist/commands/status.gateway-probe.js +1 -16
  310. package/dist/commands/systemd-linger.js +12 -6
  311. package/dist/config/agent-limits.js +2 -0
  312. package/dist/config/commands.js +32 -15
  313. package/dist/config/config-paths.js +9 -11
  314. package/dist/config/config.js +1 -1
  315. package/dist/config/defaults.js +22 -2
  316. package/dist/config/discord-preview-streaming.js +104 -0
  317. package/dist/config/env-substitution.js +62 -34
  318. package/dist/config/env-vars.js +45 -7
  319. package/dist/config/includes.js +4 -0
  320. package/dist/config/io.js +656 -171
  321. package/dist/config/legacy.migrations.part-1.js +189 -78
  322. package/dist/config/legacy.shared.js +3 -1
  323. package/dist/config/merge-patch.js +54 -4
  324. package/dist/config/prototype-keys.js +4 -0
  325. package/dist/config/redact-snapshot.js +404 -76
  326. package/dist/config/schema.help.js +44 -7
  327. package/dist/config/schema.js +58 -570
  328. package/dist/config/schema.labels.js +38 -6
  329. package/dist/config/sessions/delivery-info.js +10 -3
  330. package/dist/config/sessions/main-session.js +10 -5
  331. package/dist/config/sessions/session-file.js +33 -0
  332. package/dist/config/sessions/session-key.js +10 -5
  333. package/dist/config/sessions/store.js +1 -1
  334. package/dist/config/sessions.js +1 -0
  335. package/dist/config/validation.js +140 -85
  336. package/dist/config/zod-schema.agent-runtime.js +11 -0
  337. package/dist/config/zod-schema.hooks.js +40 -11
  338. package/dist/config/zod-schema.installs.js +20 -0
  339. package/dist/config/zod-schema.js +156 -20
  340. package/dist/config/zod-schema.providers-core.js +78 -4
  341. package/dist/config/zod-schema.providers.js +6 -1
  342. package/dist/config/zod-schema.session.js +41 -2
  343. package/dist/cron/run-log.js +3 -0
  344. package/dist/cron/schedule.js +21 -10
  345. package/dist/cron/service/ops.js +35 -21
  346. package/dist/cron/service/timer.js +116 -16
  347. package/dist/cron/stagger.js +3 -1
  348. package/dist/daemon/cmd-argv.js +21 -0
  349. package/dist/daemon/cmd-set.js +58 -0
  350. package/dist/daemon/service-types.js +1 -0
  351. package/dist/discord/api.js +12 -6
  352. package/dist/discord/draft-chunking.js +22 -0
  353. package/dist/discord/draft-stream.js +124 -0
  354. package/dist/discord/monitor/agent-components.js +1 -1
  355. package/dist/discord/monitor/commands.js +5 -0
  356. package/dist/discord/monitor/exec-approvals.js +357 -162
  357. package/dist/discord/monitor/gateway-plugin.js +2 -1
  358. package/dist/discord/monitor/listeners.js +37 -27
  359. package/dist/discord/monitor/message-handler.js +4 -1
  360. package/dist/discord/monitor/message-handler.preflight.js +65 -8
  361. package/dist/discord/monitor/message-handler.process.js +246 -217
  362. package/dist/discord/monitor/message-utils.js +143 -6
  363. package/dist/discord/monitor/model-picker-preferences.js +143 -0
  364. package/dist/discord/monitor/model-picker.js +651 -0
  365. package/dist/discord/monitor/native-command.js +573 -16
  366. package/dist/discord/monitor/provider.allowlist.js +223 -0
  367. package/dist/discord/monitor/provider.js +275 -347
  368. package/dist/discord/monitor/provider.lifecycle.js +100 -0
  369. package/dist/discord/monitor/reply-delivery.js +123 -16
  370. package/dist/discord/monitor/thread-bindings.discord-api.js +215 -0
  371. package/dist/discord/monitor/thread-bindings.js +4 -0
  372. package/dist/discord/monitor/thread-bindings.lifecycle.js +177 -0
  373. package/dist/discord/monitor/thread-bindings.manager.js +423 -0
  374. package/dist/discord/monitor/thread-bindings.messages.js +55 -0
  375. package/dist/discord/monitor/thread-bindings.state.js +358 -0
  376. package/dist/discord/monitor/thread-bindings.types.js +6 -0
  377. package/dist/discord/resolve-users.js +33 -21
  378. package/dist/discord/send.channels.js +15 -0
  379. package/dist/discord/send.js +3 -2
  380. package/dist/discord/send.outbound.js +82 -26
  381. package/dist/discord/send.permissions.js +83 -30
  382. package/dist/discord/send.reactions.js +8 -4
  383. package/dist/discord/token.js +10 -5
  384. package/dist/discord/voice/command.js +263 -0
  385. package/dist/discord/voice/manager.js +531 -0
  386. package/dist/gateway/auth.js +72 -13
  387. package/dist/gateway/call.js +152 -83
  388. package/dist/gateway/canvas-capability.js +75 -0
  389. package/dist/gateway/client.js +28 -4
  390. package/dist/gateway/config-reload.js +3 -4
  391. package/dist/gateway/control-plane-audit.js +28 -0
  392. package/dist/gateway/control-plane-rate-limit.js +53 -0
  393. package/dist/gateway/control-ui.js +219 -96
  394. package/dist/gateway/events.js +1 -0
  395. package/dist/gateway/hooks-mapping.js +88 -38
  396. package/dist/gateway/hooks.js +109 -54
  397. package/dist/gateway/http-auth-helpers.js +3 -2
  398. package/dist/gateway/http-common.js +22 -0
  399. package/dist/gateway/http-endpoint-helpers.js +1 -0
  400. package/dist/gateway/method-scopes.js +169 -0
  401. package/dist/gateway/net.js +74 -9
  402. package/dist/gateway/node-invoke-system-run-approval.js +14 -35
  403. package/dist/gateway/node-registry.js +10 -5
  404. package/dist/gateway/openai-http.js +1 -0
  405. package/dist/gateway/openresponses-http.js +121 -110
  406. package/dist/gateway/origin-check.js +1 -18
  407. package/dist/gateway/probe-auth.js +2 -0
  408. package/dist/gateway/protocol/index.js +4 -2
  409. package/dist/gateway/protocol/schema/cron.js +1 -0
  410. package/dist/gateway/protocol/schema/devices.js +1 -0
  411. package/dist/gateway/protocol/schema/protocol-schemas.js +4 -1
  412. package/dist/gateway/protocol/schema/push.js +18 -0
  413. package/dist/gateway/protocol/schema/sessions.js +6 -0
  414. package/dist/gateway/protocol/schema.js +1 -0
  415. package/dist/gateway/role-policy.js +17 -0
  416. package/dist/gateway/server/ws-connection/connect-policy.js +37 -0
  417. package/dist/gateway/server/ws-connection/message-handler.js +175 -148
  418. package/dist/gateway/server-chat.js +83 -25
  419. package/dist/gateway/server-constants.js +10 -9
  420. package/dist/gateway/server-cron.js +1 -0
  421. package/dist/gateway/server-http.js +247 -54
  422. package/dist/gateway/server-maintenance.js +20 -5
  423. package/dist/gateway/server-methods/agent.js +162 -24
  424. package/dist/gateway/server-methods/chat.js +465 -130
  425. package/dist/gateway/server-methods/config.js +193 -152
  426. package/dist/gateway/server-methods/devices.js +17 -3
  427. package/dist/gateway/server-methods/models.js +11 -1
  428. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  429. package/dist/gateway/server-methods/nodes.js +251 -69
  430. package/dist/gateway/server-methods/push.js +53 -0
  431. package/dist/gateway/server-methods/sessions.js +64 -8
  432. package/dist/gateway/server-methods/usage.js +162 -75
  433. package/dist/gateway/server-node-events.js +29 -0
  434. package/dist/gateway/server-reload-handlers.js +2 -3
  435. package/dist/gateway/server-runtime-config.js +39 -13
  436. package/dist/gateway/server-runtime-state.js +2 -0
  437. package/dist/gateway/server-startup-memory.js +17 -11
  438. package/dist/gateway/server-ws-runtime.js +1 -0
  439. package/dist/gateway/server.impl.js +296 -139
  440. package/dist/gateway/session-preview.test-helpers.js +11 -0
  441. package/dist/gateway/session-utils.fs.js +32 -34
  442. package/dist/gateway/sessions-resolve.js +17 -5
  443. package/dist/gateway/startup-auth.js +126 -0
  444. package/dist/gateway/test-helpers.agent-results.js +15 -0
  445. package/dist/gateway/test-helpers.mocks.js +37 -14
  446. package/dist/gateway/test-helpers.openai-mock.js +14 -7
  447. package/dist/gateway/test-helpers.server.js +161 -77
  448. package/dist/gateway/tools-invoke-http.js +21 -10
  449. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +3 -1
  450. package/dist/hooks/bundled/command-logger/handler.js +7 -2
  451. package/dist/hooks/bundled/session-memory/handler.js +170 -38
  452. package/dist/hooks/frontmatter.js +6 -6
  453. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  454. package/dist/hooks/gmail-watcher.js +11 -6
  455. package/dist/hooks/internal-hooks.js +11 -1
  456. package/dist/hooks/llm-slug-generator.js +4 -1
  457. package/dist/hooks/workspace.js +47 -17
  458. package/dist/imessage/accounts.js +9 -20
  459. package/dist/imessage/monitor/inbound-processing.js +2 -1
  460. package/dist/infra/archive-path.js +49 -0
  461. package/dist/infra/archive.js +174 -73
  462. package/dist/infra/control-ui-assets.js +14 -6
  463. package/dist/infra/device-pairing.js +204 -144
  464. package/dist/infra/env.js +10 -5
  465. package/dist/infra/exec-approvals-allowlist.js +141 -70
  466. package/dist/infra/exec-approvals-analysis.js +78 -20
  467. package/dist/infra/exec-approvals.js +5 -17
  468. package/dist/infra/exec-safe-bin-policy.js +277 -0
  469. package/dist/infra/fixed-window-rate-limit.js +33 -0
  470. package/dist/infra/fs-safe.js +71 -39
  471. package/dist/infra/gateway-lock.js +6 -2
  472. package/dist/infra/git-root.js +61 -0
  473. package/dist/infra/heartbeat-active-hours.js +2 -2
  474. package/dist/infra/heartbeat-reason.js +40 -0
  475. package/dist/infra/heartbeat-runner.js +72 -32
  476. package/dist/infra/heartbeat-wake.js +6 -12
  477. package/dist/infra/host-env-security-policy.json +19 -0
  478. package/dist/infra/host-env-security.js +66 -0
  479. package/dist/infra/install-source-utils.js +91 -7
  480. package/dist/infra/net/ssrf.js +131 -38
  481. package/dist/infra/node-pairing.js +50 -105
  482. package/dist/infra/npm-integrity.js +45 -0
  483. package/dist/infra/npm-pack-install.js +40 -0
  484. package/dist/infra/outbound/bound-delivery-router.js +88 -0
  485. package/dist/infra/outbound/channel-adapters.js +20 -7
  486. package/dist/infra/outbound/channel-selection.js +12 -6
  487. package/dist/infra/outbound/envelope.js +1 -1
  488. package/dist/infra/outbound/format.js +12 -6
  489. package/dist/infra/outbound/message-action-runner.js +107 -327
  490. package/dist/infra/outbound/message.js +59 -36
  491. package/dist/infra/outbound/outbound-policy.js +52 -25
  492. package/dist/infra/outbound/outbound-send-service.js +58 -71
  493. package/dist/infra/outbound/payloads.js +14 -7
  494. package/dist/infra/outbound/session-binding-service.js +123 -0
  495. package/dist/infra/pairing-files.js +10 -0
  496. package/dist/infra/path-guards.js +25 -0
  497. package/dist/infra/plain-object.js +9 -0
  498. package/dist/infra/provider-usage.fetch.codex.js +7 -15
  499. package/dist/infra/provider-usage.fetch.gemini.js +14 -11
  500. package/dist/infra/provider-usage.fetch.shared.js +30 -1
  501. package/dist/infra/provider-usage.fetch.zai.js +10 -9
  502. package/dist/infra/push-apns.js +365 -0
  503. package/dist/infra/restart-sentinel.js +16 -1
  504. package/dist/infra/restart.js +229 -26
  505. package/dist/infra/retry-policy.js +4 -2
  506. package/dist/infra/retry.js +9 -5
  507. package/dist/infra/scp-host.js +54 -0
  508. package/dist/infra/session-cost-usage.js +107 -59
  509. package/dist/infra/session-maintenance-warning.js +3 -1
  510. package/dist/infra/shell-env.js +98 -34
  511. package/dist/infra/ssh-config.js +12 -6
  512. package/dist/infra/system-run-command.js +49 -4
  513. package/dist/infra/update-channels.js +10 -5
  514. package/dist/infra/update-startup.js +86 -9
  515. package/dist/line/accounts.js +5 -7
  516. package/dist/line/bot-access.js +8 -20
  517. package/dist/line/bot-handlers.js +3 -1
  518. package/dist/link-understanding/detect.js +15 -7
  519. package/dist/media/constants.js +15 -6
  520. package/dist/media/image-ops.js +7 -0
  521. package/dist/media/inbound-path-policy.js +114 -0
  522. package/dist/media/input-files.js +16 -0
  523. package/dist/media/local-roots.js +3 -2
  524. package/dist/media-understanding/apply.js +4 -1
  525. package/dist/media-understanding/concurrency.js +8 -20
  526. package/dist/memory/backend-config.js +45 -6
  527. package/dist/memory/embeddings.js +10 -4
  528. package/dist/memory/fs-utils.js +23 -0
  529. package/dist/memory/manager-search.js +12 -6
  530. package/dist/memory/manager-sync-ops.js +12 -2
  531. package/dist/memory/qmd-manager.js +466 -53
  532. package/dist/memory/query-expansion.js +167 -3
  533. package/dist/memory/status-format.js +10 -5
  534. package/dist/memory/sync-memory-files.js +1 -1
  535. package/dist/memory/test-manager.js +8 -0
  536. package/dist/node-host/invoke-system-run.js +281 -0
  537. package/dist/node-host/invoke.js +55 -337
  538. package/dist/pairing/pairing-store.js +22 -0
  539. package/dist/plugin-sdk/allow-from.js +1 -1
  540. package/dist/plugin-sdk/command-auth.js +3 -1
  541. package/dist/plugin-sdk/index.js +6 -3
  542. package/dist/plugin-sdk/temp-path.js +47 -0
  543. package/dist/plugin-sdk/webhook-targets.js +32 -0
  544. package/dist/plugins/bundled-dir.js +9 -6
  545. package/dist/plugins/discovery.js +217 -23
  546. package/dist/plugins/hook-runner-global.js +16 -0
  547. package/dist/plugins/hooks.js +50 -0
  548. package/dist/plugins/install.js +28 -16
  549. package/dist/plugins/loader.js +192 -26
  550. package/dist/plugins/logger.js +8 -0
  551. package/dist/plugins/manifest-registry.js +3 -0
  552. package/dist/plugins/path-safety.js +34 -0
  553. package/dist/plugins/registry.js +5 -2
  554. package/dist/plugins/runtime/index.js +271 -206
  555. package/dist/plugins/runtime.js +3 -17
  556. package/dist/plugins/update.js +78 -12
  557. package/dist/process/spawn-utils.js +14 -7
  558. package/dist/providers/github-copilot-models.js +4 -1
  559. package/dist/providers/github-copilot-token.js +11 -6
  560. package/dist/providers/qwen-portal-oauth.js +14 -6
  561. package/dist/routing/account-id.js +30 -0
  562. package/dist/routing/resolve-route.js +3 -7
  563. package/dist/routing/session-key.js +2 -16
  564. package/dist/security/audit-channel.js +100 -20
  565. package/dist/security/audit-extra.async.js +505 -179
  566. package/dist/security/audit-extra.js +12 -2
  567. package/dist/security/audit-extra.sync.js +421 -35
  568. package/dist/security/audit-fs.js +31 -13
  569. package/dist/security/audit.js +180 -370
  570. package/dist/security/dm-policy-shared.js +68 -0
  571. package/dist/security/external-content.js +46 -14
  572. package/dist/security/fix.js +49 -85
  573. package/dist/security/scan-paths.js +20 -0
  574. package/dist/security/secret-equal.js +3 -7
  575. package/dist/security/windows-acl.js +30 -15
  576. package/dist/shared/entry-status.js +6 -0
  577. package/dist/shared/frontmatter.js +5 -5
  578. package/dist/shared/node-list-parse.js +13 -0
  579. package/dist/shared/node-match.js +11 -4
  580. package/dist/shared/operator-scope-compat.js +42 -0
  581. package/dist/shared/text-chunking.js +29 -0
  582. package/dist/signal/accounts.js +7 -20
  583. package/dist/signal/monitor/event-handler.js +3 -1
  584. package/dist/slack/accounts.js +6 -19
  585. package/dist/slack/actions.js +11 -3
  586. package/dist/slack/blocks.test-helpers.js +31 -0
  587. package/dist/slack/monitor/auth.js +1 -1
  588. package/dist/slack/monitor/message-handler/dispatch.js +50 -29
  589. package/dist/slack/monitor/mrkdwn.js +8 -0
  590. package/dist/slack/monitor/replies.js +15 -7
  591. package/dist/slack/monitor/slash.js +22 -13
  592. package/dist/slack/resolve-channels.js +10 -5
  593. package/dist/slack/send.js +102 -12
  594. package/dist/slack/stream-mode.js +10 -0
  595. package/dist/slack/streaming.js +4 -2
  596. package/dist/telegram/accounts.js +19 -14
  597. package/dist/telegram/bot/helpers.js +3 -5
  598. package/dist/telegram/bot-access.js +35 -36
  599. package/dist/telegram/bot-handlers.js +120 -148
  600. package/dist/telegram/bot-message-context.js +68 -9
  601. package/dist/telegram/bot-message-dispatch.js +477 -210
  602. package/dist/telegram/bot-native-commands.js +16 -0
  603. package/dist/telegram/draft-stream.js +44 -8
  604. package/dist/telegram/inline-buttons.js +5 -15
  605. package/dist/telegram/monitor.js +11 -7
  606. package/dist/telegram/network-config.js +19 -7
  607. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  608. package/dist/telegram/send.js +3 -2
  609. package/dist/telegram/sent-message-cache.js +5 -6
  610. package/dist/telegram/status-reaction-variants.js +208 -0
  611. package/dist/telegram/sticker-cache.js +11 -9
  612. package/dist/terminal/prompt-select-styled.js +9 -0
  613. package/dist/terminal/theme.js +12 -12
  614. package/dist/test-utils/command-runner.js +6 -0
  615. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  616. package/dist/test-utils/model-auth-mock.js +12 -0
  617. package/dist/test-utils/provider-usage-fetch.js +14 -0
  618. package/dist/test-utils/temp-home.js +33 -0
  619. package/dist/tts/tts.js +80 -567
  620. package/dist/tui/components/chat-log.js +50 -8
  621. package/dist/tui/theme/theme.js +10 -12
  622. package/dist/tui/tui-command-handlers.js +36 -27
  623. package/dist/tui/tui-event-handlers.js +122 -32
  624. package/dist/tui/tui-local-shell.js +16 -6
  625. package/dist/tui/tui.js +236 -48
  626. package/dist/utils/account-id.js +2 -4
  627. package/dist/utils/boolean.js +10 -5
  628. package/dist/utils/directive-tags.js +11 -0
  629. package/dist/utils/mask-api-key.js +10 -0
  630. package/dist/utils/queue-helpers.js +67 -12
  631. package/dist/utils/run-with-concurrency.js +39 -0
  632. package/dist/web/auto-reply/deliver-reply.js +8 -4
  633. package/dist/web/auto-reply/mentions.js +10 -5
  634. package/dist/web/auto-reply/monitor/group-members.js +14 -7
  635. package/dist/web/auto-reply/monitor/process-message.js +45 -24
  636. package/dist/web/inbound/access-control.js +5 -2
  637. package/dist/web/login-qr.js +12 -6
  638. package/dist/web/media.js +126 -15
  639. package/docs/tools/slash-commands.md +5 -1
  640. package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
  641. package/extensions/bluebubbles/src/monitor.ts +208 -1950
  642. package/extensions/feishu/src/external-keys.ts +19 -0
  643. package/extensions/lobster/src/windows-spawn.ts +193 -0
  644. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  645. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  646. package/package.json +1 -1
@@ -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)}`;
@@ -26,18 +26,28 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([
26
26
  "minProperties",
27
27
  "maxProperties",
28
28
  ]);
29
+ const SCHEMA_META_KEYS = ["description", "title", "default"];
30
+ function copySchemaMeta(from, to) {
31
+ for (const key of SCHEMA_META_KEYS) {
32
+ if (key in from && from[key] !== undefined) {
33
+ to[key] = from[key];
34
+ }
35
+ }
36
+ }
29
37
  // Check if an anyOf/oneOf array contains only literal values that can be flattened.
30
38
  // TypeBox Type.Literal generates { const: "value", type: "string" }.
31
39
  // Some schemas may use { enum: ["value"], type: "string" }.
32
40
  // Both patterns are flattened to { type: "string", enum: ["a", "b", ...] }.
33
41
  function tryFlattenLiteralAnyOf(variants) {
34
- if (variants.length === 0)
42
+ if (variants.length === 0) {
35
43
  return null;
44
+ }
36
45
  const allValues = [];
37
46
  let commonType = null;
38
47
  for (const variant of variants) {
39
- if (!variant || typeof variant !== "object")
48
+ if (!variant || typeof variant !== "object") {
40
49
  return null;
50
+ }
41
51
  const v = variant;
42
52
  let literalValue;
43
53
  if ("const" in v) {
@@ -50,16 +60,20 @@ function tryFlattenLiteralAnyOf(variants) {
50
60
  return null;
51
61
  }
52
62
  const variantType = typeof v.type === "string" ? v.type : null;
53
- if (!variantType)
63
+ if (!variantType) {
54
64
  return null;
55
- if (commonType === null)
65
+ }
66
+ if (commonType === null) {
56
67
  commonType = variantType;
57
- else if (commonType !== variantType)
68
+ }
69
+ else if (commonType !== variantType) {
58
70
  return null;
71
+ }
59
72
  allValues.push(literalValue);
60
73
  }
61
- if (commonType && allValues.length > 0)
74
+ if (commonType && allValues.length > 0) {
62
75
  return { type: commonType, enum: allValues };
76
+ }
63
77
  return null;
64
78
  }
65
79
  function isNullSchema(variant) {
@@ -67,22 +81,25 @@ function isNullSchema(variant) {
67
81
  return false;
68
82
  }
69
83
  const record = variant;
70
- if ("const" in record && record.const === null)
84
+ if ("const" in record && record.const === null) {
71
85
  return true;
86
+ }
72
87
  if (Array.isArray(record.enum) && record.enum.length === 1) {
73
88
  return record.enum[0] === null;
74
89
  }
75
90
  const typeValue = record.type;
76
- if (typeValue === "null")
91
+ if (typeValue === "null") {
77
92
  return true;
93
+ }
78
94
  if (Array.isArray(typeValue) && typeValue.length === 1 && typeValue[0] === "null") {
79
95
  return true;
80
96
  }
81
97
  return false;
82
98
  }
83
99
  function stripNullVariants(variants) {
84
- if (variants.length === 0)
100
+ if (variants.length === 0) {
85
101
  return { variants, stripped: false };
102
+ }
86
103
  const nonNull = variants.filter((variant) => !isNullSchema(variant));
87
104
  return {
88
105
  variants: nonNull,
@@ -98,16 +115,19 @@ function extendSchemaDefs(defs, schema) {
98
115
  !Array.isArray(schema.definitions)
99
116
  ? schema.definitions
100
117
  : undefined;
101
- if (!defsEntry && !legacyDefsEntry)
118
+ if (!defsEntry && !legacyDefsEntry) {
102
119
  return defs;
120
+ }
103
121
  const next = defs ? new Map(defs) : new Map();
104
122
  if (defsEntry) {
105
- for (const [key, value] of Object.entries(defsEntry))
123
+ for (const [key, value] of Object.entries(defsEntry)) {
106
124
  next.set(key, value);
125
+ }
107
126
  }
108
127
  if (legacyDefsEntry) {
109
- for (const [key, value] of Object.entries(legacyDefsEntry))
128
+ for (const [key, value] of Object.entries(legacyDefsEntry)) {
110
129
  next.set(key, value);
130
+ }
111
131
  }
112
132
  return next;
113
133
  }
@@ -115,19 +135,48 @@ function decodeJsonPointerSegment(segment) {
115
135
  return segment.replaceAll("~1", "/").replaceAll("~0", "~");
116
136
  }
117
137
  function tryResolveLocalRef(ref, defs) {
118
- if (!defs)
138
+ if (!defs) {
119
139
  return undefined;
140
+ }
120
141
  const match = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
121
- if (!match)
142
+ if (!match) {
122
143
  return undefined;
144
+ }
123
145
  const name = decodeJsonPointerSegment(match[1] ?? "");
124
- if (!name)
146
+ if (!name) {
125
147
  return undefined;
148
+ }
126
149
  return defs.get(name);
127
150
  }
151
+ function simplifyUnionVariants(params) {
152
+ const { obj, variants } = params;
153
+ const { variants: nonNullVariants, stripped } = stripNullVariants(variants);
154
+ const flattened = tryFlattenLiteralAnyOf(nonNullVariants);
155
+ if (flattened) {
156
+ const result = {
157
+ type: flattened.type,
158
+ enum: flattened.enum,
159
+ };
160
+ copySchemaMeta(obj, result);
161
+ return { variants: nonNullVariants, simplified: result };
162
+ }
163
+ if (stripped && nonNullVariants.length === 1) {
164
+ const lone = nonNullVariants[0];
165
+ if (lone && typeof lone === "object" && !Array.isArray(lone)) {
166
+ const result = {
167
+ ...lone,
168
+ };
169
+ copySchemaMeta(obj, result);
170
+ return { variants: nonNullVariants, simplified: result };
171
+ }
172
+ return { variants: nonNullVariants, simplified: lone };
173
+ }
174
+ return { variants: stripped ? nonNullVariants : variants };
175
+ }
128
176
  function cleanSchemaForGeminiWithDefs(schema, defs, refStack) {
129
- if (!schema || typeof schema !== "object")
177
+ if (!schema || typeof schema !== "object") {
130
178
  return schema;
179
+ }
131
180
  if (Array.isArray(schema)) {
132
181
  return schema.map((item) => cleanSchemaForGeminiWithDefs(item, defs, refStack));
133
182
  }
@@ -135,8 +184,9 @@ function cleanSchemaForGeminiWithDefs(schema, defs, refStack) {
135
184
  const nextDefs = extendSchemaDefs(defs, obj);
136
185
  const refValue = typeof obj.$ref === "string" ? obj.$ref : undefined;
137
186
  if (refValue) {
138
- if (refStack?.has(refValue))
187
+ if (refStack?.has(refValue)) {
139
188
  return {};
189
+ }
140
190
  const resolved = tryResolveLocalRef(refValue, nextDefs);
141
191
  if (resolved) {
142
192
  const nextRefStack = refStack ? new Set(refStack) : new Set();
@@ -148,17 +198,11 @@ function cleanSchemaForGeminiWithDefs(schema, defs, refStack) {
148
198
  const result = {
149
199
  ...cleaned,
150
200
  };
151
- for (const key of ["description", "title", "default"]) {
152
- if (key in obj && obj[key] !== undefined)
153
- result[key] = obj[key];
154
- }
201
+ copySchemaMeta(obj, result);
155
202
  return result;
156
203
  }
157
204
  const result = {};
158
- for (const key of ["description", "title", "default"]) {
159
- if (key in obj && obj[key] !== undefined)
160
- result[key] = obj[key];
161
- }
205
+ copySchemaMeta(obj, result);
162
206
  return result;
163
207
  }
164
208
  const hasAnyOf = "anyOf" in obj && Array.isArray(obj.anyOf);
@@ -170,77 +214,31 @@ function cleanSchemaForGeminiWithDefs(schema, defs, refStack) {
170
214
  ? obj.oneOf.map((variant) => cleanSchemaForGeminiWithDefs(variant, nextDefs, refStack))
171
215
  : undefined;
172
216
  if (hasAnyOf) {
173
- const { variants: nonNullVariants, stripped } = stripNullVariants(cleanedAnyOf ?? []);
174
- if (stripped)
175
- cleanedAnyOf = nonNullVariants;
176
- const flattened = tryFlattenLiteralAnyOf(nonNullVariants);
177
- if (flattened) {
178
- const result = {
179
- type: flattened.type,
180
- enum: flattened.enum,
181
- };
182
- for (const key of ["description", "title", "default"]) {
183
- if (key in obj && obj[key] !== undefined)
184
- result[key] = obj[key];
185
- }
186
- return result;
187
- }
188
- if (stripped && nonNullVariants.length === 1) {
189
- const lone = nonNullVariants[0];
190
- if (lone && typeof lone === "object" && !Array.isArray(lone)) {
191
- const result = {
192
- ...lone,
193
- };
194
- for (const key of ["description", "title", "default"]) {
195
- if (key in obj && obj[key] !== undefined)
196
- result[key] = obj[key];
197
- }
198
- return result;
199
- }
200
- return lone;
217
+ const simplified = simplifyUnionVariants({ obj, variants: cleanedAnyOf ?? [] });
218
+ cleanedAnyOf = simplified.variants;
219
+ if ("simplified" in simplified) {
220
+ return simplified.simplified;
201
221
  }
202
222
  }
203
223
  if (hasOneOf) {
204
- const { variants: nonNullVariants, stripped } = stripNullVariants(cleanedOneOf ?? []);
205
- if (stripped)
206
- cleanedOneOf = nonNullVariants;
207
- const flattened = tryFlattenLiteralAnyOf(nonNullVariants);
208
- if (flattened) {
209
- const result = {
210
- type: flattened.type,
211
- enum: flattened.enum,
212
- };
213
- for (const key of ["description", "title", "default"]) {
214
- if (key in obj && obj[key] !== undefined)
215
- result[key] = obj[key];
216
- }
217
- return result;
218
- }
219
- if (stripped && nonNullVariants.length === 1) {
220
- const lone = nonNullVariants[0];
221
- if (lone && typeof lone === "object" && !Array.isArray(lone)) {
222
- const result = {
223
- ...lone,
224
- };
225
- for (const key of ["description", "title", "default"]) {
226
- if (key in obj && obj[key] !== undefined)
227
- result[key] = obj[key];
228
- }
229
- return result;
230
- }
231
- return lone;
224
+ const simplified = simplifyUnionVariants({ obj, variants: cleanedOneOf ?? [] });
225
+ cleanedOneOf = simplified.variants;
226
+ if ("simplified" in simplified) {
227
+ return simplified.simplified;
232
228
  }
233
229
  }
234
230
  const cleaned = {};
235
231
  for (const [key, value] of Object.entries(obj)) {
236
- if (GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS.has(key))
232
+ if (GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS.has(key)) {
237
233
  continue;
234
+ }
238
235
  if (key === "const") {
239
236
  cleaned.enum = [value];
240
237
  continue;
241
238
  }
242
- if (key === "type" && (hasAnyOf || hasOneOf))
239
+ if (key === "type" && (hasAnyOf || hasOneOf)) {
243
240
  continue;
241
+ }
244
242
  if (key === "type" &&
245
243
  Array.isArray(value) &&
246
244
  value.every((entry) => typeof entry === "string")) {
@@ -283,13 +281,62 @@ function cleanSchemaForGeminiWithDefs(schema, defs, refStack) {
283
281
  cleaned[key] = value;
284
282
  }
285
283
  }
284
+ // Cloud Code Assist API rejects anyOf/oneOf in nested schemas even after
285
+ // simplifyUnionVariants runs above. Flatten remaining unions as a fallback:
286
+ // pick the common type or use the first variant's type so the tool
287
+ // declaration is accepted by Google's validation layer.
288
+ if (cleaned.anyOf && Array.isArray(cleaned.anyOf)) {
289
+ const flattened = flattenUnionFallback(cleaned, cleaned.anyOf);
290
+ if (flattened) {
291
+ return flattened;
292
+ }
293
+ }
294
+ if (cleaned.oneOf && Array.isArray(cleaned.oneOf)) {
295
+ const flattened = flattenUnionFallback(cleaned, cleaned.oneOf);
296
+ if (flattened) {
297
+ return flattened;
298
+ }
299
+ }
286
300
  return cleaned;
287
301
  }
302
+ /**
303
+ * Last-resort flattening for anyOf/oneOf arrays that could not be simplified
304
+ * by `simplifyUnionVariants`. Picks a representative type so the schema is
305
+ * accepted by Google's restricted JSON Schema validation.
306
+ */
307
+ function flattenUnionFallback(obj, variants) {
308
+ const objects = variants.filter((v) => !!v && typeof v === "object");
309
+ if (objects.length === 0) {
310
+ return undefined;
311
+ }
312
+ const types = new Set(objects.map((v) => v.type).filter(Boolean));
313
+ if (objects.length === 1) {
314
+ const merged = { ...objects[0] };
315
+ copySchemaMeta(obj, merged);
316
+ return merged;
317
+ }
318
+ if (types.size === 1) {
319
+ const merged = { type: Array.from(types)[0] };
320
+ copySchemaMeta(obj, merged);
321
+ return merged;
322
+ }
323
+ const first = objects[0];
324
+ if (first?.type) {
325
+ const merged = { type: first.type };
326
+ copySchemaMeta(obj, merged);
327
+ return merged;
328
+ }
329
+ const merged = {};
330
+ copySchemaMeta(obj, merged);
331
+ return merged;
332
+ }
288
333
  export function cleanSchemaForGemini(schema) {
289
- if (!schema || typeof schema !== "object")
334
+ if (!schema || typeof schema !== "object") {
290
335
  return schema;
291
- if (Array.isArray(schema))
336
+ }
337
+ if (Array.isArray(schema)) {
292
338
  return schema.map(cleanSchemaForGemini);
339
+ }
293
340
  const defs = extendSchemaDefs(undefined, schema);
294
341
  return cleanSchemaForGeminiWithDefs(schema, defs, undefined);
295
342
  }
@@ -103,30 +103,35 @@ function randomChoice(values, fallback) {
103
103
  }
104
104
  function createSlugBase(words = 2) {
105
105
  const parts = [randomChoice(SLUG_ADJECTIVES, "steady"), randomChoice(SLUG_NOUNS, "harbor")];
106
- if (words > 2)
106
+ if (words > 2) {
107
107
  parts.push(randomChoice(SLUG_NOUNS, "reef"));
108
+ }
108
109
  return parts.join("-");
109
110
  }
110
111
  export function createSessionSlug(isTaken) {
111
112
  const isIdTaken = isTaken ?? (() => false);
112
113
  for (let attempt = 0; attempt < 12; attempt += 1) {
113
114
  const base = createSlugBase(2);
114
- if (!isIdTaken(base))
115
+ if (!isIdTaken(base)) {
115
116
  return base;
117
+ }
116
118
  for (let i = 2; i <= 12; i += 1) {
117
119
  const candidate = `${base}-${i}`;
118
- if (!isIdTaken(candidate))
120
+ if (!isIdTaken(candidate)) {
119
121
  return candidate;
122
+ }
120
123
  }
121
124
  }
122
125
  for (let attempt = 0; attempt < 12; attempt += 1) {
123
126
  const base = createSlugBase(3);
124
- if (!isIdTaken(base))
127
+ if (!isIdTaken(base)) {
125
128
  return base;
129
+ }
126
130
  for (let i = 2; i <= 12; i += 1) {
127
131
  const candidate = `${base}-${i}`;
128
- if (!isIdTaken(candidate))
132
+ if (!isIdTaken(candidate)) {
129
133
  return candidate;
134
+ }
130
135
  }
131
136
  }
132
137
  const fallback = `${createSlugBase(3)}-${Math.random().toString(36).slice(2, 5)}`;