@capybara-hq/kernel 0.1.0-rc.22 → 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 (234) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin.controller.d.ts +3 -3
  3. package/dist/admin/admin.controller.js +6 -2
  4. package/dist/admin/admin.module.d.ts +2 -1
  5. package/dist/admin/admin.module.js +7 -3
  6. package/dist/admin/admin.schemas.d.ts +13 -0
  7. package/dist/admin/outbox-dead.controller.d.ts +23 -0
  8. package/dist/admin/outbox-dead.controller.js +62 -0
  9. package/dist/admin/outbox-dead.schemas.d.ts +8 -0
  10. package/dist/admin/outbox-dead.schemas.js +18 -0
  11. package/dist/admin/outbox-dead.service.d.ts +66 -0
  12. package/dist/admin/outbox-dead.service.js +179 -0
  13. package/dist/admin/overview.service.d.ts +40 -5
  14. package/dist/admin/overview.service.js +41 -7
  15. package/dist/agents/from-template.js +1 -1
  16. package/dist/background/finish.d.ts +5 -2
  17. package/dist/background/finish.js +14 -3
  18. package/dist/background/launch.d.ts +44 -5
  19. package/dist/background/launch.js +92 -9
  20. package/dist/background/session.d.ts +14 -2
  21. package/dist/background/session.js +26 -4
  22. package/dist/background/status.d.ts +1 -1
  23. package/dist/background/status.js +3 -3
  24. package/dist/background/stop.d.ts +0 -2
  25. package/dist/background/stop.js +1 -21
  26. package/dist/background/store.d.ts +8 -0
  27. package/dist/background/store.js +24 -0
  28. package/dist/catalog/admin-catalog.controller.d.ts +21 -2
  29. package/dist/catalog/admin-catalog.controller.js +45 -6
  30. package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
  31. package/dist/catalog/admin-catalog.schemas.js +8 -0
  32. package/dist/catalog/catalog-content.d.ts +5 -0
  33. package/dist/catalog/catalog-content.js +1 -0
  34. package/dist/catalog/catalog.module.d.ts +2 -1
  35. package/dist/catalog/catalog.module.js +5 -1
  36. package/dist/catalog/marketplaces.service.d.ts +1 -1
  37. package/dist/catalog/platform-import-update.d.ts +50 -0
  38. package/dist/catalog/platform-import-update.js +116 -0
  39. package/dist/catalog/plugin-import.d.ts +36 -1
  40. package/dist/catalog/plugin-import.js +23 -1
  41. package/dist/catalog/plugin-upgrade.d.ts +18 -0
  42. package/dist/catalog/plugin-upgrade.js +19 -0
  43. package/dist/catalog/requests.service.js +2 -2
  44. package/dist/channels/channels.module.d.ts +5 -2
  45. package/dist/channels/channels.module.js +9 -2
  46. package/dist/channels/deliver-job.d.ts +25 -0
  47. package/dist/channels/deliver-job.js +25 -0
  48. package/dist/channels/inbound.d.ts +1 -1
  49. package/dist/channels/inbound.js +2 -15
  50. package/dist/channels/jobs.d.ts +28 -18
  51. package/dist/channels/jobs.js +63 -26
  52. package/dist/config/schema.d.ts +3 -6
  53. package/dist/config/schema.js +20 -27
  54. package/dist/conversations/agent-tree.d.ts +16 -0
  55. package/dist/conversations/agent-tree.js +36 -0
  56. package/dist/conversations/conversations.service.d.ts +44 -14
  57. package/dist/conversations/conversations.service.js +52 -38
  58. package/dist/conversations/event-log.d.ts +7 -10
  59. package/dist/conversations/event-log.js +24 -15
  60. package/dist/conversations/events.js +2 -1
  61. package/dist/conversations/queue.service.d.ts +8 -3
  62. package/dist/conversations/queue.service.js +37 -13
  63. package/dist/conversations/titles.d.ts +21 -8
  64. package/dist/conversations/titles.js +42 -15
  65. package/dist/db/kernel-schema.d.ts +26 -8
  66. package/dist/devices/devices.service.d.ts +12 -3
  67. package/dist/devices/devices.service.js +30 -9
  68. package/dist/errors.d.ts +15 -0
  69. package/dist/errors.js +15 -0
  70. package/dist/evals/run-replay.js +14 -8
  71. package/dist/extensions/extensions.module.js +3 -0
  72. package/dist/extensions/lifecycle.d.ts +6 -1
  73. package/dist/extensions/lifecycle.js +9 -2
  74. package/dist/extensions/usage-subscribers.d.ts +43 -0
  75. package/dist/extensions/usage-subscribers.js +108 -0
  76. package/dist/http/api.module.js +2 -0
  77. package/dist/identity/settings.d.ts +53 -3
  78. package/dist/identity/settings.js +92 -9
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.js +1 -0
  81. package/dist/infra/cli/kernel-cli.module.js +5 -0
  82. package/dist/infra/config/kernel-config.service.d.ts +7 -0
  83. package/dist/infra/config/kernel-config.service.js +20 -0
  84. package/dist/infra/events/kernel-events.d.ts +9 -35
  85. package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
  86. package/dist/infra/outbox/outbox-relay.module.js +18 -0
  87. package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
  88. package/dist/infra/outbox/outbox.dispatcher.js +310 -0
  89. package/dist/infra/outbox/outbox.module.d.ts +24 -0
  90. package/dist/infra/outbox/outbox.module.js +50 -0
  91. package/dist/infra/outbox/outbox.relay.d.ts +31 -0
  92. package/dist/infra/outbox/outbox.relay.js +76 -0
  93. package/dist/infra/outbox/outbox.service.d.ts +51 -0
  94. package/dist/infra/outbox/outbox.service.js +131 -0
  95. package/dist/infra/outbox/registry.d.ts +120 -0
  96. package/dist/infra/outbox/registry.js +108 -0
  97. package/dist/infra/queue/queue-names.d.ts +8 -0
  98. package/dist/infra/queue/queue-names.js +16 -0
  99. package/dist/kernel.module.js +3 -0
  100. package/dist/local-devices/device-policy.service.d.ts +4 -0
  101. package/dist/local-devices/device-policy.service.js +61 -3
  102. package/dist/local-devices/local-device-gateway.d.ts +4 -2
  103. package/dist/local-devices/local-device-gateway.js +39 -14
  104. package/dist/local-devices/machines.d.ts +25 -1
  105. package/dist/local-devices/machines.js +91 -15
  106. package/dist/local-devices/policy-summary.d.ts +44 -1
  107. package/dist/local-devices/policy-summary.js +69 -0
  108. package/dist/models/embeddings.d.ts +9 -9
  109. package/dist/models/embeddings.js +11 -18
  110. package/dist/models/models.module.js +3 -0
  111. package/dist/orgs/orgs.service.js +7 -0
  112. package/dist/orgs/settings.controller.js +2 -2
  113. package/dist/orgs/settings.service.d.ts +11 -5
  114. package/dist/orgs/settings.service.js +28 -14
  115. package/dist/overrides.d.ts +20 -3
  116. package/dist/overrides.js +10 -2
  117. package/dist/platform/exclusions.js +5 -0
  118. package/dist/platform/ops/admin.ops.js +50 -9
  119. package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
  120. package/dist/platform/ops/catalog-admin.ops.js +32 -4
  121. package/dist/platform/ops/org-settings.ops.js +3 -3
  122. package/dist/platform/ops/orgs.ops.js +1 -0
  123. package/dist/platform/ops/outbox.ops.d.ts +21 -0
  124. package/dist/platform/ops/outbox.ops.js +125 -0
  125. package/dist/platform/ops/platform.ops.js +1 -1
  126. package/dist/platform/ops/schedules.ops.js +40 -1
  127. package/dist/platform/platform-ops.module.d.ts +2 -1
  128. package/dist/platform/platform-ops.module.js +3 -0
  129. package/dist/platform/route-schemas.js +3 -0
  130. package/dist/roles/worker.module.js +9 -4
  131. package/dist/runtime/langgraph-loop.js +13 -5
  132. package/dist/runtime/limits.d.ts +15 -0
  133. package/dist/runtime/limits.js +25 -0
  134. package/dist/runtime/loop.d.ts +8 -3
  135. package/dist/runtime/loop.js +2 -1
  136. package/dist/runtime/maintenance.d.ts +4 -2
  137. package/dist/runtime/maintenance.js +17 -2
  138. package/dist/runtime/model-call.d.ts +14 -16
  139. package/dist/runtime/model-call.js +14 -24
  140. package/dist/runtime/queue-lost-jobs.d.ts +24 -0
  141. package/dist/runtime/queue-lost-jobs.js +51 -0
  142. package/dist/runtime/run-control.d.ts +42 -5
  143. package/dist/runtime/run-control.js +66 -12
  144. package/dist/runtime/run-finish.d.ts +39 -12
  145. package/dist/runtime/run-finish.js +97 -14
  146. package/dist/runtime/run-jobs.d.ts +95 -13
  147. package/dist/runtime/run-jobs.js +199 -28
  148. package/dist/runtime/run-orphan-check.job.d.ts +142 -0
  149. package/dist/runtime/run-orphan-check.job.js +339 -0
  150. package/dist/runtime/run-processors.d.ts +16 -9
  151. package/dist/runtime/run-processors.js +1 -1
  152. package/dist/runtime/run-queue.d.ts +64 -55
  153. package/dist/runtime/run-queue.js +147 -284
  154. package/dist/runtime/run-steps.d.ts +1 -2
  155. package/dist/runtime/run-steps.js +8 -11
  156. package/dist/runtime/run-worker.d.ts +6 -17
  157. package/dist/runtime/run-worker.js +32 -34
  158. package/dist/runtime/runtime.module.d.ts +2 -3
  159. package/dist/runtime/runtime.module.js +5 -6
  160. package/dist/runtime/step-log.d.ts +16 -4
  161. package/dist/runtime/step-log.js +26 -6
  162. package/dist/runtime/stream.controller.d.ts +2 -4
  163. package/dist/runtime/stream.controller.js +5 -11
  164. package/dist/runtime/subagents/child.js +1 -1
  165. package/dist/runtime/subagents/definition.d.ts +2 -1
  166. package/dist/runtime/subagents/definition.js +4 -2
  167. package/dist/runtime/subagents/deliveries.d.ts +6 -3
  168. package/dist/runtime/subagents/deliveries.js +16 -7
  169. package/dist/runtime/subagents/dispatch.d.ts +12 -12
  170. package/dist/runtime/subagents/dispatch.js +13 -38
  171. package/dist/runtime/subagents/messages.d.ts +5 -1
  172. package/dist/runtime/subagents/messages.js +11 -3
  173. package/dist/runtime/subagents/skill-fork.d.ts +3 -3
  174. package/dist/runtime/subagents/skill-fork.js +5 -5
  175. package/dist/runtime/subagents/subagents.service.js +1 -1
  176. package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
  177. package/dist/runtime/subagents/tree-jobs.js +86 -0
  178. package/dist/runtime/usage-report-jobs.d.ts +23 -48
  179. package/dist/runtime/usage-report-jobs.js +67 -119
  180. package/dist/runtime/usage-report.d.ts +28 -77
  181. package/dist/runtime/usage-report.js +35 -213
  182. package/dist/runtime/waits.d.ts +48 -198
  183. package/dist/runtime/waits.js +103 -805
  184. package/dist/sandbox/platform-tiers.d.ts +86 -0
  185. package/dist/sandbox/platform-tiers.js +97 -0
  186. package/dist/sandbox/sandbox.service.d.ts +18 -5
  187. package/dist/sandbox/sandbox.service.js +75 -20
  188. package/dist/sandbox/tiers.d.ts +14 -2
  189. package/dist/sandbox/tiers.js +8 -0
  190. package/dist/schedules/calendar.d.ts +134 -0
  191. package/dist/schedules/calendar.js +410 -0
  192. package/dist/schedules/fire.d.ts +1 -3
  193. package/dist/schedules/fire.js +5 -10
  194. package/dist/schedules/run-tracker.d.ts +0 -5
  195. package/dist/schedules/run-tracker.js +0 -13
  196. package/dist/schedules/scan.job.js +15 -20
  197. package/dist/schedules/schedule-math.d.ts +42 -0
  198. package/dist/schedules/schedule-math.js +52 -0
  199. package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
  200. package/dist/schedules/scheduled-tasks.service.js +53 -0
  201. package/dist/schedules/schedules.controller.d.ts +5 -1
  202. package/dist/schedules/schedules.controller.js +15 -1
  203. package/dist/schedules/schedules.schemas.d.ts +35 -0
  204. package/dist/schedules/schedules.schemas.js +27 -0
  205. package/dist/schedules/store.d.ts +5 -0
  206. package/dist/schedules/store.js +20 -0
  207. package/dist/tools/approvals.service.d.ts +26 -7
  208. package/dist/tools/approvals.service.js +88 -10
  209. package/dist/tools/builtin-names.d.ts +5 -3
  210. package/dist/tools/builtin-names.js +12 -4
  211. package/dist/tools/builtins/builtin-tools.d.ts +1 -2
  212. package/dist/tools/builtins/builtin-tools.js +21 -17
  213. package/dist/tools/builtins/sandbox/bash.js +9 -3
  214. package/dist/tools/builtins/sandbox/common.d.ts +18 -1
  215. package/dist/tools/builtins/sandbox/common.js +14 -1
  216. package/dist/tools/builtins/sandbox/index.d.ts +2 -1
  217. package/dist/tools/builtins/sandbox/index.js +4 -1
  218. package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
  219. package/dist/tools/builtins/sandbox/task-list.js +130 -0
  220. package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
  221. package/dist/tools/builtins/sandbox/task-output.js +21 -3
  222. package/dist/tools/builtins/web/extract.d.ts +2 -4
  223. package/dist/tools/builtins/web/extract.js +2 -5
  224. package/dist/tools/pipeline.d.ts +1 -1
  225. package/dist/tools/pipeline.js +1 -1
  226. package/dist/tools/questions.service.d.ts +15 -6
  227. package/dist/tools/questions.service.js +20 -8
  228. package/dist/tools/registry.d.ts +4 -2
  229. package/dist/tools/registry.js +4 -3
  230. package/dist/tools/tools.module.js +4 -3
  231. package/package.json +3 -3
  232. package/src/migrations/0118_device_policy_ops.sql +6 -0
  233. package/src/migrations/0119_outbox.sql +22 -0
  234. package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
