@loopingai/core 0.5.0 → 0.6.0

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 (304) hide show
  1. package/README.md +6 -4
  2. package/dist/a2a/agent-stub.d.ts +0 -1
  3. package/dist/a2a/agent-stub.js +0 -1
  4. package/dist/a2a/caller.d.ts +0 -1
  5. package/dist/a2a/caller.js +0 -1
  6. package/dist/a2a/card.d.ts +0 -1
  7. package/dist/a2a/card.js +0 -1
  8. package/dist/a2a/context.d.ts +0 -1
  9. package/dist/a2a/context.js +0 -1
  10. package/dist/a2a/executor.d.ts +0 -1
  11. package/dist/a2a/executor.js +0 -1
  12. package/dist/a2a/index.d.ts +0 -1
  13. package/dist/a2a/index.js +0 -1
  14. package/dist/a2a/notify.d.ts +4 -4
  15. package/dist/a2a/notify.js +4 -4
  16. package/dist/a2a/parts.d.ts +0 -1
  17. package/dist/a2a/parts.js +0 -1
  18. package/dist/a2a/push.d.ts +0 -1
  19. package/dist/a2a/push.js +0 -1
  20. package/dist/a2a/task-store.d.ts +0 -1
  21. package/dist/a2a/task-store.js +0 -1
  22. package/dist/a2a/task.d.ts +0 -1
  23. package/dist/a2a/task.js +0 -1
  24. package/dist/a2a/verify.d.ts +0 -1
  25. package/dist/a2a/verify.js +0 -1
  26. package/dist/agent/anthropic/index.d.ts +15 -0
  27. package/dist/agent/anthropic/index.js +19 -0
  28. package/dist/agent/anthropic/language-model.d.ts +59 -0
  29. package/dist/agent/anthropic/language-model.js +442 -0
  30. package/dist/agent/anthropic/prompt.d.ts +84 -0
  31. package/dist/agent/anthropic/prompt.js +541 -0
  32. package/dist/agent/anthropic/runtime.d.ts +79 -0
  33. package/dist/agent/anthropic/runtime.js +130 -0
  34. package/dist/agent/budget.d.ts +0 -1
  35. package/dist/agent/budget.js +0 -1
  36. package/dist/agent/control.d.ts +0 -1
  37. package/dist/agent/control.js +10 -10
  38. package/dist/agent/errors.d.ts +85 -0
  39. package/dist/agent/errors.js +64 -0
  40. package/dist/agent/final-reply.d.ts +14 -14
  41. package/dist/agent/final-reply.js +28 -12
  42. package/dist/agent/history.d.ts +3 -4
  43. package/dist/agent/history.js +2 -3
  44. package/dist/agent/index.d.ts +4 -3
  45. package/dist/agent/index.js +4 -3
  46. package/dist/agent/inference.d.ts +58 -2
  47. package/dist/agent/inference.js +44 -1
  48. package/dist/agent/model.d.ts +42 -26
  49. package/dist/agent/model.js +1 -49
  50. package/dist/agent/session.d.ts +6 -8
  51. package/dist/agent/session.js +3 -4
  52. package/dist/agent/workers-ai/index.d.ts +23 -0
  53. package/dist/agent/workers-ai/index.js +23 -0
  54. package/dist/agent/workers-ai/runtime.d.ts +42 -0
  55. package/dist/agent/workers-ai/runtime.js +63 -0
  56. package/dist/config.d.ts +49 -16
  57. package/dist/config.js +30 -2
  58. package/dist/contract/index.d.ts +0 -1
  59. package/dist/contract/index.js +0 -1
  60. package/dist/contract/plugin.d.ts +63 -4
  61. package/dist/contract/plugin.js +76 -1
  62. package/dist/contract/recipe.d.ts +16 -18
  63. package/dist/contract/recipe.js +0 -1
  64. package/dist/contract/validation.d.ts +0 -1
  65. package/dist/contract/validation.js +0 -1
  66. package/dist/db/db.d.ts +0 -2
  67. package/dist/db/db.js +0 -1
  68. package/dist/db/index.d.ts +0 -1
  69. package/dist/db/index.js +0 -1
  70. package/dist/db/migrations/index.d.ts +0 -1
  71. package/dist/db/migrations/index.js +8 -2
  72. package/dist/db/models/subtasks.d.ts +24 -26
  73. package/dist/db/models/subtasks.js +33 -77
  74. package/dist/db/models/tasks.d.ts +0 -1
  75. package/dist/db/models/tasks.js +0 -1
  76. package/dist/db/schema.d.ts +2 -22
  77. package/dist/db/schema.js +2 -5
  78. package/dist/env.d.ts +0 -1
  79. package/dist/env.js +0 -1
  80. package/dist/host/agent.d.ts +58 -5
  81. package/dist/host/agent.js +63 -10
  82. package/dist/host/index.d.ts +0 -1
  83. package/dist/host/index.js +0 -1
  84. package/dist/host/plugin-host.d.ts +0 -1
  85. package/dist/host/plugin-host.js +0 -1
  86. package/dist/index.d.ts +2 -3
  87. package/dist/index.js +2 -3
  88. package/dist/platform.d.ts +74 -12
  89. package/dist/platform.js +76 -14
  90. package/dist/round/agent.d.ts +36 -32
  91. package/dist/round/agent.js +61 -90
  92. package/dist/round/index.d.ts +3 -3
  93. package/dist/round/index.js +2 -3
  94. package/dist/round/policy.d.ts +2 -3
  95. package/dist/round/policy.js +0 -1
  96. package/dist/round/subagent.d.ts +19 -2
  97. package/dist/round/subagent.js +22 -6
  98. package/dist/round/turn.d.ts +32 -14
  99. package/dist/round/turn.js +83 -17
  100. package/dist/round/workflow.d.ts +30 -9
  101. package/dist/round/workflow.js +146 -78
  102. package/dist/runtime/index.d.ts +4 -3
  103. package/dist/runtime/index.js +6 -1
  104. package/dist/runtime/tool-families.d.ts +0 -1
  105. package/dist/runtime/tool-families.js +0 -1
  106. package/dist/subagent/fingerprint.d.ts +2 -3
  107. package/dist/subagent/fingerprint.js +8 -18
  108. package/dist/subagent/index.d.ts +6 -5
  109. package/dist/subagent/index.js +8 -7
  110. package/dist/subagent/prompt.d.ts +4 -6
  111. package/dist/subagent/prompt.js +0 -9
  112. package/dist/subagent/run.d.ts +8 -2
  113. package/dist/subagent/run.js +59 -10
  114. package/dist/subagent/workspace.d.ts +0 -1
  115. package/dist/subagent/workspace.js +0 -1
  116. package/dist/subtasks/catalog.d.ts +1 -2
  117. package/dist/subtasks/catalog.js +1 -2
  118. package/dist/subtasks/decomposition.d.ts +16 -21
  119. package/dist/subtasks/decomposition.js +27 -76
  120. package/dist/subtasks/delegate.d.ts +20 -2
  121. package/dist/subtasks/delegate.js +21 -17
  122. package/dist/subtasks/index.d.ts +1 -3
  123. package/dist/subtasks/index.js +1 -3
  124. package/dist/subtasks/subtask-types.d.ts +0 -9
  125. package/dist/subtasks/subtask-types.js +0 -8
  126. package/dist/subtasks/types.d.ts +45 -71
  127. package/dist/subtasks/types.js +0 -1
  128. package/dist/testing/auth.d.ts +0 -1
  129. package/dist/testing/auth.js +0 -1
  130. package/dist/testing/do.d.ts +0 -1
  131. package/dist/testing/do.js +0 -1
  132. package/dist/testing/fake-session.d.ts +0 -1
  133. package/dist/testing/fake-session.js +0 -1
  134. package/dist/testing/fixtures.d.ts +0 -1
  135. package/dist/testing/fixtures.js +0 -1
  136. package/dist/testing/harness.d.ts +0 -1
  137. package/dist/testing/harness.js +0 -1
  138. package/dist/testing/index.d.ts +0 -1
  139. package/dist/testing/index.js +0 -1
  140. package/dist/testing/mock-model.d.ts +35 -1
  141. package/dist/testing/mock-model.js +75 -1
  142. package/dist/testing/node.d.ts +0 -1
  143. package/dist/testing/node.js +0 -1
  144. package/dist/testing/vcr-global-setup.d.ts +1 -4
  145. package/dist/testing/vcr-global-setup.js +1 -4
  146. package/dist/testing/vcr-shared.d.ts +0 -1
  147. package/dist/testing/vcr-shared.js +0 -1
  148. package/dist/testing/vcr-spec.d.ts +0 -1
  149. package/dist/testing/vcr-spec.js +0 -1
  150. package/dist/testing/vcr-store.d.ts +0 -1
  151. package/dist/testing/vcr-store.js +0 -1
  152. package/dist/testing/vcr.d.ts +0 -1
  153. package/dist/testing/vcr.js +0 -1
  154. package/dist/worker/define-agent.d.ts +0 -1
  155. package/dist/worker/define-agent.js +0 -1
  156. package/dist/worker/index.d.ts +5 -13
  157. package/dist/worker/index.js +5 -13
  158. package/package.json +20 -2
  159. package/dist/a2a/agent-stub.d.ts.map +0 -1
  160. package/dist/a2a/agent-stub.js.map +0 -1
  161. package/dist/a2a/caller.d.ts.map +0 -1
  162. package/dist/a2a/caller.js.map +0 -1
  163. package/dist/a2a/card.d.ts.map +0 -1
  164. package/dist/a2a/card.js.map +0 -1
  165. package/dist/a2a/context.d.ts.map +0 -1
  166. package/dist/a2a/context.js.map +0 -1
  167. package/dist/a2a/executor.d.ts.map +0 -1
  168. package/dist/a2a/executor.js.map +0 -1
  169. package/dist/a2a/index.d.ts.map +0 -1
  170. package/dist/a2a/index.js.map +0 -1
  171. package/dist/a2a/notify.d.ts.map +0 -1
  172. package/dist/a2a/notify.js.map +0 -1
  173. package/dist/a2a/parts.d.ts.map +0 -1
  174. package/dist/a2a/parts.js.map +0 -1
  175. package/dist/a2a/push.d.ts.map +0 -1
  176. package/dist/a2a/push.js.map +0 -1
  177. package/dist/a2a/task-store.d.ts.map +0 -1
  178. package/dist/a2a/task-store.js.map +0 -1
  179. package/dist/a2a/task.d.ts.map +0 -1
  180. package/dist/a2a/task.js.map +0 -1
  181. package/dist/a2a/verify.d.ts.map +0 -1
  182. package/dist/a2a/verify.js.map +0 -1
  183. package/dist/agent/budget.d.ts.map +0 -1
  184. package/dist/agent/budget.js.map +0 -1
  185. package/dist/agent/control.d.ts.map +0 -1
  186. package/dist/agent/control.js.map +0 -1
  187. package/dist/agent/final-reply.d.ts.map +0 -1
  188. package/dist/agent/final-reply.js.map +0 -1
  189. package/dist/agent/history.d.ts.map +0 -1
  190. package/dist/agent/history.js.map +0 -1
  191. package/dist/agent/index.d.ts.map +0 -1
  192. package/dist/agent/index.js.map +0 -1
  193. package/dist/agent/inference.d.ts.map +0 -1
  194. package/dist/agent/inference.js.map +0 -1
  195. package/dist/agent/model.d.ts.map +0 -1
  196. package/dist/agent/model.js.map +0 -1
  197. package/dist/agent/session.d.ts.map +0 -1
  198. package/dist/agent/session.js.map +0 -1
  199. package/dist/config.d.ts.map +0 -1
  200. package/dist/config.js.map +0 -1
  201. package/dist/contract/index.d.ts.map +0 -1
  202. package/dist/contract/index.js.map +0 -1
  203. package/dist/contract/plugin.d.ts.map +0 -1
  204. package/dist/contract/plugin.js.map +0 -1
  205. package/dist/contract/recipe.d.ts.map +0 -1
  206. package/dist/contract/recipe.js.map +0 -1
  207. package/dist/contract/validation.d.ts.map +0 -1
  208. package/dist/contract/validation.js.map +0 -1
  209. package/dist/db/db.d.ts.map +0 -1
  210. package/dist/db/db.js.map +0 -1
  211. package/dist/db/index.d.ts.map +0 -1
  212. package/dist/db/index.js.map +0 -1
  213. package/dist/db/migrations/index.d.ts.map +0 -1
  214. package/dist/db/migrations/index.js.map +0 -1
  215. package/dist/db/models/subtasks.d.ts.map +0 -1
  216. package/dist/db/models/subtasks.js.map +0 -1
  217. package/dist/db/models/tasks.d.ts.map +0 -1
  218. package/dist/db/models/tasks.js.map +0 -1
  219. package/dist/db/schema.d.ts.map +0 -1
  220. package/dist/db/schema.js.map +0 -1
  221. package/dist/env.d.ts.map +0 -1
  222. package/dist/env.js.map +0 -1
  223. package/dist/host/agent.d.ts.map +0 -1
  224. package/dist/host/agent.js.map +0 -1
  225. package/dist/host/index.d.ts.map +0 -1
  226. package/dist/host/index.js.map +0 -1
  227. package/dist/host/plugin-host.d.ts.map +0 -1
  228. package/dist/host/plugin-host.js.map +0 -1
  229. package/dist/index.d.ts.map +0 -1
  230. package/dist/index.js.map +0 -1
  231. package/dist/platform.d.ts.map +0 -1
  232. package/dist/platform.js.map +0 -1
  233. package/dist/round/agent.d.ts.map +0 -1
  234. package/dist/round/agent.js.map +0 -1
  235. package/dist/round/index.d.ts.map +0 -1
  236. package/dist/round/index.js.map +0 -1
  237. package/dist/round/policy.d.ts.map +0 -1
  238. package/dist/round/policy.js.map +0 -1
  239. package/dist/round/subagent.d.ts.map +0 -1
  240. package/dist/round/subagent.js.map +0 -1
  241. package/dist/round/turn.d.ts.map +0 -1
  242. package/dist/round/turn.js.map +0 -1
  243. package/dist/round/workflow.d.ts.map +0 -1
  244. package/dist/round/workflow.js.map +0 -1
  245. package/dist/runtime/index.d.ts.map +0 -1
  246. package/dist/runtime/index.js.map +0 -1
  247. package/dist/runtime/tool-families.d.ts.map +0 -1
  248. package/dist/runtime/tool-families.js.map +0 -1
  249. package/dist/subagent/fingerprint.d.ts.map +0 -1
  250. package/dist/subagent/fingerprint.js.map +0 -1
  251. package/dist/subagent/index.d.ts.map +0 -1
  252. package/dist/subagent/index.js.map +0 -1
  253. package/dist/subagent/prompt.d.ts.map +0 -1
  254. package/dist/subagent/prompt.js.map +0 -1
  255. package/dist/subagent/run.d.ts.map +0 -1
  256. package/dist/subagent/run.js.map +0 -1
  257. package/dist/subagent/workspace.d.ts.map +0 -1
  258. package/dist/subagent/workspace.js.map +0 -1
  259. package/dist/subtasks/catalog.d.ts.map +0 -1
  260. package/dist/subtasks/catalog.js.map +0 -1
  261. package/dist/subtasks/decomposition.d.ts.map +0 -1
  262. package/dist/subtasks/decomposition.js.map +0 -1
  263. package/dist/subtasks/delegate.d.ts.map +0 -1
  264. package/dist/subtasks/delegate.js.map +0 -1
  265. package/dist/subtasks/index.d.ts.map +0 -1
  266. package/dist/subtasks/index.js.map +0 -1
  267. package/dist/subtasks/scheduler.d.ts +0 -49
  268. package/dist/subtasks/scheduler.d.ts.map +0 -1
  269. package/dist/subtasks/scheduler.js +0 -48
  270. package/dist/subtasks/scheduler.js.map +0 -1
  271. package/dist/subtasks/subtask-types.d.ts.map +0 -1
  272. package/dist/subtasks/subtask-types.js.map +0 -1
  273. package/dist/subtasks/types.d.ts.map +0 -1
  274. package/dist/subtasks/types.js.map +0 -1
  275. package/dist/testing/auth.d.ts.map +0 -1
  276. package/dist/testing/auth.js.map +0 -1
  277. package/dist/testing/do.d.ts.map +0 -1
  278. package/dist/testing/do.js.map +0 -1
  279. package/dist/testing/fake-session.d.ts.map +0 -1
  280. package/dist/testing/fake-session.js.map +0 -1
  281. package/dist/testing/fixtures.d.ts.map +0 -1
  282. package/dist/testing/fixtures.js.map +0 -1
  283. package/dist/testing/harness.d.ts.map +0 -1
  284. package/dist/testing/harness.js.map +0 -1
  285. package/dist/testing/index.d.ts.map +0 -1
  286. package/dist/testing/index.js.map +0 -1
  287. package/dist/testing/mock-model.d.ts.map +0 -1
  288. package/dist/testing/mock-model.js.map +0 -1
  289. package/dist/testing/node.d.ts.map +0 -1
  290. package/dist/testing/node.js.map +0 -1
  291. package/dist/testing/vcr-global-setup.d.ts.map +0 -1
  292. package/dist/testing/vcr-global-setup.js.map +0 -1
  293. package/dist/testing/vcr-shared.d.ts.map +0 -1
  294. package/dist/testing/vcr-shared.js.map +0 -1
  295. package/dist/testing/vcr-spec.d.ts.map +0 -1
  296. package/dist/testing/vcr-spec.js.map +0 -1
  297. package/dist/testing/vcr-store.d.ts.map +0 -1
  298. package/dist/testing/vcr-store.js.map +0 -1
  299. package/dist/testing/vcr.d.ts.map +0 -1
  300. package/dist/testing/vcr.js.map +0 -1
  301. package/dist/worker/define-agent.d.ts.map +0 -1
  302. package/dist/worker/define-agent.js.map +0 -1
  303. package/dist/worker/index.d.ts.map +0 -1
  304. package/dist/worker/index.js.map +0 -1
