@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
@@ -0,0 +1,1326 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getMarkdownTheme } from "@duckmind/dm-coding-agent";
4
+ import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@duckmind/dm-tui";
5
+ import { snapshotExternalRuns } from "../api/external-runs.js";
6
+ import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.js";
7
+ import { formatDuration, formatModelThinking, formatTokens, formatTokenUsage, shortenPath } from "../shared/formatters.js";
8
+ import { DIRS } from "../shared/types.js";
9
+ import { decodeUtf8Tail } from "../shared/utf8.js";
10
+ import { readStatus } from "../shared/utils.js";
11
+ import { formatAsyncRunTranscript } from "../runs/background/fleet-view.js";
12
+ import { listAsyncRuns } from "../runs/background/async-status.js";
13
+ import { steerAsyncRun } from "../runs/foreground/async-steering-action.js";
14
+ import { stopAsyncRun } from "../runs/foreground/async-stop-action.js";
15
+ import { resolveWorkflowForegroundSteeringTarget, steerWorkflowForegroundTarget } from "../runs/foreground/workflow-foreground-steering.js";
16
+ import { contextModeBadge, contextModeLabel } from "../runs/shared/context-mode.js";
17
+ import { FLEET_STATUS_WIDGET_KEY } from "./fleet-status.js";
18
+ import { readFleetTranscript, renderFleetTranscript } from "./fleet-transcript.js";
19
+ import { handleHerdrInspectorAction } from "../inspectors/herdr/actions.js";
20
+ import { getLivePromptAudit } from "../runs/foreground/prompt-audit.js";
21
+ const REFRESH_MS = 750;
22
+ const MIN_REFRESH_MS = 250;
23
+ const MAX_RECENT_ASYNC_RUNS = 20;
24
+ const MAX_FLEET_HISTORY_CANDIDATES = 100;
25
+ const TRANSCRIPT_LINES = 200;
26
+ const OUTPUT_TAIL_BYTES = 64 * 1024;
27
+ const PROMPT_AUDIT_SUMMARY_WIDTH = 160;
28
+ export const DEFAULT_FLEET_KEYBINDINGS = {
29
+ close: ["escape", "ctrl+c", "q"],
30
+ scrollUp: ["K"],
31
+ scrollDown: ["J"],
32
+ selectUp: ["up", "k"],
33
+ selectDown: ["down", "j"],
34
+ selectFirst: ["home"],
35
+ selectLast: ["end"],
36
+ pageUp: ["pageUp"],
37
+ pageDown: ["pageDown"],
38
+ refresh: ["r", "R"],
39
+ steer: ["s"],
40
+ inspect: ["H"],
41
+ stop: ["D"],
42
+ toggleTools: ["x", "X", "ctrl+o"]
43
+ };
44
+ export function resolveFleetKeybindings(config) {
45
+ return Object.fromEntries(Object.entries(DEFAULT_FLEET_KEYBINDINGS).map(([action, defaults]) => [action, config?.[action] ?? defaults]));
46
+ }
47
+ function matchesFleetBinding(data, binding) {
48
+ const key = /^[A-Z]$/.test(binding) ? `shift+${binding.toLowerCase()}` : binding;
49
+ return matchesKey(data, key);
50
+ }
51
+ function matchesFleetAction(data, bindings, action) {
52
+ return bindings[action].some((binding) => matchesFleetBinding(data, binding));
53
+ }
54
+ function bindingLabel(bindings, action) {
55
+ return bindings[action].map((binding) => binding === "up" ? "↑" : binding === "down" ? "↓" : binding === "escape" ? "Esc" : binding === "return" ? "Enter" : binding).join("/");
56
+ }
57
+ function belongsToCurrentSession(sessionId, currentSessionId) {
58
+ return !currentSessionId || sessionId === currentSessionId;
59
+ }
60
+ function trackedJobSummary(job) {
61
+ const startedAt = job.startedAt ?? job.updatedAt ?? Date.now();
62
+ return {
63
+ id: job.asyncId,
64
+ asyncDir: job.asyncDir,
65
+ ...job.sessionId ? { sessionId: job.sessionId } : {},
66
+ state: job.status,
67
+ activityState: job.activityState,
68
+ lastActivityAt: job.lastActivityAt,
69
+ currentTool: job.currentTool,
70
+ currentToolStartedAt: job.currentToolStartedAt,
71
+ currentPath: job.currentPath,
72
+ turnCount: job.turnCount,
73
+ toolCount: job.toolCount,
74
+ steering: job.steering,
75
+ mode: job.mode ?? "single",
76
+ ...job.context ? { context: job.context } : {},
77
+ ...job.cwd ? { cwd: job.cwd } : {},
78
+ startedAt,
79
+ ...job.updatedAt !== undefined ? { lastUpdate: job.updatedAt } : {},
80
+ ...job.timeoutMs !== undefined ? { timeoutMs: job.timeoutMs } : {},
81
+ ...job.deadlineAt !== undefined ? { deadlineAt: job.deadlineAt } : {},
82
+ ...job.timedOut !== undefined ? { timedOut: job.timedOut } : {},
83
+ ...job.stopped !== undefined ? { stopped: job.stopped } : {},
84
+ ...job.turnBudget ? { turnBudget: job.turnBudget } : {},
85
+ ...job.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: job.turnBudgetExceeded } : {},
86
+ ...job.wrapUpRequested !== undefined ? { wrapUpRequested: job.wrapUpRequested } : {},
87
+ ...job.currentStep !== undefined ? { currentStep: job.currentStep } : {},
88
+ ...job.chainStepCount !== undefined ? { chainStepCount: job.chainStepCount } : {},
89
+ ...job.parallelGroups?.length ? { parallelGroups: job.parallelGroups } : {},
90
+ ...job.preflight ? { preflight: job.preflight } : {},
91
+ steps: (job.steps ?? job.agents?.map((agent, index) => ({ agent, index, status: job.status === "queued" ? "pending" : job.status })) ?? []).map((step, index) => ({
92
+ ...step,
93
+ index: step.index ?? index
94
+ })),
95
+ ...job.sessionDir ? { sessionDir: job.sessionDir } : {},
96
+ ...job.outputFile ? { outputFile: job.outputFile } : {},
97
+ ...job.totalTokens ? { totalTokens: job.totalTokens } : {},
98
+ ...job.sessionFile ? { sessionFile: job.sessionFile } : {},
99
+ ...job.nestedChildren?.length ? { nestedChildren: job.nestedChildren } : {}
100
+ };
101
+ }
102
+ function asyncItems(run, description) {
103
+ const updatedAt = run.lastUpdate ?? run.endedAt ?? run.startedAt;
104
+ if (run.steps.length === 0 || run.mode === "workflow") {
105
+ return [{ key: `async:${run.id}`, kind: "async", runId: run.id, agent: run.mode, state: run.state, updatedAt, run, ...description ? { description } : {} }];
106
+ }
107
+ return run.steps.map((step) => ({
108
+ key: `async:${run.id}:${step.index}`,
109
+ kind: "async",
110
+ runId: run.id,
111
+ index: step.index,
112
+ agent: step.label ? `${step.label} (${step.agent})` : step.agent,
113
+ state: step.status,
114
+ updatedAt: step.lastActivityAt ?? updatedAt,
115
+ run,
116
+ step,
117
+ ...description ? { description } : {}
118
+ }));
119
+ }
120
+ function orderFleetAsyncRuns(runs, terminalLimit) {
121
+ const updatedAt = (run) => run.lastUpdate ?? run.endedAt ?? run.startedAt;
122
+ const byNewest = (left, right) => updatedAt(right) - updatedAt(left);
123
+ const active = runs.filter((run) => run.state === "queued" || run.state === "running").sort(byNewest);
124
+ const terminal = runs.filter((run) => run.state !== "queued" && run.state !== "running").sort(byNewest);
125
+ return [...active, ...terminal.slice(0, terminalLimit)];
126
+ }
127
+ export function collectFleetSnapshot(state, options = {}) {
128
+ const items = [];
129
+ const activeForegroundIds = new Set;
130
+ const trackedJobs = state.fleetJobs ?? state.asyncJobs;
131
+ const workflowParentIds = new Set([...trackedJobs.values()].filter((job) => job.mode === "workflow" && belongsToCurrentSession(job.sessionId, state.currentSessionId)).map((job) => job.asyncId));
132
+ const workflowForegroundChildCounts = new Map;
133
+ const liveWorkflowForegroundControls = new Set;
134
+ for (const control of state.foregroundControls.values()) {
135
+ const activeChildCount = control.activeChildren?.size ?? 0;
136
+ if (!control.parentWorkflowRunId || !workflowParentIds.has(control.parentWorkflowRunId) || !belongsToCurrentSession(control.sessionId, state.currentSessionId) || !control.workflowSteeringDir || activeChildCount === 0)
137
+ continue;
138
+ liveWorkflowForegroundControls.add(control);
139
+ workflowForegroundChildCounts.set(control.parentWorkflowRunId, (workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) + activeChildCount);
140
+ }
141
+ for (const control of [...state.foregroundControls.values()].sort((left, right) => right.updatedAt - left.updatedAt)) {
142
+ activeForegroundIds.add(control.runId);
143
+ if (control.parentWorkflowRunId && workflowParentIds.has(control.parentWorkflowRunId) && ((workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) <= 1 || !liveWorkflowForegroundControls.has(control)))
144
+ continue;
145
+ if (control.activeChildren) {
146
+ for (const child of [...control.activeChildren.values()].sort((left, right) => left.index - right.index)) {
147
+ items.push({
148
+ key: `foreground-active:${control.runId}:${child.index}`,
149
+ kind: "foreground-active",
150
+ runId: control.runId,
151
+ index: child.index,
152
+ agent: child.agent,
153
+ state: "running",
154
+ updatedAt: child.updatedAt,
155
+ control,
156
+ activeChild: child,
157
+ ...child.description ? { description: child.description } : {}
158
+ });
159
+ }
160
+ continue;
161
+ }
162
+ items.push({
163
+ key: `foreground-active:${control.runId}:${control.currentIndex ?? 0}`,
164
+ kind: "foreground-active",
165
+ runId: control.runId,
166
+ ...control.currentIndex !== undefined ? { index: control.currentIndex } : {},
167
+ agent: control.currentAgent ?? control.mode,
168
+ state: "running",
169
+ updatedAt: control.updatedAt,
170
+ control,
171
+ ...control.description ? { description: control.description } : {}
172
+ });
173
+ }
174
+ let error;
175
+ try {
176
+ let runs;
177
+ const descriptions = new Map;
178
+ const tracked = [...trackedJobs.values()].filter((job) => belongsToCurrentSession(job.sessionId, state.currentSessionId));
179
+ const byUpdate = (left, right) => (right.updatedAt ?? right.startedAt ?? 0) - (left.updatedAt ?? left.startedAt ?? 0);
180
+ const active = tracked.filter((job) => job.status === "queued" || job.status === "running").sort(byUpdate);
181
+ const recent = tracked.filter((job) => job.status !== "queued" && job.status !== "running").sort(byUpdate).slice(0, options.limit ?? MAX_RECENT_ASYNC_RUNS);
182
+ const trackedRuns = [];
183
+ for (const job of [...active, ...recent]) {
184
+ try {
185
+ trackedRuns.push(trackedJobSummary(job));
186
+ if (job.description)
187
+ descriptions.set(job.asyncId, job.description);
188
+ } catch (cause) {
189
+ error = `Failed to inspect async run '${job.asyncId}': ${cause instanceof Error ? cause.message : String(cause)}`;
190
+ }
191
+ }
192
+ if (options.asyncDirRoot !== undefined) {
193
+ const trackedIds = new Set(trackedRuns.map((run) => run.id));
194
+ const history = listAsyncRuns(options.asyncDirRoot, {
195
+ ...state.currentSessionId ? { sessionId: state.currentSessionId } : {},
196
+ entryLimit: MAX_FLEET_HISTORY_CANDIDATES,
197
+ resultsDir: options.resultsDir ?? DIRS.results,
198
+ reconcile: false
199
+ }).filter((run) => !trackedIds.has(run.id));
200
+ runs = [...trackedRuns, ...history];
201
+ } else {
202
+ runs = trackedRuns;
203
+ }
204
+ for (const run of orderFleetAsyncRuns(runs, options.limit ?? MAX_RECENT_ASYNC_RUNS)) {
205
+ items.push(...asyncItems(run, descriptions.get(run.id)));
206
+ }
207
+ } catch (cause) {
208
+ error = cause instanceof Error ? cause.message : String(cause);
209
+ }
210
+ if (state.currentSessionId) {
211
+ try {
212
+ for (const run of snapshotExternalRuns(state.currentSessionId, { ignoreMalformed: true, onMalformedRecord: (message) => console.warn(`[dm-subagents] Removed ${message}`) })) {
213
+ items.push({
214
+ key: `external:${run.id}`,
215
+ kind: "external",
216
+ runId: run.id,
217
+ agent: run.label,
218
+ state: run.state,
219
+ updatedAt: run.updatedAt ?? run.endedAt ?? run.startedAt,
220
+ run,
221
+ ...run.currentAction ? { description: run.currentAction } : {}
222
+ });
223
+ }
224
+ } catch (cause) {
225
+ const message = `Failed to inspect external jobs: ${cause instanceof Error ? cause.message : String(cause)}`;
226
+ error = error ? `${error}; ${message}` : message;
227
+ }
228
+ }
229
+ const recentForeground = [...state.foregroundRuns?.values() ?? []].filter((run) => belongsToCurrentSession(run.sessionId, state.currentSessionId) && !activeForegroundIds.has(run.runId)).sort((left, right) => right.updatedAt - left.updatedAt);
230
+ for (const run of recentForeground) {
231
+ for (const child of run.children) {
232
+ items.push({
233
+ key: `foreground-recent:${run.runId}:${child.index}`,
234
+ kind: "foreground-recent",
235
+ runId: run.runId,
236
+ index: child.index,
237
+ agent: child.agent,
238
+ state: child.status,
239
+ updatedAt: child.updatedAt ?? run.updatedAt,
240
+ run,
241
+ child
242
+ });
243
+ }
244
+ }
245
+ return { items, ...error ? { error } : {} };
246
+ }
247
+ function visibleWorkflowParentKeyForForegroundKey(state, key, items) {
248
+ for (const control of state.foregroundControls.values()) {
249
+ if (!control.parentWorkflowRunId)
250
+ continue;
251
+ let matches = false;
252
+ if (control.activeChildren) {
253
+ for (const child of control.activeChildren.values()) {
254
+ if (`foreground-active:${control.runId}:${child.index}` === key) {
255
+ matches = true;
256
+ break;
257
+ }
258
+ }
259
+ } else {
260
+ matches = `foreground-active:${control.runId}:${control.currentIndex ?? 0}` === key;
261
+ }
262
+ if (!matches)
263
+ continue;
264
+ const parentKey = `async:${control.parentWorkflowRunId}`;
265
+ return items.some((item) => item.key === parentKey) ? parentKey : undefined;
266
+ }
267
+ return;
268
+ }
269
+ function statusGlyph(item, theme) {
270
+ if (item.state === "running")
271
+ return theme.fg("accent", "●");
272
+ if (item.state === "queued" || item.state === "pending")
273
+ return theme.fg("muted", "◦");
274
+ if (item.state === "complete" || item.state === "completed")
275
+ return theme.fg("success", "✓");
276
+ if (item.state === "paused" || item.state === "stopped" || item.state === "detached")
277
+ return theme.fg("warning", "■");
278
+ return theme.fg("error", "✗");
279
+ }
280
+ function foregroundPromptAuditCount(item, state) {
281
+ if (!state.currentSessionId || item.control.sessionId !== state.currentSessionId)
282
+ return 0;
283
+ if (!item.control.activeChildren)
284
+ return getLivePromptAudit(item.control, item.index ?? 0) ? 1 : 0;
285
+ return [...item.control.activeChildren.keys()].filter((index) => getLivePromptAudit(item.control, index)).length;
286
+ }
287
+ function promptAuditString(value) {
288
+ return typeof value === "string" ? value : undefined;
289
+ }
290
+ function authoredPromptSummary(text) {
291
+ if (typeof text !== "string")
292
+ return;
293
+ const summary = text.replace(/\s+/g, " ").trim();
294
+ return summary ? truncateToWidth(summary, PROMPT_AUDIT_SUMMARY_WIDTH, "…") : undefined;
295
+ }
296
+ function foregroundAuthoredPromptSummary(item, state) {
297
+ if (!state.currentSessionId || item.control.sessionId !== state.currentSessionId)
298
+ return;
299
+ const prompt = getLivePromptAudit(item.control, item.index ?? 0);
300
+ return prompt ? authoredPromptSummary(prompt.authoredTask) : undefined;
301
+ }
302
+ function promptAuditText(prompt, view) {
303
+ switch (view) {
304
+ case "authored":
305
+ return promptAuditString(prompt.authoredTask);
306
+ case "runtime":
307
+ return promptAuditString(prompt.runtimeAdditions);
308
+ case "effective":
309
+ return promptAuditString(prompt.finalEffectivePrompt);
310
+ }
311
+ }
312
+ function promptAuditViewLabel(view) {
313
+ switch (view) {
314
+ case "authored":
315
+ return "[1] Authored task";
316
+ case "runtime":
317
+ return "[2] Runtime additions";
318
+ case "effective":
319
+ return "[3] Final effective prompt";
320
+ }
321
+ }
322
+ function foregroundActiveDetail(item, state) {
323
+ const { control } = item;
324
+ const live = item.activeChild ?? control;
325
+ const modelThinking = formatModelThinking(live.model, live.thinking);
326
+ const promptAuditCount = foregroundPromptAuditCount(item, state);
327
+ const promptSummary = foregroundAuthoredPromptSummary(item, state);
328
+ const lines = [
329
+ `Run: ${item.runId}`,
330
+ "Source: foreground",
331
+ `State: running`,
332
+ `Mode: ${control.mode}`,
333
+ control.parentWorkflowRunId ? `Workflow child of: ${control.parentWorkflowRunId}${control.workflowKey ? ` (${control.workflowKey})` : ""}` : undefined,
334
+ control.sourceRunId ? `Redo source: ${control.sourceRunId}` : undefined,
335
+ control.supersededByRunId ? `Superseded by: ${control.supersededByRunId}` : undefined,
336
+ item.index !== undefined ? `Child: ${item.index} (${item.agent})` : `Agent: ${item.agent}`,
337
+ modelThinking ? `Model: ${modelThinking}` : undefined,
338
+ promptSummary ? `Task: ${promptSummary}` : undefined,
339
+ `Started: ${new Date(live.startedAt).toISOString()}`,
340
+ live.currentTool ? `Current tool: ${live.currentTool}${live.currentPath ? ` · ${shortenPath(live.currentPath)}` : ""}` : undefined,
341
+ live.turnCount !== undefined ? `Turns: ${live.turnCount}` : undefined,
342
+ live.toolCount !== undefined ? `Tools: ${live.toolCount}` : undefined,
343
+ live.tokens !== undefined ? `Tokens: ${formatTokenUsage({ input: live.inputTokens ?? 0, output: live.outputTokens ?? 0, total: live.tokens, ...live.window !== undefined ? { window: live.window } : {}, ...live.windowPeak !== undefined ? { windowPeak: live.windowPeak } : {} }, "tokens")}` : undefined,
344
+ promptAuditCount > 0 ? `Prompt audit: ${promptAuditCount} live · 3 views · p opens` : undefined,
345
+ "",
346
+ "Transcript",
347
+ "Live foreground output remains in the expanded subagent tool result. Persisted output and session paths appear here after the child settles."
348
+ ];
349
+ return lines.filter((line) => line !== undefined);
350
+ }
351
+ function pathWithin(base, candidate) {
352
+ const resolvedBase = path.resolve(base);
353
+ const resolvedCandidate = path.resolve(candidate);
354
+ return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
355
+ }
356
+ function trustedFileTail(filePath, trustedRoots) {
357
+ const resolvedPath = path.resolve(filePath);
358
+ if (trustedRoots.length === 0 || !trustedRoots.some((root) => pathWithin(root, resolvedPath)))
359
+ return { warning: `output artifact is outside trusted roots: ${filePath}` };
360
+ let stat;
361
+ try {
362
+ stat = fs.lstatSync(resolvedPath);
363
+ } catch (error) {
364
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
365
+ return { unavailable: `output artifact unavailable: ${filePath}` };
366
+ return { warning: `output artifact could not be inspected: ${error instanceof Error ? error.message : String(error)}` };
367
+ }
368
+ if (stat.isSymbolicLink())
369
+ return { warning: `output artifact refused a symlink: ${filePath}` };
370
+ if (!stat.isFile())
371
+ return { warning: `output artifact is not a file: ${filePath}` };
372
+ try {
373
+ const realPath = fs.realpathSync(resolvedPath);
374
+ const realRoots = trustedRoots.filter((root) => fs.existsSync(root)).map((root) => fs.realpathSync(root));
375
+ if (!realRoots.some((root) => pathWithin(root, realPath)))
376
+ return { warning: `output artifact resolves outside trusted roots: ${filePath}` };
377
+ const fd = fs.openSync(realPath, "r");
378
+ try {
379
+ const bytes = Math.min(stat.size, OUTPUT_TAIL_BYTES);
380
+ const buffer = Buffer.alloc(bytes);
381
+ fs.readSync(fd, buffer, 0, bytes, stat.size - bytes);
382
+ return { text: decodeUtf8Tail(buffer) };
383
+ } finally {
384
+ fs.closeSync(fd);
385
+ }
386
+ } catch (error) {
387
+ return { warning: `output artifact could not be read: ${error instanceof Error ? error.message : String(error)}` };
388
+ }
389
+ }
390
+ function foregroundRecentOutputLines(item, state) {
391
+ const outputPath = item.child.artifactPaths?.outputPath ?? item.child.savedOutputPath;
392
+ const output = item.child.finalOutput ? { text: item.child.finalOutput } : outputPath ? trustedFileTail(path.isAbsolute(outputPath) ? outputPath : path.resolve(item.run.cwd, outputPath), uniquePaths([
393
+ fleetArtifactsRoot(state, item.run.cwd),
394
+ fleetArtifactsRoot(state, state.baseCwd)
395
+ ])) : undefined;
396
+ if (output?.warning)
397
+ return [`(${output.warning})`];
398
+ if (output?.unavailable)
399
+ return [`(${output.unavailable})`];
400
+ const outputLines = (output?.text ?? "").split(/\r?\n/).filter((line) => line.trim()).slice(-TRANSCRIPT_LINES);
401
+ return outputLines.length ? outputLines : ["(no recovered output available)"];
402
+ }
403
+ function foregroundRecentDetail(item, state) {
404
+ const { child, run } = item;
405
+ const outputPath = child.artifactPaths?.outputPath ?? child.savedOutputPath;
406
+ const modelThinking = formatModelThinking(child.model, child.thinking);
407
+ const lines = [
408
+ `Run: ${item.runId}`,
409
+ "Source: foreground",
410
+ `State: ${child.status}`,
411
+ `Mode: ${run.mode}`,
412
+ `Child: ${child.index} (${child.agent})${contextModeLabel(child.context) ? ` ${contextModeLabel(child.context)}` : ""}`,
413
+ modelThinking ? `Model: ${modelThinking}` : undefined,
414
+ `Updated: ${new Date(child.updatedAt ?? run.updatedAt).toISOString()}`,
415
+ outputPath ? `Output: ${outputPath}` : undefined,
416
+ child.sessionFile ? `Session: ${child.sessionFile}` : undefined,
417
+ child.transcriptPath ? `Transcript file: ${child.transcriptPath}` : undefined,
418
+ child.error ? `Error: ${child.error}` : undefined,
419
+ child.outputSaveError ? `Output warning: ${child.outputSaveError}` : undefined,
420
+ child.transcriptError ? `Transcript warning: ${child.transcriptError}` : undefined,
421
+ "",
422
+ "Result transcript tail"
423
+ ];
424
+ lines.push(...foregroundRecentOutputLines(item, state));
425
+ return lines.filter((line) => line !== undefined);
426
+ }
427
+ function externalElapsedEnd(run) {
428
+ const terminal = run.state !== "queued" && run.state !== "running";
429
+ return terminal ? run.endedAt ?? run.updatedAt ?? Date.now() : Date.now();
430
+ }
431
+ function workflowStepLabel(step, index) {
432
+ const key = step.workflowKey ?? `step ${index + 1}`;
433
+ const label = step.label && step.label !== key ? ` · ${step.label}` : "";
434
+ const phase = step.phase ? `${step.phase}: ` : "";
435
+ return `${phase}${key}${label} (${step.agent})`;
436
+ }
437
+ function workflowStepActivity(step) {
438
+ if (step.currentTool)
439
+ return `tool ${step.currentTool}`;
440
+ if (step.currentPath)
441
+ return shortenPath(step.currentPath);
442
+ if (step.activityState === "needs_attention")
443
+ return "needs attention";
444
+ if (step.activityState === "active_long_running")
445
+ return "long-running";
446
+ if (step.turnCount !== undefined)
447
+ return `${step.turnCount} turns`;
448
+ if (step.toolCount !== undefined)
449
+ return `${step.toolCount} tools`;
450
+ return;
451
+ }
452
+ function visibleWorkflowProgressSteps(steps, visibleLimit) {
453
+ if (steps.length <= visibleLimit)
454
+ return steps.map((step, index) => ({ step, index }));
455
+ const selected = new Set;
456
+ for (const [index, step] of steps.entries()) {
457
+ if (step.status !== "complete" && step.status !== "completed")
458
+ selected.add(index);
459
+ if (selected.size >= visibleLimit)
460
+ break;
461
+ }
462
+ for (let index = steps.length - 1;index >= 0 && selected.size < visibleLimit; index--)
463
+ selected.add(index);
464
+ const visible = [...selected].sort((left, right) => left - right).map((index) => ({ step: steps[index], index }));
465
+ return [{ hidden: steps.length - visible.length }, ...visible];
466
+ }
467
+ function workflowProgressLines(steps) {
468
+ if (!steps?.length)
469
+ return [];
470
+ const lines = ["Workflow progress:"];
471
+ for (const row of visibleWorkflowProgressSteps(steps, 8)) {
472
+ if ("hidden" in row) {
473
+ lines.push(` +${row.hidden} hidden workflow steps`);
474
+ continue;
475
+ }
476
+ const activity = workflowStepActivity(row.step);
477
+ const context = contextModeLabel(row.step.context);
478
+ const details = [row.step.status, activity, context, row.step.tokens ? formatTokenUsage(row.step.tokens) : undefined].filter(Boolean).join(" · ");
479
+ lines.push(` ${row.index + 1}. ${workflowStepLabel(row.step, row.index)}${details ? ` — ${details}` : ""}`);
480
+ }
481
+ return lines;
482
+ }
483
+ function asyncDetail(item, state) {
484
+ const status = readStatus(item.run.asyncDir);
485
+ if (status) {
486
+ const trackedJob = state.fleetJobs?.get(item.runId) ?? state.asyncJobs.get(item.runId);
487
+ const lines = formatAsyncRunTranscript(status, item.run.asyncDir, {
488
+ index: item.index,
489
+ lines: TRANSCRIPT_LINES,
490
+ sessionRoots: uniquePaths([...state.trustedSessionRoots ?? [], trackedJob?.sessionRoot]),
491
+ trustedSessionFiles: [item.step?.sessionFile ?? item.run.sessionFile].filter((value) => Boolean(value)),
492
+ trustedSessionFileRoot: state.trustedSessionFileRoot
493
+ }).split(`
494
+ `);
495
+ if (status.mode === "workflow" && item.index === undefined) {
496
+ const progress = workflowProgressLines(status.steps ?? item.run.steps);
497
+ if (progress.length) {
498
+ const modeIndex = lines.findIndex((line) => line.startsWith("Mode:"));
499
+ lines.splice(modeIndex >= 0 ? modeIndex + 1 : 0, 0, "", ...progress);
500
+ }
501
+ }
502
+ return lines;
503
+ }
504
+ const outputPath = item.index !== undefined ? path.join(item.run.asyncDir, `output-${item.index}.log`) : undefined;
505
+ return [
506
+ `Run: ${item.runId}`,
507
+ "Source: async",
508
+ `State: ${item.state}`,
509
+ `Mode: ${item.run.mode}${contextModeLabel(item.run.context) ? ` ${contextModeLabel(item.run.context)}` : ""}`,
510
+ item.index !== undefined ? `Child: ${item.index} (${item.agent})${contextModeLabel(item.step?.context) ? ` ${contextModeLabel(item.step?.context)}` : ""}` : `Agent: ${item.agent}${contextModeLabel(item.run.context) ? ` ${contextModeLabel(item.run.context)}` : ""}`,
511
+ outputPath ? `Output: ${outputPath}` : undefined,
512
+ item.step?.sessionFile ? `Session: ${item.step.sessionFile}` : item.run.sessionFile ? `Session: ${item.run.sessionFile}` : undefined,
513
+ "",
514
+ "Transcript",
515
+ "(status is no longer available)"
516
+ ].filter((line) => line !== undefined);
517
+ }
518
+ function externalDetail(item) {
519
+ return [
520
+ `Run: ${item.runId}`,
521
+ "Source: external · display-only",
522
+ `Owner: ${item.run.source}`,
523
+ `State: ${item.state}`,
524
+ `Started: ${new Date(item.run.startedAt).toISOString()}`,
525
+ item.run.updatedAt !== undefined ? `Updated: ${new Date(item.run.updatedAt).toISOString()}` : undefined,
526
+ item.run.endedAt !== undefined ? `Ended: ${new Date(item.run.endedAt).toISOString()}` : undefined,
527
+ `Elapsed: ${formatDuration(Math.max(0, externalElapsedEnd(item.run) - item.run.startedAt))}`,
528
+ item.run.currentAction ? `Current action: ${item.run.currentAction}` : undefined,
529
+ item.run.reportPath ? `Report path: ${item.run.reportPath}` : undefined,
530
+ item.run.transcriptPath ? `Transcript path: ${item.run.transcriptPath}` : undefined,
531
+ "",
532
+ "Preview",
533
+ item.run.preview ?? "(no preview supplied)",
534
+ "",
535
+ "The owning extension controls execution, persistence, cancellation, and results."
536
+ ].filter((line) => line !== undefined);
537
+ }
538
+ function detailLines(item, error, state) {
539
+ if (!item)
540
+ return [error ? `Fleet scan failed: ${error}` : "No current-session Fleet jobs.", "", "New jobs appear here automatically while this inspector remains open."];
541
+ const lines = item.kind === "foreground-active" ? foregroundActiveDetail(item, state) : item.kind === "foreground-recent" ? foregroundRecentDetail(item, state) : item.kind === "external" ? externalDetail(item) : asyncDetail(item, state);
542
+ if (error)
543
+ lines.unshift(`Fleet scan warning: ${error}`, "");
544
+ return lines;
545
+ }
546
+ function isActionableAsyncState(state) {
547
+ return state === "running" || state === "queued" || state === "pending";
548
+ }
549
+ function firstToolResultText(result, fallback) {
550
+ if (!result)
551
+ return { text: fallback, isError: true };
552
+ const text = result.content.find((item) => item.type === "text")?.text ?? fallback;
553
+ return { text, ...result.isError ? { isError: true } : {} };
554
+ }
555
+ function uniquePaths(values) {
556
+ return [...new Set(values.filter((value) => Boolean(value)).map((value) => path.resolve(value)))];
557
+ }
558
+ function fleetArtifactsRoot(state, cwd) {
559
+ return getArtifactsDir(state.parentSessionFile ?? null, cwd, state.artifactDirPreference);
560
+ }
561
+ function transcriptTarget(item, state) {
562
+ if (item.kind === "external")
563
+ return;
564
+ if (item.kind === "foreground-active") {
565
+ const artifactsRoot = fleetArtifactsRoot(state, item.control.cwd ?? state.baseCwd);
566
+ return {
567
+ path: getArtifactPaths(artifactsRoot, item.runId, item.agent, item.index ?? 0).transcriptPath,
568
+ trustedRoots: [artifactsRoot]
569
+ };
570
+ }
571
+ if (item.kind === "foreground-recent") {
572
+ if (!item.child.transcriptPath)
573
+ return;
574
+ const transcriptPath = path.isAbsolute(item.child.transcriptPath) ? item.child.transcriptPath : path.resolve(item.run.cwd, item.child.transcriptPath);
575
+ return {
576
+ path: transcriptPath,
577
+ trustedRoots: uniquePaths([
578
+ fleetArtifactsRoot(state, item.run.cwd),
579
+ fleetArtifactsRoot(state, state.baseCwd)
580
+ ])
581
+ };
582
+ }
583
+ const step = item.step ?? (item.run.steps.length === 1 ? item.run.steps[0] : undefined);
584
+ const recordedSessionFile = step?.sessionFile ?? item.run.sessionFile;
585
+ const recordedPath = step?.transcriptPath ?? recordedSessionFile;
586
+ if (!recordedPath)
587
+ return;
588
+ const transcriptPath = path.isAbsolute(recordedPath) ? recordedPath : path.resolve(item.run.asyncDir, recordedPath);
589
+ const trackedJob = state.fleetJobs?.get(item.runId) ?? state.asyncJobs.get(item.runId);
590
+ return {
591
+ path: transcriptPath,
592
+ trustedRoots: uniquePaths([
593
+ item.run.asyncDir,
594
+ fleetArtifactsRoot(state, state.baseCwd),
595
+ trackedJob?.cwd ? fleetArtifactsRoot(state, trackedJob.cwd) : undefined,
596
+ item.run.sessionFile ? getArtifactsDir(item.run.sessionFile, item.run.cwd ?? state.baseCwd, state.artifactDirPreference) : undefined
597
+ ]),
598
+ ...!step?.transcriptPath && recordedSessionFile ? { trustedFiles: [recordedSessionFile], trustedFileRoot: state.trustedSessionFileRoot } : {}
599
+ };
600
+ }
601
+ function itemContext(item) {
602
+ if (item.kind === "async")
603
+ return contextModeLabel(item.step?.context ?? item.run.context);
604
+ if (item.kind === "foreground-recent")
605
+ return contextModeLabel(item.child.context);
606
+ return;
607
+ }
608
+ function itemMode(item) {
609
+ if (item.kind === "external")
610
+ return "display-only";
611
+ return item.kind === "foreground-active" ? item.control.mode : item.run.mode;
612
+ }
613
+ function itemSource(item) {
614
+ if (item.kind === "external")
615
+ return `external · ${item.run.source}`;
616
+ if (item.kind === "async")
617
+ return "background";
618
+ return item.kind === "foreground-active" ? "foreground · live" : "foreground · recent";
619
+ }
620
+ function itemStats(item) {
621
+ let model;
622
+ let tokens;
623
+ let tokenUsage;
624
+ let tools;
625
+ let durationMs;
626
+ if (item.kind === "foreground-active") {
627
+ const live = item.activeChild ?? item.control;
628
+ model = formatModelThinking(live.model, live.thinking) || undefined;
629
+ tokens = live.tokens;
630
+ if (tokens !== undefined)
631
+ tokenUsage = { input: live.inputTokens ?? 0, output: live.outputTokens ?? 0, total: tokens, ...live.window !== undefined ? { window: live.window } : {}, ...live.windowPeak !== undefined ? { windowPeak: live.windowPeak } : {} };
632
+ tools = live.toolCount;
633
+ durationMs = Math.max(0, Date.now() - live.startedAt);
634
+ } else if (item.kind === "foreground-recent") {
635
+ model = formatModelThinking(item.child.model, item.child.thinking) || undefined;
636
+ tokens = item.child.tokens;
637
+ if (tokens !== undefined)
638
+ tokenUsage = { input: 0, output: 0, total: tokens, ...item.child.window !== undefined ? { window: item.child.window } : {}, ...item.child.windowPeak !== undefined ? { windowPeak: item.child.windowPeak } : {} };
639
+ tools = item.child.toolCount;
640
+ } else if (item.kind === "external") {
641
+ durationMs = Math.max(0, externalElapsedEnd(item.run) - item.run.startedAt);
642
+ } else {
643
+ model = formatModelThinking(item.step?.model, item.step?.thinking) || undefined;
644
+ tokenUsage = item.step?.tokens ?? (item.index === undefined ? item.run.totalTokens : undefined);
645
+ tokens = tokenUsage?.total;
646
+ tools = item.step?.toolCount ?? (item.index === undefined ? item.run.toolCount : undefined);
647
+ const terminalRun = item.state !== "queued" && item.state !== "running" && item.state !== "pending";
648
+ const endTime = item.run.endedAt ?? (terminalRun ? item.run.lastUpdate : undefined) ?? Date.now();
649
+ durationMs = item.step?.durationMs ?? Math.max(0, endTime - item.run.startedAt);
650
+ }
651
+ return [
652
+ model,
653
+ tokenUsage ? formatTokenUsage(tokenUsage) : tokens !== undefined ? `${formatTokens(tokens)} tok` : undefined,
654
+ tools !== undefined ? `${tools} tool${tools === 1 ? "" : "s"}` : undefined,
655
+ durationMs !== undefined ? formatDuration(durationMs) : undefined
656
+ ].filter((value) => Boolean(value));
657
+ }
658
+ function structuredHeader(item, width, theme, conversationState, promptSummary) {
659
+ const lines = [];
660
+ lines.push(rightAligned(` ${statusGlyph(item, theme)} ${theme.bold(item.agent)}`, theme.fg("dim", item.state), width));
661
+ const child = "index" in item && item.index !== undefined ? ` · child ${item.index + 1}` : "";
662
+ const context = itemContext(item);
663
+ const identity = `${itemSource(item)} · ${item.runId.slice(0, 8)}${child} · ${itemMode(item)}${context ? ` ${context}` : ""}`;
664
+ lines.push(` ${theme.fg("dim", identity)}`);
665
+ const stats = itemStats(item);
666
+ if (stats.length)
667
+ lines.push(` ${theme.fg("muted", stats.join(" · "))}`);
668
+ if (promptSummary)
669
+ lines.push(` ${theme.fg("muted", `Task: ${promptSummary}`)}`);
670
+ lines.push(`${theme.fg("accent", "Conversation")} ${theme.fg("dim", `· ${conversationState}`)}`);
671
+ return lines.map((line) => truncateToWidth(line, width));
672
+ }
673
+ function fit(text, width) {
674
+ const clipped = truncateToWidth(text, Math.max(0, width));
675
+ return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
676
+ }
677
+ function rightAligned(left, right, width) {
678
+ const rightWidth = visibleWidth(right);
679
+ const leftWidth = Math.max(0, width - rightWidth - 1);
680
+ return fit(left, leftWidth) + " ".repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);
681
+ }
682
+ function transcriptFingerprint(filePath) {
683
+ try {
684
+ const stat = fs.statSync(filePath);
685
+ return `${stat.size}:${stat.mtimeMs}`;
686
+ } catch {
687
+ return "missing";
688
+ }
689
+ }
690
+
691
+ export class SubagentFleetComponent {
692
+ snapshot = { items: [] };
693
+ selected = 0;
694
+ selectedKey;
695
+ detailScroll = 0;
696
+ detailAutoFollow = true;
697
+ detailLineCount = 0;
698
+ detailViewportHeight = 8;
699
+ bodyHeight = 8;
700
+ expandedTools = false;
701
+ promptAuditOpen = false;
702
+ promptAuditView = "authored";
703
+ actionNotice;
704
+ steerDraft;
705
+ redoGuidanceDraft;
706
+ steerMode = "steer";
707
+ stopConfirming = false;
708
+ actionBusy = false;
709
+ transcriptCache;
710
+ disposed = false;
711
+ refreshTimer;
712
+ refreshMs;
713
+ tui;
714
+ theme;
715
+ markdownTheme;
716
+ state;
717
+ done;
718
+ options;
719
+ keybindings;
720
+ constructor(tui, theme, state, done, options = {}) {
721
+ this.tui = tui;
722
+ this.theme = theme;
723
+ this.markdownTheme = options.markdownTheme ?? getMarkdownTheme();
724
+ this.state = state;
725
+ this.done = done;
726
+ this.options = options;
727
+ this.keybindings = resolveFleetKeybindings(options.fleetKeybindings);
728
+ this.refreshMs = Math.max(MIN_REFRESH_MS, options.refreshMs ?? REFRESH_MS);
729
+ this.selectedKey = options.initialKey;
730
+ this.refresh();
731
+ this.scheduleRefresh();
732
+ }
733
+ scheduleRefresh() {
734
+ if (this.disposed || this.refreshTimer)
735
+ return;
736
+ this.refreshTimer = setTimeout(() => {
737
+ this.refreshTimer = undefined;
738
+ if (this.disposed)
739
+ return;
740
+ try {
741
+ this.invalidate();
742
+ this.tui.requestRender();
743
+ } finally {
744
+ this.scheduleRefresh();
745
+ }
746
+ }, this.refreshMs);
747
+ this.refreshTimer.unref?.();
748
+ }
749
+ stopRefresh() {
750
+ this.disposed = true;
751
+ if (this.refreshTimer)
752
+ clearTimeout(this.refreshTimer);
753
+ this.refreshTimer = undefined;
754
+ }
755
+ refresh() {
756
+ const previousKey = this.snapshot.items[this.selected]?.key ?? this.selectedKey;
757
+ this.snapshot = collectFleetSnapshot(this.state, this.options);
758
+ let preserved = previousKey ? this.snapshot.items.findIndex((item) => item.key === previousKey) : -1;
759
+ if (preserved < 0 && previousKey) {
760
+ const parentKey = visibleWorkflowParentKeyForForegroundKey(this.state, previousKey, this.snapshot.items);
761
+ if (parentKey)
762
+ preserved = this.snapshot.items.findIndex((item) => item.key === parentKey);
763
+ }
764
+ this.selected = preserved >= 0 ? preserved : Math.min(this.selected, Math.max(0, this.snapshot.items.length - 1));
765
+ this.selectedKey = this.snapshot.items[this.selected]?.key;
766
+ }
767
+ moveSelection(delta) {
768
+ if (this.snapshot.items.length === 0)
769
+ return;
770
+ this.selected = Math.max(0, Math.min(this.snapshot.items.length - 1, this.selected + delta));
771
+ this.selectedKey = this.snapshot.items[this.selected]?.key;
772
+ this.detailAutoFollow = true;
773
+ this.resetActionInput();
774
+ this.tui.requestRender();
775
+ }
776
+ selectedPromptAudit() {
777
+ const item = this.snapshot.items[this.selected];
778
+ if (item?.kind !== "foreground-active")
779
+ return;
780
+ if (!this.state.currentSessionId || item.control.sessionId !== this.state.currentSessionId)
781
+ return;
782
+ return getLivePromptAudit(item.control, item.index ?? 0);
783
+ }
784
+ promptAuditItems() {
785
+ const selected = this.snapshot.items[this.selected];
786
+ if (selected?.kind !== "foreground-active" || !this.state.currentSessionId || selected.control.sessionId !== this.state.currentSessionId)
787
+ return [];
788
+ return this.snapshot.items.flatMap((item) => {
789
+ if (item.kind !== "foreground-active" || item.control !== selected.control)
790
+ return [];
791
+ const prompt = getLivePromptAudit(item.control, item.index ?? 0);
792
+ return prompt ? [{ item, prompt }] : [];
793
+ });
794
+ }
795
+ selectedPromptText() {
796
+ const prompt = this.selectedPromptAudit();
797
+ return prompt ? promptAuditText(prompt, this.promptAuditView) : undefined;
798
+ }
799
+ movePromptSelection(delta) {
800
+ const items = this.promptAuditItems();
801
+ if (items.length === 0)
802
+ return;
803
+ const currentKey = this.snapshot.items[this.selected]?.key;
804
+ const current = Math.max(0, items.findIndex(({ item }) => item.key === currentKey));
805
+ const target = items[Math.max(0, Math.min(items.length - 1, current + delta))]?.item;
806
+ if (!target)
807
+ return;
808
+ this.selected = this.snapshot.items.findIndex((item) => item.key === target.key);
809
+ this.selectedKey = target.key;
810
+ this.detailScroll = 0;
811
+ this.detailAutoFollow = false;
812
+ this.tui.requestRender();
813
+ }
814
+ resetActionInput() {
815
+ this.steerDraft = undefined;
816
+ this.redoGuidanceDraft = undefined;
817
+ this.steerMode = "steer";
818
+ this.stopConfirming = false;
819
+ }
820
+ selectedAsyncAction() {
821
+ const item = this.snapshot.items[this.selected];
822
+ if (!item)
823
+ return { reason: "No child is selected." };
824
+ if (item.kind === "external")
825
+ return { reason: "External jobs are display-only and remain controlled by their owning extension." };
826
+ if (item.kind !== "async")
827
+ return { reason: "Fleet controls are available for current-session top-level async runs only." };
828
+ if (!isActionableAsyncState(item.run.state) || !isActionableAsyncState(item.state))
829
+ return { reason: `Selected child is ${item.state}; controls require a running or queued async child.` };
830
+ return { item };
831
+ }
832
+ selectedSteerAction() {
833
+ const item = this.snapshot.items[this.selected];
834
+ if (item?.kind === "foreground-active" && item.control.parentWorkflowRunId) {
835
+ const parent = this.state.asyncJobs.get(item.control.parentWorkflowRunId) ?? this.state.fleetJobs?.get(item.control.parentWorkflowRunId);
836
+ if (!parent || !isActionableAsyncState(parent.status))
837
+ return { reason: "The parent workflow is no longer available for steering." };
838
+ return { runId: item.runId, asyncDir: parent.asyncDir, ...item.index !== undefined ? { index: item.index } : {} };
839
+ }
840
+ const target = this.selectedAsyncAction();
841
+ if ("reason" in target)
842
+ return target;
843
+ return { runId: target.item.runId, asyncDir: target.item.run.asyncDir, ...target.item.index !== undefined ? { index: target.item.index } : {} };
844
+ }
845
+ selectedHerdrInspectAction() {
846
+ const item = this.snapshot.items[this.selected];
847
+ if (!item)
848
+ return { reason: "No child is selected." };
849
+ if (item.kind === "external")
850
+ return { reason: "External jobs are display-only and have no Herdr controls." };
851
+ if (item.kind === "async") {
852
+ if (!isActionableAsyncState(item.run.state) || !isActionableAsyncState(item.state))
853
+ return { reason: `Selected child is ${item.state}; controls require a running or queued async child.` };
854
+ return { runId: item.runId, asyncDir: item.run.asyncDir, ...item.index !== undefined ? { index: item.index } : {} };
855
+ }
856
+ if (item.kind !== "foreground-active" || !item.control.parentWorkflowRunId)
857
+ return { reason: "Fleet controls are available for current-session top-level async runs only." };
858
+ const parent = this.state.asyncJobs.get(item.control.parentWorkflowRunId) ?? this.state.fleetJobs?.get(item.control.parentWorkflowRunId);
859
+ if (!parent || !isActionableAsyncState(parent.status))
860
+ return { reason: "The parent workflow is no longer available for Herdr inspection." };
861
+ return { runId: parent.asyncId, asyncDir: parent.asyncDir };
862
+ }
863
+ actionLines() {
864
+ const lines = [];
865
+ if (this.actionBusy)
866
+ lines.push(this.theme.fg("accent", "Action pending..."));
867
+ if (this.steerDraft !== undefined) {
868
+ lines.push(this.theme.fg("accent", `Steer message (${this.steerMode}): ${this.steerDraft}${this.theme.fg("dim", "▌")}`));
869
+ lines.push(this.theme.fg("dim", "Enter sends · Tab changes mode · Esc cancels · Backspace edits"));
870
+ } else if (this.redoGuidanceDraft !== undefined) {
871
+ lines.push(this.theme.fg("accent", `Redo guidance: ${this.redoGuidanceDraft}${this.theme.fg("dim", "▌")}`));
872
+ lines.push(this.theme.fg("dim", "Enter rewrites and reruns · Esc cancels · Backspace edits"));
873
+ } else if (this.stopConfirming) {
874
+ const selected = this.snapshot.items[this.selected];
875
+ lines.push(this.theme.fg("warning", `Confirm stop for async run ${selected?.runId ?? "selected run"}?`));
876
+ lines.push(this.theme.fg("dim", "Stop ends the run; use interrupt for a resumable pause. Enter/Y confirms · N returns · Esc cancels"));
877
+ } else if (this.actionNotice) {
878
+ lines.push(this.theme.fg(this.actionNotice.isError ? "error" : "success", this.actionNotice.text));
879
+ }
880
+ return lines;
881
+ }
882
+ withActionLines(body) {
883
+ const actionLines = this.actionLines();
884
+ return actionLines.length ? [...actionLines, "", ...body] : body;
885
+ }
886
+ setActionNotice(result) {
887
+ this.actionNotice = result;
888
+ this.resetActionInput();
889
+ this.detailAutoFollow = false;
890
+ this.detailScroll = 0;
891
+ this.refresh();
892
+ this.tui.requestRender();
893
+ }
894
+ runAction(action) {
895
+ if (this.actionBusy)
896
+ return;
897
+ this.actionBusy = true;
898
+ this.actionNotice = undefined;
899
+ this.tui.requestRender();
900
+ action().then((result) => this.setActionNotice(result)).catch((error) => this.setActionNotice({ text: error instanceof Error ? error.message : String(error), isError: true })).finally(() => {
901
+ this.actionBusy = false;
902
+ if (!this.disposed)
903
+ this.tui.requestRender();
904
+ });
905
+ }
906
+ scrollDetail(delta) {
907
+ const maxScroll = Math.max(0, this.detailLineCount - this.detailViewportHeight);
908
+ this.detailScroll = Math.max(0, Math.min(maxScroll, this.detailScroll + delta));
909
+ this.detailAutoFollow = this.detailScroll >= maxScroll;
910
+ this.tui.requestRender();
911
+ }
912
+ handleInput(data) {
913
+ if (this.promptAuditOpen && this.redoGuidanceDraft !== undefined) {
914
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
915
+ this.resetActionInput();
916
+ this.tui.requestRender();
917
+ return;
918
+ }
919
+ if (matchesKey(data, "return") || data === "\r" || data === `
920
+ `) {
921
+ const guidance = this.redoGuidanceDraft.trim();
922
+ if (!guidance) {
923
+ this.setActionNotice({ text: "Redo guidance cannot be empty.", isError: true });
924
+ return;
925
+ }
926
+ const selected = this.snapshot.items[this.selected];
927
+ if (selected?.kind !== "foreground-active" || !this.options.actions?.redoPrompt) {
928
+ this.setActionNotice({ text: "Redo is unavailable for this prompt.", isError: true });
929
+ return;
930
+ }
931
+ this.runAction(() => this.options.actions.redoPrompt({ runId: selected.runId, index: selected.index ?? 0, guidance, control: selected.control }));
932
+ return;
933
+ }
934
+ if (matchesKey(data, "backspace") || data === "") {
935
+ this.redoGuidanceDraft = this.redoGuidanceDraft.slice(0, -1);
936
+ this.tui.requestRender();
937
+ return;
938
+ }
939
+ if (data.length === 1 && data >= " " && data !== "") {
940
+ this.redoGuidanceDraft += data;
941
+ this.tui.requestRender();
942
+ }
943
+ return;
944
+ }
945
+ if (this.promptAuditOpen) {
946
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
947
+ this.promptAuditOpen = false;
948
+ this.detailAutoFollow = true;
949
+ this.tui.requestRender();
950
+ return;
951
+ }
952
+ if (data === "j")
953
+ return this.movePromptSelection(1);
954
+ if (data === "k")
955
+ return this.movePromptSelection(-1);
956
+ if (data === "1" || data === "2" || data === "3") {
957
+ this.promptAuditView = data === "1" ? "authored" : data === "2" ? "runtime" : "effective";
958
+ this.detailScroll = 0;
959
+ this.tui.requestRender();
960
+ return;
961
+ }
962
+ if (data === "g") {
963
+ const prompt = this.selectedPromptAudit();
964
+ if (!prompt)
965
+ this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
966
+ else if (!prompt.rerun)
967
+ this.setActionNotice({ text: "Redo is not safe for this prompt in this slice.", isError: true });
968
+ else if (!this.options.actions?.redoPrompt)
969
+ this.setActionNotice({ text: "Redo controls are unavailable in this context.", isError: true });
970
+ else {
971
+ this.actionNotice = undefined;
972
+ this.redoGuidanceDraft = "";
973
+ this.detailAutoFollow = false;
974
+ this.detailScroll = 0;
975
+ this.tui.requestRender();
976
+ }
977
+ return;
978
+ }
979
+ if (data === "c") {
980
+ const text = this.selectedPromptText();
981
+ if (!text)
982
+ this.setActionNotice({ text: "No prompt view is available to copy.", isError: true });
983
+ else if (!this.options.copyText)
984
+ this.setActionNotice({ text: "Clipboard is unavailable in this context.", isError: true });
985
+ else
986
+ this.runAction(async () => {
987
+ await this.options.copyText(text);
988
+ return { text: "Copied visible prompt view." };
989
+ });
990
+ return;
991
+ }
992
+ if (matchesFleetAction(data, this.keybindings, "scrollUp"))
993
+ return this.scrollDetail(-1);
994
+ if (matchesFleetAction(data, this.keybindings, "scrollDown"))
995
+ return this.scrollDetail(1);
996
+ if (matchesFleetAction(data, this.keybindings, "pageUp"))
997
+ return this.scrollDetail(-this.detailViewportHeight);
998
+ if (matchesFleetAction(data, this.keybindings, "pageDown"))
999
+ return this.scrollDetail(this.detailViewportHeight);
1000
+ return;
1001
+ }
1002
+ if (this.steerDraft !== undefined) {
1003
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
1004
+ this.resetActionInput();
1005
+ this.tui.requestRender();
1006
+ return;
1007
+ }
1008
+ if (matchesKey(data, "return") || data === "\r" || data === `
1009
+ `) {
1010
+ const message = this.steerDraft.trim();
1011
+ if (!message) {
1012
+ this.setActionNotice({ text: "Steer message cannot be empty.", isError: true });
1013
+ return;
1014
+ }
1015
+ const target = this.selectedSteerAction();
1016
+ if ("reason" in target || !this.options.actions) {
1017
+ this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
1018
+ return;
1019
+ }
1020
+ this.runAction(() => this.options.actions.steer({ ...target, message, mode: this.steerMode }));
1021
+ return;
1022
+ }
1023
+ if (matchesKey(data, "tab") || data === "\t") {
1024
+ const modes = ["steer", "follow_up", "auto"];
1025
+ this.steerMode = modes[(modes.indexOf(this.steerMode) + 1) % modes.length];
1026
+ this.tui.requestRender();
1027
+ return;
1028
+ }
1029
+ if (matchesKey(data, "backspace") || data === "") {
1030
+ this.steerDraft = this.steerDraft.slice(0, -1);
1031
+ this.tui.requestRender();
1032
+ return;
1033
+ }
1034
+ if (data.length === 1 && data >= " " && data !== "") {
1035
+ this.steerDraft += data;
1036
+ this.tui.requestRender();
1037
+ }
1038
+ return;
1039
+ }
1040
+ if (this.stopConfirming) {
1041
+ if (matchesKey(data, "return") || data.toLowerCase() === "y") {
1042
+ const target = this.selectedAsyncAction();
1043
+ if ("reason" in target || !this.options.actions) {
1044
+ this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
1045
+ return;
1046
+ }
1047
+ this.runAction(() => Promise.resolve(this.options.actions.stop({ runId: target.item.runId, asyncDir: target.item.run.asyncDir, ...target.item.index !== undefined ? { index: target.item.index } : {} })));
1048
+ return;
1049
+ }
1050
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || data.toLowerCase() === "n" || matchesKey(data, "backspace")) {
1051
+ this.resetActionInput();
1052
+ this.tui.requestRender();
1053
+ }
1054
+ return;
1055
+ }
1056
+ if (matchesFleetAction(data, this.keybindings, "close")) {
1057
+ this.stopRefresh();
1058
+ this.done(undefined);
1059
+ return;
1060
+ }
1061
+ if (matchesFleetAction(data, this.keybindings, "scrollUp"))
1062
+ return this.scrollDetail(-1);
1063
+ if (matchesFleetAction(data, this.keybindings, "scrollDown"))
1064
+ return this.scrollDetail(1);
1065
+ if (matchesFleetAction(data, this.keybindings, "selectUp"))
1066
+ return this.moveSelection(-1);
1067
+ if (matchesFleetAction(data, this.keybindings, "selectDown"))
1068
+ return this.moveSelection(1);
1069
+ if (matchesFleetAction(data, this.keybindings, "selectFirst"))
1070
+ return this.moveSelection(-this.snapshot.items.length);
1071
+ if (matchesFleetAction(data, this.keybindings, "selectLast"))
1072
+ return this.moveSelection(this.snapshot.items.length);
1073
+ if (matchesFleetAction(data, this.keybindings, "pageUp"))
1074
+ return this.scrollDetail(-this.detailViewportHeight);
1075
+ if (matchesFleetAction(data, this.keybindings, "pageDown"))
1076
+ return this.scrollDetail(this.detailViewportHeight);
1077
+ if (matchesFleetAction(data, this.keybindings, "refresh")) {
1078
+ this.transcriptCache = undefined;
1079
+ this.refresh();
1080
+ this.tui.requestRender();
1081
+ return;
1082
+ }
1083
+ if (data === "p") {
1084
+ if (!this.selectedPromptAudit())
1085
+ this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
1086
+ else {
1087
+ this.promptAuditOpen = true;
1088
+ this.promptAuditView = "authored";
1089
+ this.detailScroll = 0;
1090
+ this.detailAutoFollow = false;
1091
+ this.actionNotice = undefined;
1092
+ this.tui.requestRender();
1093
+ }
1094
+ return;
1095
+ }
1096
+ if (matchesFleetAction(data, this.keybindings, "steer")) {
1097
+ const target = this.selectedSteerAction();
1098
+ if ("reason" in target || !this.options.actions)
1099
+ this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
1100
+ else {
1101
+ this.actionNotice = undefined;
1102
+ this.steerDraft = "";
1103
+ this.detailAutoFollow = false;
1104
+ this.detailScroll = 0;
1105
+ this.tui.requestRender();
1106
+ }
1107
+ return;
1108
+ }
1109
+ if (matchesFleetAction(data, this.keybindings, "inspect")) {
1110
+ const target = this.selectedHerdrInspectAction();
1111
+ if ("reason" in target || !this.options.actions?.inspect)
1112
+ this.setActionNotice({ text: "reason" in target ? target.reason : "Herdr inspector controls are unavailable in this context.", isError: true });
1113
+ else
1114
+ this.runAction(() => this.options.actions.inspect(target));
1115
+ return;
1116
+ }
1117
+ if (matchesFleetAction(data, this.keybindings, "stop")) {
1118
+ const target = this.selectedAsyncAction();
1119
+ if ("reason" in target || !this.options.actions)
1120
+ this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
1121
+ else {
1122
+ this.actionNotice = undefined;
1123
+ this.stopConfirming = true;
1124
+ this.detailAutoFollow = false;
1125
+ this.detailScroll = 0;
1126
+ this.tui.requestRender();
1127
+ }
1128
+ return;
1129
+ }
1130
+ if (matchesFleetAction(data, this.keybindings, "toggleTools")) {
1131
+ this.expandedTools = !this.expandedTools;
1132
+ this.transcriptCache = undefined;
1133
+ this.tui.requestRender();
1134
+ }
1135
+ }
1136
+ rosterLines(width) {
1137
+ if (this.snapshot.items.length === 0)
1138
+ return [this.theme.fg("dim", "No tracked children")];
1139
+ const start = Math.max(0, Math.min(this.selected - this.bodyHeight + 1, Math.max(0, this.snapshot.items.length - this.bodyHeight)));
1140
+ return this.snapshot.items.slice(start, start + this.bodyHeight).map((item, offset) => {
1141
+ const index = start + offset;
1142
+ const marker = index === this.selected ? this.theme.fg("accent", "›") : " ";
1143
+ const context = item.kind === "async" ? contextModeBadge(this.theme, item.step?.context ?? item.run.context) : item.kind === "foreground-recent" ? contextModeBadge(this.theme, item.child.context) : "";
1144
+ const agent = index === this.selected ? this.theme.bold(item.agent) : item.agent;
1145
+ const identity = item.runId.slice(0, 8);
1146
+ const left = `${marker} ${statusGlyph(item, this.theme)} ${agent}${context} ${this.theme.fg("dim", `· ${identity}`)}`;
1147
+ return rightAligned(left, this.theme.fg("dim", item.state), width);
1148
+ });
1149
+ }
1150
+ renderedTranscript(target, width) {
1151
+ const fingerprint = `${target.trustedRoots.join("\x00")}|${target.trustedFiles?.join("\x00") ?? ""}|${target.trustedFileRoot ?? ""}|${transcriptFingerprint(target.path)}`;
1152
+ if (this.transcriptCache && this.transcriptCache.path === target.path && this.transcriptCache.fingerprint === fingerprint && this.transcriptCache.width === width && this.transcriptCache.expandedTools === this.expandedTools) {
1153
+ return { transcript: this.transcriptCache.transcript, body: [...this.transcriptCache.body] };
1154
+ }
1155
+ const transcript = readFleetTranscript(target.path, {
1156
+ trustedRoots: target.trustedRoots,
1157
+ ...target.trustedFiles ? { trustedFiles: target.trustedFiles } : {},
1158
+ ...target.trustedFileRoot ? { trustedFileRoot: target.trustedFileRoot } : {}
1159
+ });
1160
+ const body = transcript.events.length > 0 ? renderFleetTranscript(transcript, width, this.theme, this.markdownTheme, { expandedTools: this.expandedTools }) : [];
1161
+ this.transcriptCache = { path: target.path, fingerprint, width, expandedTools: this.expandedTools, transcript, body };
1162
+ return { transcript, body: [...body] };
1163
+ }
1164
+ promptAuditDetail(width) {
1165
+ const selected = this.snapshot.items[this.selected];
1166
+ const prompt = this.selectedPromptAudit();
1167
+ const items = this.promptAuditItems();
1168
+ const selectedPosition = selected ? items.findIndex(({ item }) => item.key === selected.key) : -1;
1169
+ const live = selected?.kind === "foreground-active" ? selected.activeChild ?? selected.control : undefined;
1170
+ const viewLabel = promptAuditViewLabel(this.promptAuditView);
1171
+ const promptText = this.selectedPromptText() ?? this.theme.fg("muted", "Selected prompt unavailable.");
1172
+ const raw = [
1173
+ this.theme.bold("Prompt Audit"),
1174
+ this.theme.fg("dim", "Retention: live memory only · no storage"),
1175
+ selected?.kind === "foreground-active" ? `Run: ${selected.runId}${selected.index !== undefined ? ` · Child: ${selected.index}` : ""} · Agent: ${selected.agent}` : "Selected prompt unavailable",
1176
+ live ? `Started: ${new Date(live.startedAt).toISOString()}` : undefined,
1177
+ live ? `Model: ${formatModelThinking(live.model, live.thinking) || "default"}` : undefined,
1178
+ prompt?.cwd ? `Cwd: ${prompt.cwd}` : undefined,
1179
+ prompt?.outputPath ? `Output: ${prompt.outputPath}` : undefined,
1180
+ this.theme.fg("dim", `Live children: ${items.map(({ item }) => item.agent).join(", ") || "none"}`),
1181
+ this.theme.fg("dim", selectedPosition >= 0 ? `Selected: ${selectedPosition + 1}/${items.length}` : "Selected prompt unavailable"),
1182
+ "",
1183
+ this.theme.fg("accent", viewLabel),
1184
+ promptText
1185
+ ];
1186
+ const body = raw.filter((line) => line !== undefined).flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width)));
1187
+ return { header: [], body: this.withActionLines(body) };
1188
+ }
1189
+ wrappedDetail(width) {
1190
+ if (this.promptAuditOpen)
1191
+ return this.promptAuditDetail(width);
1192
+ const selected = this.snapshot.items[this.selected];
1193
+ let transcriptWarning;
1194
+ if (selected) {
1195
+ const target = transcriptTarget(selected, this.state);
1196
+ if (target) {
1197
+ const { transcript, body } = this.renderedTranscript(target, width);
1198
+ transcriptWarning = transcript.warning;
1199
+ if (transcript.events.length > 0) {
1200
+ if (this.snapshot.error)
1201
+ body.unshift(this.theme.fg("warning", `Fleet scan warning: ${this.snapshot.error}`), "");
1202
+ const latest = transcript.events.at(-1);
1203
+ const conversationState = latest?.kind === "assistant" ? "assistant response" : latest?.kind === "user" ? "supervisor message" : latest?.kind === "tool" ? `${latest.name} · ${latest.status}` : "activity";
1204
+ const promptSummary = selected.kind === "foreground-active" ? foregroundAuthoredPromptSummary(selected, this.state) : undefined;
1205
+ return { header: structuredHeader(selected, width, this.theme, conversationState, promptSummary), body: this.withActionLines(body) };
1206
+ }
1207
+ }
1208
+ }
1209
+ const raw = detailLines(selected, this.snapshot.error, this.state);
1210
+ if (transcriptWarning)
1211
+ raw.unshift(`Transcript preview warning: ${transcriptWarning}`, "");
1212
+ const lines = [];
1213
+ for (const line of raw) {
1214
+ const styled = /^(Run|State|Mode|Source|Child|Agent|Model|Task):/.test(line) ? this.theme.bold(line) : /^(Transcript|Result transcript tail)/.test(line) ? this.theme.fg("accent", line) : /^(Output|Session|Transcript file|Artifacts):/.test(line) ? this.theme.fg("muted", line) : /^Transcript preview warning:/.test(line) ? this.theme.fg("warning", line) : line;
1215
+ const wrapped = wrapTextWithAnsi(styled, Math.max(1, width));
1216
+ lines.push(...wrapped.length ? wrapped : [""]);
1217
+ }
1218
+ return { header: [], body: this.withActionLines(lines) };
1219
+ }
1220
+ render(width) {
1221
+ if (width < 36)
1222
+ return [truncateToWidth("Subagent fleet needs at least 36 columns. Esc closes.", width)];
1223
+ const innerWidth = width - 2;
1224
+ const rows = this.tui.terminal?.rows ?? 32;
1225
+ this.bodyHeight = Math.max(2, Math.floor(rows * 0.85) - 6);
1226
+ const rosterWidth = Math.max(22, Math.min(46, Math.floor((innerWidth - 1) * 0.38)));
1227
+ const detailWidth = Math.max(1, innerWidth - rosterWidth - 1);
1228
+ const roster = this.rosterLines(rosterWidth);
1229
+ const detail = this.wrappedDetail(detailWidth);
1230
+ const detailHeader = detail.header.slice(0, Math.max(0, this.bodyHeight - 1));
1231
+ this.detailViewportHeight = Math.max(1, this.bodyHeight - detailHeader.length);
1232
+ this.detailLineCount = detail.body.length;
1233
+ const maxDetailScroll = Math.max(0, detail.body.length - this.detailViewportHeight);
1234
+ if (this.detailAutoFollow)
1235
+ this.detailScroll = maxDetailScroll;
1236
+ else if (this.detailScroll > maxDetailScroll)
1237
+ this.detailScroll = maxDetailScroll;
1238
+ const visibleDetails = [
1239
+ ...detailHeader,
1240
+ ...detail.body.slice(this.detailScroll, this.detailScroll + this.detailViewportHeight)
1241
+ ];
1242
+ const lines = [this.theme.fg("border", `╭${"─".repeat(innerWidth)}╮`)];
1243
+ const selected = this.snapshot.items[this.selected];
1244
+ const title = selected?.kind === "external" ? ` ${this.theme.bold("Fleet inspector")} ${this.theme.fg("dim", "· external display-only")}` : ` ${this.theme.bold("Subagent fleet inspector")} ${this.theme.fg("dim", "· live controls")}`;
1245
+ const selectedStatus = selected ? `${statusGlyph(selected, this.theme)} ${selected.agent} · ${selected.state} ` : this.theme.fg("dim", "no children ");
1246
+ lines.push(this.theme.fg("border", "│") + rightAligned(title, selectedStatus, innerWidth) + this.theme.fg("border", "│"));
1247
+ lines.push(this.theme.fg("border", `├${"─".repeat(rosterWidth)}┬${"─".repeat(detailWidth)}┤`));
1248
+ for (let index = 0;index < this.bodyHeight; index++) {
1249
+ lines.push(this.theme.fg("border", "│") + fit(roster[index] ?? "", rosterWidth) + this.theme.fg("border", "│") + fit(visibleDetails[index] ?? "", detailWidth) + this.theme.fg("border", "│"));
1250
+ }
1251
+ lines.push(this.theme.fg("border", `├${"─".repeat(rosterWidth)}┴${"─".repeat(detailWidth)}┤`));
1252
+ const position = this.snapshot.items.length ? `${this.selected + 1}/${this.snapshot.items.length}` : "0/0";
1253
+ const footer = this.promptAuditOpen ? ` j/k child · 1/2/3 view · g redo with guidance · c copy · Esc close Prompt Audit · ${position}` : selected?.kind === "external" ? ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} job · display-only · ${bindingLabel(this.keybindings, "refresh")} refresh · ${bindingLabel(this.keybindings, "close")} close · ${position}` : ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} agent · p Prompt Audit · ${bindingLabel(this.keybindings, "inspect")} Herdr · ${bindingLabel(this.keybindings, "steer")} steer · ${bindingLabel(this.keybindings, "stop")} stop · ${bindingLabel(this.keybindings, "toggleTools")} tools · ${bindingLabel(this.keybindings, "refresh")} refresh · ${bindingLabel(this.keybindings, "close")} close · ${position}`;
1254
+ lines.push(this.theme.fg("border", "│") + fit(this.theme.fg("dim", footer), innerWidth) + this.theme.fg("border", "│"));
1255
+ lines.push(this.theme.fg("border", `╰${"─".repeat(innerWidth)}╯`));
1256
+ return lines.map((line) => truncateToWidth(line, width));
1257
+ }
1258
+ invalidate() {
1259
+ this.transcriptCache = undefined;
1260
+ this.refresh();
1261
+ }
1262
+ dispose() {
1263
+ this.stopRefresh();
1264
+ }
1265
+ }
1266
+ export async function openSubagentFleet(ctx, state, options = {}) {
1267
+ const wasOpen = state.fleetInspectorOpen === true;
1268
+ state.fleetInspectorOpen = true;
1269
+ if (typeof ctx.ui.setWidget === "function")
1270
+ ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
1271
+ const copyText = options.copyText ?? (async (text) => {
1272
+ const module = await import("@duckmind/dm-coding-agent");
1273
+ const copyToClipboard = module.copyToClipboard;
1274
+ if (!copyToClipboard)
1275
+ throw new Error("Clipboard is unavailable in this DM version.");
1276
+ await copyToClipboard(text);
1277
+ });
1278
+ const actions = options.actions ?? {
1279
+ steer: async (input) => {
1280
+ const status = readStatus(input.asyncDir);
1281
+ const liveWorkflowRunId = status?.mode === "workflow" && state.workflowControllers?.has(status.runId || input.runId) ? status.runId || input.runId : undefined;
1282
+ if (liveWorkflowRunId) {
1283
+ const route = state.foregroundControls.get(input.runId)?.parentWorkflowRunId === liveWorkflowRunId ? resolveWorkflowForegroundSteeringTarget({ state, childRunId: input.runId, asyncDirRoot: options.asyncDirRoot ?? DIRS.async }) : resolveWorkflowForegroundSteeringTarget({ state, workflowRunId: liveWorkflowRunId, asyncDirRoot: options.asyncDirRoot ?? DIRS.async });
1284
+ if (!route.ok)
1285
+ return { text: route.message, isError: true };
1286
+ return firstToolResultText(await steerWorkflowForegroundTarget({ target: route.target, message: input.message, mode: input.mode, ...input.index !== undefined ? { index: input.index } : {} }), `Failed to steer foreground run ${input.runId}.`);
1287
+ }
1288
+ return firstToolResultText(await steerAsyncRun({
1289
+ state,
1290
+ runId: input.runId,
1291
+ ...input.index !== undefined ? { index: input.index } : {},
1292
+ message: input.message,
1293
+ mode: input.mode,
1294
+ location: { asyncDir: input.asyncDir, resolvedId: input.runId }
1295
+ }), `Failed to steer async run ${input.runId}.`);
1296
+ },
1297
+ stop: (input) => firstToolResultText(stopAsyncRun(state, input.runId, undefined, { asyncDir: input.asyncDir, resolvedId: input.runId }), `Failed to stop async run ${input.runId}.`),
1298
+ inspect: async (input) => firstToolResultText(await handleHerdrInspectorAction("inspector.open", {
1299
+ id: input.runId,
1300
+ dir: input.asyncDir,
1301
+ focus: true,
1302
+ ...input.index !== undefined ? { index: input.index } : {}
1303
+ }, {
1304
+ state,
1305
+ sessionRoots: state.trustedSessionRoots,
1306
+ cwd: state.baseCwd,
1307
+ ...options.herdrClient ? { client: options.herdrClient } : {},
1308
+ ...state.authorityPolicy ? { authorityPolicy: state.authorityPolicy } : {},
1309
+ ...state.missionStoreConfig ? { missions: state.missionStoreConfig } : {}
1310
+ }), `Failed to open Herdr inspector for async run ${input.runId}.`),
1311
+ redoPrompt: async (input) => {
1312
+ const control = input.control ?? state.foregroundControls.get(input.runId);
1313
+ if (!control?.promptAuditRedo)
1314
+ return { text: "Redo is not available for this live child.", isError: true };
1315
+ return control.promptAuditRedo(input.index, input.guidance);
1316
+ }
1317
+ };
1318
+ try {
1319
+ await ctx.ui.custom((tui, theme, _keybindings, done) => new SubagentFleetComponent(tui, theme, state, done, { ...options, actions, copyText }), {
1320
+ overlay: true,
1321
+ overlayOptions: { anchor: "center", width: "95%", minWidth: 60, maxHeight: "85%", margin: 1 }
1322
+ });
1323
+ } finally {
1324
+ state.fleetInspectorOpen = wasOpen;
1325
+ }
1326
+ }