@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,55 +1,298 @@
1
1
  import { sanitizeAgentId } from "../routing/session-key.js";
2
+ import { isRecord } from "../utils.js";
2
3
  import { parseAbsoluteTimeMs } from "./parse.js";
3
4
  import { migrateLegacyCronPayload } from "./payload-migration.js";
5
+ import { inferLegacyName } from "./service/normalize.js";
4
6
  const DEFAULT_OPTIONS = {
5
7
  applyDefaults: false,
6
8
  };
7
- function isRecord(value) {
8
- return typeof value === "object" && value !== null && !Array.isArray(value);
9
- }
10
9
  function coerceSchedule(schedule) {
11
10
  const next = { ...schedule };
12
- const kind = typeof schedule.kind === "string" ? schedule.kind : undefined;
11
+ const rawKind = typeof schedule.kind === "string" ? schedule.kind.trim().toLowerCase() : "";
12
+ const kind = rawKind === "at" || rawKind === "every" || rawKind === "cron" ? rawKind : undefined;
13
13
  const atMsRaw = schedule.atMs;
14
14
  const atRaw = schedule.at;
15
- const parsedAtMs = typeof atMsRaw === "string"
16
- ? parseAbsoluteTimeMs(atMsRaw)
17
- : typeof atRaw === "string"
18
- ? parseAbsoluteTimeMs(atRaw)
19
- : null;
20
- if (!kind) {
15
+ const atString = typeof atRaw === "string" ? atRaw.trim() : "";
16
+ const parsedAtMs = typeof atMsRaw === "number"
17
+ ? atMsRaw
18
+ : typeof atMsRaw === "string"
19
+ ? parseAbsoluteTimeMs(atMsRaw)
20
+ : atString
21
+ ? parseAbsoluteTimeMs(atString)
22
+ : null;
23
+ if (kind) {
24
+ next.kind = kind;
25
+ }
26
+ else {
21
27
  if (typeof schedule.atMs === "number" ||
22
28
  typeof schedule.at === "string" ||
23
- typeof schedule.atMs === "string")
29
+ typeof schedule.atMs === "string") {
24
30
  next.kind = "at";
25
- else if (typeof schedule.everyMs === "number")
31
+ }
32
+ else if (typeof schedule.everyMs === "number") {
26
33
  next.kind = "every";
27
- else if (typeof schedule.expr === "string")
34
+ }
35
+ else if (typeof schedule.expr === "string") {
28
36
  next.kind = "cron";
37
+ }
38
+ }
39
+ if (atString) {
40
+ next.at = parsedAtMs !== null ? new Date(parsedAtMs).toISOString() : atString;
29
41
  }
30
- if (typeof schedule.atMs !== "number" && parsedAtMs !== null) {
31
- next.atMs = parsedAtMs;
42
+ else if (parsedAtMs !== null) {
43
+ next.at = new Date(parsedAtMs).toISOString();
44
+ }
45
+ if ("atMs" in next) {
46
+ delete next.atMs;
32
47
  }
33
- if ("at" in next)
34
- delete next.at;
35
48
  return next;
36
49
  }
37
50
  function coercePayload(payload) {
38
51
  const next = { ...payload };
39
52
  // Back-compat: older configs used `provider` for delivery channel.
40
53
  migrateLegacyCronPayload(next);
54
+ const kindRaw = typeof next.kind === "string" ? next.kind.trim().toLowerCase() : "";
55
+ if (kindRaw === "agentturn") {
56
+ next.kind = "agentTurn";
57
+ }
58
+ else if (kindRaw === "systemevent") {
59
+ next.kind = "systemEvent";
60
+ }
61
+ else if (kindRaw) {
62
+ next.kind = kindRaw;
63
+ }
64
+ if (!next.kind) {
65
+ const hasMessage = typeof next.message === "string" && next.message.trim().length > 0;
66
+ const hasText = typeof next.text === "string" && next.text.trim().length > 0;
67
+ if (hasMessage) {
68
+ next.kind = "agentTurn";
69
+ }
70
+ else if (hasText) {
71
+ next.kind = "systemEvent";
72
+ }
73
+ }
74
+ if (typeof next.message === "string") {
75
+ const trimmed = next.message.trim();
76
+ if (trimmed) {
77
+ next.message = trimmed;
78
+ }
79
+ }
80
+ if (typeof next.text === "string") {
81
+ const trimmed = next.text.trim();
82
+ if (trimmed) {
83
+ next.text = trimmed;
84
+ }
85
+ }
86
+ if ("model" in next) {
87
+ if (typeof next.model === "string") {
88
+ const trimmed = next.model.trim();
89
+ if (trimmed) {
90
+ next.model = trimmed;
91
+ }
92
+ else {
93
+ delete next.model;
94
+ }
95
+ }
96
+ else {
97
+ delete next.model;
98
+ }
99
+ }
100
+ if ("thinking" in next) {
101
+ if (typeof next.thinking === "string") {
102
+ const trimmed = next.thinking.trim();
103
+ if (trimmed) {
104
+ next.thinking = trimmed;
105
+ }
106
+ else {
107
+ delete next.thinking;
108
+ }
109
+ }
110
+ else {
111
+ delete next.thinking;
112
+ }
113
+ }
114
+ if ("timeoutSeconds" in next) {
115
+ if (typeof next.timeoutSeconds === "number" && Number.isFinite(next.timeoutSeconds)) {
116
+ next.timeoutSeconds = Math.max(1, Math.floor(next.timeoutSeconds));
117
+ }
118
+ else {
119
+ delete next.timeoutSeconds;
120
+ }
121
+ }
122
+ if ("allowUnsafeExternalContent" in next &&
123
+ typeof next.allowUnsafeExternalContent !== "boolean") {
124
+ delete next.allowUnsafeExternalContent;
125
+ }
41
126
  return next;
42
127
  }
128
+ function coerceDelivery(delivery) {
129
+ const next = { ...delivery };
130
+ if (typeof delivery.mode === "string") {
131
+ const mode = delivery.mode.trim().toLowerCase();
132
+ if (mode === "deliver") {
133
+ next.mode = "announce";
134
+ }
135
+ else if (mode === "announce" || mode === "none") {
136
+ next.mode = mode;
137
+ }
138
+ else {
139
+ delete next.mode;
140
+ }
141
+ }
142
+ else if ("mode" in next) {
143
+ delete next.mode;
144
+ }
145
+ if (typeof delivery.channel === "string") {
146
+ const trimmed = delivery.channel.trim().toLowerCase();
147
+ if (trimmed) {
148
+ next.channel = trimmed;
149
+ }
150
+ else {
151
+ delete next.channel;
152
+ }
153
+ }
154
+ if (typeof delivery.to === "string") {
155
+ const trimmed = delivery.to.trim();
156
+ if (trimmed) {
157
+ next.to = trimmed;
158
+ }
159
+ else {
160
+ delete next.to;
161
+ }
162
+ }
163
+ return next;
164
+ }
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
+ }
43
208
  function unwrapJob(raw) {
44
- if (isRecord(raw.data))
209
+ if (isRecord(raw.data)) {
45
210
  return raw.data;
46
- if (isRecord(raw.job))
211
+ }
212
+ if (isRecord(raw.job)) {
47
213
  return raw.job;
214
+ }
48
215
  return raw;
49
216
  }