@@ -3,6 +3,7 @@ import type { EventPayload } from "../../conversations/events.js";
3
3
  import { KernelDb } from "../../infra/database/kernel-db.js";
4
4
  import { TransactionHost } from "../../infra/database/transaction-host.js";
5
5
  import { KernelEvents } from "../../infra/events/kernel-events.js";
6
+ import { OutboxService } from "../../infra/outbox/outbox.service.js";
6
7
  import type { KernelLogger } from "../../log.js";
7
8
  import { type DispatchRow } from "./child.js";
8
9
  /** What a caller of {@link SubagentDeliveries.deliverIn} gives; the rest comes from the dispatch. */
@@ -17,7 +18,7 @@ export declare const finishedDeliveryIdemKey: (childRunId: string) => string;
17
18
  *
18
19
  * A background child's run reaching a terminal state ({@link childFinished}) writes the
19
20
  * display-only `subagent_finished` and a `finished` delivery in one transaction, both idempotent by
20
- * the child run, so of two finishers (the worker and a stop, the orphan sweep) exactly one writes
21
+ * the child run, so of two finishers (the worker, a stop, the `deliver-to-parent` job, the `run-orphan-check`) exactly one writes
21
22
  * them. A run that handed back a report and then completed writes no delivery (acceptance
22
23
  * 2026-11-07): the report already told the parent everything, and a content-free "completed" line
