@capybara-hq/kernel 0.1.0-rc.23 → 0.1.0-rc.25

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 (293) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin.module.d.ts +2 -1
  3. package/dist/admin/admin.module.js +9 -3
  4. package/dist/admin/health.service.d.ts +2 -2
  5. package/dist/admin/health.service.js +6 -2
  6. package/dist/admin/outbox-dead.controller.d.ts +23 -0
  7. package/dist/admin/outbox-dead.controller.js +62 -0
  8. package/dist/admin/outbox-dead.schemas.d.ts +8 -0
  9. package/dist/admin/outbox-dead.schemas.js +18 -0
  10. package/dist/admin/outbox-dead.service.d.ts +66 -0
  11. package/dist/admin/outbox-dead.service.js +179 -0
  12. package/dist/admin/overview.service.d.ts +40 -5
  13. package/dist/admin/overview.service.js +41 -7
  14. package/dist/agents/agents.module.js +3 -0
  15. package/dist/agents/agents.service.d.ts +6 -1
  16. package/dist/agents/agents.service.js +11 -2
  17. package/dist/agents/from-template.js +1 -1
  18. package/dist/background/finish.d.ts +5 -2
  19. package/dist/background/finish.js +14 -3
  20. package/dist/background/launch.d.ts +2 -1
  21. package/dist/background/launch.js +5 -2
  22. package/dist/background/store.d.ts +8 -0
  23. package/dist/background/store.js +24 -0
  24. package/dist/catalog/admin-catalog.controller.d.ts +21 -2
  25. package/dist/catalog/admin-catalog.controller.js +45 -6
  26. package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
  27. package/dist/catalog/admin-catalog.schemas.js +8 -0
  28. package/dist/catalog/catalog-import.module.js +0 -4
  29. package/dist/catalog/catalog-scope.service.d.ts +63 -26
  30. package/dist/catalog/catalog-scope.service.js +146 -71
  31. package/dist/catalog/catalog.module.d.ts +2 -1
  32. package/dist/catalog/catalog.module.js +9 -4
  33. package/dist/catalog/catalog.service.d.ts +11 -4
  34. package/dist/catalog/catalog.service.js +21 -5
  35. package/dist/catalog/cli.d.ts +4 -3
  36. package/dist/catalog/cli.js +8 -13
  37. package/dist/catalog/job-ids.d.ts +8 -20
  38. package/dist/catalog/job-ids.js +11 -16
  39. package/dist/catalog/marketplaces.service.d.ts +1 -1
  40. package/dist/catalog/org-plugin-uploads.d.ts +11 -4
  41. package/dist/catalog/org-plugin-uploads.js +21 -6
  42. package/dist/catalog/owned-items.d.ts +6 -13
  43. package/dist/catalog/owned-items.js +11 -29
  44. package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
  45. package/dist/catalog/owned-plugin-cleanup.js +12 -66
  46. package/dist/catalog/platform-components.d.ts +56 -34
  47. package/dist/catalog/platform-components.js +68 -78
  48. package/dist/catalog/platform-import-update.d.ts +50 -0
  49. package/dist/catalog/platform-import-update.js +116 -0
  50. package/dist/catalog/plugin-import.d.ts +64 -26
  51. package/dist/catalog/plugin-import.js +113 -77
  52. package/dist/catalog/plugin-upgrade.d.ts +22 -4
  53. package/dist/catalog/plugin-upgrade.js +26 -19
  54. package/dist/catalog/scope-jobs.d.ts +59 -0
  55. package/dist/catalog/scope-jobs.js +65 -0
  56. package/dist/catalog/scope-sync-workers.d.ts +22 -0
  57. package/dist/catalog/scope-sync-workers.js +58 -2
  58. package/dist/catalog/scope-sync.d.ts +40 -65
  59. package/dist/catalog/scope-sync.js +114 -122
  60. package/dist/channels/channels.module.d.ts +5 -2
  61. package/dist/channels/channels.module.js +9 -2
  62. package/dist/channels/deliver-job.d.ts +25 -0
  63. package/dist/channels/deliver-job.js +25 -0
  64. package/dist/channels/inbound.d.ts +1 -1
  65. package/dist/channels/inbound.js +4 -17
  66. package/dist/channels/jobs.d.ts +28 -18
  67. package/dist/channels/jobs.js +63 -26
  68. package/dist/channels/threads.d.ts +4 -3
  69. package/dist/channels/threads.js +4 -3
  70. package/dist/config/schema.d.ts +0 -3
  71. package/dist/config/schema.js +11 -20
  72. package/dist/conversations/agent-tree.d.ts +16 -0
  73. package/dist/conversations/agent-tree.js +36 -0
  74. package/dist/conversations/conversations.controller.js +4 -2
  75. package/dist/conversations/conversations.service.d.ts +61 -19
  76. package/dist/conversations/conversations.service.js +69 -43
  77. package/dist/conversations/event-log.d.ts +7 -10
  78. package/dist/conversations/event-log.js +24 -15
  79. package/dist/conversations/events.d.ts +21 -0
  80. package/dist/conversations/events.js +26 -2
  81. package/dist/conversations/queue.service.d.ts +8 -3
  82. package/dist/conversations/queue.service.js +37 -13
  83. package/dist/conversations/source.d.ts +10 -0
  84. package/dist/conversations/source.js +17 -0
  85. package/dist/conversations/titles.d.ts +21 -8
  86. package/dist/conversations/titles.js +42 -15
  87. package/dist/conversations/tree.d.ts +10 -0
  88. package/dist/conversations/tree.js +35 -0
  89. package/dist/db/kernel-schema.d.ts +35 -22
  90. package/dist/devices/devices.service.d.ts +23 -11
  91. package/dist/devices/devices.service.js +34 -26
  92. package/dist/errors.d.ts +11 -0
  93. package/dist/errors.js +11 -0
  94. package/dist/evals/run-replay.d.ts +13 -2
  95. package/dist/evals/run-replay.js +42 -9
  96. package/dist/evals/snapshot.d.ts +7 -1
  97. package/dist/evals/snapshot.js +2 -1
  98. package/dist/extensions/extensions.module.js +3 -0
  99. package/dist/extensions/hooks.d.ts +22 -3
  100. package/dist/extensions/hooks.js +15 -1
  101. package/dist/extensions/lifecycle-hooks.d.ts +99 -0
  102. package/dist/extensions/lifecycle-hooks.js +273 -0
  103. package/dist/extensions/lifecycle.d.ts +6 -1
  104. package/dist/extensions/lifecycle.js +9 -2
  105. package/dist/extensions/org-config.d.ts +13 -1
  106. package/dist/extensions/org-config.js +19 -2
  107. package/dist/extensions/permission-view.d.ts +9 -5
  108. package/dist/extensions/permission-view.js +9 -4
  109. package/dist/extensions/point-defaults.d.ts +28 -1
  110. package/dist/extensions/point-defaults.js +44 -13
  111. package/dist/extensions/usage-subscribers.d.ts +43 -0
  112. package/dist/extensions/usage-subscribers.js +108 -0
  113. package/dist/http/api.module.js +2 -0
  114. package/dist/inbox/inbox.service.d.ts +6 -20
  115. package/dist/inbox/inbox.service.js +0 -3
  116. package/dist/inbox/org-notices.d.ts +1 -1
  117. package/dist/inbox/platform-inbox.service.d.ts +3 -32
  118. package/dist/inbox/platform-inbox.service.js +5 -54
  119. package/dist/index.d.ts +1 -0
  120. package/dist/index.js +1 -0
  121. package/dist/infra/cli/kernel-cli.module.js +5 -0
  122. package/dist/infra/events/kernel-events.d.ts +9 -35
  123. package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
  124. package/dist/infra/outbox/outbox-relay.module.js +18 -0
  125. package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
  126. package/dist/infra/outbox/outbox.dispatcher.js +310 -0
  127. package/dist/infra/outbox/outbox.module.d.ts +24 -0
  128. package/dist/infra/outbox/outbox.module.js +50 -0
  129. package/dist/infra/outbox/outbox.relay.d.ts +31 -0
  130. package/dist/infra/outbox/outbox.relay.js +76 -0
  131. package/dist/infra/outbox/outbox.service.d.ts +51 -0
  132. package/dist/infra/outbox/outbox.service.js +131 -0
  133. package/dist/infra/outbox/registry.d.ts +244 -0
  134. package/dist/infra/outbox/registry.js +234 -0
  135. package/dist/infra/queue/queue-names.d.ts +8 -0
  136. package/dist/infra/queue/queue-names.js +17 -0
  137. package/dist/kernel.module.js +3 -0
  138. package/dist/local-devices/device-policy.service.d.ts +4 -0
  139. package/dist/local-devices/device-policy.service.js +61 -3
  140. package/dist/local-devices/machines.d.ts +13 -5
  141. package/dist/local-devices/machines.js +45 -7
  142. package/dist/local-devices/policy-summary.d.ts +44 -1
  143. package/dist/local-devices/policy-summary.js +69 -0
  144. package/dist/models/embeddings.d.ts +9 -9
  145. package/dist/models/embeddings.js +11 -18
  146. package/dist/models/models.module.js +3 -0
  147. package/dist/orgs/directory-sync.js +1 -1
  148. package/dist/orgs/members.service.js +1 -1
  149. package/dist/orgs/offboarding.d.ts +50 -57
  150. package/dist/orgs/offboarding.js +117 -162
  151. package/dist/orgs/orgs.module.js +3 -0
  152. package/dist/orgs/orgs.service.d.ts +11 -5
  153. package/dist/orgs/orgs.service.js +22 -6
  154. package/dist/overrides.d.ts +23 -3
  155. package/dist/overrides.js +12 -2
  156. package/dist/platform/exclusions.js +5 -0
  157. package/dist/platform/ops/admin.ops.js +18 -2
  158. package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
  159. package/dist/platform/ops/catalog-admin.ops.js +32 -4
  160. package/dist/platform/ops/inbox.ops.js +4 -20
  161. package/dist/platform/ops/outbox.ops.d.ts +21 -0
  162. package/dist/platform/ops/outbox.ops.js +125 -0
  163. package/dist/platform/ops/platform.ops.d.ts +1 -0
  164. package/dist/platform/ops/platform.ops.js +6 -3
  165. package/dist/platform/ops/schedules.ops.js +40 -1
  166. package/dist/platform/platform-ops.module.d.ts +2 -1
  167. package/dist/platform/platform-ops.module.js +3 -0
  168. package/dist/platform/route-schemas.js +3 -0
  169. package/dist/roles/worker-role.d.ts +10 -5
  170. package/dist/roles/worker-role.js +19 -9
  171. package/dist/roles/worker.module.js +11 -8
  172. package/dist/runtime/context/build.d.ts +3 -0
  173. package/dist/runtime/context/build.js +15 -0
  174. package/dist/runtime/context/plugin-context.d.ts +20 -0
  175. package/dist/runtime/context/plugin-context.js +30 -0
  176. package/dist/runtime/langgraph-loop.js +17 -5
  177. package/dist/runtime/limits.d.ts +15 -0
  178. package/dist/runtime/limits.js +25 -0
  179. package/dist/runtime/loop.d.ts +16 -3
  180. package/dist/runtime/loop.js +2 -1
  181. package/dist/runtime/maintenance.d.ts +7 -3
  182. package/dist/runtime/maintenance.js +52 -12
  183. package/dist/runtime/model-call.d.ts +20 -17
  184. package/dist/runtime/model-call.js +15 -24
  185. package/dist/runtime/plugin-context.service.d.ts +69 -0
  186. package/dist/runtime/plugin-context.service.js +208 -0
  187. package/dist/runtime/queue-lost-jobs.d.ts +24 -0
  188. package/dist/runtime/queue-lost-jobs.js +51 -0
  189. package/dist/runtime/run-control.d.ts +47 -5
  190. package/dist/runtime/run-control.js +79 -12
  191. package/dist/runtime/run-finish.d.ts +39 -12
  192. package/dist/runtime/run-finish.js +97 -14
  193. package/dist/runtime/run-jobs.d.ts +95 -13
  194. package/dist/runtime/run-jobs.js +199 -28
  195. package/dist/runtime/run-orphan-check.job.d.ts +142 -0
  196. package/dist/runtime/run-orphan-check.job.js +339 -0
  197. package/dist/runtime/run-processors.d.ts +16 -9
  198. package/dist/runtime/run-processors.js +1 -1
  199. package/dist/runtime/run-queue.d.ts +75 -53
  200. package/dist/runtime/run-queue.js +189 -263
  201. package/dist/runtime/run-steps.d.ts +1 -2
  202. package/dist/runtime/run-steps.js +8 -11
  203. package/dist/runtime/run-worker.d.ts +26 -16
  204. package/dist/runtime/run-worker.js +132 -37
  205. package/dist/runtime/runtime.module.d.ts +2 -3
  206. package/dist/runtime/runtime.module.js +7 -6
  207. package/dist/runtime/step-log.d.ts +16 -4
  208. package/dist/runtime/step-log.js +26 -6
  209. package/dist/runtime/stream.controller.d.ts +2 -4
  210. package/dist/runtime/stream.controller.js +5 -11
  211. package/dist/runtime/subagents/child.js +1 -1
  212. package/dist/runtime/subagents/definition.d.ts +2 -1
  213. package/dist/runtime/subagents/definition.js +4 -2
  214. package/dist/runtime/subagents/deliveries.d.ts +6 -3
  215. package/dist/runtime/subagents/deliveries.js +16 -7
  216. package/dist/runtime/subagents/dispatch.d.ts +12 -12
  217. package/dist/runtime/subagents/dispatch.js +14 -38
  218. package/dist/runtime/subagents/messages.d.ts +5 -1
  219. package/dist/runtime/subagents/messages.js +11 -3
  220. package/dist/runtime/subagents/skill-fork.d.ts +3 -3
  221. package/dist/runtime/subagents/skill-fork.js +8 -5
  222. package/dist/runtime/subagents/subagents.service.js +1 -1
  223. package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
  224. package/dist/runtime/subagents/tree-jobs.js +86 -0
  225. package/dist/runtime/usage-report-jobs.d.ts +23 -48
  226. package/dist/runtime/usage-report-jobs.js +67 -119
  227. package/dist/runtime/usage-report.d.ts +28 -77
  228. package/dist/runtime/usage-report.js +35 -213
  229. package/dist/runtime/waits.d.ts +48 -198
  230. package/dist/runtime/waits.js +103 -805
  231. package/dist/sandbox/jobs.d.ts +20 -10
  232. package/dist/sandbox/jobs.js +23 -17
  233. package/dist/sandbox/sandbox.service.d.ts +17 -4
  234. package/dist/sandbox/sandbox.service.js +55 -14
  235. package/dist/schedules/calendar.d.ts +134 -0
  236. package/dist/schedules/calendar.js +410 -0
  237. package/dist/schedules/fire.d.ts +26 -7
  238. package/dist/schedules/fire.js +91 -22
  239. package/dist/schedules/job-ids.d.ts +66 -17
  240. package/dist/schedules/job-ids.js +81 -13
  241. package/dist/schedules/run-tracker.d.ts +0 -5
  242. package/dist/schedules/run-tracker.js +2 -14
  243. package/dist/schedules/scan.job.d.ts +13 -32
  244. package/dist/schedules/scan.job.js +50 -164
  245. package/dist/schedules/schedule-math.d.ts +42 -0
  246. package/dist/schedules/schedule-math.js +52 -0
  247. package/dist/schedules/scheduled-tasks.service.d.ts +22 -6
  248. package/dist/schedules/scheduled-tasks.service.js +70 -14
  249. package/dist/schedules/schedules.controller.d.ts +5 -1
  250. package/dist/schedules/schedules.controller.js +15 -1
  251. package/dist/schedules/schedules.module.js +0 -4
  252. package/dist/schedules/schedules.schemas.d.ts +35 -0
  253. package/dist/schedules/schedules.schemas.js +27 -0
  254. package/dist/schedules/store.d.ts +5 -0
  255. package/dist/schedules/store.js +20 -0
  256. package/dist/skills/changes.controller.d.ts +1 -1
  257. package/dist/skills/changes.controller.js +1 -1
  258. package/dist/tools/approvals.service.d.ts +41 -11
  259. package/dist/tools/approvals.service.js +108 -14
  260. package/dist/tools/builtin-names.d.ts +5 -3
  261. package/dist/tools/builtin-names.js +12 -4
  262. package/dist/tools/builtins/builtin-tools.d.ts +1 -2
  263. package/dist/tools/builtins/builtin-tools.js +21 -17
  264. package/dist/tools/builtins/sandbox/bash.js +9 -3
  265. package/dist/tools/builtins/sandbox/common.d.ts +18 -1
  266. package/dist/tools/builtins/sandbox/common.js +14 -1
  267. package/dist/tools/builtins/sandbox/index.d.ts +2 -1
  268. package/dist/tools/builtins/sandbox/index.js +4 -1
  269. package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
  270. package/dist/tools/builtins/sandbox/task-list.js +130 -0
  271. package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
  272. package/dist/tools/builtins/sandbox/task-output.js +21 -3
  273. package/dist/tools/builtins/web/extract.d.ts +2 -4
  274. package/dist/tools/builtins/web/extract.js +2 -5
  275. package/dist/tools/pipeline.d.ts +12 -3
  276. package/dist/tools/pipeline.js +6 -1
  277. package/dist/tools/questions.service.d.ts +30 -10
  278. package/dist/tools/questions.service.js +41 -27
  279. package/dist/tools/registry.d.ts +12 -3
  280. package/dist/tools/registry.js +13 -3
  281. package/dist/tools/tools.module.js +4 -3
  282. package/package.json +3 -3
  283. package/src/migrations/0118_device_policy_ops.sql +6 -0
  284. package/src/migrations/0119_outbox.sql +22 -0
  285. package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
  286. package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
  287. package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
  288. package/src/migrations/0123_pgvector.sql +27 -0
  289. package/src/migrations/0124_conversation_source.sql +36 -0
  290. package/dist/catalog/platform-migration.d.ts +0 -115
  291. package/dist/catalog/platform-migration.js +0 -583
  292. package/dist/infra/queue/add-job-once.d.ts +0 -14
  293. package/dist/infra/queue/add-job-once.js +0 -16