217
+ function normalizeSessionTarget(raw) {
218
+ if (typeof raw !== "string") {
219
+ return undefined;
220
+ }
221
+ const trimmed = raw.trim().toLowerCase();
222
+ if (trimmed === "main" || trimmed === "isolated") {
223
+ return trimmed;
224
+ }
225
+ return undefined;
226
+ }
227
+ function normalizeWakeMode(raw) {
228
+ if (typeof raw !== "string") {
229
+ return undefined;
230
+ }
231
+ const trimmed = raw.trim().toLowerCase();
232
+ if (trimmed === "now" || trimmed === "next-heartbeat") {
233
+ return trimmed;
234
+ }
235
+ return undefined;
236
+ }
237
+ function copyTopLevelAgentTurnFields(next, payload) {
238
+ const copyString = (field) => {
239
+ if (typeof payload[field] === "string" && payload[field].trim()) {
240
+ return;
241
+ }
242
+ const value = next[field];
243
+ if (typeof value === "string" && value.trim()) {
244
+ payload[field] = value.trim();
245
+ }
246
+ };
247
+ copyString("model");
248
+ copyString("thinking");
249
+ if (typeof payload.timeoutSeconds !== "number" && typeof next.timeoutSeconds === "number") {
250
+ payload.timeoutSeconds = next.timeoutSeconds;
251
+ }
252
+ if (typeof payload.allowUnsafeExternalContent !== "boolean" &&
253
+ typeof next.allowUnsafeExternalContent === "boolean") {
254
+ payload.allowUnsafeExternalContent = next.allowUnsafeExternalContent;
255
+ }
256
+ }
257
+ function copyTopLevelLegacyDeliveryFields(next, payload) {
258
+ if (typeof payload.deliver !== "boolean" && typeof next.deliver === "boolean") {
259
+ payload.deliver = next.deliver;
260
+ }
261
+ if (typeof payload.channel !== "string" &&
262
+ typeof next.channel === "string" &&
263
+ next.channel.trim()) {
264
+ payload.channel = next.channel.trim();
265
+ }
266
+ if (typeof payload.to !== "string" && typeof next.to === "string" && next.to.trim()) {
267
+ payload.to = next.to.trim();
268
+ }
269
+ if (typeof payload.bestEffortDeliver !== "boolean" &&
270
+ typeof next.bestEffortDeliver === "boolean") {
271
+ payload.bestEffortDeliver = next.bestEffortDeliver;
272
+ }
273
+ if (typeof payload.provider !== "string" &&
274
+ typeof next.provider === "string" &&
275
+ next.provider.trim()) {
276
+ payload.provider = next.provider.trim();
277
+ }
278
+ }
279
+ function stripLegacyTopLevelFields(next) {
280
+ delete next.model;
281
+ delete next.thinking;
282
+ delete next.timeoutSeconds;
283
+ delete next.allowUnsafeExternalContent;
284
+ delete next.message;
285
+ delete next.text;
286
+ delete next.deliver;
287
+ delete next.channel;
288
+ delete next.to;
289
+ delete next.bestEffortDeliver;
290
+ delete next.provider;
291
+ }
50
292
  export function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
