@capybara-hq/kernel 0.1.0-rc.23 → 0.1.0-rc.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin.module.d.ts +2 -1
  3. package/dist/admin/admin.module.js +9 -3
  4. package/dist/admin/health.service.d.ts +2 -2
  5. package/dist/admin/health.service.js +6 -2
  6. package/dist/admin/outbox-dead.controller.d.ts +23 -0
  7. package/dist/admin/outbox-dead.controller.js +62 -0
  8. package/dist/admin/outbox-dead.schemas.d.ts +8 -0
  9. package/dist/admin/outbox-dead.schemas.js +18 -0
  10. package/dist/admin/outbox-dead.service.d.ts +66 -0
  11. package/dist/admin/outbox-dead.service.js +179 -0
  12. package/dist/admin/overview.service.d.ts +40 -5
  13. package/dist/admin/overview.service.js +41 -7
  14. package/dist/agents/agents.module.js +3 -0
  15. package/dist/agents/agents.service.d.ts +6 -1
  16. package/dist/agents/agents.service.js +11 -2
  17. package/dist/agents/from-template.js +1 -1
  18. package/dist/background/finish.d.ts +5 -2
  19. package/dist/background/finish.js +14 -3
  20. package/dist/background/launch.d.ts +2 -1
  21. package/dist/background/launch.js +5 -2
  22. package/dist/background/store.d.ts +8 -0
  23. package/dist/background/store.js +24 -0
  24. package/dist/catalog/admin-catalog.controller.d.ts +21 -2
  25. package/dist/catalog/admin-catalog.controller.js +45 -6
  26. package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
  27. package/dist/catalog/admin-catalog.schemas.js +8 -0
  28. package/dist/catalog/catalog-import.module.js +0 -4
  29. package/dist/catalog/catalog-scope.service.d.ts +63 -26
  30. package/dist/catalog/catalog-scope.service.js +146 -71
  31. package/dist/catalog/catalog.module.d.ts +2 -1
  32. package/dist/catalog/catalog.module.js +9 -4
  33. package/dist/catalog/catalog.service.d.ts +11 -4
  34. package/dist/catalog/catalog.service.js +21 -5
  35. package/dist/catalog/cli.d.ts +4 -3
  36. package/dist/catalog/cli.js +8 -13
  37. package/dist/catalog/job-ids.d.ts +8 -20
  38. package/dist/catalog/job-ids.js +11 -16
  39. package/dist/catalog/marketplaces.service.d.ts +1 -1
  40. package/dist/catalog/org-plugin-uploads.d.ts +11 -4
  41. package/dist/catalog/org-plugin-uploads.js +21 -6
  42. package/dist/catalog/owned-items.d.ts +6 -13
  43. package/dist/catalog/owned-items.js +11 -29
  44. package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
  45. package/dist/catalog/owned-plugin-cleanup.js +12 -66
  46. package/dist/catalog/platform-components.d.ts +56 -34
  47. package/dist/catalog/platform-components.js +68 -78
  48. package/dist/catalog/platform-import-update.d.ts +50 -0
  49. package/dist/catalog/platform-import-update.js +116 -0
  50. package/dist/catalog/plugin-import.d.ts +64 -26
  51. package/dist/catalog/plugin-import.js +113 -77
  52. package/dist/catalog/plugin-upgrade.d.ts +22 -4
  53. package/dist/catalog/plugin-upgrade.js +26 -19
  54. package/dist/catalog/scope-jobs.d.ts +59 -0
  55. package/dist/catalog/scope-jobs.js +65 -0
  56. package/dist/catalog/scope-sync-workers.d.ts +22 -0
  57. package/dist/catalog/scope-sync-workers.js +58 -2
  58. package/dist/catalog/scope-sync.d.ts +40 -65
  59. package/dist/catalog/scope-sync.js +114 -122
  60. package/dist/channels/channels.module.d.ts +5 -2
  61. package/dist/channels/channels.module.js +9 -2
  62. package/dist/channels/deliver-job.d.ts +25 -0
  63. package/dist/channels/deliver-job.js +25 -0
  64. package/dist/channels/inbound.d.ts +1 -1
  65. package/dist/channels/inbound.js +4 -17
  66. package/dist/channels/jobs.d.ts +28 -18
  67. package/dist/channels/jobs.js +63 -26
  68. package/dist/channels/threads.d.ts +4 -3
  69. package/dist/channels/threads.js +4 -3
  70. package/dist/config/schema.d.ts +0 -3
  71. package/dist/config/schema.js +11 -20
  72. package/dist/conversations/agent-tree.d.ts +16 -0
  73. package/dist/conversations/agent-tree.js +36 -0
  74. package/dist/conversations/conversations.controller.js +4 -2
  75. package/dist/conversations/conversations.service.d.ts +61 -19
  76. package/dist/conversations/conversations.service.js +69 -43
  77. package/dist/conversations/event-log.d.ts +7 -10
  78. package/dist/conversations/event-log.js +24 -15
  79. package/dist/conversations/events.d.ts +21 -0
  80. package/dist/conversations/events.js +26 -2
  81. package/dist/conversations/queue.service.d.ts +8 -3
  82. package/dist/conversations/queue.service.js +37 -13
  83. package/dist/conversations/source.d.ts +10 -0
  84. package/dist/conversations/source.js +17 -0
  85. package/dist/conversations/titles.d.ts +21 -8
  86. package/dist/conversations/titles.js +42 -15
  87. package/dist/conversations/tree.d.ts +10 -0
  88. package/dist/conversations/tree.js +35 -0
  89. package/dist/db/kernel-schema.d.ts +35 -22
  90. package/dist/devices/devices.service.d.ts +23 -11
  91. package/dist/devices/devices.service.js +34 -26
  92. package/dist/errors.d.ts +11 -0
  93. package/dist/errors.js +11 -0
  94. package/dist/evals/run-replay.d.ts +13 -2
  95. package/dist/evals/run-replay.js +42 -9
  96. package/dist/evals/snapshot.d.ts +7 -1
  97. package/dist/evals/snapshot.js +2 -1
  98. package/dist/extensions/extensions.module.js +3 -0
  99. package/dist/extensions/hooks.d.ts +22 -3
  100. package/dist/extensions/hooks.js +15 -1
  101. package/dist/extensions/lifecycle-hooks.d.ts +99 -0
  102. package/dist/extensions/lifecycle-hooks.js +273 -0
  103. package/dist/extensions/lifecycle.d.ts +6 -1
  104. package/dist/extensions/lifecycle.js +9 -2
  105. package/dist/extensions/org-config.d.ts +13 -1
  106. package/dist/extensions/org-config.js +19 -2
  107. package/dist/extensions/permission-view.d.ts +9 -5
  108. package/dist/extensions/permission-view.js +9 -4
  109. package/dist/extensions/point-defaults.d.ts +28 -1
  110. package/dist/extensions/point-defaults.js +44 -13
  111. package/dist/extensions/usage-subscribers.d.ts +43 -0
  112. package/dist/extensions/usage-subscribers.js +108 -0
  113. package/dist/http/api.module.js +2 -0
  114. package/dist/inbox/inbox.service.d.ts +6 -20
  115. package/dist/inbox/inbox.service.js +0 -3
  116. package/dist/inbox/org-notices.d.ts +1 -1
  117. package/dist/inbox/platform-inbox.service.d.ts +3 -32
  118. package/dist/inbox/platform-inbox.service.js +5 -54
  119. package/dist/index.d.ts +1 -0
  120. package/dist/index.js +1 -0
  121. package/dist/infra/cli/kernel-cli.module.js +5 -0
  122. package/dist/infra/events/kernel-events.d.ts +9 -35
  123. package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
  124. package/dist/infra/outbox/outbox-relay.module.js +18 -0
  125. package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
  126. package/dist/infra/outbox/outbox.dispatcher.js +310 -0
  127. package/dist/infra/outbox/outbox.module.d.ts +24 -0
  128. package/dist/infra/outbox/outbox.module.js +50 -0
  129. package/dist/infra/outbox/outbox.relay.d.ts +31 -0
  130. package/dist/infra/outbox/outbox.relay.js +76 -0
  131. package/dist/infra/outbox/outbox.service.d.ts +51 -0
  132. package/dist/infra/outbox/outbox.service.js +131 -0
  133. package/dist/infra/outbox/registry.d.ts +244 -0
  134. package/dist/infra/outbox/registry.js +234 -0
  135. package/dist/infra/queue/queue-names.d.ts +8 -0
  136. package/dist/infra/queue/queue-names.js +17 -0
  137. package/dist/kernel.module.js +3 -0
  138. package/dist/local-devices/device-policy.service.d.ts +4 -0
  139. package/dist/local-devices/device-policy.service.js +61 -3
  140. package/dist/local-devices/machines.d.ts +13 -5
  141. package/dist/local-devices/machines.js +45 -7
  142. package/dist/local-devices/policy-summary.d.ts +44 -1
  143. package/dist/local-devices/policy-summary.js +69 -0
  144. package/dist/models/embeddings.d.ts +9 -9
  145. package/dist/models/embeddings.js +11 -18
  146. package/dist/models/models.module.js +3 -0
  147. package/dist/orgs/directory-sync.js +1 -1
  148. package/dist/orgs/members.service.js +1 -1
  149. package/dist/orgs/offboarding.d.ts +50 -57
  150. package/dist/orgs/offboarding.js +117 -162
  151. package/dist/orgs/orgs.module.js +3 -0
  152. package/dist/orgs/orgs.service.d.ts +11 -5
  153. package/dist/orgs/orgs.service.js +22 -6
  154. package/dist/overrides.d.ts +23 -3
  155. package/dist/overrides.js +12 -2
  156. package/dist/platform/exclusions.js +5 -0
  157. package/dist/platform/ops/admin.ops.js +18 -2
  158. package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
  159. package/dist/platform/ops/catalog-admin.ops.js +32 -4
  160. package/dist/platform/ops/inbox.ops.js +4 -20
  161. package/dist/platform/ops/outbox.ops.d.ts +21 -0
  162. package/dist/platform/ops/outbox.ops.js +125 -0
  163. package/dist/platform/ops/platform.ops.d.ts +1 -0
  164. package/dist/platform/ops/platform.ops.js +6 -3
  165. package/dist/platform/ops/schedules.ops.js +40 -1
  166. package/dist/platform/platform-ops.module.d.ts +2 -1
  167. package/dist/platform/platform-ops.module.js +3 -0
  168. package/dist/platform/route-schemas.js +3 -0
  169. package/dist/roles/worker-role.d.ts +10 -5
  170. package/dist/roles/worker-role.js +19 -9
  171. package/dist/roles/worker.module.js +11 -8
  172. package/dist/runtime/context/build.d.ts +3 -0
  173. package/dist/runtime/context/build.js +15 -0
  174. package/dist/runtime/context/plugin-context.d.ts +20 -0
  175. package/dist/runtime/context/plugin-context.js +30 -0
  176. package/dist/runtime/langgraph-loop.js +17 -5
  177. package/dist/runtime/limits.d.ts +15 -0
  178. package/dist/runtime/limits.js +25 -0
  179. package/dist/runtime/loop.d.ts +16 -3
  180. package/dist/runtime/loop.js +2 -1
  181. package/dist/runtime/maintenance.d.ts +7 -3
  182. package/dist/runtime/maintenance.js +52 -12
  183. package/dist/runtime/model-call.d.ts +20 -17
  184. package/dist/runtime/model-call.js +15 -24
  185. package/dist/runtime/plugin-context.service.d.ts +69 -0
  186. package/dist/runtime/plugin-context.service.js +208 -0
  187. package/dist/runtime/queue-lost-jobs.d.ts +24 -0
  188. package/dist/runtime/queue-lost-jobs.js +51 -0
  189. package/dist/runtime/run-control.d.ts +47 -5
  190. package/dist/runtime/run-control.js +79 -12
  191. package/dist/runtime/run-finish.d.ts +39 -12
  192. package/dist/runtime/run-finish.js +97 -14
  193. package/dist/runtime/run-jobs.d.ts +95 -13
  194. package/dist/runtime/run-jobs.js +199 -28
  195. package/dist/runtime/run-orphan-check.job.d.ts +142 -0
  196. package/dist/runtime/run-orphan-check.job.js +339 -0
  197. package/dist/runtime/run-processors.d.ts +16 -9
  198. package/dist/runtime/run-processors.js +1 -1
  199. package/dist/runtime/run-queue.d.ts +75 -53
  200. package/dist/runtime/run-queue.js +189 -263
  201. package/dist/runtime/run-steps.d.ts +1 -2
  202. package/dist/runtime/run-steps.js +8 -11
  203. package/dist/runtime/run-worker.d.ts +26 -16
  204. package/dist/runtime/run-worker.js +132 -37
  205. package/dist/runtime/runtime.module.d.ts +2 -3
  206. package/dist/runtime/runtime.module.js +7 -6
  207. package/dist/runtime/step-log.d.ts +16 -4
  208. package/dist/runtime/step-log.js +26 -6
  209. package/dist/runtime/stream.controller.d.ts +2 -4
  210. package/dist/runtime/stream.controller.js +5 -11
  211. package/dist/runtime/subagents/child.js +1 -1
  212. package/dist/runtime/subagents/definition.d.ts +2 -1
  213. package/dist/runtime/subagents/definition.js +4 -2
  214. package/dist/runtime/subagents/deliveries.d.ts +6 -3
  215. package/dist/runtime/subagents/deliveries.js +16 -7
  216. package/dist/runtime/subagents/dispatch.d.ts +12 -12
  217. package/dist/runtime/subagents/dispatch.js +14 -38
  218. package/dist/runtime/subagents/messages.d.ts +5 -1
  219. package/dist/runtime/subagents/messages.js +11 -3
  220. package/dist/runtime/subagents/skill-fork.d.ts +3 -3
  221. package/dist/runtime/subagents/skill-fork.js +8 -5
  222. package/dist/runtime/subagents/subagents.service.js +1 -1
  223. package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
  224. package/dist/runtime/subagents/tree-jobs.js +86 -0
  225. package/dist/runtime/usage-report-jobs.d.ts +23 -48
  226. package/dist/runtime/usage-report-jobs.js +67 -119
  227. package/dist/runtime/usage-report.d.ts +28 -77
  228. package/dist/runtime/usage-report.js +35 -213
  229. package/dist/runtime/waits.d.ts +48 -198
  230. package/dist/runtime/waits.js +103 -805
  231. package/dist/sandbox/jobs.d.ts +20 -10
  232. package/dist/sandbox/jobs.js +23 -17
  233. package/dist/sandbox/sandbox.service.d.ts +17 -4
  234. package/dist/sandbox/sandbox.service.js +55 -14
  235. package/dist/schedules/calendar.d.ts +134 -0
  236. package/dist/schedules/calendar.js +410 -0
  237. package/dist/schedules/fire.d.ts +26 -7
  238. package/dist/schedules/fire.js +91 -22
  239. package/dist/schedules/job-ids.d.ts +66 -17
  240. package/dist/schedules/job-ids.js +81 -13
  241. package/dist/schedules/run-tracker.d.ts +0 -5
  242. package/dist/schedules/run-tracker.js +2 -14
  243. package/dist/schedules/scan.job.d.ts +13 -32
  244. package/dist/schedules/scan.job.js +50 -164
  245. package/dist/schedules/schedule-math.d.ts +42 -0
  246. package/dist/schedules/schedule-math.js +52 -0
  247. package/dist/schedules/scheduled-tasks.service.d.ts +22 -6
  248. package/dist/schedules/scheduled-tasks.service.js +70 -14
  249. package/dist/schedules/schedules.controller.d.ts +5 -1
  250. package/dist/schedules/schedules.controller.js +15 -1
  251. package/dist/schedules/schedules.module.js +0 -4
  252. package/dist/schedules/schedules.schemas.d.ts +35 -0
  253. package/dist/schedules/schedules.schemas.js +27 -0
  254. package/dist/schedules/store.d.ts +5 -0
  255. package/dist/schedules/store.js +20 -0
  256. package/dist/skills/changes.controller.d.ts +1 -1
  257. package/dist/skills/changes.controller.js +1 -1
  258. package/dist/tools/approvals.service.d.ts +41 -11
  259. package/dist/tools/approvals.service.js +108 -14
  260. package/dist/tools/builtin-names.d.ts +5 -3
  261. package/dist/tools/builtin-names.js +12 -4
  262. package/dist/tools/builtins/builtin-tools.d.ts +1 -2
  263. package/dist/tools/builtins/builtin-tools.js +21 -17
  264. package/dist/tools/builtins/sandbox/bash.js +9 -3
  265. package/dist/tools/builtins/sandbox/common.d.ts +18 -1
  266. package/dist/tools/builtins/sandbox/common.js +14 -1
  267. package/dist/tools/builtins/sandbox/index.d.ts +2 -1
  268. package/dist/tools/builtins/sandbox/index.js +4 -1
  269. package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
  270. package/dist/tools/builtins/sandbox/task-list.js +130 -0
  271. package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
  272. package/dist/tools/builtins/sandbox/task-output.js +21 -3
  273. package/dist/tools/builtins/web/extract.d.ts +2 -4
  274. package/dist/tools/builtins/web/extract.js +2 -5
  275. package/dist/tools/pipeline.d.ts +12 -3
  276. package/dist/tools/pipeline.js +6 -1
  277. package/dist/tools/questions.service.d.ts +30 -10
  278. package/dist/tools/questions.service.js +41 -27
  279. package/dist/tools/registry.d.ts +12 -3
  280. package/dist/tools/registry.js +13 -3
  281. package/dist/tools/tools.module.js +4 -3
  282. package/package.json +3 -3
  283. package/src/migrations/0118_device_policy_ops.sql +6 -0
  284. package/src/migrations/0119_outbox.sql +22 -0
  285. package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
  286. package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
  287. package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
  288. package/src/migrations/0123_pgvector.sql +27 -0
  289. package/src/migrations/0124_conversation_source.sql +36 -0
  290. package/dist/catalog/platform-migration.d.ts +0 -115
  291. package/dist/catalog/platform-migration.js +0 -583
  292. package/dist/infra/queue/add-job-once.d.ts +0 -14
  293. package/dist/infra/queue/add-job-once.js +0 -16