@@ -8,31 +8,22 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { randomUUID } from "node:crypto";
11
- import { getQueueToken } from "@nestjs/bullmq";
12
11
  import { Inject, Injectable } from "@nestjs/common";
13
- import { sql } from "kysely";
14
12
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
15
- import { afterCommit } from "../infra/database/after-commit.js";
16
13
  import { KernelDb } from "../infra/database/kernel-db.js";
17
14
  import { TransactionHost } from "../infra/database/transaction-host.js";
18
15
  import { Transactional } from "../infra/database/transactional.decorator.js";
19
16
  import { LockService } from "../infra/lock/lock.service.js";
17
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
20
18
  import { LockedCron } from "../infra/schedule/locked-cron.decorator.js";
21
19
  import { KERNEL_LOGGER } from "../infra/tokens.js";
22
- import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
23
20
  import { readScheduleSettings } from "./effective-settings.js";
24
- import { SCHED_SCAN_JOB, schedFireJob } from "./job-ids.js";
21
+ import { oweSchedFire, oweSchedSettleUnfired, SCHED_SCAN_JOB } from "./job-ids.js";
25
22
  import { pauseTaskIn } from "./pause.js";
26
- import { lockTaskRow, ScheduledTaskRunTracker } from "./run-tracker.js";
27
- import { dueTicks, nextTick } from "./schedule-math.js";
23
+ import { scanStep } from "./schedule-math.js";
28
24
  import { ScheduledTaskStore } from "./store.js";