51
- if (!isRecord(raw))
293
+ if (!isRecord(raw)) {
52
294
  return null;
295
+ }
53
296
  const base = unwrapJob(raw);
54
297
  const next = { ...base };
55
298
  if ("agentId" in base) {
@@ -59,10 +302,12 @@ export function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
59
302
  }
60
303
  else if (typeof agentId === "string") {
61
304
  const trimmed = agentId.trim();
62
- if (trimmed)
305
+ if (trimmed) {
63
306
  next.agentId = sanitizeAgentId(trimmed);
64
- else
307
+ }
308
+ else {
65
309
  delete next.agentId;
310
+ }
66
311
  }
67
312
  }
68
313
  if ("enabled" in base) {
@@ -72,27 +317,110 @@ export function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
72
317
  }
73
318
  else if (typeof enabled === "string") {
74
319
  const trimmed = enabled.trim().toLowerCase();
75
- if (trimmed === "true")
320
+ if (trimmed === "true") {
76
321
  next.enabled = true;
77
- if (trimmed === "false")
322
+ }
323
+ if (trimmed === "false") {
78
324
  next.enabled = false;
325
+ }
326
+ }
327
+ }
328
+ if ("sessionTarget" in base) {
329
+ const normalized = normalizeSessionTarget(base.sessionTarget);
330
+ if (normalized) {
331
+ next.sessionTarget = normalized;
332
+ }
333
+ else {
334
+ delete next.sessionTarget;
335
+ }
336
+ }
337
+ if ("wakeMode" in base) {
338
+ const normalized = normalizeWakeMode(base.wakeMode);
339
+ if (normalized) {
340
+ next.wakeMode = normalized;
341
+ }
342
+ else {
343
+ delete next.wakeMode;
79
344
  }
80
345
  }
