@pi-unipi/unipi 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (359) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +8 -11
  3. package/docs/prefix-cache-architecture.md +14 -0
  4. package/package.json +22 -24
  5. package/packages/ask-user/README.md +55 -107
  6. package/packages/ask-user/ask-ui.ts +306 -732
  7. package/packages/ask-user/clipboard.ts +50 -0
  8. package/packages/ask-user/config.ts +74 -78
  9. package/packages/ask-user/package.json +2 -2
  10. package/packages/ask-user/questions.ts +151 -0
  11. package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
  12. package/packages/ask-user/tools.ts +198 -432
  13. package/packages/autocomplete/package.json +1 -1
  14. package/packages/autocomplete/src/constants.ts +38 -43
  15. package/packages/autocomplete/src/sorting.ts +8 -0
  16. package/packages/background-tasks/package.json +2 -2
  17. package/packages/background-tasks/src/config.ts +0 -73
  18. package/packages/background-tasks/src/index.ts +20 -16
  19. package/packages/background-tasks/src/registry.ts +1 -171
  20. package/packages/background-tasks/src/types.ts +0 -32
  21. package/packages/btw/README.md +13 -56
  22. package/packages/btw/extensions/btw.ts +403 -1796
  23. package/packages/btw/package.json +5 -2
  24. package/packages/btw/skills/btw/SKILL.md +7 -143
  25. package/packages/compactor/README.md +56 -287
  26. package/packages/compactor/package.json +5 -9
  27. package/packages/compactor/skills/compactor/SKILL.md +13 -20
  28. package/packages/compactor/src/card.ts +90 -0
  29. package/packages/compactor/src/commands/index.ts +73 -130
  30. package/packages/compactor/src/compaction/compact-args.ts +5 -3
  31. package/packages/compactor/src/compaction/filter-noise.ts +1 -1
  32. package/packages/compactor/src/compaction/hooks.ts +487 -311
  33. package/packages/compactor/src/compaction/source.ts +178 -0
  34. package/packages/compactor/src/compaction/summarize.ts +586 -52
  35. package/packages/compactor/src/config/manager.ts +130 -127
  36. package/packages/compactor/src/config/schema.ts +32 -48
  37. package/packages/compactor/src/index.ts +40 -431
  38. package/packages/compactor/src/info-screen.ts +44 -190
  39. package/packages/compactor/src/session/recall-blocks.ts +1 -1
  40. package/packages/compactor/src/stats.ts +76 -0
  41. package/packages/compactor/src/tools/context-budget.ts +17 -34
  42. package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
  43. package/packages/compactor/src/tools/register.ts +29 -269
  44. package/packages/compactor/src/types.ts +47 -186
  45. package/packages/core/README.md +25 -0
  46. package/packages/core/child-agent.ts +1 -0
  47. package/packages/core/command-echo.ts +63 -0
  48. package/packages/core/compaction-context.ts +41 -0
  49. package/packages/core/constants.ts +8 -38
  50. package/packages/core/events.ts +0 -53
  51. package/packages/core/fusion-status.ts +4 -0
  52. package/packages/core/global.d.ts +10 -0
  53. package/packages/core/harness-messages.ts +392 -0
  54. package/packages/core/index.ts +14 -0
  55. package/packages/core/kanboard-status.ts +49 -0
  56. package/packages/core/long-horizon-owner-status.ts +46 -0
  57. package/packages/core/model-cache.ts +81 -7
  58. package/packages/core/package.json +6 -3
  59. package/packages/core/src/attach/detect.ts +163 -0
  60. package/packages/core/src/child-agent/child-args.ts +31 -0
  61. package/packages/core/src/child-agent/delivery.ts +41 -0
  62. package/packages/core/src/child-agent/index.ts +5 -0
  63. package/packages/core/src/child-agent/readers.ts +203 -0
  64. package/packages/core/src/child-agent/runtime.ts +584 -0
  65. package/packages/core/src/evidence.ts +78 -0
  66. package/packages/core/src/hints/browser.ts +278 -0
  67. package/packages/core/src/hints/crab-data.ts +19 -0
  68. package/packages/core/src/hints/index.ts +865 -0
  69. package/packages/core/src/hints/lines.ts +607 -0
  70. package/packages/core/src/hints/store.ts +120 -0
  71. package/packages/core/src/jev/chatter.ts +24 -0
  72. package/packages/core/src/jev/settings.ts +177 -14
  73. package/packages/core/src/package-colors.ts +2 -0
  74. package/packages/core/src/settings/catalog.ts +39 -15
  75. package/packages/core/src/settings/engine.ts +48 -2
  76. package/packages/core/src/settings/hub.ts +83 -20
  77. package/packages/core/src/settings/open.ts +49 -0
  78. package/packages/core/src/settings/schema.ts +27 -7
  79. package/packages/core/src/tui/hub-kit.ts +18 -5
  80. package/packages/core/src/tui/kit.ts +198 -0
  81. package/packages/core/src/tui/progress.ts +73 -0
  82. package/packages/core/src/turn/arbiter.ts +290 -0
  83. package/packages/footer/README.md +4 -6
  84. package/packages/footer/package.json +3 -3
  85. package/packages/footer/src/glance-editor.ts +19 -11
  86. package/packages/footer/src/help.ts +1 -1
  87. package/packages/footer/src/index.ts +36 -3
  88. package/packages/footer/src/presets.ts +3 -4
  89. package/packages/footer/src/rendering/icons.ts +0 -20
  90. package/packages/footer/src/segments/compactor.ts +52 -142
  91. package/packages/footer/src/segments/status-ext.ts +0 -1
  92. package/packages/fusion/README.md +14 -4
  93. package/packages/fusion/package.json +4 -3
  94. package/packages/fusion/src/child-args.ts +1 -0
  95. package/packages/fusion/src/index.ts +161 -8
  96. package/packages/fusion/src/pi-settings.ts +57 -0
  97. package/packages/fusion/src/picker.ts +186 -51
  98. package/packages/fusion/src/preset.ts +56 -2
  99. package/packages/fusion/src/prompts.ts +1 -1
  100. package/packages/fusion/src/sidekick-runtime.ts +1 -420
  101. package/packages/fusion/src/tools.ts +107 -128
  102. package/packages/fusion/src/transcript.ts +27 -85
  103. package/packages/info-screen/package.json +2 -2
  104. package/packages/input-shortcuts/README.md +18 -30
  105. package/packages/input-shortcuts/package.json +2 -2
  106. package/packages/input-shortcuts/src/burst.ts +88 -0
  107. package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
  108. package/packages/input-shortcuts/src/cleared-input.ts +97 -0
  109. package/packages/input-shortcuts/src/index.ts +99 -193
  110. package/packages/input-shortcuts/src/last-response.ts +40 -0
  111. package/packages/input-shortcuts/src/registers.ts +8 -23
  112. package/packages/input-shortcuts/src/types.ts +4 -16
  113. package/packages/input-shortcuts/src/undo-redo.ts +59 -47
  114. package/packages/kanboard/README.md +51 -47
  115. package/packages/kanboard/index.ts +293 -55
  116. package/packages/kanboard/package.json +8 -8
  117. package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
  118. package/packages/kanboard/src/badges.ts +93 -0
  119. package/packages/kanboard/src/bin.ts +4 -2
  120. package/packages/kanboard/src/commands.ts +218 -84
  121. package/packages/kanboard/src/debug.ts +20 -0
  122. package/packages/kanboard/src/guard.ts +267 -109
  123. package/packages/kanboard/src/monitor.ts +330 -0
  124. package/packages/kanboard/src/notice-buffer.ts +74 -0
  125. package/packages/kanboard/src/progress.ts +27 -0
  126. package/packages/kanboard/src/reminders.ts +234 -0
  127. package/packages/kanboard/src/settings.ts +69 -15
  128. package/packages/kanboard/src/shapes.ts +5 -0
  129. package/packages/long-horizon/index.ts +190 -13
  130. package/packages/long-horizon/package.json +3 -3
  131. package/packages/long-horizon/src/commands.ts +103 -7
  132. package/packages/long-horizon/src/compaction-brief.ts +35 -0
  133. package/packages/long-horizon/src/engine/continuation.ts +84 -15
  134. package/packages/long-horizon/src/engine/goal-state.ts +64 -13
  135. package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
  136. package/packages/long-horizon/src/engine/ralph.ts +3 -2
  137. package/packages/long-horizon/src/engine/runaway.ts +68 -11
  138. package/packages/long-horizon/src/engine/verifier.ts +19 -0
  139. package/packages/long-horizon/src/gate.ts +37 -12
  140. package/packages/long-horizon/src/judge/resolve.ts +1 -0
  141. package/packages/long-horizon/src/owner.ts +35 -2
  142. package/packages/long-horizon/src/progress.ts +87 -0
  143. package/packages/long-horizon/src/prompts/goal.ts +1 -1
  144. package/packages/long-horizon/src/runtime.ts +172 -18
  145. package/packages/long-horizon/src/settings.ts +54 -39
  146. package/packages/long-horizon/src/tools/goal.ts +6 -0
  147. package/packages/long-horizon/src/tools/graph.ts +1 -1
  148. package/packages/long-horizon/src/tools/swarm.ts +3 -3
  149. package/packages/mcp/package.json +2 -2
  150. package/packages/memory/commands.ts +7 -2
  151. package/packages/memory/index.ts +210 -32
  152. package/packages/memory/package.json +3 -3
  153. package/packages/memory/save-session.ts +264 -0
  154. package/packages/memory/settings.ts +17 -0
  155. package/packages/memory/tools.ts +253 -203
  156. package/packages/notify/package.json +2 -2
  157. package/packages/skill-registry/README.md +53 -16
  158. package/packages/skill-registry/index.ts +230 -0
  159. package/packages/skill-registry/package.json +19 -8
  160. package/packages/skill-registry/src/editor.ts +206 -0
  161. package/packages/skill-registry/src/judge.ts +289 -0
  162. package/packages/skill-registry/src/registry.ts +87 -0
  163. package/packages/skill-registry/src/settings.ts +259 -0
  164. package/packages/skill-registry/src/vault.ts +80 -0
  165. package/packages/subagents/README.md +39 -119
  166. package/packages/subagents/package.json +6 -13
  167. package/packages/subagents/skills/subagents/SKILL.md +18 -42
  168. package/packages/subagents/src/agents.ts +213 -0
  169. package/packages/subagents/src/cards.ts +182 -0
  170. package/packages/subagents/src/index.ts +609 -1084
  171. package/packages/subagents/src/manager.ts +429 -0
  172. package/packages/subagents/src/profiles.ts +130 -0
  173. package/packages/subagents/src/transcript.ts +154 -0
  174. package/packages/subagents/src/ui.ts +389 -0
  175. package/packages/unipi/bundled.js +39756 -45569
  176. package/packages/unipi/index.ts +55 -10
  177. package/packages/updater/package.json +2 -2
  178. package/packages/utility/README.md +76 -69
  179. package/packages/utility/package.json +2 -2
  180. package/packages/utility/skills/image/SKILL.md +37 -0
  181. package/packages/utility/skills/utility/SKILL.md +17 -60
  182. package/packages/utility/src/answer/extract.ts +128 -0
  183. package/packages/utility/src/answer/index.ts +213 -0
  184. package/packages/utility/src/answer/reply.ts +133 -0
  185. package/packages/utility/src/answer/web.ts +167 -0
  186. package/packages/utility/src/attach/index.ts +205 -0
  187. package/packages/utility/src/commands.ts +38 -142
  188. package/packages/utility/src/diagnostics/engine.ts +50 -5
  189. package/packages/utility/src/image/index.ts +33 -0
  190. package/packages/utility/src/image/models.ts +221 -0
  191. package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
  192. package/packages/utility/src/image/settings.ts +178 -0
  193. package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
  194. package/packages/utility/src/image/tools.ts +168 -0
  195. package/packages/utility/src/image/transports.ts +186 -0
  196. package/packages/utility/src/index.ts +68 -389
  197. package/packages/utility/src/lifecycle/cleanup.ts +116 -311
  198. package/packages/utility/src/rename/gate.ts +102 -0
  199. package/packages/utility/src/rename/index.ts +191 -0
  200. package/packages/utility/src/rename/session.ts +170 -0
  201. package/packages/utility/src/render/delegated.ts +285 -0
  202. package/packages/utility/src/render/harness.ts +683 -0
  203. package/packages/utility/src/render/parse.ts +156 -0
  204. package/packages/utility/src/render/pi-settings.ts +38 -0
  205. package/packages/utility/src/render/reply-bg.ts +307 -0
  206. package/packages/utility/src/render/simple.ts +724 -0
  207. package/packages/utility/src/render/spacing.ts +235 -0
  208. package/packages/utility/src/render/styled.ts +251 -0
  209. package/packages/utility/src/render/tools.ts +353 -0
  210. package/packages/utility/src/settings.ts +166 -233
  211. package/packages/utility/src/types.ts +1 -222
  212. package/packages/watchdog/index.ts +17 -6
  213. package/packages/watchdog/package.json +3 -3
  214. package/packages/watchdog/src/config.ts +3 -2
  215. package/packages/web-api/package.json +2 -2
  216. package/packages/workflow/README.md +3 -0
  217. package/packages/workflow/index.ts +0 -2
  218. package/packages/workflow/package.json +2 -2
  219. package/packages/workflow/src/permission/decide.ts +6 -4
  220. package/packages/workflow/src/permission/index.ts +17 -7
  221. package/packages/workflow/src/permission/settings.ts +39 -4
  222. package/packages/workflow/src/plan/index.ts +19 -4
  223. package/packages/background-tasks/extensions/delegate-child.ts +0 -1
  224. package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
  225. package/packages/background-tasks/src/context-token-budget.ts +0 -903
  226. package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
  227. package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
  228. package/packages/background-tasks/src/delegate/budget.ts +0 -415
  229. package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
  230. package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
  231. package/packages/background-tasks/src/delegate/launch.ts +0 -484
  232. package/packages/background-tasks/src/delegate/result-package.ts +0 -459
  233. package/packages/background-tasks/src/delegate/runner.ts +0 -430
  234. package/packages/background-tasks/src/delegate/seed.ts +0 -423
  235. package/packages/background-tasks/src/delegate/types.ts +0 -323
  236. package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
  237. package/packages/background-tasks/src/delegate-extension.ts +0 -654
  238. package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
  239. package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
  240. package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
  241. package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
  242. package/packages/compactor/src/compaction/build-sections.ts +0 -76
  243. package/packages/compactor/src/compaction/extract/goals.ts +0 -36
  244. package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
  245. package/packages/compactor/src/compaction/format.ts +0 -53
  246. package/packages/compactor/src/compaction/merge.ts +0 -143
  247. package/packages/compactor/src/config/presets.ts +0 -160
  248. package/packages/compactor/src/executor/executor.ts +0 -233
  249. package/packages/compactor/src/executor/runtime.ts +0 -115
  250. package/packages/compactor/src/security/evaluator.ts +0 -89
  251. package/packages/compactor/src/security/policy.ts +0 -96
  252. package/packages/compactor/src/security/scanner.ts +0 -65
  253. package/packages/compactor/src/session/auto-inject.ts +0 -60
  254. package/packages/compactor/src/session/db.ts +0 -325
  255. package/packages/compactor/src/session/extract.ts +0 -144
  256. package/packages/compactor/src/session/resume-inject.ts +0 -64
  257. package/packages/compactor/src/session/snapshot.ts +0 -327
  258. package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
  259. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
  260. package/packages/compactor/src/tools/ctx-execute.ts +0 -23
  261. package/packages/compactor/src/tools/ctx-stats.ts +0 -96
  262. package/packages/image/README.md +0 -99
  263. package/packages/image/package.json +0 -61
  264. package/packages/image/skills/image/SKILL.md +0 -76
  265. package/packages/image/src/generate.ts +0 -256
  266. package/packages/image/src/index.ts +0 -135
  267. package/packages/image/src/models.ts +0 -519
  268. package/packages/image/src/openai-images-api.ts +0 -282
  269. package/packages/image/src/register-providers.ts +0 -209
  270. package/packages/image/src/settings.ts +0 -173
  271. package/packages/image/src/tools.ts +0 -326
  272. package/packages/input-shortcuts/src/clipboard.ts +0 -119
  273. package/packages/kanboard/src/runner.ts +0 -817
  274. package/packages/skill-registry/skills/auto/SKILL.md +0 -465
  275. package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
  276. package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
  277. package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
  278. package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
  279. package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
  280. package/packages/skill-registry/skills/debug/SKILL.md +0 -228
  281. package/packages/skill-registry/skills/document/SKILL.md +0 -120
  282. package/packages/skill-registry/skills/fix/SKILL.md +0 -212
  283. package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
  284. package/packages/skill-registry/skills/plan/SKILL.md +0 -231
  285. package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
  286. package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
  287. package/packages/skill-registry/skills/research/SKILL.md +0 -251
  288. package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
  289. package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
  290. package/packages/skill-registry/skills/work/SKILL.md +0 -218
  291. package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
  292. package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
  293. package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
  294. package/packages/subagents/agents/delegate.md +0 -14
  295. package/packages/subagents/agents/oracle.md +0 -78
  296. package/packages/subagents/agents/researcher.md +0 -52
  297. package/packages/subagents/agents/reviewer.md +0 -80
  298. package/packages/subagents/agents/scout.md +0 -50
  299. package/packages/subagents/agents/worker.md +0 -59
  300. package/packages/subagents/prompts/council.md +0 -48
  301. package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
  302. package/packages/subagents/prompts/parallel-cleanup.md +0 -59
  303. package/packages/subagents/prompts/parallel-research.md +0 -50
  304. package/packages/subagents/prompts/parallel-review.md +0 -54
  305. package/packages/subagents/prompts/review-loop.md +0 -43
  306. package/packages/subagents/skills/council-mode/SKILL.md +0 -230
  307. package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
  308. package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
  309. package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
  310. package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
  311. package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
  312. package/packages/subagents/src/acceptance.ts +0 -331
  313. package/packages/subagents/src/agent-manager.ts +0 -405
  314. package/packages/subagents/src/agent-memory.ts +0 -300
  315. package/packages/subagents/src/agent-overrides.ts +0 -183
  316. package/packages/subagents/src/agent-runner.ts +0 -271
  317. package/packages/subagents/src/async-runner.ts +0 -422
  318. package/packages/subagents/src/authority-policy.ts +0 -47
  319. package/packages/subagents/src/budgets.ts +0 -285
  320. package/packages/subagents/src/child-safety.ts +0 -140
  321. package/packages/subagents/src/config.ts +0 -249
  322. package/packages/subagents/src/conversation-viewer.ts +0 -281
  323. package/packages/subagents/src/core-compat.ts +0 -94
  324. package/packages/subagents/src/custom-agents.ts +0 -343
  325. package/packages/subagents/src/file-system-retry.ts +0 -50
  326. package/packages/subagents/src/fleet-data.ts +0 -105
  327. package/packages/subagents/src/fleet-view.ts +0 -322
  328. package/packages/subagents/src/foreground-detach.ts +0 -59
  329. package/packages/subagents/src/fork-context.ts +0 -285
  330. package/packages/subagents/src/global.d.ts +0 -7
  331. package/packages/subagents/src/guide.ts +0 -129
  332. package/packages/subagents/src/mission-state.ts +0 -133
  333. package/packages/subagents/src/mission-store.ts +0 -436
  334. package/packages/subagents/src/output-limits.ts +0 -142
  335. package/packages/subagents/src/parity-types.ts +0 -315
  336. package/packages/subagents/src/pi-args.ts +0 -167
  337. package/packages/subagents/src/result-files.ts +0 -232
  338. package/packages/subagents/src/result-watcher.ts +0 -221
  339. package/packages/subagents/src/retained-children.ts +0 -176
  340. package/packages/subagents/src/run-fanout-budget.ts +0 -356
  341. package/packages/subagents/src/scheduled-runs.ts +0 -349
  342. package/packages/subagents/src/schemas.ts +0 -263
  343. package/packages/subagents/src/slash-commands.ts +0 -80
  344. package/packages/subagents/src/supervisor-channel.ts +0 -249
  345. package/packages/subagents/src/tool-handler.ts +0 -1144
  346. package/packages/subagents/src/types.ts +0 -238
  347. package/packages/subagents/src/widget.ts +0 -457
  348. package/packages/subagents/src/workflow-script.ts +0 -737
  349. package/packages/subagents/src/workflow-worker.ts +0 -384
  350. package/packages/subagents/src/worktree.ts +0 -614
  351. package/packages/utility/src/analytics/collector.ts +0 -293
  352. package/packages/utility/src/info-screen.ts +0 -80
  353. package/packages/utility/src/lifecycle/process.ts +0 -162
  354. package/packages/utility/src/prefix-cache.ts +0 -263
  355. package/packages/utility/src/skill-discovery.ts +0 -542
  356. package/packages/utility/src/tools/env.ts +0 -113
  357. package/packages/utility/src/tui/name-badge-state.ts +0 -315
  358. package/packages/utility/src/tui/name-badge.ts +0 -109
  359. /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
