@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
@@ -2,24 +2,25 @@ import { resolveAgentConfig, resolveAgentDir, resolveAgentModelFallbacksOverride
2
2
  import { runCliAgent } from "../../agents/cli-runner.js";
3
3
  import { getCliSessionId, setCliSessionId } from "../../agents/cli-session.js";
4
4
  import { lookupContextTokens } from "../../agents/context.js";
5
- import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone, } from "../../agents/date-time.js";
5
+ import { resolveCronStyleNow } from "../../agents/current-time.js";
6
6
  import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
7
7
  import { loadModelCatalog } from "../../agents/model-catalog.js";
8
8
  import { runWithModelFallback } from "../../agents/model-fallback.js";
9
9
  import { getModelRefStatus, isCliProvider, resolveAllowedModelRef, resolveConfiguredModelRef, resolveHooksGmailModel, resolveThinkingDefault, } from "../../agents/model-selection.js";
10
10
  import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
11
- import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
12
- import { getSkillsSnapshotVersion } from "../../agents/skills/refresh.js";
13
11
  import { runSubagentAnnounceFlow } from "../../agents/subagent-announce.js";
12
+ import { countActiveDescendantRuns } from "../../agents/subagent-registry.js";
14
13
  import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
15
14
  import { deriveSessionTotalTokens, hasNonzeroUsage } from "../../agents/usage.js";
16
15
  import { ensureAgentWorkspace } from "../../agents/workspace.js";
17
16
  import { normalizeThinkLevel, normalizeVerboseLevel, supportsXHighThinking, } from "../../auto-reply/thinking.js";
17
+ import { SILENT_REPLY_TOKEN } from "../../auto-reply/tokens.js";
18
18
  import { createOutboundSendDeps } from "../../cli/outbound-send-deps.js";
19
19
  import { resolveAgentMainSessionKey, resolveSessionTranscriptPath, updateSessionStore, } from "../../config/sessions.js";
20
20
  import { registerAgentRunContext } from "../../infra/agent-events.js";
21
21
  import { deliverOutboundPayloads } from "../../infra/outbound/deliver.js";
22
- import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
22
+ import { resolveAgentOutboundIdentity } from "../../infra/outbound/identity.js";
23
+ import { resolveOutboundSessionRoute } from "../../infra/outbound/outbound-session.js";
23
24
  import { logWarn } from "../../logger.js";
24
25
  import { buildAgentMainSessionKey, normalizeAgentId } from "../../routing/session-key.js";
25
26
  import { buildSafeExternalPrompt, detectSuspiciousPatterns, getHookType, isExternalHookSession, } from "../../security/external-content.js";
@@ -27,6 +28,8 @@ import { resolveCronDeliveryPlan } from "../delivery.js";
27
28
  import { resolveDeliveryTarget } from "./delivery-target.js";
28
29
  import { isHeartbeatOnlyResponse, pickLastDeliverablePayload, pickLastNonEmptyTextFromPayloads, pickSummaryFromOutput, pickSummaryFromPayloads, resolveHeartbeatAckMaxChars, } from "./helpers.js";
29
30
  import { resolveCronSession } from "./session.js";
