@duckmind/dm-windows-x64 0.61.4 → 0.61.9

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 (279) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +211 -67
  3. package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
  4. package/extensions/dm-subagents/agents/claude-code.md +15 -0
  5. package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
  6. package/extensions/dm-subagents/agents/codex-exec.md +15 -0
  7. package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
  8. package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
  9. package/extensions/dm-subagents/agents/delegate.md +3 -2
  10. package/extensions/dm-subagents/agents/oracle.md +10 -5
  11. package/extensions/dm-subagents/agents/researcher.md +2 -2
  12. package/extensions/dm-subagents/agents/reviewer.md +17 -7
  13. package/extensions/dm-subagents/agents/scout.md +5 -5
  14. package/extensions/dm-subagents/agents/worker.md +6 -2
  15. package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
  16. package/extensions/dm-subagents/index.js +4 -0
  17. package/extensions/dm-subagents/inspector-runner.mjs +10 -0
  18. package/extensions/dm-subagents/install.mjs +3 -2
  19. package/extensions/dm-subagents/package.json +2 -2
  20. package/extensions/dm-subagents/prompts/council.md +60 -0
  21. package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
  22. package/extensions/dm-subagents/prompts/review-loop.md +13 -7
  23. package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
  24. package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
  25. package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
  26. package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
  27. package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
  28. package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
  29. package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
  30. package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
  31. package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
  32. package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
  33. package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
  34. package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
  35. package/extensions/dm-subagents/src/agents/agents.js +1447 -299
  36. package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
  37. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
  38. package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
  39. package/extensions/dm-subagents/src/agents/identity.js +1 -1
  40. package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
  41. package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
  42. package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
  43. package/extensions/dm-subagents/src/agents/skills.js +52 -35
  44. package/extensions/dm-subagents/src/api/agents.js +6 -0
  45. package/extensions/dm-subagents/src/api/background-work.js +151 -0
  46. package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
  47. package/extensions/dm-subagents/src/api/control-channel.js +3 -0
  48. package/extensions/dm-subagents/src/api/delegation.js +5 -0
  49. package/extensions/dm-subagents/src/api/dm-args.js +3 -0
  50. package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
  51. package/extensions/dm-subagents/src/api/external-runs.js +233 -0
  52. package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
  53. package/extensions/dm-subagents/src/api/preflight.js +322 -0
  54. package/extensions/dm-subagents/src/api/project-panes.js +11 -0
  55. package/extensions/dm-subagents/src/api/shared-types.js +4 -0
  56. package/extensions/dm-subagents/src/extension/config.js +175 -0
  57. package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
  58. package/extensions/dm-subagents/src/extension/doctor.js +71 -17
  59. package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
  60. package/extensions/dm-subagents/src/extension/index.js +711 -265
  61. package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
  62. package/extensions/dm-subagents/src/extension/rpc.js +427 -22
  63. package/extensions/dm-subagents/src/extension/schemas.js +158 -65
  64. package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
  65. package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
  66. package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
  67. package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
  68. package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
  69. package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
  70. package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
  71. package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
  72. package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
  73. package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
  74. package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
  75. package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
  76. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
  77. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
  78. package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
  79. package/extensions/dm-subagents/src/missions/actions.js +394 -0
  80. package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
  81. package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
  82. package/extensions/dm-subagents/src/missions/store.js +548 -0
  83. package/extensions/dm-subagents/src/missions/types.js +9 -0
  84. package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
  85. package/extensions/dm-subagents/src/policy/authority.js +37 -0
  86. package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
  87. package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
  88. package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
  89. package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
  90. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
  91. package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
  92. package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
  93. package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
  94. package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
  95. package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
  96. package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
  97. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
  98. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
  99. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
  100. package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
  101. package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
  102. package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
  103. package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
  104. package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
  105. package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
  106. package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
  107. package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
  108. package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
  109. package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
  110. package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
  111. package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
  112. package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
  113. package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
  114. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
  115. package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
  116. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
  117. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
  118. package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
  119. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
  120. package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
  121. package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
  122. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
  123. package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
  124. package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
  125. package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
  126. package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
  127. package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
  128. package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
  129. package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
  130. package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
  131. package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
  132. package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
  133. package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
  134. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
  135. package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
  136. package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
  137. package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
  138. package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
  139. package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
  140. package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
  141. package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
  142. package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
  143. package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
  144. package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
  145. package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
  146. package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
  147. package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
  148. package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
  149. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
  150. package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
  151. package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
  152. package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
  153. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
  154. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
  155. package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
  156. package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
  157. package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
  158. package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
  159. package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
  160. package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
  161. package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
  162. package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
  163. package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
  164. package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
  165. package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
  166. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
  167. package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
  168. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
  169. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
  170. package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
  171. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
  172. package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
  173. package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
  174. package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
  175. package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
  176. package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
  177. package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
  178. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
  179. package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
  180. package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
  181. package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
  182. package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
  183. package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
  184. package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
  185. package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
  186. package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
  187. package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
  188. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
  189. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
  190. package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
  191. package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
  192. package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
  193. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
  194. package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
  195. package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
  196. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
  197. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
  198. package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
  199. package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
  200. package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
  201. package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
  202. package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
  203. package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
  204. package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
  205. package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
  206. package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
  207. package/extensions/dm-subagents/src/shared/display-text.js +142 -0
  208. package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
  209. package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
  210. package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
  211. package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
  212. package/extensions/dm-subagents/src/shared/formatters.js +21 -7
  213. package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
  214. package/extensions/dm-subagents/src/shared/model-info.js +10 -5
  215. package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
  216. package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
  217. package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
  218. package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
  219. package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
  220. package/extensions/dm-subagents/src/shared/settings.js +39 -47
  221. package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
  222. package/extensions/dm-subagents/src/shared/status-format.js +9 -2
  223. package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
  224. package/extensions/dm-subagents/src/shared/types.js +47 -7
  225. package/extensions/dm-subagents/src/shared/utf8.js +12 -0
  226. package/extensions/dm-subagents/src/shared/utils.js +151 -131
  227. package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
  228. package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
  229. package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
  230. package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
  231. package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
  232. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
  233. package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
  234. package/extensions/dm-subagents/src/slash/selector.js +101 -0
  235. package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
  236. package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
  237. package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
  238. package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
  239. package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
  240. package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
  241. package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
  242. package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
  243. package/extensions/dm-subagents/src/tui/render.js +1511 -261
  244. package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
  245. package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
  246. package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
  247. package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
  248. package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
  249. package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
  250. package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
  251. package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
  252. package/extensions/dm-subagents/src/watchdog/render.js +54 -0
  253. package/extensions/dm-subagents/src/watchdog/review.js +251 -0
  254. package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
  255. package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
  256. package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
  257. package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
  258. package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
  259. package/extensions/dm-subagents/src/watchdog/types.js +29 -0
  260. package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
  261. package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
  262. package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
  263. package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
  264. package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
  265. package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
  266. package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
  267. package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
  268. package/package.json +4 -3
  269. package/extensions/dm-fff/package.json +0 -21
  270. package/extensions/dm-fff/src/index.js +0 -691
  271. package/extensions/dm-fff/src/query.js +0 -60
  272. package/extensions/dm-subagents/agents/context-builder.md +0 -46
  273. package/extensions/dm-subagents/agents/planner.md +0 -55
  274. package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
  275. package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
  276. package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
  277. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
  278. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
  279. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