@@ -160,9 +160,9 @@ export interface AgentPlugin<TRuntime = SubtaskRuntime> {
160
160
  *
161
161
  * A plugin that declares a {@link subtaskType} should put its capability block
162
162
  * on the *type* instead ({@link SubtaskTypeSpec.capability}) and leave this
163
- * unset the two are rendered by different call sites, so declaring both
164
- * makes the main agent read the same advice twice per round. Which is the
165
- * exact failure the type's own prompt fields were introduced to end.
163
+ * unset. Both are rendered, so declaring both makes the main agent read the
164
+ * same advice twice per round the exact failure the type's own prompt
165
+ * fields were introduced to end.
166
166
  */
167
167
  capability?: string;
168
168
  /**
@@ -262,4 +262,63 @@ export interface AgentPlugin<TRuntime = SubtaskRuntime> {
262
262
  export declare function definePlugin<TRuntime = SubtaskRuntime>(plugin: Omit<AgentPlugin<TRuntime>, "contractVersion"> & {
263
263
  contractVersion?: number;
264
264
  }): AgentPlugin<TRuntime>;
265
- //# sourceMappingURL=plugin.d.ts.map
265
+ /** See {@link restrictMainAgentTools}. */
266
+ export interface RestrictMainAgentToolsOptions {
267
+ /**
268
+ * The tool names the main agent keeps. `[]` removes the hook entirely, which
269
+ * is the "orchestrator with no hands" case.
270
+ *
271
+ * Names rather than a predicate, because the point is to state the surface
272
+ * explicitly at the install site: a reader of `plugins.ts` should be able to
273
+ * see what the main agent can do without opening the plugin.
274
+ */
275
+ allow: readonly string[];
276
+ /**
277
+ * What the main agent is told instead of the plugin's own
278
+ * {@link AgentPlugin.capability}.
279
+ *
280
+ * Required in spirit whenever `allow` is non-empty: the plugin's block
281
+ * describes its *whole* surface, so leaving it in place tells the model about
282
+ * tools it no longer has — which it then tries to call. Omit it to say
283
+ * nothing, which is right for `allow: []`.
284
+ */
285
+ capability?: string;
286
+ }
287
+ /**
288
+ * A plugin whose tool families stay registered but whose main-agent surface is
289
+ * narrowed, or removed.
290
+ *
291
+ * ## Why this has to exist
292
+ *
293
+ * `validateRecipe` runs on the **parent** and drops any tool family no installed
294
+ * plugin registered. So an agent that wants "my subagents can run a shell, I
295
+ * cannot" has a problem: uninstalling the sandbox plugin from the parent also
296
+ * deletes `sandbox` from every recipe the parent validates, and the subagents
297
+ * silently lose it. The parent has to install the plugin and decline its tools,
298
+ * which is exactly what this expresses.
299
+ *
300
+ * ```ts
301
+ * // The parent registers the family, and gets three read-only tools from it.
302
+ * restrictMainAgentTools(sandbox(cfg), {
303
+ * allow: ["sb_read", "sb_ls", "sb_exists"],
304
+ * capability: "You can read the checkout, not change it."
305
+ * })
306
+ * ```
307
+ *
308
+ * ## What passes through untouched
309
+ *
310
+ * Everything else: `toolFamilies`, `subtaskType`, `resolveRuntime`, `store`,
311
+ * `requires`, and the lifecycle hooks. In particular `requires` must survive, or
312
+ * a parent that stops offering `sb_exec` also stops asserting that the `Sandbox`
313
+ * binding exists — and the failure moves from DO start to a subagent's first
314
+ * tool call.
315
+ *
316
+ * ## Why a missing name logs instead of throwing
317
+ *
318
+ * `mainAgentTools` is async and called per turn, so a throw here lands inside a
319
+ * request someone is waiting on — a bad trade for what is always a typo. The
320
+ * error names the plugin and the tool so it is greppable, and the honest place
321
+ * to catch it is a test over the assembled surface, which is cheap to write and
322
+ * fails the build instead.
323
+ */
324
+ export declare function restrictMainAgentTools<TRuntime = SubtaskRuntime>(plugin: AgentPlugin<TRuntime>, options: RestrictMainAgentToolsOptions): AgentPlugin<TRuntime>;
@@ -36,4 +36,79 @@ export function definePlugin(plugin) {
36
36
  contractVersion: plugin.contractVersion ?? PLUGIN_CONTRACT_VERSION
37
37
  };
38
38
  }
