@poolzin/pool-bot 2026.2.21 → 2026.2.23

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 (378) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/device-pair/index.ts +2 -2
  326. package/extensions/diagnostics-otel/package.json +1 -1
  327. package/extensions/discord/package.json +1 -1
  328. package/extensions/feishu/package.json +1 -1
  329. package/extensions/google-antigravity-auth/package.json +1 -1
  330. package/extensions/google-gemini-cli-auth/package.json +1 -1
  331. package/extensions/googlechat/package.json +1 -1
  332. package/extensions/imessage/package.json +1 -1
  333. package/extensions/irc/package.json +1 -1
  334. package/extensions/irc/src/accounts.ts +1 -1
  335. package/extensions/irc/src/onboarding.ts +4 -4
  336. package/extensions/line/package.json +1 -1
  337. package/extensions/llm-task/package.json +1 -1
  338. package/extensions/lobster/package.json +1 -1
  339. package/extensions/matrix/CHANGELOG.md +10 -0
  340. package/extensions/matrix/package.json +1 -1
  341. package/extensions/mattermost/package.json +1 -1
  342. package/extensions/memory-core/package.json +1 -1
  343. package/extensions/memory-lancedb/package.json +1 -1
  344. package/extensions/minimax-portal-auth/package.json +1 -1
  345. package/extensions/msteams/CHANGELOG.md +10 -0
  346. package/extensions/msteams/package.json +1 -1
  347. package/extensions/nextcloud-talk/package.json +1 -1
  348. package/extensions/nostr/CHANGELOG.md +10 -0
  349. package/extensions/nostr/package.json +1 -1
  350. package/extensions/open-prose/package.json +1 -1
  351. package/extensions/openai-codex-auth/package.json +1 -1
  352. package/extensions/signal/package.json +1 -1
  353. package/extensions/slack/package.json +1 -1
  354. package/extensions/telegram/package.json +1 -1
  355. package/extensions/tlon/package.json +1 -1
  356. package/extensions/twitch/CHANGELOG.md +10 -0
  357. package/extensions/twitch/package.json +1 -1
  358. package/extensions/voice-call/CHANGELOG.md +10 -0
  359. package/extensions/voice-call/package.json +1 -1
  360. package/extensions/whatsapp/package.json +1 -1
  361. package/extensions/zalo/CHANGELOG.md +10 -0
  362. package/extensions/zalo/package.json +1 -1
  363. package/extensions/zalouser/CHANGELOG.md +10 -0
  364. package/extensions/zalouser/package.json +1 -1
  365. package/package.json +1 -1
  366. package/skills/apple-reminders/SKILL.md +100 -49
  367. package/skills/coding-agent/SKILL.md +34 -28
  368. package/skills/github/SKILL.md +131 -16
  369. package/skills/imsg/SKILL.md +112 -15
  370. package/skills/openhue/SKILL.md +101 -19
  371. package/skills/tmux/SKILL.md +111 -79
  372. package/skills/weather/SKILL.md +88 -25
  373. package/dist/agents/openclaw-tools.js +0 -151
  374. package/dist/agents/tool-security.js +0 -96
  375. package/dist/gateway/url-validation.js +0 -94
  376. package/dist/infra/openclaw-root.js +0 -109
  377. package/dist/infra/tmp-openclaw-dir.js +0 -81
  378. package/dist/media/path-sanitization.js +0 -78
@@ -1,28 +1,32 @@
1
1
  import fs from "node:fs/promises";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
- import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
5
- import { runCommandWithTimeout } from "../process/exec.js";
6
- import { CONFIG_DIR, resolveUserPath } from "../utils.js";
3
+ import { MANIFEST_KEY } from "../compat/legacy-names.js";
7
4
  import { extractArchive, fileExists, readJsonFile, resolveArchiveKind, resolvePackedRootDir, } from "../infra/archive.js";
