@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
@@ -3,19 +3,28 @@
3
3
  *
4
4
  * These functions perform I/O (filesystem, config reads) to detect security issues.
5
5
  */
6
- import JSON5 from "json5";
7
6
  import fs from "node:fs/promises";
8
7
  import path from "node:path";
9
- import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
8
+ import { resolveDefaultAgentId } from "../agents/agent-scope.js";
9
+ import { isToolAllowedByPolicies } from "../agents/pi-tools.policy.js";
10
+ import { resolveSandboxConfigForAgent, resolveSandboxToolPolicyForAgent, } from "../agents/sandbox.js";
11
+ import { SANDBOX_BROWSER_SECURITY_HASH_EPOCH } from "../agents/sandbox/constants.js";
12
+ import { execDockerRaw } from "../agents/sandbox/docker.js";
10
13
  import { loadWorkspaceSkillEntries } from "../agents/skills.js";
11
- import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
14
+ import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
15
+ import { listAgentWorkspaceDirs } from "../agents/workspace-dirs.js";
16
+ import { formatCliCommand } from "../cli/command-format.js";
17
+ import { MANIFEST_KEY } from "../compat/legacy-names.js";
12
18
  import { resolveNativeSkillsEnabled } from "../config/commands.js";
13
19
  import { createConfigIO } from "../config/config.js";
14
- import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
20
+ import { collectIncludePathsRecursive } from "../config/includes-scan.js";
15
21
  import { resolveOAuthDir } from "../config/paths.js";
22
+ import { normalizePluginsConfig } from "../plugins/config-state.js";
16
23
  import { normalizeAgentId } from "../routing/session-key.js";
17
24
  import { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, safeStat, } from "./audit-fs.js";
18
- import { scanDirectoryWithSummary } from "./skill-scanner.js";
25
+ import { pickSandboxToolPolicy } from "./audit-tool-policy.js";
26
+ import { extensionUsesSkippedScannerPath, isPathInside } from "./scan-paths.js";
27
+ import * as skillScanner from "./skill-scanner.js";
19
28
  // --------------------------------------------------------------------------
20
29
  // Helpers
21
30
  // --------------------------------------------------------------------------
@@ -35,91 +44,6 @@ function expandTilde(p, env) {
35
44
  }
36
45
  return null;
37
46
  }
38
- function resolveIncludePath(baseConfigPath, includePath) {
39
- return path.normalize(path.isAbsolute(includePath)
40
- ? includePath
41
- : path.resolve(path.dirname(baseConfigPath), includePath));
42
- }
43
- function listDirectIncludes(parsed) {
44
- const out = [];
45
- const visit = (value) => {
46
- if (!value) {
47
- return;
48
- }
49
- if (Array.isArray(value)) {
50
- for (const item of value) {
51
- visit(item);
52
- }
53
- return;
54
- }
55
- if (typeof value !== "object") {
56
- return;
57
- }
58
- const rec = value;
59
- const includeVal = rec[INCLUDE_KEY];
60
- if (typeof includeVal === "string") {
61
- out.push(includeVal);
62
- }
63
- else if (Array.isArray(includeVal)) {
64
- for (const item of includeVal) {
65
- if (typeof item === "string") {
66
- out.push(item);
67
- }
68
- }
69
- }
70
- for (const v of Object.values(rec)) {
71
- visit(v);
72
- }
73
- };
74
- visit(parsed);
75
- return out;
76
- }
77
- async function collectIncludePathsRecursive(params) {
78
- const visited = new Set();
79
- const result = [];
80
- const walk = async (basePath, parsed, depth) => {
81
- if (depth > MAX_INCLUDE_DEPTH) {
82
- return;
83
- }
84
- for (const raw of listDirectIncludes(parsed)) {
85
- const resolved = resolveIncludePath(basePath, raw);
86
- if (visited.has(resolved)) {
87
- continue;
88
- }
89
- visited.add(resolved);
90
- result.push(resolved);
91
- const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
92
- if (!rawText) {
93
- continue;
94
- }
95
- const nestedParsed = (() => {
96
- try {
97
- return JSON5.parse(rawText);
98
- }
99
- catch {
100
- return null;
101
- }
102
- })();
103
- if (nestedParsed) {
104
- // eslint-disable-next-line no-await-in-loop
105
- await walk(resolved, nestedParsed, depth + 1);
106
- }
107
- }
108
- };
109
- await walk(params.configPath, params.parsed, 0);
110
- return result;
111
- }
112
- function isPathInside(basePath, candidatePath) {
113
- const base = path.resolve(basePath);
114
- const candidate = path.resolve(candidatePath);
115
- const rel = path.relative(base, candidate);
116
- return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
117
- }
118
- function extensionUsesSkippedScannerPath(entry) {
119
- const segments = entry.split(/[\\/]+/).filter(Boolean);
120
- return segments.some((segment) => segment === "node_modules" ||
121
- (segment.startsWith(".") && segment !== "." && segment !== ".."));
122
- }
123
47
  async function readPluginManifestExtensions(pluginPath) {
124
48
  const manifestPath = path.join(pluginPath, "package.json");
125
49
  const raw = await fs.readFile(manifestPath, "utf-8").catch(() => "");
@@ -127,25 +51,12 @@ async function readPluginManifestExtensions(pluginPath) {
127
51
  return [];
128
52
  }
129
53
  const parsed = JSON.parse(raw);
130
- const extensions = parsed?.[LEGACY_MANIFEST_KEY]?.extensions;
54
+ const extensions = parsed?.[MANIFEST_KEY]?.extensions;
131
55
  if (!Array.isArray(extensions)) {
132
56
  return [];
133
57
  }
134
58
  return extensions.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
135
59
  }