31
+ import { resolveCronSkillsSnapshot } from "./skills-snapshot.js";
32
+ import { expectsSubagentFollowup, isLikelyInterimCronMessage, readDescendantSubagentFallbackReply, waitForDescendantSubagentSummary, } from "./subagent-followup.js";
30
33
  function matchesMessagingToolDeliveryTarget(target, delivery) {
31
34
  if (!delivery.to || !target.to) {
32
35
  return false;
@@ -50,7 +53,32 @@ function resolveCronDeliveryBestEffort(job) {
50
53
  }
51
54
  return false;
52
55
  }
56
+ async function resolveCronAnnounceSessionKey(params) {
57
+ const to = params.delivery.to?.trim();
58
+ if (!to) {
59
+ return params.fallbackSessionKey;
60
+ }
61
+ try {
62
+ const route = await resolveOutboundSessionRoute({
63
+ cfg: params.cfg,
64
+ channel: params.delivery.channel,
65
+ agentId: params.agentId,
66
+ accountId: params.delivery.accountId,
67
+ target: to,
68
+ threadId: params.delivery.threadId,
69
+ });
70
+ const resolved = route?.sessionKey?.trim();
71
+ if (resolved) {
72
+ return resolved;
73
+ }
74
+ }
75
+ catch {
76
+ // Fall back to main session routing if announce session resolution fails.
77
+ }
78
+ return params.fallbackSessionKey;
79
+ }
53
80
  export async function runCronIsolatedAgentTurn(params) {
81
+ const isFastTestEnv = process.env.POOLBOT_TEST_FAST === "1";
54
82
  const defaultAgentId = resolveDefaultAgentId(params.cfg);
55
83
  const requestedAgentId = typeof params.agentId === "string" && params.agentId.trim()
56
84
  ? params.agentId
@@ -62,13 +90,20 @@ export async function runCronIsolatedAgentTurn(params) {
62
90
  ? resolveAgentConfig(params.cfg, normalizedRequested)
63
91
  : undefined;
64
92
  const { model: overrideModel, ...agentOverrideRest } = agentConfigOverride ?? {};
65
- const agentId = agentConfigOverride ? (normalizedRequested ?? defaultAgentId) : defaultAgentId;
93
+ // Use the requested agentId even when there is no explicit agent config entry.
94
+ // This ensures auth-profiles, workspace, and agentDir all resolve to the
95
+ // correct per-agent paths (e.g. ~/.poolbot/agents/<agentId>/agent/).
96
+ const agentId = normalizedRequested ?? defaultAgentId;
66
97
  const agentCfg = Object.assign({}, params.cfg.agents?.defaults, agentOverrideRest);
98
+ // Merge agent model override with defaults instead of replacing, so that
99
+ // `fallbacks` from `agents.defaults.model` are preserved when the agent
100
+ // (or its per-cron model pin) only specifies `primary`.
101
+ const existingModel = agentCfg.model && typeof agentCfg.model === "object" ? agentCfg.model : {};
67
102
  if (typeof overrideModel === "string") {
68
- agentCfg.model = { primary: overrideModel };
103
+ agentCfg.model = { ...existingModel, primary: overrideModel };
69
104
  }
70
105
  else if (overrideModel) {
71
- agentCfg.model = overrideModel;
106
+ agentCfg.model = { ...existingModel, ...overrideModel };
72
107
  }
73
108
  const cfgWithAgentDefaults = {
74
109
  ...params.cfg,
@@ -83,7 +118,7 @@ export async function runCronIsolatedAgentTurn(params) {
83
118
  const agentDir = resolveAgentDir(params.cfg, agentId);
84
119
  const workspace = await ensureAgentWorkspace({
85
120
  dir: workspaceDirRaw,
86
- ensureBootstrapFiles: !agentCfg?.skipBootstrap,
121
+ ensureBootstrapFiles: !agentCfg?.skipBootstrap && !isFastTestEnv,
87
122
  });
88
123
  const workspaceDir = workspace.dir;
89
124
  const resolvedDefault = resolveConfiguredModelRef({
@@ -102,6 +137,7 @@ export async function runCronIsolatedAgentTurn(params) {
102
137
  };
103
138
  // Resolve model - prefer hooks.gmail.model for Gmail hooks.
104
139
  const isGmailHook = baseSessionKey.startsWith("hook:gmail:");
140
+ let hooksGmailModelApplied = false;
105
141
  const hooksGmailModelRef = isGmailHook
106
142
  ? resolveHooksGmailModel({
107
143
  cfg: params.cfg,
@@ -119,6 +155,7 @@ export async function runCronIsolatedAgentTurn(params) {
119
155
  if (status.allowed) {
120
156
  provider = hooksGmailModelRef.provider;
121
157
  model = hooksGmailModelRef.model;
158
+ hooksGmailModelApplied = true;
122
159
  }
123
160
  }
124
161
  const modelOverrideRaw = params.job.payload.kind === "agentTurn" ? params.job.payload.model : undefined;
@@ -149,6 +186,9 @@ export async function runCronIsolatedAgentTurn(params) {
149
186
  ? `${agentSessionKey}:run:${runSessionId}`
150
187
  : agentSessionKey;
151
188
  const persistSessionEntry = async () => {
189
+ if (isFastTestEnv) {
190
+ return;
191
+ }
152
192
  cronSession.store[agentSessionKey] = cronSession.sessionEntry;
153
193
  if (runSessionKey !== agentSessionKey) {
154
194
  cronSession.store[runSessionKey] = cronSession.sessionEntry;
@@ -171,6 +211,26 @@ export async function runCronIsolatedAgentTurn(params) {
171
211
  : params.job.id;
172
212
  cronSession.sessionEntry.label = `Cron: ${labelSuffix}`;
173
213
  }
214
+ // Respect session model override — check session.modelOverride before falling
215
+ // back to the default config model. This ensures /model changes are honoured
216
+ // by cron and isolated agent runs.
217
+ if (!modelOverride && !hooksGmailModelApplied) {
218
+ const sessionModelOverride = cronSession.sessionEntry.modelOverride?.trim();
219
+ if (sessionModelOverride) {
220
+ const sessionProviderOverride = cronSession.sessionEntry.providerOverride?.trim() || resolvedDefault.provider;
221
+ const resolvedSessionOverride = resolveAllowedModelRef({
222
+ cfg: cfgWithAgentDefaults,
223
+ catalog: await loadCatalog(),
224
+ raw: `${sessionProviderOverride}/${sessionModelOverride}`,
225
+ defaultProvider: resolvedDefault.provider,
226
+ defaultModel: resolvedDefault.model,
227
+ });
228
+ if (!("error" in resolvedSessionOverride)) {
229
+ provider = resolvedSessionOverride.ref.provider;
230
+ model = resolvedSessionOverride.ref.model;
231
+ }
232
+ }
233
+ }
174
234
  // Resolve thinking level - job thinking > hooks.gmail.thinking > agent default
175
235
  const hooksGmailThinking = isGmailHook
176
236
  ? normalizeThinkLevel(params.cfg.hooks?.gmail?.thinking)
@@ -202,10 +262,7 @@ export async function runCronIsolatedAgentTurn(params) {
202
262
  channel: deliveryPlan.channel ?? "last",
203
263
  to: deliveryPlan.to,
204
264
  });
205
- const userTimezone = resolveUserTimezone(params.cfg.agents?.defaults?.userTimezone);
206
- const userTimeFormat = resolveUserTimeFormat(params.cfg.agents?.defaults?.timeFormat);
207
- const formattedTime = formatUserTime(new Date(now), userTimezone, userTimeFormat) ?? new Date(now).toISOString();
208
- const timeLine = `Current time: ${formattedTime} (${userTimezone})`;
265
+ const { formattedTime, timeLine } = resolveCronStyleNow(params.cfg, now);
209
266
  const base = `[cron:${params.job.id} ${params.job.name}] ${params.message}`.trim();
210
267
  // SECURITY: Wrap external hook content with security boundaries to prevent prompt injection
211
268
  // unless explicitly allowed via a dangerous config override.
@@ -242,17 +299,15 @@ export async function runCronIsolatedAgentTurn(params) {
242
299
  commandBody =
243
300
  `${commandBody}\n\nReturn your summary as plain text; it will be delivered automatically. If the task explicitly calls for messaging a specific external recipient, note who/where it should go instead of sending it yourself.`.trim();
244
301
  }
245
- const existingSnapshot = cronSession.sessionEntry.skillsSnapshot;
246
- const skillsSnapshotVersion = getSkillsSnapshotVersion(workspaceDir);
247
- const needsSkillsSnapshot = !existingSnapshot || existingSnapshot.version !== skillsSnapshotVersion;
248
- const skillsSnapshot = needsSkillsSnapshot
249
- ? buildWorkspaceSkillSnapshot(workspaceDir, {
250
- config: cfgWithAgentDefaults,
251
- eligibility: { remote: getRemoteSkillEligibility() },
252
- snapshotVersion: skillsSnapshotVersion,
253
- })
254
- : cronSession.sessionEntry.skillsSnapshot;
255
- if (needsSkillsSnapshot && skillsSnapshot) {
302
+ const existingSkillsSnapshot = cronSession.sessionEntry.skillsSnapshot;
303
+ const skillsSnapshot = resolveCronSkillsSnapshot({
304
+ workspaceDir,
305
+ config: cfgWithAgentDefaults,
306
+ agentId,
307
+ existingSnapshot: existingSkillsSnapshot,
308
+ isFastTestEnv,
309
+ });
310
+ if (!isFastTestEnv && skillsSnapshot !== existingSkillsSnapshot) {
256
311
  cronSession.sessionEntry = {
257
312
  ...cronSession.sessionEntry,
258
313
  updatedAt: Date.now(),
@@ -290,6 +345,7 @@ export async function runCronIsolatedAgentTurn(params) {
290
345
  return runCliAgent({
291
346
  sessionId: cronSession.sessionEntry.sessionId,
292
347
  sessionKey: agentSessionKey,
348
+ agentId,
293
349
  sessionFile,
294
350
  workspaceDir,
295
351
  config: cfgWithAgentDefaults,
@@ -305,6 +361,7 @@ export async function runCronIsolatedAgentTurn(params) {
305
361
  return runEmbeddedPiAgent({
306
362
  sessionId: cronSession.sessionEntry.sessionId,
307
363
  sessionKey: agentSessionKey,
364
+ agentId,
308
365
  messageChannel,
309
366
  agentAccountId: resolvedDelivery.accountId,
310
367
  sessionFile,
@@ -319,6 +376,8 @@ export async function runCronIsolatedAgentTurn(params) {
319
376
  verboseLevel: resolvedVerboseLevel,
320
377
  timeoutMs,
321
378
  runId: cronSession.sessionEntry.sessionId,
379
+ requireExplicitMessageTarget: true,
380
+ disableMessageTool: deliveryRequested,
322
381
  });
323
382
  },
324
383
  });
@@ -332,16 +391,19 @@ export async function runCronIsolatedAgentTurn(params) {
332
391
  }
333
392
  const payloads = runResult.payloads ?? [];
334
393
  // Update token+model fields in the session store.
394
+ // Also collect best-effort telemetry for the cron run log.
395
+ let telemetry;
335
396
  {
336
- const usage = runResult.meta.agentMeta?.usage;
337
- const modelUsed = runResult.meta.agentMeta?.model ?? fallbackModel ?? model;
338
- const providerUsed = runResult.meta.agentMeta?.provider ?? fallbackProvider ?? provider;
397
+ const usage = runResult.meta?.agentMeta?.usage;
398
+ const promptTokens = runResult.meta?.agentMeta?.promptTokens;
399
+ const modelUsed = runResult.meta?.agentMeta?.model ?? fallbackModel ?? model;
400
+ const providerUsed = runResult.meta?.agentMeta?.provider ?? fallbackProvider ?? provider;
339
401
  const contextTokens = agentCfg?.contextTokens ?? lookupContextTokens(modelUsed) ?? DEFAULT_CONTEXT_TOKENS;
340
402
  cronSession.sessionEntry.modelProvider = providerUsed;
341
403
  cronSession.sessionEntry.model = modelUsed;
342
404
  cronSession.sessionEntry.contextTokens = contextTokens;
343
405
  if (isCliProvider(providerUsed, cfgWithAgentDefaults)) {
344
- const cliSessionId = runResult.meta.agentMeta?.sessionId?.trim();
406
+ const cliSessionId = runResult.meta?.agentMeta?.sessionId?.trim();
345
407
  if (cliSessionId) {
346
408
  setCliSessionId(cronSession.sessionEntry, providerUsed, cliSessionId);
347
409
  }
@@ -349,22 +411,39 @@ export async function runCronIsolatedAgentTurn(params) {
349
411
  if (hasNonzeroUsage(usage)) {
350
412
  const input = usage.input ?? 0;
351
413
  const output = usage.output ?? 0;
414
+ const totalTokens = deriveSessionTotalTokens({
415
+ usage,
416
+ contextTokens,
417
+ promptTokens,
418
+ }) ?? input;
352
419
  cronSession.sessionEntry.inputTokens = input;
353
420
  cronSession.sessionEntry.outputTokens = output;
354
- cronSession.sessionEntry.totalTokens =
355
- deriveSessionTotalTokens({
356
- usage,
357
- contextTokens,
358
- }) ?? input;
421
+ cronSession.sessionEntry.totalTokens = totalTokens;
422
+ cronSession.sessionEntry.totalTokensFresh = true;
423
+ telemetry = {
424
+ model: modelUsed,
425
+ provider: providerUsed,
426
+ usage: {
427
+ input_tokens: input,
428
+ output_tokens: output,
429
+ total_tokens: totalTokens,
430
+ },
431
+ };
432
+ }
433
+ else {
434
+ telemetry = {
435
+ model: modelUsed,
436
+ provider: providerUsed,
437
+ };
359
438
  }
360
439
  await persistSessionEntry();
361
440
  }
362
441
  const firstText = payloads[0]?.text ?? "";
363
- const summary = pickSummaryFromPayloads(payloads) ?? pickSummaryFromOutput(firstText);
364
- const outputText = pickLastNonEmptyTextFromPayloads(payloads);
365
- const synthesizedText = outputText?.trim() || summary?.trim() || undefined;
442
+ let summary = pickSummaryFromPayloads(payloads) ?? pickSummaryFromOutput(firstText);
443
+ let outputText = pickLastNonEmptyTextFromPayloads(payloads);
444
+ let synthesizedText = outputText?.trim() || summary?.trim() || undefined;
366
445
  const deliveryPayload = pickLastDeliverablePayload(payloads);
367
- const deliveryPayloads = deliveryPayload !== undefined
446
+ let deliveryPayloads = deliveryPayload !== undefined
368
447
  ? [deliveryPayload]
369
448
  : synthesizedText
370
449
  ? [{ text: synthesizedText }]
@@ -383,6 +462,9 @@ export async function runCronIsolatedAgentTurn(params) {
383
462
  to: resolvedDelivery.to,
384
463
  accountId: resolvedDelivery.accountId,
385
464
  }));
465
+ // `true` means we confirmed at least one outbound send reached the target.
466
+ // Keep this strict so timer fallback can safely decide whether to wake main.
467
+ let delivered = skipMessagingToolDelivery;
386
468
  if (deliveryRequested && !skipHeartbeatDelivery && !skipMessagingToolDelivery) {
387
469
  if (resolvedDelivery.error) {
388
470
  if (!deliveryBestEffort) {
@@ -391,10 +473,11 @@ export async function runCronIsolatedAgentTurn(params) {
391
473
  error: resolvedDelivery.error.message,
392
474
  summary,
393
475
  outputText,
476
+ ...telemetry,
394
477
  });
395
478
  }
396
479
  logWarn(`[cron:${params.job.id}] ${resolvedDelivery.error.message}`);
397
- return withRunSession({ status: "ok", summary, outputText });
480
+ return withRunSession({ status: "ok", summary, outputText, ...telemetry });
398
481
  }
399
482
  if (!resolvedDelivery.to) {
400
483
  const message = "cron delivery target is missing";
@@ -404,43 +487,117 @@ export async function runCronIsolatedAgentTurn(params) {
404
487
  error: message,
405
488
  summary,
406
489
  outputText,
490
+ ...telemetry,
407
491
  });
408
492
  }
409
493
  logWarn(`[cron:${params.job.id}] ${message}`);
410
- return withRunSession({ status: "ok", summary, outputText });
494
+ return withRunSession({ status: "ok", summary, outputText, ...telemetry });
411
495
  }
412
- // Shared subagent announce flow is text-based; keep direct outbound delivery
413
- // for media/channel payloads so structured content is preserved.
496
+ const identity = resolveAgentOutboundIdentity(cfgWithAgentDefaults, agentId);
497
+ // Route text-only cron announce output back through the main session so it
498
+ // follows the same system-message injection path as subagent completions.
499
+ // Keep direct outbound delivery only for structured payloads (media/channel
500
+ // data), which cannot be represented by the shared announce flow.
414
501
  if (deliveryPayloadHasStructuredContent) {
415
502
  try {
416
- await deliverOutboundPayloads({
417
- cfg: cfgWithAgentDefaults,
418
- channel: resolvedDelivery.channel,
419
- to: resolvedDelivery.to,
420
- accountId: resolvedDelivery.accountId,
421
- threadId: resolvedDelivery.threadId,
422
- payloads: deliveryPayloads,
423
- bestEffort: deliveryBestEffort,
424
- deps: createOutboundSendDeps(params.deps),
425
- });
503
+ const payloadsForDelivery = deliveryPayloads.length > 0
504
+ ? deliveryPayloads
505
+ : synthesizedText
506
+ ? [{ text: synthesizedText }]
507
+ : [];
508
+ if (payloadsForDelivery.length > 0) {
509
+ const deliveryResults = await deliverOutboundPayloads({
510
+ cfg: cfgWithAgentDefaults,
511
+ channel: resolvedDelivery.channel,
512
+ to: resolvedDelivery.to,
513
+ accountId: resolvedDelivery.accountId,
514
+ threadId: resolvedDelivery.threadId,
515
+ payloads: payloadsForDelivery,
516
+ agentId,
517
+ identity,
518
+ bestEffort: deliveryBestEffort,
519
+ deps: createOutboundSendDeps(params.deps),
520
+ });
521
+ delivered = deliveryResults.length > 0;
522
+ }
426
523
  }
427
524
  catch (err) {
428
525
  if (!deliveryBestEffort) {
429
- return withRunSession({ status: "error", summary, outputText, error: String(err) });
526
+ return withRunSession({
527
+ status: "error",
528
+ summary,
529
+ outputText,
530
+ error: String(err),
531
+ ...telemetry,
532
+ });
430
533
  }
431
534
  }
432
535
  }
433
536
  else if (synthesizedText) {
434
- const announceSessionKey = resolveAgentMainSessionKey({
537
+ const announceMainSessionKey = resolveAgentMainSessionKey({
435
538
  cfg: params.cfg,
436
539
  agentId,
437
540
  });
541
+ const announceSessionKey = await resolveCronAnnounceSessionKey({
542
+ cfg: cfgWithAgentDefaults,
543
+ agentId,
544
+ fallbackSessionKey: announceMainSessionKey,
545
+ delivery: {
546
+ channel: resolvedDelivery.channel,
547
+ to: resolvedDelivery.to,
548
+ accountId: resolvedDelivery.accountId,
549
+ threadId: resolvedDelivery.threadId,
550
+ },
551
+ });
438
552
  const taskLabel = typeof params.job.name === "string" && params.job.name.trim()
439
553
  ? params.job.name.trim()
440
554
  : `cron:${params.job.id}`;
555
+ const initialSynthesizedText = synthesizedText.trim();
556
+ let activeSubagentRuns = countActiveDescendantRuns(agentSessionKey);
557
+ const expectedSubagentFollowup = expectsSubagentFollowup(initialSynthesizedText);
558
+ const hadActiveDescendants = activeSubagentRuns > 0;
559
+ if (activeSubagentRuns > 0 || expectedSubagentFollowup) {
560
+ let finalReply = await waitForDescendantSubagentSummary({
561
+ sessionKey: agentSessionKey,
562
+ initialReply: initialSynthesizedText,
563
+ timeoutMs,
564
+ observedActiveDescendants: activeSubagentRuns > 0 || expectedSubagentFollowup,
565
+ });
566
+ activeSubagentRuns = countActiveDescendantRuns(agentSessionKey);
567
+ if (!finalReply &&
568
+ activeSubagentRuns === 0 &&
569
+ (hadActiveDescendants || expectedSubagentFollowup)) {
570
+ finalReply = await readDescendantSubagentFallbackReply({
571
+ sessionKey: agentSessionKey,
572
+ runStartedAt,
573
+ });
574
+ }
575
+ if (finalReply && activeSubagentRuns === 0) {
576
+ outputText = finalReply;
577
+ summary = pickSummaryFromOutput(finalReply) ?? summary;
578
+ synthesizedText = finalReply;
579
+ deliveryPayloads = [{ text: finalReply }];
580
+ }
581
+ }
582
+ if (activeSubagentRuns > 0) {
583
+ // Parent orchestration is still in progress; avoid announcing a partial
584
+ // update to the main requester.
585
+ return withRunSession({ status: "ok", summary, outputText, ...telemetry });
586
+ }
587
+ if ((hadActiveDescendants || expectedSubagentFollowup) &&
588
+ synthesizedText.trim() === initialSynthesizedText &&
589
+ isLikelyInterimCronMessage(initialSynthesizedText) &&
590
+ initialSynthesizedText.toUpperCase() !== SILENT_REPLY_TOKEN.toUpperCase()) {
591
+ // Descendants existed but no post-orchestration synthesis arrived, so
592
+ // suppress stale parent text like "on it, pulling everything together".
593
+ return withRunSession({ status: "ok", summary, outputText, ...telemetry });
594
+ }
595
+ if (synthesizedText.toUpperCase() === SILENT_REPLY_TOKEN.toUpperCase()) {
596
+ return withRunSession({ status: "ok", summary, outputText, ...telemetry });
597
+ }
441
598
  try {
442
599
  const didAnnounce = await runSubagentAnnounceFlow({
443
- childSessionKey: runSessionKey,
600
+ childSessionKey: agentSessionKey,
444
601
  childRunId: `${params.job.id}:${runSessionId}`,
445
602
  requesterSessionKey: announceSessionKey,
446
603
  requesterOrigin: {
@@ -452,7 +609,7 @@ export async function runCronIsolatedAgentTurn(params) {
452
609
  requesterDisplayKey: announceSessionKey,
453
610
  task: taskLabel,
454
611
  timeoutMs,
455
- cleanup: "keep",
612
+ cleanup: params.job.deleteAfterRun ? "delete" : "keep",
456
613
  roundOneReply: synthesizedText,
457
614
  waitForCompletion: false,
458
615
  startedAt: runStartedAt,
@@ -460,7 +617,10 @@ export async function runCronIsolatedAgentTurn(params) {
460
617
  outcome: { status: "ok" },
461
618
  announceType: "cron job",
462
619
  });
463
- if (!didAnnounce) {
620
+ if (didAnnounce) {
621
+ delivered = true;
622
+ }
623
+ else {
464
624
  const message = "cron announce delivery failed";
465
625
  if (!deliveryBestEffort) {
466
626
  return withRunSession({
@@ -468,6 +628,7 @@ export async function runCronIsolatedAgentTurn(params) {
468
628
  summary,
469
629
  outputText,
470
630
  error: message,
631
+ ...telemetry,
471
632
  });
472
633
  }
473
634
  logWarn(`[cron:${params.job.id}] ${message}`);
@@ -475,11 +636,17 @@ export async function runCronIsolatedAgentTurn(params) {
475
636
  }
476
637
  catch (err) {
477
638
  if (!deliveryBestEffort) {
478
- return withRunSession({ status: "error", summary, outputText, error: String(err) });
639
+ return withRunSession({
640
+ status: "error",
641
+ summary,
642
+ outputText,
643
+ error: String(err),
644
+ ...telemetry,
645
+ });
479
646
  }
480
647
  logWarn(`[cron:${params.job.id}] ${String(err)}`);
481
648
  }
482
649
  }
483
650
  }
484
- return withRunSession({ status: "ok", summary, outputText });
651
+ return withRunSession({ status: "ok", summary, outputText, delivered, ...telemetry });
485
652
  }
@@ -1,8 +1,10 @@
1
1
  import { sanitizeAgentId } from "../routing/session-key.js";
2
2
  import { isRecord } from "../utils.js";
3
+ import { buildDeliveryFromLegacyPayload, hasLegacyDeliveryHints, stripLegacyDeliveryFields, } from "./legacy-delivery.js";
3
4
  import { parseAbsoluteTimeMs } from "./parse.js";
4
5
  import { migrateLegacyCronPayload } from "./payload-migration.js";
5
6
  import { inferLegacyName } from "./service/normalize.js";
7
+ import { normalizeCronStaggerMs, resolveDefaultCronStaggerMs } from "./stagger.js";
6
8
  const DEFAULT_OPTIONS = {
7
9
  applyDefaults: false,
8
10
  };
@@ -45,6 +47,13 @@ function coerceSchedule(schedule) {
45
47
  if ("atMs" in next) {
46
48
  delete next.atMs;
47
49
  }
50
+ const staggerMs = normalizeCronStaggerMs(schedule.staggerMs);
51
+ if (staggerMs !== undefined) {
52
+ next.staggerMs = staggerMs;
53
+ }
54
+ else if ("staggerMs" in next) {
55
+ delete next.staggerMs;
56
+ }
48
57
  return next;
49
58
  }
50
59
  function coercePayload(payload) {
@@ -64,12 +73,20 @@ function coercePayload(payload) {
64
73
  if (!next.kind) {
65
74
  const hasMessage = typeof next.message === "string" && next.message.trim().length > 0;
66
75
  const hasText = typeof next.text === "string" && next.text.trim().length > 0;
76
+ const hasAgentTurnHint = typeof next.model === "string" ||
77
+ typeof next.thinking === "string" ||
78
+ typeof next.timeoutSeconds === "number" ||
79
+ typeof next.allowUnsafeExternalContent === "boolean";
67
80
  if (hasMessage) {
68
81
  next.kind = "agentTurn";
69
82
  }
70
83
  else if (hasText) {
71
84
  next.kind = "systemEvent";
72
85
  }
86
+ else if (hasAgentTurnHint) {
87
+ // Accept partial agentTurn payload patches that only tweak agent-turn-only fields.
88
+ next.kind = "agentTurn";
89
+ }
73
90
  }
74
91
  if (typeof next.message === "string") {
75
92
  const trimmed = next.message.trim();
@@ -113,7 +130,7 @@ function coercePayload(payload) {
113
130
  }
114
131
  if ("timeoutSeconds" in next) {
115
132
  if (typeof next.timeoutSeconds === "number" && Number.isFinite(next.timeoutSeconds)) {
116
- next.timeoutSeconds = Math.max(1, Math.floor(next.timeoutSeconds));
133
+ next.timeoutSeconds = Math.max(0, Math.floor(next.timeoutSeconds));
117
134
  }
118
135
  else {
119
136
  delete next.timeoutSeconds;
@@ -132,7 +149,7 @@ function coerceDelivery(delivery) {
132
149
  if (mode === "deliver") {
133
150
  next.mode = "announce";
134
151
  }
135
- else if (mode === "announce" || mode === "none") {
152
+ else if (mode === "announce" || mode === "none" || mode === "webhook") {
136
153
  next.mode = mode;
137
154
  }
138
155
  else {
@@ -162,49 +179,6 @@ function coerceDelivery(delivery) {
162
179
  }
163
180
  return next;
164
181
  }
165
- function hasLegacyDeliveryHints(payload) {
166
- if (typeof payload.deliver === "boolean") {
167
- return true;
168
- }
169
- if (typeof payload.bestEffortDeliver === "boolean") {
170
- return true;
171
- }
172
- if (typeof payload.to === "string" && payload.to.trim()) {
173
- return true;
174
- }
175
- return false;
176
- }
177
- function buildDeliveryFromLegacyPayload(payload) {
178
- const deliver = payload.deliver;
179
- const mode = deliver === false ? "none" : "announce";
180
- const channelRaw = typeof payload.channel === "string" ? payload.channel.trim().toLowerCase() : "";
181
- const toRaw = typeof payload.to === "string" ? payload.to.trim() : "";
182
- const next = { mode };
183
- if (channelRaw) {
184
- next.channel = channelRaw;
185
- }
186
- if (toRaw) {
187
- next.to = toRaw;
188
- }
189
- if (typeof payload.bestEffortDeliver === "boolean") {
190
- next.bestEffort = payload.bestEffortDeliver;
191
- }
192
- return next;
193
- }
194
- function stripLegacyDeliveryFields(payload) {
195
- if ("deliver" in payload) {
196
- delete payload.deliver;
197
- }
198
- if ("channel" in payload) {
199
- delete payload.channel;
200
- }
201
- if ("to" in payload) {
202
- delete payload.to;
203
- }
204
- if ("bestEffortDeliver" in payload) {
205
- delete payload.bestEffortDeliver;
206
- }
207
- }
208
182
  function unwrapJob(raw) {
209
183
  if (isRecord(raw.data)) {
210
184
  return raw.data;
@@ -310,6 +284,21 @@ export function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
310
284
  }
311
285
  }
312
286
  }
287
+ if ("sessionKey" in base) {
288
+ const sessionKey = base.sessionKey;
289
+ if (sessionKey === null) {
290
+ next.sessionKey = null;
291
+ }
292
+ else if (typeof sessionKey === "string") {
293
+ const trimmed = sessionKey.trim();
294
+ if (trimmed) {
295
+ next.sessionKey = trimmed;
296
+ }
297
+ else {
298
+ delete next.sessionKey;
299
+ }
300
+ }
301
+ }
313
302
  if ("enabled" in base) {
314
303
  const enabled = base.enabled;
315
304
  if (typeof enabled === "boolean") {
@@ -407,6 +396,20 @@ export function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
407
396
  !("deleteAfterRun" in next)) {
408
397
  next.deleteAfterRun = true;
409
398
  }
399
+ if ("schedule" in next && isRecord(next.schedule) && next.schedule.kind === "cron") {
400
+ const schedule = next.schedule;
401
+ const explicit = normalizeCronStaggerMs(schedule.staggerMs);
402
+ if (explicit !== undefined) {
403
+ schedule.staggerMs = explicit;
404
+ }
405
+ else {
406
+ const expr = typeof schedule.expr === "string" ? schedule.expr : "";
407
+ const defaultStaggerMs = resolveDefaultCronStaggerMs(expr);
408
+ if (defaultStaggerMs !== undefined) {
409
+ schedule.staggerMs = defaultStaggerMs;
410
+ }
411
+ }
412
+ }
410
413
  const payload = isRecord(next.payload) ? next.payload : null;
411
414
  const payloadKind = payload && typeof payload.kind === "string" ? payload.kind : "";
412
415
  const sessionTarget = typeof next.sessionTarget === "string" ? next.sessionTarget : "";