@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,17 +1,21 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
3
+ import { MANIFEST_KEY } from "../compat/legacy-names.js";
4
+ import { createSubsystemLogger } from "../logging/subsystem.js";
5
+ import { isPathInsideWithRealpath } from "../security/scan-paths.js";
4
6
  import { CONFIG_DIR, resolveUserPath } from "../utils.js";
5
7
  import { resolveBundledHooksDir } from "./bundled-dir.js";
6
8
  import { shouldIncludeHook } from "./config.js";
7
9
  import { parseFrontmatter, resolvePoolbotMetadata, resolveHookInvocationPolicy, } from "./frontmatter.js";
10
+ const log = createSubsystemLogger("hooks/workspace");
8
11
  function filterHookEntries(entries, config, eligibility) {
9
12
  return entries.filter((entry) => shouldIncludeHook({ entry, config, eligibility }));
10
13
  }
11
14
  function readHookPackageManifest(dir) {
12
15
  const manifestPath = path.join(dir, "package.json");
13
- if (!fs.existsSync(manifestPath))
16
+ if (!fs.existsSync(manifestPath)) {
14
17
  return null;
18
+ }
15
19
  try {
16
20
  const raw = fs.readFileSync(manifestPath, "utf-8");
17
21
  return JSON.parse(raw);
@@ -21,15 +25,27 @@ function readHookPackageManifest(dir) {
21
25
  }
22
26
  }
23
27
  function resolvePackageHooks(manifest) {
24
- const raw = manifest.poolbot?.hooks ?? manifest[LEGACY_MANIFEST_KEY]?.hooks;
25
- if (!Array.isArray(raw))
28
+ const raw = manifest[MANIFEST_KEY]?.hooks;
29
+ if (!Array.isArray(raw)) {
26
30
  return [];
31
+ }
27
32
  return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
28
33
  }
34
+ function resolveContainedDir(baseDir, targetDir) {
35
+ const base = path.resolve(baseDir);
36
+ const resolved = path.resolve(baseDir, targetDir);
37
+ if (!isPathInsideWithRealpath(base, resolved, {
38
+ requireRealpath: true,
39
+ })) {
40
+ return null;
41
+ }
42
+ return resolved;
43
+ }
29
44
  function loadHookFromDir(params) {
30
45
  const hookMdPath = path.join(params.hookDir, "HOOK.md");
31
- if (!fs.existsSync(hookMdPath))
46
+ if (!fs.existsSync(hookMdPath)) {
32
47
  return null;
48
+ }
33
49
  try {
34
50
  const content = fs.readFileSync(hookMdPath, "utf-8");
35
51
  const frontmatter = parseFrontmatter(content);
@@ -45,7 +61,7 @@ function loadHookFromDir(params) {
45
61
  }
46
62
  }
47
63
  if (!handlerPath) {
48
- console.warn(`[hooks] Hook "${name}" has HOOK.md but no handler file in ${params.hookDir}`);
64
+ log.warn(`Hook "${name}" has HOOK.md but no handler file in ${params.hookDir}`);
49
65
  return null;
50
66
  }
51
67
  return {
@@ -59,7 +75,8 @@ function loadHookFromDir(params) {
59
75
  };
60
76
  }
61
77
  catch (err) {
62
- console.warn(`[hooks] Failed to load hook from ${params.hookDir}:`, err);
78
+ const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
79
+ log.warn(`Failed to load hook from ${params.hookDir}: ${message}`);
63
80
  return null;
64
81
  }
65
82
  }
@@ -68,30 +85,38 @@ function loadHookFromDir(params) {
68
85
  */
69
86
  function loadHooksFromDir(params) {
70
87
  const { dir, source, pluginId } = params;
71
- if (!fs.existsSync(dir))
88
+ if (!fs.existsSync(dir)) {
72
89
  return [];
90
+ }
73
91
  const stat = fs.statSync(dir);
74
- if (!stat.isDirectory())
92
+ if (!stat.isDirectory()) {
75
93
  return [];
94
+ }
76
95
  const hooks = [];
77
96
  const entries = fs.readdirSync(dir, { withFileTypes: true });
78
97
  for (const entry of entries) {
79
- if (!entry.isDirectory())
98
+ if (!entry.isDirectory()) {
80
99
  continue;
100
+ }
81
101
  const hookDir = path.join(dir, entry.name);
82
102
  const manifest = readHookPackageManifest(hookDir);
83
103
  const packageHooks = manifest ? resolvePackageHooks(manifest) : [];
84
104
  if (packageHooks.length > 0) {
85
105
  for (const hookPath of packageHooks) {
86
- const resolvedHookDir = path.resolve(hookDir, hookPath);
106
+ const resolvedHookDir = resolveContainedDir(hookDir, hookPath);
107
+ if (!resolvedHookDir) {
108
+ log.warn(`Ignoring out-of-package hook path "${hookPath}" in ${hookDir} (must be within package directory)`);
109
+ continue;
110
+ }
87
111
  const hook = loadHookFromDir({
88
112
  hookDir: resolvedHookDir,
89
113
  source,
90
114
  pluginId,
91
115
  nameHint: path.basename(resolvedHookDir),
92
116
  });
93
- if (hook)
117
+ if (hook) {
94
118
  hooks.push(hook);
119
+ }
95
120
  }
96
121
  continue;
97
122
  }
@@ -101,8 +126,9 @@ function loadHooksFromDir(params) {
101
126
  pluginId,
102
127
  nameHint: entry.name,
103
128
  });
104
- if (hook)
129
+ if (hook) {
105
130
  hooks.push(hook);
131
+ }
106
132
  }
107
133
  return hooks;
108
134
  }
@@ -165,14 +191,18 @@ function loadHookEntries(workspaceDir, opts) {
165
191
  });
