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

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 (206) 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 +7 -3
  4. package/dist/admin/outbox-dead.controller.d.ts +23 -0
  5. package/dist/admin/outbox-dead.controller.js +62 -0
  6. package/dist/admin/outbox-dead.schemas.d.ts +8 -0
  7. package/dist/admin/outbox-dead.schemas.js +18 -0
  8. package/dist/admin/outbox-dead.service.d.ts +66 -0
  9. package/dist/admin/outbox-dead.service.js +179 -0
  10. package/dist/admin/overview.service.d.ts +40 -5
  11. package/dist/admin/overview.service.js +41 -7
  12. package/dist/agents/from-template.js +1 -1
  13. package/dist/background/finish.d.ts +5 -2
  14. package/dist/background/finish.js +14 -3
  15. package/dist/background/launch.d.ts +2 -1
  16. package/dist/background/launch.js +5 -2
  17. package/dist/background/store.d.ts +8 -0
  18. package/dist/background/store.js +24 -0
  19. package/dist/catalog/admin-catalog.controller.d.ts +21 -2
  20. package/dist/catalog/admin-catalog.controller.js +45 -6
  21. package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
  22. package/dist/catalog/admin-catalog.schemas.js +8 -0
  23. package/dist/catalog/catalog.module.d.ts +2 -1
  24. package/dist/catalog/catalog.module.js +5 -1
  25. package/dist/catalog/marketplaces.service.d.ts +1 -1
  26. package/dist/catalog/platform-import-update.d.ts +50 -0
  27. package/dist/catalog/platform-import-update.js +116 -0
  28. package/dist/catalog/plugin-import.d.ts +36 -1
  29. package/dist/catalog/plugin-import.js +23 -1
  30. package/dist/catalog/plugin-upgrade.d.ts +18 -0
  31. package/dist/catalog/plugin-upgrade.js +19 -0
  32. package/dist/channels/channels.module.d.ts +5 -2
  33. package/dist/channels/channels.module.js +9 -2
  34. package/dist/channels/deliver-job.d.ts +25 -0
  35. package/dist/channels/deliver-job.js +25 -0
  36. package/dist/channels/inbound.d.ts +1 -1
  37. package/dist/channels/inbound.js +2 -15
  38. package/dist/channels/jobs.d.ts +28 -18
  39. package/dist/channels/jobs.js +63 -26
  40. package/dist/config/schema.d.ts +0 -3
  41. package/dist/config/schema.js +11 -20
  42. package/dist/conversations/agent-tree.d.ts +16 -0
  43. package/dist/conversations/agent-tree.js +36 -0
  44. package/dist/conversations/conversations.service.d.ts +44 -14
  45. package/dist/conversations/conversations.service.js +52 -38
  46. package/dist/conversations/event-log.d.ts +7 -10
  47. package/dist/conversations/event-log.js +24 -15
  48. package/dist/conversations/events.js +2 -1
  49. package/dist/conversations/queue.service.d.ts +8 -3
  50. package/dist/conversations/queue.service.js +37 -13
  51. package/dist/conversations/titles.d.ts +21 -8
  52. package/dist/conversations/titles.js +42 -15
  53. package/dist/db/kernel-schema.d.ts +26 -8
  54. package/dist/devices/devices.service.d.ts +12 -3
  55. package/dist/devices/devices.service.js +18 -3
  56. package/dist/errors.d.ts +11 -0
  57. package/dist/errors.js +11 -0
  58. package/dist/evals/run-replay.js +14 -8
  59. package/dist/extensions/extensions.module.js +3 -0
  60. package/dist/extensions/lifecycle.d.ts +6 -1
  61. package/dist/extensions/lifecycle.js +9 -2
  62. package/dist/extensions/usage-subscribers.d.ts +43 -0
  63. package/dist/extensions/usage-subscribers.js +108 -0
  64. package/dist/http/api.module.js +2 -0
  65. package/dist/index.d.ts +1 -0
  66. package/dist/index.js +1 -0
  67. package/dist/infra/cli/kernel-cli.module.js +5 -0
  68. package/dist/infra/events/kernel-events.d.ts +9 -35
  69. package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
  70. package/dist/infra/outbox/outbox-relay.module.js +18 -0
  71. package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
  72. package/dist/infra/outbox/outbox.dispatcher.js +310 -0
  73. package/dist/infra/outbox/outbox.module.d.ts +24 -0
  74. package/dist/infra/outbox/outbox.module.js +50 -0
  75. package/dist/infra/outbox/outbox.relay.d.ts +31 -0
  76. package/dist/infra/outbox/outbox.relay.js +76 -0
  77. package/dist/infra/outbox/outbox.service.d.ts +51 -0
  78. package/dist/infra/outbox/outbox.service.js +131 -0
  79. package/dist/infra/outbox/registry.d.ts +120 -0
  80. package/dist/infra/outbox/registry.js +108 -0
  81. package/dist/infra/queue/queue-names.d.ts +8 -0
  82. package/dist/infra/queue/queue-names.js +16 -0
  83. package/dist/kernel.module.js +3 -0
  84. package/dist/local-devices/device-policy.service.d.ts +4 -0
  85. package/dist/local-devices/device-policy.service.js +61 -3
  86. package/dist/local-devices/machines.d.ts +13 -5
  87. package/dist/local-devices/machines.js +45 -7
  88. package/dist/local-devices/policy-summary.d.ts +44 -1
  89. package/dist/local-devices/policy-summary.js +69 -0
  90. package/dist/models/embeddings.d.ts +9 -9
  91. package/dist/models/embeddings.js +11 -18
  92. package/dist/models/models.module.js +3 -0
  93. package/dist/orgs/orgs.service.js +7 -0
  94. package/dist/overrides.d.ts +20 -3
  95. package/dist/overrides.js +10 -2
  96. package/dist/platform/exclusions.js +5 -0
  97. package/dist/platform/ops/admin.ops.js +18 -2
  98. package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
  99. package/dist/platform/ops/catalog-admin.ops.js +32 -4
  100. package/dist/platform/ops/outbox.ops.d.ts +21 -0
  101. package/dist/platform/ops/outbox.ops.js +125 -0
  102. package/dist/platform/ops/schedules.ops.js +40 -1
  103. package/dist/platform/platform-ops.module.d.ts +2 -1
  104. package/dist/platform/platform-ops.module.js +3 -0
  105. package/dist/platform/route-schemas.js +3 -0
  106. package/dist/roles/worker.module.js +9 -4
  107. package/dist/runtime/langgraph-loop.js +13 -5
  108. package/dist/runtime/limits.d.ts +15 -0
  109. package/dist/runtime/limits.js +25 -0
  110. package/dist/runtime/loop.d.ts +8 -3
  111. package/dist/runtime/loop.js +2 -1
  112. package/dist/runtime/maintenance.d.ts +4 -2
  113. package/dist/runtime/maintenance.js +17 -2
  114. package/dist/runtime/model-call.d.ts +14 -16
  115. package/dist/runtime/model-call.js +14 -24
  116. package/dist/runtime/queue-lost-jobs.d.ts +24 -0
  117. package/dist/runtime/queue-lost-jobs.js +51 -0
  118. package/dist/runtime/run-control.d.ts +42 -5
  119. package/dist/runtime/run-control.js +66 -12
  120. package/dist/runtime/run-finish.d.ts +39 -12
  121. package/dist/runtime/run-finish.js +97 -14
  122. package/dist/runtime/run-jobs.d.ts +95 -13
  123. package/dist/runtime/run-jobs.js +199 -28
  124. package/dist/runtime/run-orphan-check.job.d.ts +142 -0
  125. package/dist/runtime/run-orphan-check.job.js +339 -0
  126. package/dist/runtime/run-processors.d.ts +16 -9
  127. package/dist/runtime/run-processors.js +1 -1
  128. package/dist/runtime/run-queue.d.ts +64 -55
  129. package/dist/runtime/run-queue.js +147 -284
  130. package/dist/runtime/run-steps.d.ts +1 -2
  131. package/dist/runtime/run-steps.js +8 -11
  132. package/dist/runtime/run-worker.d.ts +6 -17
  133. package/dist/runtime/run-worker.js +32 -34
  134. package/dist/runtime/runtime.module.d.ts +2 -3
  135. package/dist/runtime/runtime.module.js +5 -6
  136. package/dist/runtime/step-log.d.ts +16 -4
  137. package/dist/runtime/step-log.js +26 -6
  138. package/dist/runtime/stream.controller.d.ts +2 -4
  139. package/dist/runtime/stream.controller.js +5 -11
  140. package/dist/runtime/subagents/child.js +1 -1
  141. package/dist/runtime/subagents/definition.d.ts +2 -1
  142. package/dist/runtime/subagents/definition.js +4 -2
  143. package/dist/runtime/subagents/deliveries.d.ts +6 -3
  144. package/dist/runtime/subagents/deliveries.js +16 -7
  145. package/dist/runtime/subagents/dispatch.d.ts +12 -12
  146. package/dist/runtime/subagents/dispatch.js +13 -38
  147. package/dist/runtime/subagents/messages.d.ts +5 -1
  148. package/dist/runtime/subagents/messages.js +11 -3
  149. package/dist/runtime/subagents/skill-fork.d.ts +3 -3
  150. package/dist/runtime/subagents/skill-fork.js +5 -5
  151. package/dist/runtime/subagents/subagents.service.js +1 -1
  152. package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
  153. package/dist/runtime/subagents/tree-jobs.js +86 -0
  154. package/dist/runtime/usage-report-jobs.d.ts +23 -48
  155. package/dist/runtime/usage-report-jobs.js +67 -119
  156. package/dist/runtime/usage-report.d.ts +28 -77
  157. package/dist/runtime/usage-report.js +35 -213
  158. package/dist/runtime/waits.d.ts +48 -198
  159. package/dist/runtime/waits.js +103 -805
  160. package/dist/sandbox/sandbox.service.d.ts +17 -4
  161. package/dist/sandbox/sandbox.service.js +55 -14
  162. package/dist/schedules/calendar.d.ts +134 -0
  163. package/dist/schedules/calendar.js +410 -0
  164. package/dist/schedules/fire.d.ts +1 -3
  165. package/dist/schedules/fire.js +5 -10
  166. package/dist/schedules/run-tracker.d.ts +0 -5
  167. package/dist/schedules/run-tracker.js +0 -13
  168. package/dist/schedules/scan.job.js +15 -20
  169. package/dist/schedules/schedule-math.d.ts +42 -0
  170. package/dist/schedules/schedule-math.js +52 -0
  171. package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
  172. package/dist/schedules/scheduled-tasks.service.js +53 -0
  173. package/dist/schedules/schedules.controller.d.ts +5 -1
  174. package/dist/schedules/schedules.controller.js +15 -1
  175. package/dist/schedules/schedules.schemas.d.ts +35 -0
  176. package/dist/schedules/schedules.schemas.js +27 -0
  177. package/dist/schedules/store.d.ts +5 -0
  178. package/dist/schedules/store.js +20 -0
  179. package/dist/tools/approvals.service.d.ts +26 -7
  180. package/dist/tools/approvals.service.js +88 -10
  181. package/dist/tools/builtin-names.d.ts +5 -3
  182. package/dist/tools/builtin-names.js +12 -4
  183. package/dist/tools/builtins/builtin-tools.d.ts +1 -2
  184. package/dist/tools/builtins/builtin-tools.js +21 -17
  185. package/dist/tools/builtins/sandbox/bash.js +9 -3
  186. package/dist/tools/builtins/sandbox/common.d.ts +18 -1
  187. package/dist/tools/builtins/sandbox/common.js +14 -1
  188. package/dist/tools/builtins/sandbox/index.d.ts +2 -1
  189. package/dist/tools/builtins/sandbox/index.js +4 -1
  190. package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
  191. package/dist/tools/builtins/sandbox/task-list.js +130 -0
  192. package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
  193. package/dist/tools/builtins/sandbox/task-output.js +21 -3
  194. package/dist/tools/builtins/web/extract.d.ts +2 -4
  195. package/dist/tools/builtins/web/extract.js +2 -5
  196. package/dist/tools/pipeline.d.ts +1 -1
  197. package/dist/tools/pipeline.js +1 -1
  198. package/dist/tools/questions.service.d.ts +15 -6
  199. package/dist/tools/questions.service.js +20 -8
  200. package/dist/tools/registry.d.ts +4 -2
  201. package/dist/tools/registry.js +4 -3
  202. package/dist/tools/tools.module.js +4 -3
  203. package/package.json +3 -3
  204. package/src/migrations/0118_device_policy_ops.sql +6 -0
  205. package/src/migrations/0119_outbox.sql +22 -0
  206. package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