39
- //# sourceMappingURL=plugin.js.map
39
+ /**
40
+ * A plugin whose tool families stay registered but whose main-agent surface is
41
+ * narrowed, or removed.
42
+ *
43
+ * ## Why this has to exist
44
+ *
45
+ * `validateRecipe` runs on the **parent** and drops any tool family no installed
46
+ * plugin registered. So an agent that wants "my subagents can run a shell, I
47
+ * cannot" has a problem: uninstalling the sandbox plugin from the parent also
48
+ * deletes `sandbox` from every recipe the parent validates, and the subagents
49
+ * silently lose it. The parent has to install the plugin and decline its tools,
50
+ * which is exactly what this expresses.
51
+ *
52
+ * ```ts
53
+ * // The parent registers the family, and gets three read-only tools from it.
54
+ * restrictMainAgentTools(sandbox(cfg), {
55
+ * allow: ["sb_read", "sb_ls", "sb_exists"],
56
+ * capability: "You can read the checkout, not change it."
57
+ * })
58
+ * ```
59
+ *
60
+ * ## What passes through untouched
61
+ *
62
+ * Everything else: `toolFamilies`, `subtaskType`, `resolveRuntime`, `store`,
63
+ * `requires`, and the lifecycle hooks. In particular `requires` must survive, or
64
+ * a parent that stops offering `sb_exec` also stops asserting that the `Sandbox`
65
+ * binding exists — and the failure moves from DO start to a subagent's first
66
+ * tool call.
67
+ *
68
+ * ## Why a missing name logs instead of throwing
69
+ *
70
+ * `mainAgentTools` is async and called per turn, so a throw here lands inside a
71
+ * request someone is waiting on — a bad trade for what is always a typo. The
72
+ * error names the plugin and the tool so it is greppable, and the honest place
73
+ * to catch it is a test over the assembled surface, which is cheap to write and
74
+ * fails the build instead.
75
+ */
76
+ export function restrictMainAgentTools(plugin, options) {
77
+ const allow = new Set(options.allow);
78
+ // Spread first so anything added to `AgentPlugin` later keeps flowing through
79
+ // without an edit here — the failure mode of an explicit field list is a
80
+ // capability that silently stops reaching the parent.
81
+ const restricted = { ...plugin };
82
+ if (allow.size === 0) {
83
+ delete restricted.mainAgentTools;
84
+ }
85
+ else {
86
+ // Installed even when the plugin offers no `mainAgentTools` at all, rather
87
+ // than left `undefined`. A plugin with no main-agent surface is not the
88
+ // "nothing to narrow" case it looks like — it is an allowlist naming tools
89
+ // that do not exist, which is precisely the typo this helper promises to
90
+ // report, and the one shape of it that is hardest to spot: a whole surface
91
+ // removed or renamed disappears without a word. The assembled tool set is
92
+ // identical either way, because `runtime.mainAgentTools` merges each
93
+ // plugin's result and an empty one contributes nothing.
94
+ const inner = plugin.mainAgentTools;
95
+ restricted.mainAgentTools = async (ctx) => {
96
+ const all = inner ? await inner(ctx) : {};
97
+ const kept = {};
98
+ for (const name of allow) {
99
+ if (name in all)
100
+ kept[name] = all[name];
101
+ else
102
+ console.error(`[plugin] "${plugin.key}" offers no main-agent tool "${name}" — ` +
103
+ "the allowlist names a tool that does not exist, so the main " +
104
+ "agent is quietly missing it. Check for a rename.");
105
+ }
106
+ return kept;
107
+ };
108
+ }
109
+ if (options.capability === undefined)
110
+ delete restricted.capability;
111
+ else
112
+ restricted.capability = options.capability;
113
+ return restricted;
114
+ }
@@ -48,12 +48,10 @@ export interface DelegationNames {
48
48
  *
49
49
  * Deliberately *not* here: how a run is sliced into durable chunks. That is a
50
50
  * Workers step-timeout constraint, it is identical for every Recipe, and it lives
51
- * in {@link file://../platform.ts}. It used to include a `turnsPerChunk`, on the
52
- * theory that a turn count could keep a step under the timeout it cannot,
53
- * because nothing predicts how long a turn takes, and the resulting arithmetic was
54
- * wrong by 2-3× in practice.
55
- */
56
- /**
51
+ * in {@link file://../platform.ts}. A turn count cannot do that job — nothing
52
+ * predicts how long a turn takes so no per-chunk turn budget belongs on a
53
+ * Recipe.
54
+ *
57
55
  * Structurally identical to {@link AgentLimits} — a budget is a budget at both
58
56
  * levels, and keeping one shape means `resolveLimits` merges a recipe's
59
57
  * declaration straight over the host's baseline with no translation. Aliased
@@ -81,8 +79,10 @@ export interface ResolvedRecipe {
81
79
  toolFamilies: string[];
82
80
  enabled: boolean;
83
81
  /**
84
- * Only the budget fields this Recipe overrides; the rest come from
85
- * `SUBAGENT_LIMITS`. `{}` means "the baseline", which is what most Recipes want.
82
+ * Only the budget fields this Recipe overrides; the rest come from the host's
83
+ * `CoreConfig.subagentLimits`, which reaches validation as
84
+ * `RecipePolicy.baselineLimits`. `{}` means "the baseline", which is what most
85
+ * Recipes want.
86
86
  */
87
87
  limits: Partial<RecipeLimits>;
88
88
  /**
@@ -107,8 +107,8 @@ export interface ValidatedRecipe extends ResolvedRecipe {
107
107
  *
108
108
  * These exist only here, never on {@link ResolvedRecipe}, and that asymmetry
109
109
  * is the design: a recipe cannot state a model, so the only way to hold one is
110
- * to have been through {@link validateRecipe}, which copies the host's. Recipe
111
- * data has no path to influence them.
110
+ * to have been through `validateRecipe`, which copies the host's. Recipe data
111
+ * has no path to influence them.
112
112
  *
113
113
  * Guaranteed non-empty and guaranteed distinct, because `resolveConfig`
114
114
  * refuses a config that is either — so the fallback is always a genuinely
@@ -145,9 +145,9 @@ export interface SubtaskTypeSpec {
145
145
  *
146
146
  * A `z.object`, not an opaque `z.ZodType`, and that is load-bearing: the agent
147
147
  * reads `.shape` back to build the `params` field of the delegate tool's schema
148
- * (see `subtaskParamProperties`). Declaring a param the model is never shown is
149
- * the failure this shape exists to prevent — describe each key with
150
- * `.describe()`, because that text is what the model reads.
148
+ * (see `SubtaskTypeRegistry.paramProperties`). Declaring a param the model is
149
+ * never shown is the failure this shape exists to prevent — describe each key
150
+ * with `.describe()`, because that text is what the model reads.
151
151
  */
152
152
  params: SubtaskParamsSchema | null;
153
153
  /** How the model is told to obtain each param, appended to the description. */
@@ -159,10 +159,9 @@ export interface SubtaskTypeSpec {
159
159
  *
160
160
  * These two prompt fields exist to hold one rule: **everything the main agent
161
161
  * is told about a domain is declared here, never written inside the runtime.**
162
- * Both of them started as hand-written blocks in `agent/prompt.ts` and
163
- * `agent/turn.ts`; the main agent read the same advice twice per round from two
164
- * files that had already drifted into contradicting each other on how many
165
- * subtasks a multi-game request gets.
162
+ * Advice written in the runtime instead has to be repeated per call site, and
163
+ * the copies drift into telling the main agent two contradictory things
164
+ * about the same domain, twice per round.
166
165
  */
167
166
  capability?: string;
168
167
  /**
@@ -179,4 +178,3 @@ export interface SubtaskTypeSpec {
179
178
  /** The execution configuration this type runs under. */
180
179
  recipe: ResolvedRecipe;
181
180
  }
182
- //# sourceMappingURL=recipe.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=recipe.js.map
@@ -89,4 +89,3 @@ export declare class RecipeValidationError extends Error {
89
89
  * house default can guess. Both fail the recipe outright.
90
90
  */
91
91
  export declare function validateRecipe(recipe: ResolvedRecipe, policy: RecipePolicy): ValidatedRecipe;
92
- //# sourceMappingURL=validation.d.ts.map
@@ -82,4 +82,3 @@ export function validateRecipe(recipe, policy) {
82
82
  limits: resolveLimits(recipe.limits, policy.baselineLimits)
83
83
  };
84
84
  }
85
- //# sourceMappingURL=validation.js.map
package/dist/db/db.d.ts CHANGED
@@ -130,7 +130,6 @@ export declare class AgentDB {
130
130
  }): boolean;
131
131
  complete(id: import("../subtasks/types.js").SubtaskId, resultParts: import("../subtasks/types.js").SubtaskResultPart[]): boolean;
132
132
  fail(id: import("../subtasks/types.js").SubtaskId, error: string): boolean;
133
- skip(id: import("../subtasks/types.js").SubtaskId): boolean;
134
133
  cancelRunning(id: import("../subtasks/types.js").SubtaskId): boolean;
135
134
  cancelPending(taskId: string): number;
136
135
  cleanup(): void;
@@ -146,4 +145,3 @@ export declare class AgentDB {
146
145
  */
147
146
  private applyStores;
148
147
  }
149
- //# sourceMappingURL=db.d.ts.map
package/dist/db/db.js CHANGED
@@ -88,4 +88,3 @@ export class AgentDB {
88
88
  }
89
89
  }
