@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
@@ -17,28 +17,43 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
17
17
  import { Transactional } from "../infra/database/transactional.decorator.js";
18
18
  import { recordToolSpecs } from "../tools/specs.js";
19
19
  import { budgetRunOf } from "./subagents/limits.js";
20
+ import { UsageReportJobs, UsageReportJobsModule } from "./usage-report-jobs.js";
20
21
  /**
21
22
  * The writes of a run step that go to the event log together with another row, each in a
22
23
  * transaction of its own (the caller notifies the log after the commit): a model call with the
23
- * tool definitions it sent, a denial with its audit row, and a sub-agent's usage counted on its
24
- * parent run. The step functions (`model-call.ts`, `hook-calls.ts`) reach it through their deps.
24
+ * tool definitions it sent, a denial with its audit row, usage events with their owed
25
+ * `usage.report` deliveries (transactional outbox spec §8.1), and a sub-agent's usage counted on
26
+ * its parent run. The step functions (`model-call.ts`, `hook-calls.ts`) reach it through their deps.
25
27
  */
26
28
  let StepLog = class StepLog {
27
29
  kdb;
28
30
  eventLog;
31
+ usage;
29
32
  txHost;
30
- constructor(kdb, eventLog, txHost) {
33
+ constructor(kdb, eventLog, usage, txHost) {
31
34
  this.kdb = kdb;
32
35
  this.eventLog = eventLog;
36
+ this.usage = usage;
33
37
  this.txHost = txHost;
34
38
  }
39
+ /**
40
+ * Appends events and owes the `usage.report` delivery of the `usage` events it newly wrote, in
41
+ * one transaction. Returns what was written (hits included) and the highest new seq (null:
42
+ * nothing new).
43
+ */
44
+ async appendReported(conversationId, events) {
45
+ const { events: written, fresh } = await this.eventLog.appendInReport(conversationId, events);
46
+ const added = written.filter((_, i) => fresh[i]);
47
+ await this.usage.oweEvents(conversationId, added);
48
+ return { written, toSeq: added.length > 0 ? Math.max(...added.map((e) => e.seq)) : null };
49
+ }
35
50
  /**
36
51
  * Appends events of a sub-agent's run and adds the tokens of the usage events it newly wrote to
37
52
  * the `subagent_tokens_used` of the tree's budget run (sub-agents spec §8.3, agent map spec §6:
38
53
  * the whole tree counts on one run): `counter.budgetRunId`, the run's own window
39
54
  * (`SubagentParent.budgetRunId`), else the parent run's ({@link budgetRunOf}). A usage event
40
55
  * already in the log (an idempotent hit after a crash) adds nothing, so each call counts exactly
41
- * once. Returns what was written (hits included) and the highest new seq (null: nothing new).
56
+ * once; the new usage events' `usage.report` deliveries are owed in the same transaction. Returns what was written (hits included) and the highest new seq (null: nothing new).
42
57
  */
43
58
  async appendCounted(conversationId, counter, events) {
44
59
  const t = this.kdb.db;
@@ -51,6 +66,7 @@ let StepLog = class StepLog {
51
66
  .executeTakeFirstOrThrow();
52
67
  const written = await this.eventLog.appendIn(conversationId, events);
53
68
  const fresh = written.filter((e) => e.seq > Number(before.last_seq));
69
+ await this.usage.oweEvents(conversationId, fresh);
54
70
  let tokens = 0;
55
71
  for (const e of fresh)
56
72
  if (e.type === "usage")
@@ -79,6 +95,9 @@ let StepLog = class StepLog {
79
95
  return this.eventLog.appendIn(conversationId, events);
80
96
  }
81
97
  };
98
+ __decorate([
99
+ Transactional({ propagation: "requires_new" })
100
+ ], StepLog.prototype, "appendReported", null);
82
101
  __decorate([
83
102
  Transactional({ propagation: "requires_new" })
84
103
  ], StepLog.prototype, "appendCounted", null);
@@ -92,7 +111,8 @@ StepLog = __decorate([
92
111
  Injectable(),
93
112
  __param(0, Inject(KernelDb)),
94
113
  __param(1, Inject(EventLog)),
95
- __param(2, Inject(TransactionHost))
114
+ __param(2, Inject(UsageReportJobs)),
115
+ __param(3, Inject(TransactionHost))
96
116
  ], StepLog);
97
117
  export { StepLog };
98
118
  /**
@@ -103,7 +123,7 @@ let StepLogModule = class StepLogModule {
103
123
  };
104
124
  StepLogModule = __decorate([
105
125
  Module({
106
- imports: [EventLogModule],
126
+ imports: [EventLogModule, UsageReportJobsModule],
107
127
  providers: [StepLog],
108
128
  exports: [StepLog],
109
129
  })
@@ -2,7 +2,6 @@ import type { Logger } from "@capybara-hq/plugin-sdk";
2
2
  import type { FastifyReply, FastifyRequest } from "fastify";
3
3
  import { z } from "zod";
4
4
  import { ConversationAccess } from "../conversations/access.js";
5
- import { ConversationsService } from "../conversations/conversations.service.js";
6
5
  import { ApiTokenService } from "../identity/api-tokens.js";
7
6
  import { type Principal } from "../identity/principal.js";
8
7
  import { SessionService } from "../identity/sessions.js";
@@ -18,7 +17,7 @@ declare const DraftParams: z.ZodObject<{
18
17
  }, z.core.$strip>;
19
18
  /**
20
19
  * 会话事件流(SSE):`GET /api/v1/orgs/:orgId/conversations/:id/stream?after=<seq>`,`agents.use`,只有会话所有者可用。
21
- * - 打开时不阻塞地调用一次 ensureRun(`ConversationsService.kickRun`),停在「运行中」的孤儿运行由此被清理;
20
+ * - opening it never changes run state (a read; transactional outbox spec §6);
22
21
  * - 先订阅会话频道、再回放 `after`(或优先的 `Last-Event-ID`)之后的事件:`id: <seq>` / `event: event`;
23
22
  * - after that catch-up, one `event: draft` (`data: {"runId","step","text","thinking"}`) per step of
24
23
  * the running run whose `assistant_message` is not logged (live drafts, spec 2026-11-07 §3.2);
@@ -33,11 +32,10 @@ export declare class ConversationStreamController {
33
32
  private readonly streams;
34
33
  private readonly sessions;
35
34
  private readonly tokens;
36
- private readonly conversations;
37
35
  private readonly access;
38
36
  private readonly drafts;
39
37
  private readonly log;
40
- constructor(streams: ConversationStreams, sessions: SessionService, tokens: ApiTokenService, conversations: ConversationsService, access: ConversationAccess, drafts: RunDrafts, log: Pick<Logger, "warn">);
38
+ constructor(streams: ConversationStreams, sessions: SessionService, tokens: ApiTokenService, access: ConversationAccess, drafts: RunDrafts, log: Pick<Logger, "warn">);
41
39
  /**
42
40
  * The live drafts of one run (spec 2026-11-07 §3.2): `{ drafts: { step, text, thinking }[] }`,
43
41
  * the steps still streaming whose `assistant_message` is not logged (none once the run is no
@@ -10,7 +10,6 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { Controller, Get, Headers, Inject, Param, Req, Res } from "@nestjs/common";
11
11
  import { z } from "zod";
12
12
  import { ConversationAccess } from "../conversations/access.js";
13
- import { ConversationsService } from "../conversations/conversations.service.js";
14
13
  import { ApiError } from "../errors.js";
15
14
  import { ApiTokenService } from "../identity/api-tokens.js";
16
15
  import { credentialCheck } from "../identity/credential-check.js";
@@ -45,7 +44,7 @@ class RunDraftParamsDto extends createZodDto(DraftParams, "RunDraftParams") {
45
44
  }
46
45
  /**
47
46
  * 会话事件流(SSE):`GET /api/v1/orgs/:orgId/conversations/:id/stream?after=<seq>`,`agents.use`,只有会话所有者可用。
48
- * - 打开时不阻塞地调用一次 ensureRun(`ConversationsService.kickRun`),停在「运行中」的孤儿运行由此被清理;
47
+ * - opening it never changes run state (a read; transactional outbox spec §6);
49
48
  * - 先订阅会话频道、再回放 `after`(或优先的 `Last-Event-ID`)之后的事件:`id: <seq>` / `event: event`;
50
49
  * - after that catch-up, one `event: draft` (`data: {"runId","step","text","thinking"}`) per step of
51
50
  * the running run whose `assistant_message` is not logged (live drafts, spec 2026-11-07 §3.2);
@@ -60,15 +59,13 @@ let ConversationStreamController = class ConversationStreamController {
60
59
  streams;
61
60
  sessions;
62
61
  tokens;
63
- conversations;
64
62
  access;
65
63
  drafts;
66
64
  log;
67
- constructor(streams, sessions, tokens, conversations, access, drafts, log) {
65
+ constructor(streams, sessions, tokens, access, drafts, log) {
68
66
  this.streams = streams;
69
67
  this.sessions = sessions;
70
68
  this.tokens = tokens;
71
- this.conversations = conversations;
72
69
  this.access = access;
73
70
  this.drafts = drafts;
74
71
  this.log = log;
@@ -131,8 +128,6 @@ let ConversationStreamController = class ConversationStreamController {
131
128
  },
132
129
  });
133
130
  reply.hijack();
134
- // 可见性已通过:不阻塞地 ensureRun(清理孤儿运行、投递等待中的消息),见 ConversationsService.kickRun。
135
- this.conversations.kickRun(id);
136
131
  res.on("close", () => handle.close());
137
132
  // 订阅期间客户端已断开:close 事件可能已经错过
138
133
  if (res.destroyed || !res.socket || res.socket.destroyed) {
@@ -173,9 +168,8 @@ ConversationStreamController = __decorate([
173
168
  __param(0, Inject(ConversationStreams)),
174
169
  __param(1, Inject(SessionService)),
175
170
  __param(2, Inject(ApiTokenService)),
176
- __param(3, Inject(ConversationsService)),
177
- __param(4, Inject(ConversationAccess)),
178
- __param(5, Inject(RunDrafts)),
179
- __param(6, Inject(KERNEL_LOGGER))
171
+ __param(3, Inject(ConversationAccess)),
172
+ __param(4, Inject(RunDrafts)),
173
+ __param(5, Inject(KERNEL_LOGGER))
180
174
  ], ConversationStreamController);
181
175
  export { ConversationStreamController };
@@ -179,7 +179,7 @@ export async function childResult(eventLog, row, opts = {}) {
179
179
  else {
180
180
  status = "aborted";
181
181
  // §4.4: a person's stop names its account; a stop by nobody (null) is the kernel stopping the
182
- // child on its parent's behalf (stopChildren with no account: the parent's end, the sweep)
182
+ // child on its parent's behalf (stopChildren with no account: the parent's end, its `stop-tree` job)
183
183
  abortedBy =
184
184
  terminal.payload.reason === "user" && terminal.payload.byAccountId === null
185
185
  ? "parent"
@@ -20,6 +20,7 @@ export declare class SubagentTypeGoneError extends Error {
20
20
  export interface EffectiveDefinition {
21
21
  definition: AgentDefinition;
22
22
  template: EventPayload<"subagent_started">["template"];
23
+ /** The child's step limit; 0 = no limit. */
23
24
  maxSteps: number;
24
25
  /**
25
26
  * The child's preamble, frozen into `subagent_started.preamble` (model-visible):
@@ -72,7 +73,7 @@ export interface ChildDepth {
72
73
  * - `general` / `inline` / `plugin`: the parent's definition narrowed by the template (tools by
73
74
  * resolved key, `disallowedTools`, {@link CHILD_REMOVED_TOOLS}; policies stricter-wins; skills
74
75
  * by its `disallowedSkills`), the template's instructions, its `skills` the child has named in
75
- * the preamble to load first (general gets no line), `maxSteps = min(template ?? runtime.maxSteps, runtime.maxSteps)`, and the model tier
76
+ * the preamble to load first (general gets no line), `maxSteps` = the tighter of the template's and `runtime.maxSteps` (0 = no limit), and the model tier
76
77
  * `tier` (the call's) → the template's → `inherit`; an unmapped tier runs on the parent's model
77
78
  * (`tierFallback`); a template `effort` is set when the resolved model declares it;
78
79
  * - `agent`: the org agent's version that passed the gates (`orgAgent`, from {@link checkOrgAgent}),
@@ -6,6 +6,7 @@ import { ApiError } from "../../errors.js";
6
6
  import { invocationOf, resolveSkills } from "../../skills/catalog.js";
7
7
  import { agentSkills, usableSkills } from "../../skills/usable.js";
8
8
  import { AGENT_TOOL, CHILD_REMOVED_TOOLS } from "../../tools/builtin-names.js";
9
+ import { tightestLimit } from "../limits.js";
9
10
  import { SUBAGENT_PREAMBLE } from "./preamble.js";
10
11
  export { CHILD_REMOVED_TOOLS } from "../../tools/builtin-names.js";
11
12
  /**
@@ -222,7 +223,7 @@ function orgAgentDefinition(d, a) {
222
223
  * - `general` / `inline` / `plugin`: the parent's definition narrowed by the template (tools by
223
224
  * resolved key, `disallowedTools`, {@link CHILD_REMOVED_TOOLS}; policies stricter-wins; skills
224
225
  * by its `disallowedSkills`), the template's instructions, its `skills` the child has named in
225
- * the preamble to load first (general gets no line), `maxSteps = min(template ?? runtime.maxSteps, runtime.maxSteps)`, and the model tier
226
+ * the preamble to load first (general gets no line), `maxSteps` = the tighter of the template's and `runtime.maxSteps` (0 = no limit), and the model tier
226
227
  * `tier` (the call's) → the template's → `inherit`; an unmapped tier runs on the parent's model
227
228
  * (`tierFallback`); a template `effort` is set when the resolved model declares it;
228
229
  * - `agent`: the org agent's version that passed the gates (`orgAgent`, from {@link checkOrgAgent}),
@@ -293,7 +294,8 @@ export async function effectiveDefinition(d, a) {
293
294
  return {
294
295
  definition,
295
296
  template: ref,
296
- maxSteps: Math.min(template?.maxSteps ?? runtimeMax, runtimeMax),
297
+ // the tighter of the template's and the deployment's, 0 = no limit (`runtime.maxSteps` 0)
298
+ maxSteps: tightestLimit(template?.maxSteps, runtimeMax) ?? 0,
297
299
  preamble: names.length === 0
298
300
  ? SUBAGENT_PREAMBLE
299
301
  : `${SUBAGENT_PREAMBLE}\n\n${loadSkillsFirstText(names)}`,
@@ -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:
@@ -121,6 +121,7 @@ export async function dispatchSubagent(d, a) {
121
121
  parent_conversation_id: ctx.conversationId,
122
122
  subagent_type: entry.type,
123
123
  subagent_depth: childDepth,
124
+ source: "subagent",
124
125
  })
125
126
  .execute();
126
127
  const [, task] = await d.eventLog.appendIn(childId, [
@@ -226,7 +227,7 @@ const isActiveStatus = (status) => ACTIVE_RUN_STATUSES.includes(status);
226
227
  * delivered (agent map spec, rc upgrade: a foreground-era dispatch whose rc finisher wrote
227
228
  * `subagent_finished` and resumed the parent): the child's report (`childResult`), with the
228
229
  * 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
230
+ * key), so the `deliver-to-parent` job never delivers it a second time. Null when the parent already
230
231
  * has a delivery for the run (it carries the result) or the run has no terminal event yet.
231
232
  */
232
233
  async function rcFinishedResult(d, row) {
@@ -272,7 +273,7 @@ export async function commitDispatchIn(d, a, plan, child) {
272
273
  const existing = await t
273
274
  .selectFrom("subagent_dispatches as sd")
274
275
  .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"])
276
+ .select(["sd.child_run_id", "sd.child_conversation_id", "cr.status"])
276
277
  .where("sd.parent_run_id", "=", ctx.runId)
277
278
  .where("sd.parent_call_id", "=", callId)
278
279
  .executeTakeFirst();
@@ -284,10 +285,10 @@ export async function commitDispatchIn(d, a, plan, child) {
284
285
  let result = null;
285
286
  if (a.forSkill === undefined) {
286
287
  // 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.
288
+ // `subagent_finished` and resumed this run (no delivery; a run with `subagent_finished` is
289
+ // never delivered): its report is this call's result, as the rc gave it, or it is lost.
289
290
  // 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.
291
+ // holder) keeps it, and its child's finish stays to its delivery (or its `deliver-to-parent` job).
291
292
  const resultKey = resultEvent(ctx, callId, AGENT_TOOL, "").idemKey;
292
293
  const finished = a.sent === undefined &&
293
294
  existing.status !== null &&
@@ -312,13 +313,6 @@ export async function commitDispatchIn(d, a, plan, child) {
312
313
  childRunId: existing.child_run_id,
313
314
  childConversationId: existing.child_conversation_id,
314
315
  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
316
  };
323
317
  }
324
318
  if (parent?.status !== "running")
@@ -384,6 +378,9 @@ export async function commitDispatchIn(d, a, plan, child) {
384
378
  for_skill: a.forSkill !== undefined,
385
379
  })
386
380
  .execute();
381
+ // the child run's job is owed with its row (transactional outbox spec §6): a queued child
382
+ // always has its job, whatever happens after the commit
383
+ await d.owe({ runId: childRunId, conversationId: plan.childConversationId }, runJobId(plan.childConversationId, task.seq), "subagent", ctx.orgId);
387
384
  // an org agent runs on its own model (§5.4): a `model` on the call was ignored
388
385
  const ownModel = plan.source === "agent" && input.model !== undefined;
389
386
  const content = a.sent?.content ?? agentStartedText(plan.childConversationId, { ownModel });
@@ -475,19 +472,7 @@ child) {
475
472
  if (out === null)
476
473
  return null;
477
474
  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
- }
475
+ // the child's job was owed with the first dispatch (a still queued child has it)
491
476
  if (out.result !== null)
492
477
  await d.eventLog.notify(ctx.conversationId, out.result.seq);
493
478
  return {
@@ -507,18 +492,9 @@ child) {
507
492
  return out.outcome;
508
493
  await d.eventLog.notify(plan.childConversationId, out.childSeq);
509
494
  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
495
  return {
520
496
  kind: "dispatched",
521
- childRunId,
497
+ childRunId: out.childRunId,
522
498
  childConversationId: plan.childConversationId,
523
499
  content: out.content,
524
500
  };
@@ -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