@@ -0,0 +1,31 @@
1
+ import type { KernelLogger } from "../../log.js";
2
+ import { KernelConfigService } from "../config/kernel-config.service.js";
3
+ import { LockService } from "../lock/lock.service.js";
4
+ import { OutboxDispatcher } from "./outbox.dispatcher.js";
5
+ /** The relay's `@LockedInterval` job name (its run lock key is the default `cron:outbox-relay`). */
6
+ export declare const OUTBOX_RELAY_JOB = "outbox-relay";
7
+ /** The relay's period: a fixed constant, not a config field. */
8
+ export declare const OUTBOX_RELAY_PERIOD_MS = 2000;
9
+ /** Rows one claim pass of the relay takes. */
10
+ export declare const OUTBOX_RELAY_BATCH = 500;
11
+ /** Most claim passes in one tick; what is left waits for the next tick. */
12
+ export declare const OUTBOX_RELAY_MAX_ROUNDS = 10;
13
+ /**
14
+ * The outbox relay (transactional outbox spec §3.3), on the worker role only: once per tick across
15
+ * replicas it delivers the due rows the fast path did not (a failed or skipped pass, a process that
16
+ * died after the commit, a retry whose backoff ran out). A tick runs passes of
17
+ * {@link OUTBOX_RELAY_BATCH} rows until one handles fewer (the backlog is drained) or
18
+ * {@link OUTBOX_RELAY_MAX_ROUNDS} have run. It also stops early when the run is aborted, and after
19
+ * a pass that delivered nothing but failed rows (Redis trouble: the next tick tries again, rather
20
+ * than spinning more passes that each hold a pool connection).
21
+ */
22
+ export declare class OutboxRelay {
23
+ private readonly dispatcher;
24
+ readonly config: KernelConfigService;
25
+ readonly lockService: LockService;
26
+ readonly log: KernelLogger;
27
+ constructor(dispatcher: Pick<OutboxDispatcher, "dispatchDue">, config: KernelConfigService, lockService: LockService, log: KernelLogger);
28
+ relay(signal?: AbortSignal): Promise<void>;
29
+ /** One tick's passes (the body of {@link relay}); returns the rows delivered. */
30
+ relayOnce(signal?: AbortSignal): Promise<number>;
31
+ }
@@ -0,0 +1,76 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { KernelConfigService } from "../config/kernel-config.service.js";
12
+ import { LockService } from "../lock/lock.service.js";
13
+ import { LockedInterval } from "../schedule/locked-interval.js";
14
+ import { KERNEL_LOGGER } from "../tokens.js";
15
+ import { OutboxDispatcher } from "./outbox.dispatcher.js";
16
+ /** The relay's `@LockedInterval` job name (its run lock key is the default `cron:outbox-relay`). */
17
+ export const OUTBOX_RELAY_JOB = "outbox-relay";
18
+ /** The relay's period: a fixed constant, not a config field. */
19
+ export const OUTBOX_RELAY_PERIOD_MS = 2000;
20
+ /** Rows one claim pass of the relay takes. */
21
+ export const OUTBOX_RELAY_BATCH = 500;
22
+ /** Most claim passes in one tick; what is left waits for the next tick. */
23
+ export const OUTBOX_RELAY_MAX_ROUNDS = 10;
24
+ /**
25
+ * The outbox relay (transactional outbox spec §3.3), on the worker role only: once per tick across
26
+ * replicas it delivers the due rows the fast path did not (a failed or skipped pass, a process that
27
+ * died after the commit, a retry whose backoff ran out). A tick runs passes of
28
+ * {@link OUTBOX_RELAY_BATCH} rows until one handles fewer (the backlog is drained) or
29
+ * {@link OUTBOX_RELAY_MAX_ROUNDS} have run. It also stops early when the run is aborted, and after
30
+ * a pass that delivered nothing but failed rows (Redis trouble: the next tick tries again, rather
31
+ * than spinning more passes that each hold a pool connection).
32
+ */
33
+ let OutboxRelay = class OutboxRelay {
34
+ dispatcher;
35
+ config;
36
+ lockService;
37
+ log;
38
+ constructor(dispatcher, config, lockService, log) {
39
+ this.dispatcher = dispatcher;
40
+ this.config = config;
41
+ this.lockService = lockService;
42
+ this.log = log;
43
+ }
44
+ async relay(signal) {
45
+ await this.relayOnce(signal);
46
+ }
47
+ /** One tick's passes (the body of {@link relay}); returns the rows delivered. */
48
+ async relayOnce(signal) {
49
+ let sent = 0;
50
+ for (let round = 0; round < OUTBOX_RELAY_MAX_ROUNDS; round++) {
51
+ if (signal?.aborted)
52
+ break;
53
+ const r = await this.dispatcher.dispatchDue(OUTBOX_RELAY_BATCH);
54
+ sent += r.sent;
55
+ // rows handled: `dead` is a part of `failed`, not counted again
56
+ if (r.sent + r.failed < OUTBOX_RELAY_BATCH)
57
+ break;
58
+ if (r.sent === 0 && r.failed > 0)
59
+ break;
60
+ }
61
+ if (sent > 0)
62
+ this.log.debug("outbox relay delivered rows", { rows: sent });
63
+ return sent;
64
+ }
65
+ };
66
+ __decorate([
67
+ LockedInterval(OUTBOX_RELAY_JOB, () => OUTBOX_RELAY_PERIOD_MS)
68
+ ], OutboxRelay.prototype, "relay", null);
69
+ OutboxRelay = __decorate([
70
+ Injectable(),
71
+ __param(0, Inject(OutboxDispatcher)),
72
+ __param(1, Inject(KernelConfigService)),
73
+ __param(2, Inject(LockService)),
74
+ __param(3, Inject(KERNEL_LOGGER))
75
+ ], OutboxRelay);
76
+ export { OutboxRelay };
@@ -0,0 +1,51 @@
1
+ import type { KernelLogger } from "../../log.js";
2
+ import { KernelDb } from "../database/kernel-db.js";
3
+ import { TransactionHost } from "../database/transaction-host.js";
4
+ import type { KernelQueueName } from "../queue/queue-names.js";
5
+ import type { OutboxJobName } from "./registry.js";
6
+ /** A BullMQ job owed by the caller's transaction (transactional outbox spec §5). */
7
+ export interface OutboxJob<T> {
8
+ /** One of the kernel queues. */
9
+ queue: KernelQueueName;
10
+ /** A registered job name ({@link OutboxJobName}). */
11
+ name: OutboxJobName;
12
+ /** Deterministic, no `:`; one pending (not dead) row per (queue, jobId). */
13
+ jobId: string;
14
+ /** JSON data: stored as jsonb, so what JSON cannot carry (undefined, Date objects) is lost. */
15
+ data: T;
16
+ /** Delay target: delivered as a BullMQ `delay` computed at dispatch. */
17
+ runAt?: Date;
18
+ /** The org the work belongs to (its deletion removes the pending row); absent for platform work. */
19
+ orgId?: string;
20
+ }
21
+ /** Delivers the given outbox rows right after their transaction committed (the fast path, §3.3). */
22
+ export type OutboxFastPath = (ids: readonly string[]) => Promise<void>;
23
+ /** The fast path `add` hands each committed transaction's rows to (`OutboxModule` provides it). */
24
+ export declare const OUTBOX_FAST_PATH: unique symbol;
25
+ /**
26
+ * Test seam (`KernelOverride`): `true` makes a reused job id with different data throw instead of
27
+ * logging an error (spec §4, designer's call: "throws in tests, logs in production").
28
+ */
29
+ export declare const OUTBOX_STRICT_CONFLICTS: unique symbol;
30
+ /**
31
+ * Writes owed work as a row in the caller's transaction (spec §3.2): the row commits or rolls back
32
+ * with the business write, and after the commit the fast path delivers it when it is due now (a
33
+ * row with a future `runAt` waits for the relay, spec §3.3).
34
+ */
35
+ export declare class OutboxService {
36
+ readonly txHost: TransactionHost;
37
+ private readonly db;
38
+ private readonly log;
39
+ private readonly fastPath;
40
+ private readonly strictConflicts;
41
+ constructor(txHost: TransactionHost, db: KernelDb, log: KernelLogger, fastPath: OutboxFastPath, strictConflicts?: boolean);
42
+ /**
43
+ * Adds the job to the outbox in the transaction in context (throws outside one). Returns true when
44
+ * it inserted a row, false when the same (queue, jobId) is already owed (a no-op; a dead row is
45
+ * not owed). A conflict whose stored data differs is a bug in the job id: it throws under strict
46
+ * conflicts, else logs an error. Undefined data is stored as JSON null.
47
+ */
48
+ add<T>(job: OutboxJob<T>): Promise<boolean>;
49
+ /** Collects `id` for its transaction; the first id registers the transaction's one hint. */
50
+ private deliverAfterCommit;
51
+ }
@@ -0,0 +1,131 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { isDeepStrictEqual } from "node:util";
11
+ import { Inject, Injectable, Optional } from "@nestjs/common";
12
+ import { afterCommit } from "../database/after-commit.js";
13
+ import { KernelDb } from "../database/kernel-db.js";
14
+ import { currentStore, TransactionHost } from "../database/transaction-host.js";
15
+ import { Transactional } from "../database/transactional.decorator.js";
16
+ import { KERNEL_LOGGER } from "../tokens.js";
17
+ /** The fast path `add` hands each committed transaction's rows to (`OutboxModule` provides it). */
18
+ export const OUTBOX_FAST_PATH = Symbol("OUTBOX_FAST_PATH");
19
+ /**
20
+ * Test seam (`KernelOverride`): `true` makes a reused job id with different data throw instead of
21
+ * logging an error (spec §4, designer's call: "throws in tests, logs in production").
22
+ */
23
+ export const OUTBOX_STRICT_CONFLICTS = Symbol("OUTBOX_STRICT_CONFLICTS");
24
+ /**
25
+ * The ids each open transaction inserted, keyed by its transaction store: lives only as long as
26
+ * that transaction (a WeakMap entry, no state shared across requests).
27
+ */
28
+ const pendingIds = new WeakMap();
29
+ /**
30
+ * Writes owed work as a row in the caller's transaction (spec §3.2): the row commits or rolls back
31
+ * with the business write, and after the commit the fast path delivers it when it is due now (a
32
+ * row with a future `runAt` waits for the relay, spec §3.3).
33
+ */
34
+ let OutboxService = class OutboxService {
35
+ txHost;
36
+ db;
37
+ log;
38
+ fastPath;
39
+ strictConflicts;
40
+ constructor(txHost, db, log, fastPath, strictConflicts = false) {
41
+ this.txHost = txHost;
42
+ this.db = db;
43
+ this.log = log;
44
+ this.fastPath = fastPath;
45
+ this.strictConflicts = strictConflicts;
46
+ }
47
+ /**
48
+ * Adds the job to the outbox in the transaction in context (throws outside one). Returns true when
49
+ * it inserted a row, false when the same (queue, jobId) is already owed (a no-op; a dead row is
50
+ * not owed). A conflict whose stored data differs is a bug in the job id: it throws under strict
51
+ * conflicts, else logs an error. Undefined data is stored as JSON null.
52
+ */
53
+ async add(job) {
54
+ if (job.jobId.includes(":")) {
55
+ throw new Error(`outbox job id "${job.jobId}" must not contain ":" (BullMQ uses it as its key separator); ` +
56
+ 'join the id\'s parts with "-" instead');
57
+ }
58
+ const json = JSON.stringify(job.data ?? null);
59
+ const row = await this.db
60
+ .insertInto("outbox")
61
+ .values({
62
+ queue: job.queue,
63
+ job_name: job.name,
64
+ job_id: job.jobId,
65
+ data: json,
66
+ run_at: job.runAt ?? null,
67
+ org_id: job.orgId ?? null,
68
+ })
69
+ .onConflict((oc) => oc.columns(["queue", "job_id"]).where("dead_at", "is", null).doNothing())
70
+ .returning("id")
71
+ .executeTakeFirst();
72
+ if (row) {
73
+ // only a job due now goes through the fast path (ruling R20): a delayed one (a backstop, a
74
+ // timeout) is claimed by the relay within its next tick and added with its remaining delay,
75
+ // so the commit's caller spends no claim transaction on work due in seconds or hours
76
+ if (job.runAt === undefined || job.runAt.getTime() <= Date.now()) {
77
+ this.deliverAfterCommit(row.id);
78
+ }
79
+ return true;
80
+ }
81
+ const stored = await this.db
82
+ .selectFrom("outbox")
83
+ .select(["job_name", "data"])
84
+ .where("queue", "=", job.queue)
85
+ .where("job_id", "=", job.jobId)
86
+ .where("dead_at", "is", null)
87
+ .executeTakeFirst();
88
+ // no row: the owed one was delivered (and deleted) since the conflict; BullMQ dedupes the id
89
+ if (stored && !isDeepStrictEqual(stored.data, JSON.parse(json))) {
90
+ const msg = "outbox job id reused with different data";
91
+ const fields = {
92
+ queue: job.queue,
93
+ jobId: job.jobId,
94
+ job: job.name,
95
+ owedJob: stored.job_name,
96
+ };
97
+ if (this.strictConflicts)
98
+ throw new Error(`${msg}: ${JSON.stringify(fields)}`);
99
+ this.log.error(msg, fields);
100
+ }
101
+ return false;
102
+ }
103
+ /** Collects `id` for its transaction; the first id registers the transaction's one hint. */
104
+ deliverAfterCommit(id) {
105
+ // `mandatory` guarantees a live store here
106
+ const store = currentStore();
107
+ if (!store)
108
+ throw new Error("outbox: no transaction in context");
109
+ const ids = pendingIds.get(store);
110
+ if (ids) {
111
+ ids.push(id);
112
+ return;
113
+ }
114
+ const first = [id];
115
+ pendingIds.set(store, first);
116
+ afterCommit(() => this.fastPath(first));
117
+ }
118
+ };
119
+ __decorate([
120
+ Transactional({ propagation: "mandatory" })
121
+ ], OutboxService.prototype, "add", null);
122
+ OutboxService = __decorate([
123
+ Injectable(),
124
+ __param(0, Inject(TransactionHost)),
125
+ __param(1, Inject(KernelDb)),
126
+ __param(2, Inject(KERNEL_LOGGER)),
127
+ __param(3, Inject(OUTBOX_FAST_PATH)),
128
+ __param(4, Optional()),
129
+ __param(4, Inject(OUTBOX_STRICT_CONFLICTS))
130
+ ], OutboxService);
131
+ export { OutboxService };
@@ -0,0 +1,120 @@
1
+ import type { JobsOptions } from "bullmq";
2
+ import type { KernelQueueName } from "../queue/queue-names.js";
3
+ /** What the registry says about one job name added through the outbox (spec §3.5, §5). */
4
+ export interface OutboxJobSpec {
5
+ /** The kernel queue the job is added to, or the queues when one job name serves several. */
6
+ queue: KernelQueueName | readonly KernelQueueName[];
7
+ /** One sentence: why a duplicate delivery of this job is harmless (state check, key, id). */
8
+ idempotent: string;
9
+ /** Repo-relative path of the file whose `@Processor` handles the job. */
10
+ processedIn: string;
11
+ /**
12
+ * BullMQ options the dispatcher adds the job with over the queue defaults
13
+ * (`QUEUE_JOB_DEFAULTS`); the job id and the delay always come from the row.
14
+ */
15
+ opts?: Pick<JobsOptions, "attempts" | "backoff" | "priority" | "removeOnComplete" | "removeOnFail">;
16
+ }
17
+ /**
18
+ * Every job name added through the outbox. The task that introduces a job name adds its entry; a
19
+ * name without one does not type-check as an `OutboxJobName` (`add` does not check it at runtime).
20
+ */
21
+ export declare const OUTBOX_JOBS: {
22
+ readonly run: {
23
+ readonly queue: readonly ["cap-kernel-runs", "cap-kernel-subagent-runs"];
24
+ readonly idempotent: "the run worker starts only a run in `queued` state for this job's runId; a finished run's job only repeats the idempotent post-run ensureRun, a waiting run's does nothing";
25
+ readonly processedIn: "packages/kernel/src/runtime/run-processors.ts";
26
+ readonly opts: {
27
+ readonly attempts: 1;
28
+ readonly removeOnComplete: true;
29
+ readonly removeOnFail: true;
30
+ };
31
+ };
32
+ readonly "ensure-run": {
33
+ readonly queue: "cap-kernel-maintenance";
34
+ readonly idempotent: "ensureRun decides from the conversation's state; with nothing unconsumed it is a no-op";
35
+ readonly processedIn: "packages/kernel/src/runtime/maintenance.ts";
36
+ readonly opts: {
37
+ readonly removeOnComplete: true;
38
+ readonly removeOnFail: true;
39
+ };
40
+ };
41
+ readonly "wait-timeout": {
42
+ readonly queue: "cap-kernel-maintenance";
43
+ readonly idempotent: "a timeout acts only on a still-pending wait (a row lock and status check) and a resume starts only a waiting run with nothing else pending";
44
+ readonly processedIn: "packages/kernel/src/runtime/maintenance.ts";
45
+ readonly opts: {
46
+ readonly attempts: 5;
47
+ readonly backoff: {
48
+ readonly type: "exponential";
49
+ readonly delay: 5000;
50
+ };
51
+ readonly removeOnComplete: true;
52
+ readonly removeOnFail: true;
53
+ };
54
+ };
55
+ readonly "stop-tree": {
56
+ readonly queue: "cap-kernel-maintenance";
57
+ readonly idempotent: "stopping a stopped run is a no-op";
58
+ readonly processedIn: "packages/kernel/src/runtime/maintenance.ts";
59
+ readonly opts: {
60
+ readonly attempts: 5;
61
+ readonly backoff: {
62
+ readonly type: "exponential";
63
+ readonly delay: 5000;
64
+ };
65
+ readonly removeOnComplete: true;
66
+ readonly removeOnFail: true;
67
+ };
68
+ };
69
+ readonly "deliver-to-parent": {
70
+ readonly queue: "cap-kernel-maintenance";
71
+ readonly idempotent: "the delivery's idempotency key is the child run id";
72
+ readonly processedIn: "packages/kernel/src/runtime/maintenance.ts";
73
+ readonly opts: {
74
+ readonly attempts: 5;
75
+ readonly backoff: {
76
+ readonly type: "exponential";
77
+ readonly delay: 5000;
78
+ };
79
+ readonly removeOnComplete: true;
80
+ readonly removeOnFail: true;
81
+ };
82
+ };
83
+ readonly "usage-report": {
84
+ readonly queue: "cap-kernel-usage-report";
85
+ readonly idempotent: "handlers are idempotent by eventId (the event's `<conversationId>/<seq>` or the model_usage row id), and the job's `pending` list keeps a retry from reaching plugins already served";
86
+ readonly processedIn: "packages/kernel/src/runtime/run-processors.ts";
87
+ readonly opts: {
88
+ readonly attempts: 6;
89
+ readonly backoff: {
90
+ readonly type: "exponential";
91
+ readonly delay: 2000;
92
+ };
93
+ readonly removeOnComplete: {
94
+ readonly age: number;
95
+ readonly count: 100000;
96
+ };
97
+ readonly removeOnFail: {
98
+ readonly age: number;
99
+ readonly count: 100000;
100
+ };
101
+ };
102
+ };
103
+ readonly deliver: {
104
+ readonly queue: "cap-kernel-channels";
105
+ readonly idempotent: "a delivery resumes from the conversation's delivered seq under a per-conversation lock and sends with idempotency keys, so a second job sends nothing twice";
106
+ readonly processedIn: "packages/kernel/src/channels/channels.module.ts";
107
+ readonly opts: {
108
+ readonly attempts: 8;
109
+ readonly backoff: {
110
+ readonly type: "deliver";
111
+ };
112
+ readonly priority: 10;
113
+ readonly removeOnComplete: true;
114
+ readonly removeOnFail: true;
115
+ };
116
+ };
117
+ };
118
+ export type OutboxJobName = keyof typeof OUTBOX_JOBS;
119
+ /** The registry's BullMQ options of a job name (none for a name it does not list). */
120
+ export declare function outboxJobOptions(name: string): OutboxJobSpec["opts"];
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Every job name added through the outbox. The task that introduces a job name adds its entry; a
3
+ * name without one does not type-check as an `OutboxJobName` (`add` does not check it at runtime).
4
+ */
5
+ export const OUTBOX_JOBS = {
6
+ // `runtime/run-jobs.ts` RUN_JOB: one name on both run queues (a sub-agent's runs go to the
7
+ // sub-agent queue), under the job ids `runJobId` builds and, for a waiting run's resume
8
+ // (`RunResume.resumeIn`), `resumeJobId`
9
+ run: {
10
+ queue: ["cap-kernel-runs", "cap-kernel-subagent-runs"],
11
+ idempotent: "the run worker starts only a run in `queued` state for this job's runId; a finished run's job only repeats the idempotent post-run ensureRun, a waiting run's does nothing",
12
+ processedIn: "packages/kernel/src/runtime/run-processors.ts",
13
+ // one attempt (the run's own paths handle failures); removed once done, so the next run of the
14
+ // same trigger can take the id again (the post-run ensureRun, R34)
15
+ opts: { attempts: 1, removeOnComplete: true, removeOnFail: true },
16
+ },
17
+ // `runtime/run-jobs.ts` ENSURE_RUN_JOB: the ensure-run backstop owed with every message, queue
18
+ // change, background task finish, sub-agent delivery and run end (spec §6), under the job ids
19
+ // `ensureRunJobId` builds
20
+ "ensure-run": {
21
+ queue: "cap-kernel-maintenance",
22
+ idempotent: "ensureRun decides from the conversation's state; with nothing unconsumed it is a no-op",
23
+ processedIn: "packages/kernel/src/runtime/maintenance.ts",
24
+ // removed once done (the queue defaults keep finished jobs), so a later write under the same
25
+ // seq (a queue change writes no event of its own) owes a job that is not deduped away
26
+ opts: { removeOnComplete: true, removeOnFail: true },
27
+ },
28
+ // `runtime/waits.ts` WAIT_TIMEOUT_JOB: owed with the suspend that sets a run `waiting`, under
29
+ // `waitTimeoutJobId`, with `runAt` = the wait's deadline (spec §6 "Resumes and timeouts")
30
+ "wait-timeout": {
31
+ queue: "cap-kernel-maintenance",
32
+ idempotent: "a timeout acts only on a still-pending wait (a row lock and status check) and a resume starts only a waiting run with nothing else pending",
33
+ processedIn: "packages/kernel/src/runtime/maintenance.ts",
34
+ // retried while PG is briefly unavailable; removed once done
35
+ opts: {
36
+ attempts: 5,
37
+ backoff: { type: "exponential", delay: 5_000 },
38
+ removeOnComplete: true,
39
+ removeOnFail: true,
40
+ },
41
+ },
42
+ // `runtime/subagents/tree-jobs.ts` STOP_TREE_JOB: owed by a run's terminal transaction that
43
+ // leaves an active fork child (or a person-stopped sub-agent's descendants), under
44
+ // `stopChildrenJobId`, and by a root's delete, under `stopDeletedTreeJobId` (spec §6)
45
+ "stop-tree": {
46
+ queue: "cap-kernel-maintenance",
47
+ idempotent: "stopping a stopped run is a no-op",
48
+ processedIn: "packages/kernel/src/runtime/maintenance.ts",
49
+ // retried while PG is briefly unavailable; removed once done
50
+ opts: {
51
+ attempts: 5,
52
+ backoff: { type: "exponential", delay: 5_000 },
53
+ removeOnComplete: true,
54
+ removeOnFail: true,
55
+ },
56
+ },
57
+ // `runtime/subagents/tree-jobs.ts` DELIVER_TO_PARENT_JOB: owed by a sub-agent run's terminal
58
+ // transaction, under `deliverToParentJobId` (spec §6)
59
+ "deliver-to-parent": {
60
+ queue: "cap-kernel-maintenance",
61
+ idempotent: "the delivery's idempotency key is the child run id",
62
+ processedIn: "packages/kernel/src/runtime/maintenance.ts",
63
+ opts: {
64
+ attempts: 5,
65
+ backoff: { type: "exponential", delay: 5_000 },
66
+ removeOnComplete: true,
67
+ removeOnFail: true,
68
+ },
69
+ },
70
+ // `runtime/usage-report-jobs.ts` USAGE_REPORT_JOB: owed with the usage it reports (a `usage`
71
+ // event or a `model_usage` row) while some plugin subscribes, under `usageReportJobId` /
72
+ // `modelUsageJobId` (spec §8.1)
73
+ "usage-report": {
74
+ queue: "cap-kernel-usage-report",
75
+ idempotent: "handlers are idempotent by eventId (the event's `<conversationId>/<seq>` or the model_usage row id), and the job's `pending` list keeps a retry from reaching plugins already served",
76
+ processedIn: "packages/kernel/src/runtime/run-processors.ts",
77
+ // first attempt plus 5 retries, 2 s doubling (F11); finished jobs kept a day so a re-add of the
78
+ // same id (a crash between the add and the row's delete) dedupes; failed ones a week; both
79
+ // capped by count (I-2: a long outage of a billing endpoint cannot grow Redis)
80
+ opts: {
81
+ attempts: 6,
82
+ backoff: { type: "exponential", delay: 2_000 },
83
+ removeOnComplete: { age: 24 * 3600, count: 100_000 },
84
+ removeOnFail: { age: 7 * 86_400, count: 100_000 },
85
+ },
86
+ },
87
+ // `channels/deliver-job.ts` DELIVER_JOB: owed by every event append of a conversation with a
88
+ // channel target (a reply, an approval decision, a question answer, a wait stop) and by binding
89
+ // a conversation to its target, under `deliverJobId` (spec §8.1)
90
+ deliver: {
91
+ queue: "cap-kernel-channels",
92
+ idempotent: "a delivery resumes from the conversation's delivered seq under a per-conversation lock and sends with idempotency keys, so a second job sends nothing twice",
93
+ processedIn: "packages/kernel/src/channels/channels.module.ts",
94
+ // the platform's retry-after, else exponential (the processor's `deliver` backoff strategy);
95
+ // behind inbound jobs; removed once done, so a later append's id is never deduped away
96
+ opts: {
97
+ attempts: 8,
98
+ backoff: { type: "deliver" },
99
+ priority: 10,
100
+ removeOnComplete: true,
101
+ removeOnFail: true,
102
+ },
103
+ },
104
+ };
105
+ /** The registry's BullMQ options of a job name (none for a name it does not list). */
106
+ export function outboxJobOptions(name) {
107
+ return OUTBOX_JOBS[name]?.opts;
108
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The kernel's own BullMQ queues, spelled as literals so infra code (the outbox registry and
3
+ * dispatcher) names a queue without importing feature modules. `KERNEL_QUEUE_NAMES`
4
+ * (admin/health.service.ts) lists the same names from the feature constants;
5
+ * test/infra/queue-names.test.ts keeps the two equal.
6
+ */
7
+ export declare const KERNEL_QUEUES: readonly ["cap-kernel-runs", "cap-kernel-subagent-runs", "cap-kernel-channels", "cap-kernel-maintenance", "cap-kernel-plugin-scope", "cap-kernel-titles", "cap-kernel-usage-report", "cap-kernel-evals"];
8
+ export type KernelQueueName = (typeof KERNEL_QUEUES)[number];
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The kernel's own BullMQ queues, spelled as literals so infra code (the outbox registry and
3
+ * dispatcher) names a queue without importing feature modules. `KERNEL_QUEUE_NAMES`
4
+ * (admin/health.service.ts) lists the same names from the feature constants;
5
+ * test/infra/queue-names.test.ts keeps the two equal.
6
+ */
7
+ export const KERNEL_QUEUES = [
8
+ "cap-kernel-runs",
9
+ "cap-kernel-subagent-runs",
10
+ "cap-kernel-channels",
11
+ "cap-kernel-maintenance",
12
+ "cap-kernel-plugin-scope",
13
+ "cap-kernel-titles",
14
+ "cap-kernel-usage-report",
15
+ "cap-kernel-evals",
16
+ ];
@@ -25,6 +25,7 @@ import { databaseCheck, HEALTH_CHECKS, redisCheck } from "./infra/health/health.
25
25
  import { GracefulShutdownModule } from "./infra/lifecycle/graceful-shutdown.module.js";