@@ -2,8 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { keyText } from "@duckmind/dm-coding-agent";
5
- import { Key, matchesKey } from "@duckmind/dm-tui";
6
- import { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll } from "../agents/agents.js";
5
+ import { Key, matchesKey, truncateToWidth } from "@duckmind/dm-tui";
6
+ import { BUILTIN_AGENT_NAMES, discoverAgentSnapshot, discoverAgents, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext } from "../agents/agents.js";
7
+ import { listRuntimeAgentConfigs, mergeRuntimeAgents } from "../agents/runtime-agent-registry.js";
8
+ import { resolveExistingReadPaths } from "../shared/settings.js";
7
9
  import {
8
10
  DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS,
9
11
  applySubagentProfile,
@@ -13,12 +15,21 @@ import {
13
15
  readSubagentProfile,
14
16
  refreshProviderModelCatalog
15
17
  } from "../profiles/profiles.js";
16
- import { isDynamicParallelStep, isParallelStep } from "../shared/settings.js";
17
18
  import { findModelInfo, toModelInfo } from "../shared/model-info.js";
18
- import { formatTokens } from "../shared/formatters.js";
19
- import { assertJsonSchemaObject } from "../runs/shared/structured-output.js";
20
- import { validateAcceptanceInput } from "../runs/shared/acceptance.js";
19
+ import { formatTokens, shortenPath } from "../shared/formatters.js";
20
+ import { listAsyncRuns, formatAsyncRunProgressLabel } from "../runs/background/async-status.js";
21
+ import { encodeInspectReply, handleInspectRpcArgs, INSPECT_WIDGET_KEY } from "../runs/background/inspect-rpc.js";
22
+ import { listScheduledRunSummaries } from "../runs/background/scheduled-runs.js";
23
+ import { SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/dm-args.js";
24
+ import { resolveAsyncStatusChild } from "../runs/shared/child-identity.js";
25
+ import { readStatus } from "../shared/utils.js";
26
+ import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.js";
27
+ import { readWorkflowReceipt } from "../workflows/workflow-receipt.js";
28
+ import { FLEET_OPEN_SHORTCUT } from "../shared/shortcuts.js";
21
29
  import { registerPromptWorkflowCommands } from "./prompt-workflows.js";
30
+ import { openSubagentsAdmin } from "./subagents-admin.js";
31
+ import { SUBAGENT_GUIDE_TOPICS } from "../extension/subagent-guide.js";
32
+ import { openSubagentFleet } from "../tui/fleet.js";
22
33
  import {
23
34
  applySlashUpdate,
24
35
  buildSlashInitialResult,
@@ -33,7 +44,8 @@ import {
33
44
  SLASH_SUBAGENT_REQUEST_EVENT,
34
45
  SLASH_SUBAGENT_RESPONSE_EVENT,
35
46
  SLASH_SUBAGENT_STARTED_EVENT,
36
- SLASH_SUBAGENT_UPDATE_EVENT
47
+ SLASH_SUBAGENT_UPDATE_EVENT,
48
+ DIRS
37
49
  } from "../shared/types.js";
38
50
  const parseInlineConfig = (raw) => {
39
51
  const config = {};
@@ -42,11 +54,8 @@ const parseInlineConfig = (raw) => {
42
54
  if (!trimmed)
43
55
  continue;
44
56
  const eq = trimmed.indexOf("=");
45
- if (eq === -1) {
46
- if (trimmed === "progress")
47
- config.progress = true;
57
+ if (eq === -1)
48
58
  continue;
49
- }
50
59
  const key = trimmed.slice(0, eq).trim();
51
60
  const val = trimmed.slice(eq + 1).trim();
52
61
  switch (key) {
@@ -67,33 +76,6 @@ const parseInlineConfig = (raw) => {
67
76
  case "skills":
68
77
  config.skill = val === "false" ? false : val.split("+").filter(Boolean);
69
78
  break;
70
- case "progress":
71
- config.progress = val !== "false";
72
- break;
73
- case "as":
74
- config.as = val || undefined;
75
- break;
76
- case "label":
77
- config.label = val || undefined;
78
- break;
79
- case "phase":
80
- config.phase = val || undefined;
81
- break;
82
- case "cwd":
83
- config.cwd = val || undefined;
84
- break;
85
- case "count": {
86
- const n = Number(val);
87
- if (Number.isInteger(n) && n > 0)
88
- config.count = n;
89
- break;
90
- }
91
- case "outputSchema":
92
- config.outputSchema = val || undefined;
93
- break;
94
- case "acceptance":
95
- config.acceptance = val || undefined;
96
- break;
97
79
  }
98
80
  }
99
81
  return config;
@@ -124,73 +106,27 @@ const extractExecutionFlags = (rawArgs) => {
124
106
  }
125
107
  return { args, bg, fork };
126
108
  };
127
- const makeAgentCompletions = (state, multiAgent) => (prefix) => {
128
- if (!state.baseCwd)
129
- return null;
130
- const agents = discoverAgents(state.baseCwd, "both").agents;
131
- if (!multiAgent) {
132
- if (prefix.includes(" "))
133
- return null;
134
- return agents.filter((a) => a.name.startsWith(prefix)).map((a) => ({ value: a.name, label: a.name }));
135
- }
136
- let inSingle = false, inDouble = false, depth = 0, segStart = 0;
137
- for (let i = 0;i < prefix.length; i++) {
138
- const ch = prefix[i];
139
- if (inSingle) {
140
- if (ch === "'")
141
- inSingle = false;
142
- continue;
143
- }
144
- if (inDouble) {
145
- if (ch === '"')
146
- inDouble = false;
147
- continue;
148
- }
149
- if (ch === "'") {
150
- inSingle = true;
151
- continue;
152
- }
153
- if (ch === '"') {
154
- inDouble = true;
155
- continue;
156
- }
157
- if (ch === "(") {
158
- if (!prefix.slice(segStart, i).includes(" -- ")) {
159
- depth++;
160
- segStart = i + 1;
161
- }
162
- } else if (ch === ")") {
163
- if (depth > 0) {
164
- depth--;
165
- segStart = i + 1;
166
- }
167
- } else if (ch === "|" && depth > 0)
168
- segStart = i + 1;
169
- else if (ch === ">" && prefix[i - 1] === "-" && depth === 0)
170
- segStart = i + 1;
171
- }
172
- if (inSingle || inDouble)
173
- return null;
174
- const segment = prefix.slice(segStart);
175
- if (segment.includes(" -- ") || segment.includes('"') || segment.includes("'"))
176
- return null;
177
- const lastWord = (segment.match(/(\S*)$/) || ["", ""])[1];
178
- let beforeLastWord = prefix.slice(0, prefix.length - lastWord.length);
179
- if (lastWord === "" && /[>|]$/.test(beforeLastWord))
180
- beforeLastWord = `${beforeLastWord} `;
181
- return agents.filter((a) => a.name.startsWith(lastWord)).map((a) => ({ value: `${beforeLastWord}${a.name}`, label: a.name }));
182
- };
183
- const discoverSavedChains = (cwd) => {
184
- const chainsByName = new Map;
185
- for (const chain of discoverAgentsAll(cwd).chains) {
186
- chainsByName.set(chain.name, chain);
109
+ function discoverSlashAgents(dm, cwd, scope) {
110
+ if (listRuntimeAgentConfigs(dm).length === 0) {
111
+ const discovered = discoverAgents(cwd, scope);
112
+ return { ...discovered, unknownAgentDiagnosticContext: unknownAgentDiagnosticContext(discovered) };
187
113
  }
188
- return Array.from(chainsByName.values());
189
- };
190
- const makeChainCompletions = (state) => (prefix) => {
191
- if (prefix.includes(" ") || !state.baseCwd)
114
+ const snapshot = discoverAgentSnapshot(cwd, scope, undefined, { includeChains: false });
115
+ const discovered = snapshot.effective;
116
+ const all = snapshot.all;
117
+ const configuredAgents = [
118
+ ...all.builtin,
119
+ ...all.package,
120
+ ...scope !== "project" ? all.user : [],
121
+ ...scope !== "user" ? all.project : []
122
+ ];
123
+ const merged = mergeRuntimeAgents(dm, discovered, configuredAgents);
124
+ return { ...merged, unknownAgentDiagnosticContext: { ...unknownAgentDiagnosticContext(discovered), agents: merged.agents } };
125
+ }
126
+ const makeAgentCompletions = (dm, state) => (prefix) => {
127
+ if (!state.baseCwd || prefix.includes(" "))
192
128
  return null;
193
- return discoverSavedChains(state.baseCwd).filter((chain) => chain.name.startsWith(prefix)).map((chain) => ({ value: chain.name, label: chain.name }));
129
+ return discoverSlashAgents(dm, state.baseCwd, "both").agents.filter((agent) => agent.name.startsWith(prefix)).map((agent) => ({ value: agent.name, label: agent.name }));
194
130
  };
195
131
  const makeBuiltinAgentNameCompletions = () => (prefix) => {
196
132
  if (prefix.includes(" "))
@@ -206,8 +142,8 @@ const makeProviderCompletions = (state) => (prefix) => {
206
142
  const providers = [...new Set(available.map((model) => typeof model?.provider === "string" ? model.provider : "").filter(Boolean))].sort((a, b) => a.localeCompare(b));
207
143
  return providers.filter((provider) => provider.startsWith(prefix)).map((provider) => ({ value: provider, label: provider }));
208
144
  };
209
- function sendSlashText(pi, text) {
210
- pi.sendMessage({ customType: SLASH_TEXT_RESULT_TYPE, content: text, display: true });
145
+ function sendSlashText(dm, text) {
146
+ dm.sendMessage({ customType: SLASH_TEXT_RESULT_TYPE, content: text, display: true });
211
147
  }
212
148
  async function withSlashStatus(ctx, text, run) {
213
149
  if (ctx.hasUI)
@@ -219,6 +155,149 @@ async function withSlashStatus(ctx, text, run) {
219
155
  ctx.ui.setStatus("subagent-slash-text", undefined);
220
156
  }
221
157
  }
158
+ function commandForTarget(target) {
159
+ return target.kind === "scheduled" ? `subagent({ action: "schedule.pause", id: ${JSON.stringify(target.id)} })` : `subagent({ action: "stop", id: ${JSON.stringify(target.id)} })`;
160
+ }
161
+ function formatAsyncStopTarget(run) {
162
+ const progress = formatAsyncRunProgressLabel(run);
163
+ const cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);
164
+ return {
165
+ kind: "async",
166
+ id: run.id,
167
+ label: `${run.id} · ${run.mode} · ${progress}`,
168
+ detail: `${run.state} · ${cwd}`,
169
+ actionLabel: "stop async run"
170
+ };
171
+ }
172
+ function scheduledStopTargets(ctx, _state) {
173
+ try {
174
+ return listScheduledRunSummaries(ctx.cwd).filter((schedule) => !schedule.paused && !schedule.activeRunId && schedule.trigger.nextRunAt).sort((left, right) => left.trigger.nextRunAt.localeCompare(right.trigger.nextRunAt)).map((schedule) => ({
175
+ kind: "scheduled",
176
+ id: schedule.id,
177
+ label: `${schedule.id} · ${schedule.name}`,
178
+ detail: `scheduled · ${schedule.trigger.nextRunAt}`,
179
+ actionLabel: "pause schedule"
180
+ }));
181
+ } catch {
182
+ return [];
183
+ }
184
+ }
185
+ function discoverStopTargets(ctx, state) {
186
+ const sessionId = state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined;
187
+ const asyncTargets = listAsyncRuns(DIRS.async, {
188
+ states: ["queued", "running"],
189
+ ...sessionId ? { sessionId } : {}
190
+ }).map(formatAsyncStopTarget);
191
+ return [...asyncTargets, ...scheduledStopTargets(ctx, state)];
192
+ }
193
+ function stopFallbackText(targets) {
194
+ if (targets.length === 0)
195
+ return "No active current-session async runs or scheduled subagent runs to stop.";
196
+ const lines = ["Subagent stop targets:", ""];
197
+ for (const target of targets) {
198
+ lines.push(`- ${target.label}`);
199
+ lines.push(` ${target.detail}`);
200
+ lines.push(` ${target.actionLabel}: ${commandForTarget(target)}`);
201
+ if (target.kind === "async")
202
+ lines.push(` slash: /subagents-stop ${target.id}`);
203
+ }
204
+ return lines.join(`
205
+ `);
206
+ }
207
+ function selectForegroundDetachControl(state, requested) {
208
+ const controls = [...state.foregroundControls.values()];
209
+ if (requested) {
210
+ const matches = controls.filter((control) => control.runId === requested || control.runId.startsWith(requested));
211
+ if (matches.length > 1)
212
+ throw new Error(`Ambiguous foreground run id prefix '${requested}' matched: ${matches.map((control) => control.runId).join(", ")}. Provide a longer id.`);
213
+ return matches[0];
214
+ }
215
+ const singleControls = controls.filter((control) => control.mode === "single");
216
+ if (state.lastForegroundControlId) {
217
+ const latest = state.foregroundControls.get(state.lastForegroundControlId);
218
+ if (latest?.mode === "single")
219
+ return latest;
220
+ }
221
+ return singleControls.sort((left, right) => right.updatedAt - left.updatedAt)[0];
222
+ }
223
+
224
+ class SubagentsStopSelector {
225
+ width = 84;
226
+ selected = 0;
227
+ confirming = false;
228
+ tui;
229
+ theme;
230
+ targets;
231
+ done;
232
+ constructor(tui, theme, targets, done) {
233
+ this.tui = tui;
234
+ this.theme = theme;
235
+ this.targets = targets;
236
+ this.done = done;
237
+ }
238
+ handleInput(data) {
239
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
240
+ this.done({ confirmed: false });
241
+ return;
242
+ }
243
+ if (this.confirming) {
244
+ if (matchesKey(data, "return") || data.toLowerCase() === "y") {
245
+ this.done({ confirmed: true, target: this.targets[this.selected] });
246
+ return;
247
+ }
248
+ if (data.toLowerCase() === "n" || matchesKey(data, "backspace")) {
249
+ this.confirming = false;
250
+ this.tui.requestRender();
251
+ }
252
+ return;
253
+ }
254
+ if (matchesKey(data, "up") || matchesKey(data, "k")) {
255
+ this.selected = Math.max(0, this.selected - 1);
256
+ this.tui.requestRender();
257
+ return;
258
+ }
259
+ if (matchesKey(data, "down") || matchesKey(data, "j")) {
260
+ this.selected = Math.min(this.targets.length - 1, this.selected + 1);
261
+ this.tui.requestRender();
262
+ return;
263
+ }
264
+ if (matchesKey(data, "return")) {
265
+ this.confirming = true;
266
+ this.tui.requestRender();
267
+ }
268
+ }
269
+ invalidate() {}
270
+ render(width) {
271
+ const contentWidth = Math.max(0, Math.min(this.width, Math.floor(width)));
272
+ const lines = [this.theme.bold("Stop subagent run"), this.theme.fg("dim", "Select a current-session async run to stop, or a scheduled run to cancel."), ""];
273
+ const maxRows = 10;
274
+ const start = Math.max(0, Math.min(this.selected - maxRows + 1, Math.max(0, this.targets.length - maxRows)));
275
+ for (let index = start;index < Math.min(this.targets.length, start + maxRows); index++) {
276
+ const target = this.targets[index];
277
+ const selected = index === this.selected;
278
+ const marker = selected ? "›" : " ";
279
+ const actionLabel = target.actionLabel;
280
+ const action = target.kind === "scheduled" ? this.theme.fg("warning", actionLabel) : this.theme.fg("accent", actionLabel);
281
+ const labelWidth = Math.max(0, contentWidth - marker.length - actionLabel.length - 2);
282
+ lines.push(`${marker} ${action} ${target.label.slice(0, labelWidth)}`);
283
+ if (selected)
284
+ lines.push(this.theme.fg("dim", ` ${target.detail}`.slice(0, contentWidth)));
285
+ }
286
+ if (this.targets.length > maxRows)
287
+ lines.push(this.theme.fg("dim", `Showing ${start + 1}-${Math.min(this.targets.length, start + maxRows)} of ${this.targets.length}`));
288
+ lines.push("");
289
+ if (this.confirming) {
290
+ const target = this.targets[this.selected];
291
+ lines.push(this.theme.fg("warning", `Confirm: ${target.actionLabel} ${target.id}?`));
292
+ if (target.kind === "async")
293
+ lines.push(this.theme.fg("dim", "Stop ends this run; use interrupt for a resumable pause."));
294
+ lines.push(this.theme.fg("dim", "Enter/Y confirms · N returns · Esc cancels"));
295
+ } else {
296
+ lines.push(this.theme.fg("dim", "↑↓/jk select · Enter confirm · Esc cancel"));
297
+ }
298
+ return lines.map((line) => truncateToWidth(line, contentWidth));
299
+ }
300
+ }
222
301
  function emptyUsage() {
223
302
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
224
303
  }
@@ -233,21 +312,59 @@ function addUsage(target, source) {
233
312
  function usageHasValue(usage) {
234
313
  return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0;
235
314
  }
315
+ function nonNegativeNumber(value) {
316
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
317
+ }
318
+ function usageFromValue(value, turnsOverride) {
319
+ if (!value || typeof value !== "object" || Array.isArray(value))
320
+ return;
321
+ const record = value;
322
+ const costValue = typeof record.cost === "object" && record.cost !== null && !Array.isArray(record.cost) ? record.cost.total : record.cost;
323
+ const turnsValue = turnsOverride ?? record.turns;
324
+ const usage = {
325
+ input: nonNegativeNumber(record.input) ?? 0,
326
+ output: nonNegativeNumber(record.output) ?? 0,
327
+ cacheRead: nonNegativeNumber(record.cacheRead) ?? 0,
328
+ cacheWrite: nonNegativeNumber(record.cacheWrite) ?? 0,
329
+ cost: nonNegativeNumber(costValue) ?? 0,
330
+ turns: nonNegativeNumber(turnsValue) ?? 0
331
+ };
332
+ return Number.isSafeInteger(usage.turns) ? usage : undefined;
333
+ }
236
334
  function assistantUsageFromMessage(message) {
237
335
  if (!message || typeof message !== "object")
238
336
  return;
239
337
  const msg = message;
240
- if (msg.role !== "assistant" || !msg.usage || typeof msg.usage !== "object")
338
+ return msg.role === "assistant" ? usageFromValue(msg.usage, 1) : undefined;
339
+ }
340
+ function compactionUsageFromEntry(entry) {
341
+ if (!entry || typeof entry !== "object")
241
342
  return;
242
- const usage = msg.usage;
243
- return {
244
- input: typeof usage.input === "number" ? usage.input : 0,
245
- output: typeof usage.output === "number" ? usage.output : 0,
246
- cacheRead: typeof usage.cacheRead === "number" ? usage.cacheRead : 0,
247
- cacheWrite: typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0,
248
- cost: typeof usage.cost?.total === "number" ? usage.cost.total : 0,
249
- turns: 1
250
- };
343
+ const record = entry;
344
+ return record.type === "compaction" ? usageFromValue(record.usage, 0) : undefined;
345
+ }
346
+ const MAX_USAGE_METADATA_BYTES = 2 * 1024 * 1024;
347
+ function readUsageMetadata(metadataPath) {
348
+ let fd;
349
+ try {
350
+ fd = fs.openSync(metadataPath, "r");
351
+ const stat = fs.fstatSync(fd);
352
+ if (!stat.isFile() || stat.size > MAX_USAGE_METADATA_BYTES)
353
+ return;
354
+ const buffer = Buffer.allocUnsafe(stat.size);
355
+ let offset = 0;
356
+ while (offset < buffer.length) {
357
+ const bytesRead = fs.readSync(fd, buffer, offset, buffer.length - offset, null);
358
+ if (bytesRead <= 0)
359
+ return;
360
+ offset += bytesRead;
361
+ }
362
+ const value = JSON.parse(buffer.toString("utf-8"));
363
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
364
+ } finally {
365
+ if (fd !== undefined)
366
+ fs.closeSync(fd);
367
+ }
251
368
  }
252
369
  function isSubagentDetails(value) {
253
370
  if (!value || typeof value !== "object")
@@ -266,10 +383,33 @@ function detailsFromSessionEntry(entry) {
266
383
  if (record.type !== "message" || !record.message || typeof record.message !== "object")
267
384
  return;
268
385
  const message = record.message;
269
- if (message.role !== "toolResult" || message.toolName !== "subagent")
386
+ if (message.role !== "toolResult" || message.toolName !== "subagent" && message.toolName !== "bg_wait" && message.toolName !== "subagent_wait")
270
387
  return;
271
388
  return isSubagentDetails(message.details) ? message.details : undefined;
272
389
  }
390
+ function metadataUsage(artifactsDirs, input) {
391
+ if (!/^[A-Za-z0-9._-]+$/.test(input.runId))
392
+ return;
393
+ for (const artifactsDir of artifactsDirs) {
394
+ for (const index of [0, undefined]) {
395
+ const metadataPath = getArtifactPaths(artifactsDir, input.runId, input.agent, index).metadataPath;
396
+ try {
397
+ const metadata = readUsageMetadata(metadataPath);
398
+ if (!metadata)
399
+ continue;
400
+ if (metadata.runId !== input.runId || metadata.agent !== input.agent)
401
+ continue;
402
+ const usage = usageFromValue(metadata.usage);
403
+ if (usage && usageHasValue(usage))
404
+ return usage;
405
+ } catch (error) {
406
+ if (error.code !== "ENOENT")
407
+ console.error(`Failed to read subagent usage metadata '${metadataPath}':`, error);
408
+ }
409
+ }
410
+ }
411
+ return;
412
+ }
273
413
  function formatCostUsage(label, usage) {
274
414
  const extras = [
275
415
  usage.cacheRead ? `cache read ${formatTokens(usage.cacheRead)}` : "",
@@ -278,29 +418,98 @@ function formatCostUsage(label, usage) {
278
418
  ].filter(Boolean);
279
419
  return `${label}: ↑${formatTokens(usage.input)} ↓${formatTokens(usage.output)} $${usage.cost.toFixed(4)}${extras.length ? ` (${extras.join(", ")})` : ""}`;
280
420
  }
281
- function buildSubagentCostReport(ctx) {
421
+ function buildSubagentCostReport(ctx, state) {
282
422
  const parent = emptyUsage();
283
423
  const childTotal = emptyUsage();
284
424
  const total = emptyUsage();
285
425
  const children = [];
426
+ const seenChildren = new Set;
427
+ const workflowRunIds = new Set;
428
+ let unresolvedAsyncChildren = 0;
429
+ const addChild = (input) => {
430
+ if (!input.usage || !usageHasValue(input.usage))
431
+ return false;
432
+ const identity = input.runId ? `run:${input.runId}` : input.sessionFile ? `session:${input.sessionFile}` : undefined;
433
+ if (identity && seenChildren.has(identity))
434
+ return true;
435
+ if (identity)
436
+ seenChildren.add(identity);
437
+ const usage = { ...input.usage };
438
+ children.push({
439
+ label: `Child ${children.length + 1} (${input.agent ?? "unknown"})`,
440
+ usage,
441
+ ...input.sessionFile ? { sessionFile: input.sessionFile } : {}
442
+ });
443
+ addUsage(childTotal, usage);
444
+ return true;
445
+ };
286
446
  for (const entry of ctx.sessionManager.getBranch()) {
287
447
  const message = entry.type === "message" ? entry.message : undefined;
288
- const parentUsage = assistantUsageFromMessage(message);
448
+ const parentUsage = assistantUsageFromMessage(message) ?? compactionUsageFromEntry(entry);
289
449
  if (parentUsage)
290
450
  addUsage(parent, parentUsage);
291
451
  const details = detailsFromSessionEntry(entry);
292
452
  if (!details)
293
453
  continue;
454
+ if (details.mode === "workflow" && details.runId)
455
+ workflowRunIds.add(details.runId);
294
456
  for (const result of details.results) {
295
- if (!usageHasValue(result.usage))
296
- continue;
297
- const usage = { ...result.usage };
298
- children.push({
299
- label: `Child ${children.length + 1} (${result.agent})`,
300
- usage,
301
- ...result.sessionFile ? { sessionFile: result.sessionFile } : {}
302
- });
303
- addUsage(childTotal, usage);
457
+ const resultRunId = result.runId;
458
+ addChild({ agent: result.agent, runId: typeof resultRunId === "string" ? resultRunId : undefined, usage: usageFromValue(result.usage), sessionFile: result.sessionFile });
459
+ }
460
+ for (const completion of details.completions ?? []) {
461
+ if (completion.mode === "workflow")
462
+ workflowRunIds.add(completion.runId);
463
+ for (const result of completion.results ?? []) {
464
+ addChild({ agent: result.agent, runId: result.runId, usage: usageFromValue(result.usage), sessionFile: result.sessionFile });
465
+ }
466
+ }
467
+ }
468
+ let sessionFile = null;
469
+ try {
470
+ sessionFile = ctx.sessionManager.getSessionFile() ?? null;
471
+ } catch {}
472
+ const artifactsDirs = new Set;
473
+ const addArtifactsDir = (cwd) => {
474
+ try {
475
+ artifactsDirs.add(getArtifactsDir(sessionFile, cwd, state.artifactDirPreference));
476
+ } catch {}
477
+ };
478
+ addArtifactsDir(ctx.cwd);
479
+ addArtifactsDir(state.baseCwd);
480
+ for (const workflowRunId of workflowRunIds) {
481
+ try {
482
+ const status = readStatus(path.join(DIRS.async, workflowRunId));
483
+ addArtifactsDir(status?.cwd);
484
+ const receipt = readWorkflowReceipt(DIRS.async, workflowRunId);
485
+ const summaryChildren = new Map((receipt.workflowChildren?.children ?? []).map((child) => [child.childId, child]));
486
+ const refsByRunId = new Map;
487
+ const addRef = (runId, agent) => {
488
+ if (!runId || !agent || refsByRunId.has(runId))
489
+ return;
490
+ refsByRunId.set(runId, { runId, agent });
491
+ };
492
+ for (const [key, entry] of Object.entries(receipt.entries)) {
493
+ const summaryChild = summaryChildren.get(key);
494
+ const runIds = entry.continuation?.runIds?.length ? entry.continuation.runIds : entry.latestRunId ? [entry.latestRunId] : summaryChild?.runId ? [summaryChild.runId] : [];
495
+ for (const runId of runIds)
496
+ addRef(runId, entry.agent ?? summaryChild?.agent);
497
+ }
498
+ for (const child of receipt.workflowChildren?.children ?? []) {
499
+ if (!Object.hasOwn(receipt.entries, child.childId))
500
+ addRef(child.runId, child.agent);
501
+ }
502
+ const refs = [...refsByRunId.values()];
503
+ for (const ref of refs) {
504
+ if (seenChildren.has(`run:${ref.runId}`))
505
+ continue;
506
+ const usage = metadataUsage([...artifactsDirs], ref);
507
+ if (!usage || !addChild({ ...ref, usage }))
508
+ unresolvedAsyncChildren += 1;
509
+ }
510
+ } catch (error) {
511
+ if (error.code !== "ENOENT")
512
+ console.error(`Failed to resolve async subagent usage for '${workflowRunId}':`, error);
304
513
  }
305
514
  }
306
515
  addUsage(total, parent);
@@ -319,6 +528,8 @@ function buildSubagentCostReport(ctx) {
319
528
  lines.push(` Session: ${child.sessionFile}`);
320
529
  }
321
530
  }
531
+ if (unresolvedAsyncChildren > 0)
532
+ lines.push(`Async child usage unavailable: ${unresolvedAsyncChildren}.`);
322
533
  lines.push("────────────────────────────", formatCostUsage("Children", childTotal), formatCostUsage("Total", total));
323
534
  return lines.join(`
324
535
  `);
@@ -333,119 +544,121 @@ function getProfileWorkerModel(profile) {
333
544
  const model = profile.subagents?.agentOverrides?.worker?.model;
334
545
  return typeof model === "string" && model.trim() ? model.trim() : undefined;
335
546
  }
336
- function loadSavedOutputSchema(chain, stepAgent, outputSchema) {
337
- if (outputSchema === undefined)
338
- return;
339
- if (typeof outputSchema === "string") {
340
- const schemaPath = path.isAbsolute(outputSchema) ? outputSchema : path.join(path.dirname(chain.filePath), outputSchema);
341
- const parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
342
- assertJsonSchemaObject(parsed, `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`);
343
- return parsed;
547
+ function isStaleExtensionContextError(error) {
548
+ return error instanceof Error && (error.message.includes("This extension ctx is stale") || error.message.includes("Extension context no longer active"));
549
+ }
550
+ function slashHasUI(ctx) {
551
+ try {
552
+ return ctx.hasUI;
553
+ } catch (error) {
554
+ if (isStaleExtensionContextError(error))
555
+ return;
556
+ throw error;
344
557
  }
345
- assertJsonSchemaObject(outputSchema, `outputSchema for chain '${chain.name}' step '${stepAgent}'`);
346
- return outputSchema;
347
558
  }
348
- const mapSavedChainSteps = (chain, worktree = false) => {
349
- return chain.steps.map((step) => {
350
- if (isParallelStep(step)) {
351
- const parallel = step.parallel.map((task) => {
352
- const { outputSchema: rawOutputSchema, ...rest } = task;
353
- const outputSchema = loadSavedOutputSchema(chain, task.agent, rawOutputSchema);
354
- return { ...rest, ...outputSchema ? { outputSchema } : {} };
355
- });
356
- return { ...step, parallel, ...worktree ? { worktree: true } : {} };
357
- }
358
- if (isDynamicParallelStep(step)) {
359
- const { outputSchema: rawOutputSchema, ...parallelRest } = step.parallel;
360
- const outputSchema = loadSavedOutputSchema(chain, step.parallel.agent, rawOutputSchema);
361
- const collectSchema = loadSavedOutputSchema(chain, `${step.collect.as} collection`, step.collect.outputSchema);
362
- return {
363
- ...step,
364
- parallel: { ...parallelRest, ...outputSchema ? { outputSchema } : {} },
365
- collect: { ...step.collect, ...collectSchema ? { outputSchema: collectSchema } : {} }
366
- };
367
- }
368
- const outputSchema = loadSavedOutputSchema(chain, step.agent, step.outputSchema);
369
- return {
370
- agent: step.agent,
371
- task: step.task || undefined,
372
- ...step.phase ? { phase: step.phase } : {},
373
- ...step.label ? { label: step.label } : {},
374
- ...step.as ? { as: step.as } : {},
375
- ...outputSchema ? { outputSchema } : {},
376
- ...step.acceptance !== undefined ? { acceptance: step.acceptance } : {},
377
- output: step.output,
378
- outputMode: step.outputMode,
379
- reads: step.reads,
380
- progress: step.progress,
381
- skill: step.skill ?? step.skills,
382
- model: step.model
383
- };
384
- });
385
- };
386
- async function requestSlashRun(pi, ctx, requestId, params) {
559
+ async function requestSlashRun(dm, ctx, requestId, params, signal) {
387
560
  return new Promise((resolve, reject) => {
388
561
  let done = false;
389
562
  let started = false;
390
- const startTimeoutMs = 15000;
391
- const startTimeout = setTimeout(() => {
392
- finish(() => reject(new Error("Slash subagent bridge did not start within 15s. Ensure the extension is loaded correctly.")));
393
- }, startTimeoutMs);
563
+ let startTimeout;
394
564
  const onStarted = (data) => {
395
- if (done || !data || typeof data !== "object")
565
+ if (done || signal?.aborted || !data || typeof data !== "object")
396
566
  return;
397
567
  if (data.requestId !== requestId)
398
568
  return;
399
569
  started = true;
400
- clearTimeout(startTimeout);
401
- if (ctx.hasUI)
402
- ctx.ui.setStatus("subagent-slash", "running...");
570
+ if (startTimeout)
571
+ clearTimeout(startTimeout);
572
+ try {
573
+ if (ctx.hasUI)
574
+ ctx.ui.setStatus("subagent-slash", "running...");
575
+ } catch (error) {
576
+ if (isStaleExtensionContextError(error)) {
577
+ finish(() => reject(error));
578
+ return;
579
+ }
580
+ throw error;
581
+ }
403
582
  };
404
583
  const onResponse = (data) => {
405
- if (done || !data || typeof data !== "object")
584
+ if (done || signal?.aborted || !data || typeof data !== "object")
406
585
  return;
407
586
  const response = data;
408
587
  if (response.requestId !== requestId)
409
588
  return;
410
- clearTimeout(startTimeout);
589
+ if (startTimeout)
590
+ clearTimeout(startTimeout);
411
591
  finish(() => resolve(response));
412
592
  };
413
593
  const onUpdate = (data) => {
414
- if (done || !data || typeof data !== "object")
594
+ if (done || signal?.aborted || !data || typeof data !== "object")
415
595
  return;
416
596
  const update = data;
417
597
  if (update.requestId !== requestId)
418
598
  return;
419
599
  applySlashUpdate(requestId, update);
420
- if (!ctx.hasUI)
421
- return;
422
- const tool = update.currentTool ? ` ${update.currentTool}` : "";
423
- const count = update.toolCount ?? 0;
424
- const liveDetailKey = keyText("app.tools.expand");
425
- ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | ${liveDetailKey} live detail`);
600
+ try {
601
+ if (!ctx.hasUI)
602
+ return;
603
+ const tool = update.currentTool ? ` ${update.currentTool}` : "";
604
+ const count = update.toolCount ?? 0;
605
+ const liveDetailKey = keyText("app.tools.expand");
606
+ ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | ${liveDetailKey} live detail`);
607
+ } catch (error) {
608
+ if (isStaleExtensionContextError(error)) {
609
+ finish(() => reject(error));
610
+ return;
611
+ }
612
+ throw error;
613
+ }
426
614
  };
427
615
  const onTerminalInput = ctx.hasUI ? ctx.ui.onTerminalInput((input) => {
428
616
  if (!matchesKey(input, Key.escape))
429
617
  return;
430
- pi.events.emit(SLASH_SUBAGENT_CANCEL_EVENT, { requestId });
618
+ dm.events.emit(SLASH_SUBAGENT_CANCEL_EVENT, { requestId });
431
619
  finish(() => reject(new Error("Cancelled")));
432
620
  return { consume: true };
433
621
  }) : undefined;
434
- const unsubStarted = pi.events.on(SLASH_SUBAGENT_STARTED_EVENT, onStarted);
435
- const unsubResponse = pi.events.on(SLASH_SUBAGENT_RESPONSE_EVENT, onResponse);
436
- const unsubUpdate = pi.events.on(SLASH_SUBAGENT_UPDATE_EVENT, onUpdate);
622
+ const unsubStarted = dm.events.on(SLASH_SUBAGENT_STARTED_EVENT, onStarted);
623
+ const unsubResponse = dm.events.on(SLASH_SUBAGENT_RESPONSE_EVENT, onResponse);
624
+ const unsubUpdate = dm.events.on(SLASH_SUBAGENT_UPDATE_EVENT, onUpdate);
625
+ let onAbort;
437
626
  const finish = (next) => {
438
627
  if (done)
439
628
  return;
440
629
  done = true;
441
- clearTimeout(startTimeout);
630
+ if (startTimeout)
631
+ clearTimeout(startTimeout);
442
632
  unsubStarted();
443
633
  unsubResponse();
444
634
  unsubUpdate();
445
- onTerminalInput?.();
635
+ try {
636
+ onTerminalInput?.();
637
+ } catch (error) {
638
+ if (!isStaleExtensionContextError(error))
639
+ throw error;
640
+ }
641
+ signal?.removeEventListener("abort", onAbort);
446
642
  next();
447
643
  };
448
- pi.events.emit(SLASH_SUBAGENT_REQUEST_EVENT, { requestId, params, ctx });
644
+ onAbort = () => finish(() => reject(new Error("Slash subagent request canceled during session replacement or reload.")));
645
+ startTimeout = setTimeout(() => {
646
+ finish(() => reject(new Error("Slash subagent bridge did not start within 15s. Ensure the extension is loaded correctly.")));
647
+ }, 15000);
648
+ if (signal?.aborted) {
649
+ onAbort();
650
+ return;
651
+ }
652
+ signal?.addEventListener("abort", onAbort, { once: true });
653
+ try {
654
+ dm.events.emit(SLASH_SUBAGENT_REQUEST_EVENT, { requestId, params, ctx });
655
+ } catch (error) {
656
+ if (isStaleExtensionContextError(error)) {
657
+ finish(() => reject(error));
658
+ return;
659
+ }
660
+ throw error;
661
+ }
449
662
  if (!started && done)
450
663
  return;
451
664
  if (!started) {
@@ -500,466 +713,129 @@ function persistSlashSessionSnapshot(ctx) {
500
713
  console.error("Failed to persist slash session snapshot for export:", error);
501
714
  }
502
715
  }
503
- async function runSlashSubagent(pi, ctx, params) {
504
- if (ctx.hasUI)
505
- ctx.ui.setToolsExpanded(false);
716
+ async function runSlashSubagent(dm, ctx, params, signal) {
717
+ if (signal?.aborted)
718
+ return;
719
+ const initialHasUI = slashHasUI(ctx);
720
+ if (initialHasUI === undefined)
721
+ return;
722
+ try {
723
+ if (initialHasUI)
724
+ ctx.ui.setToolsExpanded(false);
725
+ } catch (error) {
726
+ if (isStaleExtensionContextError(error))
727
+ return;
728
+ throw error;
729
+ }
506
730
  const requestId = randomUUID();
507
731
  const initialDetails = buildSlashInitialResult(requestId, params);
508
732
  const initialText = extractSlashMessageText(initialDetails.result.content) || "Running subagent...";
509
- pi.sendMessage({
510
- customType: SLASH_RESULT_TYPE,
511
- content: initialText,
512
- display: true,
513
- details: initialDetails
514
- });
733
+ try {
734
+ dm.sendMessage({
735
+ customType: SLASH_RESULT_TYPE,
736
+ content: initialText,
737
+ display: true,
738
+ details: initialDetails
739
+ });
740
+ } catch (error) {
741
+ if (isStaleExtensionContextError(error))
742
+ return;
743
+ throw error;
744
+ }
515
745
  persistSlashSessionSnapshot(ctx);
516
746
  try {
517
- const response = await requestSlashRun(pi, ctx, requestId, params);
747
+ const response = await requestSlashRun(dm, ctx, requestId, params, signal);
748
+ if (signal?.aborted)
749
+ return;
750
+ const hasUI = slashHasUI(ctx);
751
+ if (hasUI === undefined)
752
+ return;
518
753
  const finalDetails = finalizeSlashResult(response);
519
- pi.sendMessage({
754
+ dm.sendMessage({
520
755
  customType: SLASH_RESULT_TYPE,
521
756
  content: buildSlashExportText(response),
522
- display: !ctx.hasUI,
757
+ display: !hasUI,
523
758
  details: finalDetails
524
759
  });
525
760
  persistSlashSessionSnapshot(ctx);
526
- if (ctx.hasUI) {
761
+ if (hasUI) {
527
762
  ctx.ui.setStatus("subagent-slash", undefined);
528
763
  }
529
- if (response.isError && ctx.hasUI) {
764
+ if (response.isError && hasUI) {
530
765
  ctx.ui.notify(response.errorText || "Subagent failed", "error");
531
766
  }
532
767
  } catch (error) {
768
+ if (signal?.aborted || isStaleExtensionContextError(error))
769
+ return;
533
770
  const message = error instanceof Error ? error.message : String(error);
771
+ const hasUI = slashHasUI(ctx);
772
+ if (hasUI === undefined)
773
+ return;
534
774
  const failedDetails = failSlashResult(requestId, params, message);
535
- pi.sendMessage({
775
+ dm.sendMessage({
536
776
  customType: SLASH_RESULT_TYPE,
537
777
  content: `## Subagent result
538
778
 
539
779
  ${message}`,
540
- display: !ctx.hasUI,
780
+ display: !hasUI,
541
781
  details: failedDetails
542
782
  });
543
783
  persistSlashSessionSnapshot(ctx);
544
- if (ctx.hasUI) {
784
+ if (hasUI) {
545
785
  ctx.ui.setStatus("subagent-slash", undefined);
546
786
  }
547
787
  if (message === "Cancelled") {
548
- if (ctx.hasUI)
788
+ if (hasUI)
549
789
  ctx.ui.notify("Cancelled", "warning");
550
790
  return;
551
791
  }
552
- if (ctx.hasUI)
792
+ if (hasUI)
553
793
  ctx.ui.notify(message, "error");
554
794
  }
555
795
  }
556
- export const PARALLEL_GROUP_USAGE = 'Usage: /chain agent "task" -> (agent2 "task" | agent3 "task") -> agent4';
557
-
558
- export class SlashParseError extends Error {
559
- }
560
- function findUnmatchedCloseParen(input) {
561
- let depth = 0, inSingle = false, inDouble = false;
562
- for (let i = 0;i < input.length; i++) {
563
- const ch = input[i];
564
- if (inSingle) {
565
- if (ch === "'")
566
- inSingle = false;
567
- continue;
568
- }
569
- if (inDouble) {
570
- if (ch === '"')
571
- inDouble = false;
572
- continue;
573
- }
574
- if (ch === "'") {
575
- inSingle = true;
576
- continue;
577
- }
578
- if (ch === '"') {
579
- inDouble = true;
580
- continue;
581
- }
582
- if (ch === "(")
583
- depth++;
584
- else if (ch === ")") {
585
- depth--;
586
- if (depth < 0)
587
- return true;
588
- }
589
- }
590
- return depth !== 0;
591
- }
592
- function splitOnArrow(input) {
593
- const segments = [];
594
- let depth = 0, inSingle = false, inDouble = false, start = 0;
595
- for (let i = 0;i < input.length; i++) {
596
- const ch = input[i];
597
- if (inSingle) {
598
- if (ch === "'")
599
- inSingle = false;
600
- continue;
601
- }
602
- if (inDouble) {
603
- if (ch === '"')
604
- inDouble = false;
605
- continue;
606
- }
607
- if (ch === "'") {
608
- inSingle = true;
609
- continue;
610
- }
611
- if (ch === '"') {
612
- inDouble = true;
613
- continue;
614
- }
615
- if (ch === "(")
616
- depth++;
617
- else if (ch === ")")
618
- depth--;
619
- else if (depth === 0 && ch === "-" && input[i + 1] === ">" && input[i + 2] === " ") {
620
- segments.push(input.slice(start, i));
621
- i += 2;
622
- start = i + 1;
623
- }
624
- }
625
- segments.push(input.slice(start));
626
- return segments;
627
- }
628
- function splitGroupTasks(inner) {
629
- const parts = [];
630
- let depth = 0, inSingle = false, inDouble = false, start = 0;
631
- for (let i = 0;i < inner.length; i++) {
632
- const ch = inner[i];
633
- if (inSingle) {
634
- if (ch === "'")
635
- inSingle = false;
636
- continue;
637
- }
638
- if (inDouble) {
639
- if (ch === '"')
640
- inDouble = false;
641
- continue;
642
- }
643
- if (ch === "'") {
644
- inSingle = true;
645
- continue;
646
- }
647
- if (ch === '"') {
648
- inDouble = true;
649
- continue;
650
- }
651
- if (ch === "(")
652
- depth++;
653
- else if (ch === ")")
654
- depth--;
655
- else if (ch === "|" && depth === 0) {
656
- parts.push(inner.slice(start, i));
657
- start = i + 1;
658
- }
659
- }
660
- parts.push(inner.slice(start));
661
- return parts;
662
- }
663
- export function parseSingleTaskToken(token) {
664
- let agentPart;
665
- let task;
666
- const qMatch = token.match(/^(\S+(?:\[[^\]]*\])?)\s+(?:"([^"]*)"|'([^']*)')$/);
667
- if (qMatch) {
668
- agentPart = qMatch[1];
669
- task = (qMatch[2] ?? qMatch[3]) || undefined;
670
- } else {
671
- const dashIdx = token.indexOf(" -- ");
672
- if (dashIdx !== -1) {
673
- agentPart = token.slice(0, dashIdx).trim();
674
- task = token.slice(dashIdx + 4).trim() || undefined;
675
- } else {
676
- agentPart = token;
677
- }
678
- }
679
- return { kind: "step", ...parseAgentToken(agentPart), task };
680
- }
681
- const parseGroupConfig = (raw) => {
682
- const config = {};
683
- for (const part of raw.split(",")) {
684
- const trimmed = part.trim();
685
- if (!trimmed)
686
- continue;
687
- const eq = trimmed.indexOf("=");
688
- const key = eq === -1 ? trimmed : trimmed.slice(0, eq).trim();
689
- const val = eq === -1 ? "" : trimmed.slice(eq + 1).trim();
690
- switch (key) {
691
- case "concurrency": {
692
- const n = Number(val);
693
- if (Number.isInteger(n) && n > 0)
694
- config.concurrency = n;
695
- break;
696
- }
697
- case "failFast":
698
- config.failFast = eq === -1 ? true : val !== "false";
699
- break;
700
- case "worktree":
701
- config.worktree = eq === -1 ? true : val !== "false";
702
- break;
703
- }
704
- }
705
- return config;
706
- };
707
- const splitGroupBody = (trimmed) => {
708
- let depth = 0, inSingle = false, inDouble = false, closeIdx = -1;
709
- for (let i = 0;i < trimmed.length; i++) {
710
- const ch = trimmed[i];
711
- if (inSingle) {
712
- if (ch === "'")
713
- inSingle = false;
714
- continue;
715
- }
716
- if (inDouble) {
717
- if (ch === '"')
718
- inDouble = false;
719
- continue;
720
- }
721
- if (ch === "'") {
722
- inSingle = true;
723
- continue;
724
- }
725
- if (ch === '"') {
726
- inDouble = true;
727
- continue;
728
- }
729
- if (ch === "(")
730
- depth++;
731
- else if (ch === ")") {
732
- depth--;
733
- if (depth === 0) {
734
- closeIdx = i;
735
- break;
736
- }
737
- }
738
- }
739
- if (closeIdx === -1)
740
- throw new SlashParseError(`Unmatched parentheses in group: '${trimmed}'`);
741
- const inner = trimmed.slice(1, closeIdx);
742
- const suffix = trimmed.slice(closeIdx + 1).trim();
743
- if (!suffix)
744
- return { inner, config: {} };
745
- if (!suffix.startsWith("[") || !suffix.endsWith("]")) {
746
- throw new SlashParseError(`Group options must be wrapped in [...]: '${suffix}'`);
747
- }
748
- return { inner, config: parseGroupConfig(suffix.slice(1, -1)) };
749
- };
750
- export function parseGroupSegment(segment) {
751
- const trimmed = segment.trim();
752
- if (!trimmed.startsWith("(")) {
753
- throw new SlashParseError(`Parallel group must be wrapped in parentheses: '${trimmed}'`);
754
- }
755
- const { inner, config } = splitGroupBody(trimmed);
756
- const rawParts = splitGroupTasks(inner).map((p) => p.trim()).filter((p) => p.length > 0);
757
- if (rawParts.length < 2) {
758
- throw new SlashParseError("Parallel group must contain at least two tasks separated by ' | '");
759
- }
760
- return { kind: "group", tasks: rawParts.map((part) => parseSingleTaskToken(part)), config };
761
- }
762
- export function hasGroupSyntax(input) {
763
- return splitOnArrow(input).some((seg) => seg.trim().startsWith("("));
796
+ function slashRunWorkflowScript(key, child) {
797
+ return `return runs.run(${JSON.stringify(key)}, ${JSON.stringify(child)})`;
764
798
  }
765
- export function parseChainExpression(input) {
766
- const trimmed = input.trim();
767
- if (!trimmed.includes(" -> ")) {
768
- throw new SlashParseError('Parallel groups in /chain require " -> " between steps');
769
- }
770
- if (findUnmatchedCloseParen(trimmed)) {
771
- throw new SlashParseError("Unmatched parentheses in /chain expression");
772
- }
773
- const steps = [];
774
- for (const seg of splitOnArrow(trimmed)) {
775
- const t = seg.trim();
776
- if (!t)
777
- continue;
778
- if (t.startsWith("(")) {
779
- steps.push(parseGroupSegment(t));
780
- continue;
781
- }
782
- if (findUnmatchedCloseParen(t)) {
783
- throw new SlashParseError(`Unmatched parentheses in chain segment: '${t}'`);
784
- }
785
- steps.push(parseSingleTaskToken(t));
786
- }
787
- if (steps.length === 0) {
788
- throw new SlashParseError("/chain expression must include at least one step");
789
- }
790
- return { steps };
791
- }
792
- const parseAgentArgs = (state, args, command, ctx) => {
793
- const input = args.trim();
794
- const usage = `Usage: /${command} agent1 "task1" -> agent2 "task2"`;
795
- let steps;
796
- let sharedTask;
797
- let perStep = false;
798
- if (input.includes(" -> ")) {
799
- perStep = true;
800
- const segments = input.split(" -> ");
801
- steps = [];
802
- for (const seg of segments) {
803
- const trimmed = seg.trim();
804
- if (!trimmed)
805
- continue;
806
- steps.push(parseSingleTaskToken(trimmed));
807
- }
808
- sharedTask = steps.find((s) => s.task)?.task ?? "";
809
- } else {
810
- const delimiterIndex = input.indexOf(" -- ");
811
- if (delimiterIndex === -1) {
812
- ctx.ui.notify(usage, "error");
813
- return null;
814
- }
815
- const agentsPart = input.slice(0, delimiterIndex).trim();
816
- sharedTask = input.slice(delimiterIndex + 4).trim();
817
- if (!agentsPart || !sharedTask) {
818
- ctx.ui.notify(usage, "error");
819
- return null;
799
+ export function registerSlashCommands(dm, state, options = {}) {
800
+ let fleetOpen = false;
801
+ let disposed = false;
802
+ const pendingRequests = new Set;
803
+ const runCommand = (ctx, params) => {
804
+ if (disposed)
805
+ return Promise.resolve();
806
+ const controller = new AbortController;
807
+ pendingRequests.add(controller);
808
+ return runSlashSubagent(dm, ctx, params, controller.signal).finally(() => pendingRequests.delete(controller));
809
+ };
810
+ const launchCommand = (ctx, params) => {
811
+ runCommand(ctx, params);
812
+ };
813
+ const showFleet = async (ctx) => {
814
+ state.lastUiContext = ctx;
815
+ if (!ctx.hasUI) {
816
+ await runCommand(ctx, { action: "status", view: "fleet" });
817
+ return;
820
818
  }
821
- steps = agentsPart.split(/\s+/).filter(Boolean).map((t) => parseSingleTaskToken(t));
822
- }
823
- if (steps.length === 0) {
824
- ctx.ui.notify(usage, "error");
825
- return null;
826
- }
827
- if (!state.baseCwd) {
828
- ctx.ui.notify("Subagent session cwd is not initialized yet", "error");
829
- return null;
830
- }
831
- const agents = discoverAgents(state.baseCwd, "both").agents;
832
- for (const step of steps) {
833
- if (!agents.find((a) => a.name === step.name)) {
834
- ctx.ui.notify(`Unknown agent: ${step.name}`, "error");
835
- return null;
819
+ if (fleetOpen) {
820
+ ctx.ui.notify("Subagent fleet inspector is already open.", "info");
821
+ return;
836
822
  }
837
- }
838
- if (command === "chain" && !steps[0]?.task && (perStep || !sharedTask)) {
839
- ctx.ui.notify(`First step must have a task: /chain agent "task" -> agent2`, "error");
840
- return null;
841
- }
842
- if (command === "parallel" && !steps.some((s) => s.task) && !sharedTask) {
843
- ctx.ui.notify("At least one step must have a task", "error");
844
- return null;
845
- }
846
- return { steps, task: sharedTask };
847
- };
848
- const INLINE_ACCEPTANCE_LEVELS = new Set(["auto", "attested", "checked"]);
849
- function validateInlineAcceptanceInput(value, agent) {
850
- const errors = validateAcceptanceInput(value, `acceptance for step '${agent}'`);
851
- if (errors.length > 0)
852
- throw new SlashParseError(errors[0]);
853
- if (!INLINE_ACCEPTANCE_LEVELS.has(value)) {
854
- throw new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none, verified, or reviewed acceptance contracts.`);
855
- }
856
- }
857
- function loadInlineOutputSchema(baseCwd, agent, value) {
858
- const schemaPath = path.isAbsolute(value) ? value : path.join(baseCwd, value);
859
- const label = `outputSchema for step '${agent}' (${schemaPath})`;
860
- let parsed;
861
- try {
862
- parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
863
- } catch (error) {
864
- throw new SlashParseError(`Cannot read ${label}: ${error instanceof Error ? error.message : String(error)}`);
865
- }
866
- assertJsonSchemaObject(parsed, label);
867
- return parsed;
868
- }
869
- const mapParsedTaskToStepObject = (step, fallbackTask, isFirst, opts) => {
870
- const { name, config, task: stepTask } = step;
871
- if (config.acceptance !== undefined)
872
- validateInlineAcceptanceInput(config.acceptance, name);
873
- return {
874
- agent: name,
875
- ...stepTask ? { task: stepTask } : isFirst && fallbackTask ? { task: fallbackTask } : {},
876
- ...config.output !== undefined ? { output: config.output } : {},
877
- ...config.outputMode !== undefined ? { outputMode: config.outputMode } : {},
878
- ...config.reads !== undefined ? { reads: config.reads } : {},
879
- ...config.model ? { model: config.model } : {},
880
- ...config.skill !== undefined ? { skill: config.skill } : {},
881
- ...config.progress !== undefined ? { progress: config.progress } : {},
882
- ...config.as ? { as: config.as } : {},
883
- ...config.label ? { label: config.label } : {},
884
- ...config.phase ? { phase: config.phase } : {},
885
- ...config.cwd ? { cwd: config.cwd } : {},
886
- ...opts.inGroup && config.count !== undefined ? { count: config.count } : {},
887
- ...config.outputSchema ? { outputSchema: loadInlineOutputSchema(opts.baseCwd, name, config.outputSchema) } : {},
888
- ...config.acceptance ? { acceptance: config.acceptance } : {}
889
- };
890
- };
891
- export function buildChainExpressionSteps(state, input, ctx) {
892
- const notify = (message) => ctx.ui.notify(message, "error");
893
- if (!hasGroupSyntax(input)) {
894
- const parsed = parseAgentArgs(state, input, "chain", ctx);
895
- if (!parsed)
896
- return null;
897
- const baseCwd = state.baseCwd;
823
+ fleetOpen = true;
898
824
  try {
899
- const chain = parsed.steps.map((step, i) => mapParsedTaskToStepObject(step, parsed.task || undefined, i === 0, { baseCwd, inGroup: false }));
900
- return { chain, task: parsed.task };
901
- } catch (error) {
902
- notify(error instanceof Error ? error.message : String(error));
903
- return null;
825
+ await openSubagentFleet(ctx, state, { asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: options.fleetKeybindings });
826
+ } finally {
827
+ fleetOpen = false;
904
828
  }
905
- }
906
- let expression;
907
- try {
908
- expression = parseChainExpression(input);
909
- } catch (error) {
910
- notify(error instanceof Error ? error.message : String(error));
911
- return null;
912
- }
913
- if (!state.baseCwd) {
914
- notify("Subagent session cwd is not initialized yet");
915
- return null;
916
- }
917
- const agents = discoverAgents(state.baseCwd, "both").agents;
918
- const stepAgentNames = expression.steps.flatMap((step) => step.kind === "group" ? step.tasks.map((t) => t.name) : [step.name]);
919
- for (const name of stepAgentNames) {
920
- if (!agents.find((a) => a.name === name)) {
921
- notify(`Unknown agent: ${name}`);
922
- return null;
923
- }
924
- }
925
- for (const step of expression.steps) {
926
- if (step.kind === "group" && step.tasks.some((t) => !t.task)) {
927
- notify('Each task in a parallel group needs a task: (agent "a" | agent "b")');
928
- return null;
829
+ };
830
+ dm.registerCommand("subagents", {
831
+ description: "Administer subagents: inspect metadata and update models, thinking, or prompts",
832
+ handler: async (args, ctx) => {
833
+ await openSubagentsAdmin(dm, ctx, args);
929
834
  }
930
- }
931
- const firstStep = expression.steps[0];
932
- const firstHasTask = firstStep.kind === "group" ? firstStep.tasks.some((t) => Boolean(t.task)) : Boolean(firstStep.task);
933
- if (!firstHasTask) {
934
- notify('First step must have a task: /chain agent "task" -> agent2');
935
- return null;
936
- }
937
- const sharedTask = firstStep.kind === "group" ? firstStep.tasks.find((t) => t.task)?.task ?? "" : firstStep.task ?? "";
938
- const baseCwd = state.baseCwd;
939
- let chain;
940
- try {
941
- chain = expression.steps.map((step) => {
942
- if (step.kind === "group") {
943
- const parallel = step.tasks.map((t) => mapParsedTaskToStepObject(t, undefined, false, { baseCwd, inGroup: true }));
944
- return {
945
- parallel,
946
- ...step.config.concurrency !== undefined ? { concurrency: step.config.concurrency } : {},
947
- ...step.config.failFast !== undefined ? { failFast: step.config.failFast } : {},
948
- ...step.config.worktree !== undefined ? { worktree: step.config.worktree } : {}
949
- };
950
- }
951
- return mapParsedTaskToStepObject(step, sharedTask || undefined, false, { baseCwd, inGroup: false });
952
- });
953
- } catch (error) {
954
- notify(error instanceof Error ? error.message : String(error));
955
- return null;
956
- }
957
- return { chain, task: sharedTask };
958
- }
959
- export function registerSlashCommands(pi, state) {
960
- pi.registerCommand("run", {
961
- description: "Run a subagent directly: /run agent[output=file] [task] [--bg] [--fork]",
962
- getArgumentCompletions: makeAgentCompletions(state, false),
835
+ });
836
+ dm.registerCommand("run", {
837
+ description: "Run one subagent through workflowScript: /run agent[output=file] [task] [--bg] [--fork]",
838
+ getArgumentCompletions: makeAgentCompletions(dm, state),
963
839
  handler: async (args, ctx) => {
964
840
  const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
965
841
  const input = cleanedArgs.trim();
@@ -974,138 +850,242 @@ export function registerSlashCommands(pi, state) {
974
850
  ctx.ui.notify("Subagent session cwd is not initialized yet", "error");
975
851
  return;
976
852
  }
977
- const agents = discoverAgents(state.baseCwd, "both").agents;
978
- if (!agents.find((a) => a.name === agentName)) {
979
- ctx.ui.notify(`Unknown agent: ${agentName}`, "error");
853
+ const discovered = discoverSlashAgents(dm, state.baseCwd, "both");
854
+ const resolvedAgent = resolveAgentName(agentName, discovered.agents);
855
+ const candidates = resolvedAgent.error ? discovered.agents.filter((agent) => resolveAgentName(agentName, [agent]).agent) : resolvedAgent.agent;
856
+ const diagnostic = findBlockingAgentDiagnostic(agentName, candidates, discovered.agentDiagnostics);
857
+ if (diagnostic || resolvedAgent.error || !resolvedAgent.agent) {
858
+ ctx.ui.notify(diagnostic ? `Agent '${agentName}' has invalid configuration: ${diagnostic.error}` : resolvedAgent.error ?? formatUnknownAgentError(agentName, discovered.unknownAgentDiagnosticContext), "error");
980
859
  return;
981
860
  }
982
861
  let finalTask = task;
983
862
  if (inline.reads && Array.isArray(inline.reads) && inline.reads.length > 0) {
984
- finalTask = `[Read from: ${inline.reads.join(", ")}]
863
+ const existingReads = inline.reads.filter((read) => resolveExistingReadPaths([read], state.baseCwd).length > 0);
864
+ if (existingReads.length > 0)
865
+ finalTask = `[Read from: ${existingReads.join(", ")}]
985
866
 
986
867
  ${finalTask}`;
987
868
  }
988
- const params = { agent: agentName, task: finalTask, clarify: false, agentScope: "both" };
869
+ const child = { agent: agentName, task: finalTask, agentScope: "both" };
989
870
  if (inline.output !== undefined)
990
- params.output = inline.output;
871
+ child.output = inline.output;
991
872
  if (inline.outputMode !== undefined)
992
- params.outputMode = inline.outputMode;
873
+ child.outputMode = inline.outputMode;
993
874
  if (inline.skill !== undefined)
994
- params.skill = inline.skill;
875
+ child.skill = inline.skill;
995
876
  if (inline.model)
996
- params.model = inline.model;
997
- if (bg)
998
- params.async = true;
877
+ child.model = inline.model;
999
878
  if (fork)
1000
- params.context = "fork";
1001
- await runSlashSubagent(pi, ctx, params);
879
+ child.context = "fork";
880
+ launchCommand(ctx, { workflowScript: slashRunWorkflowScript("run", child), async: bg ? true : false });
1002
881
  }
1003
882
  });
1004
- pi.registerCommand("chain", {
1005
- description: 'Run agents in sequence: /chain scout "task" -> planner [--bg] [--fork]',
1006
- getArgumentCompletions: makeAgentCompletions(state, true),
1007
- handler: async (args, ctx) => {
1008
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1009
- const built = buildChainExpressionSteps(state, cleanedArgs, ctx);
1010
- if (!built)
1011
- return;
1012
- const params = { chain: built.chain, task: built.task, clarify: false, agentScope: "both" };
1013
- if (bg)
1014
- params.async = true;
1015
- if (fork)
1016
- params.context = "fork";
1017
- await runSlashSubagent(pi, ctx, params);
883
+ dm.registerCommand("subagent-cost", {
884
+ description: "Show parent and subagent child usage cost for this session",
885
+ handler: async (_args, ctx) => {
886
+ sendSlashText(dm, buildSubagentCostReport(ctx, state));
1018
887
  }
1019
888
  });
1020
- pi.registerCommand("run-chain", {
1021
- description: "Run a saved chain: /run-chain chainName -- task [--bg] [--fork]",
1022
- getArgumentCompletions: makeChainCompletions(state),
889
+ dm.registerCommand("subagents-doctor", {
890
+ description: "Show subagent diagnostics",
891
+ handler: async (_args, ctx) => {
892
+ await runCommand(ctx, { action: "doctor" });
893
+ }
894
+ });
895
+ dm.registerCommand("subagents-inspect-rpc", {
896
+ description: "Host integration bridge: answer an async child inspection request with a correlated widget payload (no model turn)",
1023
897
  handler: async (args, ctx) => {
1024
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1025
- const delimiterIndex = cleanedArgs.indexOf(" -- ");
1026
- const usage = "Usage: /run-chain <chainName> -- <task> [--bg] [--fork]";
1027
- if (delimiterIndex === -1) {
1028
- ctx.ui.notify(usage, "error");
1029
- return;
1030
- }
1031
- const chainName = cleanedArgs.slice(0, delimiterIndex).trim();
1032
- const task = cleanedArgs.slice(delimiterIndex + 4).trim();
1033
- if (!chainName || !task) {
1034
- ctx.ui.notify(usage, "error");
898
+ if (ctx.mode === "tui") {
899
+ ctx.ui.notify('Inspection replies are emitted only on RPC surfaces. Use /subagents or subagent({ action: "status", view: "transcript" }) interactively.', "info");
1035
900
  return;
1036
901
  }
1037
- if (!state.baseCwd) {
1038
- ctx.ui.notify("Subagent session cwd is not initialized yet", "error");
902
+ if (!ctx.hasUI)
1039
903
  return;
1040
- }
1041
- const chain = discoverSavedChains(state.baseCwd).find((candidate) => candidate.name === chainName);
1042
- if (!chain) {
1043
- ctx.ui.notify(`Unknown chain: ${chainName}`, "error");
904
+ const reply = handleInspectRpcArgs(args, { state });
905
+ ctx.ui.setWidget(INSPECT_WIDGET_KEY, encodeInspectReply(reply));
906
+ ctx.ui.setWidget(INSPECT_WIDGET_KEY, undefined);
907
+ }
908
+ });
909
+ dm.registerCommand("subagents-guide", {
910
+ description: "Show a packaged subagents guide topic",
911
+ getArgumentCompletions: (prefix) => prefix.includes(" ") ? null : SUBAGENT_GUIDE_TOPICS.filter((topic) => topic.startsWith(prefix)).map((topic) => ({ value: topic, label: topic })),
912
+ handler: async (args, ctx) => {
913
+ const topic = args.trim();
914
+ if (topic.includes(" ")) {
915
+ ctx.ui.notify("Usage: /subagents-guide [topic]", "error");
1044
916
  return;
1045
917
  }
1046
- const params = { chain: mapSavedChainSteps(chain), task, clarify: false, agentScope: "both" };
1047
- if (bg)
1048
- params.async = true;
1049
- if (fork)
1050
- params.context = "fork";
1051
- await runSlashSubagent(pi, ctx, params);
918
+ await runCommand(ctx, { action: "guide", ...topic ? { topic } : {} });
1052
919
  }
1053
920
  });
1054
- pi.registerCommand("parallel", {
1055
- description: 'Run agents in parallel: /parallel scout "task1" -> reviewer "task2" [--bg] [--fork]',
1056
- getArgumentCompletions: makeAgentCompletions(state, true),
921
+ dm.registerCommand("subagents-refine", {
922
+ description: "Generate a bounded project-local refinement overlay for one subagent",
923
+ getArgumentCompletions: makeAgentCompletions(dm, state),
1057
924
  handler: async (args, ctx) => {
1058
- const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
1059
- const parsed = parseAgentArgs(state, cleanedArgs, "parallel", ctx);
1060
- if (!parsed)
925
+ const parts = args.trim().split(/\s+/).filter(Boolean);
926
+ if (parts.length !== 1) {
927
+ ctx.ui.notify("Usage: /subagents-refine <agent>", "error");
1061
928
  return;
1062
- const tasks = parsed.steps.map(({ name, config, task: stepTask }) => ({
1063
- agent: name,
1064
- task: stepTask ?? parsed.task,
1065
- ...config.output !== undefined ? { output: config.output } : {},
1066
- ...config.outputMode !== undefined ? { outputMode: config.outputMode } : {},
1067
- ...config.reads !== undefined ? { reads: config.reads } : {},
1068
- ...config.model ? { model: config.model } : {},
1069
- ...config.skill !== undefined ? { skill: config.skill } : {},
1070
- ...config.progress !== undefined ? { progress: config.progress } : {}
1071
- }));
1072
- const params = { tasks, clarify: false, agentScope: "both" };
1073
- if (bg)
1074
- params.async = true;
1075
- if (fork)
1076
- params.context = "fork";
1077
- await runSlashSubagent(pi, ctx, params);
929
+ }
930
+ await runCommand(ctx, { action: "refine", agent: parts[0] });
1078
931
  }
1079
932
  });
1080
- pi.registerCommand("subagent-cost", {
1081
- description: "Show parent and subagent child usage cost for this session",
1082
- handler: async (_args, ctx) => {
1083
- sendSlashText(pi, buildSubagentCostReport(ctx));
933
+ dm.registerCommand("subagents-fleet", {
934
+ description: "Open the live subagent fleet inspector",
935
+ handler: async (_args, ctx) => showFleet(ctx)
936
+ });
937
+ dm.registerShortcut(FLEET_OPEN_SHORTCUT, {
938
+ description: "Open subagent fleet inspector",
939
+ handler: async (ctx) => showFleet(ctx)
940
+ });
941
+ const detachForegroundRun = (args, ctx) => {
942
+ const id = args.trim();
943
+ let control;
944
+ try {
945
+ control = selectForegroundDetachControl(state, id);
946
+ } catch (error) {
947
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
948
+ return;
949
+ }
950
+ if (!control) {
951
+ ctx.ui.notify(id ? `No active foreground run found for '${id}'.` : "No active foreground single-subagent run to detach.", "info");
952
+ return;
953
+ }
954
+ if (control.mode !== "single") {
955
+ ctx.ui.notify("/subagents-detach currently supports single-subagent runs only.", "error");
956
+ return;
957
+ }
958
+ if (!control.detach?.()) {
959
+ ctx.ui.notify(`Foreground run ${control.runId} is not currently detachable.`, "info");
960
+ return;
1084
961
  }
962
+ sendSlashText(dm, `Detached foreground run ${control.runId} without terminating its child. Use subagent({ action: "status", id: ${JSON.stringify(control.runId)} }) or bg_wait({ id: ${JSON.stringify(control.runId)} }) to recover the eventual result. This does not daemonize the process or guarantee survival across DM reload/restart.`);
963
+ };
964
+ dm.registerCommand("subagents-detach", {
965
+ description: "Detach the active foreground single-subagent run without terminating it",
966
+ handler: async (args, ctx) => detachForegroundRun(args, ctx)
1085
967
  });
1086
- pi.registerCommand("subagents-doctor", {
1087
- description: "Show subagent diagnostics",
1088
- handler: async (_args, ctx) => {
1089
- await runSlashSubagent(pi, ctx, { action: "doctor" });
968
+ if (options.foregroundDetachShortcut) {
969
+ dm.registerShortcut(options.foregroundDetachShortcut, {
970
+ description: "Detach the active foreground subagent and keep it running in the background",
971
+ handler: async (ctx) => detachForegroundRun("", ctx)
972
+ });
973
+ }
974
+ dm.registerCommand("subagents-stop", {
975
+ description: "Stop a current-session async subagent run, or one child of it with /subagents-stop <run-id> <child-id>",
976
+ handler: async (args, ctx) => {
977
+ const [id, childId, ...extra] = args.trim().split(/\s+/).filter(Boolean);
978
+ if (extra.length > 0) {
979
+ sendSlashText(dm, "Usage: /subagents-stop [run-id] [child-id]");
980
+ return;
981
+ }
982
+ if (id) {
983
+ await runCommand(ctx, childId ? { action: "stop", id, childId } : { action: "stop", id });
984
+ return;
985
+ }
986
+ if (process.env[SUBAGENT_FANOUT_CHILD_ENV] === "1") {
987
+ sendSlashText(dm, 'Selector unavailable in child-safe fanout mode. Pass an explicit current-session top-level async run id, for example `/subagents-stop <run-id>` or `subagent({ action: "stop", id: "<run-id>" })`.');
988
+ return;
989
+ }
990
+ let targets;
991
+ try {
992
+ targets = discoverStopTargets(ctx, state);
993
+ } catch (error) {
994
+ const message = error instanceof Error ? error.message : String(error);
995
+ ctx.ui.notify(message, "error");
996
+ return;
997
+ }
998
+ if (!ctx.hasUI) {
999
+ sendSlashText(dm, stopFallbackText(targets));
1000
+ return;
1001
+ }
1002
+ if (targets.length === 0) {
1003
+ ctx.ui.notify("No active current-session async runs or scheduled subagent runs to stop.", "info");
1004
+ return;
1005
+ }
1006
+ const result = await ctx.ui.custom((tui, theme, _kb, done) => new SubagentsStopSelector(tui, theme, targets, done), { overlay: true, overlayOptions: { anchor: "center", width: 88, maxHeight: "80%" } });
1007
+ if (!result?.confirmed || !result.target)
1008
+ return;
1009
+ if (result.target.kind === "scheduled") {
1010
+ await runCommand(ctx, { action: "schedule.pause", id: result.target.id });
1011
+ return;
1012
+ }
1013
+ await runCommand(ctx, { action: "stop", id: result.target.id });
1090
1014
  }
1091
1015
  });
1092
- pi.registerCommand("subagents-fleet", {
1093
- description: "Show active subagent fleet status and transcript commands",
1094
- handler: async (_args, ctx) => {
1095
- await runSlashSubagent(pi, ctx, { action: "status", view: "fleet" });
1016
+ dm.registerCommand("subagents-steer", {
1017
+ description: "Steer a live async subagent run with a message, or one child of it with --child <child-id>",
1018
+ handler: async (args, ctx) => {
1019
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
1020
+ const usage = "Usage: /subagents-steer <run-id> [--child <child-id>] <message>";
1021
+ const [id, ...rest] = tokens;
1022
+ if (!id || id.startsWith("--")) {
1023
+ sendSlashText(dm, usage);
1024
+ return;
1025
+ }
1026
+ let childId;
1027
+ let messageStart = 0;
1028
+ if (rest[0] === "--child" && rest[1] && !rest[1].startsWith("--")) {
1029
+ childId = rest[1];
1030
+ messageStart = 2;
1031
+ }
1032
+ const message = rest.slice(messageStart).join(" ").trim();
1033
+ if (!message) {
1034
+ sendSlashText(dm, usage);
1035
+ return;
1036
+ }
1037
+ let targetId = id;
1038
+ let childIndex;
1039
+ if (childId !== undefined) {
1040
+ const sessionId = state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined;
1041
+ const run = listAsyncRuns(DIRS.async, {
1042
+ runId: id,
1043
+ states: ["queued", "running", "paused"],
1044
+ ...sessionId ? { sessionId } : {}
1045
+ })[0];
1046
+ const status = run ? readStatus(run.asyncDir) : null;
1047
+ if (!run || !status) {
1048
+ sendSlashText(dm, `No current-session async run found for '${id}'.`);
1049
+ return;
1050
+ }
1051
+ const resolutionStatus = {
1052
+ ...status,
1053
+ steps: status.steps?.map((step, index) => ({ ...step, children: run.steps[index]?.children ?? step.children }))
1054
+ };
1055
+ const resolution = resolveAsyncStatusChild(resolutionStatus, childId, { includeNested: true });
1056
+ if (!resolution.ok) {
1057
+ sendSlashText(dm, resolution.message);
1058
+ return;
1059
+ }
1060
+ if (resolution.child.nested)
1061
+ targetId = resolution.child.nested.id;
1062
+ else if (resolution.child.step.runId)
1063
+ targetId = resolution.child.step.runId;
1064
+ else
1065
+ childIndex = resolution.child.index;
1066
+ }
1067
+ await runCommand(ctx, {
1068
+ action: "steer",
1069
+ id: targetId,
1070
+ message,
1071
+ ...childIndex !== undefined ? { index: childIndex } : {},
1072
+ steeringRecovery: false
1073
+ });
1096
1074
  }
1097
1075
  });
1098
1076
  registerPromptWorkflowCommands({
1099
- pi,
1100
- run: (params, ctx) => runSlashSubagent(pi, ctx, params)
1077
+ dm,
1078
+ run: async (params, ctx) => {
1079
+ launchCommand(ctx, params);
1080
+ }
1101
1081
  });
1102
- pi.registerCommand("subagents-models", {
1082
+ dm.registerCommand("subagents-models", {
1103
1083
  description: "Show runtime-loaded builtin subagent models",
1104
1084
  getArgumentCompletions: makeBuiltinAgentNameCompletions(),
1105
1085
  handler: async (args, ctx) => {
1106
1086
  const trimmed = args.trim();
1107
1087
  if (!trimmed) {
1108
- await runSlashSubagent(pi, ctx, { action: "models" });
1088
+ await runCommand(ctx, { action: "models" });
1109
1089
  return;
1110
1090
  }
1111
1091
  const parts = trimmed.split(/\s+/).filter(Boolean);
@@ -1118,26 +1098,26 @@ ${finalTask}`;
1118
1098
  ctx.ui.notify(`Unknown builtin agent: ${agent}`, "error");
1119
1099
  return;
1120
1100
  }
1121
- await runSlashSubagent(pi, ctx, { action: "models", agent });
1101
+ await runCommand(ctx, { action: "models", agent });
1122
1102
  }
1123
1103
  });
1124
- pi.registerCommand("subagents-profiles", {
1104
+ dm.registerCommand("subagents-profiles", {
1125
1105
  description: "List saved subagent profiles",
1126
1106
  handler: async (_args, _ctx) => {
1127
1107
  const profiles = listSubagentProfiles();
1128
1108
  if (profiles.length === 0) {
1129
- sendSlashText(pi, `Subagent profiles
1109
+ sendSlashText(dm, `Subagent profiles
1130
1110
 
1131
1111
  No subagent profiles found in ~/.dm/agent/profiles/dm-subagents/`);
1132
1112
  return;
1133
1113
  }
1134
- sendSlashText(pi, `Subagent profiles
1114
+ sendSlashText(dm, `Subagent profiles
1135
1115
 
1136
1116
  ${profiles.join(`
1137
1117
  `)}`);
1138
1118
  }
1139
1119
  });
1140
- pi.registerCommand("subagents-load-profile", {
1120
+ dm.registerCommand("subagents-load-profile", {
1141
1121
  description: "Load a subagent profile into ~/.dm/agent/settings.json",
1142
1122
  getArgumentCompletions: (prefix) => {
1143
1123
  if (prefix.includes(" "))
@@ -1146,7 +1126,7 @@ ${profiles.join(`
1146
1126
  },
1147
1127
  handler: async (args, ctx) => {
1148
1128
  const parsed = parseSingleRequiredArg(args, "Usage: /subagents-load-profile <name>");
1149
- if (!parsed.ok) {
1129
+ if (parsed.ok === false) {
1150
1130
  ctx.ui.notify(parsed.message, "error");
1151
1131
  return;
1152
1132
  }
@@ -1160,7 +1140,7 @@ ${profiles.join(`
1160
1140
  `Profile: ${result.filePath}`,
1161
1141
  `Updated: ${result.settingsPath}`
1162
1142
  ];
1163
- if (workerModel && typeof pi.setModel === "function" && typeof ctx.modelRegistry?.find === "function" && typeof ctx.modelRegistry?.getAvailable === "function") {
1143
+ if (workerModel && typeof dm.setModel === "function" && typeof ctx.modelRegistry?.find === "function" && typeof ctx.modelRegistry?.getAvailable === "function") {
1164
1144
  const shouldSwitch = await ctx.ui.confirm("", `Profile loaded. Also switch this session to the profile worker model?
1165
1145
 
1166
1146
  ${workerModel}`);
@@ -1170,7 +1150,7 @@ ${workerModel}`);
1170
1150
  if (!modelInfo || !model) {
1171
1151
  lines.push(`Could not switch current session model: '${workerModel}' is not available in the current model registry.`);
1172
1152
  } else {
1173
- const success = await pi.setModel(model);
1153
+ const success = await dm.setModel(model);
1174
1154
  if (success)
1175
1155
  lines.push(`Current session model switched to: ${modelInfo.fullId}`);
1176
1156
  else
@@ -1180,7 +1160,7 @@ ${workerModel}`);
1180
1160
  } else if (workerModel) {
1181
1161
  lines.push(`Profile worker model: ${workerModel}`);
1182
1162
  }
1183
- sendSlashText(pi, lines.join(`
1163
+ sendSlashText(dm, lines.join(`
1184
1164
  `));
1185
1165
  });
1186
1166
  } catch (error) {
@@ -1188,7 +1168,7 @@ ${workerModel}`);
1188
1168
  }
1189
1169
  }
1190
1170
  });
1191
- pi.registerCommand("subagents-refresh-provider-models", {
1171
+ dm.registerCommand("subagents-refresh-provider-models", {
1192
1172
  description: "Refresh the cached model catalog for one provider",
1193
1173
  getArgumentCompletions: makeProviderCompletions(state),
1194
1174
  handler: async (args, ctx) => {
@@ -1196,13 +1176,13 @@ ${workerModel}`);
1196
1176
  const force = /(?:^|\s)--force$/.test(trimmed) || /(?:^|\s)force$/.test(trimmed);
1197
1177
  const withoutForce = trimmed.replace(/(?:^|\s)(?:--force|force)$/, "").trim();
1198
1178
  const parsed = parseSingleRequiredArg(withoutForce, "Usage: /subagents-refresh-provider-models <provider> [--force]");
1199
- if (!parsed.ok) {
1179
+ if (parsed.ok === false) {
1200
1180
  ctx.ui.notify(parsed.message, "error");
1201
1181
  return;
1202
1182
  }
1203
1183
  try {
1204
1184
  await withSlashStatus(ctx, `Refreshing provider models for ${parsed.value}…`, async () => {
1205
- const result = await refreshProviderModelCatalog(pi, ctx, parsed.value, { force, maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });
1185
+ const result = await refreshProviderModelCatalog(dm, ctx, parsed.value, { force, maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });
1206
1186
  const lines = [
1207
1187
  "Provider model catalog",
1208
1188
  `Provider: ${parsed.value}`,
@@ -1214,7 +1194,7 @@ ${workerModel}`);
1214
1194
  if (result.heuristicFallbackCount > 0) {
1215
1195
  lines.push(`Warning: ${result.heuristicFallbackCount} model${result.heuristicFallbackCount === 1 ? " was" : "s were"} classified with name heuristics fallback.`);
1216
1196
  }
1217
- sendSlashText(pi, lines.join(`
1197
+ sendSlashText(dm, lines.join(`
1218
1198
  `));
1219
1199
  });
1220
1200
  } catch (error) {
@@ -1222,18 +1202,18 @@ ${workerModel}`);
1222
1202
  }
1223
1203
  }
1224
1204
  });
1225
- pi.registerCommand("subagents-generate-profiles", {
1205
+ dm.registerCommand("subagents-generate-profiles", {
1226
1206
  description: "Generate <provider>.quota and <provider>.quality subagent profiles",
1227
1207
  getArgumentCompletions: makeProviderCompletions(state),
1228
1208
  handler: async (args, ctx) => {
1229
1209
  const parsed = parseSingleRequiredArg(args, "Usage: /subagents-generate-profiles <provider>");
1230
- if (!parsed.ok) {
1210
+ if (parsed.ok === false) {
1231
1211
  ctx.ui.notify(parsed.message, "error");
1232
1212
  return;
1233
1213
  }
1234
1214
  try {
1235
1215
  await withSlashStatus(ctx, `Generating profiles for ${parsed.value}…`, async () => {
1236
- const result = await generateProfilesForProvider(pi, ctx, parsed.value, { maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });
1216
+ const result = await generateProfilesForProvider(dm, ctx, parsed.value, { maxAgeDays: DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS });
1237
1217
  const lines = [
1238
1218
  "Generated subagent profiles",
1239
1219
  `Provider: ${parsed.value}`,
@@ -1252,7 +1232,7 @@ ${workerModel}`);
1252
1232
  } else if (result.heuristicFallbackCount > 0) {
1253
1233
  lines.push(`Warning: provider catalog still contains ${result.heuristicFallbackCount} heuristic-classified model${result.heuristicFallbackCount === 1 ? "" : "s"}.`);
1254
1234
  }
1255
- sendSlashText(pi, lines.join(`
1235
+ sendSlashText(dm, lines.join(`
1256
1236
  `));
1257
1237
  });
1258
1238
  } catch (error) {
@@ -1260,7 +1240,7 @@ ${workerModel}`);
1260
1240
  }
1261
1241
  }
1262
1242
  });
1263
- pi.registerCommand("subagents-check-profile", {
1243
+ dm.registerCommand("subagents-check-profile", {
1264
1244
  description: "Check whether a saved profile still points to usable models",
1265
1245
  getArgumentCompletions: (prefix) => {
1266
1246
  if (prefix.includes(" "))
@@ -1269,13 +1249,13 @@ ${workerModel}`);
1269
1249
  },
1270
1250
  handler: async (args, ctx) => {
1271
1251
  const parsed = parseSingleRequiredArg(args, "Usage: /subagents-check-profile <name>");
1272
- if (!parsed.ok) {
1252
+ if (parsed.ok === false) {
1273
1253
  ctx.ui.notify(parsed.message, "error");
1274
1254
  return;
1275
1255
  }
1276
1256
  try {
1277
1257
  await withSlashStatus(ctx, `Checking profile ${parsed.value}…`, async () => {
1278
- const result = await checkSubagentProfile(pi, ctx, parsed.value);
1258
+ const result = await checkSubagentProfile(dm, ctx, parsed.value);
1279
1259
  const lines = [
1280
1260
  "Subagent profile check",
1281
1261
  `Profile: ${result.profileName}`,
@@ -1283,7 +1263,7 @@ ${workerModel}`);
1283
1263
  "",
1284
1264
  ...result.results.map((entry) => `${entry.agent} → ${entry.model} — registry ${entry.inRegistry ? "ok" : "missing"}; probe ${entry.probe.status}${entry.probe.message ? ` (${entry.probe.message.split(/\r?\n/, 1)[0]})` : ""}`)
1285
1265
  ];
1286
- sendSlashText(pi, lines.join(`
1266
+ sendSlashText(dm, lines.join(`
1287
1267
  `));
1288
1268
  });
1289
1269
  } catch (error) {
@@ -1291,4 +1271,14 @@ ${workerModel}`);
1291
1271
  }
1292
1272
  }
1293
1273
  });
1274
+ return {
1275
+ dispose: () => {
1276
+ if (disposed)
1277
+ return;
1278
+ disposed = true;
1279
+ for (const controller of pendingRequests)
1280
+ controller.abort();
1281
+ pendingRequests.clear();
1282
+ }
1283
+ };
1294
1284
  }