@poolzin/pool-bot 2026.2.20 → 2026.2.22

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 (388) 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-auth.js +12 -0
  14. package/dist/agents/model-catalog.js +40 -9
  15. package/dist/agents/model-fallback.js +24 -0
  16. package/dist/agents/model-forward-compat.js +60 -23
  17. package/dist/agents/model-selection.js +134 -41
  18. package/dist/agents/pi-auth-json.js +2 -2
  19. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  20. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  21. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  22. package/dist/agents/pi-embedded-helpers.js +2 -2
  23. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  24. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  25. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  26. package/dist/agents/pi-embedded-runner/google.js +109 -19
  27. package/dist/agents/pi-embedded-runner/history.js +35 -17
  28. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -80
  29. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  30. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  31. package/dist/agents/pi-embedded-runner/run.js +193 -25
  32. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  33. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  34. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  35. package/dist/agents/pi-embedded-runner.js +1 -1
  36. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  37. package/dist/agents/pi-embedded-subscribe.js +37 -0
  38. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  39. package/dist/agents/pi-model-discovery.js +9 -2
  40. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  41. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  42. package/dist/agents/pi-tools.js +113 -94
  43. package/dist/agents/pi-tools.read.js +337 -38
  44. package/dist/agents/poolbot-tools.js +14 -5
  45. package/dist/agents/provider/config-loader.js +76 -0
  46. package/dist/agents/provider/index.js +15 -0
  47. package/dist/agents/provider/integration.js +136 -0
  48. package/dist/agents/provider/models-dev.js +129 -0
  49. package/dist/agents/provider/rate-limits.js +458 -0
  50. package/dist/agents/provider/request-monitor.js +449 -0
  51. package/dist/agents/provider/session-binding.js +376 -0
  52. package/dist/agents/provider/token-pool.js +541 -0
  53. package/dist/agents/sandbox/docker.js +10 -5
  54. package/dist/agents/sandbox/registry.js +96 -46
  55. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  56. package/dist/agents/sandbox-paths.js +43 -10
  57. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  58. package/dist/agents/session-tool-result-guard.js +39 -39
  59. package/dist/agents/session-transcript-repair.js +36 -33
  60. package/dist/agents/session-write-lock.js +62 -44
  61. package/dist/agents/skills/frontmatter.js +49 -88
  62. package/dist/agents/skills/workspace.js +335 -28
  63. package/dist/agents/subagent-announce.js +508 -174
  64. package/dist/agents/subagent-registry.js +45 -4
  65. package/dist/agents/subagent-spawn.js +16 -33
  66. package/dist/agents/system-prompt-report.js +27 -10
  67. package/dist/agents/system-prompt.js +26 -32
  68. package/dist/agents/tool-call-id.js +69 -17
  69. package/dist/agents/tool-display-common.js +1 -1
  70. package/dist/agents/tool-images.js +64 -31
  71. package/dist/agents/tools/canvas-tool.js +17 -11
  72. package/dist/agents/tools/common.js +37 -19
  73. package/dist/agents/tools/cron-tool.js +40 -38
  74. package/dist/agents/tools/gateway.js +70 -2
  75. package/dist/agents/tools/message-tool.js +181 -40
  76. package/dist/agents/tools/nodes-tool.js +128 -36
  77. package/dist/agents/tools/nodes-utils.js +12 -38
  78. package/dist/agents/tools/session-status-tool.js +24 -71
  79. package/dist/agents/tools/sessions-helpers.js +38 -210
  80. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  81. package/dist/agents/tools/telegram-actions.js +58 -7
  82. package/dist/agents/tools/web-fetch-utils.js +112 -7
  83. package/dist/agents/tools/web-fetch.js +279 -175
  84. package/dist/agents/tools/web-shared.js +71 -8
  85. package/dist/agents/usage.js +25 -16
  86. package/dist/auto-reply/commands-registry.data.js +85 -11
  87. package/dist/auto-reply/dispatch.js +40 -21
  88. package/dist/auto-reply/reply/abort.js +102 -33
  89. package/dist/auto-reply/reply/commands-core.js +82 -33
  90. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  91. package/dist/auto-reply/reply/commands-info.js +41 -12
  92. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  93. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  94. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  95. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  96. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  97. package/dist/auto-reply/reply/mentions.js +18 -11
  98. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  99. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  100. package/dist/auto-reply/reply/session.js +102 -21
  101. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  102. package/dist/auto-reply/status.js +73 -50
  103. package/dist/browser/extension-relay.js +3 -3
  104. package/dist/browser/http-auth.js +1 -1
  105. package/dist/browser/paths.js +2 -2
  106. package/dist/build-info.json +3 -3
  107. package/dist/channels/allowlist-match.js +20 -0
  108. package/dist/channels/allowlists/resolve-utils.js +65 -2
  109. package/dist/channels/chat-type.js +8 -4
  110. package/dist/channels/dock.js +127 -35
  111. package/dist/channels/draft-stream-loop.js +6 -2
  112. package/dist/channels/plugins/actions/telegram.js +42 -18
  113. package/dist/channels/plugins/allowlist-match.js +1 -1
  114. package/dist/channels/plugins/group-mentions.js +51 -41
  115. package/dist/channels/plugins/message-action-names.js +2 -0
  116. package/dist/channels/plugins/message-actions.js +24 -5
  117. package/dist/channels/plugins/normalize/discord.js +26 -4
  118. package/dist/channels/plugins/normalize/signal.js +35 -22
  119. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  120. package/dist/channels/plugins/outbound/imessage.js +15 -14
  121. package/dist/channels/registry.js +20 -7
  122. package/dist/cli/acp-cli.js +7 -5
  123. package/dist/cli/browser-cli-extension.js +25 -12
  124. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  125. package/dist/cli/browser-cli-state.js +101 -145
  126. package/dist/cli/command-options.js +28 -0
  127. package/dist/cli/completion-cli.js +6 -6
  128. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  129. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  130. package/dist/cli/cron-cli/shared.js +7 -1
  131. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  132. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  133. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  134. package/dist/cli/daemon-cli.js +1 -0
  135. package/dist/cli/devices-cli.js +33 -20
  136. package/dist/cli/gateway-cli/register.js +37 -105
  137. package/dist/cli/gateway-cli/run.js +49 -11
  138. package/dist/cli/nodes-camera.js +59 -4
  139. package/dist/cli/nodes-cli/register.camera.js +27 -24
  140. package/dist/cli/nodes-cli/rpc.js +21 -38
  141. package/dist/cli/qr-cli.js +2 -2
  142. package/dist/cli/skills-cli.format.js +2 -2
  143. package/dist/cli/update-cli/progress.js +2 -2
  144. package/dist/cli/update-cli/restart-helper.js +28 -7
  145. package/dist/cli/update-cli/shared.js +7 -7
  146. package/dist/cli/update-cli/status.js +1 -1
  147. package/dist/cli/update-cli/update-command.js +14 -8
  148. package/dist/cli/update-cli/wizard.js +2 -2
  149. package/dist/cli/update-cli.js +21 -1027
  150. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  151. package/dist/commands/channels/add-mutators.js +3 -35
  152. package/dist/commands/channels/add.js +39 -51
  153. package/dist/commands/config-validation.js +1 -1
  154. package/dist/commands/configure.gateway-auth.js +52 -15
  155. package/dist/commands/configure.gateway.js +84 -40
  156. package/dist/commands/doctor-completion.js +3 -3
  157. package/dist/commands/doctor-config-flow.js +536 -16
  158. package/dist/commands/doctor-gateway-services.js +103 -79
  159. package/dist/commands/doctor-memory-search.js +9 -9
  160. package/dist/commands/doctor-platform-notes.js +57 -30
  161. package/dist/commands/doctor-prompter.js +26 -15
  162. package/dist/commands/doctor-session-locks.js +1 -1
  163. package/dist/commands/doctor.js +21 -9
  164. package/dist/commands/model-picker.js +120 -95
  165. package/dist/commands/models/set.js +2 -21
  166. package/dist/commands/models/shared.js +65 -37
  167. package/dist/commands/onboard-helpers.js +81 -39
  168. package/dist/commands/openai-codex-oauth.js +1 -1
  169. package/dist/commands/sessions.js +52 -53
  170. package/dist/commands/status.summary.js +52 -34
  171. package/dist/commands/test-wizard-helpers.js +2 -2
  172. package/dist/config/defaults.js +79 -42
  173. package/dist/config/group-policy.js +50 -18
  174. package/dist/config/includes.js +37 -10
  175. package/dist/config/schema.help.js +5 -4
  176. package/dist/config/schema.hints.js +2 -2
  177. package/dist/config/schema.labels.js +1 -0
  178. package/dist/config/sessions/group.js +12 -11
  179. package/dist/config/sessions/paths.js +137 -11
  180. package/dist/config/sessions/store.js +185 -65
  181. package/dist/config/sessions/types.js +15 -1
  182. package/dist/config/sessions.js +1 -0
  183. package/dist/config/telegram-custom-commands.js +3 -2
  184. package/dist/config/types.js +2 -0
  185. package/dist/config/zod-schema.agent-defaults.js +6 -27
  186. package/dist/config/zod-schema.agent-runtime.js +171 -79
  187. package/dist/config/zod-schema.providers-core.js +138 -65
  188. package/dist/config/zod-schema.session.js +49 -22
  189. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  190. package/dist/cron/isolated-agent/run.js +224 -57
  191. package/dist/cron/normalize.js +48 -45
  192. package/dist/cron/run-log.js +14 -0
  193. package/dist/cron/service/jobs.js +190 -28
  194. package/dist/cron/service/normalize.js +29 -11
  195. package/dist/cron/service/store.js +30 -44
  196. package/dist/cron/service/timer.js +182 -96
  197. package/dist/cron/service.js +3 -0
  198. package/dist/cron/stagger.js +37 -0
  199. package/dist/daemon/inspect.js +132 -92
  200. package/dist/daemon/runtime-paths.js +25 -4
  201. package/dist/daemon/service-audit.js +47 -16
  202. package/dist/discord/accounts.js +23 -20
  203. package/dist/discord/monitor/agent-components.js +1115 -219
  204. package/dist/discord/monitor/allow-list.js +114 -34
  205. package/dist/discord/monitor/listeners.js +204 -97
  206. package/dist/discord/monitor/message-handler.js +21 -10
  207. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  208. package/dist/discord/monitor/message-handler.process.js +384 -123
  209. package/dist/discord/monitor/message-utils.js +86 -23
  210. package/dist/discord/monitor/native-command.js +77 -57
  211. package/dist/discord/monitor/provider.js +122 -117
  212. package/dist/discord/monitor/reply-context.js +20 -16
  213. package/dist/discord/monitor/reply-delivery.js +40 -8
  214. package/dist/discord/monitor/rest-fetch.js +22 -0
  215. package/dist/discord/monitor/threading.js +117 -24
  216. package/dist/discord/send.js +2 -1
  217. package/dist/discord/send.outbound.js +124 -11
  218. package/dist/discord/send.shared.js +112 -72
  219. package/dist/discord/voice-message.js +3 -3
  220. package/dist/gateway/auth.js +119 -44
  221. package/dist/gateway/call.js +76 -34
  222. package/dist/gateway/channel-health-monitor.js +57 -50
  223. package/dist/gateway/client.js +63 -29
  224. package/dist/gateway/control-ui-contract.js +1 -1
  225. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  226. package/dist/gateway/net.js +109 -1
  227. package/dist/gateway/protocol/index.js +5 -8
  228. package/dist/gateway/protocol/schema/agent.js +19 -1
  229. package/dist/gateway/protocol/schema/channels.js +21 -0
  230. package/dist/gateway/protocol/schema/cron.js +43 -30
  231. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  232. package/dist/gateway/protocol/schema/sessions.js +5 -1
  233. package/dist/gateway/protocol/schema.js +0 -1
  234. package/dist/gateway/server/presence-events.js +12 -0
  235. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  236. package/dist/gateway/server/ws-connection.js +58 -21
  237. package/dist/gateway/server-broadcast.js +18 -13
  238. package/dist/gateway/server-cron.js +177 -10
  239. package/dist/gateway/server-methods/agent-job.js +131 -38
  240. package/dist/gateway/server-methods/send.js +60 -14
  241. package/dist/gateway/server-methods/sessions.js +160 -96
  242. package/dist/gateway/server-methods/system.js +5 -7
  243. package/dist/gateway/server-methods-list.js +8 -0
  244. package/dist/gateway/server-methods.js +24 -8
  245. package/dist/gateway/server-node-events.js +278 -68
  246. package/dist/gateway/session-utils.fs.js +316 -75
  247. package/dist/gateway/session-utils.js +224 -70
  248. package/dist/gateway/sessions-patch.js +63 -20
  249. package/dist/gateway/test-temp-config.js +1 -1
  250. package/dist/gateway/tools-invoke-http.js +118 -70
  251. package/dist/gateway/ws-log.js +135 -107
  252. package/dist/hooks/frontmatter.js +36 -82
  253. package/dist/hooks/install.js +149 -139
  254. package/dist/hooks/internal-hooks.js +29 -4
  255. package/dist/hooks/plugin-hooks.js +2 -1
  256. package/dist/imessage/monitor/deliver.js +10 -4
  257. package/dist/imessage/monitor/monitor-provider.js +138 -375
  258. package/dist/imessage/monitor/runtime.js +4 -8
  259. package/dist/imessage/send.js +65 -19
  260. package/dist/infra/exec-approvals-allowlist.js +7 -0
  261. package/dist/infra/exec-approvals.js +35 -920
  262. package/dist/infra/exec-safe-bin-trust.js +64 -0
  263. package/dist/infra/heartbeat-runner.js +207 -134
  264. package/dist/infra/heartbeat-wake.js +183 -22
  265. package/dist/infra/install-source-utils.js +47 -0
  266. package/dist/infra/net/ssrf.js +170 -36
  267. package/dist/infra/outbound/deliver.js +224 -58
  268. package/dist/infra/outbound/message-action-spec.js +12 -5
  269. package/dist/infra/outbound/outbound-session.js +27 -25
  270. package/dist/infra/poolbot-root.js +32 -22
  271. package/dist/infra/ports.js +14 -11
  272. package/dist/infra/skills-remote.js +48 -37
  273. package/dist/infra/system-events.js +25 -11
  274. package/dist/infra/system-presence.js +26 -33
  275. package/dist/infra/tmp-poolbot-dir.js +81 -2
  276. package/dist/infra/wsl.js +37 -1
  277. package/dist/line/bot-message-context.js +163 -191
  278. package/dist/logging/subsystem.js +59 -22
  279. package/dist/markdown/ir.js +124 -50
  280. package/dist/media/store.js +1 -1
  281. package/dist/media-understanding/runner.entries.js +42 -25
  282. package/dist/media-understanding/runner.js +53 -488
  283. package/dist/memory/embeddings-gemini.js +53 -38
  284. package/dist/memory/manager-embedding-ops.js +48 -69
  285. package/dist/pairing/pairing-store.js +178 -119
  286. package/dist/plugin-sdk/index.js +34 -6
  287. package/dist/plugins/hooks.js +135 -14
  288. package/dist/plugins/install.js +190 -152
  289. package/dist/polls.js +11 -0
  290. package/dist/routing/resolve-route.js +190 -56
  291. package/dist/routing/session-key.js +38 -22
  292. package/dist/runtime.js +35 -9
  293. package/dist/security/audit-channel.js +1 -1
  294. package/dist/sessions/session-key-utils.js +29 -11
  295. package/dist/shared/frontmatter.js +5 -5
  296. package/dist/shared/node-list-types.js +1 -0
  297. package/dist/shared/string-normalization.js +15 -0
  298. package/dist/signal/monitor/event-handler.js +68 -36
  299. package/dist/signal/send.js +29 -37
  300. package/dist/slack/monitor/allow-list.js +10 -11
  301. package/dist/slack/monitor/commands.js +14 -3
  302. package/dist/slack/monitor/events/interactions.js +4 -4
  303. package/dist/slack/monitor/media.js +224 -16
  304. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  305. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  306. package/dist/slack/monitor/slash.js +357 -144
  307. package/dist/slack/streaming.js +77 -0
  308. package/dist/telegram/accounts.js +40 -13
  309. package/dist/telegram/allowed-updates.js +3 -0
  310. package/dist/telegram/bot/delivery.js +129 -66
  311. package/dist/telegram/bot/helpers.js +136 -122
  312. package/dist/telegram/bot-handlers.js +600 -339
  313. package/dist/telegram/bot-message-context.js +115 -73
  314. package/dist/telegram/bot-message-dispatch.js +235 -104
  315. package/dist/telegram/bot-native-command-menu.js +3 -1
  316. package/dist/telegram/bot-native-commands.js +213 -193
  317. package/dist/telegram/bot.js +24 -132
  318. package/dist/telegram/draft-stream.js +84 -75
  319. package/dist/telegram/format.js +150 -6
  320. package/dist/telegram/send.js +415 -255
  321. package/dist/telegram/targets.js +21 -2
  322. package/dist/telegram/update-offset-store.js +19 -3
  323. package/dist/terminal/restore.js +5 -2
  324. package/dist/test-utils/fetch-mock.js +5 -0
  325. package/dist/version.js +18 -5
  326. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  327. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  328. package/dist/web/inbound/media.js +34 -8
  329. package/dist/web/inbound/monitor.js +34 -17
  330. package/dist/web/inbound/send-api.js +18 -17
  331. package/dist/web/outbound.js +12 -5
  332. package/dist/wizard/clack-prompter.js +40 -7
  333. package/extensions/bluebubbles/package.json +1 -1
  334. package/extensions/copilot-proxy/package.json +1 -1
  335. package/extensions/diagnostics-otel/package.json +1 -1
  336. package/extensions/discord/package.json +1 -1
  337. package/extensions/feishu/package.json +1 -1
  338. package/extensions/google-antigravity-auth/package.json +1 -1
  339. package/extensions/google-gemini-cli-auth/package.json +1 -1
  340. package/extensions/googlechat/package.json +1 -1
  341. package/extensions/imessage/package.json +1 -1
  342. package/extensions/irc/package.json +1 -1
  343. package/extensions/line/package.json +1 -1
  344. package/extensions/llm-task/package.json +1 -1
  345. package/extensions/lobster/package.json +1 -1
  346. package/extensions/matrix/CHANGELOG.md +5 -0
  347. package/extensions/matrix/package.json +1 -1
  348. package/extensions/mattermost/package.json +1 -1
  349. package/extensions/memory-core/package.json +1 -1
  350. package/extensions/memory-lancedb/package.json +1 -1
  351. package/extensions/minimax-portal-auth/package.json +1 -1
  352. package/extensions/msteams/CHANGELOG.md +5 -0
  353. package/extensions/msteams/package.json +1 -1
  354. package/extensions/nextcloud-talk/package.json +1 -1
  355. package/extensions/nostr/CHANGELOG.md +5 -0
  356. package/extensions/nostr/package.json +1 -1
  357. package/extensions/open-prose/package.json +1 -1
  358. package/extensions/openai-codex-auth/package.json +1 -1
  359. package/extensions/signal/package.json +1 -1
  360. package/extensions/slack/package.json +1 -1
  361. package/extensions/telegram/package.json +1 -1
  362. package/extensions/tlon/package.json +1 -1
  363. package/extensions/twitch/CHANGELOG.md +5 -0
  364. package/extensions/twitch/package.json +1 -1
  365. package/extensions/voice-call/CHANGELOG.md +5 -0
  366. package/extensions/voice-call/package.json +1 -1
  367. package/extensions/whatsapp/package.json +1 -1
  368. package/extensions/zalo/CHANGELOG.md +5 -0
  369. package/extensions/zalo/package.json +1 -1
  370. package/extensions/zalouser/CHANGELOG.md +5 -0
  371. package/extensions/zalouser/package.json +1 -1
  372. package/package.json +1 -1
  373. package/skills/apple-reminders/SKILL.md +100 -49
  374. package/skills/coding-agent/SKILL.md +34 -28
  375. package/skills/github/SKILL.md +131 -16
  376. package/skills/imsg/SKILL.md +112 -15
  377. package/skills/openhue/SKILL.md +101 -19
  378. package/skills/plcode-controller/SKILL.md +156 -0
  379. package/skills/plcode-controller/assets/operator-prompts.md +65 -0
  380. package/skills/plcode-controller/references/command-cheatsheet.md +53 -0
  381. package/skills/plcode-controller/references/failure-handling.md +60 -0
  382. package/skills/plcode-controller/references/model-selection.md +57 -0
  383. package/skills/plcode-controller/references/plan-vs-build.md +52 -0
  384. package/skills/plcode-controller/references/question-handling.md +40 -0
  385. package/skills/plcode-controller/references/session-management.md +63 -0
  386. package/skills/plcode-controller/references/workflow.md +35 -0
  387. package/skills/tmux/SKILL.md +111 -79
  388. package/skills/weather/SKILL.md +88 -25
@@ -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
  }