81
346
  if (isRecord(base.schedule)) {
82
347
  next.schedule = coerceSchedule(base.schedule);
83
348
  }
349
+ if (!("payload" in next) || !isRecord(next.payload)) {
350
+ const message = typeof next.message === "string" ? next.message.trim() : "";
351
+ const text = typeof next.text === "string" ? next.text.trim() : "";
352
+ if (message) {
353
+ next.payload = { kind: "agentTurn", message };
354
+ }
355
+ else if (text) {
356
+ next.payload = { kind: "systemEvent", text };
357
+ }
358
+ }
84
359
  if (isRecord(base.payload)) {
85
360
  next.payload = coercePayload(base.payload);
86
361
  }
362
+ if (isRecord(base.delivery)) {
363
+ next.delivery = coerceDelivery(base.delivery);
364
+ }
365
+ if ("isolation" in next) {
366
+ delete next.isolation;
367
+ }
368
+ const payload = isRecord(next.payload) ? next.payload : null;
369
+ if (payload && payload.kind === "agentTurn") {
370
+ copyTopLevelAgentTurnFields(next, payload);
371
+ copyTopLevelLegacyDeliveryFields(next, payload);
372
+ }
373
+ stripLegacyTopLevelFields(next);
87
374
  if (options.applyDefaults) {
88
- if (!next.wakeMode)
89
- next.wakeMode = "next-heartbeat";
375
+ if (!next.wakeMode) {
376
+ next.wakeMode = "now";
377
+ }
378
+ if (typeof next.enabled !== "boolean") {
379
+ next.enabled = true;
380
+ }
381
+ if ((typeof next.name !== "string" || !next.name.trim()) &&
382
+ isRecord(next.schedule) &&
383
+ isRecord(next.payload)) {
384
+ next.name = inferLegacyName({
385
+ schedule: next.schedule,
386
+ payload: next.payload,
387
+ });
388
+ }
389
+ else if (typeof next.name === "string") {
390
+ const trimmed = next.name.trim();
391
+ if (trimmed) {
392
+ next.name = trimmed;
393
+ }
394
+ }
90
395
  if (!next.sessionTarget && isRecord(next.payload)) {
91
396
  const kind = typeof next.payload.kind === "string" ? next.payload.kind : "";
92
- if (kind === "systemEvent")
397
+ if (kind === "systemEvent") {
93
398
  next.sessionTarget = "main";
94
- if (kind === "agentTurn")
399
+ }
400
+ if (kind === "agentTurn") {
95
401
  next.sessionTarget = "isolated";
402
+ }
403
+ }
404
+ if ("schedule" in next &&
405
+ isRecord(next.schedule) &&
406
+ next.schedule.kind === "at" &&
407
+ !("deleteAfterRun" in next)) {
408
+ next.deleteAfterRun = true;
409
+ }
410
+ const payload = isRecord(next.payload) ? next.payload : null;
411
+ const payloadKind = payload && typeof payload.kind === "string" ? payload.kind : "";
412
+ const sessionTarget = typeof next.sessionTarget === "string" ? next.sessionTarget : "";
413
+ const isIsolatedAgentTurn = sessionTarget === "isolated" || (sessionTarget === "" && payloadKind === "agentTurn");
414
+ const hasDelivery = "delivery" in next && next.delivery !== undefined;
415
+ const hasLegacyDelivery = payload ? hasLegacyDeliveryHints(payload) : false;
416
+ if (!hasDelivery && isIsolatedAgentTurn && payloadKind === "agentTurn") {
417
+ if (payload && hasLegacyDelivery) {
418
+ next.delivery = buildDeliveryFromLegacyPayload(payload);
419
+ stripLegacyDeliveryFields(payload);
420
+ }
421
+ else {
422
+ next.delivery = { mode: "announce" };
423
+ }
96
424
  }
97
425
  }
98
426
  return next;