136
- function listWorkspaceDirs(cfg) {
137
- const dirs = new Set();
138
- const list = cfg.agents?.list;
139
- if (Array.isArray(list)) {
140
- for (const entry of list) {
141
- if (entry && typeof entry === "object" && typeof entry.id === "string") {
142
- dirs.add(resolveAgentWorkspaceDir(cfg, entry.id));
143
- }
144
- }
145
- }
146
- dirs.add(resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
147
- return [...dirs];
148
- }
149
60
  function formatCodeSafetyDetails(findings, rootDir) {
150
61
  return findings
151
62
  .map((finding) => {
@@ -158,74 +69,491 @@ function formatCodeSafetyDetails(findings, rootDir) {
158
69
  })
159
70
  .join("\n");
160
71
  }
161
- // --------------------------------------------------------------------------
162
- // Exported collectors
163
- // --------------------------------------------------------------------------
164
- export async function collectPluginsTrustFindings(params) {
165
- const findings = [];
72
+ async function listInstalledPluginDirs(params) {
166
73
  const extensionsDir = path.join(params.stateDir, "extensions");
167
74
  const st = await safeStat(extensionsDir);
168
75
  if (!st.ok || !st.isDir) {
169
- return findings;
76
+ return { extensionsDir, pluginDirs: [] };
170
77
  }
171
- const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch(() => []);
78
+ const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch((err) => {
79
+ params.onReadError?.(err);
80
+ return [];
81
+ });
172
82
  const pluginDirs = entries
173
- .filter((e) => e.isDirectory())
174
- .map((e) => e.name)
83
+ .filter((entry) => entry.isDirectory())
84
+ .map((entry) => entry.name)
175
85
  .filter(Boolean);
176
- if (pluginDirs.length === 0) {
86
+ return { extensionsDir, pluginDirs };
87
+ }
88
+ function resolveToolPolicies(params) {
89
+ const profile = params.agentTools?.profile ?? params.cfg.tools?.profile;
90
+ const profilePolicy = resolveToolProfilePolicy(profile);
91
+ const policies = [
92
+ profilePolicy,
93
+ pickSandboxToolPolicy(params.cfg.tools ?? undefined),
94
+ pickSandboxToolPolicy(params.agentTools),
95
+ ];
96
+ if (params.sandboxMode === "all") {
97
+ policies.push(resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? undefined));
98
+ }
99
+ return policies;
100
+ }
101
+ function normalizePluginIdSet(entries) {
102
+ return new Set(entries.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
103
+ }
104
+ function resolveEnabledExtensionPluginIds(params) {
105
+ const normalized = normalizePluginsConfig(params.cfg.plugins);
106
+ if (!normalized.enabled) {
107
+ return [];
108
+ }
109
+ const allowSet = normalizePluginIdSet(normalized.allow);
110
+ const denySet = normalizePluginIdSet(normalized.deny);
111
+ const entryById = new Map();
112
+ for (const [id, entry] of Object.entries(normalized.entries)) {
113
+ entryById.set(id.trim().toLowerCase(), entry);
114
+ }
115
+ const enabled = [];
116
+ for (const id of params.pluginDirs) {
117
+ const normalizedId = id.trim().toLowerCase();
118
+ if (!normalizedId) {
119
+ continue;
120
+ }
121
+ if (denySet.has(normalizedId)) {
122
+ continue;
123
+ }
124
+ if (allowSet.size > 0 && !allowSet.has(normalizedId)) {
125
+ continue;
126
+ }
127
+ if (entryById.get(normalizedId)?.enabled === false) {
128
+ continue;
129
+ }
130
+ enabled.push(normalizedId);
131
+ }
132
+ return enabled;
133
+ }
134
+ function collectAllowEntries(config) {
135
+ const out = [];
136
+ if (Array.isArray(config?.allow)) {
137
+ out.push(...config.allow);
138
+ }
139
+ if (Array.isArray(config?.alsoAllow)) {
140
+ out.push(...config.alsoAllow);
141
+ }
142
+ return out.map((entry) => entry.trim().toLowerCase()).filter(Boolean);
143
+ }
144
+ function hasExplicitPluginAllow(params) {
145
+ return params.allowEntries.some((entry) => entry === "group:plugins" || params.enabledPluginIds.has(entry));
146
+ }
147
+ function hasProviderPluginAllow(params) {
148
+ if (!params.byProvider) {
149
+ return false;
150
+ }
151
+ for (const policy of Object.values(params.byProvider)) {
152
+ if (hasExplicitPluginAllow({
153
+ allowEntries: collectAllowEntries(policy),
154
+ enabledPluginIds: params.enabledPluginIds,
155
+ })) {
156
+ return true;
157
+ }
158
+ }
159
+ return false;
160
+ }
161
+ function isPinnedRegistrySpec(spec) {
162
+ const value = spec.trim();
163
+ if (!value) {
164
+ return false;
165
+ }
166
+ const at = value.lastIndexOf("@");
167
+ if (at <= 0 || at >= value.length - 1) {
168
+ return false;
169
+ }
170
+ const version = value.slice(at + 1).trim();
171
+ return /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
172
+ }
173
+ async function readInstalledPackageVersion(dir) {
174
+ try {
175
+ const raw = await fs.readFile(path.join(dir, "package.json"), "utf-8");
176
+ const parsed = JSON.parse(raw);
177
+ return typeof parsed.version === "string" ? parsed.version : undefined;
178
+ }
179
+ catch {
180
+ return undefined;
181
+ }
182
+ }
183
+ // --------------------------------------------------------------------------
184
+ // Exported collectors
185
+ // --------------------------------------------------------------------------
186
+ function normalizeDockerLabelValue(raw) {
187
+ const trimmed = raw?.trim() ?? "";
188
+ if (!trimmed || trimmed === "<no value>") {
189
+ return null;
190
+ }
191
+ return trimmed;
192
+ }
193
+ async function listSandboxBrowserContainers(execDockerRawFn) {
194
+ try {
195
+ const result = await execDockerRawFn(["ps", "-a", "--filter", "label=poolbot.sandboxBrowser=1", "--format", "{{.Names}}"], { allowFailure: true });
196
+ if (result.code !== 0) {
197
+ return null;
198
+ }
199
+ return result.stdout
200
+ .toString("utf8")
201
+ .split(/\r?\n/)
202
+ .map((entry) => entry.trim())
203
+ .filter(Boolean);
204
+ }
205
+ catch {
206
+ return null;
207
+ }
208
+ }
209
+ async function readSandboxBrowserHashLabels(params) {
210
+ try {
211
+ const result = await params.execDockerRawFn([
212
+ "inspect",
213
+ "-f",
214
+ '{{ index .Config.Labels "poolbot.configHash" }}\t{{ index .Config.Labels "poolbot.browserConfigEpoch" }}',
215
+ params.containerName,
216
+ ], { allowFailure: true });
217
+ if (result.code !== 0) {
218
+ return null;
219
+ }
220
+ const [hashRaw, epochRaw] = result.stdout.toString("utf8").split("\t");
221
+ return {
222
+ configHash: normalizeDockerLabelValue(hashRaw),
223
+ epoch: normalizeDockerLabelValue(epochRaw),
224
+ };
225
+ }
226
+ catch {
227
+ return null;
228
+ }
229
+ }
230
+ function parsePublishedHostFromDockerPortLine(line) {
231
+ const trimmed = line.trim();
232
+ const rhs = trimmed.includes("->") ? (trimmed.split("->").at(-1)?.trim() ?? "") : trimmed;
233
+ if (!rhs) {
234
+ return null;
235
+ }
236
+ const bracketHost = rhs.match(/^\[([^\]]+)\]:\d+$/);
237
+ if (bracketHost?.[1]) {
238
+ return bracketHost[1];
239
+ }
240
+ const hostPort = rhs.match(/^([^:]+):\d+$/);
241
+ if (hostPort?.[1]) {
242
+ return hostPort[1];
243
+ }
244
+ return null;
245
+ }
246
+ function isLoopbackPublishHost(host) {
247
+ const normalized = host.trim().toLowerCase();
248
+ return normalized === "127.0.0.1" || normalized === "::1" || normalized === "localhost";
249
+ }
250
+ async function readSandboxBrowserPortMappings(params) {
251
+ try {
252
+ const result = await params.execDockerRawFn(["port", params.containerName], {
253
+ allowFailure: true,
254
+ });
255
+ if (result.code !== 0) {
256
+ return null;
257
+ }
258
+ return result.stdout
259
+ .toString("utf8")
260
+ .split(/\r?\n/)
261
+ .map((entry) => entry.trim())
262
+ .filter(Boolean);
263
+ }
264
+ catch {
265
+ return null;
266
+ }
267
+ }
268
+ export async function collectSandboxBrowserHashLabelFindings(params) {
269
+ const findings = [];
270
+ const execFn = params?.execDockerRawFn ?? execDockerRaw;
271
+ const containers = await listSandboxBrowserContainers(execFn);
272
+ if (!containers || containers.length === 0) {
177
273
  return findings;
178
274
  }
179
- const allow = params.cfg.plugins?.allow;
180
- const allowConfigured = Array.isArray(allow) && allow.length > 0;
181
- if (!allowConfigured) {
182
- const hasString = (value) => typeof value === "string" && value.trim().length > 0;
183
- const hasAccountStringKey = (account, key) => Boolean(account &&
184
- typeof account === "object" &&
185
- hasString(account[key]));
186
- const discordConfigured = hasString(params.cfg.channels?.discord?.token) ||
187
- Boolean(params.cfg.channels?.discord?.accounts &&
188
- Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token"))) ||
189
- hasString(process.env.DISCORD_BOT_TOKEN);
190
- const telegramConfigured = hasString(params.cfg.channels?.telegram?.botToken) ||
191
- hasString(params.cfg.channels?.telegram?.tokenFile) ||
192
- Boolean(params.cfg.channels?.telegram?.accounts &&
193
- Object.values(params.cfg.channels.telegram.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"))) ||
194
- hasString(process.env.TELEGRAM_BOT_TOKEN);
195
- const slackConfigured = hasString(params.cfg.channels?.slack?.botToken) ||
196
- hasString(params.cfg.channels?.slack?.appToken) ||
197
- Boolean(params.cfg.channels?.slack?.accounts &&
198
- Object.values(params.cfg.channels.slack.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"))) ||
199
- hasString(process.env.SLACK_BOT_TOKEN) ||
200
- hasString(process.env.SLACK_APP_TOKEN);
201
- const skillCommandsLikelyExposed = (discordConfigured &&
202
- resolveNativeSkillsEnabled({
203
- providerId: "discord",
204
- providerSetting: params.cfg.channels?.discord?.commands?.nativeSkills,
205
- globalSetting: params.cfg.commands?.nativeSkills,
206
- })) ||
207
- (telegramConfigured &&
275
+ const missingHash = [];
276
+ const staleEpoch = [];
277
+ const nonLoopbackPublished = [];
278
+ for (const containerName of containers) {
279
+ const labels = await readSandboxBrowserHashLabels({ containerName, execDockerRawFn: execFn });
280
+ if (!labels) {
281
+ continue;
282
+ }
283
+ if (!labels.configHash) {
284
+ missingHash.push(containerName);
285
+ }
286
+ if (labels.epoch !== SANDBOX_BROWSER_SECURITY_HASH_EPOCH) {
287
+ staleEpoch.push(containerName);
288
+ }
289
+ const portMappings = await readSandboxBrowserPortMappings({
290
+ containerName,
291
+ execDockerRawFn: execFn,
292
+ });
293
+ if (!portMappings?.length) {
294
+ continue;
295
+ }
296
+ const exposedMappings = portMappings.filter((line) => {
297
+ const host = parsePublishedHostFromDockerPortLine(line);
298
+ return Boolean(host && !isLoopbackPublishHost(host));
299
+ });
300
+ if (exposedMappings.length > 0) {
301
+ nonLoopbackPublished.push(`${containerName} (${exposedMappings.join("; ")})`);
302
+ }
303
+ }
304
+ if (missingHash.length > 0) {
305
+ findings.push({
306
+ checkId: "sandbox.browser_container.hash_label_missing",
307
+ severity: "warn",
308
+ title: "Sandbox browser container missing config hash label",
309
+ detail: `Containers: ${missingHash.join(", ")}. ` +
310
+ "These browser containers predate hash-based drift checks and may miss security remediations until recreated.",
311
+ remediation: `${formatCliCommand("poolbot sandbox recreate --browser --all")} (add --force to skip prompt).`,
312
+ });
313
+ }
314
+ if (staleEpoch.length > 0) {
315
+ findings.push({
316
+ checkId: "sandbox.browser_container.hash_epoch_stale",
317
+ severity: "warn",
318
+ title: "Sandbox browser container hash epoch is stale",
319
+ detail: `Containers: ${staleEpoch.join(", ")}. ` +
320
+ `Expected poolbot.browserConfigEpoch=${SANDBOX_BROWSER_SECURITY_HASH_EPOCH}.`,
321
+ remediation: `${formatCliCommand("poolbot sandbox recreate --browser --all")} (add --force to skip prompt).`,
322
+ });
323
+ }
324
+ if (nonLoopbackPublished.length > 0) {
325
+ findings.push({
326
+ checkId: "sandbox.browser_container.non_loopback_publish",
327
+ severity: "critical",
328
+ title: "Sandbox browser container publishes ports on non-loopback interfaces",
329
+ detail: `Containers: ${nonLoopbackPublished.join(", ")}. ` +
330
+ "Sandbox browser observer/control ports should stay loopback-only to avoid unintended remote access.",
331
+ remediation: `${formatCliCommand("poolbot sandbox recreate --browser --all")} (add --force to skip prompt), ` +
332
+ "then verify published ports are bound to 127.0.0.1.",
333
+ });
334
+ }
335
+ return findings;
336
+ }
337
+ export async function collectPluginsTrustFindings(params) {
338
+ const findings = [];
339
+ const { extensionsDir, pluginDirs } = await listInstalledPluginDirs({
340
+ stateDir: params.stateDir,
341
+ });
342
+ if (pluginDirs.length > 0) {
343
+ const allow = params.cfg.plugins?.allow;
344
+ const allowConfigured = Array.isArray(allow) && allow.length > 0;
345
+ if (!allowConfigured) {
346
+ const hasString = (value) => typeof value === "string" && value.trim().length > 0;
347
+ const hasAccountStringKey = (account, key) => Boolean(account &&
348
+ typeof account === "object" &&
349
+ hasString(account[key]));
350
+ const discordConfigured = hasString(params.cfg.channels?.discord?.token) ||
351
+ Boolean(params.cfg.channels?.discord?.accounts &&
352
+ Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token"))) ||
353
+ hasString(process.env.DISCORD_BOT_TOKEN);
354
+ const telegramConfigured = hasString(params.cfg.channels?.telegram?.botToken) ||
355
+ hasString(params.cfg.channels?.telegram?.tokenFile) ||
356
+ Boolean(params.cfg.channels?.telegram?.accounts &&
357
+ Object.values(params.cfg.channels.telegram.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"))) ||
358
+ hasString(process.env.TELEGRAM_BOT_TOKEN);
359
+ const slackConfigured = hasString(params.cfg.channels?.slack?.botToken) ||
360
+ hasString(params.cfg.channels?.slack?.appToken) ||
361
+ Boolean(params.cfg.channels?.slack?.accounts &&
362
+ Object.values(params.cfg.channels.slack.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"))) ||
363
+ hasString(process.env.SLACK_BOT_TOKEN) ||
364
+ hasString(process.env.SLACK_APP_TOKEN);
365
+ const skillCommandsLikelyExposed = (discordConfigured &&
208
366
  resolveNativeSkillsEnabled({
209
- providerId: "telegram",
210
- providerSetting: params.cfg.channels?.telegram?.commands?.nativeSkills,
367
+ providerId: "discord",
368
+ providerSetting: params.cfg.channels?.discord?.commands?.nativeSkills,
211
369
  globalSetting: params.cfg.commands?.nativeSkills,
212
370
  })) ||
213
- (slackConfigured &&
214
- resolveNativeSkillsEnabled({
215
- providerId: "slack",
216
- providerSetting: params.cfg.channels?.slack?.commands?.nativeSkills,
217
- globalSetting: params.cfg.commands?.nativeSkills,
218
- }));
219
- findings.push({
220
- checkId: "plugins.extensions_no_allowlist",
221
- severity: skillCommandsLikelyExposed ? "critical" : "warn",
222
- title: "Extensions exist but plugins.allow is not set",
223
- detail: `Found ${pluginDirs.length} extension(s) under ${extensionsDir}. Without plugins.allow, any discovered plugin id may load (depending on config and plugin behavior).` +
224
- (skillCommandsLikelyExposed
225
- ? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk."
226
- : ""),
227
- remediation: "Set plugins.allow to an explicit list of plugin ids you trust.",
371
+ (telegramConfigured &&
372
+ resolveNativeSkillsEnabled({
373
+ providerId: "telegram",
374
+ providerSetting: params.cfg.channels?.telegram?.commands?.nativeSkills,
375
+ globalSetting: params.cfg.commands?.nativeSkills,
376
+ })) ||
377
+ (slackConfigured &&
378
+ resolveNativeSkillsEnabled({
379
+ providerId: "slack",
380
+ providerSetting: params.cfg.channels?.slack?.commands?.nativeSkills,
381
+ globalSetting: params.cfg.commands?.nativeSkills,
382
+ }));
383
+ findings.push({
384
+ checkId: "plugins.extensions_no_allowlist",
385
+ severity: skillCommandsLikelyExposed ? "critical" : "warn",
386
+ title: "Extensions exist but plugins.allow is not set",
387
+ detail: `Found ${pluginDirs.length} extension(s) under ${extensionsDir}. Without plugins.allow, any discovered plugin id may load (depending on config and plugin behavior).` +
388
+ (skillCommandsLikelyExposed
389
+ ? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk."
390
+ : ""),
391
+ remediation: "Set plugins.allow to an explicit list of plugin ids you trust.",
392
+ });
393
+ }
394
+ const enabledExtensionPluginIds = resolveEnabledExtensionPluginIds({
395
+ cfg: params.cfg,
396
+ pluginDirs,
228
397
  });
398
+ if (enabledExtensionPluginIds.length > 0) {
399
+ const enabledPluginSet = new Set(enabledExtensionPluginIds);
400
+ const contexts = [{ label: "default" }];
401
+ for (const entry of params.cfg.agents?.list ?? []) {
402
+ if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
403
+ continue;
404
+ }
405
+ contexts.push({
406
+ label: `agents.list.${entry.id}`,
407
+ agentId: entry.id,
408
+ tools: entry.tools,
409
+ });
410
+ }
411
+ const permissiveContexts = [];
412
+ for (const context of contexts) {
413
+ const profile = context.tools?.profile ?? params.cfg.tools?.profile;
414
+ const restrictiveProfile = Boolean(resolveToolProfilePolicy(profile));
415
+ const sandboxMode = resolveSandboxConfigForAgent(params.cfg, context.agentId).mode;
416
+ const policies = resolveToolPolicies({
417
+ cfg: params.cfg,
418
+ agentTools: context.tools,
419
+ sandboxMode,
420
+ agentId: context.agentId,
421
+ });
422
+ const broadPolicy = isToolAllowedByPolicies("__poolbot_plugin_probe__", policies);
423
+ const explicitPluginAllow = !restrictiveProfile &&
424
+ (hasExplicitPluginAllow({
425
+ allowEntries: collectAllowEntries(params.cfg.tools),
426
+ enabledPluginIds: enabledPluginSet,
427
+ }) ||
428
+ hasProviderPluginAllow({
429
+ byProvider: params.cfg.tools?.byProvider,
430
+ enabledPluginIds: enabledPluginSet,
431
+ }) ||
432
+ hasExplicitPluginAllow({
433
+ allowEntries: collectAllowEntries(context.tools),
434
+ enabledPluginIds: enabledPluginSet,
435
+ }) ||
436
+ hasProviderPluginAllow({
437
+ byProvider: context.tools?.byProvider,
438
+ enabledPluginIds: enabledPluginSet,
439
+ }));
440
+ if (broadPolicy || explicitPluginAllow) {
441
+ permissiveContexts.push(context.label);
442
+ }
443
+ }
444
+ if (permissiveContexts.length > 0) {
445
+ findings.push({
446
+ checkId: "plugins.tools_reachable_permissive_policy",
447
+ severity: "warn",
448
+ title: "Extension plugin tools may be reachable under permissive tool policy",
449
+ detail: `Enabled extension plugins: ${enabledExtensionPluginIds.join(", ")}.\n` +
450
+ `Permissive tool policy contexts:\n${permissiveContexts.map((entry) => `- ${entry}`).join("\n")}`,
451
+ remediation: "Use restrictive profiles (`minimal`/`coding`) or explicit tool allowlists that exclude plugin tools for agents handling untrusted input.",
452
+ });
453
+ }
454
+ }
455
+ }
456
+ const pluginInstalls = params.cfg.plugins?.installs ?? {};
457
+ const npmPluginInstalls = Object.entries(pluginInstalls).filter(([, record]) => record?.source === "npm");
458
+ if (npmPluginInstalls.length > 0) {
459
+ const unpinned = npmPluginInstalls
460
+ .filter(([, record]) => typeof record.spec === "string" && !isPinnedRegistrySpec(record.spec))
461
+ .map(([pluginId, record]) => `${pluginId} (${record.spec})`);
462
+ if (unpinned.length > 0) {
463
+ findings.push({
464
+ checkId: "plugins.installs_unpinned_npm_specs",
465
+ severity: "warn",
466
+ title: "Plugin installs include unpinned npm specs",
467
+ detail: `Unpinned plugin install records:\n${unpinned.map((entry) => `- ${entry}`).join("\n")}`,
468
+ remediation: "Pin install specs to exact versions (for example, `@scope/pkg@1.2.3`) for higher supply-chain stability.",
469
+ });
470
+ }
471
+ const missingIntegrity = npmPluginInstalls
472
+ .filter(([, record]) => typeof record.integrity !== "string" || record.integrity.trim() === "")
473
+ .map(([pluginId]) => pluginId);
474
+ if (missingIntegrity.length > 0) {
475
+ findings.push({
476
+ checkId: "plugins.installs_missing_integrity",
477
+ severity: "warn",
478
+ title: "Plugin installs are missing integrity metadata",
479
+ detail: `Plugin install records missing integrity:\n${missingIntegrity.map((entry) => `- ${entry}`).join("\n")}`,
480
+ remediation: "Reinstall or update plugins to refresh install metadata with resolved integrity hashes.",
481
+ });
482
+ }
483
+ const pluginVersionDrift = [];
484
+ for (const [pluginId, record] of npmPluginInstalls) {
485
+ const recordedVersion = record.resolvedVersion ?? record.version;
486
+ if (!recordedVersion) {
487
+ continue;
488
+ }
489
+ const installPath = record.installPath ?? path.join(params.stateDir, "extensions", pluginId);
490
+ // eslint-disable-next-line no-await-in-loop
491
+ const installedVersion = await readInstalledPackageVersion(installPath);
492
+ if (!installedVersion || installedVersion === recordedVersion) {
493
+ continue;
494
+ }
495
+ pluginVersionDrift.push(`${pluginId} (recorded ${recordedVersion}, installed ${installedVersion})`);
496
+ }
497
+ if (pluginVersionDrift.length > 0) {
498
+ findings.push({
499
+ checkId: "plugins.installs_version_drift",
500
+ severity: "warn",
501
+ title: "Plugin install records drift from installed package versions",
502
+ detail: `Detected plugin install metadata drift:\n${pluginVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
503
+ remediation: "Run `poolbot plugins update --all` (or reinstall affected plugins) to refresh install metadata.",
504
+ });
505
+ }
506
+ }
507
+ const hookInstalls = params.cfg.hooks?.internal?.installs ?? {};
508
+ const npmHookInstalls = Object.entries(hookInstalls).filter(([, record]) => record?.source === "npm");
509
+ if (npmHookInstalls.length > 0) {
510
+ const unpinned = npmHookInstalls
511
+ .filter(([, record]) => typeof record.spec === "string" && !isPinnedRegistrySpec(record.spec))
512
+ .map(([hookId, record]) => `${hookId} (${record.spec})`);
513
+ if (unpinned.length > 0) {
514
+ findings.push({
515
+ checkId: "hooks.installs_unpinned_npm_specs",
516
+ severity: "warn",
517
+ title: "Hook installs include unpinned npm specs",
518
+ detail: `Unpinned hook install records:\n${unpinned.map((entry) => `- ${entry}`).join("\n")}`,
519
+ remediation: "Pin hook install specs to exact versions (for example, `@scope/pkg@1.2.3`) for higher supply-chain stability.",
520
+ });
521
+ }
522
+ const missingIntegrity = npmHookInstalls
523
+ .filter(([, record]) => typeof record.integrity !== "string" || record.integrity.trim() === "")
524
+ .map(([hookId]) => hookId);
525
+ if (missingIntegrity.length > 0) {
526
+ findings.push({
527
+ checkId: "hooks.installs_missing_integrity",
528
+ severity: "warn",
529
+ title: "Hook installs are missing integrity metadata",
530
+ detail: `Hook install records missing integrity:\n${missingIntegrity.map((entry) => `- ${entry}`).join("\n")}`,
531
+ remediation: "Reinstall or update hooks to refresh install metadata with resolved integrity hashes.",
532
+ });
533
+ }
534
+ const hookVersionDrift = [];
535
+ for (const [hookId, record] of npmHookInstalls) {
536
+ const recordedVersion = record.resolvedVersion ?? record.version;
537
+ if (!recordedVersion) {
538
+ continue;
539
+ }
540
+ const installPath = record.installPath ?? path.join(params.stateDir, "hooks", hookId);
541
+ // eslint-disable-next-line no-await-in-loop
542
+ const installedVersion = await readInstalledPackageVersion(installPath);
543
+ if (!installedVersion || installedVersion === recordedVersion) {
544
+ continue;
545
+ }
546
+ hookVersionDrift.push(`${hookId} (recorded ${recordedVersion}, installed ${installedVersion})`);
547
+ }
548
+ if (hookVersionDrift.length > 0) {
549
+ findings.push({
550
+ checkId: "hooks.installs_version_drift",
551
+ severity: "warn",
552
+ title: "Hook install records drift from installed package versions",
553
+ detail: `Detected hook install metadata drift:\n${hookVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
554
+ remediation: "Run `poolbot hooks update --all` (or reinstall affected hooks) to refresh install metadata.",
555
+ });
556
+ }
229
557
  }
230
558
  return findings;
231
559
  }
@@ -452,22 +780,18 @@ export async function readConfigSnapshotForAudit(params) {
452
780
  }
453
781
  export async function collectPluginsCodeSafetyFindings(params) {
454
782
  const findings = [];
455
- const extensionsDir = path.join(params.stateDir, "extensions");
456
- const st = await safeStat(extensionsDir);
457
- if (!st.ok || !st.isDir) {
458
- return findings;
459
- }
460
- const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch((err) => {
461
- findings.push({
462
- checkId: "plugins.code_safety.scan_failed",
463
- severity: "warn",
464
- title: "Plugin extensions directory scan failed",
465
- detail: `Static code scan could not list extensions directory: ${String(err)}`,
466
- remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
467
- });
468
- return [];
783
+ const { extensionsDir, pluginDirs } = await listInstalledPluginDirs({
784
+ stateDir: params.stateDir,
785
+ onReadError: (err) => {
786
+ findings.push({
787
+ checkId: "plugins.code_safety.scan_failed",
788
+ severity: "warn",
789
+ title: "Plugin extensions directory scan failed",
790
+ detail: `Static code scan could not list extensions directory: ${String(err)}`,
791
+ remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
792
+ });
793
+ },
469
794
  });
470
- const pluginDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
471
795
  for (const pluginName of pluginDirs) {
472
796
  const pluginPath = path.join(extensionsDir, pluginName);
473
797
  const extensionEntries = await readPluginManifestExtensions(pluginPath).catch(() => []);
@@ -499,9 +823,11 @@ export async function collectPluginsCodeSafetyFindings(params) {
499
823
  remediation: "Update the plugin manifest so all poolbot.extensions entries stay inside the plugin directory.",
500
824
  });
501
825
  }
502
- const summary = await scanDirectoryWithSummary(pluginPath, {
826
+ const summary = await skillScanner
827
+ .scanDirectoryWithSummary(pluginPath, {
503
828
  includeFiles: forcedScanEntries,
504
- }).catch((err) => {
829
+ })
830
+ .catch((err) => {
505
831
  findings.push({
506
832
  checkId: "plugins.code_safety.scan_failed",
507
833
  severity: "warn",
@@ -543,7 +869,7 @@ export async function collectInstalledSkillsCodeSafetyFindings(params) {
543
869
  const findings = [];
544
870
  const pluginExtensionsDir = path.join(params.stateDir, "extensions");
545
871
  const scannedSkillDirs = new Set();
546
- const workspaceDirs = listWorkspaceDirs(params.cfg);
872
+ const workspaceDirs = listAgentWorkspaceDirs(params.cfg);
547
873
  for (const workspaceDir of workspaceDirs) {
548
874
  const entries = loadWorkspaceSkillEntries(workspaceDir, { config: params.cfg });
549
875
  for (const entry of entries) {
@@ -560,7 +886,7 @@ export async function collectInstalledSkillsCodeSafetyFindings(params) {
560
886
  }
561
887
  scannedSkillDirs.add(skillDir);
562
888
  const skillName = entry.skill.name;
563
- const summary = await scanDirectoryWithSummary(skillDir).catch((err) => {
889
+ const summary = await skillScanner.scanDirectoryWithSummary(skillDir).catch((err) => {
564
890
  findings.push({
565
891
  checkId: "skills.code_safety.scan_failed",
566
892
  severity: "warn",