166
192
  const merged = new Map();
167
193
  // Precedence: extra < bundled < managed < workspace (workspace wins)
168
- for (const hook of extraHooks)
194
+ for (const hook of extraHooks) {
169
195
  merged.set(hook.name, hook);
170
- for (const hook of bundledHooks)
196
+ }
197
+ for (const hook of bundledHooks) {
171
198
  merged.set(hook.name, hook);
172
- for (const hook of managedHooks)
199
+ }
200
+ for (const hook of managedHooks) {
173
201
  merged.set(hook.name, hook);
174
- for (const hook of workspaceHooks)
202
+ }
203
+ for (const hook of workspaceHooks) {
175
204
  merged.set(hook.name, hook);
205
+ }
176
206
  return Array.from(merged.values()).map((hook) => {
177
207
  let frontmatter = {};
178
208
  try {
@@ -1,26 +1,13 @@
1
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
2
- function listConfiguredAccountIds(cfg) {
3
- const accounts = cfg.channels?.imessage?.accounts;
4
- if (!accounts || typeof accounts !== "object")
5
- return [];
6
- return Object.keys(accounts).filter(Boolean);
7
- }
8
- export function listIMessageAccountIds(cfg) {
9
- const ids = listConfiguredAccountIds(cfg);
10
- if (ids.length === 0)
11
- return [DEFAULT_ACCOUNT_ID];
12
- return ids.sort((a, b) => a.localeCompare(b));
13
- }
14
- export function resolveDefaultIMessageAccountId(cfg) {
15
- const ids = listIMessageAccountIds(cfg);
16
- if (ids.includes(DEFAULT_ACCOUNT_ID))
17
- return DEFAULT_ACCOUNT_ID;
18
- return ids[0] ?? DEFAULT_ACCOUNT_ID;
19
- }
1
+ import { createAccountListHelpers } from "../channels/plugins/account-helpers.js";
2
+ import { normalizeAccountId } from "../routing/session-key.js";
3
+ const { listAccountIds, resolveDefaultAccountId } = createAccountListHelpers("imessage");
4
+ export const listIMessageAccountIds = listAccountIds;
5
+ export const resolveDefaultIMessageAccountId = resolveDefaultAccountId;
20
6
  function resolveAccountConfig(cfg, accountId) {
21
7
  const accounts = cfg.channels?.imessage?.accounts;
22
- if (!accounts || typeof accounts !== "object")
8
+ if (!accounts || typeof accounts !== "object") {
23
9
  return undefined;
10
+ }
24
11
  return accounts[accountId];
25
12
  }
26
13
  function mergeIMessageAccountConfig(cfg, accountId) {
@@ -43,6 +30,8 @@ export function resolveIMessageAccount(params) {
43
30
  merged.dmPolicy ||
44
31
  merged.groupPolicy ||
45
32
  typeof merged.includeAttachments === "boolean" ||
33
+ (merged.attachmentRoots && merged.attachmentRoots.length > 0) ||
34
+ (merged.remoteAttachmentRoots && merged.remoteAttachmentRoots.length > 0) ||
46
35
  typeof merged.mediaMaxMb === "number" ||
47
36
  typeof merged.textChunkLimit === "number" ||
48
37
  (merged.groups && Object.keys(merged.groups).length > 0));
@@ -63,7 +63,8 @@ export function resolveIMessageInboundDecision(params) {
63
63
  return { kind: "drop", reason: "group without chat_id" };
64
64
  }
65
65
  const groupId = isGroup ? groupIdCandidate : undefined;
66
- const effectiveDmAllowFrom = Array.from(new Set([...params.allowFrom, ...params.storeAllowFrom]))
66
+ const storeAllowFrom = params.dmPolicy === "allowlist" ? [] : params.storeAllowFrom;
67
+ const effectiveDmAllowFrom = Array.from(new Set([...params.allowFrom, ...storeAllowFrom]))
67
68
  .map((v) => String(v).trim())
68
69
  .filter(Boolean);
69
70
  // Keep DM pairing-store authorization scoped to DMs; group access must come from explicit group allowlist config.
@@ -1,11 +1,20 @@
1
- import { createWriteStream } from "node:fs";
1
+ import { constants as fsConstants } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Readable, Transform } from "node:stream";
5
5
  import { pipeline } from "node:stream/promises";
6
6
  import JSZip from "jszip";
7
7
  import * as tar from "tar";
8
- import { resolveSafeBaseDir } from "./path-safety.js";
8
+ import { resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-path.js";
9
+ import { isNotFoundPathError, isPathInside, isSymlinkOpenError } from "./path-guards.js";
10
+ export class ArchiveSecurityError extends Error {
11
+ code;
12
+ constructor(code, message, options) {
13
+ super(message, options);
14
+ this.code = code;
15
+ this.name = "ArchiveSecurityError";
16
+ }
17
+ }
9
18
  /** @internal */
10
19
  export const DEFAULT_MAX_ARCHIVE_BYTES_ZIP = 256 * 1024 * 1024;
11
20
  /** @internal */
@@ -18,7 +27,12 @@ const ERROR_ARCHIVE_SIZE_EXCEEDS_LIMIT = "archive size exceeds limit";
18
27
  const ERROR_ARCHIVE_ENTRY_COUNT_EXCEEDS_LIMIT = "archive entry count exceeds limit";
19
28
  const ERROR_ARCHIVE_ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT = "archive entry extracted size exceeds limit";
20
29
  const ERROR_ARCHIVE_EXTRACTED_SIZE_EXCEEDS_LIMIT = "archive extracted size exceeds limit";
30
+ const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
21
31
  const TAR_SUFFIXES = [".tgz", ".tar.gz", ".tar"];
32
+ const OPEN_WRITE_FLAGS = fsConstants.O_WRONLY |
33
+ fsConstants.O_CREAT |
34
+ fsConstants.O_TRUNC |
35
+ (process.platform !== "win32" && "O_NOFOLLOW" in fsConstants ? fsConstants.O_NOFOLLOW : 0);
22
36
  export function resolveArchiveKind(filePath) {
23
37
  const lower = filePath.toLowerCase();
24
38
  if (lower.endsWith(".zip")) {
@@ -67,53 +81,6 @@ export async function withTimeout(promise, timeoutMs, label) {
67
81
  }
68
82
  }
69
83
  }
70
- // Path hygiene.
71
- function normalizeArchivePath(raw) {
72
- // Archives may contain Windows separators; treat them as separators.
73
- return raw.replaceAll("\\", "/");
74
- }
75
- function isWindowsDrivePath(p) {
76
- return /^[a-zA-Z]:[\\/]/.test(p);
77
- }
78
- function validateArchiveEntryPath(entryPath) {
79
- if (!entryPath || entryPath === "." || entryPath === "./") {
80
- return;
81
- }
82
- if (isWindowsDrivePath(entryPath)) {
83
- throw new Error(`archive entry uses a drive path: ${entryPath}`);
84
- }
85
- const normalized = path.posix.normalize(normalizeArchivePath(entryPath));
86
- if (normalized === ".." || normalized.startsWith("../")) {
87
- throw new Error(`archive entry escapes destination: ${entryPath}`);
88
- }
89
- if (path.posix.isAbsolute(normalized) || normalized.startsWith("//")) {
90
- throw new Error(`archive entry is absolute: ${entryPath}`);
91
- }
92
- }
93
- function stripArchivePath(entryPath, stripComponents) {
94
- const raw = normalizeArchivePath(entryPath);
95
- if (!raw || raw === "." || raw === "./") {
96
- return null;
97
- }
98
- // Important: mimic tar --strip-components semantics (raw segments before
99
- // normalization) so strip-induced escapes like "a/../b" are not hidden.
100
- const parts = raw.split("/").filter((part) => part.length > 0 && part !== ".");
101
- const strip = Math.max(0, Math.floor(stripComponents));
102
- const stripped = strip === 0 ? parts.join("/") : parts.slice(strip).join("/");
103
- const result = path.posix.normalize(stripped);
104
- if (!result || result === "." || result === "./") {
105
- return null;
106
- }
107
- return result;
108
- }
109
- function resolveCheckedOutPath(destDir, relPath, original) {
110
- const safeBase = resolveSafeBaseDir(destDir);
111
- const outPath = path.resolve(destDir, relPath);
112
- if (!outPath.startsWith(safeBase)) {
113
- throw new Error(`archive entry escapes destination: ${original}`);
114
- }
115
- return outPath;
116
- }
117
84
  function clampLimit(value) {
118
85
  if (typeof value !== "number" || !Number.isFinite(value)) {
119
86
  return undefined;
@@ -181,6 +148,80 @@ function createExtractBudgetTransform(params) {
181
148
  },
182
149
  });
183
150
  }
151
+ function symlinkTraversalError(originalPath) {
152
+ return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${originalPath}`);
153
+ }
154
+ async function assertDestinationDirReady(destDir) {
155
+ const stat = await fs.lstat(destDir);
156
+ if (stat.isSymbolicLink()) {
157
+ throw new ArchiveSecurityError("destination-symlink", "archive destination is a symlink");
158
+ }
159
+ if (!stat.isDirectory()) {
160
+ throw new ArchiveSecurityError("destination-not-directory", "archive destination is not a directory");
161
+ }
162
+ return await fs.realpath(destDir);
163
+ }
164
+ async function assertNoSymlinkTraversal(params) {
165
+ const parts = params.relPath.split("/").filter(Boolean);
166
+ let current = path.resolve(params.rootDir);
167
+ for (const part of parts) {
168
+ current = path.join(current, part);
169
+ let stat;
170
+ try {
171
+ stat = await fs.lstat(current);
172
+ }
173
+ catch (err) {
174
+ if (isNotFoundPathError(err)) {
175
+ continue;
176
+ }
177
+ throw err;
178
+ }
179
+ if (stat.isSymbolicLink()) {
180
+ throw symlinkTraversalError(params.originalPath);
181
+ }
182
+ }
183
+ }
184
+ async function assertResolvedInsideDestination(params) {
185
+ let resolved;
186
+ try {
187
+ resolved = await fs.realpath(params.targetPath);
188
+ }
189
+ catch (err) {
190
+ if (isNotFoundPathError(err)) {
191
+ return;
192
+ }
193
+ throw err;
194
+ }
195
+ if (!isPathInside(params.destinationRealDir, resolved)) {
196
+ throw symlinkTraversalError(params.originalPath);
197
+ }
198
+ }
199
+ async function openZipOutputFile(outPath, originalPath) {
200
+ try {
201
+ return await fs.open(outPath, OPEN_WRITE_FLAGS, 0o666);
202
+ }
203
+ catch (err) {
204
+ if (isSymlinkOpenError(err)) {
205
+ throw symlinkTraversalError(originalPath);
206
+ }
207
+ throw err;
208
+ }
209
+ }
210
+ async function cleanupPartialRegularFile(filePath) {
211
+ let stat;
212
+ try {
213
+ stat = await fs.lstat(filePath);
214
+ }
215
+ catch (err) {
216
+ if (isNotFoundPathError(err)) {
217
+ return;
218
+ }
219
+ throw err;
220
+ }
221
+ if (stat.isFile()) {
222
+ await fs.unlink(filePath).catch(() => undefined);
223
+ }
224
+ }
184
225
  async function readZipEntryStream(entry) {
185
226
  if (typeof entry.nodeStream === "function") {
186
227
  return entry.nodeStream();
@@ -189,8 +230,68 @@ async function readZipEntryStream(entry) {
189
230
  const buf = await entry.async("nodebuffer");
190
231
  return Readable.from(buf);
191
232
  }
233
+ function resolveZipOutputPath(params) {
234
+ validateArchiveEntryPath(params.entryPath);
235
+ const relPath = stripArchivePath(params.entryPath, params.strip);
236
+ if (!relPath) {
237
+ return null;
238
+ }
239
+ validateArchiveEntryPath(relPath);
240
+ return {
241
+ relPath,
242
+ outPath: resolveArchiveOutputPath({
243
+ rootDir: params.destinationDir,
244
+ relPath,
245
+ originalPath: params.entryPath,
246
+ }),
247
+ };
248
+ }
249
+ async function prepareZipOutputPath(params) {
250
+ await assertNoSymlinkTraversal({
251
+ rootDir: params.destinationDir,
252
+ relPath: params.relPath,
253
+ originalPath: params.originalPath,
254
+ });
255
+ if (params.isDirectory) {
256
+ await fs.mkdir(params.outPath, { recursive: true });
257
+ await assertResolvedInsideDestination({
258
+ destinationRealDir: params.destinationRealDir,
259
+ targetPath: params.outPath,
260
+ originalPath: params.originalPath,
261
+ });
262
+ return;
263
+ }
264
+ const parentDir = path.dirname(params.outPath);
265
+ await fs.mkdir(parentDir, { recursive: true });
266
+ await assertResolvedInsideDestination({
267
+ destinationRealDir: params.destinationRealDir,
268
+ targetPath: parentDir,
269
+ originalPath: params.originalPath,
270
+ });
271
+ }
272
+ async function writeZipFileEntry(params) {
273
+ const handle = await openZipOutputFile(params.outPath, params.entry.name);
274
+ params.budget.startEntry();
275
+ const readable = await readZipEntryStream(params.entry);
276
+ const writable = handle.createWriteStream();
277
+ try {
278
+ await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), writable);
279
+ }
280
+ catch (err) {
281
+ await cleanupPartialRegularFile(params.outPath).catch(() => undefined);
282
+ throw err;
283
+ }
284
+ // Best-effort permission restore for zip entries created on unix.
285
+ if (typeof params.entry.unixPermissions === "number") {
286
+ const mode = params.entry.unixPermissions & 0o777;
287
+ if (mode !== 0) {
288
+ await fs.chmod(params.outPath, mode).catch(() => undefined);
289
+ }
290
+ }
291
+ }
192
292
  async function extractZip(params) {
193
293
  const limits = resolveExtractLimits(params.limits);
294
+ const destinationRealDir = await assertDestinationDirReady(params.destDir);
194
295
  const stat = await fs.stat(params.archivePath);
195
296
  if (stat.size > limits.maxArchiveBytes) {
196
297
  throw new Error(ERROR_ARCHIVE_SIZE_EXCEEDS_LIMIT);
@@ -202,34 +303,30 @@ async function extractZip(params) {
202
303
  assertArchiveEntryCountWithinLimit(entries.length, limits);
203
304
  const budget = createByteBudgetTracker(limits);
204
305
  for (const entry of entries) {
205
- validateArchiveEntryPath(entry.name);
206
- const relPath = stripArchivePath(entry.name, strip);
207
- if (!relPath) {
306
+ const output = resolveZipOutputPath({
307
+ entryPath: entry.name,
308
+ strip,
309
+ destinationDir: params.destDir,
310
+ });
311
+ if (!output) {
208
312
  continue;
209
313
  }
210
- validateArchiveEntryPath(relPath);
211
- const outPath = resolveCheckedOutPath(params.destDir, relPath, entry.name);
314
+ await prepareZipOutputPath({
315
+ destinationDir: params.destDir,
316
+ destinationRealDir,
317
+ relPath: output.relPath,
318
+ outPath: output.outPath,
319
+ originalPath: entry.name,
320
+ isDirectory: entry.dir,
321
+ });
212
322
  if (entry.dir) {
213
- await fs.mkdir(outPath, { recursive: true });
214
323
  continue;
215
324
  }
216
- await fs.mkdir(path.dirname(outPath), { recursive: true });
217
- budget.startEntry();
218
- const readable = await readZipEntryStream(entry);
219
- try {
220
- await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: budget.addBytes }), createWriteStream(outPath));
221
- }
222
- catch (err) {
223
- await fs.unlink(outPath).catch(() => undefined);
224
- throw err;
225
- }
226
- // Best-effort permission restore for zip entries created on unix.
227
- if (typeof entry.unixPermissions === "number") {
228
- const mode = entry.unixPermissions & 0o777;
229
- if (mode !== 0) {
230
- await fs.chmod(outPath, mode).catch(() => undefined);
231
- }
232
- }
325
+ await writeZipFileEntry({
326
+ entry,
327
+ outPath: output.outPath,
328
+ budget,
329
+ });
233
330
  }
234
331
  }
235
332
  function readTarEntryInfo(entry) {
@@ -275,7 +372,11 @@ export async function extractArchive(params) {
275
372
  return;
276
373
  }
277
374
  validateArchiveEntryPath(relPath);
278
- resolveCheckedOutPath(params.destDir, relPath, info.path);
375
+ resolveArchiveOutputPath({
376
+ rootDir: params.destDir,
377
+ relPath,
378
+ originalPath: info.path,
379
+ });
279
380
  if (info.type === "SymbolicLink" ||
280
381
  info.type === "Link" ||
281
382
  info.type === "BlockDevice" ||
@@ -11,7 +11,10 @@ export function resolveControlUiDistIndexPathForRoot(root) {
11
11
  export async function resolveControlUiDistIndexHealth(opts = {}) {
12
12
  const indexPath = opts.root
13
13
  ? resolveControlUiDistIndexPathForRoot(opts.root)
14
- : await resolveControlUiDistIndexPath(opts.argv1 ?? process.argv[1]);
14
+ : await resolveControlUiDistIndexPath({
15
+ argv1: opts.argv1 ?? process.argv[1],
16
+ moduleUrl: opts.moduleUrl,
17
+ });
15
18
  return {
16
19
  indexPath,
17
20
  exists: Boolean(indexPath && fs.existsSync(indexPath)),
@@ -44,7 +47,9 @@ export function resolveControlUiRepoRoot(argv1 = process.argv[1]) {
44
47
  }
45
48
  return null;
46
49
  }
47
- export async function resolveControlUiDistIndexPath(argv1 = process.argv[1]) {
50
+ export async function resolveControlUiDistIndexPath(argv1OrOpts) {
51
+ const argv1 = typeof argv1OrOpts === "string" ? argv1OrOpts : (argv1OrOpts?.argv1 ?? process.argv[1]);
52
+ const moduleUrl = typeof argv1OrOpts === "object" ? argv1OrOpts?.moduleUrl : undefined;
48
53
  if (!argv1) {
49
54
  return null;
50
55
  }
@@ -54,7 +59,7 @@ export async function resolveControlUiDistIndexPath(argv1 = process.argv[1]) {
54
59
  if (path.basename(distDir) === "dist") {
55
60
  return path.join(distDir, "control-ui", "index.html");
56
61
  }
57
- const packageRoot = await resolvePoolBotPackageRoot({ argv1: normalized });
62
+ const packageRoot = await resolvePoolBotPackageRoot({ argv1: normalized, moduleUrl });
58
63
  if (packageRoot) {
59
64
  return path.join(packageRoot, "dist", "control-ui", "index.html");
60
65
  }
@@ -64,16 +69,19 @@ export async function resolveControlUiDistIndexPath(argv1 = process.argv[1]) {
64
69
  for (let i = 0; i < 8; i++) {
65
70
  const pkgJsonPath = path.join(dir, "package.json");
66
71
  const indexPath = path.join(dir, "dist", "control-ui", "index.html");
67
- if (fs.existsSync(pkgJsonPath) && fs.existsSync(indexPath)) {
72
+ if (fs.existsSync(pkgJsonPath)) {
68
73
  try {
69
74
  const raw = fs.readFileSync(pkgJsonPath, "utf-8");
70
75
  const parsed = JSON.parse(raw);
71
76
  if (parsed.name === "poolbot") {
72
- return indexPath;
77
+ return fs.existsSync(indexPath) ? indexPath : null;
73
78
  }
79
+ // Stop at the first package boundary to avoid resolving through unrelated ancestors.
80
+ return null;
74
81
  }
75
82
  catch {
76
- // Invalid package.json, continue searching
83
+ // Invalid package.json at package boundary; abort fallback resolution.
84
+ return null;
77
85
  }
78
86
  }
79
87
  const parent = path.dirname(dir);