@@ -1,1144 +0,0 @@
1
- /**
2
- * @pi-unipi/subagents — spawn_helper parity handler
3
- *
4
- * Routes the extended spawn_helper surface: management actions, workflowScript
5
- * execution, and legacy single-child launches (type/prompt aliases). Applies
6
- * the Phase 2 libraries: agent resolution + aliases, enablement, budgets,
7
- * depth guard, context policy, spawn budgets, timeout defaults, and output
8
- * truncation. The render/notify plumbing stays in index.ts — this module only
9
- * decides and executes.
10
- */
11
-
12
- import { existsSync } from "node:fs";
13
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
14
- import type { AgentManager } from "./agent-manager.js";
15
- import type { SubagentsConfig, AgentConfig } from "./types.js";
16
- import { SUBAGENT_ACTIONS } from "./parity-types.js";
17
- import { runWorkflowScript, type WorkflowScriptChildResult } from "./workflow-script.js";
18
- import {
19
- resolveTurnBudgetConfig,
20
- validateToolBudgetConfig,
21
- validateUsageBudgetConfig,
22
- appendTurnBudgetSystemPrompt,
23
- } from "./budgets.js";
24
- import {
25
- createRunFanoutBudget,
26
- claimRunFanoutBatch,
27
- getRunFanoutBudgetSnapshot,
28
- formatRunFanoutBudget,
29
- } from "./run-fanout-budget.js";
30
- import { truncateOutput, resolveMaxOutput } from "./output-limits.js";
31
- import {
32
- resolveContext,
33
- assertForegroundContextSupported,
34
- resolveMaxSubagentDepth,
35
- depthExceeded,
36
- isFanoutChild,
37
- withChildBoundaryInstructions,
38
- } from "./child-safety.js";
39
- import { listRetainedChildren, formatRetainedChildren, resolveResumeTarget } from "./retained-children.js";
40
- import { ASYNC_DIR, TEMP_ROOT_DIR, RESULTS_DIR } from "./parity-types.js";
41
- import { buildGuideText } from "./guide.js";
42
- import { ScheduledRunManager } from "./scheduled-runs.js";
43
- import {
44
- validateAcceptanceInput,
45
- normalizeAcceptanceInput,
46
- normalizeGateAcceptance,
47
- evaluateAcceptance,
48
- stripAcceptanceReport,
49
- } from "./acceptance.js";
50
- import type { AcceptanceConfig } from "./acceptance.js";
51
- import {
52
- resolveMissionStoreLocation,
53
- createMission,
54
- readMission,
55
- listMissions,
56
- updateMission,
57
- MissionNotFoundError,
58
- type MissionStoreConfig,
59
- type MissionStatus,
60
- } from "./mission-store.js";
61
-
62
- export interface HandlerDeps {
63
- pi: ExtensionAPI;
64
- manager: AgentManager;
65
- config: SubagentsConfig;
66
- /** Legacy single-child spawn (foreground). */
67
- spawnForeground: (
68
- ctx: ExtensionContext,
69
- agentName: string,
70
- prompt: string,
71
- options: {
72
- description?: string;
73
- maxTurns?: number;
74
- modelInput?: string;
75
- thinkingLevel?: unknown;
76
- signal?: AbortSignal;
77
- },
78
- ) => Promise<{ ok: boolean; output: string; error?: string; toolUses: number; durationMs: number }>;
79
- /** Legacy single-child spawn (background). */
80
- spawnBackground: (
81
- ctx: ExtensionContext,
82
- agentName: string,
83
- prompt: string,
84
- options: {
85
- description?: string;
86
- maxTurns?: number;
87
- modelInput?: string;
88
- thinkingLevel?: unknown;
89
- },
90
- ) => string;
91
- /** Session-wide cumulative spawn accounting (maxSubagentSpawnsPerSession). */
92
- spawnAccounting?: {
93
- used(): number;
94
- cap(): number | undefined;
95
- consume(count: number): void;
96
- };
97
- /** Depth env for the recursion guard. */
98
- env?: NodeJS.ProcessEnv;
99
- /** Retained-children dir override (defaults to our async temp root). */
100
- retainedDir?: string;
101
- /** Mission store config (from OUR subagents.json). */
102
- missionConfig?: MissionStoreConfig;
103
- /** Project root for mission records. */
104
- projectRoot?: string;
105
- /** Async process runner (Phase 3). When present, background launches and
106
- * fork contexts run as child pi processes; when absent they error with
107
- * guidance (test environments). */
108
- runAsync?: (launch: {
109
- agentName: string;
110
- task: string;
111
- description?: string;
112
- model?: string;
113
- thinking?: string | false;
114
- context: "fresh" | "fork";
115
- timeoutMs?: number;
116
- maxTurns?: number;
117
- /** Resume: continue a retained child in its stored session. */
118
- resumeSessionFile?: string;
119
- /** Worktree isolation: launch the child in a managed git worktree. */
120
- worktree?: boolean;
121
- }) => Promise<{
122
- runId: string;
123
- status: string;
124
- output?: string;
125
- error?: string;
126
- }>;
127
- }
128
-
129
- function textResult(msg: string, details?: unknown) {
130
- return { content: [{ type: "text" as const, text: msg }], details };
131
- }
132
-
133
- /**
134
- * Resolve the effective acceptance config: explicit gate shorthand wins over
135
- * acceptance; agent-level defaultAcceptance fills gaps. Returns undefined for
136
- * no gate. Errors are visible strings.
137
- */
138
- function resolveEffectiveAcceptance(
139
- args: Record<string, unknown>,
140
- ): { ok: true; acceptance?: AcceptanceConfig } | { ok: false; error: string } {
141
- if (args.gate !== undefined && args.acceptance !== undefined) {
142
- return { ok: false, error: "gate cannot be combined with acceptance; use one gate command or acceptance.verify." };
143
- }
144
- if (args.gate !== undefined) {
145
- const normalized = normalizeGateAcceptance(args.gate);
146
- if (!normalized) return { ok: false, error: "gate must be a non-empty command string." };
147
- return { ok: true, acceptance: normalized };
148
- }
149
- if (args.acceptance === undefined) return { ok: true, acceptance: undefined };
150
- const errors = validateAcceptanceInput(args.acceptance);
151
- if (errors.length > 0) return { ok: false, error: errors.join(" ") };
152
- return { ok: true, acceptance: normalizeAcceptanceInput(args.acceptance) };
153
- }
154
-
155
- /** Post-run acceptance evaluation wrapper. */
156
- async function evaluateRunAcceptance(
157
- acceptance: AcceptanceConfig | undefined,
158
- output: string,
159
- cwd: string,
160
- ): Promise<{ output: string; ledger?: Awaited<ReturnType<typeof evaluateAcceptance>> }> {
161
- if (!acceptance || acceptance.level === "auto") return { output };
162
- const ledger = await evaluateAcceptance({ acceptance, output, cwd });
163
- const cleanOutput = stripAcceptanceReport(output);
164
- if (ledger.status === "rejected") {
165
- return { output: cleanOutput, ledger };
166
- }
167
- return { output: cleanOutput, ledger };
168
- }
169
-
170
- /** Parse + validate everything, resolve the agent, or return an error result. */
171
- function preflight(
172
- deps: HandlerDeps,
173
- args: Record<string, unknown>,
174
- ): { ok: true; agent: AgentConfig; agentName: string; prompt: string } | { ok: false; error: string } {
175
- const agentNameRaw = (args.agent ?? args.type) as string | undefined;
176
- const prompt = (args.task ?? args.prompt) as string | undefined;
177
-
178
- if (!agentNameRaw || typeof agentNameRaw !== "string") {
179
- return { ok: false, error: "spawn_helper requires an agent (or legacy type) parameter." };
180
- }
181
-
182
- const agentName = deps.manager.resolveAlias(agentNameRaw);
183
- const agent = deps.manager.getAgentConfig(agentName);
184
- if (!agent) {
185
- const known = deps.manager.getKnownTypes().join(", ");
186
- return { ok: false, error: `Unknown agent type "${agentNameRaw}". Known types: ${known}` };
187
- }
188
- if (!deps.manager.isTypeEnabled(agentName)) {
189
- return { ok: false, error: `Agent type "${agentName}" is disabled by configuration.` };
190
- }
191
-
192
- // Depth guard
193
- const env = deps.env ?? process.env;
194
- const maxDepth = resolveMaxSubagentDepth(agent, deps.config, env);
195
- if (depthExceeded(env, maxDepth)) {
196
- return {
197
- ok: false,
198
- error: `Subagent depth cap reached (maxSubagentDepth ${maxDepth}). This child cannot spawn further subagents for its assigned fanout.`,
199
- };
200
- }
201
-
202
- if (!prompt || typeof prompt !== "string" || !prompt.trim()) {
203
- return { ok: false, error: "spawn_helper requires a task (or legacy prompt) parameter." };
204
- }
205
-
206
- return { ok: true, agent, agentName, prompt };
207
- }
208
-
209
- /**
210
- * Main entry: route a spawn_helper invocation.
211
- * Returns a tool result; never throws.
212
- */
213
- export async function handleSpawnHelper(
214
- deps: HandlerDeps,
215
- ctx: ExtensionContext,
216
- args: Record<string, unknown>,
217
- signal?: AbortSignal,
218
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details?: unknown }> {
219
- try {
220
- // ---- Management actions ----
221
- const action = args.action as string | undefined;
222
- if (action !== undefined) {
223
- return await handleAction(deps, ctx, action, args);
224
- }
225
-
226
- // ---- workflowScript execution ----
227
- const workflowScript = args.workflowScript as string | undefined;
228
- if (workflowScript !== undefined) {
229
- return await handleWorkflowScript(deps, ctx, workflowScript, args, signal);
230
- }
231
-
232
- // ---- Single-child launch (legacy + reference params) ----
233
- return await handleSingleChild(deps, ctx, args, signal);
234
- } catch (error) {
235
- return textResult(
236
- `spawn_helper failed: ${error instanceof Error ? error.message : String(error)}`,
237
- { status: "error", error: error instanceof Error ? error.message : String(error) },
238
- );
239
- }
240
- }
241
-
242
- // ============================================================================
243
- // Management actions
244
- // ============================================================================
245
-
246
- async function handleAction(
247
- deps: HandlerDeps,
248
- _ctx: ExtensionContext,
249
- action: string,
250
- args: Record<string, unknown>,
251
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details?: unknown }> {
252
- if (!(SUBAGENT_ACTIONS as readonly string[]).includes(action)) {
253
- return textResult(
254
- `Unknown action "${action}". Supported actions: ${SUBAGENT_ACTIONS.join(", ")}.`,
255
- { status: "error" },
256
- );
257
- }
258
-
259
- switch (action) {
260
- case "list": {
261
- const types = deps.manager.getKnownTypes();
262
- const lines = types.map((type) => {
263
- const agent = deps.manager.getAgentConfig(type);
264
- const enabled = deps.manager.isTypeEnabled(type);
265
- const scope = agent?.source ?? "config";
266
- const desc = agent?.description ?? "";
267
- return `- ${type}${agent?.aliases?.length ? ` (aliases: ${agent.aliases.join(", ")})` : ""} [${scope}]${enabled ? "" : " [disabled]"}: ${desc}`;
268
- });
269
- return textResult(`Available subagents:\n${lines.join("\n")}`);
270
- }
271
-
272
- case "get": {
273
- const target = (args.agent ?? args.type ?? args.id) as string | undefined;
274
- if (!target) return textResult("action 'get' requires an agent name.");
275
- const agentName = deps.manager.resolveAlias(target);
276
- const agent = deps.manager.getAgentConfig(agentName);
277
- if (!agent) return textResult(`Unknown agent "${target}".`);
278
- return textResult(
279
- [
280
- `Agent: ${agent.name}`,
281
- ...(agent.aliases?.length ? [`Aliases: ${agent.aliases.join(", ")}`] : []),
282
- `Description: ${agent.description}`,
283
- `Source: ${agent.source ?? "builtin"}`,
284
- `Tools: ${agent.builtinToolNames?.join(", ") || "(all builtins)"}`,
285
- ...(agent.disallowedTools?.length ? [`Disallowed: ${agent.disallowedTools.join(", ")}`] : []),
286
- `Prompt mode: ${agent.promptMode}`,
287
- ...(agent.model ? [`Model: ${agent.model}`] : []),
288
- ...(agent.thinking !== undefined ? [`Thinking: ${String(agent.thinking)}`] : []),
289
- ...(agent.defaultContext ? [`Default context: ${agent.defaultContext}`] : []),
290
- ...(agent.timeoutMs ? [`Timeout: ${agent.timeoutMs}ms`] : []),
291
- `Enabled: ${deps.manager.isTypeEnabled(agentName)}`,
292
- ].join("\n"),
293
- );
294
- }
295
-
296
- case "status": {
297
- const accounting = deps.spawnAccounting;
298
- const sessionCap = accounting?.cap();
299
- const used = accounting?.used() ?? 0;
300
- const lines = [
301
- `Active agents: ${deps.manager.listAgents().filter((a) => a.status === "running" || a.status === "queued").length}`,
302
- `Max concurrent: ${deps.manager.getMaxConcurrent()}`,
303
- ];
304
- if (sessionCap !== undefined) {
305
- lines.push(
306
- `Session spawn budget: ${used}/${sessionCap} used, ${Math.max(0, sessionCap - used)} remaining`,
307
- );
308
- } else {
309
- lines.push("Session spawn budget: unlimited");
310
- }
311
- const running = deps.manager.listAgents().filter((a) => a.status === "running" || a.status === "queued");
312
- for (const record of running) {
313
- lines.push(`- ${record.id} [${record.type}] ${record.status}: ${record.description}`);
314
- }
315
- return textResult(lines.join("\n"));
316
- }
317
-
318
- case "children.list": {
319
- const retained = listRetainedChildren(deps.retainedDir ?? ASYNC_DIR);
320
- if (retained.length === 0) {
321
- // Fall back to in-process completed agents from this session.
322
- const recent = deps.manager
323
- .listAgents()
324
- .filter((a) => a.status !== "running" && a.status !== "queued")
325
- .slice(-10);
326
- if (recent.length === 0) return textResult("No completed children in this session yet.");
327
- return textResult(
328
- recent.map((r) => `- ${r.id} [${r.type}] ${r.status}: ${r.description}`).join("\n"),
329
- );
330
- }
331
- return textResult(formatRetainedChildren(retained));
332
- }
333
-
334
- case "resume": {
335
- const id = args.id as string | undefined;
336
- const message = args.message as string | undefined;
337
- if (!id) return textResult("action 'resume' requires an id (retained run id or prefix).");
338
- if (!message || !message.trim()) return textResult("action 'resume' requires a non-empty follow-up message.");
339
- if (!deps.runAsync) {
340
- return textResult("action 'resume' requires the background process runner, which is unavailable in this host.", { status: "error" });
341
- }
342
- const target = resolveResumeTarget(deps.retainedDir ?? ASYNC_DIR, id);
343
- if (!target.ok) return textResult(target.error, { status: "error" });
344
- const result = await deps.runAsync({
345
- agentName: target.agent,
346
- task: message.trim(),
347
- description: `resume ${target.runId.slice(-8)}`,
348
- context: "fresh",
349
- resumeSessionFile: target.sessionFile,
350
- });
351
- return textResult(
352
- `Resumed ${target.runId} as ${result.runId} (agent: ${target.agent}, stored session contract kept).\nUse get_helper_result to retrieve results.`,
353
- { status: "background", runId: result.runId },
354
- );
355
- }
356
-
357
- case "stop": {
358
- const id = args.id as string | undefined;
359
- if (!id) return textResult("action 'stop' requires an id.");
360
- const record = deps.manager.getRecord(id);
361
- if (!record) return textResult(`Run not found: "${id}".`);
362
- record.abortController?.abort();
363
- return textResult(`Stop requested for ${id} (${record.type}, ${record.status}).`);
364
- }
365
-
366
- case "grant-spawn-budget": {
367
- const accounting = deps.spawnAccounting;
368
- if (!accounting) return textResult("Spawn budget accounting is unavailable in this host.");
369
- const cap = accounting.cap();
370
- if (cap === undefined) return textResult("Session spawn budget is unlimited; nothing to grant.");
371
- const additional = args.additional as number | undefined;
372
- if (typeof additional !== "number" || !Number.isInteger(additional) || additional < 1) {
373
- return textResult("action 'grant-spawn-budget' requires a positive integer 'additional'.");
374
- }
375
- // Grants require interactive confirmation — delegated to the host via pi.
376
- return textResult(
377
- `grant-spawn-budget requires native user confirmation; requested +${additional} against cap ${cap}. ` +
378
- `This action needs the interactive parent; run it from the root session.`,
379
- );
380
- }
381
-
382
- case "doctor": {
383
- const lines: string[] = [
384
- "Subagents doctor report",
385
- "",
386
- "Runtime",
387
- `- cwd: ${process.cwd()}`,
388
- `- async runner: ${deps.runAsync ? "available (child pi processes)" : "unavailable"}`,
389
- "",
390
- "Filesystem",
391
- `- temp root: ${TEMP_ROOT_DIR}${existsSync(TEMP_ROOT_DIR) ? "" : " (created on first run)"}`,
392
- `- async runs: ${ASYNC_DIR}`,
393
- `- results: ${RESULTS_DIR}`,
394
- "",
395
- "Discovery",
396
- `- known agent types: ${deps.manager.getKnownTypes().length}`,
397
- ...deps.manager.getKnownTypes().map((type) => {
398
- const agent = deps.manager.getAgentConfig(type);
399
- const enabled = deps.manager.isTypeEnabled(type);
400
- return ` - ${type} [${agent?.source ?? "config"}]${enabled ? "" : " [disabled]"}${agent?.aliases?.length ? ` (aliases: ${agent.aliases.join(", ")})` : ""}`;
401
- }),
402
- "",
403
- "Budgets",
404
- `- run timeout default: ${deps.config.timeoutMs ?? "30min (default)"}`,
405
- `- tool timeout: ${deps.config.toolTimeoutMs ?? "env/known-fast defaults"}`,
406
- `- spawn caps: run=${deps.config.maxSubagentSpawnsPerRun ?? 64}, session=${deps.config.maxSubagentSpawnsPerSession ?? "unlimited"}, active-async=${deps.config.maxActiveAsyncRunsPerSession ?? "unlimited"}`,
407
- `- depth cap: ${resolveMaxSubagentDepth(undefined, deps.config, deps.env ?? process.env)}`,
408
- "",
409
- "Concurrency",
410
- `- maxConcurrent: ${deps.config.maxConcurrent}`,
411
- `- active in-process: ${deps.manager.listAgents().filter((a) => a.status === "running" || a.status === "queued").length}`,
412
- `- retained children: ${listRetainedChildren(deps.retainedDir ?? ASYNC_DIR).length}`,
413
- ];
414
- return textResult(lines.join("\n"));
415
- }
416
-
417
- case "guide": {
418
- const topic = (args.topic as string | undefined) ?? "overview";
419
- return textResult(buildGuideText(topic));
420
- }
421
-
422
- case "mission.create": {
423
- const location = resolveMissionStoreLocation({
424
- projectRoot: deps.projectRoot ?? process.cwd(),
425
- config: deps.missionConfig,
426
- });
427
- const missionInput = (args.mission ?? {}) as Record<string, unknown>;
428
- const title = (missionInput.title ?? args.name) as string | undefined;
429
- if (!title || typeof title !== "string" || !title.trim()) {
430
- return textResult("action 'mission.create' requires mission.title (or name).", { status: "error" });
431
- }
432
- const objective = (missionInput.objective as string | undefined) ?? `${title}`;
433
- try {
434
- const record = createMission(location, {
435
- title: title.trim(),
436
- objective: objective.trim(),
437
- ...(missionInput.goal === true ? { goal: true } : {}),
438
- ...(missionInput.budget ? { budget: missionInput.budget as { tokens: number } } : {}),
439
- ...(typeof missionInput.labels === "object" ? { labels: missionInput.labels as string[] } : {}),
440
- });
441
- return textResult(
442
- `Mission created: ${record.id}\nTitle: ${record.title}\nStatus: ${record.status}\nAttach runs with missionId: ${record.id}`,
443
- { status: "completed", missionId: record.id },
444
- );
445
- } catch (error) {
446
- return textResult(`mission.create failed: ${error instanceof Error ? error.message : String(error)}`, { status: "error" });
447
- }
448
- }
449
-
450
- case "mission.list": {
451
- const location = resolveMissionStoreLocation({
452
- projectRoot: deps.projectRoot ?? process.cwd(),
453
- config: deps.missionConfig,
454
- });
455
- const { records, warnings } = listMissions(location);
456
- if (records.length === 0) return textResult("No missions for this project.");
457
- const lines = records.map((r) => `- ${r.id} [${r.status}] ${r.title}${r.summary ? ` — ${r.summary.slice(0, 80)}` : ""}`);
458
- return textResult(
459
- [`Missions (${records.length}):`, ...lines, ...(warnings.length ? ["", ...warnings] : [])].join("\n"),
460
- );
461
- }
462
-
463
- case "mission.show":
464
- case "mission.update":
465
- case "mission.resolve-decision":
466
- case "mission.attach-run":
467
- case "mission.close": {
468
- const missionId = args.missionId as string | undefined;
469
- if (!missionId) return textResult(`action '${action}' requires missionId.`, { status: "error" });
470
- const location = resolveMissionStoreLocation({
471
- projectRoot: deps.projectRoot ?? process.cwd(),
472
- config: deps.missionConfig,
473
- });
474
- try {
475
- if (action === "mission.show") {
476
- const record = readMission(location, missionId);
477
- return textResult(
478
- [
479
- `Mission ${record.id} [${record.status}]`,
480
- `Title: ${record.title}`,
481
- `Objective: ${record.objective}`,
482
- ...(record.goal ? [`Goal: ${record.goal.status}${record.budget ? ` (${record.usage?.tokens ?? 0}/${record.budget.tokens} tokens)` : ""}`] : []),
483
- `Runs: ${record.runs.length} | Decisions: ${record.decisions.filter((d) => d.status === "open").length} open / ${record.decisions.length} total`,
484
- ...(record.summary ? [`Summary: ${record.summary}`] : []),
485
- ...(record.runs.length ? ["", "Runs:", ...record.runs.map((r) => `- ${r.runId}${r.agent ? ` (${r.agent})` : ""} ${r.status ?? ""}`)] : []),
486
- ].join("\n"),
487
- );
488
- }
489
- if (action === "mission.close") {
490
- const summary = args.summary as string | undefined;
491
- const status: MissionStatus = (args.missionStatus as MissionStatus | undefined) ?? (summary ? "completed" : "cancelled");
492
- const record = updateMission(location, missionId, { status, ...(summary ? { summary } : {}) });
493
- return textResult(`Mission ${record.id} closed as ${record.status}.`, { status: "completed", missionId: record.id });
494
- }
495
- if (action === "mission.attach-run") {
496
- const runId = (args.runId ?? args.id) as string | undefined;
497
- if (!runId) return textResult("mission.attach-run requires runId (or id).", { status: "error" });
498
- const record = updateMission(location, missionId, {
499
- addRun: { runId, ...(typeof args.missionStatus === "string" ? { status: args.missionStatus } : {}) },
500
- });
501
- return textResult(`Run ${runId} attached to mission ${record.id}.`, { status: "completed", missionId: record.id });
502
- }
503
- if (action === "mission.resolve-decision") {
504
- const decisionId = args.id as string | undefined;
505
- const resolution = args.message as string | undefined;
506
- if (!decisionId || !resolution) {
507
- return textResult("mission.resolve-decision requires id (decision id) and message (resolution).", { status: "error" });
508
- }
509
- const record = updateMission(location, missionId, { resolveDecision: { id: decisionId, resolution } });
510
- return textResult(`Decision ${decisionId} resolved on mission ${record.id}.`, { status: "completed" });
511
- }
512
- // mission.update: generic field updates
513
- const record = updateMission(location, missionId, {
514
- ...(typeof args.summary === "string" ? { summary: args.summary } : {}),
515
- ...(typeof args.missionStatus === "string" ? { status: args.missionStatus as MissionStatus } : {}),
516
- });
517
- return textResult(`Mission ${record.id} updated.`, { status: "completed", missionId: record.id });
518
- } catch (error) {
519
- if (error instanceof MissionNotFoundError) return textResult(error.message, { status: "error" });
520
- return textResult(`${action} failed: ${error instanceof Error ? error.message : String(error)}`, { status: "error" });
521
- }
522
- }
523
-
524
- case "schedule.create":
525
- case "schedule.list":
526
- case "schedule.show":
527
- case "schedule.history":
528
- case "schedule.pause":
529
- case "schedule.resume":
530
- case "schedule.run":
531
- case "schedule.run-due":
532
- case "schedule.delete": {
533
- if (!deps.runAsync) {
534
- return textResult("Schedule actions require the background process runner, which is unavailable in this host.", { status: "error" });
535
- }
536
- const manager = new ScheduledRunManager(deps.projectRoot ?? process.cwd(), {
537
- storeRoot: deps.config.scheduledRuns?.storeRoot,
538
- maxPending: deps.config.scheduledRuns?.maxPending,
539
- launch: async (record) => {
540
- const result = await deps.runAsync!({
541
- agentName: record.agent,
542
- task: record.task,
543
- description: `schedule: ${record.name}`,
544
- context: "fresh",
545
- timeoutMs: record.timeoutMs,
546
- });
547
- return result.runId;
548
- },
549
- });
550
-
551
- try {
552
- switch (action) {
553
- case "schedule.create": {
554
- const record = manager.create({
555
- name: (args.name as string | undefined) ?? `schedule-${Date.now().toString(36)}`,
556
- agent: (args.agent ?? args.type) as string,
557
- task: (args.task ?? args.prompt) as string,
558
- at: args.at as string | undefined,
559
- every: args.every as string | undefined,
560
- catchUp: args.catchUp as "none" | "latest" | undefined,
561
- timeoutMs: args.timeoutMs as number | undefined,
562
- });
563
- const next = record.trigger.kind === "once"
564
- ? new Date(record.trigger.atMs).toISOString()
565
- : record.trigger.nextRunAt;
566
- return textResult(
567
- `Schedule created: ${record.id}\nName: ${record.name}\nAgent: ${record.agent}\nTrigger: ${record.trigger.kind === "once" ? `once at ${next}` : `every ${record.trigger.every}`}\nNext run: ${next}`,
568
- { status: "completed", scheduleId: record.id },
569
- );
570
- }
571
- case "schedule.list": {
572
- const all = manager.list();
573
- if (all.length === 0) return textResult("No schedules for this project.");
574
- return textResult(
575
- [
576
- `Schedules (${all.length}):`,
577
- ...all.map((r) => {
578
- const next = r.trigger.kind === "once" ? r.trigger.atMs : r.trigger.nextRunAt;
579
- return `- ${r.id} "${r.name}" [${r.paused ? "paused" : "active"}] ${r.trigger.kind} → ${r.agent}: ${r.trigger.kind === "once" ? new Date(next).toISOString() : `every ${r.trigger.every}`}`;
580
- }),
581
- ].join("\n"),
582
- );
583
- }
584
- case "schedule.show": {
585
- const id = args.id as string | undefined;
586
- if (!id) return textResult("schedule.show requires id.", { status: "error" });
587
- const record = manager.show(id);
588
- if (!record) return textResult(`No schedule matches "${id}".`, { status: "error" });
589
- const history = manager.readHistory(id);
590
- return textResult(
591
- [
592
- `Schedule ${record.id} "${record.name}" [${record.paused ? "paused" : "active"}]`,
593
- `Agent: ${record.agent}`,
594
- `Task: ${record.task.slice(0, 200)}`,
595
- `Trigger: ${record.trigger.kind === "once" ? `once at ${record.trigger.at}` : `every ${record.trigger.every}`}`,
596
- `Overlap: ${record.overlap} | catchUp: ${record.catchUp}`,
597
- `Runs: ${history.length}`,
598
- ...history.slice(-5).map((r) => `- ${r.id} ${r.state}${r.error ? ` (${r.error})` : ""}`),
599
- ].join("\n"),
600
- );
601
- }
602
- case "schedule.history": {
603
- const id = args.id as string | undefined;
604
- if (!id) return textResult("schedule.history requires id.", { status: "error" });
605
- const history = manager.readHistory(id);
606
- if (history.length === 0) return textResult("No runs recorded for this schedule.");
607
- return textResult(
608
- ["Schedule runs (newest last):", ...history.map((r) => `- ${r.id} ${r.state} ${r.plannedAt}${r.error ? ` (${r.error})` : ""}`)].join("\n"),
609
- );
610
- }
611
- case "schedule.pause":
612
- case "schedule.resume": {
613
- const id = args.id as string | undefined;
614
- if (!id) return textResult(`${action} requires id.`, { status: "error" });
615
- const record = manager.setPaused(id, action === "schedule.pause");
616
- return textResult(`Schedule ${record.id} "${record.name}" ${record.paused ? "paused" : "resumed"}.`);
617
- }
618
- case "schedule.run": {
619
- const id = args.id as string | undefined;
620
- if (!id) return textResult("schedule.run requires id.", { status: "error" });
621
- const run = await manager.runNow(id);
622
- return textResult(
623
- run.state === "completed"
624
- ? `Schedule ran: async run ${run.asyncRunId}`
625
- : `Schedule run ${run.state}${run.error ? `: ${run.error}` : ""}`,
626
- { status: run.state === "completed" ? "completed" : "error" },
627
- );
628
- }
629
- case "schedule.run-due": {
630
- const runs = await manager.runDue();
631
- return textResult(`${runs.length} due schedule(s) executed.`);
632
- }
633
- case "schedule.delete": {
634
- const id = args.id as string | undefined;
635
- if (!id) return textResult("schedule.delete requires id.", { status: "error" });
636
- const removed = manager.delete(id);
637
- return textResult(removed ? `Schedule ${id} deleted.` : `No schedule matches "${id}".`, { status: removed ? "completed" : "error" });
638
- }
639
- default:
640
- return textResult(`Unknown schedule action.`, { status: "error" });
641
- }
642
- } catch (error) {
643
- return textResult(`${action} failed: ${error instanceof Error ? error.message : String(error)}`, { status: "error" });
644
- }
645
- }
646
-
647
- default:
648
- // Actions that belong to later phases report their status clearly.
649
- return textResult(
650
- `action "${action}" is recognized but not yet implemented in this build (planned phase).`,
651
- { status: "error" },
652
- );
653
- }
654
- }
655
-
656
- // ============================================================================
657
- // workflowScript execution (foreground)
658
- // ============================================================================
659
-
660
- async function handleWorkflowScript(
661
- deps: HandlerDeps,
662
- ctx: ExtensionContext,
663
- script: string,
664
- args: Record<string, unknown>,
665
- signal: AbortSignal | undefined,
666
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details?: unknown }> {
667
- const asyncRequested = (args.async as boolean | undefined) ?? deps.config.asyncByDefault ?? true;
668
- if (asyncRequested) {
669
- return handleAsyncWorkflowScript(deps, ctx, script, args, signal);
670
- }
671
-
672
- // Validate call-level budgets up front (reference behavior).
673
- if (args.turnBudget !== undefined) {
674
- const { error } = resolveTurnBudgetConfig(args.turnBudget);
675
- if (error) return textResult(error, { status: "error" });
676
- }
677
- if (args.toolBudget !== undefined) {
678
- const { error } = validateToolBudgetConfig(args.toolBudget);
679
- if (error) return textResult(error, { status: "error" });
680
- }
681
- if (args.usageBudget !== undefined) {
682
- const { error } = validateUsageBudgetConfig(args.usageBudget);
683
- if (error) return textResult(error, { status: "error" });
684
- }
685
-
686
- // Spawn budget: one fanout budget per top-level workflow run.
687
- const perRunCap = deps.config.maxSubagentSpawnsPerRun ?? 64;
688
- const budget = createRunFanoutBudget(`workflow-${Date.now()}`, perRunCap);
689
-
690
- const sessionAccounting = deps.spawnAccounting;
691
- const sessionCap = sessionAccounting?.cap();
692
-
693
- const admit = (calls: Array<{ key: string; params: Record<string, unknown> }>): void => {
694
- if (calls.length === 0) return;
695
- // Per-run budget: atomic group admission.
696
- claimRunFanoutBatch(budget, calls.map((call) => call.key));
697
- // Session budget: cumulative.
698
- if (sessionCap !== undefined && sessionAccounting) {
699
- const used = sessionAccounting.used();
700
- if (used + calls.length > sessionCap) {
701
- throw new Error(
702
- `Session spawn budget exhausted: ${used}/${sessionCap} used, ${calls.length} requested. ` +
703
- `Use spawn_helper ({ action: "grant-spawn-budget", additional: N }) from the interactive parent to extend.`,
704
- );
705
- }
706
- sessionAccounting.consume(calls.length);
707
- }
708
- };
709
-
710
- const launch = async (
711
- key: string,
712
- params: Record<string, unknown>,
713
- childSignal: AbortSignal,
714
- ): Promise<WorkflowScriptChildResult> => {
715
- const agentNameRaw = params.agent as string | undefined;
716
- const task = params.task as string | undefined;
717
- if (!agentNameRaw || !task) {
718
- return { key, ok: false, output: "runs.run requires agent and task.", error: "missing agent/task", artifactPaths: [] };
719
- }
720
- const agentName = deps.manager.resolveAlias(agentNameRaw);
721
- const agent = deps.manager.getAgentConfig(agentName);
722
- if (!agent) {
723
- return { key, ok: false, output: `Unknown agent "${agentNameRaw}".`, error: "unknown agent", artifactPaths: [] };
724
- }
725
- if (!deps.manager.isTypeEnabled(agentName)) {
726
- return { key, ok: false, output: `Agent "${agentName}" is disabled.`, error: "disabled", artifactPaths: [] };
727
- }
728
-
729
- // Context policy (foreground = fresh only).
730
- const contextMode = resolveContext(params.context as string | undefined, agent, deps.config);
731
- if (contextMode === "fork") {
732
- return {
733
- key,
734
- ok: false,
735
- output: 'context "fork" requires background execution; foreground workflow children are fresh-context.',
736
- error: "fork unavailable in foreground",
737
- artifactPaths: [],
738
- };
739
- }
740
-
741
- // Turn budget: wrap-up block in the child prompt.
742
- let childTask = task;
743
- const turnBudgetResult = resolveTurnBudgetConfig(params.turnBudget ?? args.turnBudget);
744
- if (turnBudgetResult.turnBudget) {
745
- childTask = appendTurnBudgetSystemPrompt(task, turnBudgetResult.turnBudget);
746
- }
747
-
748
- // Boundary instructions (plain or fanout child).
749
- childTask = withChildBoundaryInstructions(childTask, agent);
750
-
751
- const result = await deps.spawnForeground(ctx, agentName, childTask, {
752
- description: (params.label as string | undefined) ?? key,
753
- maxTurns: (params.maxTurns as number | undefined) ?? (turnBudgetResult.turnBudget?.maxTurns),
754
- modelInput: (params.model as string | undefined) ?? agent.model,
755
- thinkingLevel: agent.thinking,
756
- signal: childSignal,
757
- });
758
-
759
- return {
760
- key,
761
- ok: result.ok,
762
- output: result.output,
763
- ...(result.error ? { error: result.error } : {}),
764
- ...(agentName ? { agent: agentName } : {}),
765
- artifactPaths: [],
766
- };
767
- };
768
-
769
- const status = async (keyOrRunId: string): Promise<WorkflowScriptChildResult> => {
770
- const record = deps.manager.getRecord(keyOrRunId);
771
- if (record) {
772
- return {
773
- key: keyOrRunId,
774
- ok: record.status === "completed",
775
- output: record.result ?? record.status,
776
- ...(record.error ? { error: record.error } : {}),
777
- artifactPaths: [],
778
- };
779
- }
780
- return { key: keyOrRunId, ok: true, output: "ok", artifactPaths: [] };
781
- };
782
-
783
- try {
784
- const result = await runWorkflowScript({
785
- script,
786
- timeoutMs: args.timeoutMs as number | undefined,
787
- signal,
788
- admit,
789
- launch,
790
- status,
791
- });
792
-
793
- const maxOutput = resolveMaxOutput(args.maxOutput as never, deps.config);
794
- const summary =
795
- typeof result.value === "string"
796
- ? result.value
797
- : JSON.stringify(result.value, null, 2) ?? "null";
798
- const truncated = truncateOutput(summary, maxOutput);
799
- return textResult(truncated.text, {
800
- status: "completed",
801
- children: result.children.length,
802
- fanout: formatRunFanoutBudget(getRunFanoutBudgetSnapshot(budget)),
803
- truncated: truncated.truncated,
804
- });
805
- } catch (error) {
806
- if (error instanceof Error && error.name === "WorkflowScriptError") {
807
- const partial = (error as { partial?: { children?: unknown[] } }).partial;
808
- return textResult(
809
- `Workflow failed: ${error.message}`,
810
- { status: "error", children: partial?.children?.length ?? 0 },
811
- );
812
- }
813
- throw error;
814
- }
815
- }
816
-
817
- // ============================================================================
818
- // Single-child launch (legacy + reference)
819
- // ============================================================================
820
-
821
- async function handleSingleChild(
822
- deps: HandlerDeps,
823
- ctx: ExtensionContext,
824
- args: Record<string, unknown>,
825
- signal: AbortSignal | undefined,
826
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details?: unknown }> {
827
- const pre = preflight(deps, args);
828
- if (!pre.ok) return textResult(pre.error, { status: "error" });
829
-
830
- const { agent, agentName, prompt } = pre;
831
- const asyncRequested =
832
- (args.async as boolean | undefined) ??
833
- (args.run_in_background as boolean | undefined) ??
834
- agent.runInBackground ??
835
- false;
836
-
837
- // Context policy. EXPLICIT fork never silently downgrades (reference rule);
838
- // implicit fork (agent/config default) falls back to fresh when the in-process
839
- // path cannot fork (reference: implicit fork needs a persisted parent session,
840
- // else fresh).
841
- const explicitContext = args.context as string | undefined;
842
- const contextMode = resolveContext(explicitContext, agent, deps.config);
843
- if (contextMode === "fork" && (explicitContext === "fork" || deps.config.defaultSubagentContext === "fork")) {
844
- if (!asyncRequested) {
845
- return textResult(
846
- 'context: "fork" requires background execution (run_in_background: true); the in-process foreground path is fresh-context only.',
847
- { status: "error" },
848
- );
849
- }
850
- if (!deps.runAsync) {
851
- return textResult(
852
- 'context: "fork" requires the background process runner, which is unavailable in this host. Re-run without context, or use context: "fresh".',
853
- { status: "error" },
854
- );
855
- }
856
- }
857
-
858
- // Budgets: validate + decorate.
859
- let childPrompt = prompt;
860
- const turnBudgetResult = resolveTurnBudgetConfig(args.turnBudget);
861
- if (turnBudgetResult.error) return textResult(turnBudgetResult.error, { status: "error" });
862
- if (turnBudgetResult.turnBudget) {
863
- childPrompt = appendTurnBudgetSystemPrompt(childPrompt, turnBudgetResult.turnBudget);
864
- }
865
- const toolBudgetResult = validateToolBudgetConfig(args.toolBudget);
866
- if (toolBudgetResult.error) return textResult(toolBudgetResult.error, { status: "error" });
867
- const usageBudgetResult = validateUsageBudgetConfig(args.usageBudget);
868
- if (usageBudgetResult.error) return textResult(usageBudgetResult.error, { status: "error" });
869
-
870
- // Acceptance gates (gate shorthand + acceptance object).
871
- const acceptanceResolved = resolveEffectiveAcceptance(args);
872
- if (!acceptanceResolved.ok) return textResult(acceptanceResolved.error, { status: "error" });
873
-
874
- // Boundary instructions (unless the agent is a fanout child).
875
- if (!isFanoutChild(agent)) {
876
- childPrompt = withChildBoundaryInstructions(childPrompt, agent);
877
- }
878
-
879
- // Session spawn budget.
880
- const sessionAccounting = deps.spawnAccounting;
881
- if (sessionAccounting) {
882
- const cap = sessionAccounting.cap();
883
- if (cap !== undefined && sessionAccounting.used() >= cap) {
884
- return textResult(
885
- `Session spawn budget exhausted: ${sessionAccounting.used()}/${cap} used. ` +
886
- `Use spawn_helper ({ action: "grant-spawn-budget", additional: N }) from the interactive parent to extend.`,
887
- { status: "error" },
888
- );
889
- }
890
- sessionAccounting.consume(1);
891
- }
892
-
893
- const description = (args.description as string | undefined) ?? `${agentName} task`;
894
-
895
- if (asyncRequested) {
896
- // Fork context requires the process runner; fresh background runs prefer
897
- // it too when available (survives parent exit), else fall back in-process.
898
- const wantsProcessRunner = contextMode === "fork" || deps.config.asyncByDefault === true;
899
- if (deps.runAsync && (contextMode === "fork" || wantsProcessRunner)) {
900
- const result = await deps.runAsync({
901
- agentName,
902
- task: childPrompt,
903
- description,
904
- model: (args.model as string | undefined) ?? agent.model,
905
- thinking: (args.thinking as string | undefined) ?? (typeof agent.thinking === "string" ? agent.thinking : undefined),
906
- context: contextMode,
907
- timeoutMs: (args.timeoutMs as number | undefined) ?? agent.timeoutMs,
908
- maxTurns: (args.max_turns as number | undefined) ?? (args.maxTurns as number | undefined) ?? turnBudgetResult.turnBudget?.maxTurns,
909
- });
910
- return textResult(
911
- `Agent started in background (process mode).\nRun ID: ${result.runId}\nType: ${agentName}\nDescription: ${description}\n\nYou will be notified when this agent completes.\nUse get_helper_result with the run ID to retrieve results.`,
912
- { status: "background", runId: result.runId, agentId: result.runId },
913
- );
914
- }
915
- const id = deps.spawnBackground(ctx, agentName, childPrompt, {
916
- description,
917
- maxTurns: (args.max_turns as number | undefined) ?? (args.maxTurns as number | undefined) ?? turnBudgetResult.turnBudget?.maxTurns,
918
- modelInput: (args.model as string | undefined) ?? agent.model,
919
- thinkingLevel: args.thinking ?? agent.thinking,
920
- });
921
- return textResult(
922
- `Agent started in background.\nID: ${id}\nType: ${agentName}\nDescription: ${description}\n\nYou will be notified when this agent completes.\nUse get_helper_result to retrieve full results.`,
923
- { status: "background", agentId: id },
924
- );
925
- }
926
-
927
- const result = await deps.spawnForeground(ctx, agentName, childPrompt, {
928
- description,
929
- maxTurns: (args.max_turns as number | undefined) ?? (args.maxTurns as number | undefined) ?? turnBudgetResult.turnBudget?.maxTurns,
930
- modelInput: (args.model as string | undefined) ?? agent.model,
931
- thinkingLevel: args.thinking ?? agent.thinking,
932
- signal,
933
- });
934
-
935
- if (!result.ok) {
936
- return textResult(`Agent failed: ${result.error ?? result.output}`, {
937
- status: "error",
938
- error: result.error,
939
- toolUses: result.toolUses,
940
- durationMs: result.durationMs,
941
- });
942
- }
943
-
944
- // Acceptance gate evaluation (host-side verify commands + report checks).
945
- let finalOutput = result.output;
946
- let ledgerDetails: unknown;
947
- if (acceptanceResolved.acceptance) {
948
- const evaluated = await evaluateRunAcceptance(acceptanceResolved.acceptance, result.output, process.cwd());
949
- finalOutput = evaluated.output;
950
- ledgerDetails = evaluated.ledger;
951
- if (evaluated.ledger?.status === "rejected") {
952
- return textResult(
953
- `Agent completed but acceptance rejected:\n${evaluated.ledger.failureMessage}\n\nOutput:\n${finalOutput.slice(0, 2000)}`,
954
- { status: "error", acceptance: evaluated.ledger.status, failureMessage: evaluated.ledger.failureMessage },
955
- );
956
- }
957
- }
958
-
959
- // Output truncation with maxOutput config (reference default 200KB/5000 lines).
960
- const maxOutput = resolveMaxOutput(args.maxOutput as never, deps.config);
961
- const truncated = truncateOutput(finalOutput, maxOutput);
962
-
963
- return textResult(
964
- `Agent completed in ${(result.durationMs / 1000).toFixed(1)}s (${result.toolUses} tool uses).\n\n${truncated.text}`,
965
- {
966
- status: "completed",
967
- toolUses: result.toolUses,
968
- durationMs: result.durationMs,
969
- ...(ledgerDetails ? { acceptance: (ledgerDetails as { status: string }).status } : {}),
970
- truncated: truncated.truncated,
971
- originalBytes: truncated.originalBytes,
972
- },
973
- );
974
- }
975
-
976
-
977
- // ============================================================================
978
- // Async workflowScript (process-backed children)
979
- // ============================================================================
980
-
981
- /**
982
- * Run a workflow with every child launched through the process runner
983
- * (child pi processes). The workflow runtime is identical; only the launch
984
- * transport differs. The tool call blocks until the workflow settles — the
985
- * parent gets the full result. (Fully detached async workflows with live
986
- * status polling land with the Phase 4 fleet work.)
987
- */
988
- async function handleAsyncWorkflowScript(
989
- deps: HandlerDeps,
990
- ctx: ExtensionContext,
991
- script: string,
992
- args: Record<string, unknown>,
993
- signal: AbortSignal | undefined,
994
- ): Promise<{ content: Array<{ type: "text"; text: string }>; details?: unknown }> {
995
- if (!deps.runAsync) {
996
- return textResult(
997
- "Async workflowScript requires the background process runner, which is unavailable in this host. " +
998
- "Re-run with async: false to execute the workflow in-process in the foreground.",
999
- { status: "error" },
1000
- );
1001
- }
1002
-
1003
- // Validate call-level budgets up front.
1004
- if (args.turnBudget !== undefined) {
1005
- const { error } = resolveTurnBudgetConfig(args.turnBudget);
1006
- if (error) return textResult(error, { status: "error" });
1007
- }
1008
- if (args.toolBudget !== undefined) {
1009
- const { error } = validateToolBudgetConfig(args.toolBudget);
1010
- if (error) return textResult(error, { status: "error" });
1011
- }
1012
- if (args.usageBudget !== undefined) {
1013
- const { error } = validateUsageBudgetConfig(args.usageBudget);
1014
- if (error) return textResult(error, { status: "error" });
1015
- }
1016
-
1017
- const perRunCap = deps.config.maxSubagentSpawnsPerRun ?? 64;
1018
- const budget = createRunFanoutBudget(`workflow-async-${Date.now()}`, perRunCap);
1019
- const sessionAccounting = deps.spawnAccounting;
1020
- const sessionCap = sessionAccounting?.cap();
1021
-
1022
- const admit = (calls: Array<{ key: string; params: Record<string, unknown> }>): void => {
1023
- if (calls.length === 0) return;
1024
- claimRunFanoutBatch(budget, calls.map((call) => call.key));
1025
- if (sessionCap !== undefined && sessionAccounting) {
1026
- const used = sessionAccounting.used();
1027
- if (used + calls.length > sessionCap) {
1028
- throw new Error(
1029
- `Session spawn budget exhausted: ${used}/${sessionCap} used, ${calls.length} requested.`,
1030
- );
1031
- }
1032
- sessionAccounting.consume(calls.length);
1033
- }
1034
- };
1035
-
1036
- const launch = async (
1037
- key: string,
1038
- params: Record<string, unknown>,
1039
- childSignal: AbortSignal,
1040
- ): Promise<WorkflowScriptChildResult> => {
1041
- const agentNameRaw = params.agent as string | undefined;
1042
- const task = params.task as string | undefined;
1043
- if (!agentNameRaw || !task) {
1044
- return { key, ok: false, output: "runs.run requires agent and task.", error: "missing agent/task", artifactPaths: [] };
1045
- }
1046
- const agentName = deps.manager.resolveAlias(agentNameRaw);
1047
- const agent = deps.manager.getAgentConfig(agentName);
1048
- if (!agent) {
1049
- return { key, ok: false, output: `Unknown agent "${agentNameRaw}".`, error: "unknown agent", artifactPaths: [] };
1050
- }
1051
- if (!deps.manager.isTypeEnabled(agentName)) {
1052
- return { key, ok: false, output: `Agent "${agentName}" is disabled.`, error: "disabled", artifactPaths: [] };
1053
- }
1054
-
1055
- // Context policy: fork children get branched sessions via the process runner.
1056
- const contextMode = resolveContext(params.context as string | undefined, agent, deps.config);
1057
-
1058
- let childTask = task;
1059
- const turnBudgetResult = resolveTurnBudgetConfig(params.turnBudget ?? args.turnBudget);
1060
- if (turnBudgetResult.turnBudget) {
1061
- childTask = appendTurnBudgetSystemPrompt(task, turnBudgetResult.turnBudget);
1062
- }
1063
- childTask = withChildBoundaryInstructions(childTask, agent);
1064
-
1065
- try {
1066
- const worktreeRequested =
1067
- (params.worktree as boolean | undefined) ??
1068
- (args.worktree as boolean | undefined) ??
1069
- (args.isolation === "worktree" ? true : undefined);
1070
- const result = await deps.runAsync!({
1071
- agentName,
1072
- task: childTask,
1073
- description: (params.label as string | undefined) ?? key,
1074
- model: (params.model as string | undefined) ?? agent.model,
1075
- thinking: typeof agent.thinking === "string" ? agent.thinking : undefined,
1076
- context: contextMode,
1077
- timeoutMs: (params.timeoutMs as number | undefined) ?? agent.timeoutMs,
1078
- maxTurns: (params.maxTurns as number | undefined) ?? turnBudgetResult.turnBudget?.maxTurns,
1079
- ...(worktreeRequested === true ? { worktree: true } : {}),
1080
- });
1081
- void childSignal; // process-level abort handled by the runner's controller
1082
- return {
1083
- key,
1084
- ok: result.status === "completed",
1085
- output: result.output ?? result.error ?? result.status,
1086
- ...(result.error ? { error: result.error } : {}),
1087
- agent: agentName,
1088
- ...(result.runId ? { runId: result.runId } : {}),
1089
- artifactPaths: [],
1090
- };
1091
- } catch (error) {
1092
- const text = error instanceof Error ? error.message : String(error);
1093
- return { key, ok: false, output: text, error: text, artifactPaths: [] };
1094
- }
1095
- };
1096
-
1097
- const status = async (keyOrRunId: string): Promise<WorkflowScriptChildResult> => {
1098
- const record = deps.manager.getRecord(keyOrRunId);
1099
- if (record) {
1100
- return {
1101
- key: keyOrRunId,
1102
- ok: record.status === "completed",
1103
- output: record.result ?? record.status,
1104
- ...(record.error ? { error: record.error } : {}),
1105
- artifactPaths: [],
1106
- };
1107
- }
1108
- return { key: keyOrRunId, ok: true, output: "ok", artifactPaths: [] };
1109
- };
1110
-
1111
- try {
1112
- const result = await runWorkflowScript({
1113
- script,
1114
- timeoutMs: args.timeoutMs as number | undefined,
1115
- signal,
1116
- admit,
1117
- launch,
1118
- status,
1119
- });
1120
-
1121
- const maxOutput = resolveMaxOutput(args.maxOutput as never, deps.config);
1122
- const summary =
1123
- typeof result.value === "string"
1124
- ? result.value
1125
- : JSON.stringify(result.value, null, 2) ?? "null";
1126
- const truncated = truncateOutput(summary, maxOutput);
1127
- return textResult(truncated.text, {
1128
- status: "completed",
1129
- mode: "async-workflow",
1130
- children: result.children.length,
1131
- fanout: formatRunFanoutBudget(getRunFanoutBudgetSnapshot(budget)),
1132
- truncated: truncated.truncated,
1133
- });
1134
- } catch (error) {
1135
- if (error instanceof Error && error.name === "WorkflowScriptError") {
1136
- const partial = (error as { partial?: { children?: unknown[] } }).partial;
1137
- return textResult(
1138
- `Workflow failed: ${error.message}`,
1139
- { status: "error", mode: "async-workflow", children: partial?.children?.length ?? 0 },
1140
- );
1141
- }
1142
- throw error;
1143
- }
1144
- }