5
+ import { installPackageDir } from "../infra/install-package-dir.js";
6
+ import { resolveSafeInstallDir, safeDirName, unscopedPackageName, } from "../infra/install-safe-path.js";
7
+ import { packNpmSpecToArchive, resolveArchiveSourcePath, withTempDir, } from "../infra/install-source-utils.js";
8
+ import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
9
+ import { extensionUsesSkippedScannerPath, isPathInside } from "../security/scan-paths.js";
10
+ import * as skillScanner from "../security/skill-scanner.js";
11
+ import { CONFIG_DIR, resolveUserPath } from "../utils.js";
8
12
  const defaultLogger = {};
9
- function unscopedPackageName(name) {
10
- const trimmed = name.trim();
11
- if (!trimmed)
12
- return trimmed;
13
- return trimmed.includes("/") ? (trimmed.split("/").pop() ?? trimmed) : trimmed;
14
- }
15
- function safeDirName(input) {
16
- const trimmed = input.trim();
17
- if (!trimmed)
18
- return trimmed;
19
- return trimmed.replaceAll("/", "__");
20
- }
21
13
  function safeFileName(input) {
22
14
  return safeDirName(input);
23
15
  }
16
+ function validatePluginId(pluginId) {
17
+ if (!pluginId) {
18
+ return "invalid plugin name: missing";
19
+ }
20
+ if (pluginId === "." || pluginId === "..") {
21
+ return "invalid plugin name: reserved path segment";
22
+ }
23
+ if (pluginId.includes("/") || pluginId.includes("\\")) {
24
+ return "invalid plugin name: path separators not allowed";
25
+ }
26
+ return null;
27
+ }
24
28
  async function ensurePoolbotExtensions(manifest) {
25
- const extensions = manifest.poolbot?.extensions ?? manifest[LEGACY_MANIFEST_KEY]?.extensions;
29
+ const extensions = manifest[MANIFEST_KEY]?.extensions;
26
30
  if (!Array.isArray(extensions)) {
27
31
  throw new Error("package.json missing poolbot.extensions");
28
32
  }
@@ -32,17 +36,49 @@ async function ensurePoolbotExtensions(manifest) {
32
36
  }
33
37
  return list;
34
38
  }
39
+ function resolvePluginInstallModeOptions(params) {
40
+ return {
41
+ logger: params.logger ?? defaultLogger,
42
+ mode: params.mode ?? "install",
43
+ dryRun: params.dryRun ?? false,
44
+ };
45
+ }
46
+ function resolveTimedPluginInstallModeOptions(params) {
47
+ return {
48
+ ...resolvePluginInstallModeOptions(params),
49
+ timeoutMs: params.timeoutMs ?? 120_000,
50
+ };
51
+ }
52
+ function buildFileInstallResult(pluginId, targetFile) {
53
+ return {
54
+ ok: true,
55
+ pluginId,
56
+ targetDir: targetFile,
57
+ manifestName: undefined,
58
+ version: undefined,
59
+ extensions: [path.basename(targetFile)],
60
+ };
61
+ }
35
62
  export function resolvePluginInstallDir(pluginId, extensionsDir) {
36
63
  const extensionsBase = extensionsDir
37
64
  ? resolveUserPath(extensionsDir)
38
65
  : path.join(CONFIG_DIR, "extensions");
39
- return path.join(extensionsBase, safeDirName(pluginId));
66
+ const pluginIdError = validatePluginId(pluginId);
67
+ if (pluginIdError) {
68
+ throw new Error(pluginIdError);
69
+ }
70
+ const targetDirResult = resolveSafeInstallDir({
71
+ baseDir: extensionsBase,
72
+ id: pluginId,
73
+ invalidNameMessage: "invalid plugin name: path traversal detected",
74
+ });
75
+ if (!targetDirResult.ok) {
76
+ throw new Error(targetDirResult.error);
77
+ }
78
+ return targetDirResult.path;
40
79
  }