90
90
  }
91
- //# sourceMappingURL=db.js.map
@@ -6,4 +6,3 @@ export { AgentDB, PLUGIN_MIGRATIONS_TABLE, type AgentDBOptions, type DB, type Pl
6
6
  export { notifyTasks, subtasks } from "./schema.js";
7
7
  export { makeTasks, stateOf, type TaskListQuery } from "./models/tasks.js";
8
8
  export { makeSubtasks, type SubtaskModelOptions } from "./models/subtasks.js";
9
- //# sourceMappingURL=index.d.ts.map
package/dist/db/index.js CHANGED
@@ -6,4 +6,3 @@ export { AgentDB, PLUGIN_MIGRATIONS_TABLE } from "./db.js";
6
6
  export { notifyTasks, subtasks } from "./schema.js";
7
7
  export { makeTasks, stateOf } from "./models/tasks.js";
8
8
  export { makeSubtasks } from "./models/subtasks.js";
9
- //# sourceMappingURL=index.js.map
@@ -18,4 +18,3 @@ import type { migrate } from "drizzle-orm/durable-sqlite/migrator";
18
18
  type MigrationConfig = Parameters<typeof migrate>[1];
19
19
  declare const dbMigrations: MigrationConfig;
20
20
  export default dbMigrations;
21
- //# sourceMappingURL=index.d.ts.map
@@ -6,6 +6,12 @@ const dbMigrations = {
6
6
  when: 1785537420937,
7
7
  tag: "0000_init",
8
8
  breakpoints: true
9
+ },
10
+ {
11
+ idx: 1,
12
+ when: 1786871925215,
13
+ tag: "0001_drop_depends_on",
14
+ breakpoints: true
9
15
  }
10
16
  ]
