@poolzin/pool-bot 2026.2.0 → 2026.2.2

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 (258) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README-header.png +0 -0
  3. package/dist/agents/bash-tools.exec.js +76 -25
  4. package/dist/agents/cli-runner/helpers.js +9 -11
  5. package/dist/agents/context.js +1 -1
  6. package/dist/agents/identity.js +47 -7
  7. package/dist/agents/memory-search.js +25 -8
  8. package/dist/agents/model-catalog.js +1 -1
  9. package/dist/agents/model-selection.js +21 -0
  10. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  11. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  12. package/dist/agents/pi-embedded-helpers.js +1 -1
  13. package/dist/agents/pi-embedded-runner/compact.js +8 -10
  14. package/dist/agents/pi-embedded-runner/model.js +62 -3
  15. package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
  16. package/dist/agents/pi-embedded-runner/run.js +199 -46
  17. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  18. package/dist/agents/pi-embedded-subscribe.js +118 -29
  19. package/dist/agents/pi-tools.js +10 -5
  20. package/dist/agents/poolbot-tools.js +15 -10
  21. package/dist/agents/sandbox-paths.js +31 -0
  22. package/dist/agents/session-tool-result-guard.js +94 -15
  23. package/dist/agents/shell-utils.js +51 -0
  24. package/dist/agents/skills/bundled-context.js +23 -0
  25. package/dist/agents/skills/bundled-dir.js +41 -7
  26. package/dist/agents/skills-install.js +60 -23
  27. package/dist/agents/subagent-announce.js +79 -34
  28. package/dist/agents/tool-policy.conformance.js +14 -0
  29. package/dist/agents/tool-policy.js +24 -0
  30. package/dist/agents/tools/cron-tool.js +166 -19
  31. package/dist/agents/tools/discord-actions-presence.js +78 -0
  32. package/dist/agents/tools/image-tool.js +1 -1
  33. package/dist/agents/tools/message-tool.js +56 -2
  34. package/dist/agents/tools/sessions-history-tool.js +69 -1
  35. package/dist/agents/tools/web-search.js +211 -42
  36. package/dist/agents/usage.js +23 -1
  37. package/dist/agents/workspace-run.js +67 -0
  38. package/dist/agents/workspace-templates.js +44 -0
  39. package/dist/auto-reply/command-auth.js +121 -6
  40. package/dist/auto-reply/envelope.js +74 -82
  41. package/dist/auto-reply/reply/commands-compact.js +1 -0
  42. package/dist/auto-reply/reply/commands-context-report.js +1 -0
  43. package/dist/auto-reply/reply/commands-context.js +1 -0
  44. package/dist/auto-reply/reply/commands-models.js +107 -60
  45. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  46. package/dist/auto-reply/reply/get-reply-run.js +2 -1
  47. package/dist/auto-reply/reply/inbound-context.js +5 -1
  48. package/dist/auto-reply/reply/mentions.js +1 -1
  49. package/dist/auto-reply/reply/model-selection.js +3 -3
  50. package/dist/auto-reply/thinking.js +88 -43
  51. package/dist/browser/bridge-server.js +13 -0
  52. package/dist/browser/cdp.helpers.js +38 -24
  53. package/dist/browser/client-fetch.js +50 -7
  54. package/dist/browser/config.js +1 -10
  55. package/dist/browser/extension-relay.js +101 -40
  56. package/dist/browser/pw-ai.js +1 -1
  57. package/dist/browser/pw-session.js +143 -8
  58. package/dist/browser/pw-tools-core.interactions.js +125 -27
  59. package/dist/browser/pw-tools-core.responses.js +1 -1
  60. package/dist/browser/pw-tools-core.state.js +1 -1
  61. package/dist/browser/routes/agent.act.js +86 -41
  62. package/dist/browser/routes/dispatcher.js +4 -4
  63. package/dist/browser/screenshot.js +1 -1
  64. package/dist/browser/server.js +13 -0
  65. package/dist/build-info.json +3 -3
  66. package/dist/canvas-host/a2ui/index.html +28 -28
  67. package/dist/channels/reply-prefix.js +8 -1
  68. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  69. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  70. package/dist/cli/cron-cli/shared.js +56 -41
  71. package/dist/cli/dns-cli.js +26 -14
  72. package/dist/cli/gateway-cli/register.js +37 -19
  73. package/dist/cli/memory-cli.js +5 -5
  74. package/dist/cli/parse-bytes.js +37 -0
  75. package/dist/cli/update-cli.js +173 -52
  76. package/dist/commands/agent.js +1 -0
  77. package/dist/commands/auth-choice.apply.oauth.js +1 -1
  78. package/dist/commands/doctor-config-flow.js +61 -5
  79. package/dist/commands/doctor-state-migrations.js +1 -1
  80. package/dist/commands/health.js +1 -1
  81. package/dist/commands/model-allowlist.js +29 -0
  82. package/dist/commands/model-picker.js +2 -1
  83. package/dist/commands/models/list.registry.js +1 -1
  84. package/dist/commands/models/list.status-command.js +43 -23
  85. package/dist/commands/models/shared.js +15 -0
  86. package/dist/commands/onboard-custom.js +384 -0
  87. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  88. package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
  89. package/dist/commands/onboard-skills.js +63 -38
  90. package/dist/commands/openai-model-default.js +41 -0
  91. package/dist/compat/legacy-names.js +2 -0
  92. package/dist/config/defaults.js +3 -2
  93. package/dist/config/paths.js +136 -35
  94. package/dist/config/plugin-auto-enable.js +21 -5
  95. package/dist/config/redact-snapshot.js +153 -0
  96. package/dist/config/schema.field-metadata.js +590 -0
  97. package/dist/config/schema.js +2 -2
  98. package/dist/config/sessions/store.js +291 -23
  99. package/dist/config/zod-schema.agent-defaults.js +3 -0
  100. package/dist/config/zod-schema.agent-runtime.js +13 -2
  101. package/dist/config/zod-schema.providers-core.js +142 -0
  102. package/dist/config/zod-schema.session.js +3 -0
  103. package/dist/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
  104. package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
  105. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
  106. package/dist/control-ui/index.html +4 -4
  107. package/dist/cron/delivery.js +57 -0
  108. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  109. package/dist/cron/isolated-agent/helpers.js +22 -5
  110. package/dist/cron/isolated-agent/run.js +172 -63
  111. package/dist/cron/isolated-agent/session.js +2 -0
  112. package/dist/cron/normalize.js +356 -28
  113. package/dist/cron/parse.js +10 -5
  114. package/dist/cron/run-log.js +35 -10
  115. package/dist/cron/schedule.js +41 -6
  116. package/dist/cron/service/jobs.js +208 -35
  117. package/dist/cron/service/ops.js +72 -16
  118. package/dist/cron/service/state.js +2 -0
  119. package/dist/cron/service/store.js +386 -14
  120. package/dist/cron/service/timer.js +390 -147
  121. package/dist/cron/session-reaper.js +86 -0
  122. package/dist/cron/store.js +23 -8
  123. package/dist/cron/validate-timestamp.js +43 -0
  124. package/dist/discord/monitor/agent-components.js +438 -0
  125. package/dist/discord/monitor/allow-list.js +28 -5
  126. package/dist/discord/monitor/gateway-registry.js +29 -0
  127. package/dist/discord/monitor/native-command.js +44 -23
  128. package/dist/discord/monitor/sender-identity.js +45 -0
  129. package/dist/discord/pluralkit.js +27 -0
  130. package/dist/discord/send.outbound.js +92 -5
  131. package/dist/discord/send.shared.js +60 -23
  132. package/dist/discord/targets.js +84 -1
  133. package/dist/entry.js +15 -9
  134. package/dist/extensionAPI.js +8 -0
  135. package/dist/gateway/control-ui.js +8 -1
  136. package/dist/gateway/hooks-mapping.js +3 -0
  137. package/dist/gateway/hooks.js +65 -0
  138. package/dist/gateway/net.js +96 -31
  139. package/dist/gateway/node-command-policy.js +50 -15
  140. package/dist/gateway/origin-check.js +56 -0
  141. package/dist/gateway/protocol/client-info.js +9 -0
  142. package/dist/gateway/protocol/index.js +9 -2
  143. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  144. package/dist/gateway/protocol/schema/cron.js +22 -10
  145. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  146. package/dist/gateway/protocol/schema/sessions.js +12 -0
  147. package/dist/gateway/server/hooks.js +1 -1
  148. package/dist/gateway/server-broadcast.js +26 -9
  149. package/dist/gateway/server-chat.js +112 -23
  150. package/dist/gateway/server-discovery-runtime.js +10 -2
  151. package/dist/gateway/server-http.js +109 -11
  152. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  153. package/dist/gateway/server-methods/agents.js +321 -2
  154. package/dist/gateway/server-methods/usage.js +559 -16
  155. package/dist/gateway/server-runtime-state.js +22 -8
  156. package/dist/gateway/server-startup-memory.js +16 -0
  157. package/dist/gateway/server.impl.js +5 -1
  158. package/dist/gateway/session-utils.fs.js +23 -25
  159. package/dist/gateway/session-utils.js +20 -10
  160. package/dist/gateway/sessions-patch.js +7 -22
  161. package/dist/gateway/test-helpers.mocks.js +11 -7
  162. package/dist/gateway/test-helpers.server.js +35 -2
  163. package/dist/imessage/constants.js +2 -0
  164. package/dist/imessage/monitor/deliver.js +4 -1
  165. package/dist/imessage/monitor/monitor-provider.js +51 -1
  166. package/dist/infra/bonjour-discovery.js +131 -70
  167. package/dist/infra/control-ui-assets.js +134 -12
  168. package/dist/infra/errors.js +12 -0
  169. package/dist/infra/exec-approvals.js +266 -57
  170. package/dist/infra/format-time/format-datetime.js +79 -0
  171. package/dist/infra/format-time/format-duration.js +81 -0
  172. package/dist/infra/format-time/format-relative.js +80 -0
  173. package/dist/infra/heartbeat-runner.js +140 -49
  174. package/dist/infra/home-dir.js +54 -0
  175. package/dist/infra/net/fetch-guard.js +122 -0
  176. package/dist/infra/net/ssrf.js +65 -29
  177. package/dist/infra/outbound/abort.js +14 -0
  178. package/dist/infra/outbound/message-action-runner.js +77 -13
  179. package/dist/infra/outbound/outbound-session.js +143 -37
  180. package/dist/infra/poolbot-root.js +43 -1
  181. package/dist/infra/session-cost-usage.js +631 -41
  182. package/dist/infra/state-migrations.js +317 -47
  183. package/dist/infra/update-global.js +35 -0
  184. package/dist/infra/update-runner.js +149 -43
  185. package/dist/infra/warning-filter.js +65 -0
  186. package/dist/infra/widearea-dns.js +30 -9
  187. package/dist/logging/redact-identifier.js +12 -0
  188. package/dist/media/fetch.js +81 -58
  189. package/dist/media/store.js +2 -0
  190. package/dist/media-understanding/apply.js +403 -3
  191. package/dist/media-understanding/attachments.js +38 -27
  192. package/dist/media-understanding/defaults.js +16 -0
  193. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  194. package/dist/media-understanding/providers/google/audio.js +24 -17
  195. package/dist/media-understanding/providers/google/video.js +24 -17
  196. package/dist/media-understanding/providers/image.js +3 -3
  197. package/dist/media-understanding/providers/index.js +4 -1
  198. package/dist/media-understanding/providers/openai/audio.js +22 -14
  199. package/dist/media-understanding/providers/shared.js +16 -11
  200. package/dist/media-understanding/providers/zai/index.js +6 -0
  201. package/dist/media-understanding/runner.js +158 -90
  202. package/dist/memory/batch-voyage.js +277 -0
  203. package/dist/memory/embeddings-voyage.js +75 -0
  204. package/dist/memory/embeddings.js +28 -16
  205. package/dist/memory/internal.js +101 -18
  206. package/dist/memory/manager.js +154 -48
  207. package/dist/memory/search-manager.js +173 -0
  208. package/dist/memory/session-files.js +9 -3
  209. package/dist/node-host/runner.js +34 -24
  210. package/dist/node-host/with-timeout.js +27 -0
  211. package/dist/plugins/commands.js +5 -1
  212. package/dist/plugins/config-state.js +86 -7
  213. package/dist/plugins/source-display.js +51 -0
  214. package/dist/process/exec.js +20 -2
  215. package/dist/routing/resolve-route.js +12 -0
  216. package/dist/routing/session-key.js +15 -0
  217. package/dist/runtime.js +2 -0
  218. package/dist/security/audit-extra.async.js +601 -0
  219. package/dist/security/audit-extra.js +2 -830
  220. package/dist/security/audit-extra.sync.js +505 -0
  221. package/dist/security/channel-metadata.js +34 -0
  222. package/dist/security/external-content.js +88 -6
  223. package/dist/security/skill-scanner.js +330 -0
  224. package/dist/sessions/session-key-utils.js +7 -0
  225. package/dist/signal/monitor/event-handler.js +80 -1
  226. package/dist/slack/monitor/media.js +85 -15
  227. package/dist/tailscale/detect.js +1 -2
  228. package/dist/telegram/bot/helpers.js +109 -28
  229. package/dist/telegram/bot-handlers.js +144 -3
  230. package/dist/telegram/bot-message-context.js +37 -10
  231. package/dist/telegram/bot-message-dispatch.js +54 -17
  232. package/dist/telegram/bot-native-commands.js +86 -29
  233. package/dist/telegram/bot.js +30 -29
  234. package/dist/telegram/model-buttons.js +163 -0
  235. package/dist/telegram/monitor.js +110 -85
  236. package/dist/telegram/send.js +129 -47
  237. package/dist/terminal/restore.js +45 -0
  238. package/dist/test-helpers/state-dir-env.js +16 -0
  239. package/dist/tts/tts.js +12 -6
  240. package/dist/tui/tui-session-actions.js +166 -54
  241. package/dist/utils/fetch-timeout.js +20 -0
  242. package/dist/utils/normalize-secret-input.js +19 -0
  243. package/dist/utils/transcript-tools.js +58 -0
  244. package/dist/utils.js +45 -14
  245. package/dist/version.js +42 -5
  246. package/dist/wizard/clack-prompter.js +9 -6
  247. package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
  248. package/extensions/googlechat/package.json +2 -2
  249. package/extensions/line/node_modules/.bin/poolbot +21 -0
  250. package/extensions/line/package.json +1 -1
  251. package/extensions/matrix/node_modules/.bin/poolbot +21 -0
  252. package/extensions/matrix/package.json +1 -1
  253. package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
  254. package/extensions/memory-core/package.json +4 -1
  255. package/extensions/twitch/node_modules/.bin/poolbot +21 -0
  256. package/extensions/twitch/package.json +1 -1
  257. package/package.json +183 -24
  258. package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