26
26
  import { LockModule } from "./infra/lock/lock.module.js";
27
27
  import { LoggingModule } from "./infra/logging/logging.module.js";
28
+ import { OutboxModule } from "./infra/outbox/outbox.module.js";
28
29
  import { PresenceModule } from "./infra/presence/presence.module.js";
29
30
  import { QueueModule } from "./infra/queue/queue.module.js";
30
31
  import { RedisModule } from "./infra/redis/redis.module.js";
@@ -95,6 +96,8 @@ let KernelModule = KernelModule_1 = class KernelModule {
95
96
  ConfigModule.forRoot(config, options.env !== undefined ? { env: options.env } : {}),
96
97
  GracefulShutdownModule,
97
98
  DatabaseModule.forRoot(),
99
+ // owed jobs written in the caller's transaction (transactional outbox spec §3.2)
100
+ OutboxModule,
98
101
  RedisModule,
99
102
  LockModule,
100
103
  CacheModule,
@@ -53,6 +53,10 @@ export declare class DevicePolicyService {
53
53
  features: DeviceFeature[];
54
54
  policy: DeviceLocalPolicySummary | null;
55
55
  } | null>;
56
+ /** Adds `op` to the machine's pending ops, dropping the expired ones. */
57
+ private notePending;
58
+ /** Removes the pending op `opId` from the machine's row. */
59
+ private dropPending;
56
60
  /**
57
61
  * Keeps the policy the machine wrote on its row and audits the change (`confirmed`); with no
58
62
  * policy (the answer never came) only audits the attempt, unconfirmed. The detail's `isolation`