@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,61 +1,222 @@
1
1
  let handler = null;
2
- let pendingReason = null;
2
+ let handlerGeneration = 0;
3
+ const pendingWakes = new Map();
3
4
  let scheduled = false;
4
5
  let running = false;
5
6
  let timer = null;
7
+ let timerDueAt = null;
8
+ let timerKind = null;
6
9
  const DEFAULT_COALESCE_MS = 250;
7
10
  const DEFAULT_RETRY_MS = 1_000;
8
- function schedule(coalesceMs) {
9
- if (timer)
11
+ const HOOK_REASON_PREFIX = "hook:";
12
+ const REASON_PRIORITY = {
13
+ RETRY: 0,
14
+ INTERVAL: 1,
15
+ DEFAULT: 2,
16
+ ACTION: 3,
17
+ };
18
+ function isActionWakeReason(reason) {
19
+ return reason === "manual" || reason === "exec-event" || reason.startsWith(HOOK_REASON_PREFIX);
20
+ }
21
+ function resolveReasonPriority(reason) {
22
+ if (reason === "retry") {
23
+ return REASON_PRIORITY.RETRY;
24
+ }
25
+ if (reason === "interval") {
26
+ return REASON_PRIORITY.INTERVAL;
27
+ }
28
+ if (isActionWakeReason(reason)) {
29
+ return REASON_PRIORITY.ACTION;
30
+ }
31
+ return REASON_PRIORITY.DEFAULT;
32
+ }
33
+ function normalizeWakeReason(reason) {
34
+ if (typeof reason !== "string") {
35
+ return "requested";
36
+ }
37
+ const trimmed = reason.trim();
38
+ return trimmed.length > 0 ? trimmed : "requested";
39
+ }
40
+ function normalizeWakeTarget(value) {
41
+ const trimmed = typeof value === "string" ? value.trim() : "";
42
+ return trimmed || undefined;
43
+ }
44
+ function getWakeTargetKey(params) {
45
+ const agentId = normalizeWakeTarget(params.agentId);
46
+ const sessionKey = normalizeWakeTarget(params.sessionKey);
47
+ return `${agentId ?? ""}::${sessionKey ?? ""}`;
48
+ }
49
+ function queuePendingWakeReason(params) {
50
+ const requestedAt = params?.requestedAt ?? Date.now();
51
+ const normalizedReason = normalizeWakeReason(params?.reason);
52
+ const normalizedAgentId = normalizeWakeTarget(params?.agentId);
53
+ const normalizedSessionKey = normalizeWakeTarget(params?.sessionKey);
54
+ const wakeTargetKey = getWakeTargetKey({
55
+ agentId: normalizedAgentId,
56
+ sessionKey: normalizedSessionKey,
57
+ });
58
+ const next = {
59
+ reason: normalizedReason,
60
+ priority: resolveReasonPriority(normalizedReason),
61
+ requestedAt,
62
+ agentId: normalizedAgentId,
63
+ sessionKey: normalizedSessionKey,
64
+ };
65
+ const previous = pendingWakes.get(wakeTargetKey);
66
+ if (!previous) {
67
+ pendingWakes.set(wakeTargetKey, next);
10
68
  return;
69
+ }
70
+ if (next.priority > previous.priority) {
71
+ pendingWakes.set(wakeTargetKey, next);
72
+ return;
73
+ }
74
+ if (next.priority === previous.priority && next.requestedAt >= previous.requestedAt) {
75
+ pendingWakes.set(wakeTargetKey, next);
76
+ }
77
+ }
78
+ function schedule(coalesceMs, kind = "normal") {
79
+ const delay = Number.isFinite(coalesceMs) ? Math.max(0, coalesceMs) : DEFAULT_COALESCE_MS;
80
+ const dueAt = Date.now() + delay;
81
+ if (timer) {
82
+ // Keep retry cooldown as a hard minimum delay. This prevents the
83
+ // finally-path reschedule (often delay=0) from collapsing backoff.
84
+ if (timerKind === "retry") {
85
+ return;
86
+ }
87
+ // If existing timer fires sooner or at the same time, keep it.
88
+ if (typeof timerDueAt === "number" && timerDueAt <= dueAt) {
89
+ return;
90
+ }
91
+ // New request needs to fire sooner — preempt the existing timer.
92
+ clearTimeout(timer);
93
+ timer = null;
94
+ timerDueAt = null;
95
+ timerKind = null;
96
+ }
97
+ timerDueAt = dueAt;
98
+ timerKind = kind;
11
99
  timer = setTimeout(async () => {
12
100
  timer = null;
101
+ timerDueAt = null;
102
+ timerKind = null;
13
103
  scheduled = false;
14
104
  const active = handler;
15
- if (!active)
105
+ if (!active) {
16
106
  return;
107
+ }
17
108
  if (running) {
18
109
  scheduled = true;
19
- schedule(coalesceMs);
110
+ schedule(delay, kind);
20
111
  return;
21
112
  }
22
- const reason = pendingReason;
23
- pendingReason = null;
113
+ const pendingBatch = Array.from(pendingWakes.values());
114
+ pendingWakes.clear();
24
115
  running = true;
25
116
  try {
26
- const res = await active({ reason: reason ?? undefined });
27
- if (res.status === "skipped" && res.reason === "requests-in-flight") {
28
- // The main lane is busy; retry soon.
29
- pendingReason = reason ?? "retry";
30
- schedule(DEFAULT_RETRY_MS);
117
+ for (const pendingWake of pendingBatch) {
118
+ const wakeOpts = {
119
+ reason: pendingWake.reason ?? undefined,
120
+ ...(pendingWake.agentId ? { agentId: pendingWake.agentId } : {}),
121
+ ...(pendingWake.sessionKey ? { sessionKey: pendingWake.sessionKey } : {}),
122
+ };
123
+ const res = await active(wakeOpts);
124
+ if (res.status === "skipped" && res.reason === "requests-in-flight") {
125
+ // The main lane is busy; retry this wake target soon.
126
+ queuePendingWakeReason({
127
+ reason: pendingWake.reason ?? "retry",
128
+ agentId: pendingWake.agentId,
129
+ sessionKey: pendingWake.sessionKey,
130
+ });
131
+ schedule(DEFAULT_RETRY_MS, "retry");
132
+ }
31
133
  }
32
134
  }
33
135
  catch {
34
136
  // Error is already logged by the heartbeat runner; schedule a retry.
35
- pendingReason = reason ?? "retry";
36
- schedule(DEFAULT_RETRY_MS);
137
+ for (const pendingWake of pendingBatch) {
138
+ queuePendingWakeReason({
139
+ reason: pendingWake.reason ?? "retry",
140
+ agentId: pendingWake.agentId,
141
+ sessionKey: pendingWake.sessionKey,
142
+ });
143
+ }
144
+ schedule(DEFAULT_RETRY_MS, "retry");
37
145
  }
38
146
  finally {
39
147
  running = false;
40
- if (pendingReason || scheduled)
41
- schedule(coalesceMs);
148
+ if (pendingWakes.size > 0 || scheduled) {
149
+ schedule(delay, "normal");
150
+ }
42
151
  }
43
- }, coalesceMs);
152
+ }, delay);
44
153
  timer.unref?.();
45
154
  }
