@capybara-hq/kernel 0.1.0-rc.2 → 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 (312) 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 +20 -2
  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 +49 -13
  14. package/dist/agents/definition.d.ts +35 -8
  15. package/dist/agents/definition.js +203 -17
  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 +20 -5
  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/catalog-content.js +4 -1
  56. package/dist/catalog/claude/agents.js +15 -10
  57. package/dist/catalog/marketplaces.service.d.ts +1 -1
  58. package/dist/catalog/plugin-import.js +1 -1
  59. package/dist/catalog/plugin-upgrade.js +1 -1
  60. package/dist/channels/cards.d.ts +8 -2
  61. package/dist/channels/cards.js +17 -9
  62. package/dist/channels/connections.service.d.ts +16 -1
  63. package/dist/channels/connections.service.js +38 -4
  64. package/dist/channels/threads.d.ts +5 -0
  65. package/dist/channels/threads.js +8 -0
  66. package/dist/config/schema.d.ts +18 -0
  67. package/dist/config/schema.js +104 -2
  68. package/dist/conversations/access.js +2 -0
  69. package/dist/conversations/artifacts.d.ts +1 -1
  70. package/dist/conversations/artifacts.js +1 -1
  71. package/dist/conversations/attachments.service.d.ts +30 -0
  72. package/dist/conversations/attachments.service.js +130 -5
  73. package/dist/conversations/conversations.controller.d.ts +36 -5
  74. package/dist/conversations/conversations.controller.js +62 -12
  75. package/dist/conversations/conversations.module.js +3 -0
  76. package/dist/conversations/conversations.service.d.ts +44 -2
  77. package/dist/conversations/conversations.service.js +140 -12
  78. package/dist/conversations/event-log.d.ts +12 -1
  79. package/dist/conversations/event-log.js +29 -1
  80. package/dist/conversations/events.d.ts +81 -3
  81. package/dist/conversations/events.js +83 -16
  82. package/dist/conversations/queue.d.ts +3 -1
  83. package/dist/conversations/queue.js +13 -2
  84. package/dist/conversations/queue.service.js +3 -1
  85. package/dist/conversations/titles.d.ts +7 -0
  86. package/dist/conversations/titles.js +15 -0
  87. package/dist/db/kernel-schema.d.ts +60 -4
  88. package/dist/db/pool.d.ts +5 -0
  89. package/dist/db/pool.js +3 -0
  90. package/dist/errors.d.ts +8 -0
  91. package/dist/errors.js +8 -0
  92. package/dist/evals/recorded-tools.d.ts +27 -1
  93. package/dist/evals/recorded-tools.js +85 -10
  94. package/dist/evals/run-replay.d.ts +10 -2
  95. package/dist/evals/run-replay.js +49 -12
  96. package/dist/evals/step-replay.js +12 -6
  97. package/dist/evals/trajectory.d.ts +2 -2
  98. package/dist/evals/trajectory.js +1 -1
  99. package/dist/extensions/admin-ops.js +2 -0
  100. package/dist/extensions/admin-view.d.ts +3 -1
  101. package/dist/extensions/admin-view.js +10 -2
  102. package/dist/extensions/blobs.js +1 -0
  103. package/dist/extensions/config-issues.d.ts +13 -1
  104. package/dist/extensions/config-issues.js +22 -5
  105. package/dist/extensions/config-json-schema.d.ts +43 -7
  106. package/dist/extensions/config-json-schema.js +124 -58
  107. package/dist/extensions/deployment-config-store.d.ts +6 -0
  108. package/dist/extensions/deployment-config.d.ts +50 -3
  109. package/dist/extensions/deployment-config.js +125 -28
  110. package/dist/extensions/display-name.d.ts +22 -0
  111. package/dist/extensions/display-name.js +77 -0
  112. package/dist/extensions/extensions.module.js +6 -0
  113. package/dist/extensions/host.d.ts +26 -1
  114. package/dist/extensions/host.js +82 -9
  115. package/dist/extensions/org-config.controller.d.ts +11 -3
  116. package/dist/extensions/org-config.controller.js +14 -6
  117. package/dist/extensions/org-config.d.ts +6 -0
  118. package/dist/extensions/org-config.js +12 -13
  119. package/dist/extensions/package.d.ts +14 -1
  120. package/dist/extensions/package.js +51 -2
  121. package/dist/extensions/permission-view.d.ts +3 -0
  122. package/dist/extensions/point-defaults.controller.d.ts +34 -0
  123. package/dist/extensions/point-defaults.controller.js +71 -0
  124. package/dist/extensions/point-defaults.d.ts +64 -0
  125. package/dist/extensions/point-defaults.js +206 -0
  126. package/dist/extensions/registry.d.ts +1 -1
  127. package/dist/extensions/registry.js +2 -0
  128. package/dist/http/api.module.js +8 -0
  129. package/dist/http/nest-logger.d.ts +6 -5
  130. package/dist/http/nest-logger.js +24 -10
  131. package/dist/identity/auth.controller.js +9 -5
  132. package/dist/identity/cookies.d.ts +20 -5
  133. package/dist/identity/cookies.js +44 -6
  134. package/dist/identity/me.controller.d.ts +5 -1
  135. package/dist/identity/me.controller.js +12 -3
  136. package/dist/identity/sessions.d.ts +12 -0
  137. package/dist/identity/sessions.js +45 -0
  138. package/dist/identity/settings.js +1 -1
  139. package/dist/index.d.ts +1 -1
  140. package/dist/infra/config/kernel-config.service.js +6 -0
  141. package/dist/infra/database/database.module.js +1 -0
  142. package/dist/infra/events/kernel-events.d.ts +8 -0
  143. package/dist/infra/lifecycle/process-guards.js +4 -0
  144. package/dist/infra/logging/logging.module.d.ts +4 -2
  145. package/dist/infra/logging/logging.module.js +24 -6
  146. package/dist/infra/queue/redis-connection.d.ts +6 -0
  147. package/dist/infra/queue/redis-connection.js +7 -0
  148. package/dist/infra/redis/redis.module.js +6 -2
  149. package/dist/infra/tokens.d.ts +6 -1
  150. package/dist/infra/tokens.js +6 -1
  151. package/dist/lifecycle/kernel-lifecycle.js +5 -0
  152. package/dist/log.d.ts +13 -1
  153. package/dist/log.js +93 -17
  154. package/dist/models/purposes.d.ts +23 -0
  155. package/dist/models/purposes.js +31 -10
  156. package/dist/orgs/offboarding.d.ts +3 -1
  157. package/dist/orgs/offboarding.js +26 -16
  158. package/dist/orgs/settings.service.d.ts +7 -2
  159. package/dist/orgs/settings.service.js +15 -4
  160. package/dist/roles/worker.module.js +6 -0
  161. package/dist/runtime/context/build.d.ts +15 -13
  162. package/dist/runtime/context/build.js +45 -27
  163. package/dist/runtime/context/claude-tools.d.ts +3 -1
  164. package/dist/runtime/context/claude-tools.js +12 -12
  165. package/dist/runtime/context/compaction.js +4 -0
  166. package/dist/runtime/context/legacy-text.d.ts +22 -0
  167. package/dist/runtime/context/legacy-text.js +37 -0
  168. package/dist/runtime/context/skills.d.ts +29 -11
  169. package/dist/runtime/context/skills.js +37 -30
  170. package/dist/runtime/langgraph-loop.js +3 -2
  171. package/dist/runtime/loop.d.ts +5 -4
  172. package/dist/runtime/maintenance.d.ts +4 -2
  173. package/dist/runtime/maintenance.js +17 -5
  174. package/dist/runtime/model-call.d.ts +17 -1
  175. package/dist/runtime/model-call.js +4 -3
  176. package/dist/runtime/providers.d.ts +6 -0
  177. package/dist/runtime/providers.js +8 -0
  178. package/dist/runtime/run-finish.d.ts +1 -1
  179. package/dist/runtime/run-queue.d.ts +28 -5
  180. package/dist/runtime/run-queue.js +131 -35
  181. package/dist/runtime/run-worker.d.ts +2 -2
  182. package/dist/runtime/run-worker.js +20 -7
  183. package/dist/runtime/runtime.module.js +8 -3
  184. package/dist/runtime/step-log.d.ts +6 -0
  185. package/dist/runtime/step-log.js +16 -2
  186. package/dist/runtime/subagents/definition.d.ts +1 -6
  187. package/dist/runtime/subagents/definition.js +14 -13
  188. package/dist/runtime/subagents/dispatch.d.ts +2 -2
  189. package/dist/runtime/subagents/dispatch.js +2 -2
  190. package/dist/runtime/subagents/org-agents.js +2 -1
  191. package/dist/runtime/subagents/roster.d.ts +1 -1
  192. package/dist/runtime/subagents/roster.js +1 -1
  193. package/dist/runtime/subagents/skill-fork.d.ts +2 -2
  194. package/dist/runtime/subagents/skill-fork.js +2 -2
  195. package/dist/runtime/subagents/subagents.service.d.ts +3 -3
  196. package/dist/runtime/subagents/subagents.service.js +3 -3
  197. package/dist/runtime/usage-report.d.ts +2 -1
  198. package/dist/runtime/usage-report.js +2 -1
  199. package/dist/runtime/waits.d.ts +13 -7
  200. package/dist/runtime/waits.js +42 -16
  201. package/dist/sandbox/sandbox.module.js +6 -1
  202. package/dist/sandbox/sandbox.service.d.ts +61 -3
  203. package/dist/sandbox/sandbox.service.js +141 -7
  204. package/dist/skills/catalog.d.ts +4 -4
  205. package/dist/skills/catalog.js +4 -4
  206. package/dist/skills/changes.service.d.ts +2 -2
  207. package/dist/skills/changes.service.js +5 -4
  208. package/dist/skills/invocation-keys.d.ts +1 -1
  209. package/dist/skills/invocation-keys.js +1 -1
  210. package/dist/skills/invoke.d.ts +1 -1
  211. package/dist/skills/invoke.js +5 -4
  212. package/dist/tools/approvals.service.d.ts +6 -0
  213. package/dist/tools/approvals.service.js +21 -1
  214. package/dist/tools/builtin-names.d.ts +91 -0
  215. package/dist/tools/builtin-names.js +143 -0
  216. package/dist/tools/builtins/agent.d.ts +7 -7
  217. package/dist/tools/builtins/agent.js +7 -11
  218. package/dist/tools/builtins/ask-user.d.ts +1 -1
  219. package/dist/tools/builtins/ask-user.js +2 -2
  220. package/dist/tools/builtins/builtin-tools.d.ts +17 -3
  221. package/dist/tools/builtins/builtin-tools.js +36 -4
  222. package/dist/tools/builtins/read-tool-output.d.ts +1 -1
  223. package/dist/tools/builtins/read-tool-output.js +2 -2
  224. package/dist/tools/builtins/sandbox/bash.d.ts +4 -4
  225. package/dist/tools/builtins/sandbox/bash.js +259 -29
  226. package/dist/tools/builtins/sandbox/common.d.ts +30 -7
  227. package/dist/tools/builtins/sandbox/common.js +9 -5
  228. package/dist/tools/builtins/sandbox/deliver.d.ts +1 -1
  229. package/dist/tools/builtins/sandbox/deliver.js +12 -12
  230. package/dist/tools/builtins/sandbox/edit.d.ts +1 -1
  231. package/dist/tools/builtins/sandbox/edit.js +41 -30
  232. package/dist/tools/builtins/sandbox/file-state.d.ts +34 -0
  233. package/dist/tools/builtins/sandbox/file-state.js +34 -0
  234. package/dist/tools/builtins/sandbox/glob-match.d.ts +1 -1
  235. package/dist/tools/builtins/sandbox/glob-match.js +1 -1
  236. package/dist/tools/builtins/sandbox/glob.d.ts +1 -1
  237. package/dist/tools/builtins/sandbox/glob.js +4 -4
  238. package/dist/tools/builtins/sandbox/grep.d.ts +7 -4
  239. package/dist/tools/builtins/sandbox/grep.js +147 -30
  240. package/dist/tools/builtins/sandbox/index.d.ts +3 -1
  241. package/dist/tools/builtins/sandbox/index.js +7 -1
  242. package/dist/tools/builtins/sandbox/read.d.ts +2 -2
  243. package/dist/tools/builtins/sandbox/read.js +21 -14
  244. package/dist/tools/builtins/sandbox/task-output.d.ts +63 -0
  245. package/dist/tools/builtins/sandbox/task-output.js +304 -0
  246. package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
  247. package/dist/tools/builtins/sandbox/task-stop.js +112 -0
  248. package/dist/tools/builtins/sandbox/write.d.ts +1 -1
  249. package/dist/tools/builtins/sandbox/write.js +37 -33
  250. package/dist/tools/builtins/skill-load.d.ts +3 -3
  251. package/dist/tools/builtins/skill-load.js +32 -16
  252. package/dist/tools/builtins/skill-write.d.ts +4 -4
  253. package/dist/tools/builtins/skill-write.js +10 -10
  254. package/dist/tools/builtins/tool-search.d.ts +13 -9
  255. package/dist/tools/builtins/tool-search.js +25 -14
  256. package/dist/tools/builtins/web/errors.d.ts +8 -0
  257. package/dist/tools/builtins/web/errors.js +53 -0
  258. package/dist/tools/builtins/web/extract.d.ts +62 -0
  259. package/dist/tools/builtins/web/extract.js +267 -0
  260. package/dist/tools/builtins/web/fetch.d.ts +55 -0
  261. package/dist/tools/builtins/web/fetch.js +174 -0
  262. package/dist/tools/builtins/web/index.d.ts +13 -0
  263. package/dist/tools/builtins/web/index.js +12 -0
  264. package/dist/tools/builtins/web/page-cache.d.ts +25 -0
  265. package/dist/tools/builtins/web/page-cache.js +77 -0
  266. package/dist/tools/builtins/web/search.d.ts +25 -0
  267. package/dist/tools/builtins/web/search.js +209 -0
  268. package/dist/tools/builtins/web/url.d.ts +17 -0
  269. package/dist/tools/builtins/web/url.js +43 -0
  270. package/dist/tools/connectors.service.js +4 -4
  271. package/dist/tools/display-names.d.ts +9 -0
  272. package/dist/tools/display-names.js +24 -0
  273. package/dist/tools/drift.js +5 -5
  274. package/dist/tools/elicitation.js +1 -1
  275. package/dist/tools/info.d.ts +20 -4
  276. package/dist/tools/info.js +55 -32
  277. package/dist/tools/legacy-data.d.ts +34 -0
  278. package/dist/tools/legacy-data.js +76 -0
  279. package/dist/tools/legacy-keys.d.ts +51 -0
  280. package/dist/tools/legacy-keys.js +115 -0
  281. package/dist/tools/naming.d.ts +1 -5
  282. package/dist/tools/naming.js +1 -7
  283. package/dist/tools/oauth/flow-paths.d.ts +1 -1
  284. package/dist/tools/oauth/flow-paths.js +1 -1
  285. package/dist/tools/outputs.js +1 -1
  286. package/dist/tools/pipeline.d.ts +13 -7
  287. package/dist/tools/pipeline.js +112 -39
  288. package/dist/tools/policy.d.ts +1 -14
  289. package/dist/tools/policy.js +13 -19
  290. package/dist/tools/questions.controller.d.ts +1 -1
  291. package/dist/tools/questions.controller.js +1 -1
  292. package/dist/tools/questions.service.d.ts +1 -1
  293. package/dist/tools/registry.d.ts +38 -24
  294. package/dist/tools/registry.js +54 -47
  295. package/dist/tools/resources.d.ts +1 -1
  296. package/dist/tools/resources.js +1 -1
  297. package/dist/tools/tools.module.js +13 -1
  298. package/dist/tools/validate.js +1 -1
  299. package/package.json +3 -3
  300. package/src/migrations/0063_tool_keys_claude_code.sql +220 -0
  301. package/src/migrations/0064_account_last_org.sql +7 -0
  302. package/src/migrations/0065_conversation_archive.sql +8 -0
  303. package/src/migrations/0066_extension_display_name.sql +4 -0
  304. package/src/migrations/0067_agent_profiles.sql +20 -0
  305. package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
  306. package/src/migrations/0069_attachments_agent_idx.sql +3 -0
  307. package/src/migrations/0070_background_tasks.sql +37 -0
  308. package/src/migrations/0071_background_tasks_reserve.sql +6 -0
  309. package/src/migrations/0072_background_tasks_stopping.sql +5 -0
  310. package/src/migrations/0073_org_extension_point_defaults.sql +11 -0
  311. package/dist/tools/builtins/sandbox/names.d.ts +0 -7
  312. package/dist/tools/builtins/sandbox/names.js +0 -20