41
80
  async function installPluginFromPackageDir(params) {
42
- const logger = params.logger ?? defaultLogger;
43
- const timeoutMs = params.timeoutMs ?? 120_000;
44
- const mode = params.mode ?? "install";
45
- const dryRun = params.dryRun ?? false;
81
+ const { logger, timeoutMs, mode, dryRun } = resolveTimedPluginInstallModeOptions(params);
46
82
  const manifestPath = path.join(params.packageDir, "package.json");
47
83
  if (!(await fileExists(manifestPath))) {
48
84
  return { ok: false, error: "extracted package missing package.json" };
@@ -63,17 +99,61 @@ async function installPluginFromPackageDir(params) {
63
99
  }
64
100
  const pkgName = typeof manifest.name === "string" ? manifest.name : "";
65
101
  const pluginId = pkgName ? unscopedPackageName(pkgName) : "plugin";
102
+ const pluginIdError = validatePluginId(pluginId);
103
+ if (pluginIdError) {
104
+ return { ok: false, error: pluginIdError };
105
+ }
66
106
  if (params.expectedPluginId && params.expectedPluginId !== pluginId) {
67
107
  return {
68
108
  ok: false,
69
109
  error: `plugin id mismatch: expected ${params.expectedPluginId}, got ${pluginId}`,
70
110
  };
71
111
  }
112
+ const packageDir = path.resolve(params.packageDir);
113
+ const forcedScanEntries = [];
114
+ for (const entry of extensions) {
115
+ const resolvedEntry = path.resolve(packageDir, entry);
116
+ if (!isPathInside(packageDir, resolvedEntry)) {
117
+ logger.warn?.(`extension entry escapes plugin directory and will not be scanned: ${entry}`);
118
+ continue;
119
+ }
120
+ if (extensionUsesSkippedScannerPath(entry)) {
121
+ logger.warn?.(`extension entry is in a hidden/node_modules path and will receive targeted scan coverage: ${entry}`);
122
+ }
123
+ forcedScanEntries.push(resolvedEntry);
124
+ }
125
+ // Scan plugin source for dangerous code patterns (warn-only; never blocks install)
126
+ try {
127
+ const scanSummary = await skillScanner.scanDirectoryWithSummary(params.packageDir, {
128
+ includeFiles: forcedScanEntries,
129
+ });
130
+ if (scanSummary.critical > 0) {
131
+ const criticalDetails = scanSummary.findings
132
+ .filter((f) => f.severity === "critical")
133
+ .map((f) => `${f.message} (${f.file}:${f.line})`)
134
+ .join("; ");
135
+ logger.warn?.(`WARNING: Plugin "${pluginId}" contains dangerous code patterns: ${criticalDetails}`);
136
+ }
137
+ else if (scanSummary.warn > 0) {
138
+ logger.warn?.(`Plugin "${pluginId}" has ${scanSummary.warn} suspicious code pattern(s). Run "poolbot security audit --deep" for details.`);
139
+ }
140
+ }
141
+ catch (err) {
142
+ logger.warn?.(`Plugin "${pluginId}" code safety scan failed (${String(err)}). Installation continues; run "poolbot security audit --deep" after install.`);
143
+ }
72
144
  const extensionsDir = params.extensionsDir
73
145
  ? resolveUserPath(params.extensionsDir)
74
146
  : path.join(CONFIG_DIR, "extensions");
75
147
  await fs.mkdir(extensionsDir, { recursive: true });
76
- const targetDir = path.join(extensionsDir, safeDirName(pluginId));
148
+ const targetDirResult = resolveSafeInstallDir({
149
+ baseDir: extensionsDir,
150
+ id: pluginId,
151
+ invalidNameMessage: "invalid plugin name: path traversal detected",
152
+ });
153
+ if (!targetDirResult.ok) {
154
+ return { ok: false, error: targetDirResult.error };
155
+ }
156
+ const targetDir = targetDirResult.path;
77
157
  if (mode === "install" && (await fileExists(targetDir))) {
78
158
  return {
79
159
  ok: false,
@@ -90,49 +170,32 @@ async function installPluginFromPackageDir(params) {
90
170
  extensions,
91
171
  };
92
172
  }
93
- logger.info?.(`Installing to ${targetDir}…`);
94
- let backupDir = null;
95
- if (mode === "update" && (await fileExists(targetDir))) {
96
- backupDir = `${targetDir}.backup-${Date.now()}`;
97
- await fs.rename(targetDir, backupDir);
98
- }
99
- try {
100
- await fs.cp(params.packageDir, targetDir, { recursive: true });
101
- }
102
- catch (err) {
103
- if (backupDir) {
104
- await fs.rm(targetDir, { recursive: true, force: true }).catch(() => undefined);
105
- await fs.rename(backupDir, targetDir).catch(() => undefined);
106
- }
107
- return { ok: false, error: `failed to copy plugin: ${String(err)}` };
108
- }
109
- for (const entry of extensions) {
110
- const resolvedEntry = path.resolve(targetDir, entry);
111
- if (!(await fileExists(resolvedEntry))) {
112
- logger.warn?.(`extension entry not found: ${entry}`);
113
- }
114
- }
115
173
  const deps = manifest.dependencies ?? {};
116
174
  const hasDeps = Object.keys(deps).length > 0;
117
- if (hasDeps) {
118
- logger.info?.("Installing plugin dependencies…");
119
- const npmRes = await runCommandWithTimeout(["npm", "install", "--omit=dev", "--silent"], {
120
- timeoutMs: Math.max(timeoutMs, 300_000),
121
- cwd: targetDir,
122
- });
123
- if (npmRes.code !== 0) {
124
- if (backupDir) {
125
- await fs.rm(targetDir, { recursive: true, force: true }).catch(() => undefined);
126
- await fs.rename(backupDir, targetDir).catch(() => undefined);
175
+ const installRes = await installPackageDir({
176
+ sourceDir: params.packageDir,
177
+ targetDir,
178
+ mode,
179
+ timeoutMs,
180
+ logger,
181
+ copyErrorPrefix: "failed to copy plugin",
182
+ hasDeps,
183
+ depsLogMessage: "Installing plugin dependencies…",
184
+ afterCopy: async () => {
185
+ for (const entry of extensions) {
186
+ const resolvedEntry = path.resolve(targetDir, entry);
187
+ if (!isPathInside(targetDir, resolvedEntry)) {
188
+ logger.warn?.(`extension entry escapes plugin directory: ${entry}`);
189
+ continue;
190
+ }
191
+ if (!(await fileExists(resolvedEntry))) {
192
+ logger.warn?.(`extension entry not found: ${entry}`);
193
+ }
127
194
  }
128
- return {
129
- ok: false,
130
- error: `npm install failed: ${npmRes.stderr.trim() || npmRes.stdout.trim()}`,
131
- };
132
- }
133
- }
134
- if (backupDir) {
135
- await fs.rm(backupDir, { recursive: true, force: true }).catch(() => undefined);
195
+ },
196
+ });
197
+ if (!installRes.ok) {
198
+ return installRes;
136
199
  }
137
200
  return {
138
201
  ok: true,
@@ -147,43 +210,42 @@ export async function installPluginFromArchive(params) {
147
210
  const logger = params.logger ?? defaultLogger;
148
211
  const timeoutMs = params.timeoutMs ?? 120_000;
149
212
  const mode = params.mode ?? "install";
150
- const archivePath = resolveUserPath(params.archivePath);
151
- if (!(await fileExists(archivePath))) {
152
- return { ok: false, error: `archive not found: ${archivePath}` };
153
- }
154
- if (!resolveArchiveKind(archivePath)) {
155
- return { ok: false, error: `unsupported archive: ${archivePath}` };
156
- }
157
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "poolbot-plugin-"));
158
- const extractDir = path.join(tmpDir, "extract");
159
- await fs.mkdir(extractDir, { recursive: true });
160
- logger.info?.(`Extracting ${archivePath}…`);
161
- try {
162
- await extractArchive({
163
- archivePath,
164
- destDir: extractDir,
213
+ const archivePathResult = await resolveArchiveSourcePath(params.archivePath);
214
+ if (!archivePathResult.ok) {
215
+ return archivePathResult;
216
+ }
217
+ const archivePath = archivePathResult.path;
218
+ return await withTempDir("poolbot-plugin-", async (tmpDir) => {
219
+ const extractDir = path.join(tmpDir, "extract");
220
+ await fs.mkdir(extractDir, { recursive: true });
221
+ logger.info?.(`Extracting ${archivePath}…`);
222
+ try {
223
+ await extractArchive({
224
+ archivePath,
225
+ destDir: extractDir,
226
+ timeoutMs,
227
+ logger,
228
+ });
229
+ }
230
+ catch (err) {
231
+ return { ok: false, error: `failed to extract archive: ${String(err)}` };
232
+ }
233
+ let packageDir = "";
234
+ try {
235
+ packageDir = await resolvePackedRootDir(extractDir);
236
+ }
237
+ catch (err) {
238
+ return { ok: false, error: String(err) };
239
+ }
240
+ return await installPluginFromPackageDir({
241
+ packageDir,
242
+ extensionsDir: params.extensionsDir,
165
243
  timeoutMs,
166
244
  logger,
245
+ mode,
246
+ dryRun: params.dryRun,
247
+ expectedPluginId: params.expectedPluginId,
167
248
  });
168
- }
169
- catch (err) {
170
- return { ok: false, error: `failed to extract archive: ${String(err)}` };
171
- }
172
- let packageDir = "";
173
- try {
174
- packageDir = await resolvePackedRootDir(extractDir);
175
- }
176
- catch (err) {
177
- return { ok: false, error: String(err) };
178
- }
179
- return await installPluginFromPackageDir({
180
- packageDir,
181
- extensionsDir: params.extensionsDir,
182
- timeoutMs,
183
- logger,
184
- mode,
185
- dryRun: params.dryRun,
186
- expectedPluginId: params.expectedPluginId,
187
249
  });
188
250
  }
189
251
  export async function installPluginFromDir(params) {
@@ -206,9 +268,7 @@ export async function installPluginFromDir(params) {
206
268
  });
207
269
  }
208
270
  export async function installPluginFromFile(params) {
209
- const logger = params.logger ?? defaultLogger;
210
- const mode = params.mode ?? "install";
211
- const dryRun = params.dryRun ?? false;
271
+ const { logger, mode, dryRun } = resolvePluginInstallModeOptions(params);
212
272
  const filePath = resolveUserPath(params.filePath);
213
273
  if (!(await fileExists(filePath))) {
214
274
  return { ok: false, error: `file not found: ${filePath}` };
@@ -219,70 +279,48 @@ export async function installPluginFromFile(params) {
219
279
  await fs.mkdir(extensionsDir, { recursive: true });
220
280
  const base = path.basename(filePath, path.extname(filePath));
221
281
  const pluginId = base || "plugin";
282
+ const pluginIdError = validatePluginId(pluginId);
283
+ if (pluginIdError) {
284
+ return { ok: false, error: pluginIdError };
285
+ }
222
286
  const targetFile = path.join(extensionsDir, `${safeFileName(pluginId)}${path.extname(filePath)}`);
223
287
  if (mode === "install" && (await fileExists(targetFile))) {
224
288
  return { ok: false, error: `plugin already exists: ${targetFile} (delete it first)` };
225
289
  }
226
290
  if (dryRun) {
227
- return {
228
- ok: true,
229
- pluginId,
230
- targetDir: targetFile,
231
- manifestName: undefined,
232
- version: undefined,
233
- extensions: [path.basename(targetFile)],
234
- };
291
+ return buildFileInstallResult(pluginId, targetFile);
235
292
  }
236
293
  logger.info?.(`Installing to ${targetFile}…`);
237
294
  await fs.copyFile(filePath, targetFile);
238
- return {
239
- ok: true,
240
- pluginId,
241
- targetDir: targetFile,
242
- manifestName: undefined,
243
- version: undefined,
244
- extensions: [path.basename(targetFile)],
245
- };
295
+ return buildFileInstallResult(pluginId, targetFile);
246
296
  }
247
297
  export async function installPluginFromNpmSpec(params) {
248
- const logger = params.logger ?? defaultLogger;
249
- const timeoutMs = params.timeoutMs ?? 120_000;
250
- const mode = params.mode ?? "install";
251
- const dryRun = params.dryRun ?? false;
298
+ const { logger, timeoutMs, mode, dryRun } = resolveTimedPluginInstallModeOptions(params);
252
299
  const expectedPluginId = params.expectedPluginId;
253
300
  const spec = params.spec.trim();
254
- if (!spec)
255
- return { ok: false, error: "missing npm spec" };
256
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "poolbot-npm-pack-"));
257
- logger.info?.(`Downloading ${spec}…`);
258
- const res = await runCommandWithTimeout(["npm", "pack", spec], {
259
- timeoutMs: Math.max(timeoutMs, 300_000),
260
- cwd: tmpDir,
261
- env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
262
- });
263
- if (res.code !== 0) {
264
- return {
265
- ok: false,
266
- error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}`,
267
- };
268
- }
269
- const packed = (res.stdout || "")
270
- .split("\n")
271
- .map((l) => l.trim())
272
- .filter(Boolean)
273
- .pop();
274
- if (!packed) {
275
- return { ok: false, error: "npm pack produced no archive" };
276
- }
277
- const archivePath = path.join(tmpDir, packed);
278
- return await installPluginFromArchive({
279
- archivePath,
280
- extensionsDir: params.extensionsDir,
281
- timeoutMs,
282
- logger,
283
- mode,
284
- dryRun,
285
- expectedPluginId,
301
+ const specError = validateRegistryNpmSpec(spec);
302
+ if (specError) {
303
+ return { ok: false, error: specError };
304
+ }
305
+ return await withTempDir("poolbot-npm-pack-", async (tmpDir) => {
306
+ logger.info?.(`Downloading ${spec}…`);
307
+ const packedResult = await packNpmSpecToArchive({
308
+ spec,
309
+ timeoutMs,
310
+ cwd: tmpDir,
311
+ });
312
+ if (!packedResult.ok) {
313
+ return packedResult;
314
+ }
315
+ return await installPluginFromArchive({
316
+ archivePath: packedResult.archivePath,
317
+ extensionsDir: params.extensionsDir,
318
+ timeoutMs,
319
+ logger,
320
+ mode,
321
+ dryRun,
322
+ expectedPluginId,
323
+ });
286
324
  });
287
325
  }
288
326
  export async function installPluginFromPath(params) {
package/dist/polls.js CHANGED
@@ -21,6 +21,13 @@ export function normalizePollInput(input, options = {}) {
21
21
  if (maxSelections > cleaned.length) {
22
22
  throw new Error("maxSelections cannot exceed option count");
23
23
  }
24
+ const durationSecondsRaw = input.durationSeconds;
25
+ const durationSeconds = typeof durationSecondsRaw === "number" && Number.isFinite(durationSecondsRaw)
26
+ ? Math.floor(durationSecondsRaw)
27
+ : undefined;
28
+ if (durationSeconds !== undefined && durationSeconds < 1) {
29
+ throw new Error("durationSeconds must be at least 1");
30
+ }
24
31
  const durationRaw = input.durationHours;
25
32
  const durationHours = typeof durationRaw === "number" && Number.isFinite(durationRaw)
26
33
  ? Math.floor(durationRaw)
@@ -28,10 +35,14 @@ export function normalizePollInput(input, options = {}) {
28
35
  if (durationHours !== undefined && durationHours < 1) {
29
36
  throw new Error("durationHours must be at least 1");
30
37
  }
38
+ if (durationSeconds !== undefined && durationHours !== undefined) {
39
+ throw new Error("durationSeconds and durationHours are mutually exclusive");
40
+ }
31
41
  return {
32
42
  question,
33
43
  options: cleaned,
34
44
  maxSelections,
45
+ durationSeconds,
35
46
  durationHours,
36
47
  };
37
48
  }