11
17
  },
@@ -47,8 +53,8 @@ CREATE TABLE \`subtasks\` (
47
53
  CREATE UNIQUE INDEX \`idx_subtasks_task_ordinal\` ON \`subtasks\` (\`task_id\`,\`ordinal\`);--> statement-breakpoint
48
54
  CREATE INDEX \`idx_subtasks_task_round\` ON \`subtasks\` (\`task_id\`,\`round\`);--> statement-breakpoint
49
55
  CREATE INDEX \`idx_subtasks_status\` ON \`subtasks\` (\`status\`);--> statement-breakpoint
50
- CREATE INDEX \`idx_subtasks_created_at\` ON \`subtasks\` (\`created_at\`);`
56
+ CREATE INDEX \`idx_subtasks_created_at\` ON \`subtasks\` (\`created_at\`);`,
57
+ m0001: `ALTER TABLE \`subtasks\` DROP COLUMN \`depends_on_json\`;`
51
58
  }
52
59
  };
53
60
  export default dbMigrations;
54
- //# sourceMappingURL=index.js.map
@@ -15,28 +15,22 @@ export interface SubtaskModelOptions {
15
15
  * durable-sqlite is synchronous; the multi-statement `createDecomposition` runs
16
16
  * inside an explicit `db.transaction` (drizzle maps it to
17
17
  * `storage.transactionSync`), so a mid-create failure rolls back every statement
18
- * instead of leaving a partial DAG. Guarded transitions filter on the expected
18
+ * instead of leaving a partial decomposition. Guarded transitions filter on the expected
19
19
  * current `status`, so a disallowed transition matches no row and is a no-op.
20
20
  */
21
21
  export declare function makeSubtasks(db: DB, opts: SubtaskModelOptions): {
22
22
  /**
23
- * Create one **round's** decomposition atomically: the entire
24
- * check-validate-insert sequence runs in one synchronous `db.transaction`, so
25
- * a failure anywhere rolls back every statement no truncated subtask set, no
26
- * nodes left with unwritten dependency edges. Idempotent on
27
- * `(taskId, round)`: if this round already has Subtasks, returns them
28
- * unchanged (a Workflow-step retry must not duplicate work); the unique
29
- * `(task_id, ordinal)` index is the schema-level backstop. Enforces the 1..8
30
- * per-round bound, unique local keys, and resolvable non-self dependency
31
- * edges, then resolves draft-local dependency keys to the SQLite-assigned
32
- * {@link SubtaskId}s. Full cycle/edge DAG validation lives in the turn
33
- * operation; this is the storage guard.
23
+ * Create one **round's** decomposition atomically: the whole check-and-insert
24
+ * sequence runs in one synchronous `db.transaction`, so a failure anywhere
25
+ * rolls back every statement rather than leaving a truncated subtask set.
26
+ * Idempotent on `(taskId, round)`: if this round already has Subtasks,
27
+ * returns them unchanged (a Workflow-step retry must not duplicate work); the
28
+ * unique `(task_id, ordinal)` index is the schema-level backstop. Enforces the
29
+ * 1..8 per-round bound as the durable guard.
34
30
  *
35
31
  * `ordinal` continues across rounds (it is the Task-wide position, and what
36
32
  * the unique index is built on), so a later round's rows sort after the
37
- * earlier ones in {@link list}. Dependency edges never cross a round: each
38
- * round's DAG is self-contained, and the drafts handed here only carry keys
39
- * from their own round.
33
+ * earlier ones in {@link list}.
40
34
  */
41
35
  createDecomposition(taskId: string, round: number, drafts: SubtaskDraft[]): Subtask[];
42
36
  /** Load one Subtask by id. */
@@ -44,9 +38,10 @@ export declare function makeSubtasks(db: DB, opts: SubtaskModelOptions): {
44
38
  /** List every round's Subtasks for a Task, in ordinal order. */
45
39
  list(taskId: string): Subtask[];
46
40
  /**
47
- * List one round's Subtasks, in ordinal order — the scheduler's view. Phase 2
48
- * drives a single round's DAG at a time, so it must not see a sibling round's
49
- * rows; composition, by contrast, reads {@link list} across all rounds.
41
+ * List one round's Subtasks, in ordinal order — what the Workflow's `scan`
42
+ * projects. It executes a single round at a time, so it must not see a
43
+ * sibling round's rows; a later round, by contrast, reads {@link list}
44
+ * across all rounds to reunite each `delegate` call with its results.
50
45
  */
51
46
  listRound(taskId: string, round: number): Subtask[];
52
47
  /**
@@ -70,17 +65,14 @@ export declare function makeSubtasks(db: DB, opts: SubtaskModelOptions): {
70
65
  * Both sides are reachable and both must land. A child's failed result arrives
71
66
  * on a `running` row. The Workflow's last resort — `failSubtask`, once
72
67
  * `execute:<id>` has exhausted every retry — can arrive on either:
73
- * `executeSubtask` may throw before its `pending -> running` claim (a
74
- * dependency-invariant fault) or after it (a transient child fault). Leaving
75
- * either behind strands the row: a `pending` node re-enters the next wave
76
- * forever, and a `running` node blocks its dependents, which {@link skip} only
77
- * propagates past *failed* prerequisites.
68
+ * `executeSubtask` may throw before its `pending -> running` claim (an
69
+ * unresolvable recipe) or after it (a transient child fault). Leaving either
70
+ * behind strands the row in a non-terminal state that nobody is coming back
71
+ * to resolve.
78
72
  *
79
73
  * Returns false once the row is terminal — a late loser to the real result.
80
74
  */
81
75
  fail(id: SubtaskId, error: string): boolean;
82
- /** Skip a Subtask blocked by a failed/skipped dependency: guarded `pending -> skipped`. */
83
- skip(id: SubtaskId): boolean;
84
76
  /**
85
77
  * Discard a late result after parent cancellation: guarded
86
78
  * `running -> canceled`. The parent calls this when a child returned a
@@ -94,9 +86,15 @@ export declare function makeSubtasks(db: DB, opts: SubtaskModelOptions): {
94
86
  * Running Subtasks are left alone here — the parent transitions those with
95
87
  * {@link cancelRunning} once their in-flight result comes back and is
96
88
  * discarded. Returns the number canceled.
89
+ *
90
+ * Keyed on the Task rather than one id, so this is the one guarded
91
+ * transition that cannot go through {@link transition} and sets its own
92
+ * timestamps. `completedAt` is part of that and not optional: every other
93
+ * terminal write records it, and a terminal row without one reads as still
94
+ * in flight to anything measuring how long a Subtask took or when a Task
95
+ * actually stopped.
97
96
  */
98
97
  cancelPending(taskId: string): number;
99
98
  /** Delete Subtasks older than 30 days (called by the weekly maintenance cron). */
100
99
  cleanup(): void;
101
100
  };
102
- //# sourceMappingURL=subtasks.d.ts.map
@@ -11,7 +11,6 @@ const resultPartSchema = z.object({
11
11
  });
12
12
  const referencesSchema = z.array(referenceSchema);
13
13
  const resultPartsSchema = z.array(resultPartSchema);
14
- const dependsOnSchema = z.array(z.number().int());
15
14
  const paramsSchema = z.record(z.string(), z.string());
16
15
  /**
17
16
  * Query methods for the `subtasks` table (durable decomposed units of work).
@@ -20,7 +19,7 @@ const paramsSchema = z.record(z.string(), z.string());
20
19
  * durable-sqlite is synchronous; the multi-statement `createDecomposition` runs
21
20
  * inside an explicit `db.transaction` (drizzle maps it to
22
21
  * `storage.transactionSync`), so a mid-create failure rolls back every statement
23
- * instead of leaving a partial DAG. Guarded transitions filter on the expected
22
+ * instead of leaving a partial decomposition. Guarded transitions filter on the expected
24
23
  * current `status`, so a disallowed transition matches no row and is a no-op.
25
24
  */
26
25
  export function makeSubtasks(db, opts) {
@@ -34,7 +33,6 @@ export function makeSubtasks(db, opts) {
34
33
  recipeVersion: row.recipeVersion,
35
34
  prompt: row.prompt,
36
35
  references: referencesSchema.parse(JSON.parse(row.referencesJson)),
37
- dependsOn: dependsOnSchema.parse(JSON.parse(row.dependsOnJson)),
38
36
  params: paramsSchema.parse(JSON.parse(row.paramsJson)),
39
37
  status: row.status,
40
38
  resultParts: row.resultPartsJson === null
@@ -74,23 +72,17 @@ export function makeSubtasks(db, opts) {
74
72
  };
75
73
  return {
76
74
  /**
77
- * Create one **round's** decomposition atomically: the entire
78
- * check-validate-insert sequence runs in one synchronous `db.transaction`, so
79
- * a failure anywhere rolls back every statement no truncated subtask set, no
80
- * nodes left with unwritten dependency edges. Idempotent on
81
- * `(taskId, round)`: if this round already has Subtasks, returns them
82
- * unchanged (a Workflow-step retry must not duplicate work); the unique
83
- * `(task_id, ordinal)` index is the schema-level backstop. Enforces the 1..8
84
- * per-round bound, unique local keys, and resolvable non-self dependency
85
- * edges, then resolves draft-local dependency keys to the SQLite-assigned
86
- * {@link SubtaskId}s. Full cycle/edge DAG validation lives in the turn
87
- * operation; this is the storage guard.
75
+ * Create one **round's** decomposition atomically: the whole check-and-insert
76
+ * sequence runs in one synchronous `db.transaction`, so a failure anywhere
77
+ * rolls back every statement rather than leaving a truncated subtask set.
78
+ * Idempotent on `(taskId, round)`: if this round already has Subtasks,
79
+ * returns them unchanged (a Workflow-step retry must not duplicate work); the
80
+ * unique `(task_id, ordinal)` index is the schema-level backstop. Enforces the
81
+ * 1..8 per-round bound as the durable guard.
88
82
  *
89
83
  * `ordinal` continues across rounds (it is the Task-wide position, and what
90
84
  * the unique index is built on), so a later round's rows sort after the
91
- * earlier ones in {@link list}. Dependency edges never cross a round: each
92
- * round's DAG is self-contained, and the drafts handed here only carry keys
93
- * from their own round.
85
+ * earlier ones in {@link list}.
94
86
  */
95
87
  createDecomposition(taskId, round, drafts) {
96
88
  return db.transaction(() => {
@@ -100,38 +92,14 @@ export function makeSubtasks(db, opts) {
100
92
  if (drafts.length < 1 || drafts.length > opts.maxSubtasks) {
101
93
  throw new Error(`decomposition must have 1..${opts.maxSubtasks} subtasks, got ${drafts.length}`);
102
94
  }
103
- // Register every local key first, rejecting duplicates. This is its own
104
- // pass because the dependency check below reads the complete key set: an
105
- // edge may point forward to a draft defined later, so every key must be
106
- // known before any edge is validated.
107
- const keys = new Set();
108
- for (const d of drafts) {
109
- if (keys.has(d.localKey)) {
110
- throw new Error(`duplicate draft local key: ${d.localKey}`);
111
- }
112
- keys.add(d.localKey);
113
- }
114
- for (const d of drafts) {
115
- for (const dep of d.dependsOn) {
116
- // A subtask cannot depend on itself.
117
- if (dep === d.localKey) {
118
- throw new Error(`subtask ${d.localKey} depends on itself`);
119
- }
120
- // Every edge must resolve to a key in this decomposition.
121
- if (!keys.has(dep)) {
122
- throw new Error(`subtask ${d.localKey} depends on unknown key: ${dep}`);
123
- }
124
- }
125
- // References must match the persisted shape before we serialize them.
95
+ // References must match the persisted shape before we serialize them.
96
+ for (const d of drafts)
126
97
  referencesSchema.parse(d.references);
127
- }
128
98
  const now = Date.now();
129
- const keyToId = new Map();
130
- // Pass 1: insert nodes (deps empty for now) to assign ids. Ordinals
131
- // continue above every earlier round's rows from the current maximum,
132
- // not a row count, so a gap left by {@link cleanup} deleting part of a
133
- // Task cannot hand a later round an ordinal that is already taken. Both
134
- // read the `(task_id, ordinal)` index; only this one stays monotonic.
99
+ // Ordinals continue above every earlier round's rows — from the current
100
+ // maximum, not a row count, so a gap left by {@link cleanup} deleting part
101
+ // of a Task cannot hand a later round an ordinal that is already taken.
102
+ // Both read the `(task_id, ordinal)` index; only this one stays monotonic.
135
103
  const highest = db
136
104
  .select({ max: max(subtasks.ordinal) })
137
105
  .from(subtasks)
@@ -139,8 +107,7 @@ export function makeSubtasks(db, opts) {
139
107
  .get()?.max ?? null;
140
108
  const firstOrdinal = highest === null ? 0 : highest + 1;
141
109
  drafts.forEach((d, offset) => {
142
- const { id } = db
143
- .insert(subtasks)
110
+ db.insert(subtasks)
144
111
  .values({
145
112
  taskId,
146
113
  round,
@@ -150,7 +117,6 @@ export function makeSubtasks(db, opts) {
150
117
  recipeVersion: null,
151
118
  prompt: d.prompt,
152
119
  referencesJson: JSON.stringify(d.references),
153
- dependsOnJson: "[]",
154
120
  paramsJson: JSON.stringify(d.params ?? {}),
155
121
  status: "pending",
156
122
  resultPartsJson: null,
@@ -159,20 +125,8 @@ export function makeSubtasks(db, opts) {
159
125
  updatedAt: now,
160
126
  completedAt: null
161
127
  })
162
- .returning({ id: subtasks.id })
163
- .get();
164
- keyToId.set(d.localKey, id);
165
- });
166
- // Pass 2: rewrite dependency edges to real ids.
167
- for (const d of drafts) {
168
- if (d.dependsOn.length === 0)
169
- continue;
170
- const ids = d.dependsOn.map((k) => keyToId.get(k));
171
- db.update(subtasks)
172
- .set({ dependsOnJson: JSON.stringify(ids) })
173
- .where(eq(subtasks.id, keyToId.get(d.localKey)))
174
128
  .run();
175
- }
129
+ });
176
130
  return listRound(taskId, round);
177
131
  });
178
132
  },
@@ -186,9 +140,10 @@ export function makeSubtasks(db, opts) {
186
140
  return list(taskId);
187
141
  },
188
142
  /**
189
- * List one round's Subtasks, in ordinal order — the scheduler's view. Phase 2
190
- * drives a single round's DAG at a time, so it must not see a sibling round's
191
- * rows; composition, by contrast, reads {@link list} across all rounds.
143
+ * List one round's Subtasks, in ordinal order — what the Workflow's `scan`
144
+ * projects. It executes a single round at a time, so it must not see a
145
+ * sibling round's rows; a later round, by contrast, reads {@link list}
146
+ * across all rounds to reunite each `delegate` call with its results.
192
147
  */
193
148
  listRound(taskId, round) {
194
149
  return listRound(taskId, round);
@@ -227,11 +182,10 @@ export function makeSubtasks(db, opts) {
227
182
  * Both sides are reachable and both must land. A child's failed result arrives
228
183
  * on a `running` row. The Workflow's last resort — `failSubtask`, once
229
184
  * `execute:<id>` has exhausted every retry — can arrive on either:
230
- * `executeSubtask` may throw before its `pending -> running` claim (a
231
- * dependency-invariant fault) or after it (a transient child fault). Leaving
232
- * either behind strands the row: a `pending` node re-enters the next wave
233
- * forever, and a `running` node blocks its dependents, which {@link skip} only
234
- * propagates past *failed* prerequisites.
185
+ * `executeSubtask` may throw before its `pending -> running` claim (an
186
+ * unresolvable recipe) or after it (a transient child fault). Leaving either
187
+ * behind strands the row in a non-terminal state that nobody is coming back
188
+ * to resolve.
235
189
  *
236
190
  * Returns false once the row is terminal — a late loser to the real result.
237
191
  */
@@ -242,10 +196,6 @@ export function makeSubtasks(db, opts) {
242
196
  completedAt: Date.now()
243
197
  });
244
198
  },
245
- /** Skip a Subtask blocked by a failed/skipped dependency: guarded `pending -> skipped`. */
246
- skip(id) {
247
- return transition(id, "pending", { status: "skipped" });
248
- },
249
199
  /**
250
200
  * Discard a late result after parent cancellation: guarded
251
201
  * `running -> canceled`. The parent calls this when a child returned a
@@ -264,12 +214,19 @@ export function makeSubtasks(db, opts) {
264
214
  * Running Subtasks are left alone here — the parent transitions those with
265
215
  * {@link cancelRunning} once their in-flight result comes back and is
266
216
  * discarded. Returns the number canceled.
217
+ *
218
+ * Keyed on the Task rather than one id, so this is the one guarded
219
+ * transition that cannot go through {@link transition} and sets its own
220
+ * timestamps. `completedAt` is part of that and not optional: every other
221
+ * terminal write records it, and a terminal row without one reads as still
222
+ * in flight to anything measuring how long a Subtask took or when a Task
223
+ * actually stopped.
267
224
  */
268
225
  cancelPending(taskId) {
269
226
  const now = Date.now();
270
227
  const canceled = db
271
228
  .update(subtasks)
272
- .set({ status: "canceled", updatedAt: now })
229
+ .set({ status: "canceled", updatedAt: now, completedAt: now })
273
230
  .where(and(eq(subtasks.taskId, taskId), eq(subtasks.status, "pending")))
274
231
  .returning({ id: subtasks.id })
275
232
  .all();
@@ -282,4 +239,3 @@ export function makeSubtasks(db, opts) {
282
239
  }
283
240
  };
284
241
  }
285
- //# sourceMappingURL=subtasks.js.map
@@ -103,4 +103,3 @@ export declare function makeTasks(db: DB): {
103
103
  /** Delete all tasks older than 30 days (called by the maintenance cron). */
104
104
  cleanup(): void;
105
105
  };
106
- //# sourceMappingURL=tasks.d.ts.map
@@ -239,4 +239,3 @@ function project(task, query) {
239
239
  : history.slice(Math.max(0, history.length - query.historyLength))
240
240
  };
241
241
  }
242
- //# sourceMappingURL=tasks.js.map