155
+ /**
156
+ * Register (or clear) the heartbeat wake handler.
157
+ * Returns a disposer function that clears this specific registration.
158
+ * Stale disposers (from previous registrations) are no-ops, preventing
159
+ * a race where an old runner's cleanup clears a newer runner's handler.
160
+ */
46
161
  export function setHeartbeatWakeHandler(next) {
162
+ handlerGeneration += 1;
163
+ const generation = handlerGeneration;
47
164
  handler = next;
48
- if (handler && pendingReason) {
49
- schedule(DEFAULT_COALESCE_MS);
165
+ if (next) {
166
+ // New lifecycle starting (e.g. after SIGUSR1 in-process restart).
167
+ // Clear any timer metadata from the previous lifecycle so stale retry
168
+ // cooldowns do not delay a fresh handler.
169
+ if (timer) {
170
+ clearTimeout(timer);
171
+ }
172
+ timer = null;
173
+ timerDueAt = null;
174
+ timerKind = null;
175
+ // Reset module-level execution state that may be stale from interrupted
176
+ // runs in the previous lifecycle. Without this, `running === true` from
177
+ // an interrupted heartbeat blocks all future schedule() attempts, and
178
+ // `scheduled === true` can cause spurious immediate re-runs.
179
+ running = false;
180
+ scheduled = false;
181
+ }
182
+ if (handler && pendingWakes.size > 0) {
183
+ schedule(DEFAULT_COALESCE_MS, "normal");
50
184
  }
185
+ return () => {
186
+ if (handlerGeneration !== generation) {
187
+ return;
188
+ }
189
+ if (handler !== next) {
190
+ return;
191
+ }
192
+ handlerGeneration += 1;
193
+ handler = null;
194
+ };
51
195
  }
52
196
  export function requestHeartbeatNow(opts) {
53
- pendingReason = opts?.reason ?? pendingReason ?? "requested";
54
- schedule(opts?.coalesceMs ?? DEFAULT_COALESCE_MS);
197
+ queuePendingWakeReason({
198
+ reason: opts?.reason,
199
+ agentId: opts?.agentId,
200
+ sessionKey: opts?.sessionKey,
201
+ });
202
+ schedule(opts?.coalesceMs ?? DEFAULT_COALESCE_MS, "normal");
55
203
  }
56
204
  export function hasHeartbeatWakeHandler() {
57
205
  return handler !== null;
58
206
  }
59
207
  export function hasPendingHeartbeatWake() {
60
- return pendingReason !== null || Boolean(timer) || scheduled;
208
+ return pendingWakes.size > 0 || Boolean(timer) || scheduled;
209
+ }
210
+ export function resetHeartbeatWakeStateForTests() {
211
+ if (timer) {
212
+ clearTimeout(timer);
213
+ }
214
+ timer = null;
215
+ timerDueAt = null;
216
+ timerKind = null;
217
+ pendingWakes.clear();
218
+ scheduled = false;
219
+ running = false;
220
+ handlerGeneration += 1;
221
+ handler = null;
61
222
  }
@@ -0,0 +1,47 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { runCommandWithTimeout } from "../process/exec.js";
5
+ import { resolveUserPath } from "../utils.js";
6
+ import { fileExists, resolveArchiveKind } from "./archive.js";
7
+ export async function withTempDir(prefix, fn) {
8
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
9
+ try {
10
+ return await fn(tmpDir);
11
+ }
12
+ finally {
13
+ await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
14
+ }
15
+ }
16
+ export async function resolveArchiveSourcePath(archivePath) {
17
+ const resolved = resolveUserPath(archivePath);
18
+ if (!(await fileExists(resolved))) {
19
+ return { ok: false, error: `archive not found: ${resolved}` };
20
+ }
21
+ if (!resolveArchiveKind(resolved)) {
22
+ return { ok: false, error: `unsupported archive: ${resolved}` };
23
+ }
24
+ return { ok: true, path: resolved };
25
+ }
26
+ export async function packNpmSpecToArchive(params) {
27
+ const res = await runCommandWithTimeout(["npm", "pack", params.spec, "--ignore-scripts"], {
28
+ timeoutMs: Math.max(params.timeoutMs, 300_000),
29
+ cwd: params.cwd,
30
+ env: {
31
+ COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
32
+ NPM_CONFIG_IGNORE_SCRIPTS: "true",
33
+ },
34
+ });
35
+ if (res.code !== 0) {
36
+ return { ok: false, error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}` };
37
+ }
38
+ const packed = (res.stdout || "")
39
+ .split("\n")
40
+ .map((line) => line.trim())
41
+ .filter(Boolean)
42
+ .pop();
43
+ if (!packed) {
44
+ return { ok: false, error: "npm pack produced no archive" };
45
+ }
46
+ return { ok: true, archivePath: path.join(params.cwd, packed) };
47
+ }
@@ -1,27 +1,44 @@
1
1
  import { lookup as dnsLookupCb } from "node:dns";
2
2
  import { lookup as dnsLookup } from "node:dns/promises";
3
3
  import { Agent } from "undici";
4
+ import { normalizeHostname } from "./hostname.js";
4
5
  export class SsrFBlockedError extends Error {
5
6
  constructor(message) {
6
7
  super(message);
7
8
  this.name = "SsrFBlockedError";
8
9
  }
9
10
  }
10
- const PRIVATE_IPV6_PREFIXES = ["fe80:", "fec0:", "fc", "fd"];
11
11
  const BLOCKED_HOSTNAMES = new Set(["localhost", "metadata.google.internal"]);
12
- function normalizeHostname(hostname) {
13
- const normalized = hostname.trim().toLowerCase().replace(/\.$/, "");
14
- if (normalized.startsWith("[") && normalized.endsWith("]")) {
15
- return normalized.slice(1, -1);
16
- }
17
- return normalized;
18
- }
19
12
  function normalizeHostnameSet(values) {
20
13
  if (!values || values.length === 0) {
21
14
  return new Set();
22
15
  }
23
16
  return new Set(values.map((value) => normalizeHostname(value)).filter(Boolean));
24
17
  }
18
+ function normalizeHostnameAllowlist(values) {
19
+ if (!values || values.length === 0) {
20
+ return [];
21
+ }
22
+ return Array.from(new Set(values
23
+ .map((value) => normalizeHostname(value))
24
+ .filter((value) => value !== "*" && value !== "*." && value.length > 0)));
25
+ }
26
+ function isHostnameAllowedByPattern(hostname, pattern) {
27
+ if (pattern.startsWith("*.")) {
28
+ const suffix = pattern.slice(2);
29
+ if (!suffix || hostname === suffix) {
30
+ return false;
31
+ }
32
+ return hostname.endsWith(`.${suffix}`);
33
+ }
34
+ return hostname === pattern;
35
+ }
36
+ function matchesHostnameAllowlist(hostname, allowlist) {
37
+ if (allowlist.length === 0) {
38
+ return true;
39
+ }
40
+ return allowlist.some((pattern) => isHostnameAllowedByPattern(hostname, pattern));
41
+ }
25
42
  function parseIpv4(address) {
26
43
  const parts = address.split(".");
27
44
  if (parts.length !== 4) {
@@ -33,33 +50,114 @@ function parseIpv4(address) {
33
50
  }
34
51
  return numbers;
35
52
  }
36
- function parseIpv4FromMappedIpv6(mapped) {
37
- if (mapped.includes(".")) {
38
- return parseIpv4(mapped);
53
+ function stripIpv6ZoneId(address) {
54
+ const index = address.indexOf("%");
55
+ return index >= 0 ? address.slice(0, index) : address;
56
+ }
57
+ function parseIpv6Hextets(address) {
58
+ let input = stripIpv6ZoneId(address.trim().toLowerCase());
59
+ if (!input) {
60
+ return null;
39
61
  }
40
- const parts = mapped.split(":").filter(Boolean);
41
- if (parts.length === 1) {
42
- const value = Number.parseInt(parts[0], 16);
43
- if (Number.isNaN(value) || value < 0 || value > 0xffff_ffff) {
62
+ // Handle IPv4-embedded IPv6 like ::ffff:127.0.0.1 by converting the tail to 2 hextets.
63
+ if (input.includes(".")) {
64
+ const lastColon = input.lastIndexOf(":");
65
+ if (lastColon < 0) {
66
+ return null;
67
+ }
68
+ const ipv4 = parseIpv4(input.slice(lastColon + 1));
69
+ if (!ipv4) {
44
70
  return null;
45
71
  }
46
- return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff];
72
+ const high = (ipv4[0] << 8) + ipv4[1];
73
+ const low = (ipv4[2] << 8) + ipv4[3];
74
+ input = `${input.slice(0, lastColon)}:${high.toString(16)}:${low.toString(16)}`;
75
+ }
76
+ const doubleColonParts = input.split("::");
77
+ if (doubleColonParts.length > 2) {
78
+ return null;
47
79
  }
48
- if (parts.length !== 2) {
80
+ const headParts = doubleColonParts[0]?.length > 0 ? doubleColonParts[0].split(":").filter(Boolean) : [];
81
+ const tailParts = doubleColonParts.length === 2 && doubleColonParts[1]?.length > 0
82
+ ? doubleColonParts[1].split(":").filter(Boolean)
83
+ : [];
84
+ const missingParts = 8 - headParts.length - tailParts.length;
85
+ if (missingParts < 0) {
49
86
  return null;
50
87
  }
51
- const high = Number.parseInt(parts[0], 16);
52
- const low = Number.parseInt(parts[1], 16);
53
- if (Number.isNaN(high) ||
54
- Number.isNaN(low) ||
55
- high < 0 ||
56
- low < 0 ||
57
- high > 0xffff ||
58
- low > 0xffff) {
88
+ const fullParts = doubleColonParts.length === 1
89
+ ? input.split(":")
90
+ : [...headParts, ...Array.from({ length: missingParts }, () => "0"), ...tailParts];
91
+ if (fullParts.length !== 8) {
59
92
  return null;
60
93
  }
61
- const value = (high << 16) + low;
62
- return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff];
94
+ const hextets = [];
95
+ for (const part of fullParts) {
96
+ if (!part) {
97
+ return null;
98
+ }
99
+ const value = Number.parseInt(part, 16);
100
+ if (Number.isNaN(value) || value < 0 || value > 0xffff) {
101
+ return null;
102
+ }
103
+ hextets.push(value);
104
+ }
105
+ return hextets;
106
+ }
107
+ function decodeIpv4FromHextets(high, low) {
108
+ return [(high >>> 8) & 0xff, high & 0xff, (low >>> 8) & 0xff, low & 0xff];
109
+ }
110
+ const EMBEDDED_IPV4_RULES = [
111
+ {
112
+ // IPv4-mapped: ::ffff:a.b.c.d and IPv4-compatible ::a.b.c.d.
113
+ matches: (hextets) => hextets[0] === 0 &&
114
+ hextets[1] === 0 &&
115
+ hextets[2] === 0 &&
116
+ hextets[3] === 0 &&
117
+ hextets[4] === 0 &&
118
+ (hextets[5] === 0xffff || hextets[5] === 0),
119
+ extract: (hextets) => [hextets[6], hextets[7]],
120
+ },
121
+ {
122
+ // NAT64 well-known prefix: 64:ff9b::/96.
123
+ matches: (hextets) => hextets[0] === 0x0064 &&
124
+ hextets[1] === 0xff9b &&
125
+ hextets[2] === 0 &&
126
+ hextets[3] === 0 &&
127
+ hextets[4] === 0 &&
128
+ hextets[5] === 0,
129
+ extract: (hextets) => [hextets[6], hextets[7]],
130
+ },
131
+ {
132
+ // NAT64 local-use prefix: 64:ff9b:1::/48.
133
+ matches: (hextets) => hextets[0] === 0x0064 &&
134
+ hextets[1] === 0xff9b &&
135
+ hextets[2] === 0x0001 &&
136
+ hextets[3] === 0 &&
137
+ hextets[4] === 0 &&
138
+ hextets[5] === 0,
139
+ extract: (hextets) => [hextets[6], hextets[7]],
140
+ },
141
+ {
142
+ // 6to4 prefix: 2002::/16 where hextets[1..2] carry IPv4.
143
+ matches: (hextets) => hextets[0] === 0x2002,
144
+ extract: (hextets) => [hextets[1], hextets[2]],
145
+ },
146
+ {
147
+ // Teredo prefix: 2001:0000::/32 with client IPv4 obfuscated via XOR 0xffff.
148
+ matches: (hextets) => hextets[0] === 0x2001 && hextets[1] === 0x0000,
149
+ extract: (hextets) => [hextets[6] ^ 0xffff, hextets[7] ^ 0xffff],
150
+ },
151
+ ];
152
+ function extractIpv4FromEmbeddedIpv6(hextets) {
153
+ for (const rule of EMBEDDED_IPV4_RULES) {
154
+ if (!rule.matches(hextets)) {
155
+ continue;
156
+ }
157
+ const [high, low] = rule.extract(hextets);
158
+ return decodeIpv4FromHextets(high, low);
159
+ }
160
+ return null;
63
161
  }
64
162
  function isPrivateIpv4(parts) {
65
163
  const [octet1, octet2] = parts;
@@ -94,18 +192,50 @@ export function isPrivateIpAddress(address) {
94
192
  if (!normalized) {
95
193
  return false;
96
194
  }
97
- if (normalized.startsWith("::ffff:")) {
98
- const mapped = normalized.slice("::ffff:".length);
99
- const ipv4 = parseIpv4FromMappedIpv6(mapped);
100
- if (ipv4) {
101
- return isPrivateIpv4(ipv4);
102
- }
103
- }
104
195
  if (normalized.includes(":")) {
105
- if (normalized === "::" || normalized === "::1") {
196
+ const hextets = parseIpv6Hextets(normalized);
197
+ if (!hextets) {
198
+ // Security-critical parse failures should fail closed.
199
+ return true;
200
+ }
201
+ const isUnspecified = hextets[0] === 0 &&
202
+ hextets[1] === 0 &&
203
+ hextets[2] === 0 &&
204
+ hextets[3] === 0 &&
205
+ hextets[4] === 0 &&
206
+ hextets[5] === 0 &&
207
+ hextets[6] === 0 &&
208
+ hextets[7] === 0;
209
+ const isLoopback = hextets[0] === 0 &&
210
+ hextets[1] === 0 &&
211
+ hextets[2] === 0 &&
212
+ hextets[3] === 0 &&
213
+ hextets[4] === 0 &&
214
+ hextets[5] === 0 &&
215
+ hextets[6] === 0 &&
216
+ hextets[7] === 1;
217
+ if (isUnspecified || isLoopback) {
218
+ return true;
219
+ }
220
+ const embeddedIpv4 = extractIpv4FromEmbeddedIpv6(hextets);
221
+ if (embeddedIpv4) {
222
+ return isPrivateIpv4(embeddedIpv4);
223
+ }
224
+ // IPv6 private/internal ranges
225
+ // - link-local: fe80::/10
226
+ // - site-local (deprecated, but internal): fec0::/10
227
+ // - unique local: fc00::/7
228
+ const first = hextets[0];
229
+ if ((first & 0xffc0) === 0xfe80) {
230
+ return true;
231
+ }
232
+ if ((first & 0xffc0) === 0xfec0) {
233
+ return true;
234
+ }
235
+ if ((first & 0xfe00) === 0xfc00) {
106
236
  return true;
107
237
  }
108
- return PRIVATE_IPV6_PREFIXES.some((prefix) => normalized.startsWith(prefix));
238
+ return false;
109
239
  }
110
240
  const ipv4 = parseIpv4(normalized);
111
241
  if (!ipv4) {
@@ -171,7 +301,11 @@ export async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
171
301
  }
172
302
  const allowPrivateNetwork = Boolean(params.policy?.allowPrivateNetwork);
173
303
  const allowedHostnames = normalizeHostnameSet(params.policy?.allowedHostnames);
304
+ const hostnameAllowlist = normalizeHostnameAllowlist(params.policy?.hostnameAllowlist);
174
305
  const isExplicitAllowed = allowedHostnames.has(normalized);
306
+ if (!matchesHostnameAllowlist(normalized, hostnameAllowlist)) {
307
+ throw new SsrFBlockedError(`Blocked hostname (not in allowlist): ${hostname}`);
308
+ }
175
309
  if (!allowPrivateNetwork && !isExplicitAllowed) {
176
310
  if (isBlockedHostname(normalized)) {
177
311
  throw new SsrFBlockedError(`Blocked hostname: ${hostname}`);