@@ -0,0 +1,53 @@
1
+ import type { KernelConfig } from "../config/schema.js";
2
+ import { EventLog } from "../conversations/event-log.js";
3
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
4
+ import { LockService } from "../infra/lock/lock.service.js";
5
+ import type { KernelLogger } from "../log.js";
6
+ import { SandboxService } from "../sandbox/sandbox.service.js";
7
+ import { BackgroundTaskFinisher } from "./finish.js";
8
+ import { BackgroundTaskStore } from "./store.js";
9
+ /** The poll of the running background tasks (`@LockedInterval`, every `pollSeconds`). */
10
+ export declare const BACKGROUND_POLL_JOB = "background-task-poll";
11
+ /** The daily prune of finished background tasks older than `tools.outputRetentionDays`. */
12
+ export declare const BACKGROUND_PRUNE_JOB = "background-task-prune";
13
+ /**
14
+ * A reservation (`sid` null) older than this has lost its launch. The slowest legitimate launch
15
+ * is the pipeline's own limit for a background `Bash` call ({@link LAUNCH_EXEC_TIMEOUT_MS} plus
16
+ * {@link sandboxBaseTimeoutMs}: the tool timeout, `sandbox.lockWaitSeconds` and the setup margin),
17
+ * so twice that (never under 2 minutes) leaves a wide margin: only a launching process that died
18
+ * leaves a reservation this old.
19
+ */
20
+ export declare const launchAbandonedAfterMs: (cfg: KernelConfig) => number;
21
+ /**
22
+ * The worker's background task jobs (spec 2026-11-05-background-bash-design §4.3), each once per
23
+ * tick across replicas:
24
+ * - `background-task-poll` (every `sandbox.background.pollSeconds`): for every sandbox with
25
+ * running tasks, one exec through `SandboxService.ifRunning` (which never creates or resumes a
26
+ * sandbox) reports each task's exit file and liveness; ended tasks finish through
27
+ * {@link BackgroundTaskFinisher}. Each poll takes and releases a lease, so `last_used_at` stays
28
+ * fresh and a sandbox with running tasks is never idle-suspended;
29
+ * - `background-task-prune` (daily): deletes finished tasks older than `tools.outputRetentionDays`.
30
+ */
31
+ export declare class BackgroundTaskPollJob {
32
+ private readonly store;
33
+ private readonly finisher;
34
+ private readonly eventLog;
35
+ private readonly sandbox;
36
+ readonly config: KernelConfigService;
37
+ readonly lockService: LockService;
38
+ readonly log: KernelLogger;
39
+ constructor(store: Pick<BackgroundTaskStore, "runningStarted" | "pruneFinished" | "failAbandonedLaunches">, finisher: BackgroundTaskFinisher, eventLog: Pick<EventLog, "publishChanged">, sandbox: Pick<SandboxService, "available" | "ifRunning">, config: KernelConfigService, lockService: LockService, log: KernelLogger);
40
+ poll(signal?: AbortSignal): Promise<void>;
41
+ /**
42
+ * One poll of every running task (the body of {@link poll}); returns how many tasks finished.
43
+ * A sandbox whose poll fails (busy, a provider error) is logged and tried again next tick.
44
+ * First, reservations abandoned by a launch that died ({@link launchAbandonedAfterMs}) are
45
+ * failed, so they stop holding a slot of `sandbox.background.maxTasks` (not counted in the
46
+ * result: they never started), with a `changed: "background"` frame to their conversations.
47
+ */
48
+ pollOnce(signal?: AbortSignal): Promise<number>;
49
+ /** Polls the tasks of one sandbox instance; returns how many finished. */
50
+ private pollSandbox;
51
+ /** Deletes the finished tasks older than `tools.outputRetentionDays` (their rows only). */
52
+ prune(): Promise<void>;
53
+ }
@@ -0,0 +1,182 @@
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 { EventLog } from "../conversations/event-log.js";
12
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
13
+ import { LockService } from "../infra/lock/lock.service.js";
14
+ import { LockedCron } from "../infra/schedule/locked-cron.decorator.js";
15
+ import { LockedInterval } from "../infra/schedule/locked-interval.js";
16
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
17
+ import { SandboxService } from "../sandbox/sandbox.service.js";
18
+ import { sandboxBaseTimeoutMs } from "../tools/builtins/sandbox/common.js";
19
+ import { BackgroundTaskFinisher } from "./finish.js";
20
+ import { LAUNCH_EXEC_TIMEOUT_MS } from "./launch.js";
21
+ import { parsePollOutput, polledAs, pollScript } from "./status.js";
22
+ import { notRunningAs, RECREATED } from "./stop.js";
23
+ import { BackgroundTaskStore } from "./store.js";
24
+ /** The poll of the running background tasks (`@LockedInterval`, every `pollSeconds`). */
25
+ export const BACKGROUND_POLL_JOB = "background-task-poll";
26
+ /** The daily prune of finished background tasks older than `tools.outputRetentionDays`. */
27
+ export const BACKGROUND_PRUNE_JOB = "background-task-prune";
28
+ /** The least age at which a reservation counts as abandoned, whatever the config. */
29
+ const LAUNCH_ABANDONED_FLOOR_MS = 120_000;
30
+ /**
31
+ * A reservation (`sid` null) older than this has lost its launch. The slowest legitimate launch
32
+ * is the pipeline's own limit for a background `Bash` call ({@link LAUNCH_EXEC_TIMEOUT_MS} plus
33
+ * {@link sandboxBaseTimeoutMs}: the tool timeout, `sandbox.lockWaitSeconds` and the setup margin),
34
+ * so twice that (never under 2 minutes) leaves a wide margin: only a launching process that died
35
+ * leaves a reservation this old.
36
+ */
37
+ export const launchAbandonedAfterMs = (cfg) => Math.max(LAUNCH_ABANDONED_FLOOR_MS, 2 * (LAUNCH_EXEC_TIMEOUT_MS + sandboxBaseTimeoutMs(cfg)));
38
+ /** The time limit of one sandbox's poll exec. */
39
+ const POLL_EXEC_TIMEOUT_MS = 30_000;
40
+ /** The most output one poll exec keeps (a line per task, with a tail when it ended). */
41
+ const POLL_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
42
+ /**
43
+ * The worker's background task jobs (spec 2026-11-05-background-bash-design §4.3), each once per
44
+ * tick across replicas:
45
+ * - `background-task-poll` (every `sandbox.background.pollSeconds`): for every sandbox with
46
+ * running tasks, one exec through `SandboxService.ifRunning` (which never creates or resumes a
47
+ * sandbox) reports each task's exit file and liveness; ended tasks finish through
48
+ * {@link BackgroundTaskFinisher}. Each poll takes and releases a lease, so `last_used_at` stays
49
+ * fresh and a sandbox with running tasks is never idle-suspended;
50
+ * - `background-task-prune` (daily): deletes finished tasks older than `tools.outputRetentionDays`.
51
+ */
52
+ let BackgroundTaskPollJob = class BackgroundTaskPollJob {
53
+ store;
54
+ finisher;
55
+ eventLog;
56
+ sandbox;
57
+ config;
58
+ lockService;
59
+ log;
60
+ constructor(store, finisher, eventLog, sandbox, config, lockService, log) {
61
+ this.store = store;
62
+ this.finisher = finisher;
63
+ this.eventLog = eventLog;
64
+ this.sandbox = sandbox;
65
+ this.config = config;
66
+ this.lockService = lockService;
67
+ this.log = log;
68
+ }
69
+ async poll(signal) {
70
+ await this.pollOnce(signal);
71
+ }
72
+ /**
73
+ * One poll of every running task (the body of {@link poll}); returns how many tasks finished.
74
+ * A sandbox whose poll fails (busy, a provider error) is logged and tried again next tick.
75
+ * First, reservations abandoned by a launch that died ({@link launchAbandonedAfterMs}) are
76
+ * failed, so they stop holding a slot of `sandbox.background.maxTasks` (not counted in the
77
+ * result: they never started), with a `changed: "background"` frame to their conversations.
78
+ */
79
+ async pollOnce(signal = new AbortController().signal) {
80
+ const abandoned = await this.store.failAbandonedLaunches(new Date(Date.now() - launchAbandonedAfterMs(this.config.get())));
81
+ if (abandoned.length > 0) {
82
+ this.log.warn("abandoned background launches failed", { count: abandoned.length });
83
+ // a client that listed the reservation as running refreshes (the owner and, for a
84
+ // sub-agent's task, the conversation that owns the sandbox); a failed publish only logs
85
+ const conversations = new Set(abandoned.flatMap((t) => [t.conversationId, t.sandboxConversationId]));
86
+ for (const c of conversations)
87
+ await this.eventLog.publishChanged(c, "background");
88
+ }
89
+ if (!this.sandbox.available)
90
+ return 0;
91
+ const groups = new Map();
92
+ for (const t of await this.store.runningStarted()) {
93
+ if (t.sid === null || t.sandboxCreatedAt === null)
94
+ continue;
95
+ const key = `${t.sandboxConversationId} ${t.sandboxCreatedAt.getTime()}`;
96
+ groups.set(key, [...(groups.get(key) ?? []), t]);
97
+ }
98
+ let finished = 0;
99
+ for (const tasks of groups.values()) {
100
+ if (signal.aborted)
101
+ break;
102
+ try {
103
+ finished += await this.pollSandbox(tasks, signal);
104
+ }
105
+ catch (err) {
106
+ if (signal.aborted)
107
+ break;
108
+ this.log.warn("cannot poll background tasks; retrying next tick", {
109
+ conversationId: tasks[0]?.sandboxConversationId,
110
+ err,
111
+ });
112
+ }
113
+ }
114
+ if (finished > 0)
115
+ this.log.info("background tasks finished", { count: finished });
116
+ return finished;
117
+ }
118
+ /** Polls the tasks of one sandbox instance; returns how many finished. */
119
+ async pollSandbox(tasks, signal) {
120
+ const [first] = tasks;
121
+ const r = await this.sandbox.ifRunning(first.sandboxConversationId, { createdAt: first.sandboxCreatedAt }, async (p, h, sig) => {
122
+ const res = await p.exec(h, {
123
+ command: pollScript(tasks.map((t) => ({ id: t.id, sid: t.sid }))),
124
+ cwd: "/workspace",
125
+ timeoutMs: POLL_EXEC_TIMEOUT_MS,
126
+ maxOutputBytes: POLL_MAX_OUTPUT_BYTES,
127
+ signal: sig,
128
+ });
129
+ if (res.exitCode !== 0 || res.truncated) {
130
+ throw new Error(`the poll exec failed (exit ${res.exitCode}${res.truncated ? ", truncated" : ""}): ${res.stderr.slice(0, 500)}`);
131
+ }
132
+ return parsePollOutput(res.stdout);
133
+ }, signal);
134
+ const now = new Date();
135
+ let n = 0;
136
+ for (const t of tasks) {
137
+ let to;
138
+ let lastLines = "";
139
+ if (r.kind === "ran") {
140
+ const report = r.value.get(t.id);
141
+ // a task the exec did not report is checked again next tick
142
+ to = report ? polledAs(t, report, now) : null;
143
+ lastLines = report?.lastLines ?? "";
144
+ }
145
+ else if (r.kind === "not_running") {
146
+ // a suspended sandbox may keep the processes (E2B's pause): killed after its next resume
147
+ to = notRunningAs(r.status, r.status === "suspended");
148
+ }
149
+ else {
150
+ to = RECREATED;
151
+ }
152
+ // a stop that claimed the task after the read above finishes it itself
153
+ if (to && (await this.finisher.finish(t, to, lastLines, false, { unclaimed: true })))
154
+ n++;
155
+ }
156
+ return n;
157
+ }
158
+ /** Deletes the finished tasks older than `tools.outputRetentionDays` (their rows only). */
159
+ async prune() {
160
+ const days = this.config.get().tools.outputRetentionDays;
161
+ const n = await this.store.pruneFinished(new Date(Date.now() - days * 86_400_000));
162
+ if (n > 0)
163
+ this.log.info("finished background tasks pruned", { count: n });
164
+ }
165
+ };
166
+ __decorate([
167
+ LockedInterval(BACKGROUND_POLL_JOB, (cfg) => cfg.sandbox.background.pollSeconds * 1000)
168
+ ], BackgroundTaskPollJob.prototype, "poll", null);
169
+ __decorate([
170
+ LockedCron("30 3 * * *", { name: BACKGROUND_PRUNE_JOB })
171
+ ], BackgroundTaskPollJob.prototype, "prune", null);
172
+ BackgroundTaskPollJob = __decorate([
173
+ Injectable(),
174
+ __param(0, Inject(BackgroundTaskStore)),
175
+ __param(1, Inject(BackgroundTaskFinisher)),
176
+ __param(2, Inject(EventLog)),
177
+ __param(3, Inject(SandboxService)),
178
+ __param(4, Inject(KernelConfigService)),
179
+ __param(5, Inject(LockService)),
180
+ __param(6, Inject(KERNEL_LOGGER))
181
+ ], BackgroundTaskPollJob);
182
+ export { BackgroundTaskPollJob };
@@ -0,0 +1,23 @@
1
+ import type { EventPayload } from "../conversations/events.js";
2
+ /** `42s`, `2m05s`, `1h02m05s`. */
3
+ export declare function formatElapsed(ms: number): string;
4
+ /** Why a task was lost, as the model reads it (an unknown reason as is). */
5
+ export declare const lostReasonText: (reason: string) => string;
6
+ type Finished = EventPayload<"background_task_finished">;
7
+ /**
8
+ * The model-visible text of a `background_task_finished` event, rendered as a system message (spec
9
+ * 2026-11-05-background-bash-design §2.4):
10
+ *
11
+ * ```
12
+ * Background task bt_k3f9a2 ("Run the test suite") completed with exit code 1 after 4m12s.
13
+ * Last lines:
14
+ * …
15
+ * Full output: /workspace/.capybara/tasks/bt_k3f9a2.out
16
+ * ```
17
+ *
18
+ * Without an exit code the first line has no "with exit code"; without output the last lines are
19
+ * `Last lines: (none)`. A finish a tool call already reported (`reported`) is one short line.
20
+ * Read only from the payload, so a replay renders it identically.
21
+ */
22
+ export declare function renderBackgroundFinished(p: Finished): string;
23
+ export {};
@@ -0,0 +1,48 @@
1
+ /** `42s`, `2m05s`, `1h02m05s`. */
2
+ export function formatElapsed(ms) {
3
+ const s = Math.max(0, Math.floor(ms / 1000));
4
+ const pad = (n) => String(n).padStart(2, "0");
5
+ if (s < 60)
6
+ return `${s}s`;
7
+ if (s < 3600)
8
+ return `${Math.floor(s / 60)}m${pad(s % 60)}s`;
9
+ return `${Math.floor(s / 3600)}h${pad(Math.floor((s % 3600) / 60))}m${pad(s % 60)}s`;
10
+ }
11
+ const LOST_REASONS = {
12
+ sandbox_suspended: "the sandbox was suspended",
13
+ sandbox_gone: "the sandbox is gone",
14
+ sandbox_recreated: "the sandbox was recreated",
15
+ process_gone: "its processes ended without an exit code",
16
+ launch_abandoned: "its launch was interrupted",
17
+ };
18
+ /** Why a task was lost, as the model reads it (an unknown reason as is). */
19
+ export const lostReasonText = (reason) => LOST_REASONS[reason] ?? reason;
20
+ const statusWord = (status) => status === "timed_out" ? "timed out" : status;
21
+ /**
22
+ * The model-visible text of a `background_task_finished` event, rendered as a system message (spec
23
+ * 2026-11-05-background-bash-design §2.4):
24
+ *
25
+ * ```
26
+ * Background task bt_k3f9a2 ("Run the test suite") completed with exit code 1 after 4m12s.
27
+ * Last lines:
28
+ * …
29
+ * Full output: /workspace/.capybara/tasks/bt_k3f9a2.out
30
+ * ```
31
+ *
32
+ * Without an exit code the first line has no "with exit code"; without output the last lines are
33
+ * `Last lines: (none)`. A finish a tool call already reported (`reported`) is one short line.
34
+ * Read only from the payload, so a replay renders it identically.
35
+ */
36
+ export function renderBackgroundFinished(p) {
37
+ const head = `Background task ${p.taskId} (${JSON.stringify(p.description)}) ${statusWord(p.status)}`;
38
+ if (p.reported)
39
+ return `${head}; already reported.`;
40
+ const lost = p.lostReason === undefined ? "" : ` (${lostReasonText(p.lostReason)})`;
41
+ const exit = p.exitCode === null ? "" : ` with exit code ${p.exitCode}`;
42
+ const lines = p.lastLines.replace(/\n+$/, "");
43
+ return [
44
+ `${head}${lost}${exit} after ${formatElapsed(p.elapsedMs)}.`,
45
+ lines === "" ? "Last lines: (none)" : `Last lines:\n${lines}`,
46
+ `Full output: ${p.outputPath}`,
47
+ ].join("\n");
48
+ }
@@ -0,0 +1,37 @@
1
+ import type { SandboxHandle, SandboxProvider } from "@capybara-hq/plugin-sdk";
2
+ /**
3
+ * A background task's session inside the sandbox (spec 2026-11-05-background-bash-design §4.2):
4
+ * every process of a task is in the session `setsid` started, so liveness is `pgrep -s <sid>` and
5
+ * stopping is `pkill -s <sid>` (never `kill -0` or `/proc`, which disagree between sandbox-fake and
6
+ * Linux).
7
+ */
8
+ /** A session id (the pid `setsid` started). */
9
+ export declare const SID_RE: RegExp;
10
+ /** The time limit of one kill of a session ({@link killSession}). */
11
+ export declare const KILL_SESSION_TIMEOUT_MS = 10000;
12
+ /** Throws unless `id` is a task id and `sid` a session id (both go into scripts unquoted). */
13
+ export declare function assertTaskRef(id: string, sid: string): void;
14
+ /**
15
+ * A shell fragment that kills every process of the session `sid` with SIGKILL, repeated until the
16
+ * session is empty (pkill exits 1: no process left): a process may fork between pkill's listing
17
+ * and its signals, and the child would live on. Sets `ks` to 0 when the session was confirmed
18
+ * empty, else to pkill's failing status (3 when it was still not empty after 10 tries). Uses only
19
+ * the variables `ks`, `kc` and `ki`. `sid` must be a session id.
20
+ */
21
+ export declare function killSessionScript(sid: string): string;
22
+ /**
23
+ * Kills every process of the session `sid` ({@link killSessionScript}) in an exec of its own. Any
24
+ * pkill status other than 0 and 1 is a failure. Runs with a fresh signal of its own (a command
25
+ * must be killed even when the caller was just aborted). True when the session was confirmed
26
+ * empty; false when it could not be (an error is never thrown).
27
+ */
28
+ export declare function killSession(p: SandboxProvider, h: SandboxHandle, sid: string): Promise<boolean>;
29
+ /** The longest `lastLines` of a finished task (spec §2.4). */
30
+ export declare const LAST_LINES_MAX_CHARS = 4000;
31
+ /**
32
+ * A shell pipeline printing the last 20 lines of task `id`'s output (at most {@link TAIL_BYTES}),
33
+ * base64 on one line (empty when there is no output file). Relative to `/workspace`.
34
+ */
35
+ export declare function tailCommand(id: string): string;
36
+ /** Decodes a {@link tailCommand} result into `lastLines`: its end, at most 4 000 characters. */
37
+ export declare function decodeTail(b64: string): string;
@@ -0,0 +1,66 @@
1
+ import { TASK_ID_RE, TASKS_DIR } from "./launch.js";
2
+ /**
3
+ * A background task's session inside the sandbox (spec 2026-11-05-background-bash-design §4.2):
4
+ * every process of a task is in the session `setsid` started, so liveness is `pgrep -s <sid>` and
5
+ * stopping is `pkill -s <sid>` (never `kill -0` or `/proc`, which disagree between sandbox-fake and
6
+ * Linux).
7
+ */
8
+ /** A session id (the pid `setsid` started). */
9
+ export const SID_RE = /^[1-9][0-9]*$/;
10
+ /** The time limit of one kill of a session ({@link killSession}). */
11
+ export const KILL_SESSION_TIMEOUT_MS = 10_000;
12
+ /** Throws unless `id` is a task id and `sid` a session id (both go into scripts unquoted). */
13
+ export function assertTaskRef(id, sid) {
14
+ if (!TASK_ID_RE.test(id))
15
+ throw new Error(`invalid background task id: ${JSON.stringify(id)}`);
16
+ if (!SID_RE.test(sid))
17
+ throw new Error(`invalid background task session: ${JSON.stringify(sid)}`);
18
+ }
19
+ /**
20
+ * A shell fragment that kills every process of the session `sid` with SIGKILL, repeated until the
21
+ * session is empty (pkill exits 1: no process left): a process may fork between pkill's listing
22
+ * and its signals, and the child would live on. Sets `ks` to 0 when the session was confirmed
23
+ * empty, else to pkill's failing status (3 when it was still not empty after 10 tries). Uses only
24
+ * the variables `ks`, `kc` and `ki`. `sid` must be a session id.
25
+ */
26
+ export function killSessionScript(sid) {
27
+ if (!SID_RE.test(sid))
28
+ throw new Error(`invalid background task session: ${JSON.stringify(sid)}`);
29
+ return `ks=3; for ki in 1 2 3 4 5 6 7 8 9 10; do pkill -KILL -s ${sid}; kc=$?; if [ "$kc" -eq 1 ]; then ks=0; break; fi; if [ "$kc" -ne 0 ]; then ks=$kc; break; fi; sleep 0.2; done`;
30
+ }
31
+ /**
32
+ * Kills every process of the session `sid` ({@link killSessionScript}) in an exec of its own. Any
33
+ * pkill status other than 0 and 1 is a failure. Runs with a fresh signal of its own (a command
34
+ * must be killed even when the caller was just aborted). True when the session was confirmed
35
+ * empty; false when it could not be (an error is never thrown).
36
+ */
37
+ export async function killSession(p, h, sid) {
38
+ if (!SID_RE.test(sid))
39
+ return false;
40
+ const r = await p
41
+ .exec(h, {
42
+ command: `${killSessionScript(sid)}; exit "$ks"`,
43
+ cwd: "/workspace",
44
+ timeoutMs: KILL_SESSION_TIMEOUT_MS,
45
+ maxOutputBytes: 4096,
46
+ signal: AbortSignal.timeout(KILL_SESSION_TIMEOUT_MS),
47
+ })
48
+ .catch(() => undefined);
49
+ return r?.exitCode === 0;
50
+ }
51
+ /** The most bytes of a task's output end a tail reads (the event keeps at most 4 000 characters). */
52
+ const TAIL_BYTES = 12_000;
53
+ /** The longest `lastLines` of a finished task (spec §2.4). */
54
+ export const LAST_LINES_MAX_CHARS = 4000;
55
+ /**
56
+ * A shell pipeline printing the last 20 lines of task `id`'s output (at most {@link TAIL_BYTES}),
57
+ * base64 on one line (empty when there is no output file). Relative to `/workspace`.
58
+ */
59
+ export function tailCommand(id) {
60
+ return `{ tail -n 20 ${TASKS_DIR}/${id}.out 2>/dev/null | tail -c ${TAIL_BYTES} | base64 | tr -d '\\n'; }`;
61
+ }
62
+ /** Decodes a {@link tailCommand} result into `lastLines`: its end, at most 4 000 characters. */
63
+ export function decodeTail(b64) {
64
+ const text = Buffer.from(b64.trim(), "base64").toString("utf8");
65
+ return text.length > LAST_LINES_MAX_CHARS ? text.slice(-LAST_LINES_MAX_CHARS) : text;
66
+ }
@@ -0,0 +1,35 @@
1
+ import type { BackgroundFinish } from "./finish.js";
2
+ import type { BackgroundTaskRow } from "./store.js";
3
+ /** What the poll exec reports for one task. */
4
+ export interface TaskReport {
5
+ /** The exit file's code; null while there is none. */
6
+ exitCode: number | null;
7
+ /**
8
+ * Whether the session still has a process: `pgrep -s <sid>`, or, for a task whose command
9
+ * exited, whether its leftover processes could not be confirmed killed.
10
+ */
11
+ alive: boolean;
12
+ /** The last lines (decoded), read when the task exited or no process was left. */
13
+ lastLines: string;
14
+ }
15
+ /**
16
+ * The poll exec of one sandbox: for every task one line `<id> <exit code or -> <1|0> <tail or ->`.
17
+ * Liveness is asked before the exit file: a task ending in between is seen by its exit file, never
18
+ * as a task without process and exit file. A task whose command exited while its session still has
19
+ * processes (it left `server &` running) has the session killed in the same exec (so under the
20
+ * caller's lease), and is reported without a process once the session was confirmed empty: the
21
+ * task ends when its command exits, and nothing of it runs on untracked. The tail (base64) is read
22
+ * only for a task that exited or has no process left. Relative to `/workspace`.
23
+ */
24
+ export declare function pollScript(tasks: {
25
+ id: string;
26
+ sid: string;
27
+ }[]): string;
28
+ /** Parses {@link pollScript}'s output; a task missing from it is absent from the map. */
29
+ export declare function parsePollOutput(stdout: string): Map<string, TaskReport>;
30
+ /**
31
+ * How a polled task ends, or null while it runs (spec §4.3). A task whose command exited but whose
32
+ * leftover processes could not be confirmed killed ({@link pollScript}) stays running: the next
33
+ * poll kills them again before it finishes.
34
+ */
35
+ export declare function polledAs(task: BackgroundTaskRow, r: TaskReport, now: Date): BackgroundFinish | null;
@@ -0,0 +1,57 @@
1
+ import { TASK_ID_RE, TASKS_DIR } from "./launch.js";
2
+ import { decodeTail, killSessionScript, SID_RE, tailCommand } from "./session.js";
3
+ import { exitedAs } from "./stop.js";
4
+ /**
5
+ * The poll exec of one sandbox: for every task one line `<id> <exit code or -> <1|0> <tail or ->`.
6
+ * Liveness is asked before the exit file: a task ending in between is seen by its exit file, never
7
+ * as a task without process and exit file. A task whose command exited while its session still has
8
+ * processes (it left `server &` running) has the session killed in the same exec (so under the
9
+ * caller's lease), and is reported without a process once the session was confirmed empty: the
10
+ * task ends when its command exits, and nothing of it runs on untracked. The tail (base64) is read
11
+ * only for a task that exited or has no process left. Relative to `/workspace`.
12
+ */
13
+ export function pollScript(tasks) {
14
+ const lines = tasks.map(({ id, sid }) => {
15
+ if (!TASK_ID_RE.test(id) || !SID_RE.test(sid)) {
16
+ throw new Error(`invalid background task ${JSON.stringify(id)} / ${JSON.stringify(sid)}`);
17
+ }
18
+ const f = `${TASKS_DIR}/${id}`;
19
+ return [
20
+ `if pgrep -s ${sid} >/dev/null 2>&1; then a=1; else a=0; fi`,
21
+ `if [ -s ${f}.exit ]; then c=$(tr -dc '0-9-' < ${f}.exit); else c=-; fi`,
22
+ `[ -n "$c" ] || c=-`,
23
+ `if [ "$c" != - ] && [ "$a" = 1 ]; then ${killSessionScript(sid)}; [ "$ks" = 0 ] && a=0; fi`,
24
+ `if [ "$c" != - ] || [ "$a" = 0 ]; then t=$(${tailCommand(id)}); else t=; fi`,
25
+ `echo "${id} $c $a \${t:--}"`,
26
+ ].join("\n");
27
+ });
28
+ return lines.join("\n");
29
+ }
30
+ /** Parses {@link pollScript}'s output; a task missing from it is absent from the map. */
31
+ export function parsePollOutput(stdout) {
32
+ const out = new Map();
33
+ for (const line of stdout.split("\n")) {
34
+ const m = /^(bt_[a-z2-7]{6}) (-?[0-9]+|-) ([01]) (\S+)$/.exec(line.trim());
35
+ if (!m)
36
+ continue;
37
+ const [, id, code, alive, tail] = m;
38
+ out.set(id, {
39
+ exitCode: code === "-" ? null : Number(code),
40
+ alive: alive === "1",
41
+ lastLines: tail === "-" ? "" : decodeTail(tail),
42
+ });
43
+ }
44
+ return out;
45
+ }
46
+ /**
47
+ * How a polled task ends, or null while it runs (spec §4.3). A task whose command exited but whose
48
+ * leftover processes could not be confirmed killed ({@link pollScript}) stays running: the next
49
+ * poll kills them again before it finishes.
50
+ */
51
+ export function polledAs(task, r, now) {
52
+ if (r.exitCode !== null)
53
+ return r.alive ? null : exitedAs(task, r.exitCode, now);
54
+ if (r.alive)
55
+ return null;
56
+ return { status: "lost", lostReason: "process_gone", killPending: false };
57
+ }
@@ -0,0 +1,62 @@
1
+ import { KernelDb } from "../infra/database/kernel-db.js";
2
+ import type { KernelLogger } from "../log.js";
3
+ import { SandboxService } from "../sandbox/sandbox.service.js";
4
+ import { type BackgroundFinish, BackgroundTaskFinisher } from "./finish.js";
5
+ import { type BackgroundTaskRow, BackgroundTaskStore } from "./store.js";
6
+ /** Why a task is stopped. */
7
+ export type StopReason = "tool" | "person" | "subagent_end" | "conversation_deleted";
8
+ /** How long a stop waits after SIGTERM before SIGKILL (spec §2.3). */
9
+ export declare const STOP_GRACE_MS = 5000;
10
+ /** How a task that exited by itself ends (spec §4.3): exit 137 past its deadline timed out. */
11
+ export declare function exitedAs(task: BackgroundTaskRow, code: number, now: Date): BackgroundFinish;
12
+ /** How a task whose sandbox is not running ends (`ifRunning`'s `not_running` status). */
13
+ export declare function notRunningAs(status: string, killPending: boolean): BackgroundFinish;
14
+ /** A task found in another sandbox instance (a rebuild): its processes are gone with the old one. */
15
+ export declare const RECREATED: BackgroundFinish;
16
+ /**
17
+ * Stops background tasks (spec 2026-11-05-background-bash-design §2.3, §4.3): SIGTERM to the
18
+ * task's session, up to 5 s for it to empty, then SIGKILL until it is empty, all through
19
+ * `SandboxService.ifRunning` (it never creates or resumes a sandbox); the task then finishes as
20
+ * `stopped` with its last 20 lines. A task that had already exited finishes as its exit says. When
21
+ * the sandbox is not running the task finishes as `lost` with `kill_pending` (killed right after a
22
+ * resume); in a rebuilt sandbox as `lost` (`sandbox_recreated`). Processes a task that had
23
+ * already exited left running are killed as well.
24
+ */
25
+ export declare class BackgroundTaskStopper {
26
+ private readonly store;
27
+ private readonly finisher;
28
+ private readonly sandbox;
29
+ private readonly db;
30
+ private readonly log;
31
+ constructor(store: Pick<BackgroundTaskStore, "get" | "runningOwnedBy" | "runningIn" | "claimStop" | "releaseStop">, finisher: BackgroundTaskFinisher, sandbox: Pick<SandboxService, "ifRunning">, db: KernelDb, log: Pick<KernelLogger, "warn">);
32
+ /**
33
+ * Stops `task` and returns its row afterwards. A task that is no longer running, or that another
34
+ * live stop already claimed, is returned as it is now.
35
+ *
36
+ * The stop first claims the task (`stopping_at`), so the poller leaves it alone while the
37
+ * signals take effect and the stop's finish is its only one. A reservation whose launch has not
38
+ * started the command yet (no `sid`) is only claimed: the launch then kills what it starts and
39
+ * finishes the row `stopped`. When the stop fails while the sandbox runs (busy, a provider
40
+ * error, a session that could not be confirmed empty), the claim is given up, the task stays
41
+ * `running` and the error is thrown.
42
+ */
43
+ stop(task: BackgroundTaskRow, why: StopReason, signal?: AbortSignal): Promise<BackgroundTaskRow>;
44
+ /**
45
+ * After the sub-agent run `runId` ended (spec §4.3): stops the running tasks that run started in
46
+ * `conversationId` when it is a sub-agent's conversation (nobody can read or stop them through
47
+ * that run's model after that); any other conversation is left alone. Only that run's tasks: a
48
+ * follow-up run of the same child keeps its own when an older run's end is handled late. Never
49
+ * throws (see {@link stopAll}).
50
+ */
51
+ stopSubagentTasks(conversationId: string, runId: string): Promise<void>;
52
+ /**
53
+ * Before a conversation is deleted: stops every running task in its sandbox (its own and its
54
+ * sub-agents'). Never throws (see {@link stopAll}).
55
+ */
56
+ stopForDeletedConversation(conversationId: string): Promise<void>;
57
+ /**
58
+ * Stops `tasks` side by side. A stop that fails while the sandbox runs leaves its task running
59
+ * (logged): the poller still checks it and it ends at its own deadline at the latest.
60
+ */
61
+ private stopAll;
62
+ }