@@ -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
@@ -1,4 +1,4 @@
1
- import { type FormAnswer, type InputForm, type Logger } from "@capybara-hq/plugin-sdk";
1
+ import { type ConversationSource, type FormAnswer, type InputForm, type Logger } from "@capybara-hq/plugin-sdk";
2
2
  import type { AgentDefinition } from "../agents/definition.js";
3
3
  import { EventLog } from "../conversations/event-log.js";
4
4
  import type { KernelSchemaDb } from "../db/kernel-schema.js";
@@ -222,8 +222,17 @@ export interface ToolStepContext {
222
222
  /** 运行所用的 agent 版本(审计链记录)。 */
223
223
  agentVersion: number;
224
224
  agent: AgentDefinition;
225
- /** 对话发起人(取个人凭据、审计 actor)。 */
225
+ /** The run's trigger account (personal credentials, the audit actor). */
226
226
  accountId: string;
227
+ /**
228
+ * The conversation's source (memory plugin spec §6.2): extension tools limited by
229
+ * `spec.sources` are resolved with it, and plugin handlers receive it.
230
+ */
231
+ source: ConversationSource;
232
+ /** The conversation's owner (`conversations.account_id`), given to plugin handlers. */
233
+ ownerAccountId: string;
234
+ /** The trigger account's current org role, given to plugin handlers. */
235
+ role: OrgRole;
227
236
  /** run 的中止信号(用户中止 / 截止时间)。 */
228
237
  signal: AbortSignal;
229
238
  /** run 的截止时间(epoch 毫秒);传给内置工具(沙箱命令据此收紧超时,Ruling M20)。 */
@@ -310,7 +319,7 @@ export declare class ToolPipeline {
310
319
  runStep(given: ToolStepContext): Promise<ToolStepOutcome>;
311
320
  /**
312
321
  * 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
322
+ * its deadline, a failure, an orphan repair) still owes its log: the report of its fork invocation
314
323
  * (phase D, `pairStoppedForks`; a still active fork child is stopped by the caller after its
315
324
  * commit). An `Agent` call has its result from its dispatch (agent map spec §4.1), so there is
316
325
  * nothing to write for it. Returns the fork's call id (the caller must not write a "not executed"
@@ -314,6 +314,7 @@ let ToolPipeline = class ToolPipeline {
314
314
  forkChild: ctx.parent?.forSkill === true,
315
315
  sandboxConversationId: ctx.parent?.rootConversationId ?? ctx.conversationId,
316
316
  replay: ctx.conversationKind === "eval",
317
+ source: ctx.source,
317
318
  });
318
319
  }
319
320
  return resolved;
@@ -331,7 +332,7 @@ let ToolPipeline = class ToolPipeline {
331
332
  }
332
333
  /**
333
334
  * 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
335
+ * its deadline, a failure, an orphan repair) still owes its log: the report of its fork invocation
335
336
  * (phase D, `pairStoppedForks`; a still active fork child is stopped by the caller after its
336
337
  * commit). An `Agent` call has its result from its dispatch (agent map spec §4.1), so there is
337
338
  * nothing to write for it. Returns the fork's call id (the caller must not write a "not executed"
@@ -1001,6 +1002,10 @@ let ToolPipeline = class ToolPipeline {
1001
1002
  conversationId: ctx.conversationId,
1002
1003
  callId,
1003
1004
  ...(resume !== undefined && { resume }),
1005
+ // who and where (memory plugin spec §6.2): `accountId` stays the trigger account
1006
+ source: ctx.source,
1007
+ ownerAccountId: ctx.ownerAccountId,
1008
+ role: ctx.role,
1004
1009
  ...(tool.source.kind === "builtin" && stepTools !== undefined && { tools: stepTools }),
1005
1010
  ...(tool.source.kind === "builtin" && { runId: ctx.runId }),
1006
1011
  ...(tool.source.kind === "builtin" &&
@@ -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>` 幂等。
@@ -98,11 +105,22 @@ export declare class QuestionsService {
98
105
  */
99
106
  cancelPendingIn(runId: string): Promise<QuestionRecord[]>;
100
107
  /**
101
- * 在调用方的事务内(`mandatory`)取消某组织里由 `accountId` 发起的运行上的全部 pending 输入请求(只有运行发起人能回答,
102
- * 即「其作为处理人」的提问;成员离职时调用),走与 {@link cancelPendingIn} 相同的取消路径:置 cancelled、
103
- * 写 `question_answered{cancelled}`。返回被取消的记录与各会话写到的最大 seq;调用方提交后发布事件。
108
+ * The conversations of `orgId` holding a pending question of a run `accountId` started (only the
109
+ * run's starter answers), read without locks: offboarding locks them, by tree root first
110
+ * (`lockConversationTrees`), before {@link cancelPendingFor}.
104
111
  */
105
- cancelPendingFor(orgId: string, accountId: string): Promise<{
112
+ pendingConversationsFor(orgId: string, accountId: string): Promise<string[]>;
113
+ /**
114
+ * In the caller's transaction (`mandatory`; offboarding): cancels every pending question of a run
115
+ * `accountId` started in `orgId` within `conversationIds`, the conversations the caller already
116
+ * locked (by tree root first, `lockConversationTrees`; lock order D7: conversation, then question
117
+ * rows), along the same path as {@link cancelPendingIn}: `cancelled`,
118
+ * `question_answered{cancelled}`. A question asked meanwhile in another conversation waits for
119
+ * its run's stop (offboarding owes it), so no conversation is locked after a row. Returns the
120
+ * cancelled records and the highest seq written per conversation; the caller publishes after the
121
+ * commit.
122
+ */
123
+ cancelPendingFor(orgId: string, accountId: string, conversationIds: readonly string[]): Promise<{
106
124
  records: QuestionRecord[];
107
125
  seqs: Map<string, number>;
108
126
  }>;
@@ -113,8 +131,10 @@ export declare class QuestionsService {
113
131
  * by a run of it that is `running` or `waiting` and was started by `accountId`. The caller holds
114
132
  * the conversation row lock (its `appendIn`), so only questions logged before the message are
115
133
  * 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.
134
+ * The caller resumes their waiting runs in this transaction after it locked every row it skips
135
+ * (`ConversationsService.skipWaitsIn`: conversations, decided rows, runs). Returns the records and
136
+ * the highest seq written (null: none); the caller notifies and emits `question.answered` after
137
+ * the commit.
118
138
  */
119
139
  skipPendingIn(conversationId: string, accountId: string, messageSeq: number): Promise<{
120
140
  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
  /**
@@ -315,14 +324,12 @@ let QuestionsService = class QuestionsService {
315
324
  return out;
316
325
  }
317
326
  /**
318
- * 在调用方的事务内(`mandatory`)取消某组织里由 `accountId` 发起的运行上的全部 pending 输入请求(只有运行发起人能回答,
319
- * 即「其作为处理人」的提问;成员离职时调用),走与 {@link cancelPendingIn} 相同的取消路径:置 cancelled、
320
- * 写 `question_answered{cancelled}`。返回被取消的记录与各会话写到的最大 seq;调用方提交后发布事件。
327
+ * The conversations of `orgId` holding a pending question of a run `accountId` started (only the
328
+ * run's starter answers), read without locks: offboarding locks them, by tree root first
329
+ * (`lockConversationTrees`), before {@link cancelPendingFor}.
321
330
  */
322
- async cancelPendingFor(orgId, accountId) {
323
- const t = this.db.db;
324
- // the conversations first, in id order (lock order D7: conversation, then question rows)
325
- const convs = await t
331
+ async pendingConversationsFor(orgId, accountId) {
332
+ const rows = await this.db
326
333
  .selectFrom("questions")
327
334
  .select("conversation_id")
328
335
  .distinct()
@@ -330,24 +337,28 @@ let QuestionsService = class QuestionsService {
330
337
  .where("status", "=", "pending")
331
338
  .where("run_id", "in", (eb) => eb.selectFrom("runs").select("id").where("trigger_account_id", "=", accountId))
332
339
  .execute();
333
- if (convs.length > 0) {
334
- await t
335
- .selectFrom("conversations")
336
- .select("id")
337
- .where("id", "in", convs.map((c) => c.conversation_id))
338
- .orderBy("id")
339
- .forUpdate()
340
- .execute();
341
- }
342
- // only the conversations locked above: a question asked meanwhile in another one waits for
343
- // the run's stop (offboarding aborts it) or the sweep, so no conversation is locked after a row
340
+ return rows.map((r) => r.conversation_id);
341
+ }
342
+ /**
343
+ * In the caller's transaction (`mandatory`; offboarding): cancels every pending question of a run
344
+ * `accountId` started in `orgId` within `conversationIds`, the conversations the caller already
345
+ * locked (by tree root first, `lockConversationTrees`; lock order D7: conversation, then question
346
+ * rows), along the same path as {@link cancelPendingIn}: `cancelled`,
347
+ * `question_answered{cancelled}`. A question asked meanwhile in another conversation waits for
348
+ * its run's stop (offboarding owes it), so no conversation is locked after a row. Returns the
349
+ * cancelled records and the highest seq written per conversation; the caller publishes after the
350
+ * commit.
351
+ */
352
+ async cancelPendingFor(orgId, accountId, conversationIds) {
353
+ const t = this.db.db;
354
+ const convs = [...conversationIds];
344
355
  if (convs.length === 0)
345
356
  return { records: [], seqs: new Map() };
346
357
  const rows = await t
347
358
  .selectFrom("questions")
348
359
  .selectAll()
349
360
  .where("org_id", "=", orgId)
350
- .where("conversation_id", "in", convs.map((c) => c.conversation_id))
361
+ .where("conversation_id", "in", convs)
351
362
  .where("status", "=", "pending")
352
363
  .where("run_id", "in", (eb) => eb.selectFrom("runs").select("id").where("trigger_account_id", "=", accountId))
353
364
  .orderBy("created_at")
@@ -375,8 +386,10 @@ let QuestionsService = class QuestionsService {
375
386
  * by a run of it that is `running` or `waiting` and was started by `accountId`. The caller holds
376
387
  * the conversation row lock (its `appendIn`), so only questions logged before the message are
377
388
  * 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.
389
+ * The caller resumes their waiting runs in this transaction after it locked every row it skips
390
+ * (`ConversationsService.skipWaitsIn`: conversations, decided rows, runs). Returns the records and
391
+ * the highest seq written (null: none); the caller notifies and emits `question.answered` after
392
+ * the commit.
380
393
  */
381
394
  async skipPendingIn(conversationId, accountId, messageSeq) {
382
395
  const t = this.db.db;
@@ -451,6 +464,7 @@ QuestionsService = __decorate([
451
464
  __param(1, Inject(KernelConfigService)),
452
465
  __param(2, Inject(EventLog)),
453
466
  __param(3, Inject(KernelEvents)),
454
- __param(4, Inject(TransactionHost))
467
+ __param(4, Inject(TransactionHost)),
468
+ __param(5, Inject(RunResume))
455
469
  ], QuestionsService);
456
470
  export { QuestionsService };
@@ -1,4 +1,4 @@
1
- import type { Connector, HookParentScope, InputRequired, NetworkBlockedHost, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
1
+ import type { Connector, ConversationSource, HookParentScope, InputRequired, NetworkBlockedHost, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
2
2
  import { type AgentDefinition, type DisabledToolRef, type SubagentTools } from "../agents/definition.js";
3
3
  import type { ToolView } from "../conversations/events.js";
4
4
  import { EffectiveConfigs } from "../extensions/effective-config.js";
@@ -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
  /**
@@ -211,6 +213,8 @@ export declare function withoutDisplayFields<T>(spec: T): T;
211
213
  export declare function specHash(spec: unknown): string;
212
214
  /** 由注解推导默认策略:只读 → allow;其余(破坏性 / 无注解)→ ask。 */
213
215
  export declare function policyFromAnnotations(a: ToolAnnotations | undefined): "allow" | "ask";
216
+ /** Why an extension tool limited by `spec.sources` is not offered in a `source` conversation. */
217
+ export declare const sourceHiddenReason: (source: ConversationSource) => string;
214
218
  /** `forAgent` 的结果。 */
215
219
  export interface AgentTools {
216
220
  tools: ResolvedTool[];
@@ -236,7 +240,7 @@ export interface AgentTools {
236
240
  *
237
241
  * What is offered:
238
242
  * - 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;
243
+ * says why not), `TaskOutput` / `TaskStop` / `TaskList` with `Bash`, `Agent` while its roster is not empty;
240
244
  * - extension tools: every tool registered by an extension loaded on this replica and enabled in
241
245
  * the org (`extension_org_configs.enabled`; required bootstrap plugins are enabled in every org,
242
246
  * `enabled.ts`), whose effective config for the org is valid (layering spec A §6) and that the
@@ -360,6 +364,11 @@ export declare class ToolRegistry {
360
364
  sandboxConversationId?: string;
361
365
  /** An eval replay's listing: its calls are never executed (the sandbox tools are listed). */
362
366
  replay?: boolean;
367
+ /**
368
+ * The conversation's source (memory plugin spec §6.2): an extension tool whose
369
+ * `spec.sources` leaves it out is hidden. Required, so nothing defaults to the widest.
370
+ */
371
+ source: ConversationSource;
363
372
  }): Promise<AgentTools>;
364
373
  /** 是否启用 `ToolSearch`(见 {@link forAgent});先比阈值,超过才读组织设置。 */
365
374
  private searchActive;
@@ -380,7 +389,7 @@ export declare class ToolRegistry {
380
389
  }
381
390
  /**
382
391
  * `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
392
+ * and with `allow` only its keys kept. `TaskOutput` / `TaskStop` / `TaskList` follow `Bash`, as they come with
384
393
  * it. Without a filter the list is returned as it is.
385
394
  */
386
395
  export declare function filterSubagentTools(tools: ResolvedTool[], filter: SubagentTools | undefined): ResolvedTool[];
@@ -59,6 +59,8 @@ export function specHash(spec) {
59
59
  export function policyFromAnnotations(a) {
60
60
  return a?.readOnlyHint === true ? "allow" : "ask";
61
61
  }
62
+ /** Why an extension tool limited by `spec.sources` is not offered in a `source` conversation. */
63
+ export const sourceHiddenReason = (source) => `Not available in ${source} conversations`;
62
64
  // `ToolSearch` is added by the registry by org setting and threshold, `Skill` when the log has a
63
65
  // skills catalog snapshot; neither comes from agent definitions (`./builtin-names.ts`).
64
66
  /** 工具定义(模型可见三项)的估算 token 数(与请求估算同一口径)。 */
@@ -92,7 +94,7 @@ const builtinTool = (b, spec) => ({
92
94
  *
93
95
  * What is offered:
94
96
  * - 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;
97
+ * says why not), `TaskOutput` / `TaskStop` / `TaskList` with `Bash`, `Agent` while its roster is not empty;
96
98
  * - extension tools: every tool registered by an extension loaded on this replica and enabled in
97
99
  * the org (`extension_org_configs.enabled`; required bootstrap plugins are enabled in every org,
98
100
  * `enabled.ts`), whose effective config for the org is valid (layering spec A §6) and that the
@@ -348,6 +350,7 @@ let ToolRegistry = class ToolRegistry {
348
350
  }
349
351
  async resolve(orgId, def) {
350
352
  const disabled = def.disabledTools ?? [];
353
+ const { source } = def;
351
354
  const scope = {
352
355
  orgId,
353
356
  ...(def.sandboxConversationId !== undefined && {
@@ -396,7 +399,8 @@ let ToolRegistry = class ToolRegistry {
396
399
  spec = agentToolSpec(roster, await this.maxConcurrentSubagents(orgId));
397
400
  }
398
401
  add(builtinTool(b, spec));
399
- // `TaskOutput` and `TaskStop` come with an available `Bash` (background Bash spec §2)
402
+ // `TaskOutput`, `TaskStop` and `TaskList` come with an available `Bash` (background Bash
403
+ // spec §2)
400
404
  if (name === BUILTIN.Bash) {
401
405
  for (const companion of BASH_COMPANION_TOOLS) {
402
406
  const c = this.builtins.find((x) => x.spec.name === companion);
@@ -434,6 +438,12 @@ let ToolRegistry = class ToolRegistry {
434
438
  hidden.push({ ref: key, reason: toolTurnedOffReason(key) });
435
439
  continue;
436
440
  }
441
+ // offered only in some conversation sources; the source never changes, so this also
442
+ // refuses a resumed call (the pipeline resolves the step's tools with the same source)
443
+ if (t.spec.sources !== undefined && !t.spec.sources.includes(source)) {
444
+ hidden.push({ ref: key, reason: sourceHiddenReason(source) });
445
+ continue;
446
+ }
437
447
  if (!(await configuredIn(t.plugin))) {
438
448
  hidden.push({ ref: key, reason: `Plugin ${t.plugin} ${NEEDS_CONFIG_REASON}` });
439
449
  continue;
@@ -585,7 +595,7 @@ ToolRegistry = __decorate([
585
595
  export { ToolRegistry };
586
596
  /**
587
597
  * `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
598
+ * and with `allow` only its keys kept. `TaskOutput` / `TaskStop` / `TaskList` follow `Bash`, as they come with
589
599
  * it. Without a filter the list is returned as it is.
590
600
  */
591
601
  export function filterSubagentTools(tools, filter) {