@@ -2,22 +2,27 @@ const ISO_TZ_RE = /(Z|[+-]\d{2}:?\d{2})$/i;
2
2
  const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
3
3
  const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T/;
4
4
  function normalizeUtcIso(raw) {
5
- if (ISO_TZ_RE.test(raw))
5
+ if (ISO_TZ_RE.test(raw)) {
6
6
  return raw;
7
- if (ISO_DATE_RE.test(raw))
7
+ }
8
+ if (ISO_DATE_RE.test(raw)) {
8
9
  return `${raw}T00:00:00Z`;
9
- if (ISO_DATE_TIME_RE.test(raw))
10
+ }
11
+ if (ISO_DATE_TIME_RE.test(raw)) {
10
12
  return `${raw}Z`;
13
+ }
11
14
  return raw;
12
15
  }
13
16
  export function parseAbsoluteTimeMs(input) {
14
17
  const raw = input.trim();
15
- if (!raw)
18
+ if (!raw) {
16
19
  return null;
20
+ }
17
21
  if (/^\d+$/.test(raw)) {
18
22
  const n = Number(raw);
19
- if (Number.isFinite(n) && n > 0)
23
+ if (Number.isFinite(n) && n > 0) {
20
24
  return Math.floor(n);
25
+ }
21
26
  }
22
27
  const parsed = Date.parse(normalizeUtcIso(raw));
23
28
  return Number.isFinite(parsed) ? parsed : null;
@@ -8,8 +8,9 @@ export function resolveCronRunLogPath(params) {
8
8
  const writesByPath = new Map();
9
9
  async function pruneIfNeeded(filePath, opts) {
10
10
  const stat = await fs.stat(filePath).catch(() => null);
11
- if (!stat || stat.size <= opts.maxBytes)
11
+ if (!stat || stat.size <= opts.maxBytes) {
12
12
  return;
13
+ }
13
14
  const raw = await fs.readFile(filePath, "utf-8").catch(() => "");
14
15
  const lines = raw
15
16
  .split("\n")
@@ -40,31 +41,55 @@ export async function readCronRunLogEntries(filePath, opts) {
40
41
  const limit = Math.max(1, Math.min(5000, Math.floor(opts?.limit ?? 200)));
41
42
  const jobId = opts?.jobId?.trim() || undefined;
42
43
  const raw = await fs.readFile(path.resolve(filePath), "utf-8").catch(() => "");
43
- if (!raw.trim())
44
+ if (!raw.trim()) {
44
45
  return [];
46
+ }
45
47
  const parsed = [];
46
48
  const lines = raw.split("\n");
47
49
  for (let i = lines.length - 1; i >= 0 && parsed.length < limit; i--) {
48
50
  const line = lines[i]?.trim();
49
- if (!line)
51
+ if (!line) {
50
52
  continue;
53
+ }
51
54
  try {
52
55
  const obj = JSON.parse(line);
53
- if (!obj || typeof obj !== "object")
56
+ if (!obj || typeof obj !== "object") {
54
57
  continue;
55
- if (obj.action !== "finished")
58
+ }
59
+ if (obj.action !== "finished") {
56
60
  continue;
57
- if (typeof obj.jobId !== "string" || obj.jobId.trim().length === 0)
61
+ }
62
+ if (typeof obj.jobId !== "string" || obj.jobId.trim().length === 0) {
58
63
  continue;
59
- if (typeof obj.ts !== "number" || !Number.isFinite(obj.ts))
64
+ }
65
+ if (typeof obj.ts !== "number" || !Number.isFinite(obj.ts)) {
60
66
  continue;
61
- if (jobId && obj.jobId !== jobId)
67
+ }
68
+ if (jobId && obj.jobId !== jobId) {
62
69
  continue;
63
- parsed.push(obj);
70
+ }
71
+ const entry = {
72
+ ts: obj.ts,
73
+ jobId: obj.jobId,
74
+ action: "finished",
75
+ status: obj.status,
76
+ error: obj.error,
77
+ summary: obj.summary,
78
+ runAtMs: obj.runAtMs,
79
+ durationMs: obj.durationMs,
80
+ nextRunAtMs: obj.nextRunAtMs,
81
+ };
82
+ if (typeof obj.sessionId === "string" && obj.sessionId.trim().length > 0) {
83
+ entry.sessionId = obj.sessionId;
84
+ }
85
+ if (typeof obj.sessionKey === "string" && obj.sessionKey.trim().length > 0) {
86
+ entry.sessionKey = obj.sessionKey;
87
+ }
88
+ parsed.push(entry);
64
89
  }
65
90
  catch {
66
91
  // ignore invalid lines
67
92
  }
68
93
  }
69
- return parsed.reverse();
94
+ return parsed.toReversed();
70
95
  }
@@ -1,24 +1,59 @@
1
1
  import { Cron } from "croner";
2
+ import { parseAbsoluteTimeMs } from "./parse.js";
3
+ function resolveCronTimezone(tz) {
4
+ const trimmed = typeof tz === "string" ? tz.trim() : "";
5
+ if (trimmed) {
6
+ return trimmed;
7
+ }
8
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
9
+ }
2
10
  export function computeNextRunAtMs(schedule, nowMs) {
3
11
  if (schedule.kind === "at") {
4
- return schedule.atMs > nowMs ? schedule.atMs : undefined;
12
+ // Handle both canonical `at` (string) and legacy `atMs` (number) fields.
13
+ // The store migration should convert atMs→at, but be defensive in case
14
+ // the migration hasn't run yet or was bypassed.
15
+ const sched = schedule;
16
+ const atMs = typeof sched.atMs === "number" && Number.isFinite(sched.atMs) && sched.atMs > 0
17
+ ? sched.atMs
18
+ : typeof sched.atMs === "string"
19
+ ? parseAbsoluteTimeMs(sched.atMs)
20
+ : typeof sched.at === "string"
21
+ ? parseAbsoluteTimeMs(sched.at)
22
+ : null;
23
+ if (atMs === null) {
24
+ return undefined;
25
+ }
26
+ return atMs > nowMs ? atMs : undefined;
5
27
  }
6
28
  if (schedule.kind === "every") {
7
29
  const everyMs = Math.max(1, Math.floor(schedule.everyMs));
8
30
  const anchor = Math.max(0, Math.floor(schedule.anchorMs ?? nowMs));
9
- if (nowMs < anchor)
31
+ if (nowMs < anchor) {
10
32
  return anchor;
33
+ }
11
34
  const elapsed = nowMs - anchor;
12
35
  const steps = Math.max(1, Math.floor((elapsed + everyMs - 1) / everyMs));
13
36
  return anchor + steps * everyMs;
14
37
  }
15
38
  const expr = schedule.expr.trim();
16
- if (!expr)
39
+ if (!expr) {
17
40
  return undefined;
41
+ }
18
42
  const cron = new Cron(expr, {
19
- timezone: schedule.tz?.trim() || undefined,
43
+ timezone: resolveCronTimezone(schedule.tz),
20
44
  catch: false,
21
45
  });
22
- const next = cron.nextRun(new Date(nowMs));
23
- return next ? next.getTime() : undefined;
46
+ // Cron operates at second granularity, so floor nowMs to the start of the
47
+ // current second. This prevents the lookback from landing inside a matching
48
+ // second — if nowMs is e.g. 12:00:00.500 and the pattern fires at second 0,
49
+ // a 1ms lookback (12:00:00.499) is still *within* that second, causing
50
+ // croner to skip ahead to the next occurrence (e.g. the following day).
51
+ // Flooring first ensures the lookback always falls in the *previous* second.
52
+ const nowSecondMs = Math.floor(nowMs / 1000) * 1000;
53
+ const next = cron.nextRun(new Date(nowSecondMs - 1));
54
+ if (!next) {
55
+ return undefined;
56
+ }
57
+ const nextMs = next.getTime();
58
+ return Number.isFinite(nextMs) && nextMs >= nowSecondMs ? nextMs : undefined;
24
59
  }