@@ -1,19 +1,245 @@
1
+ import fs from "node:fs";
2
+ import { parseAbsoluteTimeMs } from "../parse.js";
1
3
  import { migrateLegacyCronPayload } from "../payload-migration.js";
2
4
  import { loadCronStore, saveCronStore } from "../store.js";
5
+ import { recomputeNextRuns } from "./jobs.js";
3
6
  import { inferLegacyName, normalizeOptionalText } from "./normalize.js";
4
- const storeCache = new Map();
5
- export async function ensureLoaded(state) {
6
- if (state.store)
7
- return;
8
- const cached = storeCache.get(state.deps.storePath);
9
- if (cached) {
10
- state.store = cached;
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
+ function buildDeliveryPatchFromLegacyPayload(payload) {
37
+ const deliver = payload.deliver;
38
+ const channelRaw = typeof payload.channel === "string" ? payload.channel.trim().toLowerCase() : "";
39
+ const toRaw = typeof payload.to === "string" ? payload.to.trim() : "";
40
+ const next = {};
41
+ let hasPatch = false;
42
+ if (deliver === false) {
43
+ next.mode = "none";
44
+ hasPatch = true;
45
+ }
46
+ else if (deliver === true || toRaw) {
47
+ next.mode = "announce";
48
+ hasPatch = true;
49
+ }
50
+ if (channelRaw) {
51
+ next.channel = channelRaw;
52
+ hasPatch = true;
53
+ }
54
+ if (toRaw) {
55
+ next.to = toRaw;
56
+ hasPatch = true;
57
+ }
58
+ if (typeof payload.bestEffortDeliver === "boolean") {
59
+ next.bestEffort = payload.bestEffortDeliver;
60
+ hasPatch = true;
61
+ }
62
+ return hasPatch ? next : null;
63
+ }
64
+ function mergeLegacyDeliveryInto(delivery, payload) {
65
+ const patch = buildDeliveryPatchFromLegacyPayload(payload);
66
+ if (!patch) {
67
+ return { delivery, mutated: false };
68
+ }
69
+ const next = { ...delivery };
70
+ let mutated = false;
71
+ if ("mode" in patch && patch.mode !== next.mode) {
72
+ next.mode = patch.mode;
73
+ mutated = true;
74
+ }
75
+ if ("channel" in patch && patch.channel !== next.channel) {
76
+ next.channel = patch.channel;
77
+ mutated = true;
78
+ }
79
+ if ("to" in patch && patch.to !== next.to) {
80
+ next.to = patch.to;
81
+ mutated = true;
82
+ }
83
+ if ("bestEffort" in patch && patch.bestEffort !== next.bestEffort) {
84
+ next.bestEffort = patch.bestEffort;
85
+ mutated = true;
86
+ }
87
+ return { delivery: next, mutated };
88
+ }
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
+ function normalizePayloadKind(payload) {
104
+ const raw = typeof payload.kind === "string" ? payload.kind.trim().toLowerCase() : "";
105
+ if (raw === "agentturn") {
106
+ payload.kind = "agentTurn";
107
+ return true;
108
+ }
109
+ if (raw === "systemevent") {
110
+ payload.kind = "systemEvent";
111
+ return true;
112
+ }
113
+ return false;
114
+ }
115
+ function inferPayloadIfMissing(raw) {
116
+ const message = typeof raw.message === "string" ? raw.message.trim() : "";
117
+ const text = typeof raw.text === "string" ? raw.text.trim() : "";
118
+ if (message) {
119
+ raw.payload = { kind: "agentTurn", message };
120
+ return true;
121
+ }
122
+ if (text) {
123
+ raw.payload = { kind: "systemEvent", text };
124
+ return true;
125
+ }
126
+ return false;
127
+ }
128
+ function copyTopLevelAgentTurnFields(raw, payload) {
129
+ let mutated = false;
130
+ const copyTrimmedString = (field) => {
131
+ const existing = payload[field];
132
+ if (typeof existing === "string" && existing.trim()) {
133
+ return;
134
+ }
135
+ const value = raw[field];
136
+ if (typeof value === "string" && value.trim()) {
137
+ payload[field] = value.trim();
138
+ mutated = true;
139
+ }
140
+ };
141
+ copyTrimmedString("model");
142
+ copyTrimmedString("thinking");
143
+ if (typeof payload.timeoutSeconds !== "number" &&
144
+ typeof raw.timeoutSeconds === "number" &&
145
+ Number.isFinite(raw.timeoutSeconds)) {
146
+ payload.timeoutSeconds = Math.max(1, Math.floor(raw.timeoutSeconds));
147
+ mutated = true;
148
+ }
149
+ if (typeof payload.allowUnsafeExternalContent !== "boolean" &&
150
+ typeof raw.allowUnsafeExternalContent === "boolean") {
151
+ payload.allowUnsafeExternalContent = raw.allowUnsafeExternalContent;
152
+ mutated = true;
153
+ }
154
+ if (typeof payload.deliver !== "boolean" && typeof raw.deliver === "boolean") {
155
+ payload.deliver = raw.deliver;
156
+ mutated = true;
157
+ }
158
+ if (typeof payload.channel !== "string" &&
159
+ typeof raw.channel === "string" &&
160
+ raw.channel.trim()) {
161
+ payload.channel = raw.channel.trim();
162
+ mutated = true;
163
+ }
164
+ if (typeof payload.to !== "string" && typeof raw.to === "string" && raw.to.trim()) {
165
+ payload.to = raw.to.trim();
166
+ mutated = true;
167
+ }
168
+ if (typeof payload.bestEffortDeliver !== "boolean" &&
169
+ typeof raw.bestEffortDeliver === "boolean") {
170
+ payload.bestEffortDeliver = raw.bestEffortDeliver;
171
+ mutated = true;
172
+ }
173
+ if (typeof payload.provider !== "string" &&
174
+ typeof raw.provider === "string" &&
175
+ raw.provider.trim()) {
176
+ payload.provider = raw.provider.trim();
177
+ mutated = true;
178
+ }
179
+ return mutated;
180
+ }
181
+ function stripLegacyTopLevelFields(raw) {
182
+ if ("model" in raw) {
183
+ delete raw.model;
184
+ }
185
+ if ("thinking" in raw) {
186
+ delete raw.thinking;
187
+ }
188
+ if ("timeoutSeconds" in raw) {
189
+ delete raw.timeoutSeconds;
190
+ }
191
+ if ("allowUnsafeExternalContent" in raw) {
192
+ delete raw.allowUnsafeExternalContent;
193
+ }
194
+ if ("message" in raw) {
195
+ delete raw.message;
196
+ }
197
+ if ("text" in raw) {
198
+ delete raw.text;
199
+ }
200
+ if ("deliver" in raw) {
201
+ delete raw.deliver;
202
+ }
203
+ if ("channel" in raw) {
204
+ delete raw.channel;
205
+ }
206
+ if ("to" in raw) {
207
+ delete raw.to;
208
+ }
209
+ if ("bestEffortDeliver" in raw) {
210
+ delete raw.bestEffortDeliver;
211
+ }
212
+ if ("provider" in raw) {
213
+ delete raw.provider;
214
+ }
215
+ }
216
+ async function getFileMtimeMs(path) {
217
+ try {
218
+ const stats = await fs.promises.stat(path);
219
+ return stats.mtimeMs;
220
+ }
221
+ catch {
222
+ return null;
223
+ }
224
+ }
225
+ export async function ensureLoaded(state, opts) {
226
+ // Fast path: store is already in memory. Other callers (add, list, run, …)
227
+ // trust the in-memory copy to avoid a stat syscall on every operation.
228
+ if (state.store && !opts?.forceReload) {
11
229
  return;
12
230
  }
231
+ // Force reload always re-reads the file to avoid missing cross-service
232
+ // edits on filesystems with coarse mtime resolution.
233
+ const fileMtimeMs = await getFileMtimeMs(state.deps.storePath);
13
234
  const loaded = await loadCronStore(state.deps.storePath);
14
235
  const jobs = (loaded.jobs ?? []);
15
236
  let mutated = false;
16
237
  for (const raw of jobs) {
238
+ const state = raw.state;
239
+ if (!state || typeof state !== "object" || Array.isArray(state)) {
240
+ raw.state = {};
241
+ mutated = true;
242
+ }
17
243
  const nameRaw = raw.name;
18
244
  if (typeof nameRaw !== "string" || nameRaw.trim().length === 0) {
19
245
  raw.name = inferLegacyName({
@@ -30,28 +256,174 @@ export async function ensureLoaded(state) {
30
256
  raw.description = desc;
31
257
  mutated = true;
32
258
  }
259
+ if (typeof raw.enabled !== "boolean") {
260
+ raw.enabled = true;
261
+ mutated = true;
262
+ }
33
263
  const payload = raw.payload;
34
- if (payload && typeof payload === "object" && !Array.isArray(payload)) {
35
- if (migrateLegacyCronPayload(payload)) {
264
+ if ((!payload || typeof payload !== "object" || Array.isArray(payload)) &&
265
+ inferPayloadIfMissing(raw)) {
266
+ mutated = true;
267
+ }
268
+ const payloadRecord = raw.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload)
269
+ ? raw.payload
270
+ : null;
271
+ if (payloadRecord) {
272
+ if (normalizePayloadKind(payloadRecord)) {
273
+ mutated = true;
274
+ }
275
+ if (!payloadRecord.kind) {
276
+ if (typeof payloadRecord.message === "string" && payloadRecord.message.trim()) {
277
+ payloadRecord.kind = "agentTurn";
278
+ mutated = true;
279
+ }
280
+ else if (typeof payloadRecord.text === "string" && payloadRecord.text.trim()) {
281
+ payloadRecord.kind = "systemEvent";
282
+ mutated = true;
283
+ }
284
+ }
285
+ if (payloadRecord.kind === "agentTurn") {
286
+ if (copyTopLevelAgentTurnFields(raw, payloadRecord)) {
287
+ mutated = true;
288
+ }
289
+ }
290
+ }
291
+ const hadLegacyTopLevelFields = "model" in raw ||
292
+ "thinking" in raw ||
293
+ "timeoutSeconds" in raw ||
294
+ "allowUnsafeExternalContent" in raw ||
295
+ "message" in raw ||
296
+ "text" in raw ||
297
+ "deliver" in raw ||
298
+ "channel" in raw ||
299
+ "to" in raw ||
300
+ "bestEffortDeliver" in raw ||
301
+ "provider" in raw;
302
+ if (hadLegacyTopLevelFields) {
303
+ stripLegacyTopLevelFields(raw);
304
+ mutated = true;
305
+ }
306
+ if (payloadRecord) {
307
+ if (migrateLegacyCronPayload(payloadRecord)) {
308
+ mutated = true;
309
+ }
310
+ }
311
+ const schedule = raw.schedule;
312
+ if (schedule && typeof schedule === "object" && !Array.isArray(schedule)) {
313
+ const sched = schedule;
314
+ const kind = typeof sched.kind === "string" ? sched.kind.trim().toLowerCase() : "";
315
+ if (!kind && ("at" in sched || "atMs" in sched)) {
316
+ sched.kind = "at";
317
+ mutated = true;
318
+ }
319
+ const atRaw = typeof sched.at === "string" ? sched.at.trim() : "";
320
+ const atMsRaw = sched.atMs;
321
+ const parsedAtMs = typeof atMsRaw === "number"
322
+ ? atMsRaw
323
+ : typeof atMsRaw === "string"
324
+ ? parseAbsoluteTimeMs(atMsRaw)
325
+ : atRaw
326
+ ? parseAbsoluteTimeMs(atRaw)
327
+ : null;
328
+ if (parsedAtMs !== null) {
329
+ sched.at = new Date(parsedAtMs).toISOString();
330
+ if ("atMs" in sched) {
331
+ delete sched.atMs;
332
+ }
333
+ mutated = true;
334
+ }
335
+ const everyMsRaw = sched.everyMs;
336
+ const everyMs = typeof everyMsRaw === "number" && Number.isFinite(everyMsRaw)
337
+ ? Math.floor(everyMsRaw)
338
+ : null;
339
+ if ((kind === "every" || sched.kind === "every") && everyMs !== null) {
340
+ const anchorRaw = sched.anchorMs;
341
+ const normalizedAnchor = typeof anchorRaw === "number" && Number.isFinite(anchorRaw)
342
+ ? Math.max(0, Math.floor(anchorRaw))
343
+ : typeof raw.createdAtMs === "number" && Number.isFinite(raw.createdAtMs)
344
+ ? Math.max(0, Math.floor(raw.createdAtMs))
345
+ : typeof raw.updatedAtMs === "number" && Number.isFinite(raw.updatedAtMs)
346
+ ? Math.max(0, Math.floor(raw.updatedAtMs))
347
+ : null;
348
+ if (normalizedAnchor !== null && anchorRaw !== normalizedAnchor) {
349
+ sched.anchorMs = normalizedAnchor;
350
+ mutated = true;
351
+ }
352
+ }
353
+ }
354
+ const delivery = raw.delivery;
355
+ if (delivery && typeof delivery === "object" && !Array.isArray(delivery)) {
356
+ const modeRaw = delivery.mode;
357
+ if (typeof modeRaw === "string") {
358
+ const lowered = modeRaw.trim().toLowerCase();
359
+ if (lowered === "deliver") {
360
+ delivery.mode = "announce";
361
+ mutated = true;
362
+ }
363
+ }
364
+ else if (modeRaw === undefined || modeRaw === null) {
365
+ // Explicitly persist the default so existing jobs don't silently
366
+ // change behaviour when the runtime default shifts.
367
+ delivery.mode = "announce";
368
+ mutated = true;
369
+ }
370
+ }
371
+ const isolation = raw.isolation;
372
+ if (isolation && typeof isolation === "object" && !Array.isArray(isolation)) {
373
+ delete raw.isolation;
374
+ mutated = true;
375
+ }
376
+ const payloadKind = payloadRecord && typeof payloadRecord.kind === "string" ? payloadRecord.kind : "";
377
+ const sessionTarget = typeof raw.sessionTarget === "string" ? raw.sessionTarget.trim().toLowerCase() : "";
378
+ const isIsolatedAgentTurn = sessionTarget === "isolated" || (sessionTarget === "" && payloadKind === "agentTurn");
379
+ const hasDelivery = delivery && typeof delivery === "object" && !Array.isArray(delivery);
380
+ const hasLegacyDelivery = payloadRecord ? hasLegacyDeliveryHints(payloadRecord) : false;
381
+ if (isIsolatedAgentTurn && payloadKind === "agentTurn") {
382
+ if (!hasDelivery) {
383
+ raw.delivery =
384
+ payloadRecord && hasLegacyDelivery
385
+ ? buildDeliveryFromLegacyPayload(payloadRecord)
386
+ : { mode: "announce" };
387
+ mutated = true;
388
+ }
389
+ if (payloadRecord && hasLegacyDelivery) {
390
+ if (hasDelivery) {
391
+ const merged = mergeLegacyDeliveryInto(delivery, payloadRecord);
392
+ if (merged.mutated) {
393
+ raw.delivery = merged.delivery;
394
+ mutated = true;
395
+ }
396
+ }
397
+ stripLegacyDeliveryFields(payloadRecord);
36
398
  mutated = true;
37
399
  }
38
400
  }
39
401
  }
40
402
  state.store = { version: 1, jobs: jobs };
41
- storeCache.set(state.deps.storePath, state.store);
42
- if (mutated)
403
+ state.storeLoadedAtMs = state.deps.nowMs();
404
+ state.storeFileMtimeMs = fileMtimeMs;
405
+ if (!opts?.skipRecompute) {
406
+ recomputeNextRuns(state);
407
+ }
408
+ if (mutated) {
43
409
  await persist(state);
410
+ }
44
411
  }
45
412
  export function warnIfDisabled(state, action) {
46
- if (state.deps.cronEnabled)
413
+ if (state.deps.cronEnabled) {
47
414
  return;
48
- if (state.warnedDisabled)
415
+ }
416
+ if (state.warnedDisabled) {
49
417
  return;
418
+ }
50
419
  state.warnedDisabled = true;
51
420
  state.deps.log.warn({ enabled: false, action, storePath: state.deps.storePath }, "cron: scheduler disabled; jobs will not run automatically");
52
421
  }
53
422
  export async function persist(state) {
54
- if (!state.store)
423
+ if (!state.store) {
55
424
  return;
425
+ }
56
426
  await saveCronStore(state.deps.storePath, state.store);
427
+ // Update file mtime after save to prevent immediate reload
428
+ state.storeFileMtimeMs = await getFileMtimeMs(state.deps.storePath);
57
429
  }