29
25
  /** Due tasks per scan; what is left waits for the next minute. */
30
26
  const MAX_TASKS = 2000;
31
- /** A pending task run never fired this long after its tick: its fire job is enqueued again. */
32
- const REPAIR_AFTER_MS = 10 * 60_000;
33
- /** ... and this long after its tick it is given up (`skipped`, `missed`). */
34
- const GIVE_UP_AFTER_MS = 60 * 60_000;
35
- const REPAIR_BATCH = 100;
36
27
  /**
37
28
  * The schedule math could not evaluate a due task's stored trigger (its transaction rolled back);
38
29
  * the scan pauses it. Only the math raises it: a database error fails the scan pass instead (the
@@ -51,32 +42,28 @@ class TaskScanFailed extends Error {
51
42
  * same task): the ticks missed outside the org's `catchUpWindowMinutes` become one `skipped` run
52
43
  * (`missed`, at the first missed tick), the latest tick in the window gets its run (the unique
53
44
  * `(task_id, scheduled_for)` key makes a second insert a no-op), `next_run_at` moves to the next
54
- * tick plus the task's jitter (or a `once` task ends, or one past its `expires_at`), and after the
55
- * commit `sched-fire-<taskRunId>` is enqueued. A task whose stored trigger the schedule math cannot
56
- * evaluate is paused (`invalid_schedule`) instead of failing the scan; a database error fails the
57
- * pass (the next minute retries) and pauses nothing.
58
- *
59
- * Then the repair: a run still `pending` and unfired 10 minutes after its tick has its fire job
60
- * enqueued again, and is given up (`skipped`, `missed`) after an hour; a run fired an hour ago and
61
- * still `pending` (its start failed for good) goes through the run tracker's check, which links,
62
- * settles or releases it, so a `once` task's last run never stays pending.
45
+ * tick plus the task's jitter (or a `once` task ends, or one past its `expires_at`), and the same
46
+ * transaction owes `sched-fire-<taskRunId>` (transactional outbox spec §8.1), so a written tick is
47
+ * never left unfired and the scan needs no repair of lost fire jobs (a fire delivered too late
48
+ * marks its tick missed itself, `ScheduledTaskFirer`), and the run's unfired settle check (a fire
49
+ * job that failed for good before its claim: the tick is missed, `job-ids.ts`). A task whose stored trigger the schedule
50
+ * math cannot evaluate is paused (`invalid_schedule`) instead of failing the scan; a database
51
+ * error fails the pass (the next minute retries) and pauses nothing.
63
52
  */
