@capybara-hq/kernel 0.1.0-rc.3 → 0.1.0-rc.4

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 (226) hide show
  1. package/dist/admin/admin.controller.d.ts +9 -4
  2. package/dist/admin/admin.controller.js +13 -4
  3. package/dist/admin/extensions.controller.d.ts +8 -1
  4. package/dist/admin/extensions.controller.js +22 -2
  5. package/dist/agents/agent-lookup.d.ts +3 -0
  6. package/dist/agents/agent-lookup.js +10 -0
  7. package/dist/agents/agent-profile.service.d.ts +29 -0
  8. package/dist/agents/agent-profile.service.js +117 -0
  9. package/dist/agents/agents.controller.d.ts +93 -3
  10. package/dist/agents/agents.controller.js +114 -4
  11. package/dist/agents/agents.module.js +6 -2
  12. package/dist/agents/agents.service.d.ts +13 -5
  13. package/dist/agents/agents.service.js +30 -7
  14. package/dist/agents/definition.d.ts +4 -0
  15. package/dist/agents/definition.js +10 -0
  16. package/dist/agents/instruction-files.d.ts +60 -0
  17. package/dist/agents/instruction-files.js +111 -0
  18. package/dist/agents/models.controller.d.ts +9 -3
  19. package/dist/agents/models.controller.js +19 -4
  20. package/dist/agents/profile.d.ts +96 -0
  21. package/dist/agents/profile.js +51 -0
  22. package/dist/agents/readiness.controller.d.ts +26 -0
  23. package/dist/agents/readiness.controller.js +56 -0
  24. package/dist/agents/templates.js +1 -1
  25. package/dist/background/background.controller.d.ts +38 -0
  26. package/dist/background/background.controller.js +87 -0
  27. package/dist/background/background.module.d.ts +10 -0
  28. package/dist/background/background.module.js +32 -0
  29. package/dist/background/finish.d.ts +43 -0
  30. package/dist/background/finish.js +94 -0
  31. package/dist/background/launch-lock.d.ts +14 -0
  32. package/dist/background/launch-lock.js +39 -0
  33. package/dist/background/launch.d.ts +36 -0
  34. package/dist/background/launch.js +70 -0
  35. package/dist/background/poll.job.d.ts +53 -0
  36. package/dist/background/poll.job.js +182 -0
  37. package/dist/background/render.d.ts +23 -0
  38. package/dist/background/render.js +48 -0
  39. package/dist/background/session.d.ts +37 -0
  40. package/dist/background/session.js +66 -0
  41. package/dist/background/status.d.ts +35 -0
  42. package/dist/background/status.js +57 -0
  43. package/dist/background/stop.d.ts +62 -0
  44. package/dist/background/stop.js +225 -0
  45. package/dist/background/store.d.ts +150 -0
  46. package/dist/background/store.js +351 -0
  47. package/dist/background/store.module.d.ts +6 -0
  48. package/dist/background/store.module.js +22 -0
  49. package/dist/background/tasks.service.d.ts +67 -0
  50. package/dist/background/tasks.service.js +186 -0
  51. package/dist/background/wake-rules.d.ts +57 -0
  52. package/dist/background/wake-rules.js +83 -0
  53. package/dist/background/wake.d.ts +28 -0
  54. package/dist/background/wake.js +57 -0
  55. package/dist/catalog/claude/agents.js +2 -0
  56. package/dist/catalog/marketplaces.service.d.ts +1 -1
  57. package/dist/catalog/plugin-import.js +1 -1
  58. package/dist/catalog/plugin-upgrade.js +1 -1
  59. package/dist/channels/cards.js +1 -1
  60. package/dist/channels/connections.service.d.ts +16 -1
  61. package/dist/channels/connections.service.js +38 -4
  62. package/dist/channels/threads.d.ts +5 -0
  63. package/dist/channels/threads.js +8 -0
  64. package/dist/config/schema.d.ts +18 -0
  65. package/dist/config/schema.js +102 -0
  66. package/dist/conversations/access.js +2 -0
  67. package/dist/conversations/attachments.service.d.ts +30 -0
  68. package/dist/conversations/attachments.service.js +130 -5
  69. package/dist/conversations/conversations.controller.d.ts +36 -5
  70. package/dist/conversations/conversations.controller.js +62 -12
  71. package/dist/conversations/conversations.module.js +3 -0
  72. package/dist/conversations/conversations.service.d.ts +44 -2
  73. package/dist/conversations/conversations.service.js +140 -12
  74. package/dist/conversations/event-log.d.ts +1 -1
  75. package/dist/conversations/event-log.js +10 -1
  76. package/dist/conversations/events.d.ts +58 -0
  77. package/dist/conversations/events.js +57 -2
  78. package/dist/conversations/queue.d.ts +3 -1
  79. package/dist/conversations/queue.js +13 -2
  80. package/dist/conversations/queue.service.js +3 -1
  81. package/dist/conversations/titles.d.ts +7 -0
  82. package/dist/conversations/titles.js +15 -0
  83. package/dist/db/kernel-schema.d.ts +58 -2
  84. package/dist/db/pool.d.ts +5 -0
  85. package/dist/db/pool.js +3 -0
  86. package/dist/errors.d.ts +8 -0
  87. package/dist/errors.js +8 -0
  88. package/dist/evals/run-replay.d.ts +5 -2
  89. package/dist/evals/run-replay.js +30 -7
  90. package/dist/evals/step-replay.js +12 -6
  91. package/dist/evals/trajectory.js +1 -1
  92. package/dist/extensions/admin-ops.js +2 -0
  93. package/dist/extensions/admin-view.d.ts +3 -1
  94. package/dist/extensions/admin-view.js +10 -2
  95. package/dist/extensions/blobs.js +1 -0
  96. package/dist/extensions/config-issues.d.ts +13 -1
  97. package/dist/extensions/config-issues.js +22 -5
  98. package/dist/extensions/config-json-schema.d.ts +43 -7
  99. package/dist/extensions/config-json-schema.js +124 -58
  100. package/dist/extensions/deployment-config-store.d.ts +6 -0
  101. package/dist/extensions/deployment-config.d.ts +50 -3
  102. package/dist/extensions/deployment-config.js +125 -28
  103. package/dist/extensions/display-name.d.ts +22 -0
  104. package/dist/extensions/display-name.js +77 -0
  105. package/dist/extensions/extensions.module.js +6 -0
  106. package/dist/extensions/host.d.ts +26 -1
  107. package/dist/extensions/host.js +82 -9
  108. package/dist/extensions/org-config.controller.d.ts +11 -3
  109. package/dist/extensions/org-config.controller.js +14 -6
  110. package/dist/extensions/org-config.d.ts +6 -0
  111. package/dist/extensions/org-config.js +12 -13
  112. package/dist/extensions/package.d.ts +14 -1
  113. package/dist/extensions/package.js +51 -2
  114. package/dist/extensions/permission-view.d.ts +3 -0
  115. package/dist/extensions/point-defaults.controller.d.ts +34 -0
  116. package/dist/extensions/point-defaults.controller.js +71 -0
  117. package/dist/extensions/point-defaults.d.ts +64 -0
  118. package/dist/extensions/point-defaults.js +206 -0
  119. package/dist/extensions/registry.d.ts +1 -1
  120. package/dist/extensions/registry.js +2 -0
  121. package/dist/http/api.module.js +8 -0
  122. package/dist/http/nest-logger.d.ts +6 -5
  123. package/dist/http/nest-logger.js +24 -10
  124. package/dist/identity/auth.controller.js +9 -5
  125. package/dist/identity/cookies.d.ts +20 -5
  126. package/dist/identity/cookies.js +44 -6
  127. package/dist/identity/me.controller.d.ts +5 -1
  128. package/dist/identity/me.controller.js +12 -3
  129. package/dist/identity/sessions.d.ts +12 -0
  130. package/dist/identity/sessions.js +45 -0
  131. package/dist/index.d.ts +1 -1
  132. package/dist/infra/config/kernel-config.service.js +6 -0
  133. package/dist/infra/database/database.module.js +1 -0
  134. package/dist/infra/events/kernel-events.d.ts +8 -0
  135. package/dist/infra/lifecycle/process-guards.js +4 -0
  136. package/dist/infra/logging/logging.module.d.ts +4 -2
  137. package/dist/infra/logging/logging.module.js +24 -6
  138. package/dist/infra/queue/redis-connection.d.ts +6 -0
  139. package/dist/infra/queue/redis-connection.js +7 -0
  140. package/dist/infra/redis/redis.module.js +6 -2
  141. package/dist/infra/tokens.d.ts +6 -1
  142. package/dist/infra/tokens.js +6 -1
  143. package/dist/log.d.ts +13 -1
  144. package/dist/log.js +93 -17
  145. package/dist/models/purposes.d.ts +23 -0
  146. package/dist/models/purposes.js +31 -10
  147. package/dist/orgs/offboarding.d.ts +3 -1
  148. package/dist/orgs/offboarding.js +26 -16
  149. package/dist/orgs/settings.service.d.ts +5 -0
  150. package/dist/orgs/settings.service.js +13 -2
  151. package/dist/roles/worker.module.js +6 -0
  152. package/dist/runtime/context/build.d.ts +9 -7
  153. package/dist/runtime/context/build.js +27 -19
  154. package/dist/runtime/context/claude-tools.d.ts +2 -1
  155. package/dist/runtime/context/claude-tools.js +5 -4
  156. package/dist/runtime/context/compaction.js +4 -0
  157. package/dist/runtime/langgraph-loop.js +3 -2
  158. package/dist/runtime/loop.d.ts +4 -3
  159. package/dist/runtime/maintenance.d.ts +4 -2
  160. package/dist/runtime/maintenance.js +17 -5
  161. package/dist/runtime/model-call.d.ts +17 -1
  162. package/dist/runtime/model-call.js +3 -2
  163. package/dist/runtime/providers.d.ts +6 -0
  164. package/dist/runtime/providers.js +8 -0
  165. package/dist/runtime/run-queue.d.ts +25 -2
  166. package/dist/runtime/run-queue.js +129 -33
  167. package/dist/runtime/run-worker.js +16 -3
  168. package/dist/runtime/runtime.module.js +8 -3
  169. package/dist/runtime/step-log.d.ts +6 -0
  170. package/dist/runtime/step-log.js +16 -2
  171. package/dist/runtime/subagents/definition.js +3 -1
  172. package/dist/runtime/usage-report.d.ts +2 -1
  173. package/dist/runtime/usage-report.js +2 -1
  174. package/dist/runtime/waits.d.ts +8 -2
  175. package/dist/runtime/waits.js +37 -11
  176. package/dist/sandbox/sandbox.module.js +6 -1
  177. package/dist/sandbox/sandbox.service.d.ts +61 -3
  178. package/dist/sandbox/sandbox.service.js +141 -7
  179. package/dist/tools/approvals.service.js +2 -0
  180. package/dist/tools/builtin-names.d.ts +20 -2
  181. package/dist/tools/builtin-names.js +29 -2
  182. package/dist/tools/builtins/builtin-tools.d.ts +15 -1
  183. package/dist/tools/builtins/builtin-tools.js +34 -2
  184. package/dist/tools/builtins/sandbox/bash.d.ts +1 -1
  185. package/dist/tools/builtins/sandbox/bash.js +230 -3
  186. package/dist/tools/builtins/sandbox/common.d.ts +27 -4
  187. package/dist/tools/builtins/sandbox/common.js +7 -3
  188. package/dist/tools/builtins/sandbox/deliver.js +1 -1
  189. package/dist/tools/builtins/sandbox/index.d.ts +2 -0
  190. package/dist/tools/builtins/sandbox/index.js +6 -0
  191. package/dist/tools/builtins/sandbox/task-output.d.ts +63 -0
  192. package/dist/tools/builtins/sandbox/task-output.js +304 -0
  193. package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
  194. package/dist/tools/builtins/sandbox/task-stop.js +112 -0
  195. package/dist/tools/builtins/web/errors.d.ts +8 -0
  196. package/dist/tools/builtins/web/errors.js +53 -0
  197. package/dist/tools/builtins/web/extract.d.ts +62 -0
  198. package/dist/tools/builtins/web/extract.js +267 -0
  199. package/dist/tools/builtins/web/fetch.d.ts +55 -0
  200. package/dist/tools/builtins/web/fetch.js +174 -0
  201. package/dist/tools/builtins/web/index.d.ts +13 -0
  202. package/dist/tools/builtins/web/index.js +12 -0
  203. package/dist/tools/builtins/web/page-cache.d.ts +25 -0
  204. package/dist/tools/builtins/web/page-cache.js +77 -0
  205. package/dist/tools/builtins/web/search.d.ts +25 -0
  206. package/dist/tools/builtins/web/search.js +209 -0
  207. package/dist/tools/builtins/web/url.d.ts +17 -0
  208. package/dist/tools/builtins/web/url.js +43 -0
  209. package/dist/tools/oauth/flow-paths.d.ts +1 -1
  210. package/dist/tools/oauth/flow-paths.js +1 -1
  211. package/dist/tools/pipeline.js +15 -0
  212. package/dist/tools/registry.d.ts +22 -3
  213. package/dist/tools/registry.js +26 -11
  214. package/dist/tools/tools.module.js +12 -0
  215. package/dist/tools/validate.js +1 -1
  216. package/package.json +3 -3
  217. package/src/migrations/0064_account_last_org.sql +7 -0
  218. package/src/migrations/0065_conversation_archive.sql +8 -0
  219. package/src/migrations/0066_extension_display_name.sql +4 -0
  220. package/src/migrations/0067_agent_profiles.sql +20 -0
  221. package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
  222. package/src/migrations/0069_attachments_agent_idx.sql +3 -0
  223. package/src/migrations/0070_background_tasks.sql +37 -0
  224. package/src/migrations/0071_background_tasks_reserve.sql +6 -0
  225. package/src/migrations/0072_background_tasks_stopping.sql +5 -0
  226. package/src/migrations/0073_org_extension_point_defaults.sql +11 -0
