@capybara-hq/kernel 0.1.0-rc.22 → 0.1.0-rc.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin.controller.d.ts +3 -3
  3. package/dist/admin/admin.controller.js +6 -2
  4. package/dist/admin/admin.module.d.ts +2 -1
  5. package/dist/admin/admin.module.js +7 -3
  6. package/dist/admin/admin.schemas.d.ts +13 -0
  7. package/dist/admin/outbox-dead.controller.d.ts +23 -0
  8. package/dist/admin/outbox-dead.controller.js +62 -0
  9. package/dist/admin/outbox-dead.schemas.d.ts +8 -0
  10. package/dist/admin/outbox-dead.schemas.js +18 -0
  11. package/dist/admin/outbox-dead.service.d.ts +66 -0
  12. package/dist/admin/outbox-dead.service.js +179 -0
  13. package/dist/admin/overview.service.d.ts +40 -5
  14. package/dist/admin/overview.service.js +41 -7
  15. package/dist/agents/from-template.js +1 -1
  16. package/dist/background/finish.d.ts +5 -2
  17. package/dist/background/finish.js +14 -3
  18. package/dist/background/launch.d.ts +44 -5
  19. package/dist/background/launch.js +92 -9
  20. package/dist/background/session.d.ts +14 -2
  21. package/dist/background/session.js +26 -4
  22. package/dist/background/status.d.ts +1 -1
  23. package/dist/background/status.js +3 -3
  24. package/dist/background/stop.d.ts +0 -2
  25. package/dist/background/stop.js +1 -21
  26. package/dist/background/store.d.ts +8 -0
  27. package/dist/background/store.js +24 -0
  28. package/dist/catalog/admin-catalog.controller.d.ts +21 -2
  29. package/dist/catalog/admin-catalog.controller.js +45 -6
  30. package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
  31. package/dist/catalog/admin-catalog.schemas.js +8 -0
  32. package/dist/catalog/catalog-content.d.ts +5 -0
  33. package/dist/catalog/catalog-content.js +1 -0
  34. package/dist/catalog/catalog.module.d.ts +2 -1
  35. package/dist/catalog/catalog.module.js +5 -1
  36. package/dist/catalog/marketplaces.service.d.ts +1 -1
  37. package/dist/catalog/platform-import-update.d.ts +50 -0
  38. package/dist/catalog/platform-import-update.js +116 -0
  39. package/dist/catalog/plugin-import.d.ts +36 -1
  40. package/dist/catalog/plugin-import.js +23 -1
  41. package/dist/catalog/plugin-upgrade.d.ts +18 -0
  42. package/dist/catalog/plugin-upgrade.js +19 -0
  43. package/dist/catalog/requests.service.js +2 -2
  44. package/dist/channels/channels.module.d.ts +5 -2
  45. package/dist/channels/channels.module.js +9 -2
  46. package/dist/channels/deliver-job.d.ts +25 -0
  47. package/dist/channels/deliver-job.js +25 -0
  48. package/dist/channels/inbound.d.ts +1 -1
  49. package/dist/channels/inbound.js +2 -15
  50. package/dist/channels/jobs.d.ts +28 -18
  51. package/dist/channels/jobs.js +63 -26
  52. package/dist/config/schema.d.ts +3 -6
  53. package/dist/config/schema.js +20 -27
  54. package/dist/conversations/agent-tree.d.ts +16 -0
  55. package/dist/conversations/agent-tree.js +36 -0
  56. package/dist/conversations/conversations.service.d.ts +44 -14
  57. package/dist/conversations/conversations.service.js +52 -38
  58. package/dist/conversations/event-log.d.ts +7 -10
  59. package/dist/conversations/event-log.js +24 -15
  60. package/dist/conversations/events.js +2 -1
  61. package/dist/conversations/queue.service.d.ts +8 -3
  62. package/dist/conversations/queue.service.js +37 -13
  63. package/dist/conversations/titles.d.ts +21 -8
  64. package/dist/conversations/titles.js +42 -15
  65. package/dist/db/kernel-schema.d.ts +26 -8
  66. package/dist/devices/devices.service.d.ts +12 -3
  67. package/dist/devices/devices.service.js +30 -9
  68. package/dist/errors.d.ts +15 -0
  69. package/dist/errors.js +15 -0
  70. package/dist/evals/run-replay.js +14 -8
  71. package/dist/extensions/extensions.module.js +3 -0
  72. package/dist/extensions/lifecycle.d.ts +6 -1
  73. package/dist/extensions/lifecycle.js +9 -2
  74. package/dist/extensions/usage-subscribers.d.ts +43 -0
  75. package/dist/extensions/usage-subscribers.js +108 -0
  76. package/dist/http/api.module.js +2 -0
  77. package/dist/identity/settings.d.ts +53 -3
  78. package/dist/identity/settings.js +92 -9
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.js +1 -0
  81. package/dist/infra/cli/kernel-cli.module.js +5 -0
  82. package/dist/infra/config/kernel-config.service.d.ts +7 -0
  83. package/dist/infra/config/kernel-config.service.js +20 -0
  84. package/dist/infra/events/kernel-events.d.ts +9 -35
  85. package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
  86. package/dist/infra/outbox/outbox-relay.module.js +18 -0
  87. package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
  88. package/dist/infra/outbox/outbox.dispatcher.js +310 -0
  89. package/dist/infra/outbox/outbox.module.d.ts +24 -0
  90. package/dist/infra/outbox/outbox.module.js +50 -0
  91. package/dist/infra/outbox/outbox.relay.d.ts +31 -0
  92. package/dist/infra/outbox/outbox.relay.js +76 -0
  93. package/dist/infra/outbox/outbox.service.d.ts +51 -0
  94. package/dist/infra/outbox/outbox.service.js +131 -0
  95. package/dist/infra/outbox/registry.d.ts +120 -0
  96. package/dist/infra/outbox/registry.js +108 -0
  97. package/dist/infra/queue/queue-names.d.ts +8 -0
  98. package/dist/infra/queue/queue-names.js +16 -0
  99. package/dist/kernel.module.js +3 -0
  100. package/dist/local-devices/device-policy.service.d.ts +4 -0
  101. package/dist/local-devices/device-policy.service.js +61 -3
  102. package/dist/local-devices/local-device-gateway.d.ts +4 -2
  103. package/dist/local-devices/local-device-gateway.js +39 -14
  104. package/dist/local-devices/machines.d.ts +25 -1
  105. package/dist/local-devices/machines.js +91 -15
  106. package/dist/local-devices/policy-summary.d.ts +44 -1
  107. package/dist/local-devices/policy-summary.js +69 -0
  108. package/dist/models/embeddings.d.ts +9 -9
  109. package/dist/models/embeddings.js +11 -18
  110. package/dist/models/models.module.js +3 -0
  111. package/dist/orgs/orgs.service.js +7 -0
  112. package/dist/orgs/settings.controller.js +2 -2
  113. package/dist/orgs/settings.service.d.ts +11 -5
  114. package/dist/orgs/settings.service.js +28 -14
  115. package/dist/overrides.d.ts +20 -3
  116. package/dist/overrides.js +10 -2
  117. package/dist/platform/exclusions.js +5 -0
  118. package/dist/platform/ops/admin.ops.js +50 -9
  119. package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
  120. package/dist/platform/ops/catalog-admin.ops.js +32 -4
  121. package/dist/platform/ops/org-settings.ops.js +3 -3
  122. package/dist/platform/ops/orgs.ops.js +1 -0
  123. package/dist/platform/ops/outbox.ops.d.ts +21 -0
  124. package/dist/platform/ops/outbox.ops.js +125 -0
  125. package/dist/platform/ops/platform.ops.js +1 -1
  126. package/dist/platform/ops/schedules.ops.js +40 -1
  127. package/dist/platform/platform-ops.module.d.ts +2 -1
  128. package/dist/platform/platform-ops.module.js +3 -0
  129. package/dist/platform/route-schemas.js +3 -0
  130. package/dist/roles/worker.module.js +9 -4
  131. package/dist/runtime/langgraph-loop.js +13 -5
  132. package/dist/runtime/limits.d.ts +15 -0
  133. package/dist/runtime/limits.js +25 -0
  134. package/dist/runtime/loop.d.ts +8 -3
  135. package/dist/runtime/loop.js +2 -1
  136. package/dist/runtime/maintenance.d.ts +4 -2
  137. package/dist/runtime/maintenance.js +17 -2
  138. package/dist/runtime/model-call.d.ts +14 -16
  139. package/dist/runtime/model-call.js +14 -24
  140. package/dist/runtime/queue-lost-jobs.d.ts +24 -0
  141. package/dist/runtime/queue-lost-jobs.js +51 -0
  142. package/dist/runtime/run-control.d.ts +42 -5
  143. package/dist/runtime/run-control.js +66 -12
  144. package/dist/runtime/run-finish.d.ts +39 -12
  145. package/dist/runtime/run-finish.js +97 -14
  146. package/dist/runtime/run-jobs.d.ts +95 -13
  147. package/dist/runtime/run-jobs.js +199 -28
  148. package/dist/runtime/run-orphan-check.job.d.ts +142 -0
  149. package/dist/runtime/run-orphan-check.job.js +339 -0
  150. package/dist/runtime/run-processors.d.ts +16 -9
  151. package/dist/runtime/run-processors.js +1 -1
  152. package/dist/runtime/run-queue.d.ts +64 -55
  153. package/dist/runtime/run-queue.js +147 -284
  154. package/dist/runtime/run-steps.d.ts +1 -2
  155. package/dist/runtime/run-steps.js +8 -11
  156. package/dist/runtime/run-worker.d.ts +6 -17
  157. package/dist/runtime/run-worker.js +32 -34
  158. package/dist/runtime/runtime.module.d.ts +2 -3
  159. package/dist/runtime/runtime.module.js +5 -6
  160. package/dist/runtime/step-log.d.ts +16 -4
  161. package/dist/runtime/step-log.js +26 -6
  162. package/dist/runtime/stream.controller.d.ts +2 -4
  163. package/dist/runtime/stream.controller.js +5 -11
  164. package/dist/runtime/subagents/child.js +1 -1
  165. package/dist/runtime/subagents/definition.d.ts +2 -1
  166. package/dist/runtime/subagents/definition.js +4 -2
  167. package/dist/runtime/subagents/deliveries.d.ts +6 -3
  168. package/dist/runtime/subagents/deliveries.js +16 -7
  169. package/dist/runtime/subagents/dispatch.d.ts +12 -12
  170. package/dist/runtime/subagents/dispatch.js +13 -38
  171. package/dist/runtime/subagents/messages.d.ts +5 -1
  172. package/dist/runtime/subagents/messages.js +11 -3
  173. package/dist/runtime/subagents/skill-fork.d.ts +3 -3
  174. package/dist/runtime/subagents/skill-fork.js +5 -5
  175. package/dist/runtime/subagents/subagents.service.js +1 -1
  176. package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
  177. package/dist/runtime/subagents/tree-jobs.js +86 -0
  178. package/dist/runtime/usage-report-jobs.d.ts +23 -48
  179. package/dist/runtime/usage-report-jobs.js +67 -119
  180. package/dist/runtime/usage-report.d.ts +28 -77
  181. package/dist/runtime/usage-report.js +35 -213
  182. package/dist/runtime/waits.d.ts +48 -198
  183. package/dist/runtime/waits.js +103 -805
  184. package/dist/sandbox/platform-tiers.d.ts +86 -0
  185. package/dist/sandbox/platform-tiers.js +97 -0
  186. package/dist/sandbox/sandbox.service.d.ts +18 -5
  187. package/dist/sandbox/sandbox.service.js +75 -20
  188. package/dist/sandbox/tiers.d.ts +14 -2
  189. package/dist/sandbox/tiers.js +8 -0
  190. package/dist/schedules/calendar.d.ts +134 -0
  191. package/dist/schedules/calendar.js +410 -0
  192. package/dist/schedules/fire.d.ts +1 -3
  193. package/dist/schedules/fire.js +5 -10
  194. package/dist/schedules/run-tracker.d.ts +0 -5
  195. package/dist/schedules/run-tracker.js +0 -13
  196. package/dist/schedules/scan.job.js +15 -20
  197. package/dist/schedules/schedule-math.d.ts +42 -0
  198. package/dist/schedules/schedule-math.js +52 -0
  199. package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
  200. package/dist/schedules/scheduled-tasks.service.js +53 -0
  201. package/dist/schedules/schedules.controller.d.ts +5 -1
  202. package/dist/schedules/schedules.controller.js +15 -1
  203. package/dist/schedules/schedules.schemas.d.ts +35 -0
  204. package/dist/schedules/schedules.schemas.js +27 -0
  205. package/dist/schedules/store.d.ts +5 -0
  206. package/dist/schedules/store.js +20 -0
  207. package/dist/tools/approvals.service.d.ts +26 -7
  208. package/dist/tools/approvals.service.js +88 -10
  209. package/dist/tools/builtin-names.d.ts +5 -3
  210. package/dist/tools/builtin-names.js +12 -4
  211. package/dist/tools/builtins/builtin-tools.d.ts +1 -2
  212. package/dist/tools/builtins/builtin-tools.js +21 -17
  213. package/dist/tools/builtins/sandbox/bash.js +9 -3
  214. package/dist/tools/builtins/sandbox/common.d.ts +18 -1
  215. package/dist/tools/builtins/sandbox/common.js +14 -1
  216. package/dist/tools/builtins/sandbox/index.d.ts +2 -1
  217. package/dist/tools/builtins/sandbox/index.js +4 -1
  218. package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
  219. package/dist/tools/builtins/sandbox/task-list.js +130 -0
  220. package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
  221. package/dist/tools/builtins/sandbox/task-output.js +21 -3
  222. package/dist/tools/builtins/web/extract.d.ts +2 -4
  223. package/dist/tools/builtins/web/extract.js +2 -5
  224. package/dist/tools/pipeline.d.ts +1 -1
  225. package/dist/tools/pipeline.js +1 -1
  226. package/dist/tools/questions.service.d.ts +15 -6
  227. package/dist/tools/questions.service.js +20 -8
  228. package/dist/tools/registry.d.ts +4 -2
  229. package/dist/tools/registry.js +4 -3
  230. package/dist/tools/tools.module.js +4 -3
  231. package/package.json +3 -3
  232. package/src/migrations/0118_device_policy_ops.sql +6 -0
  233. package/src/migrations/0119_outbox.sql +22 -0
  234. package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