64
53
  let ScheduledTaskScanJob = class ScheduledTaskScanJob {
65
54
  kdb;
66
55
  txHost;
67
56
  config;
68
57
  store;
69
- tracker;
70
- maintenance;
58
+ outbox;
71
59
  lockService;
72
60
  log;
73
- constructor(kdb, txHost, config, store, tracker, maintenance, lockService, log) {
61
+ constructor(kdb, txHost, config, store, outbox, lockService, log) {
74
62
  this.kdb = kdb;
75
63
  this.txHost = txHost;
76
64
  this.config = config;
77
65
  this.store = store;
78
- this.tracker = tracker;
79
- this.maintenance = maintenance;
66
+ this.outbox = outbox;
80
67
  this.lockService = lockService;
81
68
  this.log = log;
82
69
  }
@@ -86,34 +73,25 @@ let ScheduledTaskScanJob = class ScheduledTaskScanJob {
86
73
  async scan() {
87
74
  await this.scanAt(new Date());
88
75
  }
89
- /**
90
- * One scan as of `now` (tests pass their own); returns how many due tasks it handled. The repair
91
- * runs even when the scan loop failed (its error is rethrown afterwards). `repairBatch`: how many
92
- * rows each repair step takes (tests pass a small one).
93
- */
94
- async scanAt(now, repairBatch = REPAIR_BATCH) {
76
+ /** One scan as of `now` (tests pass their own); returns how many due tasks it handled. */
77
+ async scanAt(now) {
95
78
  const settings = new Map();
96
79
  let total = 0;
97
- try {
98
- for (; total < MAX_TASKS; total++) {
99
- try {
100
- if (!(await this.scanOne(now, settings)))
101
- break;
102
- }
103
- catch (err) {
104
- if (!(err instanceof TaskScanFailed))
105
- throw err;
106
- // one task that cannot be advanced must not hold the others back every minute
107
- this.log.error("scheduled task cannot be advanced; paused", {
108
- taskId: err.taskId,
109
- err: err.cause,
110
- });
111
- await this.quarantine(err.taskId);
112
- }
80
+ for (; total < MAX_TASKS; total++) {
81
+ try {
82
+ if (!(await this.scanOne(now, settings)))
83
+ break;
84
+ }
85
+ catch (err) {
86
+ if (!(err instanceof TaskScanFailed))
87
+ throw err;
88
+ // one task that cannot be advanced must not hold the others back every minute
89
+ this.log.error("scheduled task cannot be advanced; paused", {
90
+ taskId: err.taskId,
91
+ err: err.cause,
92
+ });
93
+ await this.quarantine(err.taskId);
113
94
  }
114
- }
115
- finally {
116
- await this.repair(now, repairBatch);
117
95
  }
118
96
  return total;
119
97
  }
@@ -134,8 +112,8 @@ let ScheduledTaskScanJob = class ScheduledTaskScanJob {
134
112
  }
135
113
  const fired = await this.advance(t, task, now, s.catchUpWindowMinutes);
136
114
  if (fired !== null) {
137
- const id = fired;
138
- afterCommit(() => this.enqueue([id]));
115
+ await oweSchedFire(this.outbox, fired.id, task.orgId);
116
+ await oweSchedSettleUnfired(this.outbox, fired.id, task.orgId, fired.scheduledFor, s.catchUpWindowMinutes);
139
117
  }
140
118
  return true;
141
119
  }
@@ -143,136 +121,51 @@ let ScheduledTaskScanJob = class ScheduledTaskScanJob {
143
121
  async quarantine(taskId) {
144
122
  await pauseTaskIn(this.db, taskId, "invalid_schedule", null);
145
123
  }
146
- /** One due task (see the class); returns the id of the run to fire, if any. */
124
+ /** One due task (see the class); returns the run to fire, if any. */
147
125
  async advance(t, task, now, catchUpWindowMinutes) {
148
- const jitterMs = task.jitterSeconds * 1000;
149
- // ticks are before jitter: the due tick is next_run_at - jitter, and a tick is due once
150
- // tick + jitter <= now
151
- const dueTick = new Date(task.nextRunAt.getTime() - jitterMs);
152
- const until = new Date(now.getTime() - jitterMs);
153
126
  // the schedule math alone may mark the task broken; database errors fail this pass only
154
- let ticks;
155
- let next;
127
+ let step;
156
128
  try {
157
- ticks = dueTicks(task.trigger, new Date(dueTick.getTime() - 1), until, catchUpWindowMinutes);
158
- next = nextTick(task.trigger, until);
129
+ step = scanStep({
130
+ trigger: task.trigger,
131
+ nextRunAt: task.nextRunAt,
132
+ jitterSeconds: task.jitterSeconds,
133
+ expiresAt: task.expiresAt,
134
+ }, now, catchUpWindowMinutes);
159
135
  }
160
136
  catch (err) {
161
137
  throw new TaskScanFailed(task.id, err);
162
138
  }
163
- const expires = task.expiresAt?.getTime() ?? Number.POSITIVE_INFINITY;
164
- const fireAt = ticks.fire !== null && ticks.fire.getTime() <= expires ? ticks.fire : null;
165
- const first = ticks.firstMissed;
166
- if (first !== null && first.getTime() <= expires) {
139
+ if (step.missed !== null) {
167
140
  await this.store.insertRun(t, {
168
141
  id: randomUUID(),
169
142
  taskId: task.id,
170
143
  orgId: task.orgId,
171
- scheduledFor: first,
144
+ scheduledFor: step.missed,
172
145
  trial: false,
173
146
  status: "skipped",
174
147
  skipReason: "missed",
175
148
  });
176
149
  }
177
150
  let fired = null;
178
- if (fireAt !== null) {
151
+ if (step.fire !== null) {
179
152
  const run = await this.store.insertRun(t, {
180
153
  id: randomUUID(),
181
154
  taskId: task.id,
182
155
  orgId: task.orgId,
183
- scheduledFor: fireAt,
156
+ scheduledFor: step.fire,
184
157
  trial: false,
185
158
  });
186
- fired = run?.id ?? null;
159
+ fired = run ?? null;
187
160
  }
188
- const patch = next === null
161
+ const patch = step.end === "done"
189
162
  ? { status: "ended", nextRunAt: null }
190
- : next.getTime() > expires
163
+ : step.end === "expired"
191
164
  ? { status: "ended", pauseReason: "expired", nextRunAt: null }
192
- : { nextRunAt: new Date(next.getTime() + jitterMs) };
165
+ : { nextRunAt: step.nextRunAt };
193
166
  await this.store.updateTask(t, task.id, patch);
194
167
  return fired;
195
168
  }
196
- /** Enqueues the fire jobs (after the commit; a failure is repaired by a later scan). */
197
- async enqueue(ids) {
198
- for (const id of ids) {
199
- const job = schedFireJob(id);
200
- try {
201
- await this.maintenance.add(job.name, job.data, job.opts);
202
- }
203
- catch (err) {
204
- this.log.warn("enqueueing a scheduled task run failed", { taskRunId: id, err });
205
- }
206
- }
207
- }
208
- /** Lost fire jobs (see the class). */
209
- async repair(now, batch) {
210
- const stale = await this.db
211
- .selectFrom("scheduled_task_runs")
212
- .select(["id", "scheduled_for"])
213
- .where("status", "=", "pending")
214
- .where("fired_at", "is", null)
215
- .where("scheduled_for", "<", new Date(now.getTime() - REPAIR_AFTER_MS))
216
- .orderBy("scheduled_for")
217
- .limit(batch)
218
- .execute();
219
- const retry = [];
220
- const lost = [];
221
- for (const r of stale) {
222
- const at = r.scheduled_for instanceof Date ? r.scheduled_for : new Date(r.scheduled_for);
223
- (now.getTime() - at.getTime() > GIVE_UP_AFTER_MS ? lost : retry).push(r.id);
224
- }
225
- for (const id of lost)
226
- await this.giveUp(id);
227
- await this.enqueue(retry);
228
- // the least recently rechecked first, so runs still in flight do not starve the others
229
- const started = await this.db
230
- .selectFrom("scheduled_task_runs")
231
- .select("id")
232
- .where("status", "=", "pending")
233
- .where("fired_at", "<", new Date(now.getTime() - GIVE_UP_AFTER_MS))
234
- .orderBy(sql `rechecked_at NULLS FIRST`)
235
- .orderBy("scheduled_for")
236
- .limit(batch)
237
- .execute();
238
- for (const r of started)
239
- await this.recheck(r.id, now);
240
- }
241
- /** A run never fired an hour after its tick: given up, task row first (the one lock order). */
242
- async giveUp(taskRunId) {
243
- const owner = await this.db
244
- .selectFrom("scheduled_task_runs")
245
- .select("task_id")
246
- .where("id", "=", taskRunId)
247
- .executeTakeFirst();
248
- if (!owner)
249
- return;
250
- await lockTaskRow(this.db, owner.task_id);
251
- await this.db
252
- .updateTable("scheduled_task_runs")
253
- .set({ status: "skipped", skip_reason: "missed", error: "The run could not be started" })
254
- .where("id", "=", taskRunId)
255
- .where("status", "=", "pending")
256
- .where("fired_at", "is", null)
257
- .execute();
258
- }
259
- /**
260
- * A started run still pending long after: the tracker links, settles or releases it; one still
261
- * in flight is stamped (`rechecked_at`) and goes to the back of the next rechecks.
262
- */
263
- async recheck(taskRunId, now) {
264
- const run = await this.store.updateRun(this.db, taskRunId, {});
265
- if (run?.status !== "pending")
266
- return;
267
- // inFlightIn takes the task row before the run (the one lock order)
268
- if (await this.tracker.inFlightIn(run, now)) {
269
- await this.db
270
- .updateTable("scheduled_task_runs")
271
- .set({ rechecked_at: now })
272
- .where("id", "=", taskRunId)
273
- .execute();
274
- }
275
- }
276
169
  };
277
170
  __decorate([
278
171
  LockedCron("* * * * *", { name: SCHED_SCAN_JOB })
@@ -283,21 +176,14 @@ __decorate([
283
176
  __decorate([
284
177
  Transactional()
285
178
  ], ScheduledTaskScanJob.prototype, "quarantine", null);
286
- __decorate([
287
- Transactional()
288
- ], ScheduledTaskScanJob.prototype, "giveUp", null);
289
- __decorate([
290
- Transactional()
291
- ], ScheduledTaskScanJob.prototype, "recheck", null);
292
179
  ScheduledTaskScanJob = __decorate([
293
180
  Injectable(),
294
181
  __param(0, Inject(KernelDb)),
295
182
  __param(1, Inject(TransactionHost)),
296
183
  __param(2, Inject(KernelConfigService)),
297
184
  __param(3, Inject(ScheduledTaskStore)),
298
- __param(4, Inject(ScheduledTaskRunTracker)),
299
- __param(5, Inject(getQueueToken(MAINTENANCE_QUEUE_NAME))),
300
- __param(6, Inject(LockService)),
301
- __param(7, Inject(KERNEL_LOGGER))
185
+ __param(4, Inject(OutboxService)),
186
+ __param(5, Inject(LockService)),
187
+ __param(6, Inject(KERNEL_LOGGER))
302
188
  ], ScheduledTaskScanJob);
303
189
  export { ScheduledTaskScanJob };
@@ -8,6 +8,48 @@ export declare function validateTrigger(t: ScheduleTrigger, now: Date): {
8
8
  };
9
9
  /** The first tick strictly after `after` (before jitter); null for a past once task. */
10
10
  export declare function nextTick(t: ScheduleTrigger, after: Date): Date | null;
11
+ /** The first tick strictly after `after` (before jitter) of one trigger; null when none. */
12
+ export type Ticker = (after: Date) => Date | null;
13
+ /**
14
+ * The scanner's ticker for one trigger ({@link nextTick} without rebuilding its `CronTime`s per
15
+ * call), for callers that walk the same trigger many times ({@link ticksIn}).
16
+ */
17
+ export declare function tickerOf(t: ScheduleTrigger): Ticker;
18
+ /**
19
+ * The ticks in `(after, until]` (before jitter), at most `max`, from the same ticker
20
+ * {@link nextTick} and {@link dueTicks} use, so a list of planned runs equals what the scanner
21
+ * fires (calendar spec §5.2). `exhausted`: `max` ticks were taken before a tick past `until` was
22
+ * seen, so more may follow. A `once` trigger yields its one tick when it lies in the window.
23
+ * Costs one tick evaluation per tick returned, plus one when the walk reaches `until`. A caller
24
+ * that walks one trigger in many windows passes a {@link tickerOf} it built once.
25
+ */
26
+ export declare function ticksIn(t: ScheduleTrigger | Ticker, after: Date, until: Date, max: number): {
27
+ ticks: Date[];
28
+ exhausted: boolean;
29
+ };
30
+ /** What one scan does to a due task (spec §6 step 1), as {@link scanStep} decides it. */
31
+ export interface ScanStep {
32
+ /** The first missed tick, recorded as one skipped run (`missed`); null when none. */
33
+ missed: Date | null;
34
+ /** The tick whose run is recorded and fired; null when none. */
35
+ fire: Date | null;
36
+ /** The task's new `next_run_at` (jitter included); null when it ends. */
37
+ nextRunAt: Date | null;
38
+ /** Why the task ends: no tick left (`done`) or the next one lies past `expires_at`. */
39
+ end: "done" | "expired" | null;
40
+ }
41
+ /**
42
+ * One scan of a due task (`next_run_at <= now`) as of `now`, the schedule math alone: the ticks
43
+ * are before jitter, so the due tick is `nextRunAt - jitter` and a tick is due once
44
+ * `tick + jitter <= now`. The scanner applies the result; the calendar's parity test drives it.
45
+ * Throws when the trigger cannot be evaluated.
46
+ */
47
+ export declare function scanStep(task: {
48
+ trigger: ScheduleTrigger;
49
+ nextRunAt: Date;
50
+ jitterSeconds: number;
51
+ expiresAt: Date | null;
52
+ }, now: Date, catchUpWindowMinutes: number): ScanStep;
11
53
  /** The smallest gap, in minutes, among the next 50 fire times after `from`; null for once. */
12
54
  export declare function minGapMinutes(t: ScheduleTrigger, from: Date): number | null;
13
55
  /** A fixed per-task offset: `fnv1a32(id) mod min(floor(10 % of the interval), 300 s)`; once is 0. */
@@ -104,6 +104,13 @@ export function validateTrigger(t, now) {
104
104
  export function nextTick(t, after) {
105
105
  return ticker(t)(after);
106
106
  }
107
+ /**
108
+ * The scanner's ticker for one trigger ({@link nextTick} without rebuilding its `CronTime`s per
109
+ * call), for callers that walk the same trigger many times ({@link ticksIn}).
110
+ */
111
+ export function tickerOf(t) {
112
+ return ticker(t);
113
+ }
107
114
  /** How far back a DST transition can move a tick: longer than any gap (spec §5). */
108
115
  const TRANSITION_LOOKBACK_MS = 6 * 3600_000;
109
116
  /**
@@ -138,6 +145,51 @@ function ticker(t) {
138
145
  return null;
139
146
  };
140
147
  }
148
+ /**
149
+ * The ticks in `(after, until]` (before jitter), at most `max`, from the same ticker
150
+ * {@link nextTick} and {@link dueTicks} use, so a list of planned runs equals what the scanner
151
+ * fires (calendar spec §5.2). `exhausted`: `max` ticks were taken before a tick past `until` was
152
+ * seen, so more may follow. A `once` trigger yields its one tick when it lies in the window.
153
+ * Costs one tick evaluation per tick returned, plus one when the walk reaches `until`. A caller
154
+ * that walks one trigger in many windows passes a {@link tickerOf} it built once.
155
+ */
156
+ export function ticksIn(t, after, until, max) {
157
+ const ticks = [];
158
+ if (after.getTime() >= until.getTime())
159
+ return { ticks, exhausted: false };
160
+ const next = typeof t === "function" ? t : ticker(t);
161
+ let cursor = after;
162
+ while (ticks.length < max) {
163
+ const tick = next(cursor);
164
+ if (!tick || tick.getTime() > until.getTime())
165
+ return { ticks, exhausted: false };
166
+ ticks.push(tick);
167
+ cursor = tick;
168
+ }
169
+ return { ticks, exhausted: true };
170
+ }
171
+ /**
172
+ * One scan of a due task (`next_run_at <= now`) as of `now`, the schedule math alone: the ticks
173
+ * are before jitter, so the due tick is `nextRunAt - jitter` and a tick is due once
174
+ * `tick + jitter <= now`. The scanner applies the result; the calendar's parity test drives it.
175
+ * Throws when the trigger cannot be evaluated.
176
+ */
177
+ export function scanStep(task, now, catchUpWindowMinutes) {
178
+ const jitterMs = task.jitterSeconds * 1000;
179
+ const dueTick = new Date(task.nextRunAt.getTime() - jitterMs);
180
+ const until = new Date(now.getTime() - jitterMs);
181
+ const ticks = dueTicks(task.trigger, new Date(dueTick.getTime() - 1), until, catchUpWindowMinutes);
182
+ const next = nextTick(task.trigger, until);
183
+ const expires = task.expiresAt?.getTime() ?? Number.POSITIVE_INFINITY;
184
+ const fire = ticks.fire !== null && ticks.fire.getTime() <= expires ? ticks.fire : null;
185
+ const first = ticks.firstMissed;
186
+ const missed = first !== null && first.getTime() <= expires ? first : null;
187
+ if (next === null)
188
+ return { missed, fire, nextRunAt: null, end: "done" };
189
+ if (next.getTime() > expires)
190
+ return { missed, fire, nextRunAt: null, end: "expired" };
191
+ return { missed, fire, nextRunAt: new Date(next.getTime() + jitterMs), end: null };
192
+ }
141
193
  /** The wall clock of `at` in `timeZone`, read as if it were UTC (epoch ms, seconds dropped). */
142
194
  function localWallClock(at, timeZone) {
143
195
  return Math.floor(wallOf(at.getTime(), timeZone) / 60000) * 60000;
@@ -1,4 +1,3 @@
1
- import type { Queue } from "bullmq";
2
1
  import { AgentLookup } from "../agents/agent-lookup.js";
3
2
  import type { ModelRef } from "../agents/definition.js";
4
3
  import { ConversationAccess } from "../conversations/access.js";
@@ -7,12 +6,14 @@ import { ExtensionRegistry } from "../extensions/registry.js";
7
6
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
8
7
  import { KernelDb } from "../infra/database/kernel-db.js";
9
8
  import { TransactionHost } from "../infra/database/transaction-host.js";
9
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
10
10
  import { DevicePolicyService } from "../local-devices/device-policy.service.js";
11
11
  import type { KernelLogger } from "../log.js";
12
12
  import { OrgLock } from "../orgs/org-lock.js";
13
13
  import { ProviderRegistry } from "../runtime/providers.js";
14
14
  import { SandboxService } from "../sandbox/sandbox.service.js";
15
15
  import { ToolRegistry } from "../tools/registry.js";
16
+ import { type CalendarAnswer, type CalendarLimits } from "./calendar.js";
16
17
  import { type GrantContext, type GrantRefusalCode, grantRefusal, grantRefusalOf, networkGrantRefusal } from "./grant-rules.js";
17
18
  import { ScheduledTaskStore } from "./store.js";
18
19
  import type { ScheduleChat, ScheduledTask, ScheduledTaskGrant, ScheduleMode, SchedulePauseReason, TaskRun } from "./types.js";
@@ -197,8 +198,8 @@ export declare class ScheduledTasksService {
197
198
  /** A task's model must resolve for the org and declare its effort (as a message's switch). */
198
199
  private readonly providers;
199
200
  private readonly log;
200
- /** "Run now" enqueues the fire job (consumed by the worker role). */
201
- private readonly maintenance;
201
+ /** "Run now" owes the fire job (consumed by the worker role). */
202
+ private readonly outbox;
202
203
  /** Network grants: the org's effective egress (network ask-to-allow spec §8.1). */
203
204
  private readonly sandbox;
204
205
  /** Network grants: the task's device's local policy summary (`remoteGrants`). */
@@ -206,8 +207,8 @@ export declare class ScheduledTasksService {
206
207
  constructor(db: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, store: ScheduledTaskStore, orgLock: Pick<OrgLock, "lockAndAuthorize">, agents: Pick<AgentLookup, "get">, access: Pick<ConversationAccess, "isAccessible">, tools: Pick<ToolRegistry, "forAgent">, extensions: Pick<ExtensionRegistry, "list">,
207
208
  /** A task's model must resolve for the org and declare its effort (as a message's switch). */
208
209
  providers: Pick<ProviderRegistry, "resolve">, log: Pick<KernelLogger, "warn">,
209
- /** "Run now" enqueues the fire job (consumed by the worker role). */
210
- maintenance: Pick<Queue, "add">,
210
+ /** "Run now" owes the fire job (consumed by the worker role). */
211
+ outbox: Pick<OutboxService, "add">,
211
212
  /** Network grants: the org's effective egress (network ask-to-allow spec §8.1). */
212
213
  sandbox: Pick<SandboxService, "ceilingLimits" | "devicePolicy">,
213
214
  /** Network grants: the task's device's local policy summary (`remoteGrants`). */
@@ -231,6 +232,21 @@ export declare class ScheduledTasksService {
231
232
  all?: boolean;
232
233
  conversationId?: string;
233
234
  }): Promise<ScheduledTaskListItem[]>;
235
+ /**
236
+ * The calendar between two instants (calendar spec §5): the runs and planned occurrences of the
237
+ * actor's own tasks, or with `all` (org admins only, else `forbidden`) every task of the org; with
238
+ * `taskId` only that task (one the actor may see, else `schedule.not_found`), never collapsed.
239
+ * Another creator's runs come without `conversationId`, `error` and `missingGrants` (§15 Q3).
240
+ * A range longer than 43 days (2 with `taskId`), or not forward, is `validation_failed`. `now`
241
+ * is for tests.
242
+ */
243
+ calendar(orgId: string, actor: ScheduleActor, q: {
244
+ from: Date;
245
+ to: Date;
246
+ timeZone: string;
247
+ all?: boolean;
248
+ taskId?: string;
249
+ }, now?: Date, limits?: CalendarLimits): Promise<CalendarAnswer>;
234
250
  /**
235
251
  * The tasks of `tasks` that post into the conversation: a `conversation` task naming it, or a
236
252
  * `chat` task whose chat the conversation is a thread of (spec §10 side panel).
@@ -285,7 +301,7 @@ export declare class ScheduledTasksService {
285
301
  /**
286
302
  * "Run now" (spec §6), creator only: a task run for the request time, outside the schedule (no
287
303
  * unique-key conflict with a tick, and `next_run_at` does not move), fired at once through the
288
- * fire job after the commit. `trial`: an attended trial run whose approvals become the task's
304
+ * fire job its transaction owes. `trial`: an attended trial run whose approvals become the task's
289
305
  * grants (§7). Counted in the rate limit like any run. An ended task cannot run; a paused one
290
306
  * can (only its creator gets here: the run is `manual`, which the fire's paused check lets by).
291
307
  */