@@ -226,13 +226,14 @@ async function runStep(deps, input, wrapUp) {
226
226
  }
227
227
  }
228
228
  /**
229
- * Appends a model call's events (its reply or compaction outcome with its `usage`). For a
229
+ * Appends a model call's events (its reply or compaction outcome with its `usage`; also the usage
230
+ * of a `WebFetch` extraction call, `tools/builtins/web/extract.ts`). For a
230
231
  * sub-agent's run (`input.parentRunId`) in one transaction that also adds the tokens of the usage
231
232
  * events it newly wrote to the parent run's `subagent_tokens_used` (sub-agents spec §8.3): a usage
232
233
  * event already in the log (an idempotent hit after a crash) adds nothing, so each call counts
233
234
  * exactly once.
234
235
  */
235
- async function appendCounted(deps, input, events) {
236
+ export async function appendCounted(deps, input, events) {
236
237
  const { conversationId, parentRunId } = input;
237
238
  if (parentRunId === undefined)
238
239
  return deps.eventLog.append(conversationId, events);
@@ -67,6 +67,12 @@ export declare class ProviderRegistry {
67
67
  listTimeoutMs?: number);
68
68
  /** `listModels()` 加时限(到时按失败处理,供应商的调用留在后台、结果丢弃)。 */
69
69
  private listModelsWithTimeout;
70
+ /**
71
+ * Whether at least one model-provider extension is loaded on this replica (enabled, configured,
72
+ * set up). Read from the extension host only: never calls a vendor, so a provider whose vendor
73
+ * is down still counts (org readiness, web-order spec §3.8).
74
+ */
75
+ hasProvider(): boolean;
70
76
  /** 每个插件取一个实现(`@2` 优先),按插件首次出现的顺序;不支持的主版本记 warn 后忽略。 */
71
77
  private providers;
72
78
  private models;
@@ -132,6 +132,14 @@ let ProviderRegistry = class ProviderRegistry {
132
132
  clearTimeout(timer);
133
133
  }
134
134
  }
135
+ /**
136
+ * Whether at least one model-provider extension is loaded on this replica (enabled, configured,
137
+ * set up). Read from the extension host only: never calls a vendor, so a provider whose vendor
138
+ * is down still counts (org readiness, web-order spec §3.8).
139
+ */
140
+ hasProvider() {
141
+ return this.providers().length > 0;
142
+ }
135
143
  /** 每个插件取一个实现(`@2` 优先),按插件首次出现的顺序;不支持的主版本记 warn 后忽略。 */
136
144
  providers() {
137
145
  const chosen = new Map();
@@ -1,6 +1,8 @@
1
1
  import type { Logger } from "@capybara-hq/plugin-sdk";
2
+ import type { Queue } from "bullmq";
2
3
  import { EventLog } from "../conversations/event-log.js";
3
4
  import type { ConversationKind } from "../db/kernel-schema.js";
5
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
4
6
  import { KernelDb } from "../infra/database/kernel-db.js";
5
7
  import { TransactionHost } from "../infra/database/transaction-host.js";
6
8
  import { KernelEvents } from "../infra/events/kernel-events.js";
@@ -29,11 +31,16 @@ export declare class RunQueue {
29
31
  private readonly events;
30
32
  private readonly log;
31
33
  readonly txHost: TransactionHost;
34
+ private readonly config;
35
+ /** The producer side of the delayed `bg-wake` job (`MaintenanceProcessor` consumes it). */
36
+ private readonly maintenance;
32
37
  constructor(kdb: KernelDb, eventLog: EventLog, lock: Pick<ConversationLock, "holder">, jobs: RunJobs, finisher: Pick<RunFinish, "finishIn">,
33
38
  /** 运行以孤儿结束(标记失败 / 直接中止)后删除它的 checkpoint thread(thread_id = runId)。 */
34
39
  checkpoints: Pick<KyselyCheckpointSaver, "deleteThread">,
35
40
  /** The run's open `Agent` call results, in the orphan repair's transaction (sub-agents spec §12). */
36
- pipeline: Pick<ToolPipeline, "stopOpenAgentCalls">, events: KernelEvents, log: Pick<Logger, "error" | "warn">, txHost: TransactionHost);
41
+ pipeline: Pick<ToolPipeline, "stopOpenAgentCalls">, events: KernelEvents, log: Pick<Logger, "error" | "warn">, txHost: TransactionHost, config: KernelConfigService,
42
+ /** The producer side of the delayed `bg-wake` job (`MaintenanceProcessor` consumes it). */
43
+ maintenance: Pick<Queue, "add">);
37
44
  private get db();
38
45
  /**
39
46
  * The `beforeTerminal` of an orphan repair: writes the run's open `Agent` call results in the
@@ -44,6 +51,11 @@ export declare class RunQueue {
44
51
  * 有未消费的用户消息(最新 user_message 的 seq > `consumed_seq`)、会话锁无人持有、
45
52
  * 且没有 queued / running / waiting 的运行时:插入 `runs`(queued)并投递任务,返回 runId;否则返回 null。
46
53
  *
54
+ * The boundary (`trigger_seq`) is that newest message; with none unconsumed, the queue's head
55
+ * once it is sent; with no head either, a wake-eligible finished background task (background
56
+ * Bash spec §3, {@link wakeDecisionIn}): the run is then attributed to the task's trigger
57
+ * account, and a finish inside the wake window schedules the delayed `bg-wake` job instead.
58
+ *
47
59
  * 孤儿清理(R29,按需进行,没有定时器):已有的 queued / running 运行若 BullMQ 任务已不存在、
48
60
  * 会话锁也无人持有(queued 的还要超过宽限期),说明没有任何 worker 会再执行它——写
49
61
  * `run_failed { code: "run.orphaned" }` 把它标记为失败(consumed_seq 推进到它的触发消息),
@@ -52,9 +64,20 @@ export declare class RunQueue {
52
64
  *
53
65
  * agent 已软删除的会话不起新运行(孤儿清理照常进行)。
54
66
  */
55
- ensureRun(conversationId: string): Promise<string | null>;
67
+ ensureRun(conversationId: string,
68
+ /** From the `bg-wake` job: it fired `wakeRetry - 1` times before its window ended. */
69
+ opts?: {
70
+ wakeRetry?: number;
71
+ }): Promise<string | null>;
56
72
  /** {@link ensureRun}'s transaction: what it decided goes back through `st`. */
57
73
  private ensureRunIn;
74
+ /**
75
+ * Schedules the delayed `bg-wake` job at the end of the conversation's wake window (after the
76
+ * commit): one job per conversation and window end, so finishes inside one window share it. It
77
+ * calls {@link ensureRun}, which wakes the conversation if a finish is still unconsumed. A failed
78
+ * enqueue is logged: the next message, run end or page opening re-derives the wake.
79
+ */
80
+ private scheduleWake;
58
81
  /**
59
82
  * Enqueues a run job under the caller's job id (resumes, dispatches, repairs), with the same
60
83
  * options as {@link ensureRun} (see {@link RunJobs.enqueue}).
@@ -8,11 +8,14 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { randomUUID } from "node:crypto";
11
+ import { getQueueToken } from "@nestjs/bullmq";
11
12
  import { Inject, Injectable } from "@nestjs/common";
12
13
  import { sql } from "kysely";
13
14
  import { visibleTo } from "../agents/agent-lookup.js";
15
+ import { BG_WAKE_JOB, bgWakeJobId, wakeDecisionIn, } from "../background/wake-rules.js";
14
16
  import { EventLog } from "../conversations/event-log.js";
15
17
  import { dispatchQueuedIn, pauseQueueWith } from "../conversations/queue.js";
18
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
16
19
  import { KernelDb } from "../infra/database/kernel-db.js";
17
20
  import { TransactionHost } from "../infra/database/transaction-host.js";
18
21
  import { Transactional } from "../infra/database/transactional.decorator.js";
@@ -21,6 +24,7 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
21
24
  import { ToolPipeline } from "../tools/pipeline.js";
22
25
  import { KyselyCheckpointSaver } from "./checkpointer.js";
23
26
  import { ConversationLock } from "./conversation-lock.js";
27
+ import { MAINTENANCE_QUEUE_NAME } from "./maintenance-queue.js";
24
28
  import { notifyFinished, RunFinish } from "./run-finish.js";
25
29
  import { ACTIVE_RUN_STATUSES, RunJobs, runJobId, runQueueNameFor, } from "./run-jobs.js";
26
30
  export { ACTIVE_RUN_STATUSES, RUN_QUEUE_NAME, runJobId, runQueueNameFor, SUBAGENT_RUN_QUEUE_NAME, } from "./run-jobs.js";
@@ -29,6 +33,12 @@ export { ACTIVE_RUN_STATUSES, RUN_QUEUE_NAME, runJobId, runQueueNameFor, SUBAGEN
29
33
  * 只有进入 queued 超过这么久(`COALESCE(queued_at, created_at)`)仍没有任务的 queued 行才视为孤儿。
30
34
  */
31
35
  const ORPHAN_QUEUED_GRACE_SECONDS = 30;
36
+ /**
37
+ * Margin added to the delayed wake job: its window is read on the database's clock, the delay runs
38
+ * on this process's and Redis's, so the job does not usually fire just before the window ends (one
39
+ * that does reschedules itself under its next retry id, `bgWakeJobId`).
40
+ */
41
+ const WAKE_JOB_MARGIN_MS = 1000;
32
42
  /**
33
43
  * 运行队列(生产方):`ensureRun` 在会话有未消费的用户消息、且没有进行中的运行时插入 `runs` 行并投递任务。
34
44
  * 「是否已有运行」以 PG 为准(会话行锁内检查 `runs` 中的 queued / running),并参考会话锁的持有者;
@@ -45,11 +55,15 @@ let RunQueue = class RunQueue {
45
55
  events;
46
56
  log;
47
57
  txHost;
58
+ config;
59
+ maintenance;
48
60
  constructor(kdb, eventLog, lock, jobs, finisher,
49
61
  /** 运行以孤儿结束(标记失败 / 直接中止)后删除它的 checkpoint thread(thread_id = runId)。 */
50
62
  checkpoints,
51
63
  /** The run's open `Agent` call results, in the orphan repair's transaction (sub-agents spec §12). */
52
- pipeline, events, log, txHost) {
64
+ pipeline, events, log, txHost, config,
65
+ /** The producer side of the delayed `bg-wake` job (`MaintenanceProcessor` consumes it). */
66
+ maintenance) {
53
67
  this.kdb = kdb;
54
68
  this.eventLog = eventLog;
55
69
  this.lock = lock;
@@ -60,6 +74,8 @@ let RunQueue = class RunQueue {
60
74
  this.events = events;
61
75
  this.log = log;
62
76
  this.txHost = txHost;
77
+ this.config = config;
78
+ this.maintenance = maintenance;
63
79
  }
64
80
  get db() {
65
81
  return this.kdb.db;
@@ -81,6 +97,11 @@ let RunQueue = class RunQueue {
81
97
  * 有未消费的用户消息(最新 user_message 的 seq > `consumed_seq`)、会话锁无人持有、
82
98
  * 且没有 queued / running / waiting 的运行时:插入 `runs`(queued)并投递任务,返回 runId;否则返回 null。
83
99
  *
100
+ * The boundary (`trigger_seq`) is that newest message; with none unconsumed, the queue's head
101
+ * once it is sent; with no head either, a wake-eligible finished background task (background
102
+ * Bash spec §3, {@link wakeDecisionIn}): the run is then attributed to the task's trigger
103
+ * account, and a finish inside the wake window schedules the delayed `bg-wake` job instead.
104
+ *
84
105
  * 孤儿清理(R29,按需进行,没有定时器):已有的 queued / running 运行若 BullMQ 任务已不存在、
85
106
  * 会话锁也无人持有(queued 的还要超过宽限期),说明没有任何 worker 会再执行它——写
86
107
  * `run_failed { code: "run.orphaned" }` 把它标记为失败(consumed_seq 推进到它的触发消息),
@@ -89,7 +110,9 @@ let RunQueue = class RunQueue {
89
110
  *
90
111
  * agent 已软删除的会话不起新运行(孤儿清理照常进行)。
91
112
  */
92
- async ensureRun(conversationId) {
113
+ async ensureRun(conversationId,
114
+ /** From the `bg-wake` job: it fired `wakeRetry - 1` times before its window ended. */
115
+ opts = {}) {
93
116
  if ((await this.lock.holder(conversationId)) !== null)
94
117
  return null;
95
118
  const st = {
@@ -97,11 +120,17 @@ let RunQueue = class RunQueue {
97
120
  orphanRunId: null,
98
121
  dispatchedSeq: null,
99
122
  dispatchedSwitch: false,
123
+ dispatchedUnarchived: false,
100
124
  pausedNow: false,
101
125
  orphanStop: undefined,
126
+ wakeLater: null,
102
127
  };
103
128
  const created = await this.ensureRunIn(conversationId, st);
129
+ if (st.wakeLater !== null) {
130
+ await this.scheduleWake(conversationId, st.wakeLater, opts.wakeRetry ?? 0);
131
+ }
104
132
  const { orphanSeq, orphanRunId, dispatchedSeq, dispatchedSwitch, pausedNow, orphanStop } = st;
133
+ const { dispatchedUnarchived } = st;
105
134
  if (orphanSeq !== null) {
106
135
  await notifyFinished(this.eventLog, conversationId, orphanSeq, "failed");
107
136
  orphanStop?.fire();
@@ -114,6 +143,8 @@ let RunQueue = class RunQueue {
114
143
  await this.eventLog.publishChanged(conversationId, "queue");
115
144
  if (dispatchedSwitch)
116
145
  await this.eventLog.publishChanged(conversationId, "selection");
146
+ if (dispatchedUnarchived)
147
+ await this.eventLog.publishChanged(conversationId, "archived");
117
148
  }
118
149
  if (orphanRunId !== null) {
119
150
  await this.dropCheckpoints(orphanRunId);
@@ -153,14 +184,25 @@ let RunQueue = class RunQueue {
153
184
  const t = this.db;
154
185
  const conv = await t
155
186
  .selectFrom("conversations")
156
- .select(["consumed_seq", "agent_id", "kind", "queue_paused", "account_id"])
187
+ .select([
188
+ "consumed_seq",
189
+ "org_id",
190
+ "agent_id",
191
+ "kind",
192
+ "queue_paused",
193
+ "account_id",
194
+ "deleted_at",
195
+ ])
157
196
  .where("id", "=", conversationId)
158
197
  .forUpdate()
159
198
  .executeTakeFirst();
160
199
  // 评测回放的隐藏对话:运行由评测 worker 在里面直接驱动(工具只回放记录),绝不为它起真实运行
161
200
  if (conv?.kind !== "chat")
162
201
  return null;
163
- let last = await t
202
+ // A deleted conversation starts no run, whatever is still unconsumed or queued
203
+ if (conv.deleted_at !== null)
204
+ return null;
205
+ const newest = await t
164
206
  .selectFrom("conversation_events")
165
207
  .select(["seq", "payload"])
166
208
  .where("conversation_id", "=", conversationId)
@@ -168,11 +210,25 @@ let RunQueue = class RunQueue {
168
210
  .orderBy("seq", "desc")
169
211
  .limit(1)
170
212
  .executeTakeFirst();
171
- if (!last || Number(last.seq) <= Number(conv.consumed_seq)) {
213
+ /** The run's boundary: its `trigger_seq` and the account it runs as. */
214
+ let last = newest && {
215
+ seq: newest.seq,
216
+ accountId: newest.payload.authorAccountId,
217
+ };
218
+ const idleActive = !last || Number(last.seq) <= Number(conv.consumed_seq)
219
+ ? await this.activeRun(t, conversationId)
220
+ : undefined;
221
+ if (idleActive && Number(idleActive.trigger_seq) > Number(conv.consumed_seq)) {
222
+ // No unconsumed message, but a run whose boundary is not consumed yet (a wake run, whose
223
+ // boundary is a finished background task): it goes through the orphan check below with
224
+ // that boundary, as a message's run does
225
+ last = { seq: idleActive.trigger_seq, accountId: idleActive.trigger_account_id };
226
+ }
227
+ else if (!last || Number(last.seq) <= Number(conv.consumed_seq)) {
172
228
  // Nothing unconsumed: the queue's head goes out when the conversation is idle (no active
173
229
  // run, which includes runs waiting for approval or an answer) and the queue is not paused.
174
230
  // Interjections are always unconsumed messages, so the queue never jumps ahead of them.
175
- if (conv.queue_paused || (await this.activeRun(t, conversationId)))
231
+ if (conv.queue_paused || idleActive)
176
232
  return null;
177
233
  const owner = await t
178
234
  .selectFrom("agents")
@@ -187,32 +243,46 @@ let RunQueue = class RunQueue {
187
243
  .where("conversation_id", "=", conversationId)
188
244
  .limit(1)
189
245
  .executeTakeFirst();
190
- if (!head)
191
- return null;
192
- // A queued message may wait for hours: it goes out only while its author can still use
193
- // the conversation (active, a member, the agent visible to them; the isAccessible rule).
194
- // Otherwise the queue pauses instead of posting as someone who lost access.
195
- if (!(await authorCanSend(t, conversationId, conv.account_id))) {
196
- await pauseQueueWith(t, conversationId, "author_cannot_send");
197
- st.pausedNow = true;
198
- this.log.warn("queued message not sent: its author can no longer use the conversation", {
199
- conversationId,
200
- });
201
- return null;
246
+ if (head) {
247
+ // A queued message may wait for hours: it goes out only while its author can still use
248
+ // the conversation (active, a member, the agent visible to them; the isAccessible rule).
249
+ // Otherwise the queue pauses instead of posting as someone who lost access.
250
+ if (!(await authorCanSend(t, conversationId, conv.account_id))) {
251
+ await pauseQueueWith(t, conversationId, "author_cannot_send");
252
+ st.pausedNow = true;
253
+ this.log.warn("queued message not sent: its author can no longer use the conversation", {
254
+ conversationId,
255
+ });
256
+ return null;
257
+ }
258
+ const sent = await dispatchQueuedIn(t, this.eventLog, conversationId);
259
+ if (!sent)
260
+ return null;
261
+ if ("paused" in sent) {
262
+ st.pausedNow = true;
263
+ this.log.warn("queued skill invocation not sent: the skill can no longer be invoked", {
264
+ conversationId,
265
+ });
266
+ return null;
267
+ }
268
+ st.dispatchedSeq = sent.event.seq;
269
+ st.dispatchedSwitch = sent.switched;
270
+ st.dispatchedUnarchived = sent.unarchived;
271
+ last = {
272
+ seq: String(sent.event.seq),
273
+ accountId: sent.event.payload.authorAccountId,
274
+ };
202
275
  }
203
- const sent = await dispatchQueuedIn(t, this.eventLog, conversationId);
204
- if (!sent)
205
- return null;
206
- if ("paused" in sent) {
207
- st.pausedNow = true;
208
- this.log.warn("queued skill invocation not sent: the skill can no longer be invoked", {
209
- conversationId,
210
- });
211
- return null;
276
+ else {
277
+ // Nothing queued either: a finished background task may wake the idle conversation
278
+ // (background Bash spec §3; a person's message or the queue always goes first)
279
+ const wake = await wakeDecisionIn(t, { id: conversationId, orgId: conv.org_id }, Number(conv.consumed_seq), this.config.get().runtime.wakeOnBackgroundTask, (accountId) => authorCanSend(t, conversationId, accountId));
280
+ if (wake?.kind === "later")
281
+ st.wakeLater = wake;
282
+ if (wake?.kind !== "wake")
283
+ return null;
284
+ last = { seq: String(wake.seq), accountId: wake.triggerAccountId };
212
285
  }
213
- st.dispatchedSeq = sent.event.seq;
214
- st.dispatchedSwitch = sent.switched;
215
- last = { seq: String(sent.event.seq), payload: sent.event.payload };
216
286
  }
217
287
  const active = await this.activeRun(t, conversationId);
218
288
  if (active) {
@@ -254,7 +324,9 @@ let RunQueue = class RunQueue {
254
324
  .orderBy("seq")
255
325
  .limit(1)
256
326
  .executeTakeFirst();
257
- const trigger = fork ?? last;
327
+ const trigger = fork
328
+ ? { seq: fork.seq, accountId: fork.payload.authorAccountId }
329
+ : last;
258
330
  // BullMQ 对重复的 jobId 静默忽略(add 返回的 Job 由调用方数据构造,看不出是否真的入队):
259
331
  // 先显式查同一触发点的任务;仍存在(例如属于刚结束、尚未从队列移除的运行)就不插入 runs 行。
260
332
  const jobId = runJobId(conversationId, Number(trigger.seq));
@@ -283,12 +355,33 @@ let RunQueue = class RunQueue {
283
355
  conversation_id: conversationId,
284
356
  status: "queued",
285
357
  agent_version: agent.current_version,
286
- trigger_account_id: trigger.payload.authorAccountId,
358
+ trigger_account_id: trigger.accountId,
287
359
  trigger_seq: Number(trigger.seq),
288
360
  })
289
361
  .execute();
290
362
  return { runId, jobId };
291
363
  }
364
+ /**
365
+ * Schedules the delayed `bg-wake` job at the end of the conversation's wake window (after the
366
+ * commit): one job per conversation and window end, so finishes inside one window share it. It
367
+ * calls {@link ensureRun}, which wakes the conversation if a finish is still unconsumed. A failed
368
+ * enqueue is logged: the next message, run end or page opening re-derives the wake.
369
+ */
370
+ async scheduleWake(conversationId, w, retry) {
371
+ const data = { conversationId, ...(retry > 0 && { retry }) };
372
+ try {
373
+ await this.maintenance.add(BG_WAKE_JOB, data, {
374
+ // a job that fired early reschedules under a new id (its own is still taken)
375
+ jobId: bgWakeJobId(conversationId, w.windowEnd, retry),
376
+ delay: w.delayMs + WAKE_JOB_MARGIN_MS,
377
+ removeOnComplete: true,
378
+ removeOnFail: true,
379
+ });
380
+ }
381
+ catch (err) {
382
+ this.log.error("cannot schedule the background task wake", { conversationId, err });
383
+ }
384
+ }
292
385
  /**
293
386
  * Enqueues a run job under the caller's job id (resumes, dispatches, repairs), with the same
294
387
  * options as {@link ensureRun} (see {@link RunJobs.enqueue}).
@@ -407,6 +500,7 @@ let RunQueue = class RunQueue {
407
500
  "id",
408
501
  "status",
409
502
  "trigger_seq",
503
+ "trigger_account_id",
410
504
  "job_id",
411
505
  sql `COALESCE(queued_at, created_at) < now() - make_interval(secs => ${ORPHAN_QUEUED_GRACE_SECONDS})`.as("past_grace"),
412
506
  ])
@@ -462,7 +556,9 @@ RunQueue = __decorate([
462
556
  __param(6, Inject(ToolPipeline)),
463
557
  __param(7, Inject(KernelEvents)),
464
558
  __param(8, Inject(KERNEL_LOGGER)),
465
- __param(9, Inject(TransactionHost))
559
+ __param(9, Inject(TransactionHost)),
560
+ __param(10, Inject(KernelConfigService)),
561
+ __param(11, Inject(getQueueToken(MAINTENANCE_QUEUE_NAME)))
466
562
  ], RunQueue);
467
563
  export { RunQueue };
468
564
  /** The conversation's owner is active and can still see its agent (`isAccessible`'s rule). */
@@ -11,6 +11,7 @@ import { Inject, Injectable, Optional } from "@nestjs/common";
11
11
  import { DelayedError } from "bullmq";
12
12
  import { sql } from "kysely";
13
13
  import { conversationTopic, EventLog, } from "../conversations/event-log.js";
14
+ import { isAbsorbable, } from "../conversations/events.js";
14
15
  import { effectiveDefinition, NO_SELECTION, readSelection, sameModel, sameSelection, } from "../conversations/selection.js";
15
16
  import { TitleQueue } from "../conversations/titles.js";
16
17
  import { ApiError } from "../errors.js";
@@ -642,15 +643,16 @@ let RunWorker = class RunWorker {
642
643
  .executeTakeFirstOrThrow();
643
644
  const startConsumed = Number(consumed_seq);
644
645
  let absorbed = startConsumed;
645
- const pendingUserMessages = async (afterSeq) => {
646
+ const pendingAbsorbable = async (afterSeq) => {
646
647
  // loop 声明已吸收到 afterSeq(续跑时来自 checkpoint,可能包含之前进程吸收的消息)。
647
648
  absorbed = Math.max(absorbed, afterSeq);
648
649
  const out = [];
649
650
  let after = Math.max(afterSeq, startConsumed);
650
651
  for (;;) {
651
652
  const page = await this.eventLog.readAfter(conversationId, after, READ_PAGE);
653
+ // messages and finished background tasks (spec 2026-11-05-background-bash-design §2.4)
652
654
  for (const e of page)
653
- if (e.type === "user_message")
655
+ if (isAbsorbable(e))
654
656
  out.push(e);
655
657
  const last = page.at(-1);
656
658
  if (page.length < READ_PAGE || !last)
@@ -677,7 +679,7 @@ let RunWorker = class RunWorker {
677
679
  signal: ac.signal,
678
680
  shutdown: state.shutdown.signal,
679
681
  sink: this.sinkFor(conversationId),
680
- pendingUserMessages,
682
+ pendingAbsorbable,
681
683
  limits: { maxSteps, deadline },
682
684
  // a sub-agent's conversation fires hooks like a chat, with `parent` set (spec §11)
683
685
  conversationKind: conv.kind === "eval" ? "eval" : conv.kind === "subagent" ? "subagent" : "chat",
@@ -814,6 +816,13 @@ let RunWorker = class RunWorker {
814
816
  const agent = saved !== undefined
815
817
  ? effectiveDefinition(saved, await readSelection(t, conversationId))
816
818
  : current;
819
+ // a wake run's boundary is a finished background task, not a message (background Bash §3)
820
+ const boundary = await t
821
+ .selectFrom("conversation_events")
822
+ .select("type")
823
+ .where("conversation_id", "=", conversationId)
824
+ .where("seq", "=", run.trigger_seq)
825
+ .executeTakeFirst();
817
826
  const [ev] = await this.eventLog.appendIn(conversationId, [
818
827
  {
819
828
  type: "run_started",
@@ -823,6 +832,10 @@ let RunWorker = class RunWorker {
823
832
  agentVersion: run.agent_version,
824
833
  model: agent.model,
825
834
  triggerAccountId: run.trigger_account_id,
835
+ ...(boundary?.type === "background_task_finished" && {
836
+ trigger: "background_task",
837
+ triggerSeq: Number(run.trigger_seq),
838
+ }),
826
839
  },
827
840
  },
828
841
  ]);
@@ -5,6 +5,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { Module } from "@nestjs/common";
8
+ import { BackgroundModule } from "../background/background.module.js";
9
+ import { BackgroundWake } from "../background/wake.js";
8
10
  import { ConversationAccessModule } from "../conversations/access.js";
9
11
  import { AttachmentsModule } from "../conversations/attachments.module.js";
10
12
  import { EventLogModule } from "../conversations/event-log.js";
@@ -28,7 +30,7 @@ import { RunJobsModule } from "./run-jobs.js";
28
30
  import { RunQueue } from "./run-queue.js";
29
31
  import { AgentLoops, RunSteps } from "./run-steps.js";
30
32
  import { RunWorker } from "./run-worker.js";
31
- import { StepLog } from "./step-log.js";
33
+ import { StepLogModule } from "./step-log.js";
32
34
  import { ConversationStreams } from "./stream.js";
33
35
  import { SubagentsModule } from "./subagents/subagents.service.js";
34
36
  import { UsageReportModule } from "./usage-report.js";
@@ -58,6 +60,8 @@ RuntimeModule = __decorate([
58
60
  SubagentsModule,
59
61
  UsageReportModule,
60
62
  TitlesModule,
63
+ StepLogModule,
64
+ BackgroundModule,
61
65
  // the producer side of `wait-timeout` jobs (`MaintenanceProcessor` consumes them)
62
66
  QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
63
67
  ],
@@ -67,7 +71,6 @@ RuntimeModule = __decorate([
67
71
  RunAbort,
68
72
  KyselyCheckpointSaver,
69
73
  RunFinish,
70
- StepLog,
71
74
  RunQueue,
72
75
  Waits,
73
76
  DecisionResumer,
@@ -75,6 +78,8 @@ RuntimeModule = __decorate([
75
78
  AgentLoops,
76
79
  RunSteps,
77
80
  RunWorker,
81
+ // a finished background task wakes an idle conversation (background Bash spec §3)
82
+ BackgroundWake,
78
83
  // one quota per key prefix (the org run quota, the org eval quota)
79
84
  {
80
85
  provide: RUN_QUOTA,
@@ -93,7 +98,7 @@ RuntimeModule = __decorate([
93
98
  RunAbort,
94
99
  KyselyCheckpointSaver,
95
100
  RunFinish,
96
- StepLog,
101
+ StepLogModule,
97
102
  RunQueue,
98
103
  Waits,
99
104
  RunControl,
@@ -38,3 +38,9 @@ export declare class StepLog {
38
38
  spec: ToolSpec;
39
39
  }[], events: AppendEvent[]): Promise<ConversationEvent[]>;
40
40
  }
41
+ /**
42
+ * The step writes ({@link StepLog}): a module of its own, so the tools domain (the `WebFetch`
43
+ * extraction's usage and denials) uses them without importing the runtime.
44
+ */
45
+ export declare class StepLogModule {
46
+ }
@@ -7,9 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { Inject, Injectable } from "@nestjs/common";
10
+ import { Inject, Injectable, Module } from "@nestjs/common";
11
11
  import { sql } from "kysely";
12
- import { EventLog } from "../conversations/event-log.js";
12
+ import { EventLog, EventLogModule } from "../conversations/event-log.js";
13
13
  import { usageTokens } from "../evals/budget.js";
14
14
  import { audit } from "../identity/audit.js";
15
15
  import { KernelDb } from "../infra/database/kernel-db.js";
@@ -92,3 +92,17 @@ StepLog = __decorate([
92
92
  __param(2, Inject(TransactionHost))
93
93
  ], StepLog);
94
94
  export { StepLog };
95
+ /**
96
+ * The step writes ({@link StepLog}): a module of its own, so the tools domain (the `WebFetch`
97
+ * extraction's usage and denials) uses them without importing the runtime.
98
+ */
99
+ let StepLogModule = class StepLogModule {
100
+ };
101
+ StepLogModule = __decorate([
102
+ Module({
103
+ imports: [EventLogModule],
104
+ providers: [StepLog],
105
+ exports: [StepLog],
106
+ })
107
+ ], StepLogModule);
108
+ export { StepLogModule };
@@ -228,7 +228,9 @@ function orgAgentDefinition(d, a) {
228
228
  }
229
229
  const agentId = a.entry.agentId ?? "";
230
230
  const own = a.orgAgent.definition;
231
- const { subagents: _subagents, skills, ...rest } = own;
231
+ // prompt files are authoring data: the child's model sees only `instructions` (their join), and
232
+ // `subagent_started.definition` keeps its shape (spec 2026-11-04 §3.3)
233
+ const { subagents: _subagents, skills, instructionFiles: _files, ...rest } = own;
232
234
  const definition = {
233
235
  ...rest,
234
236
  // its own tools, not intersected with the parent's; only the child-removed ones go
@@ -93,7 +93,8 @@ export declare function narrowPending(stored: readonly string[] | undefined, fai
93
93
  * using up an attempt.
94
94
  * Handlers must be idempotent by `eventId` (`<conversationId>/<seq>`). Nothing is written to the event
95
95
  * log (never model-visible). Compaction calls log usage too (`purpose: "compaction"`) and are reported,
96
- * as are conversation title calls (`purpose: "title"`, no run: `runId` is "").
96
+ * as are conversation title calls (`purpose: "title"`, no run: `runId` is "") and `WebFetch`
97
+ * extraction calls (`purpose: "web_fetch"`).
97
98
  * Single-step eval replays and judge requests log no usage: not reported (M7).
98
99
  */
99
100
  export declare class UsageReporter {
@@ -160,7 +160,8 @@ export function narrowPending(stored, failed) {
160
160
  * using up an attempt.
161
161
  * Handlers must be idempotent by `eventId` (`<conversationId>/<seq>`). Nothing is written to the event
162
162
  * log (never model-visible). Compaction calls log usage too (`purpose: "compaction"`) and are reported,
163
- * as are conversation title calls (`purpose: "title"`, no run: `runId` is "").
163
+ * as are conversation title calls (`purpose: "title"`, no run: `runId` is "") and `WebFetch`
164
+ * extraction calls (`purpose: "web_fetch"`).
164
165
  * Single-step eval replays and judge requests log no usage: not reported (M7).
165
166
  */
166
167
  let UsageReporter = class UsageReporter {
@@ -1,6 +1,7 @@
1
1
  import type { Logger } from "@capybara-hq/plugin-sdk";
2
2
  import { type OnApplicationShutdown } from "@nestjs/common";
3
3
  import { type Job, type Queue } from "bullmq";
4
+ import { BackgroundTaskStopper } from "../background/stop.js";
4
5
  import { EventLog } from "../conversations/event-log.js";
5
6
  import type { KernelSchemaDb } from "../db/kernel-schema.js";
6
7
  import { KernelDb } from "../infra/database/kernel-db.js";
@@ -91,6 +92,8 @@ export declare class Waits implements OnApplicationShutdown {
91
92
  /** The maintenance queue: wait timeout jobs (`MaintenanceProcessor` consumes them). */
92
93
  private readonly timeouts;
93
94
  private readonly log;
95
+ /** Stops a finished sub-agent run's background tasks ({@link childFinished}). */
96
+ private readonly background;
94
97
  readonly txHost: TransactionHost;
95
98
  /** Drops the `run.orphaned` subscription (the events may outlive this instance). */
96
99
  private readonly unsubscribe;
@@ -109,7 +112,9 @@ export declare class Waits implements OnApplicationShutdown {
109
112
  */
110
113
  abort: Pick<RunAbort, "requested" | "request">,
111
114
  /** The maintenance queue: wait timeout jobs (`MaintenanceProcessor` consumes them). */
112
- timeouts: Pick<Queue, "add">, events: KernelEvents, log: Pick<Logger, "error" | "warn">, txHost: TransactionHost);
115
+ timeouts: Pick<Queue, "add">, events: KernelEvents, log: Pick<Logger, "error" | "warn">,
116
+ /** Stops a finished sub-agent run's background tasks ({@link childFinished}). */
117
+ background: Pick<BackgroundTaskStopper, "stopSubagentTasks">, txHost: TransactionHost);
113
118
  /** Unsubscribes after the stop phases: events emitted while they drain are still handled. */
114
119
  onApplicationShutdown(): void;
115
120
  /** The context's handle (the transaction of a `@Transactional` method, else the pool). */
@@ -247,7 +252,8 @@ export declare class Waits implements OnApplicationShutdown {
247
252
  * the conditional resume lets exactly one succeed, the last to see all its siblings terminal. A
248
253
  * run that is not a dispatched child, or not terminal yet, is left alone. A crash in between is
249
254
  * repaired by the wait sweep and by the parent's tools node, which writes a missing
250
- * `subagent_finished` itself.
255
+ * `subagent_finished` itself. The running background tasks the terminal child run started are
256
+ * then stopped (not those of a later follow-up run of the same child).
251
257
  */
252
258
  childFinished(childRunId: string): Promise<void>;
253
259
  /**