@@ -0,0 +1,130 @@
1
+ import { formatElapsed, lostReasonText } from "../../../background/render.js";
2
+ import { clip, display, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, } from "./common.js";
3
+ import { outputFile, taskEnv } from "./task-output.js";
4
+ /** How many finished background tasks (and finished sub-agents) `TaskList` lists at most. */
5
+ export const TASK_LIST_FINISHED_LIMIT = 20;
6
+ const SPEC = {
7
+ name: "TaskList",
8
+ // Display only (the tool row; not in the fingerprint, never sent to the model).
9
+ keyArgs: ["status"],
10
+ description: "List this conversation's background tasks (started with Bash and run_in_background) and the " +
11
+ "sub-agents it dispatched. Running tasks come first, then the most recently finished ones " +
12
+ "(at most 20); each has its id, description, status (running, " +
13
+ "completed, failed, timed_out, stopped or lost), elapsed time, exit code once finished and " +
14
+ "output file. Sub-agents are listed with their agentId, description and status. Use it to " +
15
+ "find a task id you lost track of, for example after a long run; then use TaskOutput to read " +
16
+ "a task's output or TaskStop to stop it. Statuses are as last checked: TaskOutput checks a " +
17
+ "task now. It only reads, and lists nothing from other conversations.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ status: {
22
+ type: "string",
23
+ enum: ["running", "all"],
24
+ description: `"running" lists only what is still running; "all" (the default) also lists the ${TASK_LIST_FINISHED_LIMIT} most recently finished tasks and sub-agents.`,
25
+ },
26
+ },
27
+ additionalProperties: false,
28
+ },
29
+ annotations: { readOnlyHint: true },
30
+ };
31
+ /** `running for 2m05s`, `failed, exit code 3, after 4m12s`, `lost (the sandbox was suspended), after 3s`. */
32
+ function taskState(task, now) {
33
+ const end = task.status === "running" ? now : (task.finishedAt ?? now);
34
+ const elapsed = formatElapsed(end.getTime() - task.startedAt.getTime());
35
+ if (task.status === "running") {
36
+ return task.sandboxCreatedAt === null ? "starting" : `running for ${elapsed}`;
37
+ }
38
+ let s = task.status;
39
+ if (task.lostReason !== null)
40
+ s += ` (${lostReasonText(task.lostReason)})`;
41
+ if (task.exitCode !== null)
42
+ s += `, exit code ${task.exitCode}`;
43
+ return `${s}, after ${elapsed}`;
44
+ }
45
+ function taskEntry(task, now) {
46
+ const output = task.sandboxCreatedAt === null && task.status !== "running"
47
+ ? "none (the command never started)"
48
+ : outputFile(task);
49
+ return `- ${task.id} ${taskState(task, now)}: ${JSON.stringify(task.description)}\n Output: ${output}`;
50
+ }
51
+ const ACTIVE_AGENT = new Set(["queued", "running", "waiting"]);
52
+ const agentStatus = (s) => (s === "aborted" ? "stopped" : s);
53
+ const agentEntry = (a) => `- ${a.id} ${agentStatus(a.status)}: ${JSON.stringify(clip(a.description, 200))}`;
54
+ /** `1 running, 2 finished` (a zero count left out; `none` when both are). */
55
+ function counts(running, finished, onlyRunning) {
56
+ const parts = [];
57
+ if (running > 0)
58
+ parts.push(`${running} running`);
59
+ if (finished > 0)
60
+ parts.push(`${finished} finished`);
61
+ if (parts.length === 0)
62
+ return onlyRunning ? "none running" : "none";
63
+ return parts.join(", ");
64
+ }
65
+ /**
66
+ * Built-in `TaskList`: the calling conversation's own background tasks (never its sub-agents' or
67
+ * another conversation's: owner `conversation_id`, as `TaskOutput` checks) and the sub-agents it
68
+ * dispatched itself. Running entries first (oldest first), then with `status: "all"` the
69
+ * {@link TASK_LIST_FINISHED_LIMIT} most recently finished. Reads PG only: it never checks a task in
70
+ * its sandbox, so a status is the poller's or a tool's last one.
71
+ */
72
+ export function taskListTool(deps) {
73
+ return {
74
+ spec: SPEC,
75
+ defaultPolicy: "allow",
76
+ unavailable: unavailableWhenNoSandbox(deps),
77
+ timeoutMs: () => sandboxBaseTimeoutMs(deps.config.get()),
78
+ async run(input, ctx) {
79
+ const onlyRunning = input.status === "running";
80
+ const env = await taskEnv(deps, ctx);
81
+ const store = deps.background;
82
+ const [running, finished, agents] = await Promise.all([
83
+ store.runningOwnedBy(ctx.conversationId),
84
+ onlyRunning
85
+ ? { rows: [], total: 0 }
86
+ : store.finishedOwnedBy(ctx.conversationId, TASK_LIST_FINISHED_LIMIT),
87
+ env.subagents?.childrenOf(ctx.orgId, ctx.conversationId) ?? [],
88
+ ]);
89
+ const now = new Date();
90
+ const activeAgents = agents.filter((a) => ACTIVE_AGENT.has(a.status));
91
+ const doneAgents = onlyRunning
92
+ ? []
93
+ : agents
94
+ .filter((a) => !ACTIVE_AGENT.has(a.status))
95
+ .sort((a, b) => (b.finishedAt?.getTime() ?? 0) - (a.finishedAt?.getTime() ?? 0));
96
+ const listedDone = doneAgents.slice(0, TASK_LIST_FINISHED_LIMIT);
97
+ if (running.length + finished.total + activeAgents.length + doneAgents.length === 0) {
98
+ return result(onlyRunning
99
+ ? "No background tasks or sub-agents are running in this conversation."
100
+ : "No background tasks or sub-agents in this conversation.");
101
+ }
102
+ const lines = [
103
+ `Background tasks: ${counts(running.length, finished.total, onlyRunning)}.`,
104
+ ...running.map((t) => taskEntry(t, now)),
105
+ ...finished.rows.map((t) => taskEntry(t, now)),
106
+ ];
107
+ const olderTasks = finished.total - finished.rows.length;
108
+ if (olderTasks > 0)
109
+ lines.push(`(${olderTasks} older finished tasks are not listed.)`);
110
+ if (agents.length > 0) {
111
+ lines.push("", `Sub-agents: ${counts(activeAgents.length, doneAgents.length, onlyRunning)}.`, ...activeAgents.map(agentEntry), ...listedDone.map(agentEntry));
112
+ const olderAgents = doneAgents.length - listedDone.length;
113
+ if (olderAgents > 0)
114
+ lines.push(`(${olderAgents} older finished sub-agents are not listed.)`);
115
+ }
116
+ lines.push("", "Statuses are as last checked. Use TaskOutput to check a task now and read its output, TaskStop to stop it." +
117
+ (agents.length > 0
118
+ ? " A sub-agent's report arrives on its own; use SendMessage to give it more instructions."
119
+ : ""));
120
+ return result(lines.join("\n"));
121
+ },
122
+ };
123
+ }
124
+ function result(text) {
125
+ return {
126
+ content: [{ type: "text", text }],
127
+ isError: false,
128
+ display: display("列出后台任务", "List background tasks"),
129
+ };
130
+ }
@@ -16,6 +16,7 @@ export interface TaskEnv {
16
16
  ifRunning: SandboxService["ifRunning"];
17
17
  devices: SandboxToolDeps["devices"];
18
18
  log: SandboxToolDeps["log"];
19
+ subagents?: SandboxToolDeps["subagents"];
19
20
  }
