@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
@@ -68,6 +68,9 @@ export async function readCronRunLogEntries(filePath, opts) {
68
68
  if (jobId && obj.jobId !== jobId) {
69
69
  continue;
70
70
  }
71
+ const usage = obj.usage && typeof obj.usage === "object"
72
+ ? obj.usage
73
+ : undefined;
71
74
  const entry = {
72
75
  ts: obj.ts,
73
76
  jobId: obj.jobId,
@@ -78,6 +81,17 @@ export async function readCronRunLogEntries(filePath, opts) {
78
81
  runAtMs: obj.runAtMs,
79
82
  durationMs: obj.durationMs,
80
83
  nextRunAtMs: obj.nextRunAtMs,
84
+ model: typeof obj.model === "string" && obj.model.trim() ? obj.model : undefined,
85
+ provider: typeof obj.provider === "string" && obj.provider.trim() ? obj.provider : undefined,
86
+ usage: usage
87
+ ? {
88
+ input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : undefined,
89
+ output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : undefined,
90
+ total_tokens: typeof usage.total_tokens === "number" ? usage.total_tokens : undefined,
91
+ cache_read_tokens: typeof usage.cache_read_tokens === "number" ? usage.cache_read_tokens : undefined,
92
+ cache_write_tokens: typeof usage.cache_write_tokens === "number" ? usage.cache_write_tokens : undefined,
93
+ }
94
+ : undefined,
81
95
  };
82
96
  if (typeof obj.sessionId === "string" && obj.sessionId.trim().length > 0) {
83
97
  entry.sessionId = obj.sessionId;
@@ -1,8 +1,42 @@
1
1
  import crypto from "node:crypto";
2
2
  import { parseAbsoluteTimeMs } from "../parse.js";
3
3
  import { computeNextRunAtMs } from "../schedule.js";
4
- import { normalizeOptionalAgentId, normalizeOptionalText, normalizePayloadToSystemText, normalizeRequiredName, } from "./normalize.js";
4
+ import { normalizeCronStaggerMs, resolveCronStaggerMs, resolveDefaultCronStaggerMs, } from "../stagger.js";
5
+ import { normalizeHttpWebhookUrl } from "../webhook-url.js";
6
+ import { normalizeOptionalAgentId, normalizeOptionalSessionKey, normalizeOptionalText, normalizePayloadToSystemText, normalizeRequiredName, } from "./normalize.js";
5
7
  const STUCK_RUN_MS = 2 * 60 * 60 * 1000;
8
+ function resolveStableCronOffsetMs(jobId, staggerMs) {
9
+ if (staggerMs <= 1) {
10
+ return 0;
11
+ }
12
+ const digest = crypto.createHash("sha256").update(jobId).digest();
13
+ return digest.readUInt32BE(0) % staggerMs;
14
+ }
15
+ function computeStaggeredCronNextRunAtMs(job, nowMs) {
16
+ if (job.schedule.kind !== "cron") {
17
+ return computeNextRunAtMs(job.schedule, nowMs);
18
+ }
19
+ const staggerMs = resolveCronStaggerMs(job.schedule);
20
+ const offsetMs = resolveStableCronOffsetMs(job.id, staggerMs);
21
+ if (offsetMs <= 0) {
22
+ return computeNextRunAtMs(job.schedule, nowMs);
23
+ }
24
+ // Shift the schedule cursor backwards by the per-job offset so we can still
25
+ // target the current schedule window if its staggered slot has not passed yet.
26
+ let cursorMs = Math.max(0, nowMs - offsetMs);
27
+ for (let attempt = 0; attempt < 4; attempt += 1) {
28
+ const baseNext = computeNextRunAtMs(job.schedule, cursorMs);
29
+ if (baseNext === undefined) {
30
+ return undefined;
31
+ }
32
+ const shifted = baseNext + offsetMs;
33
+ if (shifted > nowMs) {
34
+ return shifted;
35
+ }
36
+ cursorMs = Math.max(cursorMs + 1, baseNext + 1_000);
37
+ }
38
+ return undefined;
39
+ }
6
40
  function resolveEveryAnchorMs(params) {
7
41
  const raw = params.schedule.anchorMs;
8
42
  if (typeof raw === "number" && Number.isFinite(raw)) {
@@ -19,8 +53,19 @@ export function assertSupportedJobSpec(job) {
19
53
  }
20
54
  }
21
55
  function assertDeliverySupport(job) {
22
- if (job.delivery && job.sessionTarget !== "isolated") {
23
- throw new Error('cron delivery config is only supported for sessionTarget="isolated"');
56
+ if (!job.delivery) {
57
+ return;
58
+ }
59
+ if (job.delivery.mode === "webhook") {
60
+ const target = normalizeHttpWebhookUrl(job.delivery.to);
61
+ if (!target) {
62
+ throw new Error("cron webhook delivery requires delivery.to to be a valid http(s) URL");
63
+ }
64
+ job.delivery.to = target;
65
+ return;
66
+ }
67
+ if (job.sessionTarget !== "isolated") {
68
+ throw new Error('cron channel delivery config is only supported for sessionTarget="isolated"');
24
69
  }
25
70
  }
26
71
  export function findJobOrThrow(state, id) {
@@ -59,50 +104,134 @@ export function computeJobNextRunAtMs(job, nowMs) {
59
104
  : null;
60
105
  return atMs !== null ? atMs : undefined;
61
106
  }
62
- return computeNextRunAtMs(job.schedule, nowMs);
107
+ const next = computeStaggeredCronNextRunAtMs(job, nowMs);
108
+ if (next === undefined && job.schedule.kind === "cron") {
109
+ const nextSecondMs = Math.floor(nowMs / 1000) * 1000 + 1000;
110
+ return computeStaggeredCronNextRunAtMs(job, nextSecondMs);
111
+ }
112
+ return next;
63
113
  }
64
- export function recomputeNextRuns(state) {
114
+ /** Maximum consecutive schedule errors before auto-disabling a job. */
115
+ const MAX_SCHEDULE_ERRORS = 3;
116
+ function recordScheduleComputeError(params) {
117
+ const { state, job, err } = params;
118
+ const errorCount = (job.state.scheduleErrorCount ?? 0) + 1;
119
+ const errText = String(err);
120
+ job.state.scheduleErrorCount = errorCount;
121
+ job.state.nextRunAtMs = undefined;
122
+ job.state.lastError = `schedule error: ${errText}`;
123
+ if (errorCount >= MAX_SCHEDULE_ERRORS) {
124
+ job.enabled = false;
125
+ state.deps.log.error({ jobId: job.id, name: job.name, errorCount, err: errText }, "cron: auto-disabled job after repeated schedule errors");
126
+ }
127
+ else {
128
+ state.deps.log.warn({ jobId: job.id, name: job.name, errorCount, err: errText }, "cron: failed to compute next run for job (skipping)");
129
+ }
130
+ return true;
131
+ }
132
+ function normalizeJobTickState(params) {
133
+ const { state, job, nowMs } = params;
134
+ let changed = false;
135
+ if (!job.state) {
136
+ job.state = {};
137
+ changed = true;
138
+ }
139
+ if (!job.enabled) {
140
+ if (job.state.nextRunAtMs !== undefined) {
141
+ job.state.nextRunAtMs = undefined;
142
+ changed = true;
143
+ }
144
+ if (job.state.runningAtMs !== undefined) {
145
+ job.state.runningAtMs = undefined;
146
+ changed = true;
147
+ }
148
+ return { changed, skip: true };
149
+ }
150
+ const runningAt = job.state.runningAtMs;
151
+ if (typeof runningAt === "number" && nowMs - runningAt > STUCK_RUN_MS) {
152
+ state.deps.log.warn({ jobId: job.id, runningAtMs: runningAt }, "cron: clearing stuck running marker");
153
+ job.state.runningAtMs = undefined;
154
+ changed = true;
155
+ }
156
+ return { changed, skip: false };
157
+ }
158
+ function walkSchedulableJobs(state, fn) {
65
159
  if (!state.store) {
66
160
  return false;
67
161
  }
68
162
  let changed = false;
69
163
  const now = state.deps.nowMs();
70
164
  for (const job of state.store.jobs) {
71
- if (!job.state) {
72
- job.state = {};
165
+ const tick = normalizeJobTickState({ state, job, nowMs: now });
166
+ if (tick.changed) {
73
167
  changed = true;
74
168
  }
75
- if (!job.enabled) {
76
- if (job.state.nextRunAtMs !== undefined) {
77
- job.state.nextRunAtMs = undefined;
78
- changed = true;
79
- }
80
- if (job.state.runningAtMs !== undefined) {
81
- job.state.runningAtMs = undefined;
82
- changed = true;
83
- }
169
+ if (tick.skip) {
84
170
  continue;
85
171
  }
86
- const runningAt = job.state.runningAtMs;
87
- if (typeof runningAt === "number" && now - runningAt > STUCK_RUN_MS) {
88
- state.deps.log.warn({ jobId: job.id, runningAtMs: runningAt }, "cron: clearing stuck running marker");
89
- job.state.runningAtMs = undefined;
172
+ if (fn({ job, nowMs: now })) {
173
+ changed = true;
174
+ }
175
+ }
176
+ return changed;
177
+ }
178
+ function recomputeJobNextRunAtMs(params) {
179
+ let changed = false;
180
+ try {
181
+ const newNext = computeJobNextRunAtMs(params.job, params.nowMs);
182
+ if (params.job.state.nextRunAtMs !== newNext) {
183
+ params.job.state.nextRunAtMs = newNext;
90
184
  changed = true;
91
185
  }
186
+ // Clear schedule error count on successful computation.
187
+ if (params.job.state.scheduleErrorCount) {
188
+ params.job.state.scheduleErrorCount = undefined;
189
+ changed = true;
190
+ }
191
+ }
192
+ catch (err) {
193
+ if (recordScheduleComputeError({ state: params.state, job: params.job, err })) {
194
+ changed = true;
195
+ }
196
+ }
197
+ return changed;
198
+ }
199
+ export function recomputeNextRuns(state) {
200
+ return walkSchedulableJobs(state, ({ job, nowMs: now }) => {
201
+ let changed = false;
92
202
  // Only recompute if nextRunAtMs is missing or already past-due.
93
203
  // Preserving a still-future nextRunAtMs avoids accidentally advancing
94
204
  // a job that hasn't fired yet (e.g. during restart recovery).
95
205
  const nextRun = job.state.nextRunAtMs;
96
206
  const isDueOrMissing = nextRun === undefined || now >= nextRun;
97
207
  if (isDueOrMissing) {
98
- const newNext = computeJobNextRunAtMs(job, now);
99
- if (job.state.nextRunAtMs !== newNext) {
100
- job.state.nextRunAtMs = newNext;
208
+ if (recomputeJobNextRunAtMs({ state, job, nowMs: now })) {
101
209
  changed = true;
102
210
  }
103
211
  }
104
- }
105
- return changed;
212
+ return changed;
213
+ });
214
+ }
215
+ /**
216
+ * Maintenance-only version of recomputeNextRuns that handles disabled jobs
217
+ * and stuck markers, but does NOT recompute nextRunAtMs for enabled jobs
218
+ * with existing values. Used during timer ticks when no due jobs were found
219
+ * to prevent silently advancing past-due nextRunAtMs values without execution
220
+ * (see #13992).
221
+ */
222
+ export function recomputeNextRunsForMaintenance(state) {
223
+ return walkSchedulableJobs(state, ({ job, nowMs: now }) => {
224
+ let changed = false;
225
+ // Only compute missing nextRunAtMs, do NOT recompute existing ones.
226
+ // If a job was past-due but not found by findDueJobs, recomputing would
227
+ // cause it to be silently skipped.
228
+ if (job.state.nextRunAtMs === undefined) {
229
+ if (recomputeJobNextRunAtMs({ state, job, nowMs: now })) {
230
+ changed = true;
231
+ }
232
+ }
233
+ return changed;
234
+ });
106
235
  }
107
236
  export function nextWakeAtMs(state) {
108
237
  const jobs = state.store?.jobs ?? [];
@@ -123,7 +252,18 @@ export function createJob(state, input) {
123
252
  fallbackAnchorMs: now,
124
253
  }),
125
254
  }
126
- : input.schedule;
255
+ : input.schedule.kind === "cron"
256
+ ? (() => {
257
+ const explicitStaggerMs = normalizeCronStaggerMs(input.schedule.staggerMs);
258
+ if (explicitStaggerMs !== undefined) {
259
+ return { ...input.schedule, staggerMs: explicitStaggerMs };
260
+ }
261
+ const defaultStaggerMs = resolveDefaultCronStaggerMs(input.schedule.expr);
262
+ return defaultStaggerMs !== undefined
263
+ ? { ...input.schedule, staggerMs: defaultStaggerMs }
264
+ : input.schedule;
265
+ })()
266
+ : input.schedule;
127
267
  const deleteAfterRun = typeof input.deleteAfterRun === "boolean"
128
268
  ? input.deleteAfterRun
129
269
  : schedule.kind === "at"
@@ -133,6 +273,7 @@ export function createJob(state, input) {
133
273
  const job = {
134
274
  id,
135
275
  agentId: normalizeOptionalAgentId(input.agentId),
276
+ sessionKey: normalizeOptionalSessionKey(input.sessionKey),
136
277
  name: normalizeRequiredName(input.name),
137
278
  description: normalizeOptionalText(input.description),
138
279
  enabled,
@@ -167,7 +308,25 @@ export function applyJobPatch(job, patch) {
167
308
  job.deleteAfterRun = patch.deleteAfterRun;
168
309
  }
169
310
  if (patch.schedule) {
170
- job.schedule = patch.schedule;
311
+ if (patch.schedule.kind === "cron") {
312
+ const explicitStaggerMs = normalizeCronStaggerMs(patch.schedule.staggerMs);
313
+ if (explicitStaggerMs !== undefined) {
314
+ job.schedule = { ...patch.schedule, staggerMs: explicitStaggerMs };
315
+ }
316
+ else if (job.schedule.kind === "cron") {
317
+ job.schedule = { ...patch.schedule, staggerMs: job.schedule.staggerMs };
318
+ }
319
+ else {
320
+ const defaultStaggerMs = resolveDefaultCronStaggerMs(patch.schedule.expr);
321
+ job.schedule =
322
+ defaultStaggerMs !== undefined
323
+ ? { ...patch.schedule, staggerMs: defaultStaggerMs }
324
+ : patch.schedule;
325
+ }
326
+ }
327
+ else {
328
+ job.schedule = patch.schedule;
329
+ }
171
330
  }
172
331
  if (patch.sessionTarget) {
173
332
  job.sessionTarget = patch.sessionTarget;
@@ -190,7 +349,7 @@ export function applyJobPatch(job, patch) {
190
349
  if (patch.delivery) {
191
350
  job.delivery = mergeCronDelivery(job.delivery, patch.delivery);
192
351
  }
193
- if (job.sessionTarget === "main" && job.delivery) {
352
+ if (job.sessionTarget === "main" && job.delivery?.mode !== "webhook") {
194
353
  job.delivery = undefined;
195
354
  }
196
355
  if (patch.state) {
@@ -199,6 +358,9 @@ export function applyJobPatch(job, patch) {
199
358
  if ("agentId" in patch) {
200
359
  job.agentId = normalizeOptionalAgentId(patch.agentId);
201
360
  }
361
+ if ("sessionKey" in patch) {
362
+ job.sessionKey = normalizeOptionalSessionKey(patch.sessionKey);
363
+ }
202
364
  assertSupportedJobSpec(job);
203
365
  assertDeliverySupport(job);
204
366
  }
@@ -1,32 +1,45 @@
1
1
  import { normalizeAgentId } from "../../routing/session-key.js";
2
2
  import { truncateUtf16Safe } from "../../utils.js";
3
3
  export function normalizeRequiredName(raw) {
4
- if (typeof raw !== "string")
4
+ if (typeof raw !== "string") {
5
5
  throw new Error("cron job name is required");
6
+ }
6
7
  const name = raw.trim();
7
- if (!name)
8
+ if (!name) {
8
9
  throw new Error("cron job name is required");
10
+ }
9
11
  return name;
10
12
  }
11
13
  export function normalizeOptionalText(raw) {
12
- if (typeof raw !== "string")
14
+ if (typeof raw !== "string") {
13
15
  return undefined;
16
+ }
14
17
  const trimmed = raw.trim();
15
18
  return trimmed ? trimmed : undefined;
16
19
  }
17
20
  function truncateText(input, maxLen) {
18
- if (input.length <= maxLen)
21
+ if (input.length <= maxLen) {
19
22
  return input;
23
+ }
20
24
  return `${truncateUtf16Safe(input, Math.max(0, maxLen - 1)).trimEnd()}…`;
21
25
  }
22
26
  export function normalizeOptionalAgentId(raw) {
23
- if (typeof raw !== "string")
27
+ if (typeof raw !== "string") {
24
28
  return undefined;
29
+ }
25
30
  const trimmed = raw.trim();
26
- if (!trimmed)
31
+ if (!trimmed) {
27
32
  return undefined;
33
+ }
28
34
  return normalizeAgentId(trimmed);
29
35
  }
36
+ export function normalizeOptionalSessionKey(raw) {
37
+ if (typeof raw !== "string") {
38
+ return undefined;
39
+ }
40
+ const trimmed = raw.trim();
41
+ return trimmed || undefined;
42
+ }
30
43
  export function inferLegacyName(job) {
31
44
  const text = job?.payload?.kind === "systemEvent" && typeof job.payload.text === "string"
32
45
  ? job.payload.text
@@ -37,19 +50,24 @@ export function inferLegacyName(job) {
37
50
  .split("\n")
38
51
  .map((l) => l.trim())
39
52
  .find(Boolean) ?? "";
40
- if (firstLine)
53
+ if (firstLine) {
41
54
  return truncateText(firstLine, 60);
55
+ }
42
56
  const kind = typeof job?.schedule?.kind === "string" ? job.schedule.kind : "";
43
- if (kind === "cron" && typeof job?.schedule?.expr === "string")
57
+ if (kind === "cron" && typeof job?.schedule?.expr === "string") {
44
58
  return `Cron: ${truncateText(job.schedule.expr, 52)}`;
45
- if (kind === "every" && typeof job?.schedule?.everyMs === "number")
59
+ }
60
+ if (kind === "every" && typeof job?.schedule?.everyMs === "number") {
46
61
  return `Every: ${job.schedule.everyMs}ms`;
47
- if (kind === "at")
62
+ }
63
+ if (kind === "at") {
48
64
  return "One-shot";
65
+ }
49
66
  return "Cron job";
50
67
  }
51
68
  export function normalizePayloadToSystemText(payload) {
52
- if (payload.kind === "systemEvent")
69
+ if (payload.kind === "systemEvent") {
53
70
  return payload.text.trim();
71
+ }
54
72
  return payload.message.trim();
55
73
  }
@@ -1,38 +1,11 @@
1
1
  import fs from "node:fs";
2
+ import { buildDeliveryFromLegacyPayload, hasLegacyDeliveryHints, stripLegacyDeliveryFields, } from "../legacy-delivery.js";
2
3
  import { parseAbsoluteTimeMs } from "../parse.js";
3
4
  import { migrateLegacyCronPayload } from "../payload-migration.js";
5
+ import { normalizeCronStaggerMs, resolveDefaultCronStaggerMs } from "../stagger.js";
4
6
  import { loadCronStore, saveCronStore } from "../store.js";
5
7
  import { recomputeNextRuns } from "./jobs.js";
6
8
  import { inferLegacyName, normalizeOptionalText } from "./normalize.js";
7
- function hasLegacyDeliveryHints(payload) {
8
- if (typeof payload.deliver === "boolean") {
9
- return true;
10
- }
11
- if (typeof payload.bestEffortDeliver === "boolean") {
12
- return true;
13
- }
14
- if (typeof payload.to === "string" && payload.to.trim()) {
15
- return true;
16
- }
17
- return false;
18
- }
19
- function buildDeliveryFromLegacyPayload(payload) {
20
- const deliver = payload.deliver;
21
- const mode = deliver === false ? "none" : "announce";
22
- const channelRaw = typeof payload.channel === "string" ? payload.channel.trim().toLowerCase() : "";
23
- const toRaw = typeof payload.to === "string" ? payload.to.trim() : "";
24
- const next = { mode };
25
- if (channelRaw) {
26
- next.channel = channelRaw;
27
- }
28
- if (toRaw) {
29
- next.to = toRaw;
30
- }
31
- if (typeof payload.bestEffortDeliver === "boolean") {
32
- next.bestEffort = payload.bestEffortDeliver;
33
- }
34
- return next;
35
- }
36
9
  function buildDeliveryPatchFromLegacyPayload(payload) {
37
10
  const deliver = payload.deliver;
38
11
  const channelRaw = typeof payload.channel === "string" ? payload.channel.trim().toLowerCase() : "";
@@ -86,20 +59,6 @@ function mergeLegacyDeliveryInto(delivery, payload) {
86
59
  }
87
60
  return { delivery: next, mutated };
88
61
  }
89
- function stripLegacyDeliveryFields(payload) {
90
- if ("deliver" in payload) {
91
- delete payload.deliver;
92
- }
93
- if ("channel" in payload) {
94
- delete payload.channel;
95
- }
96
- if ("to" in payload) {
97
- delete payload.to;
98
- }
99
- if ("bestEffortDeliver" in payload) {
100
- delete payload.bestEffortDeliver;
101
- }
102
- }
103
62
  function normalizePayloadKind(payload) {
104
63
  const raw = typeof payload.kind === "string" ? payload.kind.trim().toLowerCase() : "";
105
64
  if (raw === "agentturn") {
@@ -143,7 +102,7 @@ function copyTopLevelAgentTurnFields(raw, payload) {
143
102
  if (typeof payload.timeoutSeconds !== "number" &&
144
103
  typeof raw.timeoutSeconds === "number" &&
145
104
  Number.isFinite(raw.timeoutSeconds)) {
146
- payload.timeoutSeconds = Math.max(1, Math.floor(raw.timeoutSeconds));
105
+ payload.timeoutSeconds = Math.max(0, Math.floor(raw.timeoutSeconds));
147
106
  mutated = true;
148
107
  }
149
108
  if (typeof payload.allowUnsafeExternalContent !== "boolean" &&
@@ -256,6 +215,13 @@ export async function ensureLoaded(state, opts) {
256
215
  raw.description = desc;
257
216
  mutated = true;
258
217
  }
218
+ if ("sessionKey" in raw) {
219
+ const sessionKey = typeof raw.sessionKey === "string" ? normalizeOptionalText(raw.sessionKey) : undefined;
220
+ if (raw.sessionKey !== sessionKey) {
221
+ raw.sessionKey = sessionKey;
222
+ mutated = true;
223
+ }
224
+ }
259
225
  if (typeof raw.enabled !== "boolean") {
260
226
  raw.enabled = true;
261
227
  mutated = true;
@@ -350,6 +316,26 @@ export async function ensureLoaded(state, opts) {
350
316
  mutated = true;
351
317
  }
352
318
  }
319
+ const exprRaw = typeof sched.expr === "string" ? sched.expr.trim() : "";
320
+ if (typeof sched.expr === "string" && sched.expr !== exprRaw) {
321
+ sched.expr = exprRaw;
322
+ mutated = true;
323
+ }
324
+ if ((kind === "cron" || sched.kind === "cron") && exprRaw) {
325
+ const explicitStaggerMs = normalizeCronStaggerMs(sched.staggerMs);
326
+ const defaultStaggerMs = resolveDefaultCronStaggerMs(exprRaw);
327
+ const targetStaggerMs = explicitStaggerMs ?? defaultStaggerMs;
328
+ if (targetStaggerMs === undefined) {
329
+ if ("staggerMs" in sched) {
330
+ delete sched.staggerMs;
331
+ mutated = true;
332
+ }
333
+ }
334
+ else if (sched.staggerMs !== targetStaggerMs) {
335
+ sched.staggerMs = targetStaggerMs;
336
+ mutated = true;
337
+ }
338
+ }
353
339
  }
354
340
  const delivery = raw.delivery;
355
341
  if (delivery && typeof delivery === "object" && !Array.isArray(delivery)) {