23
24
  * only woke an idle parent for nothing and pushed the report away from the end of its request. Fork invocations (phase D, `for_skill`) do not deliver: their parent run waits on the child
@@ -29,7 +30,8 @@ export declare class SubagentDeliveries {
29
30
  private readonly events;
30
31
  private readonly log;
31
32
  readonly txHost: TransactionHost;
32
- constructor(kdb: KernelDb, eventLog: EventLog, events: KernelEvents, log: Pick<KernelLogger, "warn">, txHost: TransactionHost);
33
+ private readonly outbox;
34
+ constructor(kdb: KernelDb, eventLog: EventLog, events: KernelEvents, log: Pick<KernelLogger, "warn">, txHost: TransactionHost, outbox: Pick<OutboxService, "add">);
33
35
  /** The context's handle (the transaction of a `@Transactional` method, else the pool). */
34
36
  private get db();
35
37
  /**
@@ -38,7 +40,8 @@ export declare class SubagentDeliveries {
38
40
  * the child run's (a wake runs as that person, §7). Idempotent by `opts.idemKey` (a finish's
39
41
  * default is {@link finishedDeliveryIdemKey}; a report or a note must name its own): under the
40
42
  * parent conversation's row lock, an existing delivery returns null and writes nothing. After
41
- * the commit: the log's notify and `subagent.delivered`. Returns the new event's seq.
43
+ * the commit: the log's notify and `subagent.delivered`; the parent's ensure-run backstop is
44
+ * owed in the transaction. Returns the new event's seq.
42
45
  */
43
46
  deliverIn(row: DispatchRow, d: DeliveryInput, opts?: {
44
47
  idemKey?: string;
@@ -15,8 +15,9 @@ import { KernelDb } from "../../infra/database/kernel-db.js";
15
15
  import { TransactionHost } from "../../infra/database/transaction-host.js";
16
16
  import { Transactional } from "../../infra/database/transactional.decorator.js";
17
17
  import { KernelEvents } from "../../infra/events/kernel-events.js";
18
+ import { OutboxService } from "../../infra/outbox/outbox.service.js";
18
19
  import { KERNEL_LOGGER } from "../../infra/tokens.js";
19
- import { TERMINAL_STATUSES } from "../run-finish.js";
20
+ import { oweEnsureRun, TERMINAL_STATUSES } from "../run-jobs.js";
20
21
  import { childResult, deliveryRowOfChildRun, finishedEvent } from "./child.js";
21
22
  import { dispatchedIdemKey } from "./dispatch.js";
22
23
  /** Idempotency key of a child run's finished delivery (one per child run). */
@@ -31,7 +32,7 @@ const DELIVERY_TEXT_MAX = 200_000;
31
32
  *
32
33
  * A background child's run reaching a terminal state ({@link childFinished}) writes the
33
34
  * display-only `subagent_finished` and a `finished` delivery in one transaction, both idempotent by
34
- * the child run, so of two finishers (the worker and a stop, the orphan sweep) exactly one writes
35
+ * the child run, so of two finishers (the worker, a stop, the `deliver-to-parent` job, the `run-orphan-check`) exactly one writes
35
36
  * them. A run that handed back a report and then completed writes no delivery (acceptance
36
37
  * 2026-11-07): the report already told the parent everything, and a content-free "completed" line
37
38
  * only woke an idle parent for nothing and pushed the report away from the end of its request. Fork invocations (phase D, `for_skill`) do not deliver: their parent run waits on the child
@@ -43,12 +44,14 @@ let SubagentDeliveries = class SubagentDeliveries {
43
44
  events;
44
45
  log;
45
46
  txHost;
46
- constructor(kdb, eventLog, events, log, txHost) {
47
+ outbox;
48
+ constructor(kdb, eventLog, events, log, txHost, outbox) {
47
49
  this.kdb = kdb;
48
50
  this.eventLog = eventLog;
49
51
  this.events = events;
50
52
  this.log = log;
51
53
  this.txHost = txHost;
54
+ this.outbox = outbox;
52
55
  }
53
56
  /** The context's handle (the transaction of a `@Transactional` method, else the pool). */
54
57
  get db() {
@@ -60,7 +63,8 @@ let SubagentDeliveries = class SubagentDeliveries {
60
63
  * the child run's (a wake runs as that person, §7). Idempotent by `opts.idemKey` (a finish's
61
64
  * default is {@link finishedDeliveryIdemKey}; a report or a note must name its own): under the
62
65
  * parent conversation's row lock, an existing delivery returns null and writes nothing. After
63
- * the commit: the log's notify and `subagent.delivered`. Returns the new event's seq.
66
+ * the commit: the log's notify and `subagent.delivered`; the parent's ensure-run backstop is
67
+ * owed in the transaction. Returns the new event's seq.
64
68
  */
65
69
  async deliverIn(row, d, opts = {}) {
66
70
  const idemKey = opts.idemKey ?? (d.kind === "finished" ? finishedDeliveryIdemKey(row.childRunId) : undefined);
@@ -68,9 +72,9 @@ let SubagentDeliveries = class SubagentDeliveries {
68
72
  throw new Error(`a ${d.kind} delivery needs its idempotency key`);
69
73
  const t = this.db;
70
74
  const parent = row.parentConversationId;
71
- await t
75
+ const conv = await t
72
76
  .selectFrom("conversations")
73
- .select("id")
77
+ .select("org_id")
74
78
  .where("id", "=", parent)
75
79
  .forUpdate()
76
80
  .executeTakeFirst();
@@ -103,6 +107,10 @@ let SubagentDeliveries = class SubagentDeliveries {
103
107
  ]);
104
108
  if (!event)
105
109
  return null;
110
+ // the parent's wake is owed with the delivery: the wake after the commit (`BackgroundWake`)
111
+ // may never happen (transactional outbox spec §6)
112
+ if (conv)
113
+ await oweEnsureRun(this.outbox, parent, event.seq, conv.org_id);
106
114
  afterCommit(async () => {
107
115
  await this.eventLog.notify(parent, event.seq);
108
116
  await this.events.emit("subagent.delivered", { conversationId: parent });
@@ -223,6 +231,7 @@ SubagentDeliveries = __decorate([
223
231
  __param(1, Inject(EventLog)),
224
232
  __param(2, Inject(KernelEvents)),
225
233
  __param(3, Inject(KERNEL_LOGGER)),
226
- __param(4, Inject(TransactionHost))
234
+ __param(4, Inject(TransactionHost)),
235
+ __param(5, Inject(OutboxService))
227
236
  ], SubagentDeliveries);
228
237
  export { SubagentDeliveries };
@@ -18,8 +18,11 @@ export interface DispatchDeps {
18
18
  purposes: Pick<ModelPurposesService, "resolve">;
19
19
  registry: Pick<ProviderRegistry, "resolve">;
20
20
  config: Pick<KernelConfigService, "get">;
21
- /** Enqueues a child run (on the sub-agent run queue: `kind` is always `subagent`). */
22
- enqueue: (data: RunJobData, jobId: string, kind: ConversationKind) => Promise<void>;
21
+ /**
22
+ * Owes a child run's job in the dispatch's transaction (`RunJobs.owe`: on the sub-agent run
23
+ * queue, `kind` is always `subagent`); false when the job id is already owed.
24
+ */
25
+ owe: (data: RunJobData, jobId: string, kind: ConversationKind, orgId: string) => Promise<boolean>;
23
26
  log: Pick<Logger, "error" | "warn">;
24
27
  /**
25
28
  * Runs {@link commitDispatchIn} in a transaction of its own (`SubagentsService.commitDispatch`,
@@ -121,9 +124,9 @@ export interface DispatchPlan {
121
124
  * dispatches of one run serialize) inserts the child conversation, appends `subagent_started` and
122
125
  * the task message, inserts the child run (`queued`) and the `subagent_dispatches` row, appends
123
126
  * `subagent_dispatched` and the call's `tool_result` ({@link agentStartedText}: the sub-agent runs
124
- * in the background) to the parent and audits `subagent.dispatched` and `tool.called`. After
125
- * commit the child run is enqueued; a failed enqueue only logs (the dispatch is durable, the wait
126
- * sweep re-enqueues it). A fork invocation (`forSkill`) writes no `tool_result`: its run waits on
127
+ * in the background) to the parent and audits `subagent.dispatched` and `tool.called`. The child
128
+ * run's job is owed in the same transaction (transactional outbox spec §6), so the committed
129
+ * dispatch always starts its child. A fork invocation (`forSkill`) writes no `tool_result`: its run waits on
127
130
  * the child (`advanceSkillForks`).
128
131
  *
129
132
  * Refusals (§4.5) start nothing: a call from a run at `subagents.maxDepth` (agent map spec §4.1:
@@ -188,7 +191,8 @@ export declare const sentMessageIdemKey: (runId: string, callId: string) => stri
188
191
  * row, then the parent run row (the order every terminal path uses),
189
192
  * runs `child` (writes the child conversation's first events and returns the task message, or
190
193
  * null to refuse), then inserts the child run and the dispatch row, appends `subagent_dispatched`
191
- * and the audit row. After commit: notifies both conversations and enqueues the child run.
194
+ * and the audit row, and owes the child run's job (transactional outbox spec §6). After commit:
195
+ * notifies both conversations.
192
196
  * Returns null when `child` refused.
193
197
  *
194
198
  * Under the parent run's row lock it first re-checks what the pipeline checked without it: a call
@@ -205,10 +209,6 @@ export type DispatchCommit = {
205
209
  kind: "existing";
206
210
  childRunId: string;
207
211
  childConversationId: string;
208
- requeue: {
209
- conversationId: string;
210
- seq: number;
211
- } | null;
212
212
  /** The call's logged result (null for a fork dispatch) and its seq, to notify. */
213
213
  result: {
214
214
  content: string;
@@ -233,7 +233,7 @@ export type DispatchCommit = {
233
233
  * {@link commit}'s transaction (run by `SubagentsService.commitDispatch`, `d.db` its handle).
234
234
  * Null when `child` refused.
235
235
  */
236
- export declare function commitDispatchIn(d: Pick<DispatchDeps, "db" | "eventLog" | "config">, a: DispatchArgs, plan: DispatchPlan, child: ChildWriter): Promise<DispatchCommit | null>;
237
- export declare function commit(d: Pick<DispatchDeps, "commitDispatch" | "enqueue" | "log" | "eventLog">, a: DispatchArgs, plan: DispatchPlan,
236
+ export declare function commitDispatchIn(d: Pick<DispatchDeps, "db" | "eventLog" | "config" | "owe">, a: DispatchArgs, plan: DispatchPlan, child: ChildWriter): Promise<DispatchCommit | null>;
237
+ export declare function commit(d: Pick<DispatchDeps, "commitDispatch" | "eventLog">, a: DispatchArgs, plan: DispatchPlan,
238
238
  /** Writes the child's events; `checked` is the org agent version the in-lock gates passed. */
239
239
  child: ChildWriter): Promise<DispatchOutcome | null>;
@@ -31,9 +31,9 @@ export const dispatchedIdemKey = (runId, callId) => `subagent_dispatched:${runId
31
31
  * dispatches of one run serialize) inserts the child conversation, appends `subagent_started` and
32
32
  * the task message, inserts the child run (`queued`) and the `subagent_dispatches` row, appends
33
33
  * `subagent_dispatched` and the call's `tool_result` ({@link agentStartedText}: the sub-agent runs
34
- * in the background) to the parent and audits `subagent.dispatched` and `tool.called`. After
35
- * commit the child run is enqueued; a failed enqueue only logs (the dispatch is durable, the wait
36
- * sweep re-enqueues it). A fork invocation (`forSkill`) writes no `tool_result`: its run waits on
34
+ * in the background) to the parent and audits `subagent.dispatched` and `tool.called`. The child
35
+ * run's job is owed in the same transaction (transactional outbox spec §6), so the committed
36
+ * dispatch always starts its child. A fork invocation (`forSkill`) writes no `tool_result`: its run waits on
37
37
  * the child (`advanceSkillForks`).
38
38
  *
39
39
  * Refusals (§4.5) start nothing: a call from a run at `subagents.maxDepth` (agent map spec §4.1:
@@ -226,7 +226,7 @@ const isActiveStatus = (status) => ACTIVE_RUN_STATUSES.includes(status);
226
226
  * delivered (agent map spec, rc upgrade: a foreground-era dispatch whose rc finisher wrote
227
227
  * `subagent_finished` and resumed the parent): the child's report (`childResult`), with the
228
228
  * parent's `subagent_finished` appended in this transaction when it is missing (idempotent by its
229
- * key), so the undelivered sweep never delivers it a second time. Null when the parent already
229
+ * key), so the `deliver-to-parent` job never delivers it a second time. Null when the parent already
230
230
  * has a delivery for the run (it carries the result) or the run has no terminal event yet.
231
231
  */
232
232
  async function rcFinishedResult(d, row) {
@@ -272,7 +272,7 @@ export async function commitDispatchIn(d, a, plan, child) {
272
272
  const existing = await t
273
273
  .selectFrom("subagent_dispatches as sd")
274
274
  .leftJoin("runs as cr", "cr.id", "sd.child_run_id")
275
- .select(["sd.child_run_id", "sd.child_conversation_id", "cr.status", "cr.trigger_seq"])
275
+ .select(["sd.child_run_id", "sd.child_conversation_id", "cr.status"])
276
276
  .where("sd.parent_run_id", "=", ctx.runId)
277
277
  .where("sd.parent_call_id", "=", callId)
278
278
  .executeTakeFirst();
@@ -284,10 +284,10 @@ export async function commitDispatchIn(d, a, plan, child) {
284
284
  let result = null;
285
285
  if (a.forSkill === undefined) {
286
286
  // an rc child that already ended under the foreground wait, whose rc finisher wrote
287
- // `subagent_finished` and resumed this run (no delivery; the sweep never delivers a run with
288
- // `subagent_finished`): its report is this call's result, as the rc gave it, or it is lost.
287
+ // `subagent_finished` and resumed this run (no delivery; a run with `subagent_finished` is
288
+ // never delivered): its report is this call's result, as the rc gave it, or it is lost.
289
289
  // Only while the call has no result: a call that has one (a background dispatch's second
290
- // holder) keeps it, and its child's finish stays to its delivery or the undelivered sweep.
290
+ // holder) keeps it, and its child's finish stays to its delivery (or its `deliver-to-parent` job).
291
291
  const resultKey = resultEvent(ctx, callId, AGENT_TOOL, "").idemKey;
292
292
  const finished = a.sent === undefined &&
293
293
  existing.status !== null &&
@@ -312,13 +312,6 @@ export async function commitDispatchIn(d, a, plan, child) {
312
312
  childRunId: existing.child_run_id,
313
313
  childConversationId: existing.child_conversation_id,
314
314
  result,
315
- // still queued: the first holder may have died before its enqueue (review M4)
316
- requeue: existing.status === "queued" && existing.trigger_seq !== null
317
- ? {
318
- conversationId: existing.child_conversation_id,
319
- seq: Number(existing.trigger_seq),
320
- }
321
- : null,
322
315
  };
323
316
  }
324
317
  if (parent?.status !== "running")
@@ -384,6 +377,9 @@ export async function commitDispatchIn(d, a, plan, child) {
384
377
  for_skill: a.forSkill !== undefined,
385
378
  })
386
379
  .execute();
380
+ // the child run's job is owed with its row (transactional outbox spec §6): a queued child
381
+ // always has its job, whatever happens after the commit
382
+ await d.owe({ runId: childRunId, conversationId: plan.childConversationId }, runJobId(plan.childConversationId, task.seq), "subagent", ctx.orgId);
387
383
  // an org agent runs on its own model (§5.4): a `model` on the call was ignored
388
384
  const ownModel = plan.source === "agent" && input.model !== undefined;
389
385
  const content = a.sent?.content ?? agentStartedText(plan.childConversationId, { ownModel });
@@ -475,19 +471,7 @@ child) {
475
471
  if (out === null)
476
472
  return null;
477
473
  if (out.kind === "existing") {
478
- // a child still queued is enqueued again under the first dispatch's job id (BullMQ dedupes
479
- // it while that job exists), so a holder that died before its enqueue does not leave the
480
- // child to the wait sweep
481
- if (out.requeue !== null) {
482
- const jobId = runJobId(out.requeue.conversationId, out.requeue.seq);
483
- await d
484
- .enqueue({ runId: out.childRunId, conversationId: out.requeue.conversationId }, jobId, "subagent")
485
- .catch((err) => d.log.error("cannot enqueue sub-agent run; the sweep retries", {
486
- childRunId: out.childRunId,
487
- jobId,
488
- err,
489
- }));
490
- }
474
+ // the child's job was owed with the first dispatch (a still queued child has it)
491
475
  if (out.result !== null)
492
476
  await d.eventLog.notify(ctx.conversationId, out.result.seq);
493
477
  return {
@@ -507,18 +491,9 @@ child) {
507
491
  return out.outcome;
508
492
  await d.eventLog.notify(plan.childConversationId, out.childSeq);
509
493
  await d.eventLog.notify(ctx.conversationId, out.parentSeq);
510
- const childRunId = out.childRunId;
511
- const jobId = runJobId(plan.childConversationId, out.childSeq);
512
- try {
513
- await d.enqueue({ runId: childRunId, conversationId: plan.childConversationId }, jobId, "subagent");
514
- }
515
- catch (err) {
516
- // the dispatch is committed: the wait sweep re-enqueues the queued child run
517
- d.log.error("cannot enqueue sub-agent run; the sweep retries", { childRunId, jobId, err });
518
- }
519
494
  return {
520
495
  kind: "dispatched",
521
- childRunId,
496
+ childRunId: out.childRunId,
522
497
  childConversationId: plan.childConversationId,
523
498
  content: out.content,
524
499
  };
@@ -50,12 +50,16 @@ export declare class SubagentMessages {
50
50
  private readonly kdb;
51
51
  private readonly eventLog;
52
52
  private readonly config;
53
+ /** Owes a restarted sub-agent's run job in the dispatch's transaction. */
54
+ private readonly jobs;
53
55
  private readonly deliveries;
54
56
  readonly txHost: TransactionHost;
55
57
  private readonly hooks?;
56
58
  /** What the dispatch transaction of a restart needs (`commit`). */
57
59
  private readonly deps;
58
- constructor(kdb: KernelDb, eventLog: EventLog, config: KernelConfigService, jobs: RunJobs, log: KernelLogger, deliveries: SubagentDeliveries, txHost: TransactionHost, hooks?: SubagentMessagesHooks | undefined);
60
+ constructor(kdb: KernelDb, eventLog: EventLog, config: KernelConfigService,
61
+ /** Owes a restarted sub-agent's run job in the dispatch's transaction. */
62
+ jobs: Pick<RunJobs, "owe">, log: KernelLogger, deliveries: SubagentDeliveries, txHost: TransactionHost, hooks?: SubagentMessagesHooks | undefined);
59
63
  /** The restart's dispatch writes in a transaction of their own (see `commitDispatchIn`). */
60
64
  private commitDispatch;
61
65
  /** The context's handle (the transaction of a `@Transactional` method, else the pool). */
@@ -56,28 +56,36 @@ let SubagentMessages = class SubagentMessages {
56
56
  kdb;
57
57
  eventLog;
58
58
  config;
59
+ jobs;
59
60
  deliveries;
60
61
  txHost;
61
62
  hooks;
62
63
  /** What the dispatch transaction of a restart needs (`commit`). */
63
64
  deps;
64
- constructor(kdb, eventLog, config, jobs, log, deliveries, txHost, hooks) {
65
+ constructor(kdb, eventLog, config,
66
+ /** Owes a restarted sub-agent's run job in the dispatch's transaction. */
67
+ jobs, log, deliveries, txHost, hooks) {
65
68
  this.kdb = kdb;
66
69
  this.eventLog = eventLog;
67
70
  this.config = config;
71
+ this.jobs = jobs;
68
72
  this.deliveries = deliveries;
69
73
  this.txHost = txHost;
70
74
  this.hooks = hooks;
71
75
  this.deps = {
72
76
  eventLog,
73
- enqueue: (data, jobId, kind) => jobs.enqueue(data, jobId, kind),
74
77
  log,
75
78
  commitDispatch: (a, plan, child) => this.commitDispatch(a, plan, child),
76
79
  };
77
80
  }
78
81
  /** The restart's dispatch writes in a transaction of their own (see `commitDispatchIn`). */
79
82
  commitDispatch(a, plan, child) {
80
- return commitDispatchIn({ db: this.db, eventLog: this.eventLog, config: this.config }, a, plan, child);
83
+ return commitDispatchIn({
84
+ db: this.db,
85
+ eventLog: this.eventLog,
86
+ config: this.config,
87
+ owe: (data, jobId, kind, orgId) => this.jobs.owe(data, jobId, kind, orgId),
88
+ }, a, plan, child);
81
89
  }
82
90
  /** The context's handle (the transaction of a `@Transactional` method, else the pool). */
83
91
  get db() {
@@ -11,8 +11,8 @@ import { type DispatchDeps } from "./dispatch.js";
11
11
  * through phase C's `dispatchSubagent` before any model call and waits on it (`subagents` wait
12
12
  * `sa-<runId>-0`; the dispatch row has `for_skill`, agent map spec §5.4: a fork stays in the
13
13
  * foreground while `Agent` calls run in the background). When the child has ended, `prepare` appends the model-visible
14
- * `skill_fork_result` and the parent's model answers. Every terminal path of the run (and the wait
15
- * sweep) pairs a fork invocation without a report ({@link pairStoppedForks}), so the log never holds
14
+ * `skill_fork_result` and the parent's model answers. Every terminal path of the run
15
+ * (`RunFinish.finishIn`) pairs a fork invocation without a report ({@link pairStoppedForks}), so the log never holds
16
16
  * a fork lead whose report is missing before the next model call.
17
17
  */
18
18
  /** The dispatch's parent call id (no `:`; there is no tool call). */
@@ -108,7 +108,7 @@ export declare const recordedSkillForks: (eventLog: Pick<EventLog, "append" | "f
108
108
  payload: unknown;
109
109
  }[]) => SkillForks;
110
110
  /**
111
- * In the transaction of a run's terminal path (or the wait sweep): the run's fork invocation
111
+ * In the transaction of a run's terminal path (`RunFinish.finishIn`): the run's fork invocation
112
112
  * (its trigger message, by the run boundary) without a report gets one now: the ended child's
113
113
  * result (and the parent's missing `subagent_finished`), {@link AGENT_STOPPED_WITH_RUN_TEXT} for a
114
114
  * child still active (the caller stops it after its commit, as for `Agent` calls), or
@@ -1,7 +1,7 @@
1
1
  import { ApiError } from "../../errors.js";
2
2
  import { AGENT_STOPPED_WITH_RUN_TEXT } from "../../tools/builtins/agent.js";
3
3
  import { subagentsWait } from "../loop.js";
4
- import { TERMINAL_STATUSES } from "../run-finish.js";
4
+ import { TERMINAL_STATUSES } from "../run-jobs.js";
5
5
  import { childResult, dispatchOfCall, finishedEvent } from "./child.js";
6
6
  import { dispatchSubagent } from "./dispatch.js";
7
7
  /**
@@ -10,8 +10,8 @@ import { dispatchSubagent } from "./dispatch.js";
10
10
  * through phase C's `dispatchSubagent` before any model call and waits on it (`subagents` wait
11
11
  * `sa-<runId>-0`; the dispatch row has `for_skill`, agent map spec §5.4: a fork stays in the
12
12
  * foreground while `Agent` calls run in the background). When the child has ended, `prepare` appends the model-visible
13
- * `skill_fork_result` and the parent's model answers. Every terminal path of the run (and the wait
14
- * sweep) pairs a fork invocation without a report ({@link pairStoppedForks}), so the log never holds
13
+ * `skill_fork_result` and the parent's model answers. Every terminal path of the run
14
+ * (`RunFinish.finishIn`) pairs a fork invocation without a report ({@link pairStoppedForks}), so the log never holds
15
15
  * a fork lead whose report is missing before the next model call.
16
16
  */
17
17
  /** The dispatch's parent call id (no `:`; there is no tool call). */
@@ -126,7 +126,7 @@ export async function advanceSkillForks(d, a) {
126
126
  agent: ctx.agent,
127
127
  accountId: ctx.triggerAccountId,
128
128
  signal: ctx.signal,
129
- deadline: ctx.limits.deadline,
129
+ ...(ctx.limits.deadline !== undefined && { deadline: ctx.limits.deadline }),
130
130
  ...(ctx.conversationKind !== undefined &&
131
131
  ctx.conversationKind !== "eval" && { conversationKind: ctx.conversationKind }),
132
132
  },
@@ -188,7 +188,7 @@ export const recordedSkillForks = (eventLog, recorded) => ({
188
188
  },
189
189
  });
190
190
  /**
191
- * In the transaction of a run's terminal path (or the wait sweep): the run's fork invocation
191
+ * In the transaction of a run's terminal path (`RunFinish.finishIn`): the run's fork invocation
192
192
  * (its trigger message, by the run boundary) without a report gets one now: the ended child's
193
193
  * result (and the parent's missing `subagent_finished`), {@link AGENT_STOPPED_WITH_RUN_TEXT} for a
194
194
  * child still active (the caller stops it after its commit, as for `Agent` calls), or
@@ -53,7 +53,7 @@ let SubagentsService = class SubagentsService {
53
53
  purposes,
54
54
  registry,
55
55
  config,
56
- enqueue: (data, jobId, kind) => jobs.enqueue(data, jobId, kind),
56
+ owe: (data, jobId, kind, orgId) => jobs.owe(data, jobId, kind, orgId),
57
57
  log,
58
58
  invoker,
59
59
  commitDispatch: (a, plan, child) => this.commitDispatch(a, plan, child),
@@ -0,0 +1,61 @@
1
+ import type { OutboxService } from "../../infra/outbox/outbox.service.js";
2
+ import type { Waits } from "../waits.js";
3
+ /**
4
+ * The stops a transaction owes a sub-agent tree (transactional outbox spec §6 "Sub-agents"), on
5
+ * the maintenance queue. Two shapes:
6
+ * - `{ runId }`: what a run's terminal transaction owes its children: its active fork invocation
7
+ * child stops (`Waits.stopChildren`, sub-agents spec §12), and a sub-agent run a person stopped
8
+ * stops its descendants (`Waits.stopDescendantsOfStopped`, agent map spec §6);
9
+ * - `{ conversationId, byAccountId }`: a deleted root's whole tree below it stops
10
+ * (`Waits.stopTree` without the root, agent map spec §10).
11
+ *
12
+ * The caller still stops them right after its commit; the job is the backstop for a process that
13
+ * died in between, {@link TREE_JOB_BACKSTOP_MS} later.
14
+ */
15
+ export declare const STOP_TREE_JOB = "stop-tree";
16
+ /**
17
+ * A finished sub-agent run's delivery to its parent (`Waits.childFinished`: a background child's
18
+ * `subagent_finished` and `finished` delivery, a fork child's `subagent_finished` and its parent
19
+ * run's resume), owed by the child run's terminal transaction. The delivery is not written in that
20
+ * transaction: it holds the child's conversation and run rows, and the parent's conversation row
21
+ * comes before the child's in the dispatch's lock order (parent conversation, parent run, budget
22
+ * run, child conversation), so taking it there could deadlock with a follow-up dispatch. The
23
+ * caller delivers right after its commit; the job is the backstop, {@link TREE_JOB_BACKSTOP_MS}
24
+ * later.
25
+ */
26
+ export declare const DELIVER_TO_PARENT_JOB = "deliver-to-parent";
27
+ /** How long after its transaction a tree job fires (a backstop of the direct call, not a second). */
28
+ export declare const TREE_JOB_BACKSTOP_MS = 30000;
29
+ /** The `stop-tree` job's data. */
30
+ export type StopTreeJobData = {
31
+ runId: string;
32
+ } | {
33
+ conversationId: string;
34
+ byAccountId: string | null;
35
+ };
36
+ /** The `deliver-to-parent` job's data. */
37
+ export interface DeliverToParentJobData {
38
+ childRunId: string;
39
+ }
40
+ /** The `stop-tree` job id of a finished run's children (no `:`). */
41
+ export declare const stopChildrenJobId: (runId: string) => string;
42
+ /** The `stop-tree` job id of a deleted root's tree (a conversation is deleted once; no `:`). */
43
+ export declare const stopDeletedTreeJobId: (conversationId: string) => string;
44
+ /** The `deliver-to-parent` job id: one per child run (no `:`). */
45
+ export declare const deliverToParentJobId: (childRunId: string) => string;
46
+ /** Owes the stops of `runId`'s children in the caller's transaction (throws outside one). */
47
+ export declare function oweStopChildren(outbox: Pick<OutboxService, "add">, runId: string, orgId: string): Promise<boolean>;
48
+ /** Owes the stop of a deleted root's tree in the caller's transaction (throws outside one). */
49
+ export declare function oweStopDeletedTree(outbox: Pick<OutboxService, "add">, conversationId: string, byAccountId: string | null, orgId: string): Promise<boolean>;
50
+ /** Owes a finished child run's delivery to its parent in the caller's transaction. */
51
+ export declare function oweDeliverToParent(outbox: Pick<OutboxService, "add">, childRunId: string, orgId: string): Promise<boolean>;
52
+ /**
53
+ * The `stop-tree` job (`MaintenanceProcessor`): every stop reads the active runs and stops only
54
+ * those, so a run the direct call already stopped is not stopped again.
55
+ */
56
+ export declare function processStopTree(waits: Pick<Waits, "stopChildren" | "stopDescendantsOfStopped" | "stopTree">, data: StopTreeJobData): Promise<void>;
57
+ /**
58
+ * The `deliver-to-parent` job (`MaintenanceProcessor`): `Waits.childFinished` writes the delivery
59
+ * once by the child run (idempotency keys under the parent conversation's row lock).
60
+ */
61
+ export declare function processDeliverToParent(waits: Pick<Waits, "childFinished">, data: DeliverToParentJobData): Promise<void>;
@@ -0,0 +1,86 @@
1
+ import { MAINTENANCE_QUEUE_NAME } from "../maintenance-queue.js";
2
+ /**
3
+ * The stops a transaction owes a sub-agent tree (transactional outbox spec §6 "Sub-agents"), on
4
+ * the maintenance queue. Two shapes:
5
+ * - `{ runId }`: what a run's terminal transaction owes its children: its active fork invocation
6
+ * child stops (`Waits.stopChildren`, sub-agents spec §12), and a sub-agent run a person stopped
7
+ * stops its descendants (`Waits.stopDescendantsOfStopped`, agent map spec §6);
8
+ * - `{ conversationId, byAccountId }`: a deleted root's whole tree below it stops
9
+ * (`Waits.stopTree` without the root, agent map spec §10).
10
+ *
11
+ * The caller still stops them right after its commit; the job is the backstop for a process that
12
+ * died in between, {@link TREE_JOB_BACKSTOP_MS} later.
13
+ */
14
+ export const STOP_TREE_JOB = "stop-tree";
15
+ /**
16
+ * A finished sub-agent run's delivery to its parent (`Waits.childFinished`: a background child's
17
+ * `subagent_finished` and `finished` delivery, a fork child's `subagent_finished` and its parent
18
+ * run's resume), owed by the child run's terminal transaction. The delivery is not written in that
19
+ * transaction: it holds the child's conversation and run rows, and the parent's conversation row
20
+ * comes before the child's in the dispatch's lock order (parent conversation, parent run, budget
21
+ * run, child conversation), so taking it there could deadlock with a follow-up dispatch. The
22
+ * caller delivers right after its commit; the job is the backstop, {@link TREE_JOB_BACKSTOP_MS}
23
+ * later.
24
+ */
25
+ export const DELIVER_TO_PARENT_JOB = "deliver-to-parent";
26
+ /** How long after its transaction a tree job fires (a backstop of the direct call, not a second). */
27
+ export const TREE_JOB_BACKSTOP_MS = 30_000;
28
+ /** The `stop-tree` job id of a finished run's children (no `:`). */
29
+ export const stopChildrenJobId = (runId) => `stop-tree-run-${runId}`;
30
+ /** The `stop-tree` job id of a deleted root's tree (a conversation is deleted once; no `:`). */
31
+ export const stopDeletedTreeJobId = (conversationId) => `stop-tree-deleted-${conversationId}`;
32
+ /** The `deliver-to-parent` job id: one per child run (no `:`). */
33
+ export const deliverToParentJobId = (childRunId) => `deliver-to-parent-${childRunId}`;
34
+ const backstopAt = () => new Date(Date.now() + TREE_JOB_BACKSTOP_MS);
35
+ /** Owes the stops of `runId`'s children in the caller's transaction (throws outside one). */
36
+ export function oweStopChildren(outbox, runId, orgId) {
37
+ return outbox.add({
38
+ queue: MAINTENANCE_QUEUE_NAME,
39
+ name: STOP_TREE_JOB,
40
+ jobId: stopChildrenJobId(runId),
41
+ data: { runId },
42
+ runAt: backstopAt(),
43
+ orgId,
44
+ });
45
+ }
46
+ /** Owes the stop of a deleted root's tree in the caller's transaction (throws outside one). */
47
+ export function oweStopDeletedTree(outbox, conversationId, byAccountId, orgId) {
48
+ return outbox.add({
49
+ queue: MAINTENANCE_QUEUE_NAME,
50
+ name: STOP_TREE_JOB,
51
+ jobId: stopDeletedTreeJobId(conversationId),
52
+ data: { conversationId, byAccountId },
53
+ runAt: backstopAt(),
54
+ orgId,
55
+ });
56
+ }
57
+ /** Owes a finished child run's delivery to its parent in the caller's transaction. */
58
+ export function oweDeliverToParent(outbox, childRunId, orgId) {
59
+ return outbox.add({
60
+ queue: MAINTENANCE_QUEUE_NAME,
61
+ name: DELIVER_TO_PARENT_JOB,
62
+ jobId: deliverToParentJobId(childRunId),
63
+ data: { childRunId },
64
+ runAt: backstopAt(),
65
+ orgId,
66
+ });
67
+ }
68
+ /**
69
+ * The `stop-tree` job (`MaintenanceProcessor`): every stop reads the active runs and stops only
70
+ * those, so a run the direct call already stopped is not stopped again.
71
+ */
72
+ export async function processStopTree(waits, data) {
73
+ if ("runId" in data) {
74
+ await waits.stopChildren(data.runId, null);
75
+ await waits.stopDescendantsOfStopped(data.runId);
76
+ return;
77
+ }
78
+ await waits.stopTree(data.conversationId, data.byAccountId, { includeSelf: false });
79
+ }
80
+ /**
81
+ * The `deliver-to-parent` job (`MaintenanceProcessor`): `Waits.childFinished` writes the delivery
82
+ * once by the child run (idempotency keys under the parent conversation's row lock).
83
+ */
84
+ export async function processDeliverToParent(waits, data) {
85
+ await waits.childFinished(data.childRunId);
86
+ }