20
21
  /**
21
22
  * The task tools' dependencies; throws the tool's error when they are missing, or when the
@@ -23,9 +24,15 @@ export interface TaskEnv {
23
24
  * ruling R8).
24
25
  */
25
26
  export declare function taskEnv(deps: SandboxToolDeps, ctx: BuiltinCallContext): Promise<TaskEnv>;
27
+ /**
28
+ * A sub-agent's agentId passed as a task id (as Claude Code's TaskOutput allows for background
29
+ * agents): its report arrives on its own, so polling or sleeping for it only wastes the run.
30
+ */
31
+ export declare const subagentNotTaskText: (id: string) => string;
26
32
  /**
27
33
  * The task `id` when it belongs to the calling conversation; a task of another conversation (a
28
- * sub-agent's, when called from its parent) is not found, the same as an unknown id.
34
+ * sub-agent's, when called from its parent) is not found, the same as an unknown id. A sub-agent's
35
+ * agentId gets its own text ({@link subagentNotTaskText}).
29
36
  */
30
37
  export declare function ownedTask(env: TaskEnv, ctx: BuiltinCallContext, id: string): Promise<BackgroundTaskRow>;
31
38
  /**
@@ -67,16 +67,34 @@ export async function taskEnv(deps, ctx) {
67
67
  ifRunning: deps.sandbox.ifRunning.bind(deps.sandbox),
68
68
  devices: deps.devices,
69
69
  log: deps.log,
70
+ ...(deps.subagents !== undefined && { subagents: deps.subagents }),
70
71
  };
71
72
  }
72
- const notFound = (id) => new ToolError(`No background task ${clip(id, 40)} in this conversation. Pass a task id that Bash with run_in_background returned in this conversation.`);
73
+ const notFound = (id) => new ToolError(`No background task ${clip(id, 40)} in this conversation. Pass a task id that Bash with run_in_background returned in this conversation; call TaskList to see this conversation's tasks.`);
74
+ /**
75
+ * A sub-agent's agentId passed as a task id (as Claude Code's TaskOutput allows for background
76
+ * agents): its report arrives on its own, so polling or sleeping for it only wastes the run.
77
+ */
78
+ export const subagentNotTaskText = (id) => `${clip(id, 40)} is a sub-agent, not a background task. Its report arrives in this conversation as a notification when it hands back or finishes. Don't poll or sleep for it: keep working or end your turn. Use SendMessage to give it more instructions.`;
79
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
80
+ /** {@link notFound}, or {@link subagentNotTaskText} when `id` is a sub-agent of the caller's tree. */
81
+ async function missingTask(env, ctx, id) {
82
+ const trimmed = id.trim();
83
+ if (env.subagents !== undefined &&
84
+ UUID_RE.test(trimmed) &&
85
+ (await env.subagents.inTree(sandboxConversationOf(ctx), trimmed.toLowerCase()))) {
86
+ return new ToolError(subagentNotTaskText(trimmed));
87
+ }
88
+ return notFound(id);
89
+ }
73
90
  /**
74
91
  * The task `id` when it belongs to the calling conversation; a task of another conversation (a
75
- * sub-agent's, when called from its parent) is not found, the same as an unknown id.
92
+ * sub-agent's, when called from its parent) is not found, the same as an unknown id. A sub-agent's
93
+ * agentId gets its own text ({@link subagentNotTaskText}).
76
94
  */
