@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,15 +1,49 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { resolveCanvasHostUrl } from "../../infra/canvas-host-url.js";
3
- import { listSystemPresence, upsertPresence } from "../../infra/system-presence.js";
3
+ import { removeRemoteNodeInfo } from "../../infra/skills-remote.js";
4
+ import { upsertPresence } from "../../infra/system-presence.js";
5
+ import { truncateUtf16Safe } from "../../utils.js";
4
6
  import { isWebchatClient } from "../../utils/message-channel.js";
5
7
  import { isLoopbackAddress } from "../net.js";
6
8
  import { getHandshakeTimeoutMs } from "../server-constants.js";
7
9
  import { formatError } from "../server-utils.js";
8
10
  import { logWs } from "../ws-log.js";
9
- import { getHealthVersion, getPresenceVersion, incrementPresenceVersion } from "./health-state.js";
11
+ import { getHealthVersion, incrementPresenceVersion } from "./health-state.js";
12
+ import { broadcastPresenceSnapshot } from "./presence-events.js";
10
13
  import { attachGatewayWsMessageHandler } from "./ws-connection/message-handler.js";
14
+ const LOG_HEADER_MAX_LEN = 300;
15
+ const LOG_HEADER_FORMAT_REGEX = /\p{Cf}/gu;
16
+ function replaceControlChars(value) {
17
+ let cleaned = "";
18
+ for (const char of value) {
19
+ const codePoint = char.codePointAt(0);
20
+ if (codePoint !== undefined &&
21
+ (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f))) {
22
+ cleaned += " ";
23
+ continue;
24
+ }
25
+ cleaned += char;
26
+ }
27
+ return cleaned;
28
+ }
29
+ const sanitizeLogValue = (value) => {
30
+ if (!value) {
31
+ return undefined;
32
+ }
33
+ const cleaned = replaceControlChars(value)
34
+ .replace(LOG_HEADER_FORMAT_REGEX, " ")
35
+ .replace(/\s+/g, " ")
36
+ .trim();
37
+ if (!cleaned) {
38
+ return undefined;
39
+ }
40
+ if (cleaned.length <= LOG_HEADER_MAX_LEN) {
41
+ return cleaned;
42
+ }
43
+ return truncateUtf16Safe(cleaned, LOG_HEADER_MAX_LEN);
44
+ };
11
45
  export function attachGatewayWsConnectionHandler(params) {
12
- const { wss, clients, port, gatewayHost, canvasHostEnabled, canvasHostServerPort, resolvedAuth, gatewayMethods, events, logGateway, logHealth, logWsControl, extraHandlers, broadcast, buildRequestContext, } = params;
46
+ const { wss, clients, port, gatewayHost, canvasHostEnabled, canvasHostServerPort, resolvedAuth, rateLimiter, gatewayMethods, events, logGateway, logHealth, logWsControl, extraHandlers, broadcast, buildRequestContext, } = params;
13
47
  wss.on("connection", (socket, upgradeReq) => {
14
48
  let client = null;
15
49
  let closed = false;
@@ -40,8 +74,9 @@ export function attachGatewayWsConnectionHandler(params) {
40
74
  let lastFrameMethod;
41
75
  let lastFrameId;
42
76
  const setCloseCause = (cause, meta) => {
43
- if (!closeCause)
77
+ if (!closeCause) {
44
78
  closeCause = cause;
79
+ }
45
80
  if (meta && Object.keys(meta).length > 0) {
46
81
  closeMeta = { ...closeMeta, ...meta };
47
82
  }
@@ -68,12 +103,14 @@ export function attachGatewayWsConnectionHandler(params) {
68
103
  payload: { nonce: connectNonce, ts: Date.now() },
69
104
  });
70
105
  const close = (code = 1000, reason) => {
71
- if (closed)
106
+ if (closed) {
72
107
  return;
108
+ }
73
109
  closed = true;
74
110
  clearTimeout(handshakeTimer);
75
- if (client)
111
+ if (client) {
76
112
  clients.delete(client);
113
+ }
77
114
  try {
78
115
  socket.close(code, reason);
79
116
  }
@@ -88,6 +125,11 @@ export function attachGatewayWsConnectionHandler(params) {
88
125
  const isNoisySwiftPmHelperClose = (userAgent, remote) => Boolean(userAgent?.toLowerCase().includes("swiftpm-testing-helper") && isLoopbackAddress(remote));
89
126
  socket.once("close", (code, reason) => {
90
127
  const durationMs = Date.now() - openedAt;
128
+ const logForwardedFor = sanitizeLogValue(forwardedFor);
129
+ const logOrigin = sanitizeLogValue(requestOrigin);
130
+ const logHost = sanitizeLogValue(requestHost);
131
+ const logUserAgent = sanitizeLogValue(requestUserAgent);
132
+ const logReason = sanitizeLogValue(reason?.toString());
91
133
  const closeContext = {
92
134
  cause: closeCause,
93
135
  handshake: handshakeState,
@@ -95,43 +137,37 @@ export function attachGatewayWsConnectionHandler(params) {
95
137
  lastFrameType,
96
138
  lastFrameMethod,
97
139
  lastFrameId,
98
- host: requestHost,
99
- origin: requestOrigin,
100
- userAgent: requestUserAgent,
101
- forwardedFor,
140
+ host: logHost,
141
+ origin: logOrigin,
142
+ userAgent: logUserAgent,
143
+ forwardedFor: logForwardedFor,
102
144
  ...closeMeta,
103
145
  };
104
146
  if (!client) {
105
147
  const logFn = isNoisySwiftPmHelperClose(requestUserAgent, remoteAddr)
106
148
  ? logWsControl.debug
107
149
  : logWsControl.warn;
108
- logFn(`closed before connect conn=${connId} remote=${remoteAddr ?? "?"} fwd=${forwardedFor ?? "n/a"} origin=${requestOrigin ?? "n/a"} host=${requestHost ?? "n/a"} ua=${requestUserAgent ?? "n/a"} code=${code ?? "n/a"} reason=${reason?.toString() || "n/a"}`, closeContext);
150
+ logFn(`closed before connect conn=${connId} remote=${remoteAddr ?? "?"} fwd=${logForwardedFor || "n/a"} origin=${logOrigin || "n/a"} host=${logHost || "n/a"} ua=${logUserAgent || "n/a"} code=${code ?? "n/a"} reason=${logReason || "n/a"}`, closeContext);
109
151
  }
110
152
  if (client && isWebchatClient(client.connect.client)) {
111
- logWsControl.info(`webchat disconnected code=${code} reason=${reason?.toString() || "n/a"} conn=${connId}`);
153
+ logWsControl.info(`webchat disconnected code=${code} reason=${logReason || "n/a"} conn=${connId}`);
112
154
  }
113
155
  if (client?.presenceKey) {
114
156
  upsertPresence(client.presenceKey, { reason: "disconnect" });
115
- incrementPresenceVersion();
116
- broadcast("presence", { presence: listSystemPresence() }, {
117
- dropIfSlow: true,
118
- stateVersion: {
119
- presence: getPresenceVersion(),
120
- health: getHealthVersion(),
121
- },
122
- });
157
+ broadcastPresenceSnapshot({ broadcast, incrementPresenceVersion, getHealthVersion });
123
158
  }
124
159
  if (client?.connect?.role === "node") {
125
160
  const context = buildRequestContext();
126
161
  const nodeId = context.nodeRegistry.unregister(connId);
127
162
  if (nodeId) {
163
+ removeRemoteNodeInfo(nodeId);
128
164
  context.nodeUnsubscribeAll(nodeId);
129
165
  }
130
166
  }
131
167
  logWs("out", "close", {
132
168
  connId,
133
169
  code,
134
- reason: reason?.toString(),
170
+ reason: logReason,
135
171
  durationMs,
136
172
  cause: closeCause,
137
173
  handshake: handshakeState,
@@ -165,6 +201,7 @@ export function attachGatewayWsConnectionHandler(params) {
165
201
  canvasHostUrl,
166
202
  connectNonce,
167
203
  resolvedAuth,
204
+ rateLimiter,
168
205
  gatewayMethods,
169
206
  events,
170
207
  extraHandlers,
@@ -1,5 +1,5 @@
1
1
  import { MAX_BUFFERED_BYTES } from "./server-constants.js";
2
- import { logWs, summarizeAgentEventForWsLog } from "./ws-log.js";
2
+ import { logWs, shouldLogWs, summarizeAgentEventForWsLog } from "./ws-log.js";
3
3
  const ADMIN_SCOPE = "operator.admin";
4
4
  const APPROVALS_SCOPE = "operator.approvals";
5
5
  const PAIRING_SCOPE = "operator.pairing";
@@ -29,6 +29,9 @@ function hasEventScope(client, event) {
29
29
  export function createGatewayBroadcaster(params) {
30
30
  let seq = 0;
31
31
  const broadcastInternal = (event, payload, opts, targetConnIds) => {
32
+ if (params.clients.size === 0) {
33
+ return;
34
+ }
32
35
  const isTargeted = Boolean(targetConnIds);
33
36
  const eventSeq = isTargeted ? undefined : ++seq;
34
37
  const frame = JSON.stringify({
@@ -38,19 +41,21 @@ export function createGatewayBroadcaster(params) {
38
41
  seq: eventSeq,
39
42
  stateVersion: opts?.stateVersion,
40
43
  });
41
- const logMeta = {
42
- event,
43
- seq: eventSeq ?? "targeted",
44
- clients: params.clients.size,
45
- targets: targetConnIds ? targetConnIds.size : undefined,
46
- dropIfSlow: opts?.dropIfSlow,
47
- presenceVersion: opts?.stateVersion?.presence,
48
- healthVersion: opts?.stateVersion?.health,
49
- };
50
- if (event === "agent") {
51
- Object.assign(logMeta, summarizeAgentEventForWsLog(payload));
44
+ if (shouldLogWs()) {
45
+ const logMeta = {
46
+ event,
47
+ seq: eventSeq ?? "targeted",
48
+ clients: params.clients.size,
49
+ targets: targetConnIds ? targetConnIds.size : undefined,
50
+ dropIfSlow: opts?.dropIfSlow,
51
+ presenceVersion: opts?.stateVersion?.presence,
52
+ healthVersion: opts?.stateVersion?.health,
53
+ };
54
+ if (event === "agent") {
55
+ Object.assign(logMeta, summarizeAgentEventForWsLog(payload));
56
+ }
57
+ logWs("out", "event", logMeta);
52
58
  }
53
- logWs("out", "event", logMeta);
54
59
  for (const c of params.clients) {
55
60
  if (targetConnIds && !targetConnIds.has(c.connId)) {
56
61
  continue;
@@ -1,22 +1,49 @@
1
1
  import { resolveDefaultAgentId } from "../agents/agent-scope.js";
2
2
  import { loadConfig } from "../config/config.js";
3
- import { resolveAgentMainSessionKey } from "../config/sessions.js";
3
+ import { canonicalizeMainSessionAlias, resolveAgentIdFromSessionKey, resolveAgentMainSessionKey, } from "../config/sessions.js";
4
+ import { resolveStorePath } from "../config/sessions/paths.js";
4
5
  import { runCronIsolatedAgentTurn } from "../cron/isolated-agent.js";
5
6
  import { appendCronRunLog, resolveCronRunLogPath } from "../cron/run-log.js";
6
7
  import { CronService } from "../cron/service.js";
7
8
  import { resolveCronStorePath } from "../cron/store.js";
9
+ import { normalizeHttpWebhookUrl } from "../cron/webhook-url.js";
10
+ import { formatErrorMessage } from "../infra/errors.js";
8
11
  import { runHeartbeatOnce } from "../infra/heartbeat-runner.js";
9
12
  import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
13
+ import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
14
+ import { SsrFBlockedError } from "../infra/net/ssrf.js";
10
15
  import { enqueueSystemEvent } from "../infra/system-events.js";
11
16
  import { getChildLogger } from "../logging.js";
12
- import { normalizeAgentId } from "../routing/session-key.js";
17
+ import { normalizeAgentId, toAgentStoreSessionKey } from "../routing/session-key.js";
13
18
  import { defaultRuntime } from "../runtime.js";
19
+ const CRON_WEBHOOK_TIMEOUT_MS = 10_000;
20
+ function redactWebhookUrl(url) {
21
+ try {
22
+ const parsed = new URL(url);
23
+ return `${parsed.origin}${parsed.pathname}`;
24
+ }
25
+ catch {
26
+ return "<invalid-webhook-url>";
27
+ }
28
+ }
29
+ function resolveCronWebhookTarget(params) {
30
+ const mode = params.delivery?.mode?.trim().toLowerCase();
31
+ if (mode === "webhook") {
32
+ const url = normalizeHttpWebhookUrl(params.delivery?.to);
33
+ return url ? { url, source: "delivery" } : null;
34
+ }
35
+ if (params.legacyNotify) {
36
+ const legacyUrl = normalizeHttpWebhookUrl(params.legacyWebhook);
37
+ if (legacyUrl) {
38
+ return { url: legacyUrl, source: "legacy" };
39
+ }
40
+ }
41
+ return null;
42
+ }
14
43
  export function buildGatewayCronService(params) {
15
44
  const cronLogger = getChildLogger({ module: "cron" });
16
45
  const storePath = resolveCronStorePath(params.cfg.cron?.store);
17
- const cronEnabled = process.env.POOLBOT_SKIP_CRON !== "1" &&
18
- process.env.CLAWDBOT_SKIP_CRON !== "1" &&
19
- params.cfg.cron?.enabled !== false;
46
+ const cronEnabled = process.env.POOLBOT_SKIP_CRON !== "1" && params.cfg.cron?.enabled !== false;
20
47
  const resolveCronAgent = (requested) => {
21
48
  const runtimeConfig = loadConfig();
22
49
  const normalized = typeof requested === "string" && requested.trim() ? normalizeAgentId(requested) : undefined;
@@ -26,23 +53,89 @@ export function buildGatewayCronService(params) {
26
53
  const agentId = hasAgent ? normalized : resolveDefaultAgentId(runtimeConfig);
27
54
  return { agentId, cfg: runtimeConfig };
28
55
  };
56
+ const resolveCronSessionKey = (params) => {
57
+ const requested = params.requestedSessionKey?.trim();
58
+ if (!requested) {
59
+ return resolveAgentMainSessionKey({
60
+ cfg: params.runtimeConfig,
61
+ agentId: params.agentId,
62
+ });
63
+ }
64
+ const candidate = toAgentStoreSessionKey({
65
+ agentId: params.agentId,
66
+ requestKey: requested,
67
+ mainKey: params.runtimeConfig.session?.mainKey,
68
+ });
69
+ const canonical = canonicalizeMainSessionAlias({
70
+ cfg: params.runtimeConfig,
71
+ agentId: params.agentId,
72
+ sessionKey: candidate,
73
+ });
74
+ if (canonical !== "global") {
75
+ const sessionAgentId = resolveAgentIdFromSessionKey(canonical);
76
+ if (normalizeAgentId(sessionAgentId) !== normalizeAgentId(params.agentId)) {
77
+ return resolveAgentMainSessionKey({
78
+ cfg: params.runtimeConfig,
79
+ agentId: params.agentId,
80
+ });
81
+ }
82
+ }
83
+ return canonical;
84
+ };
85
+ const resolveCronWakeTarget = (opts) => {
86
+ const runtimeConfig = loadConfig();
87
+ const requestedAgentId = opts?.agentId ? resolveCronAgent(opts.agentId).agentId : undefined;
88
+ const derivedAgentId = requestedAgentId ??
89
+ (opts?.sessionKey
90
+ ? normalizeAgentId(resolveAgentIdFromSessionKey(opts.sessionKey))
91
+ : undefined);
92
+ const agentId = derivedAgentId || undefined;
93
+ const sessionKey = opts?.sessionKey && agentId
94
+ ? resolveCronSessionKey({
95
+ runtimeConfig,
96
+ agentId,
97
+ requestedSessionKey: opts.sessionKey,
98
+ })
99
+ : undefined;
100
+ return { runtimeConfig, agentId, sessionKey };
101
+ };
102
+ const defaultAgentId = resolveDefaultAgentId(params.cfg);
103
+ const resolveSessionStorePath = (agentId) => resolveStorePath(params.cfg.session?.store, {
104
+ agentId: agentId ?? defaultAgentId,
105
+ });
106
+ const sessionStorePath = resolveSessionStorePath(defaultAgentId);
107
+ const warnedLegacyWebhookJobs = new Set();
29
108
  const cron = new CronService({
30
109
  storePath,
31
110
  cronEnabled,
111
+ cronConfig: params.cfg.cron,
112
+ defaultAgentId,
113
+ resolveSessionStorePath,
114
+ sessionStorePath,
32
115
  enqueueSystemEvent: (text, opts) => {
33
116
  const { agentId, cfg: runtimeConfig } = resolveCronAgent(opts?.agentId);
34
- const sessionKey = resolveAgentMainSessionKey({
35
- cfg: runtimeConfig,
117
+ const sessionKey = resolveCronSessionKey({
118
+ runtimeConfig,
119
+ agentId,
120
+ requestedSessionKey: opts?.sessionKey,
121
+ });
122
+ enqueueSystemEvent(text, { sessionKey, contextKey: opts?.contextKey });
123
+ },
124
+ requestHeartbeatNow: (opts) => {
125
+ const { agentId, sessionKey } = resolveCronWakeTarget(opts);
126
+ requestHeartbeatNow({
127
+ reason: opts?.reason,
36
128
  agentId,
129
+ sessionKey,
37
130
  });
38
- enqueueSystemEvent(text, { sessionKey });
39
131
  },
40
- requestHeartbeatNow,
41
132
  runHeartbeatOnce: async (opts) => {
42
- const runtimeConfig = loadConfig();
133
+ const { runtimeConfig, agentId, sessionKey } = resolveCronWakeTarget(opts);
43
134
  return await runHeartbeatOnce({
44
135
  cfg: runtimeConfig,
45
136
  reason: opts?.reason,
137
+ agentId,
138
+ sessionKey,
46
139
  deps: { ...params.deps, runtime: defaultRuntime },
47
140
  });
48
141
  },
@@ -62,6 +155,75 @@ export function buildGatewayCronService(params) {
62
155
  onEvent: (evt) => {
63
156
  params.broadcast("cron", evt, { dropIfSlow: true });
64
157
  if (evt.action === "finished") {
158
+ const webhookToken = params.cfg.cron?.webhookToken?.trim();
159
+ const legacyWebhook = params.cfg.cron?.webhook?.trim();
160
+ const job = cron.getJob(evt.jobId);
161
+ const legacyNotify = job?.notify === true;
162
+ const webhookTarget = resolveCronWebhookTarget({
163
+ delivery: job?.delivery && typeof job.delivery.mode === "string"
164
+ ? { mode: job.delivery.mode, to: job.delivery.to }
165
+ : undefined,
166
+ legacyNotify,
167
+ legacyWebhook,
168
+ });
169
+ if (!webhookTarget && job?.delivery?.mode === "webhook") {
170
+ cronLogger.warn({
171
+ jobId: evt.jobId,
172
+ deliveryTo: job.delivery.to,
173
+ }, "cron: skipped webhook delivery, delivery.to must be a valid http(s) URL");
174
+ }
175
+ if (webhookTarget?.source === "legacy" && !warnedLegacyWebhookJobs.has(evt.jobId)) {
176
+ warnedLegacyWebhookJobs.add(evt.jobId);
177
+ cronLogger.warn({
178
+ jobId: evt.jobId,
179
+ legacyWebhook: redactWebhookUrl(webhookTarget.url),
180
+ }, "cron: deprecated notify+cron.webhook fallback in use, migrate to delivery.mode=webhook with delivery.to");
181
+ }
182
+ if (webhookTarget && evt.summary) {
183
+ const headers = {
184
+ "Content-Type": "application/json",
185
+ };
186
+ if (webhookToken) {
187
+ headers.Authorization = `Bearer ${webhookToken}`;
188
+ }
189
+ const abortController = new AbortController();
190
+ const timeout = setTimeout(() => {
191
+ abortController.abort();
192
+ }, CRON_WEBHOOK_TIMEOUT_MS);
193
+ void (async () => {
194
+ try {
195
+ const result = await fetchWithSsrFGuard({
196
+ url: webhookTarget.url,
197
+ init: {
198
+ method: "POST",
199
+ headers,
200
+ body: JSON.stringify(evt),
201
+ signal: abortController.signal,
202
+ },
203
+ });
204
+ await result.release();
205
+ }
206
+ catch (err) {
207
+ if (err instanceof SsrFBlockedError) {
208
+ cronLogger.warn({
209
+ reason: formatErrorMessage(err),
210
+ jobId: evt.jobId,
211
+ webhookUrl: redactWebhookUrl(webhookTarget.url),
212
+ }, "cron: webhook delivery blocked by SSRF guard");
213
+ }
214
+ else {
215
+ cronLogger.warn({
216
+ err: formatErrorMessage(err),
217
+ jobId: evt.jobId,
218
+ webhookUrl: redactWebhookUrl(webhookTarget.url),
219
+ }, "cron: webhook delivery failed");
220
+ }
221
+ }
222
+ finally {
223
+ clearTimeout(timeout);
224
+ }
225
+ })();
226
+ }
65
227
  const logPath = resolveCronRunLogPath({
66
228
  storePath,
67
229
  jobId: evt.jobId,
@@ -73,9 +235,14 @@ export function buildGatewayCronService(params) {
73
235
  status: evt.status,
74
236
  error: evt.error,
75
237
  summary: evt.summary,
238
+ sessionId: evt.sessionId,
239
+ sessionKey: evt.sessionKey,
76
240
  runAtMs: evt.runAtMs,
77
241
  durationMs: evt.durationMs,
78
242
  nextRunAtMs: evt.nextRunAtMs,
243
+ model: evt.model,
244
+ provider: evt.provider,
245
+ usage: evt.usage,
79
246
  }).catch((err) => {
80
247
  cronLogger.warn({ err: String(err), logPath }, "cron: run log append failed");
81
248
  });
@@ -1,7 +1,14 @@
1
1
  import { onAgentEvent } from "../../infra/agent-events.js";
2
2
  const AGENT_RUN_CACHE_TTL_MS = 10 * 60_000;
3
+ /**
4
+ * Embedded runs can emit transient lifecycle `error` events while auth/model
5
+ * failover is still in progress. Give errors a short grace window so a
6
+ * subsequent `start` event can cancel premature terminal snapshots.
7
+ */
8
+ const AGENT_RUN_ERROR_RETRY_GRACE_MS = 15_000;
3
9
  const agentRunCache = new Map();
4
10
  const agentRunStarts = new Map();
11
+ const pendingAgentRunErrors = new Map();
5
12
  let agentRunListenerStarted = false;
6
13
  function pruneAgentRunCache(now = Date.now()) {
7
14
  for (const [runId, entry] of agentRunCache) {
@@ -14,37 +21,89 @@ function recordAgentRunSnapshot(entry) {
14
21
  pruneAgentRunCache(entry.ts);
15
22
  agentRunCache.set(entry.runId, entry);
16
23
  }
24
+ function clearPendingAgentRunError(runId) {
25
+ const pending = pendingAgentRunErrors.get(runId);
26
+ if (!pending) {
27
+ return;
28
+ }
29
+ clearTimeout(pending.timer);
30
+ pendingAgentRunErrors.delete(runId);
31
+ }
32
+ function schedulePendingAgentRunError(snapshot) {
33
+ clearPendingAgentRunError(snapshot.runId);
34
+ const dueAt = Date.now() + AGENT_RUN_ERROR_RETRY_GRACE_MS;
35
+ const timer = setTimeout(() => {
36
+ const pending = pendingAgentRunErrors.get(snapshot.runId);
37
+ if (!pending) {
38
+ return;
39
+ }
40
+ pendingAgentRunErrors.delete(snapshot.runId);
41
+ recordAgentRunSnapshot(pending.snapshot);
42
+ }, AGENT_RUN_ERROR_RETRY_GRACE_MS);
43
+ timer.unref?.();
44
+ pendingAgentRunErrors.set(snapshot.runId, { snapshot, dueAt, timer });
45
+ }
46
+ function getPendingAgentRunError(runId) {
47
+ const pending = pendingAgentRunErrors.get(runId);
48
+ if (!pending) {
49
+ return undefined;
50
+ }
51
+ return {
52
+ snapshot: pending.snapshot,
53
+ dueAt: pending.dueAt,
54
+ };
55
+ }
56
+ function createSnapshotFromLifecycleEvent(params) {
57
+ const { runId, phase, data } = params;
58
+ const startedAt = typeof data?.startedAt === "number" ? data.startedAt : agentRunStarts.get(runId);
59
+ const endedAt = typeof data?.endedAt === "number" ? data.endedAt : undefined;
60
+ const error = typeof data?.error === "string" ? data.error : undefined;
61
+ return {
62
+ runId,
63
+ status: phase === "error" ? "error" : data?.aborted ? "timeout" : "ok",
64
+ startedAt,
65
+ endedAt,
66
+ error,
67
+ ts: Date.now(),
68
+ };
69
+ }
17
70
  function ensureAgentRunListener() {
18
- if (agentRunListenerStarted)
71
+ if (agentRunListenerStarted) {
19
72
  return;
73
+ }
20
74
  agentRunListenerStarted = true;
21
75
  onAgentEvent((evt) => {
22
- if (!evt)
76
+ if (!evt) {
23
77
  return;
24
- if (evt.stream !== "lifecycle")
78
+ }
79
+ if (evt.stream !== "lifecycle") {
25
80
  return;
81
+ }
26
82
  const phase = evt.data?.phase;
27
83
  if (phase === "start") {
28
84
  const startedAt = typeof evt.data?.startedAt === "number" ? evt.data.startedAt : undefined;
29
85
  agentRunStarts.set(evt.runId, startedAt ?? Date.now());
86
+ clearPendingAgentRunError(evt.runId);
87
+ // A new start means this run is active again (or retried). Drop stale
88
+ // terminal snapshots so waiters don't resolve from old state.
89
+ agentRunCache.delete(evt.runId);
30
90
  return;
31
91
  }
32
- if (phase !== "end" && phase !== "error")
92
+ if (phase !== "end" && phase !== "error") {
33
93
  return;
34
- const startedAt = typeof evt.data?.startedAt === "number"
35
- ? evt.data.startedAt
36
- : agentRunStarts.get(evt.runId);
37
- const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : undefined;
38
- const error = typeof evt.data?.error === "string" ? evt.data.error : undefined;
39
- agentRunStarts.delete(evt.runId);
40
- recordAgentRunSnapshot({
94
+ }
95
+ const snapshot = createSnapshotFromLifecycleEvent({
41
96
  runId: evt.runId,
42
- status: phase === "error" ? "error" : "ok",
43
- startedAt,
44
- endedAt,
45
- error,
46
- ts: Date.now(),
97
+ phase,
98
+ data: evt.data,
47
99
  });
100
+ agentRunStarts.delete(evt.runId);
101
+ if (phase === "error") {
102
+ schedulePendingAgentRunError(snapshot);
103
+ return;
104
+ }
105
+ clearPendingAgentRunError(evt.runId);
106
+ recordAgentRunSnapshot(snapshot);
48
107
  });
49
108
  }
50
109
  function getCachedAgentRun(runId) {
@@ -55,50 +114,84 @@ export async function waitForAgentJob(params) {
55
114
  const { runId, timeoutMs } = params;
56
115
  ensureAgentRunListener();
57
116
  const cached = getCachedAgentRun(runId);
58
- if (cached)
117
+ if (cached) {
59
118
  return cached;
60
- if (timeoutMs <= 0)
119
+ }
120
+ if (timeoutMs <= 0) {
61
121
  return null;
122
+ }
62
123
  return await new Promise((resolve) => {
63
124
  let settled = false;
125
+ let pendingErrorTimer;
126
+ const clearPendingErrorTimer = () => {
127
+ if (!pendingErrorTimer) {
128
+ return;
129
+ }
130
+ clearTimeout(pendingErrorTimer);
131
+ pendingErrorTimer = undefined;
132
+ };
64
133
  const finish = (entry) => {
65
- if (settled)
134
+ if (settled) {
66
135
  return;
136
+ }
67
137
  settled = true;
68
138
  clearTimeout(timer);
139
+ clearPendingErrorTimer();
69
140
  unsubscribe();
70
141
  resolve(entry);
71
142
  };
143
+ const scheduleErrorFinish = (snapshot, delayMs = AGENT_RUN_ERROR_RETRY_GRACE_MS) => {
144
+ clearPendingErrorTimer();
145
+ const effectiveDelay = Math.max(1, Math.min(Math.floor(delayMs), 2_147_483_647));
146
+ pendingErrorTimer = setTimeout(() => {
147
+ const latest = getCachedAgentRun(runId);
148
+ if (latest) {
149
+ finish(latest);
150
+ return;
151
+ }
152
+ recordAgentRunSnapshot(snapshot);
153
+ finish(snapshot);
154
+ }, effectiveDelay);
155
+ pendingErrorTimer.unref?.();
156
+ };
157
+ const pending = getPendingAgentRunError(runId);
158
+ if (pending) {
159
+ scheduleErrorFinish(pending.snapshot, pending.dueAt - Date.now());
160
+ }
72
161
  const unsubscribe = onAgentEvent((evt) => {
73
- if (!evt || evt.stream !== "lifecycle")
162
+ if (!evt || evt.stream !== "lifecycle") {
74
163
  return;
75
- if (evt.runId !== runId)
164
+ }
165
+ if (evt.runId !== runId) {
76
166
  return;
167
+ }
77
168
  const phase = evt.data?.phase;
78
- if (phase !== "end" && phase !== "error")
169
+ if (phase === "start") {
170
+ clearPendingErrorTimer();
171
+ return;
172
+ }
173
+ if (phase !== "end" && phase !== "error") {
79
174
  return;
80
- const cached = getCachedAgentRun(runId);
81
- if (cached) {
82
- finish(cached);
175
+ }
176
+ const latest = getCachedAgentRun(runId);
177
+ if (latest) {
178
+ finish(latest);
83
179
  return;
84
180
  }
85
- const startedAt = typeof evt.data?.startedAt === "number"
86
- ? evt.data.startedAt
87
- : agentRunStarts.get(evt.runId);
88
- const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : undefined;
89
- const error = typeof evt.data?.error === "string" ? evt.data.error : undefined;
90
- const snapshot = {
181
+ const snapshot = createSnapshotFromLifecycleEvent({
91
182
  runId: evt.runId,
92
- status: phase === "error" ? "error" : "ok",
93
- startedAt,
94
- endedAt,
95
- error,
96
- ts: Date.now(),
97
- };
183
+ phase,
184
+ data: evt.data,
185
+ });
186
+ if (phase === "error") {
187
+ scheduleErrorFinish(snapshot);
188
+ return;
189
+ }
98
190
  recordAgentRunSnapshot(snapshot);
99
191
  finish(snapshot);
100
192
  });
101
- const timer = setTimeout(() => finish(null), Math.max(1, timeoutMs));
193
+ const timerDelayMs = Math.max(1, Math.min(Math.floor(timeoutMs), 2_147_483_647));
194
+ const timer = setTimeout(() => finish(null), timerDelayMs);
102
195
  });
103
196
  }
104
197
  ensureAgentRunListener();