@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
@@ -0,0 +1,339 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { sql } from "kysely";
12
+ import { EventLog } from "../conversations/event-log.js";
13
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
14
+ import { KernelDb } from "../infra/database/kernel-db.js";
15
+ import { TransactionHost } from "../infra/database/transaction-host.js";
16
+ import { Transactional } from "../infra/database/transactional.decorator.js";
17
+ import { KernelEvents } from "../infra/events/kernel-events.js";
18
+ import { LockService } from "../infra/lock/lock.service.js";
19
+ import { LockedInterval } from "../infra/schedule/locked-interval.js";
20
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
21
+ import { KyselyCheckpointSaver } from "./checkpointer.js";
22
+ import { ConversationLock } from "./conversation-lock.js";
23
+ import { QueueLostJobs } from "./queue-lost-jobs.js";
24
+ import { notifyFinished, RunFinish } from "./run-finish.js";
25
+ import { RunJobs, runJobId, runQueueNameFor } from "./run-jobs.js";
26
+ import { RunQueue } from "./run-queue.js";
27
+ import { Waits } from "./waits.js";
28
+ /** The check's `@LockedInterval` job name (its run lock key is the default `cron:run-orphan-check`). */
29
+ export const RUN_ORPHAN_CHECK_JOB = "run-orphan-check";
30
+ /** The check's period: a fixed constant, not a config field. */
31
+ export const RUN_ORPHAN_CHECK_PERIOD_MS = 60_000;
32
+ /**
33
+ * How long a run must have been in its state (queued, or running since its last start) before it
34
+ * can count as an orphan: a transition in flight is never repaired.
35
+ */
36
+ export const RUN_ORPHAN_MIN_AGE_MS = 60_000;
37
+ /** The candidates one page of a pass reads (a keyset page over the state's start time and id). */
38
+ export const RUN_ORPHAN_CHECK_BATCH = 500;
39
+ /**
40
+ * The most pages one pass reads: a bound on its work. Healthy long runs and queued runs waiting
41
+ * for a quota slot are passed over page by page, so a real orphan behind them is still reached;
42
+ * beyond this many candidates the rest wait for the next minute.
43
+ */
44
+ export const RUN_ORPHAN_CHECK_MAX_PAGES = 10;
45
+ /** The error the canary logs (transactional outbox spec §8.3). */
46
+ export const QUEUE_LOST_JOBS_MESSAGE = "queued run without a job: the queue lost jobs";
47
+ /** The SQL of the time a run entered its current state (queued, or its last start). */
48
+ const SINCE = sql `GREATEST(COALESCE(r.queued_at, r.created_at), COALESCE(r.started_at, r.created_at))`;
49
+ /**
50
+ * One page of the active runs of chat and sub-agent conversations in `statuses` that entered their
51
+ * state more than `minAgeMs` ago, by (state start, id), after `after` (an eval's hidden
52
+ * conversation drives its runs itself).
53
+ */
54
+ export async function orphanCandidates(db, statuses, minAgeMs, limit, after) {
55
+ let q = db
56
+ .selectFrom("runs as r")
57
+ .innerJoin("conversations as c", "c.id", "r.conversation_id")
58
+ .select([
59
+ "r.id",
60
+ "r.conversation_id",
61
+ "r.status",
62
+ "r.job_id",
63
+ "r.trigger_seq",
64
+ "c.kind",
65
+ "c.org_id",
66
+ sql `${SINCE}::text`.as("since"),
67
+ ])
68
+ .where("r.status", "in", statuses)
69
+ .where("c.kind", "in", ["chat", "subagent"])
70
+ .where(SINCE, "<", sql `now() - ${minAgeMs}::integer * interval '1 millisecond'`);
71
+ if (after !== undefined) {
72
+ q = q.where(sql `(${SINCE}, r.id) > (${after.since}::timestamptz, ${after.runId})`);
73
+ }
74
+ const rows = await q.orderBy(SINCE).orderBy("r.id").limit(limit).execute();
75
+ return rows.map((r) => ({
76
+ runId: r.id,
77
+ conversationId: r.conversation_id,
78
+ orgId: r.org_id,
79
+ kind: r.kind,
80
+ status: r.status,
81
+ jobId: r.job_id ?? runJobId(r.conversation_id, Number(r.trigger_seq)),
82
+ queue: runQueueNameFor(r.kind),
83
+ since: r.since,
84
+ }));
85
+ }
86
+ /**
87
+ * Whether the outbox holds the job `jobId` on `queue`: a pending row (it is still owed) or a dead
88
+ * one (the outbox gave up on it; the super admin sees it under `outbox_dead` and retries or
89
+ * discards it, so it is not a job the queue lost).
90
+ */
91
+ export async function jobInOutbox(db, queue, jobId) {
92
+ const row = await db
93
+ .selectFrom("outbox")
94
+ .select("id")
95
+ .where("queue", "=", queue)
96
+ .where("job_id", "=", jobId)
97
+ .executeTakeFirst();
98
+ return row !== undefined;
99
+ }
100
+ /** Every page of {@link orphanCandidates}, at most {@link RUN_ORPHAN_CHECK_MAX_PAGES}. */
101
+ async function* candidatePages(db, statuses, minAgeMs) {
102
+ let after;
103
+ for (let page = 0; page < RUN_ORPHAN_CHECK_MAX_PAGES; page++) {
104
+ const rows = await orphanCandidates(db, statuses, minAgeMs, RUN_ORPHAN_CHECK_BATCH, after);
105
+ if (rows.length > 0)
106
+ yield rows;
107
+ if (rows.length < RUN_ORPHAN_CHECK_BATCH)
108
+ return;
109
+ after = rows[rows.length - 1];
110
+ }
111
+ }
112
+ /**
113
+ * The `queued` runs (in their state for over `minAgeMs`) whose job is neither in the outbox
114
+ * (owed, or dead) nor in BullMQ (`jobExists`), and that are still queued after both looks: the run
115
+ * queue lost their jobs. The outbox is read before BullMQ (a row is deleted only after its job was added) and the
116
+ * run again after both (a job that finished took its run out of `queued` first), so a run in
117
+ * flight is never reported. The `run-orphan-check` uses it; so does the dual-replica test harness,
118
+ * which asserts at its teardown that the kernel left none (`minAgeMs` 0).
119
+ */
120
+ export async function queuedRunsWithoutJob(db, jobExists, opts) {
121
+ const out = [];
122
+ for await (const page of candidatePages(db, ["queued"], opts.minAgeMs)) {
123
+ for (const c of page) {
124
+ if (await jobInOutbox(db, c.queue, c.jobId))
125
+ continue;
126
+ if (await jobExists(c.queue, c.jobId))
127
+ continue;
128
+ const now = await db
129
+ .selectFrom("runs")
130
+ .select("status")
131
+ .where("id", "=", c.runId)
132
+ .executeTakeFirst();
133
+ if (now?.status === "queued")
134
+ out.push(c);
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+ /**
140
+ * The `run-orphan-check` (transactional outbox spec §6 "Replica death during a run", §8.3): once a
141
+ * minute across replicas (worker role), the one repair of a run nobody runs. A run counts when it
142
+ * has been in its state for over {@link RUN_ORPHAN_MIN_AGE_MS} with neither its BullMQ job nor a
143
+ * conversation lock holder:
144
+ * - `running`: its replica died while running it (BullMQ's stalled re-deliveries ran out, or the
145
+ * process died inside `onFailed`; run jobs are `attempts: 1, removeOnFail`). It is failed
146
+ * `run.orphaned` (its terminal transaction cancels its waits and owes what every run end owes),
147
+ * then, after the commit, its checkpoints are dropped, its fork child stopped (`run.orphaned`), a
148
+ * sub-agent's failure delivered to its parent at once and the conversation's next run started.
149
+ * - `queued`, with no outbox row either (pending or dead): a canary. Every queued run has its job owed in its
150
+ * own transaction, so this means Redis lost data. It is logged as an error, counted for the admin
151
+ * overview ({@link QueueLostJobs}) and its job owed again so the run is not stuck.
152
+ * Redis is read before each repair's transaction, which then re-reads the run under its
153
+ * conversation row lock and acts only if it is still in the same state since the same time. Read
154
+ * paths never repair runs.
155
+ */
156
+ let RunOrphanCheck = class RunOrphanCheck {
157
+ kdb;
158
+ jobs;
159
+ lock;
160
+ finisher;
161
+ eventLog;
162
+ checkpoints;
163
+ waits;
164
+ queue;
165
+ lostJobs;
166
+ events;
167
+ txHost;
168
+ config;
169
+ lockService;
170
+ log;
171
+ constructor(kdb, jobs, lock, finisher, eventLog, checkpoints,
172
+ /** A failed sub-agent run reports to its parent at once ({@link Waits.childFinished}). */
173
+ waits,
174
+ /** The conversation's next run, after a repaired run's end. */
175
+ queue, lostJobs, events, txHost, config, lockService, log) {
176
+ this.kdb = kdb;
177
+ this.jobs = jobs;
178
+ this.lock = lock;
179
+ this.finisher = finisher;
180
+ this.eventLog = eventLog;
181
+ this.checkpoints = checkpoints;
182
+ this.waits = waits;
183
+ this.queue = queue;
184
+ this.lostJobs = lostJobs;
185
+ this.events = events;
186
+ this.txHost = txHost;
187
+ this.config = config;
188
+ this.lockService = lockService;
189
+ this.log = log;
190
+ }
191
+ get db() {
192
+ return this.kdb.db;
193
+ }
194
+ async run() {
195
+ await this.check();
196
+ }
197
+ /** One pass: repairs the orphans it finds (each on its own; a failure is logged). */
198
+ async check() {
199
+ const out = { runningRepaired: 0, queuedReadded: 0 };
200
+ const lost = [];
201
+ const pages = candidatePages(this.db, ["queued", "running"], RUN_ORPHAN_MIN_AGE_MS);
202
+ for await (const page of pages) {
203
+ for (const c of page) {
204
+ try {
205
+ if (!(await this.withoutJobOrHolder(c)))
206
+ continue;
207
+ if (c.status === "running") {
208
+ if (await this.failRunning(c))
209
+ out.runningRepaired++;
210
+ }
211
+ else if (await this.oweAgainIn(c)) {
212
+ out.queuedReadded++;
213
+ lost.push(c);
214
+ }
215
+ }
216
+ catch (err) {
217
+ this.log.error("run-orphan-check: cannot repair a run", { runId: c.runId, err });
218
+ }
219
+ }
220
+ }
221
+ if (lost.length > 0) {
222
+ this.log.error(QUEUE_LOST_JOBS_MESSAGE, {
223
+ count: lost.length,
224
+ runs: lost.map((c) => ({ runId: c.runId, conversationId: c.conversationId })),
225
+ });
226
+ await this.lostJobs.report(lost.length);
227
+ }
228
+ return out;
229
+ }
230
+ /**
231
+ * Neither a conversation lock holder nor the run's job in BullMQ (nor, queued, in the outbox: an
232
+ * owed row is delivered by the relay, a dead one waits for the super admin).
233
+ */
234
+ async withoutJobOrHolder(c) {
235
+ if ((await this.lock.holder(c.conversationId)) !== null)
236
+ return false;
237
+ if (c.status === "queued" && (await jobInOutbox(this.db, c.queue, c.jobId)))
238
+ return false;
239
+ return (await this.jobs.getJob(c.jobId, c.queue)) === undefined;
240
+ }
241
+ /**
242
+ * Fails a `running` orphan (`run.orphaned`), then does what its end causes after the commit:
243
+ * the open pages hear it, its checkpoints go, its fork child stops, a sub-agent reports to its
244
+ * parent and the conversation's next run starts (each also owed by the terminal transaction).
245
+ */
246
+ async failRunning(c) {
247
+ const seq = await this.failIn(c);
248
+ if (seq === null)
249
+ return false;
250
+ this.log.warn("run-orphan-check: failed a running run nobody runs", {
251
+ runId: c.runId,
252
+ conversationId: c.conversationId,
253
+ });
254
+ await notifyFinished(this.eventLog, c.conversationId, seq, "failed");
255
+ await this.checkpoints
256
+ .deleteThread(c.runId)
257
+ .catch((err) => this.log.warn("checkpoint delete failed", { runId: c.runId, err }));
258
+ await this.events.emit("run.orphaned", { runId: c.runId });
259
+ await this.waits.childFinished(c.runId).catch((err) => this.log.error("cannot report an orphaned sub-agent run to its parent", {
260
+ runId: c.runId,
261
+ err,
262
+ }));
263
+ await this.queue.ensureRun(c.conversationId).catch((err) => this.log.error("cannot start the next run after an orphan", {
264
+ conversationId: c.conversationId,
265
+ err,
266
+ }));
267
+ return true;
268
+ }
269
+ /** {@link failRunning}'s transaction: the terminal state, if the run is still the same orphan. */
270
+ async failIn(c) {
271
+ if (!(await this.stillIn(c)))
272
+ return null;
273
+ return this.finisher.finishIn({
274
+ conversationId: c.conversationId,
275
+ runId: c.runId,
276
+ status: "failed",
277
+ consumed: null,
278
+ event: {
279
+ type: "run_failed",
280
+ payload: {
281
+ error: { code: "run.orphaned", message: "The run was lost and will not continue" },
282
+ },
283
+ },
284
+ });
285
+ }
286
+ /** Owes a lost queued run's job again under its own id, if it is still the same orphan. */
287
+ async oweAgainIn(c) {
288
+ if (!(await this.stillIn(c)))
289
+ return false;
290
+ const data = { runId: c.runId, conversationId: c.conversationId };
291
+ return this.jobs.owe(data, c.jobId, c.kind, c.orgId);
292
+ }
293
+ /**
294
+ * Under the conversation row lock (the first lock of every path that changes a run's state): the
295
+ * run is still in the state and since the time the pass read. Not `FOR UPDATE` on the run: the
296
+ * terminal transaction locks it after the run's wait rows.
297
+ */
298
+ async stillIn(c) {
299
+ await this.db
300
+ .selectFrom("conversations")
301
+ .select("id")
302
+ .where("id", "=", c.conversationId)
303
+ .forUpdate()
304
+ .executeTakeFirst();
305
+ const r = await this.db
306
+ .selectFrom("runs as r")
307
+ .select(["r.status", sql `${SINCE}::text`.as("since")])
308
+ .where("r.id", "=", c.runId)
309
+ .executeTakeFirst();
310
+ return r?.status === c.status && r.since === c.since;
311
+ }
312
+ };
313
+ __decorate([
314
+ LockedInterval(RUN_ORPHAN_CHECK_JOB, () => RUN_ORPHAN_CHECK_PERIOD_MS)
315
+ ], RunOrphanCheck.prototype, "run", null);
316
+ __decorate([
317
+ Transactional({ propagation: "requires_new" })
318
+ ], RunOrphanCheck.prototype, "failIn", null);
319
+ __decorate([
320
+ Transactional({ propagation: "requires_new" })
321
+ ], RunOrphanCheck.prototype, "oweAgainIn", null);
322
+ RunOrphanCheck = __decorate([
323
+ Injectable(),
324
+ __param(0, Inject(KernelDb)),
325
+ __param(1, Inject(RunJobs)),
326
+ __param(2, Inject(ConversationLock)),
327
+ __param(3, Inject(RunFinish)),
328
+ __param(4, Inject(EventLog)),
329
+ __param(5, Inject(KyselyCheckpointSaver)),
330
+ __param(6, Inject(Waits)),
331
+ __param(7, Inject(RunQueue)),
332
+ __param(8, Inject(QueueLostJobs)),
333
+ __param(9, Inject(KernelEvents)),
334
+ __param(10, Inject(TransactionHost)),
335
+ __param(11, Inject(KernelConfigService)),
336
+ __param(12, Inject(LockService)),
337
+ __param(13, Inject(KERNEL_LOGGER))
338
+ ], RunOrphanCheck);
339
+ export { RunOrphanCheck };
@@ -3,33 +3,40 @@ import { type DynamicModule } from "@nestjs/common";
3
3
  import type { Job, Worker } from "bullmq";
4
4
  import type { KernelConfig } from "../config/schema.js";
5
5
  import { TitleQueue } from "../conversations/titles.js";
6
- import { type RunJobData } from "./run-jobs.js";
6
+ import { type RUN_JOB, type RunJobData } from "./run-jobs.js";
7
7
  import { RunWorker } from "./run-worker.js";
8
- import { UsageReporter, type UsageReportJob } from "./usage-report.js";
8
+ import { type USAGE_REPORT_JOB, UsageReporter, type UsageReportJob } from "./usage-report.js";
9
9
  type RunResult = Awaited<ReturnType<RunWorker["process"]>>;
10
+ /** A run job: both run queues carry only {@link RUN_JOB} jobs (the outbox registry's `run`). */
11
+ type RunJob = Job<RunJobData, RunResult, typeof RUN_JOB>;
10
12
  /** The consumer of `cap-kernel-runs` (conversation runs): {@link RunWorker}. */
11
13
  export declare class RunsProcessor extends WorkerHost<Worker<RunJobData, RunResult>> {
12
14
  private readonly runs;
13
15
  constructor(runs: RunWorker);
14
- process(job: Job<RunJobData>, token?: string): Promise<RunResult>;
15
- completed(job: Job<RunJobData>, result: RunResult): void;
16
- failed(job: Job<RunJobData> | undefined, err: Error): void;
16
+ process(job: RunJob, token?: string): Promise<RunResult>;
17
+ completed(job: RunJob, result: RunResult): void;
18
+ failed(job: RunJob | undefined, err: Error): void;
17
19
  error(err: Error): void;
18
20
  }
19
21
  /** The consumer of `cap-kernel-subagent-runs` (sub-agent runs, spec §7.4): {@link RunWorker}. */
20
22
  export declare class SubagentRunsProcessor extends WorkerHost<Worker<RunJobData, RunResult>> {
21
23
  private readonly runs;
22
24
  constructor(runs: RunWorker);
23
- process(job: Job<RunJobData>, token?: string): Promise<RunResult>;
24
- completed(job: Job<RunJobData>, result: RunResult): void;
25
- failed(job: Job<RunJobData> | undefined, err: Error): void;
25
+ process(job: RunJob, token?: string): Promise<RunResult>;
26
+ completed(job: RunJob, result: RunResult): void;
27
+ failed(job: RunJob | undefined, err: Error): void;
26
28
  error(err: Error): void;
27
29
  }
30
+ /**
31
+ * A delivery job: the usage report queue carries only {@link USAGE_REPORT_JOB} jobs (the outbox
32
+ * registry's `usage-report`).
33
+ */
34
+ type UsageJob = Job<UsageReportJob, void, typeof USAGE_REPORT_JOB>;
28
35
  /** The consumer of `cap-kernel-usage-report`: {@link UsageReporter}. */
29
36
  export declare class UsageReportProcessor extends WorkerHost<Worker<UsageReportJob>> {
30
37
  private readonly reporter;
31
38
  constructor(reporter: UsageReporter);
32
- process(job: Job<UsageReportJob>, token?: string): Promise<void>;
39
+ process(job: UsageJob, token?: string): Promise<void>;
33
40
  failed(job: Job<UsageReportJob> | undefined, err: Error): void;
34
41
  error(err: Error): void;
35
42
  }
@@ -14,7 +14,7 @@ import { Inject, Injectable, Module } from "@nestjs/common";
14
14
  import { TITLE_QUEUE, TitleQueue, TitlesModule } from "../conversations/titles.js";
15
15
  import { configuredProcessor } from "../infra/queue/configured-processor.js";
16
16
  import { runJob } from "../infra/queue/run-job.js";
17
- import { RUN_QUEUE_NAME, SUBAGENT_RUN_QUEUE_NAME } from "./run-jobs.js";
17
+ import { RUN_QUEUE_NAME, SUBAGENT_RUN_QUEUE_NAME, } from "./run-jobs.js";
18
18
  import { MAX_STALLED_COUNT, RunWorker } from "./run-worker.js";
19
19
  import { RuntimeModule } from "./runtime.module.js";
20
20
  import { USAGE_REPORT_QUEUE, UsageReporter, UsageReportModule, } from "./usage-report.js";
@@ -1,18 +1,21 @@
1
1
  import type { Logger } from "@capybara-hq/plugin-sdk";
2
2
  import type { Queue } from "bullmq";
3
3
  import { EventLog } from "../conversations/event-log.js";
4
- import type { ConversationKind } from "../db/kernel-schema.js";
5
4
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
6
5
  import { KernelDb } from "../infra/database/kernel-db.js";
7
6
  import { TransactionHost } from "../infra/database/transaction-host.js";
8
- import { KernelEvents } from "../infra/events/kernel-events.js";
9
- import { ToolPipeline } from "../tools/pipeline.js";
10
7
  import type { RunAbortDetail } from "./abort.js";
11
8
  import { KyselyCheckpointSaver } from "./checkpointer.js";
12
9
  import { ConversationLock } from "./conversation-lock.js";
13
10
  import { RunFinish } from "./run-finish.js";
14
- import { type RunJobData, RunJobs } from "./run-jobs.js";
15
- export { ACTIVE_RUN_STATUSES, RUN_QUEUE_NAME, type RunJobData, runJobId, runQueueNameFor, SUBAGENT_RUN_QUEUE_NAME, } from "./run-jobs.js";
11
+ import { RunJobs } from "./run-jobs.js";
12
+ export { ACTIVE_RUN_STATUSES, ENSURE_RUN_BACKSTOP_MS, ENSURE_RUN_JOB, type EnsureRunJobData, ensureRunJobId, RUN_QUEUE_NAME, type RunJobData, runJobId, runQueueNameFor, SUBAGENT_RUN_QUEUE_NAME, } from "./run-jobs.js";
13
+ /**
14
+ * Test seam (`KernelOverride`): returns true to make {@link RunQueue.ensureRun} throw before it
15
+ * does anything (Redis unreachable, or a direct call after a commit that never happened). The
16
+ * kernel never provides it.
17
+ */
18
+ export declare const ENSURE_RUN_FAILS: unique symbol;
16
19
  /**
17
20
  * 运行队列(生产方):`ensureRun` 在会话有未消费的用户消息、且没有进行中的运行时插入 `runs` 行并投递任务。
18
21
  * 「是否已有运行」以 PG 为准(会话行锁内检查 `runs` 中的 queued / running),并参考会话锁的持有者;
@@ -24,32 +27,25 @@ export declare class RunQueue {
24
27
  private readonly lock;
25
28
  private readonly jobs;
26
29
  private readonly finisher;
27
- /** 运行以孤儿结束(标记失败 / 直接中止)后删除它的 checkpoint thread(thread_id = runId)。 */
30
+ /** An orphan aborted directly loses its checkpoint thread (thread_id = runId). */
28
31
  private readonly checkpoints;
29
- /** The run's open `Agent` call results, in the orphan repair's transaction (sub-agents spec §12). */
30
- private readonly pipeline;
31
- private readonly events;
32
32
  private readonly log;
33
33
  readonly txHost: TransactionHost;
34
34
  private readonly config;
35
35
  /** The producer side of the delayed `bg-wake` job (`MaintenanceProcessor` consumes it). */
36
36
  private readonly maintenance;
37
+ private readonly ensureFails;
37
38
  constructor(kdb: KernelDb, eventLog: EventLog, lock: Pick<ConversationLock, "holder">, jobs: RunJobs, finisher: Pick<RunFinish, "finishIn">,
38
- /** 运行以孤儿结束(标记失败 / 直接中止)后删除它的 checkpoint thread(thread_id = runId)。 */
39
- checkpoints: Pick<KyselyCheckpointSaver, "deleteThread">,
40
- /** The run's open `Agent` call results, in the orphan repair's transaction (sub-agents spec §12). */
41
- pipeline: Pick<ToolPipeline, "stopOpenAgentCalls">, events: KernelEvents, log: Pick<Logger, "error" | "warn">, txHost: TransactionHost, config: KernelConfigService,
39
+ /** An orphan aborted directly loses its checkpoint thread (thread_id = runId). */
40
+ checkpoints: Pick<KyselyCheckpointSaver, "deleteThread">, log: Pick<Logger, "error" | "warn">, txHost: TransactionHost, config: KernelConfigService,
42
41
  /** The producer side of the delayed `bg-wake` job (`MaintenanceProcessor` consumes it). */
43
- maintenance: Pick<Queue, "add">);
42
+ maintenance: Pick<Queue, "add">, ensureFails?: () => boolean);
44
43
  private get db();
45
- /**
46
- * The `beforeTerminal` of an orphan repair: writes the run's open `Agent` call results in the
47
- * repair's transaction; `fire()` runs their observe hooks after the commit (review m1).
48
- */
49
- private stopOpenCalls;
50
44
  /**
51
45
  * 有未消费的用户消息(最新 user_message 的 seq > `consumed_seq`)、会话锁无人持有、
52
- * 且没有 queued / running / waiting 的运行时:插入 `runs`(queued)并投递任务,返回 runId;否则返回 null。
46
+ * 且没有 queued / running / waiting 的运行时:插入 `runs`(queued),返回 runId;否则返回 null。
47
+ * The run's job is owed in the same transaction (transactional outbox spec §6): the outbox
48
+ * delivers it after the commit, so a queued run never lacks its job.
53
49
  *
54
50
  * The boundary (`trigger_seq`) is that newest message; with none unconsumed, the queue's head
55
51
  * once it is sent; with no head either, a wake-eligible finished background task or sub-agent
@@ -58,13 +54,11 @@ export declare class RunQueue {
58
54
  * event inside the wake window schedules the delayed `bg-wake` job instead. A sub-agent's
59
55
  * conversation is only woken this way ({@link childWakeIn}).
60
56
  *
61
- * 孤儿清理(R29,按需进行,没有定时器):已有的 queued / running 运行若 BullMQ 任务已不存在、
62
- * 会话锁也无人持有(queued 的还要超过宽限期),说明没有任何 worker 会再执行它——写
63
- * `run_failed { code: "run.orphaned" }` 把它标记为失败(consumed_seq 推进到它的触发消息),
64
- * 再按常规判断是否起新一轮,并删除它的 checkpoint thread。会话因此不会永远卡在「有运行」。
65
- * 除了写入消息后,打开 SSE、读事件、运行结束后也会调用它(幂等),孤儿因此在有人查看时就被清理。
57
+ * An active run (queued, running or waiting) is left alone: a run nobody runs any more is the
58
+ * `run-orphan-check`'s to repair (transactional outbox spec §6), never this call's, and reads
59
+ * never call it.
66
60
  *
67
- * agent 已软删除的会话不起新运行(孤儿清理照常进行)。
61
+ * A conversation whose agent is soft-deleted starts no run.
68
62
  */
69
63
  ensureRun(conversationId: string,
70
64
  /** From the `bg-wake` job: it fired `wakeRetry - 1` times before its window ended. */
@@ -73,6 +67,20 @@ export declare class RunQueue {
73
67
  }): Promise<string | null>;
74
68
  /** {@link ensureRun}'s transaction: what it decided goes back through `st`. */
75
69
  private ensureRunIn;
70
+ /**
71
+ * The job id of a new run for `seq` (in {@link ensureRunIn}'s transaction, read from PG only):
72
+ * the trigger's `runJobId` for its first run; a later run of the same trigger (a run that hit its
73
+ * deadline leaves the messages it did not absorb unconsumed, R34) gets `<runJobId>-<n>`,
74
+ * recorded in `runs.job_id`, so its job is never taken for the earlier run's (BullMQ ignores an
75
+ * add under the id of a job it still holds).
76
+ */
77
+ private jobIdFor;
78
+ /**
79
+ * Owes the new run's job in {@link ensureRunIn}'s transaction, before its run row is written.
80
+ * False (logged): the same job id is still owed, so the caller writes no run (a second run of
81
+ * one trigger; the conversation's unique active run makes this a should-not-happen).
82
+ */
83
+ private oweJob;
76
84
  /**
77
85
  * {@link ensureRunIn} for a sub-agent's conversation (agent map spec §5.3), under its row lock.
78
86
  * Its runs from a task, or from a message to an idle sub-agent, are started by the dispatch
@@ -84,8 +92,7 @@ export declare class RunQueue {
84
92
  * started again only by its parent. Both read the queue and the trigger account's access on the root
85
93
  * of its tree; a message needs no wake setting and has no wake window. The run counts against
86
94
  * the root's budget window and runs on the sub-agent queue under `runJobId(child, boundary
87
- * seq)`. An active run is left to the orphan repair after the commit (`repairRunId`), never
88
- * started past.
95
+ * seq)`. An active run is never started past (one nobody runs is the `run-orphan-check`'s).
89
96
  */
90
97
  private childWakeIn;
91
98
  /**
@@ -101,10 +108,24 @@ export declare class RunQueue {
101
108
  * Re-derives the wakes a paused root held for its tree (agent map spec §5.3: a paused root queue
102
109
  * holds the wakes of the whole tree): after the root's queue is unpaused, {@link ensureRun} on
103
110
  * every sub-agent conversation below it that has an unconsumed wake event or message and no
104
- * active run (the root itself is the caller's own `ensureRun`). Errors are logged; the next
105
- * event, run end or page opening of that conversation re-derives its wake.
111
+ * active run (the root itself is the caller's own `ensureRun`). Errors are logged; the
112
+ * ensure-run backstop the releasing transaction owed ({@link oweHeldTreeIn}) wakes it later.
106
113
  */
107
114
  wakeHeldTree(rootId: string): Promise<void>;
115
+ /**
116
+ * Owes, in the caller's transaction (the one that unpauses the root's queue or sends its item
117
+ * now), the ensure-run backstop of every sub-agent {@link wakeHeldTree} would wake after the
118
+ * commit, under the sub-agent's newest seq (transactional outbox spec §6): a process that dies
119
+ * before that call leaves no held wake behind. A root whose queue is still paused (or that is
120
+ * deleted) releases nothing and owes nothing.
121
+ */
122
+ oweHeldTreeIn(rootId: string): Promise<void>;
123
+ /**
124
+ * The sub-agent conversations below `rootId` a paused root holds: not deleted, with an
125
+ * unconsumed wake event (a finished background task, a delivery) or message, and no active run;
126
+ * with their org and newest seq. Reads only (no row locks).
127
+ */
128
+ private heldTree;
108
129
  /**
109
130
  * Schedules the delayed `bg-wake` job at the end of the conversation's wake window (after the
110
131
  * commit): one job per conversation and window end, so finishes inside one window share it. It
@@ -113,41 +134,29 @@ export declare class RunQueue {
113
134
  */
114
135
  private scheduleWake;
115
136
  /**
116
- * Enqueues a run job under the caller's job id (resumes, dispatches, repairs), with the same
117
- * options as {@link ensureRun} (see {@link RunJobs.enqueue}).
137
+ * Owes the ensure-run backstop of `conversationId` in the caller's transaction
138
+ * ({@link oweEnsureRun}): `seq` is the event the transaction wrote, `orgId` the conversation's.
118
139
  */
119
- enqueue(data: RunJobData, jobId: string, kind?: ConversationKind): Promise<void>;
140
+ oweEnsureRun(conversationId: string, seq: number | string, orgId: string): Promise<boolean>;
120
141
  /**
121
- * 中止一个孤儿运行(`POST .../abort` 用):运行仍是 queued / running、会话锁无人持有、BullMQ 任务已不存在
122
- * (queued 的还要超过宽限期,判定同 {@link ensureRun} 的孤儿清理)时,没有任何 worker 会收到中止广播——
123
- * 直接写 `run_aborted`、status 置为 aborted、consumed_seq 至少推进到触发消息,返回 true。
124
- * 不是孤儿(有人在执行或即将执行)或运行已结束 → 返回 false,由调用方走广播中止。
142
+ * Stops an orphaned run directly (`POST .../abort`, a tree stop): a `running` run whose
143
+ * conversation lock is free and whose BullMQ job is gone hears no stop broadcast, so its
144
+ * `run_aborted` is written here (status aborted, `consumed_seq` at least to its trigger): true. Not
145
+ * an orphan (someone runs it; a queued run's job is owed, or lost and re-added by the
146
+ * `run-orphan-check`, whose worker then sees the stop marker) or already finished: false, and the
147
+ * caller broadcasts the stop.
125
148
  */
126
149
  abortIfOrphaned(conversationId: string, runId: string, byAccountId: string | null, detail?: RunAbortDetail): Promise<boolean>;
127
150
  private abortOrphanIn;
128
- /**
129
- * Repairs an orphaned run of a conversation nobody else repairs on demand (a sub-agent's: no
130
- * `ensureRun` ever runs for it; sub-agents spec §7.5), with the same orphan test as
131
- * {@link ensureRun}: the conversation lock is free, the run is still `queued` / `running`, its
132
- * job is gone, and a `queued` one is past the grace period.
133
- * - `queued` (the dispatch or resume committed, its enqueue was lost) → enqueued again under its
134
- * own job id (`runs.job_id`, else the trigger's), so concurrent repairs on several replicas
135
- * still leave one job: `"requeued"`;
136
- * - `running` (its worker died without a terminal state and BullMQ dropped the job) → marked
137
- * failed `run.orphaned`; the caller reports it to the parent: `"failed"`.
138
- * Not an orphan, or already finished → null.
139
- */
140
- repairOrphan(conversationId: string, runId: string): Promise<"requeued" | "failed" | null>;
141
- private repairOrphanIn;
142
151
  /** 会话当前未结束的运行(调用方已持有会话行锁)。 */
143
152
  private activeRun;
144
153
  /**
145
- * 未结束的运行是否已是孤儿(调用方已确认会话锁无人持有):waiting → 否(等人处理,本来就没有任务,
146
- * 由人工决定或超时任务续跑);queued 且仍在宽限期内 → 否(任务可能还没投递);BullMQ 任务(续跑任务按
147
- * `runs.job_id`)仍存在 → 否;否则是。
154
+ * Whether an active run is an orphan for {@link abortIfOrphaned} (the caller saw the conversation
155
+ * lock free): a `running` one whose BullMQ job (a resume's under `runs.job_id`) is gone. A
156
+ * `waiting` one never is (no job by design), nor a `queued` one (its job is owed in the
157
+ * transaction that queued it).
148
158
  */
149
159
  private isOrphan;
150
160
  /** 删除已结束运行的 checkpoint thread(失败只记日志)。 */
151
161
  private dropCheckpoints;
152
- private deleteQueued;
153
162
  }