77
95
  export async function ownedTask(env, ctx, id) {
78
96
  if (!TASK_ID_RE.test(id))
79
- throw notFound(id);
97
+ throw await missingTask(env, ctx, id);
80
98
  const task = await env.store.get(id);
81
99
  if (!task || task.conversationId !== ctx.conversationId)
82
100
  throw notFound(id);
@@ -4,7 +4,6 @@ import type { KernelConfigService } from "../../../infra/config/kernel-config.se
4
4
  import { type ModelPurposesService } from "../../../models/purposes.js";
5
5
  import { type HookRunner } from "../../../runtime/hook-calls.js";
6
6
  import type { StepLog } from "../../../runtime/step-log.js";
7
- import type { UsageReporter } from "../../../runtime/usage-report.js";
8
7
  import type { BuiltinCallContext } from "../../registry.js";
9
8
  /**
10
9
  * The `WebFetch` extraction call (web tools spec §5): the fetched page and the model's prompt go to
@@ -33,13 +32,12 @@ export declare const WEB_FETCH_DENIED_TEXT = "An org policy blocked reading this
33
32
  /** What {@link extractAnswer} needs. */
34
33
  export interface WebFetchExtractDeps {
35
34
  purposes: Pick<ModelPurposesService, "chat" | "chatLimits">;
36
- eventLog: Pick<EventLog, "append" | "notify" | "latestOfType">;
37
- stepLog: Pick<StepLog, "appendCounted" | "appendAudited">;
35
+ eventLog: Pick<EventLog, "notify" | "latestOfType">;
36
+ stepLog: Pick<StepLog, "appendCounted" | "appendReported" | "appendAudited">;
38
37
  config: Pick<KernelConfigService, "get">;
39
38
  log: Pick<Logger, "warn">;
40
39
  /** Extension hooks (absent: none fire). */
41
40
  hooks?: HookRunner | undefined;
42
- usageReport?: Pick<UsageReporter, "enqueue"> | undefined;
43
41
  }
44
42
  /**
45
43
  * The user message of the extraction call: the URL, then the page (title, markdown) between
@@ -192,7 +192,7 @@ export async function extractAnswer(deps, ctx, page, prompt) {
192
192
  }
193
193
  /**
194
194
  * Appends the call's `usage` event (the tool call's run and step, `callId`; idempotent per call)
195
- * and queues its `usage.report`. A sub-agent's call also counts on its parent run's
195
+ * and owes its `usage.report` delivery in the same transaction. A sub-agent's call also counts on its parent run's
196
196
  * `subagent_tokens_used`, in the same transaction ({@link appendCounted}).
197
197
  */
198
198
  async function logUsage(deps, ctx, runId, step, u) {
@@ -209,14 +209,11 @@ async function logUsage(deps, ctx, runId, step, u) {
209
209
  callId: ctx.callId,
210
210
  },
211
211
  };
212
- const written = await appendCounted(deps, {
212
+ await appendCounted(deps, {
213
213
  conversationId: ctx.conversationId,
214
214
  parentRunId: ctx.parentRunId,
215
215
  budgetRunId: ctx.budgetRunId,
216
216
  }, [event]);
217
- const usage = written.find((e) => e.type === "usage");
218
- if (usage)
219
- void deps.usageReport?.enqueue(ctx.conversationId, usage.seq);
220
217
  }
221
218
  /**
222
219
  * Records a `model.request` denial of the extraction call: the `hook_denied` event (the tool call's
@@ -310,7 +310,7 @@ export declare class ToolPipeline {
310
310
  runStep(given: ToolStepContext): Promise<ToolStepOutcome>;
311
311
  /**
312
312
  * Writes, in the caller's transaction (`mandatory`), what a run that is being stopped (a person,
313
- * its deadline, a failure, the wait sweep) still owes its log: the report of its fork invocation
313
+ * its deadline, a failure, an orphan repair) still owes its log: the report of its fork invocation
314
314
  * (phase D, `pairStoppedForks`; a still active fork child is stopped by the caller after its
315
315
  * commit). An `Agent` call has its result from its dispatch (agent map spec §4.1), so there is
316
316
  * nothing to write for it. Returns the fork's call id (the caller must not write a "not executed"
@@ -331,7 +331,7 @@ let ToolPipeline = class ToolPipeline {
331
331
  }
332
332
  /**
333
333
  * Writes, in the caller's transaction (`mandatory`), what a run that is being stopped (a person,
334
- * its deadline, a failure, the wait sweep) still owes its log: the report of its fork invocation
334
+ * its deadline, a failure, an orphan repair) still owes its log: the report of its fork invocation
335
335
  * (phase D, `pairStoppedForks`; a still active fork child is stopped by the caller after its
336
336
  * commit). An `Agent` call has its result from its dispatch (agent map spec §4.1), so there is
337
337
  * nothing to write for it. Returns the fork's call id (the caller must not write a "not executed"
@@ -4,6 +4,7 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
4
4
  import { KernelDb } from "../infra/database/kernel-db.js";
5
5
  import { TransactionHost } from "../infra/database/transaction-host.js";
6
6
  import { KernelEvents } from "../infra/events/kernel-events.js";
7
+ import { RunResume } from "../runtime/run-jobs.js";
7
8
  export type QuestionStatus = "pending" | "answered" | "skipped" | "expired" | "cancelled";
8
9
  /** 一条输入请求(`AskUserQuestion`、插件工具的 `input_required`、MCP elicitation 共用)。 */
9
10
  export interface QuestionRecord {
@@ -44,9 +45,11 @@ export type QuestionView = Omit<QuestionRecord, "state">;
44
45
  * 状态全部在 PG:回答 / 超时 / 取消都在事务里 `SELECT … FOR UPDATE` 该行,多副本并发时恰好一个生效,
45
46
  * 只有生效的那次写 `question_answered`。HTTP 接口见 questions.controller.ts。
46
47
  *
47
- * An answer is announced after the commit as the domain event `question.answered` (exactly once),
48
- * which the run's waits follow to resume it (spec §3b; the wait sweep resumes a run whose resume was
49
- * lost). Events are appended in the context's transaction (`EventLog.appendIn`).
48
+ * An answer resumes its waiting run in the answer's transaction (`RunResume.resumeIn`, after the
49
+ * question row: lock order conversation, question, then run; transactional outbox spec §6). It is
50
+ * announced after the commit as the domain event `question.answered` (exactly once; no kernel
51
+ * subscriber: the IM card update is owed by the `question_answered` append, transactional outbox
52
+ * spec §8.1). Events are appended in the context's transaction (`EventLog.appendIn`).
50
53
  */
51
54
  export declare class QuestionsService {
52
55
  private readonly db;
@@ -54,7 +57,11 @@ export declare class QuestionsService {
54
57
  private readonly eventLog;
55
58
  private readonly events;
56
59
  readonly txHost: TransactionHost;
57
- constructor(db: KernelDb, config: Pick<KernelConfigService, "get">, eventLog: Pick<EventLog, "appendIn" | "notify">, events: Pick<KernelEvents, "emit">, txHost: TransactionHost);
60
+ /** An answer resumes its waiting run in the answer's transaction. */
61
+ private readonly resumes;
62
+ constructor(db: KernelDb, config: Pick<KernelConfigService, "get">, eventLog: Pick<EventLog, "appendIn" | "notify">, events: Pick<KernelEvents, "emit">, txHost: TransactionHost,
63
+ /** An answer resumes its waiting run in the answer's transaction. */
64
+ resumes: Pick<RunResume, "resumeIn">);
58
65
  /**
59
66
  * 新建 pending 输入请求(`expires_at` = 现在 + `tools.approvalTimeoutHours`)并写 `question_asked`,同一事务。
60
67
  * 按 (run_id, call_id, attempt) 幂等:已存在则返回已有记录,事件按 `question_asked:<id>` 幂等。
@@ -113,8 +120,10 @@ export declare class QuestionsService {
113
120
  * by a run of it that is `running` or `waiting` and was started by `accountId`. The caller holds
114
121
  * the conversation row lock (its `appendIn`), so only questions logged before the message are
115
122
  * seen. Each becomes `skipped` with `question_answered { outcome: "skipped", skippedBySeq }`.
116
- * Returns the records and the highest seq written (null: none); the caller notifies and emits
117
- * `question.answered` after the commit.
123
+ * The caller resumes their waiting runs in this transaction after it locked every row it skips
124
+ * (`ConversationsService.skipWaitsIn`: conversations, decided rows, runs). Returns the records and
125
+ * the highest seq written (null: none); the caller notifies and emits `question.answered` after
126
+ * the commit.
118
127
  */
119
128
  skipPendingIn(conversationId: string, accountId: string, messageSeq: number): Promise<{
120
129
  records: QuestionRecord[];
@@ -18,6 +18,7 @@ import { KernelDb } from "../infra/database/kernel-db.js";
18
18
  import { TransactionHost } from "../infra/database/transaction-host.js";
19
19
  import { Transactional } from "../infra/database/transactional.decorator.js";
20
20
  import { KernelEvents } from "../infra/events/kernel-events.js";
21
+ import { RunResume } from "../runtime/run-jobs.js";
21
22
  const notFound = () => new ApiError("question.not_found", "Question not found");
22
23
  const notInitiator = () => new ApiError("forbidden", "Only the user who started the run can answer this question");
23
24
  const toView = ({ state: _state, ...rest }) => rest;
@@ -49,9 +50,11 @@ const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArr
49
50
  * 状态全部在 PG:回答 / 超时 / 取消都在事务里 `SELECT … FOR UPDATE` 该行,多副本并发时恰好一个生效,
50
51
  * 只有生效的那次写 `question_answered`。HTTP 接口见 questions.controller.ts。
51
52
  *
52
- * An answer is announced after the commit as the domain event `question.answered` (exactly once),
53
- * which the run's waits follow to resume it (spec §3b; the wait sweep resumes a run whose resume was
54
- * lost). Events are appended in the context's transaction (`EventLog.appendIn`).
53
+ * An answer resumes its waiting run in the answer's transaction (`RunResume.resumeIn`, after the
54
+ * question row: lock order conversation, question, then run; transactional outbox spec §6). It is
55
+ * announced after the commit as the domain event `question.answered` (exactly once; no kernel
56
+ * subscriber: the IM card update is owed by the `question_answered` append, transactional outbox
57
+ * spec §8.1). Events are appended in the context's transaction (`EventLog.appendIn`).
55
58
  */
56
59
  let QuestionsService = class QuestionsService {
57
60
  db;
@@ -59,12 +62,16 @@ let QuestionsService = class QuestionsService {
59
62
  eventLog;
60
63
  events;
61
64
  txHost;
62
- constructor(db, config, eventLog, events, txHost) {
65
+ resumes;
66
+ constructor(db, config, eventLog, events, txHost,
67
+ /** An answer resumes its waiting run in the answer's transaction. */
68
+ resumes) {
63
69
  this.db = db;
64
70
  this.config = config;
65
71
  this.eventLog = eventLog;
66
72
  this.events = events;
67
73
  this.txHost = txHost;
74
+ this.resumes = resumes;
68
75
  }
69
76
  /**
70
77
  * 新建 pending 输入请求(`expires_at` = 现在 + `tools.approvalTimeoutHours`)并写 `question_asked`,同一事务。
@@ -163,7 +170,7 @@ let QuestionsService = class QuestionsService {
163
170
  async answer(questionId, accountId, answer, orgId) {
164
171
  const { record, toSeq } = await this.answerIn(questionId, accountId, answer, orgId);
165
172
  await this.eventLog.notify(record.conversationId, toSeq);
166
- // waits resume the run and its IM delivery is enqueued (handler failures are logged)
173
+ // its IM delivery is enqueued (handler failures are logged); the run resumed in the answer
167
174
  await this.events.emit("question.answered", {
168
175
  id: record.id,
169
176
  orgId: record.orgId,
@@ -246,6 +253,8 @@ let QuestionsService = class QuestionsService {
246
253
  .returningAll()
247
254
  .executeTakeFirstOrThrow();
248
255
  const toSeq = await this.answeredEvent(before, answer, accountId, "answered");
256
+ // the run last; a still-running run is resumed by its suspend's re-check
257
+ await this.resumes.resumeIn(before.runId, questionId);
249
258
  return { record: toRecord(updated), toSeq };
250
259
  }
251
260
  /**
@@ -375,8 +384,10 @@ let QuestionsService = class QuestionsService {
375
384
  * by a run of it that is `running` or `waiting` and was started by `accountId`. The caller holds
376
385
  * the conversation row lock (its `appendIn`), so only questions logged before the message are
377
386
  * seen. Each becomes `skipped` with `question_answered { outcome: "skipped", skippedBySeq }`.
378
- * Returns the records and the highest seq written (null: none); the caller notifies and emits
379
- * `question.answered` after the commit.
387
+ * The caller resumes their waiting runs in this transaction after it locked every row it skips
388
+ * (`ConversationsService.skipWaitsIn`: conversations, decided rows, runs). Returns the records and
389
+ * the highest seq written (null: none); the caller notifies and emits `question.answered` after
390
+ * the commit.
380
391
  */
381
392
  async skipPendingIn(conversationId, accountId, messageSeq) {
382
393
  const t = this.db.db;
@@ -451,6 +462,7 @@ QuestionsService = __decorate([
451
462
  __param(1, Inject(KernelConfigService)),
452
463
  __param(2, Inject(EventLog)),
453
464
  __param(3, Inject(KernelEvents)),
454
- __param(4, Inject(TransactionHost))
465
+ __param(4, Inject(TransactionHost)),
466
+ __param(5, Inject(RunResume))
455
467
  ], QuestionsService);
456
468
  export { QuestionsService };
@@ -40,6 +40,8 @@ export interface BuiltinCallContext extends ToolCallContext {
40
40
  /**
41
41
  * 本次运行的截止时间(epoch 毫秒,`runtime.maxRunSeconds` 起算,Ruling M20)。到点运行被中止;
42
42
  * 沙箱命令按它收紧超时,赶在截止前自己超时、给模型可执行的说明。
43
+ * Absent when the run has no duration limit (`runtime.maxRunSeconds` 0): commands are then limited
44
+ * only by `sandbox.limits.maxCommandSeconds` and the tool timeouts.
43
45
  */
44
46
  runDeadline?: number;
45
47
  /**
@@ -236,7 +238,7 @@ export interface AgentTools {
236
238
  *
237
239
  * What is offered:
238
240
  * - builtins: every {@link AGENT_BUILTIN_NAMES} entry registered here that can run (`unavailable`
239
- * says why not), `TaskOutput` / `TaskStop` with `Bash`, `Agent` while its roster is not empty;
241
+ * says why not), `TaskOutput` / `TaskStop` / `TaskList` with `Bash`, `Agent` while its roster is not empty;
240
242
  * - extension tools: every tool registered by an extension loaded on this replica and enabled in
241
243
  * the org (`extension_org_configs.enabled`; required bootstrap plugins are enabled in every org,
242
244
  * `enabled.ts`), whose effective config for the org is valid (layering spec A §6) and that the
@@ -380,7 +382,7 @@ export declare class ToolRegistry {
380
382
  }
381
383
  /**
382
384
  * `tools` narrowed by a sub-agent's key filter (tools default-on spec §4): `deny` keys removed,
383
- * and with `allow` only its keys kept. `TaskOutput` / `TaskStop` follow `Bash`, as they come with
385
+ * and with `allow` only its keys kept. `TaskOutput` / `TaskStop` / `TaskList` follow `Bash`, as they come with
384
386
  * it. Without a filter the list is returned as it is.
385
387
  */
386
388
  export declare function filterSubagentTools(tools: ResolvedTool[], filter: SubagentTools | undefined): ResolvedTool[];
@@ -92,7 +92,7 @@ const builtinTool = (b, spec) => ({
92
92
  *
93
93
  * What is offered:
94
94
  * - builtins: every {@link AGENT_BUILTIN_NAMES} entry registered here that can run (`unavailable`
95
- * says why not), `TaskOutput` / `TaskStop` with `Bash`, `Agent` while its roster is not empty;
95
+ * says why not), `TaskOutput` / `TaskStop` / `TaskList` with `Bash`, `Agent` while its roster is not empty;
96
96
  * - extension tools: every tool registered by an extension loaded on this replica and enabled in
97
97
  * the org (`extension_org_configs.enabled`; required bootstrap plugins are enabled in every org,
98
98
  * `enabled.ts`), whose effective config for the org is valid (layering spec A §6) and that the
@@ -396,7 +396,8 @@ let ToolRegistry = class ToolRegistry {
396
396
  spec = agentToolSpec(roster, await this.maxConcurrentSubagents(orgId));
397
397
  }
398
398
  add(builtinTool(b, spec));
399
- // `TaskOutput` and `TaskStop` come with an available `Bash` (background Bash spec §2)
399
+ // `TaskOutput`, `TaskStop` and `TaskList` come with an available `Bash` (background Bash
400
+ // spec §2)
400
401
  if (name === BUILTIN.Bash) {
401
402
  for (const companion of BASH_COMPANION_TOOLS) {
402
403
  const c = this.builtins.find((x) => x.spec.name === companion);
@@ -585,7 +586,7 @@ ToolRegistry = __decorate([
585
586
  export { ToolRegistry };
586
587
  /**
587
588
  * `tools` narrowed by a sub-agent's key filter (tools default-on spec §4): `deny` keys removed,
588
- * and with `allow` only its keys kept. `TaskOutput` / `TaskStop` follow `Bash`, as they come with
589
+ * and with `allow` only its keys kept. `TaskOutput` / `TaskStop` / `TaskList` follow `Bash`, as they come with
589
590
  * it. Without a filter the list is returned as it is.
590
591
  */
591
592
  export function filterSubagentTools(tools, filter) {
@@ -17,9 +17,9 @@ import { ModelsModule } from "../models/models.module.js";
17
17
  import { OrgLockModule } from "../orgs/org-lock.js";
18
18
  import { PlatformModule } from "../platform/platform.module.js";
19
19
  import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
20
+ import { RunJobsModule } from "../runtime/run-jobs.js";
20
21
  import { StepLogModule } from "../runtime/step-log.js";
21
22
  import { SubagentsModule } from "../runtime/subagents/subagents.service.js";
22
- import { UsageReportModule } from "../runtime/usage-report.js";
23
23
  import { SandboxModule } from "../sandbox/sandbox.module.js";
24
24
  import { ScheduledTaskStoreModule } from "../schedules/store.module.js";
25
25
  import { SkillsModule } from "../skills/skills.module.js";
@@ -72,10 +72,11 @@ ToolsModule = __decorate([
72
72
  BackgroundModule,
73
73
  // the conversation page's tool metadata reads only what the viewer may see
74
74
  ConversationAccessModule,
75
- // the WebFetch extraction call: its purpose, usage report and step writes
75
+ // the WebFetch extraction call: its purpose and step writes (its usage report owed with them)
76
76
  ModelsModule,
77
- UsageReportModule,
78
77
  StepLogModule,
78
+ // a decision resumes its waiting run in the decision's transaction (`RunResume`)
79
+ RunJobsModule,
79
80
  // the `Platform` tool's operations, actor facts and agent docs (imports no domain module)
80
81
  PlatformModule,
81
82
  QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capybara-hq/kernel",
3
- "version": "0.1.0-rc.22",
3
+ "version": "0.1.0-rc.24",
4
4
  "description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -65,7 +65,7 @@
65
65
  "yaml": "^2.9.1",
66
66
  "yauzl": "3.4.0",
67
67
  "zod": "^4.6.7",
68
- "@capybara-hq/plugin-sdk": "^0.1.0-rc.16"
68
+ "@capybara-hq/plugin-sdk": "^0.1.0-rc.18"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@langchain/langgraph-checkpoint-validation": "^1.2.0",
@@ -74,7 +74,7 @@
74
74
  "@types/ws": "^8.18.2",
75
75
  "@types/yauzl": "3.4.0",
76
76
  "ws": "^8.22.0",
77
- "@capybara-hq/testing": "0.1.0-rc.15"
77
+ "@capybara-hq/testing": "0.1.0-rc.16"
78
78
  },
79
79
  "scripts": {
80
80
  "build": "tsc -b"
@@ -0,0 +1,6 @@
1
+ -- The `policy` ops the platform sent to a machine whose answer may not be recorded yet (sandbox dev
2
+ -- tiers spec §10.4): a summary the machine reports by itself (a `policy` frame, the next hello)
3
+ -- that is such an op's change is not listed again as a change made on the machine. A JSON array of
4
+ -- `{ id, add, remove, tier?, isolation?, until }`; an entry leaves when its answer is recorded or,
5
+ -- unanswered, when `until` (epoch ms) has passed.
6
+ ALTER TABLE device_machines ADD COLUMN IF NOT EXISTS pending_policy_ops jsonb NOT NULL DEFAULT '[]'::jsonb;
@@ -0,0 +1,22 @@
1
+ -- Transactional outbox (transactional outbox spec §4): work that must follow a commit is written
2
+ -- here in the same transaction as the state that owes it, and a relay delivers it to BullMQ. Rows are
3
+ -- deleted on delivery; only dead rows (`dead_at` set) stay.
4
+
5
+ CREATE TABLE IF NOT EXISTS outbox (
6
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
7
+ queue text NOT NULL,
8
+ job_name text NOT NULL,
9
+ job_id text NOT NULL,
10
+ data jsonb NOT NULL,
11
+ run_at timestamptz,
12
+ org_id text, -- for the overview and for org deletion; NULL for platform work
13
+ attempts integer NOT NULL DEFAULT 0,
14
+ next_attempt_at timestamptz NOT NULL DEFAULT now(),
15
+ last_error text,
16
+ dead_at timestamptz,
17
+ created_at timestamptz NOT NULL DEFAULT now()
18
+ );
19
+ -- one pending row per job: a second add of the same job before delivery is a no-op; a dead row
20
+ -- does not count, so it never blocks the same job owed again
21
+ CREATE UNIQUE INDEX IF NOT EXISTS outbox_job_uniq ON outbox (queue, job_id) WHERE dead_at IS NULL;
22
+ CREATE INDEX IF NOT EXISTS outbox_due ON outbox (next_attempt_at) WHERE dead_at IS NULL;
@@ -0,0 +1,7 @@
1
+ -- 0120, transactional outbox spec §8.1 / §8.2: a `usage.report` delivery is owed in the outbox
2
+ -- with the usage it reports, so the usage sweeper is gone, and with it its watermark table and the
3
+ -- index it found unreported `model_usage` rows by. (`conversation_events_usage_idx` stays: the
4
+ -- admin usage aggregate reads usage events by time.)
5
+ -- capybara:allow-destructive the sweeper that read and wrote this single-row watermark is removed in this release (project not released, no compatibility layer)
6
+ DROP TABLE IF EXISTS usage_report_sweep;
7
+ DROP INDEX IF EXISTS model_usage_unreported;