@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
@@ -3,27 +3,111 @@ import * as path from "node:path";
3
3
  import { renderWidget, widgetRenderKey } from "../../tui/render.js";
4
4
  import { formatControlNoticeMessage } from "../shared/subagent-control.js";
5
5
  import {
6
- POLL_INTERVAL_MS,
7
- RESULTS_DIR,
6
+ DIRS,
7
+ SUBAGENT_CHILD_STATUS_EVENT,
8
8
  SUBAGENT_CONTROL_EVENT,
9
- SUBAGENT_CONTROL_INTERCOM_EVENT
9
+ SUBAGENT_CONTROL_INTERCOM_EVENT,
10
+ SUBAGENT_STEERING_NOTICE_EVENT,
11
+ WIDGET_ANIMATION_INTERVAL_MS
10
12
  } from "../../shared/types.js";
11
- import { readStatus } from "../../shared/utils.js";
13
+ import { readStatus, resolveWatchPath } from "../../shared/utils.js";
12
14
  import { normalizeParallelGroups } from "./parallel-groups.js";
13
15
  import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.js";
14
- import { hasLiveNestedDescendants, updateAsyncJobNestedProjection } from "../shared/nested-events.js";
16
+ import { findNestedRouteForRootId, hasLiveNestedDescendants, updateAsyncJobNestedProjection } from "../shared/nested-events.js";
15
17
  import { listAsyncRuns } from "./async-status.js";
18
+ import { EXTERNAL_JOB_BRIDGE_REQUEST_DIR, serviceExternalJobBridgeRequests } from "../shared/external-job-bridge.js";
19
+ import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.js";
20
+ import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.js";
21
+ import { validHostStepNodes } from "../shared/host-step-status.js";
22
+ import { withCachedUiContext } from "../../shared/extension-context.js";
16
23
  const CONTROL_EVENT_READ_CHUNK_BYTES = 64 * 1024;
17
24
  const MAX_CONTROL_EVENT_LINE_BYTES = 1024 * 1024;
18
25
  const CONTROL_EVENT_SCAN_WINDOW_BYTES = 2 * 1024 * 1024;
19
- export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
26
+ const MAX_RECENT_FLEET_JOBS = 20;
27
+ const DEFAULT_LIVENESS_INTERVAL_MS = 5000;
28
+ const EVENT_REFRESH_DEBOUNCE_MS = 25;
29
+ const WATCH_ATTACHMENT_RETRY_MS = 100;
30
+ function rememberFleetJob(state, job) {
31
+ state.fleetJobs ??= new Map;
32
+ state.fleetJobs.set(job.asyncId, job);
33
+ const terminal = [...state.fleetJobs.values()].filter((candidate) => candidate.status === "complete" || candidate.status === "failed" || candidate.status === "paused" || candidate.status === "stopped").sort((left, right) => (right.updatedAt ?? right.startedAt ?? 0) - (left.updatedAt ?? left.startedAt ?? 0));
34
+ for (const stale of terminal.slice(MAX_RECENT_FLEET_JOBS))
35
+ state.fleetJobs.delete(stale.asyncId);
36
+ }
37
+ export function createAsyncJobTracker(dm, state, asyncDirRoot, options = {}) {
20
38
  const completionRetentionMs = options.completionRetentionMs ?? 1e4;
21
- const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
22
- const resultsDir = options.resultsDir ?? RESULTS_DIR;
39
+ const livenessIntervalMs = options.pollIntervalMs ?? DEFAULT_LIVENESS_INTERVAL_MS;
40
+ const resultsDir = options.resultsDir ?? DIRS.results;
41
+ const steeringNoticeSeen = new Map;
42
+ const jobWatchers = new Map;
43
+ const refreshTimers = new Map;
44
+ let widgetRerenderTimer;
45
+ const runningJobIds = new Set;
46
+ const externalJobBridgeRuns = new Set;
47
+ const externalJobBridgeEligibility = (steps) => {
48
+ if (!Array.isArray(steps))
49
+ return "unknown";
50
+ for (const step of steps) {
51
+ const runner = step?.runner;
52
+ if (runner === undefined)
53
+ continue;
54
+ if (!runner || typeof runner !== "object" || Array.isArray(runner))
55
+ return "unknown";
56
+ const runnerType = runner.type;
57
+ if (typeof runnerType !== "string")
58
+ return "unknown";
59
+ if (runnerType === "external-job")
60
+ return "required";
61
+ if (runnerType !== "dm" && runnerType !== "external-cli")
62
+ return "unknown";
63
+ }
64
+ return "not-required";
65
+ };
66
+ let rootWatcher;
67
+ let nextLivenessAt = Date.now() + livenessIntervalMs;
68
+ let nextWidgetAnimationAt = Date.now() + WIDGET_ANIMATION_INTERVAL_MS;
69
+ const watch = options.watch ?? fs.watch;
70
+ const useNativeWatcher = () => shouldUseNativeFsWatch("async-job-tracker", options.platform);
71
+ const terminalStatus = (status) => status === "complete" || status === "failed" || status === "paused" || status === "stopped";
72
+ const withLastUiContext = (run) => {
73
+ const cached = state.lastUiContext;
74
+ return withCachedUiContext(cached, () => {
75
+ if (state.lastUiContext === cached)
76
+ state.lastUiContext = null;
77
+ }, run);
78
+ };
23
79
  const rerenderWidget = (ctx, jobs = Array.from(state.asyncJobs.values())) => {
24
- renderWidget(ctx, jobs);
80
+ if (state.widgetsSuspended)
81
+ return;
82
+ renderWidget(ctx, options.widgetEnabled === false ? [] : jobs);
25
83
  ctx.ui.requestRender?.();
26
84
  };
85
+ const rerenderLastWidget = (jobs = Array.from(state.asyncJobs.values())) => {
86
+ withLastUiContext((ctx) => rerenderWidget(ctx, jobs));
87
+ };
88
+ const scheduleLastWidgetRerender = () => {
89
+ if (widgetRerenderTimer)
90
+ return;
91
+ widgetRerenderTimer = setTimeout(() => {
92
+ widgetRerenderTimer = undefined;
93
+ rerenderLastWidget();
94
+ }, EVENT_REFRESH_DEBOUNCE_MS);
95
+ widgetRerenderTimer.unref?.();
96
+ };
97
+ const requestLastWidgetRender = () => {
98
+ if (options.widgetEnabled === false)
99
+ return;
100
+ withLastUiContext((ctx) => {
101
+ if (state.widgetsSuspended)
102
+ return;
103
+ const requestRender = ctx.ui.requestRender;
104
+ if (requestRender)
105
+ requestRender.call(ctx.ui);
106
+ else
107
+ renderWidget(ctx, Array.from(state.asyncJobs.values()));
108
+ });
109
+ };
110
+ const refreshWidget = (ctx) => rerenderWidget(ctx);
27
111
  const restoredControlEventCursor = (asyncDir) => {
28
112
  try {
29
113
  return fs.statSync(path.join(asyncDir, "events.jsonl")).size;
@@ -33,6 +117,14 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
33
117
  throw error;
34
118
  }
35
119
  };
120
+ const findNestedRouteForJob = (runId) => {
121
+ try {
122
+ return findNestedRouteForRootId(runId);
123
+ } catch (error) {
124
+ console.error(`Failed to resolve nested event route for '${runId}':`, error);
125
+ return;
126
+ }
127
+ };
36
128
  const summaryToJob = (run) => {
37
129
  const groups = normalizeParallelGroups(run.parallelGroups, run.steps.length, run.chainStepCount ?? run.steps.length);
38
130
  const activeGroup = run.currentStep !== undefined ? groups.find((group) => run.currentStep >= group.start && run.currentStep < group.start + group.count) : undefined;
@@ -40,6 +132,7 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
40
132
  return {
41
133
  asyncId: run.id,
42
134
  asyncDir: run.asyncDir,
135
+ toolCallId: run.toolCallId,
43
136
  status: run.state,
44
137
  sessionId: run.sessionId,
45
138
  activityState: run.activityState,
@@ -49,11 +142,18 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
49
142
  currentPath: run.currentPath,
50
143
  turnCount: run.turnCount,
51
144
  toolCount: run.toolCount,
145
+ steering: run.steering,
52
146
  mode: run.mode,
147
+ context: run.context,
148
+ cwd: run.cwd,
149
+ sessionRoot: run.sessionRoot,
53
150
  agents: visibleSteps.map((step) => step.agent),
54
151
  currentStep: run.currentStep,
55
152
  chainStepCount: run.chainStepCount,
56
153
  parallelGroups: groups,
154
+ hostSteps: run.hostSteps,
155
+ ...run.mode === "workflow" && run.workflowGraph ? { workflowGraph: run.workflowGraph } : {},
156
+ preflight: run.preflight,
57
157
  steps: visibleSteps,
58
158
  stepsTotal: visibleSteps.length,
59
159
  runningSteps: visibleSteps.filter((step) => step.status === "running").length,
@@ -65,6 +165,7 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
65
165
  timeoutMs: run.timeoutMs,
66
166
  deadlineAt: run.deadlineAt,
67
167
  timedOut: run.timedOut,
168
+ stopped: run.stopped,
68
169
  turnBudget: run.turnBudget,
69
170
  turnBudgetExceeded: run.turnBudgetExceeded,
70
171
  wrapUpRequested: run.wrapUpRequested,
@@ -73,7 +174,12 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
73
174
  totalTokens: run.totalTokens,
74
175
  sessionFile: run.sessionFile,
75
176
  controlEventCursor: restoredControlEventCursor(run.asyncDir),
76
- nestedChildren: run.nestedChildren
177
+ nestedRoute: findNestedRouteForJob(run.id),
178
+ nestedChildren: run.nestedChildren,
179
+ parentWorkflowRunId: run.parentWorkflowRunId,
180
+ workflowKey: run.workflowKey,
181
+ workflow: run.workflow,
182
+ workflowChildren: parseWorkflowChildSummary(run.workflowChildren)
77
183
  };
78
184
  };
79
185
  const cancelCleanup = (asyncId) => {
@@ -87,10 +193,9 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
87
193
  cancelCleanup(asyncId);
88
194
  const timer = setTimeout(() => {
89
195
  state.cleanupTimers.delete(asyncId);
196
+ closeJobWatcher(asyncId);
90
197
  state.asyncJobs.delete(asyncId);
91
- if (state.lastUiContext) {
92
- rerenderWidget(state.lastUiContext);
93
- }
198
+ rerenderLastWidget();
94
199
  }, completionRetentionMs);
95
200
  state.cleanupTimers.set(asyncId, timer);
96
201
  };
@@ -114,6 +219,8 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
114
219
  cursor = stat.size - CONTROL_EVENT_SCAN_WINDOW_BYTES;
115
220
  if (stat.size <= cursor)
116
221
  return;
222
+ const previousByte = Buffer.alloc(1);
223
+ const startsMidLine = cursor > 0 && fs.readSync(fd, previousByte, 0, 1, cursor - 1) === 1 && previousByte[0] !== 10;
117
224
  const scanEnd = Math.min(stat.size, cursor + CONTROL_EVENT_SCAN_WINDOW_BYTES);
118
225
  const handleLine = (line) => {
119
226
  if (!line.trim())
@@ -125,7 +232,52 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
125
232
  console.error(`Ignoring malformed async control event in '${eventsPath}':`, error);
126
233
  return;
127
234
  }
128
- if (!parsed || typeof parsed !== "object" || parsed.type !== "subagent.control")
235
+ if (!parsed || typeof parsed !== "object")
236
+ return;
237
+ if (parsed.type === "subagent.child-status") {
238
+ const event = parsed;
239
+ if (event.version !== 1 || typeof event.runId !== "string" || typeof event.childId !== "string" || event.status !== "stopping" && event.status !== "stopped" || typeof event.ts !== "number")
240
+ return;
241
+ dm.events.emit(SUBAGENT_CHILD_STATUS_EVENT, {
242
+ type: "subagent.child-status",
243
+ version: 1,
244
+ runId: event.runId,
245
+ childId: event.childId,
246
+ status: event.status,
247
+ ts: event.ts,
248
+ ...typeof event.reason === "string" ? { reason: event.reason } : {},
249
+ source: event.source === "rpc" ? "rpc" : "async",
250
+ asyncDir: job.asyncDir,
251
+ ...typeof event.stepIndex === "number" ? { stepIndex: event.stepIndex } : {},
252
+ ...typeof event.agent === "string" ? { agent: event.agent } : {},
253
+ ...typeof event.childRunId === "string" ? { childRunId: event.childRunId } : {},
254
+ ...typeof event.workflowKey === "string" ? { workflowKey: event.workflowKey } : {},
255
+ ...typeof event.phase === "string" ? { phase: event.phase } : {},
256
+ ...typeof event.label === "string" ? { label: event.label } : {}
257
+ });
258
+ return;
259
+ }
260
+ if (parsed.type === "subagent.steering.notice") {
261
+ const notice = parsed;
262
+ if (typeof notice.requestId !== "string" || typeof notice.runId !== "string" || notice.state !== "failed" && notice.state !== "partial" && notice.state !== "recovered" || typeof notice.message !== "string")
263
+ return;
264
+ if (typeof state.currentSessionId === "string" && notice.currentSessionId !== state.currentSessionId)
265
+ return;
266
+ const key = `${notice.runId}:${notice.requestId}:${notice.state}`;
267
+ if (steeringNoticeSeen.has(key))
268
+ return;
269
+ const now = Date.now();
270
+ steeringNoticeSeen.set(key, now);
271
+ if (steeringNoticeSeen.size > 200) {
272
+ for (const [seenKey, seenAt] of steeringNoticeSeen) {
273
+ if (now - seenAt > 10 * 60 * 1000 || steeringNoticeSeen.size > 200)
274
+ steeringNoticeSeen.delete(seenKey);
275
+ }
276
+ }
277
+ dm.events.emit(SUBAGENT_STEERING_NOTICE_EVENT, { ...notice, source: "async", asyncDir: job.asyncDir, noticeText: notice.message });
278
+ return;
279
+ }
280
+ if (parsed.type !== "subagent.control")
129
281
  return;
130
282
  const record = parsed;
131
283
  if (!record.event || !Array.isArray(record.channels))
@@ -138,10 +290,10 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
138
290
  noticeText: record.noticeText ?? formatControlNoticeMessage(record.event, record.childIntercomTarget)
139
291
  };
140
292
  if (record.channels.includes("event")) {
141
- pi.events.emit(SUBAGENT_CONTROL_EVENT, payload);
293
+ dm.events.emit(SUBAGENT_CONTROL_EVENT, payload);
142
294
  }
143
295
  if (record.event.type !== "active_long_running" && record.channels.includes("intercom") && record.intercom?.to && record.intercom.message) {
144
- pi.events.emit(SUBAGENT_CONTROL_INTERCOM_EVENT, {
296
+ dm.events.emit(SUBAGENT_CONTROL_INTERCOM_EVENT, {
145
297
  ...payload,
146
298
  to: record.intercom.to,
147
299
  message: record.intercom.message
@@ -152,7 +304,7 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
152
304
  let lastCompleteCursor = cursor;
153
305
  let lineParts = [];
154
306
  let lineBytes = 0;
155
- let skippingOversizedLine = startedFromTail;
307
+ let skippingOversizedLine = startedFromTail || startsMidLine;
156
308
  const appendLineSegment = (segment) => {
157
309
  if (segment.length === 0 || skippingOversizedLine)
158
310
  return;
@@ -201,133 +353,305 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
201
353
  fs.closeSync(fd);
202
354
  }
203
355
  };
356
+ const closeJobWatcher = (asyncId) => {
357
+ const watched = jobWatchers.get(asyncId);
358
+ if (watched?.retryTimer)
359
+ clearTimeout(watched.retryTimer);
360
+ for (const watcher of watched?.watchers.values() ?? [])
361
+ watcher.close();
362
+ jobWatchers.delete(asyncId);
363
+ const timer = refreshTimers.get(asyncId);
364
+ if (timer)
365
+ clearTimeout(timer);
366
+ refreshTimers.delete(asyncId);
367
+ runningJobIds.delete(asyncId);
368
+ externalJobBridgeRuns.delete(asyncId);
369
+ };
370
+ const refreshJob = (job) => {
371
+ const widgetExpanded = withLastUiContext((ctx) => ctx.ui.getToolsExpanded?.() ?? false) ?? false;
372
+ const widgetStateBefore = widgetRenderKey(job, widgetExpanded);
373
+ let nestedRefreshFailed = false;
374
+ const refreshNestedProjection = () => {
375
+ try {
376
+ updateAsyncJobNestedProjection(job);
377
+ } catch (error) {
378
+ nestedRefreshFailed = true;
379
+ console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
380
+ }
381
+ };
382
+ let bridgeSweepAttempted = false;
383
+ try {
384
+ emitNewControlEvents(job);
385
+ const bridgeAlreadyRequired = externalJobBridgeRuns.has(job.asyncId) || externalJobBridgeEligibility(job.steps) === "required";
386
+ if (bridgeAlreadyRequired) {
387
+ externalJobBridgeRuns.add(job.asyncId);
388
+ bridgeSweepAttempted = true;
389
+ serviceExternalJobBridgeRequests(job.asyncDir);
390
+ }
391
+ try {
392
+ if (job.nestedRoute)
393
+ reconcileNestedAsyncDescendants(job.nestedRoute, { resultsDir, kill: options.kill, now: options.now });
394
+ } catch (error) {
395
+ nestedRefreshFailed = true;
396
+ console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
397
+ }
398
+ refreshNestedProjection();
399
+ const reconciliation = reconcileAsyncRun(job.asyncDir, {
400
+ resultsDir,
401
+ kill: options.kill,
402
+ now: options.now,
403
+ startedRun: {
404
+ runId: job.asyncId,
405
+ pid: job.pid,
406
+ sessionId: job.sessionId,
407
+ completionOwnerId: job.completionOwnerId,
408
+ mode: job.mode,
409
+ agents: job.agents,
410
+ chainStepCount: job.chainStepCount,
411
+ parallelGroups: job.parallelGroups,
412
+ startedAt: job.startedAt,
413
+ sessionFile: job.sessionFile
414
+ }
415
+ });
416
+ const status = reconciliation.status ?? readStatus(job.asyncDir);
417
+ if (!bridgeAlreadyRequired) {
418
+ const bridgeEligibility = externalJobBridgeEligibility(status?.steps);
419
+ if (bridgeEligibility === "required")
420
+ externalJobBridgeRuns.add(job.asyncId);
421
+ if (bridgeEligibility !== "not-required") {
422
+ bridgeSweepAttempted = true;
423
+ serviceExternalJobBridgeRequests(job.asyncDir);
424
+ }
425
+ }
426
+ if (status) {
427
+ const previousStatus = job.status;
428
+ job.status = status.state;
429
+ if (job.status === "running")
430
+ runningJobIds.add(job.asyncId);
431
+ else
432
+ runningJobIds.delete(job.asyncId);
433
+ if (job.status !== "complete" && job.status !== "failed" && job.status !== "paused" && job.status !== "stopped")
434
+ cancelCleanup(job.asyncId);
435
+ job.sessionId = status.sessionId ?? job.sessionId;
436
+ job.activityState = status.activityState;
437
+ job.lastActivityAt = status.lastActivityAt ?? job.lastActivityAt;
438
+ job.currentTool = status.currentTool;
439
+ job.currentToolStartedAt = status.currentToolStartedAt;
440
+ job.currentPath = status.currentPath;
441
+ job.turnCount = status.turnCount ?? job.turnCount;
442
+ job.toolCount = status.toolCount ?? job.toolCount;
443
+ job.steering = status.steering ?? job.steering;
444
+ job.mode = status.mode;
445
+ job.parentWorkflowRunId = status.parentWorkflowRunId ?? job.parentWorkflowRunId;
446
+ job.workflowKey = status.workflowKey ?? job.workflowKey;
447
+ job.lane = status.lane ?? job.lane;
448
+ job.workflow = status.workflow ?? job.workflow;
449
+ if (status.mode === "workflow")
450
+ job.workflowGraph = status.workflowGraph ?? job.workflowGraph;
451
+ job.hostSteps = validHostStepNodes(status.workflowGraph);
452
+ const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
453
+ if (workflowChildren && workflowChildren.workflowRunId !== status.runId)
454
+ throw new Error("workflowChildren.workflowRunId does not match async status runId.");
455
+ job.workflowChildren = workflowChildren ?? job.workflowChildren;
456
+ job.currentStep = status.currentStep ?? job.currentStep;
457
+ job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
458
+ job.startedAt = status.startedAt ?? job.startedAt;
459
+ if (status.lastUpdate !== undefined)
460
+ job.updatedAt = status.lastUpdate;
461
+ if (status.steps?.length) {
462
+ const groups = normalizeParallelGroups(status.parallelGroups, status.steps.length, status.chainStepCount ?? status.steps.length);
463
+ job.parallelGroups = groups.length ? groups : job.parallelGroups;
464
+ job.hasParallelGroups = groups.length > 0 || job.hasParallelGroups;
465
+ const activeGroup = status.currentStep !== undefined ? groups.find((group) => status.currentStep >= group.start && status.currentStep < group.start + group.count) : undefined;
466
+ const visibleSteps = activeGroup ? status.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count).map((step, index) => ({ ...step, index: activeGroup.start + index })) : status.steps.map((step, index) => ({ ...step, index }));
467
+ job.activeParallelGroup = Boolean(activeGroup);
468
+ job.agents = visibleSteps.map((step) => step.agent);
469
+ job.steps = visibleSteps;
470
+ refreshNestedProjection();
471
+ job.stepsTotal = visibleSteps.length;
472
+ job.runningSteps = visibleSteps.filter((step) => step.status === "running").length;
473
+ job.completedSteps = visibleSteps.filter((step) => step.status === "complete" || step.status === "completed").length;
474
+ if (status.state === "complete")
475
+ job.completedSteps = visibleSteps.length;
476
+ }
477
+ job.sessionDir = status.sessionDir ?? job.sessionDir;
478
+ job.outputFile = status.outputFile ?? job.outputFile;
479
+ job.totalTokens = status.totalTokens ?? job.totalTokens;
480
+ job.timeoutMs = status.timeoutMs ?? job.timeoutMs;
481
+ job.deadlineAt = status.deadlineAt ?? job.deadlineAt;
482
+ job.timedOut = status.timedOut ?? job.timedOut;
483
+ job.stopped = status.stopped ?? job.stopped;
484
+ job.turnBudget = status.turnBudget ?? job.turnBudget;
485
+ job.turnBudgetExceeded = status.turnBudgetExceeded ?? job.turnBudgetExceeded;
486
+ job.wrapUpRequested = status.wrapUpRequested ?? job.wrapUpRequested;
487
+ job.sessionFile = status.sessionFile ?? job.sessionFile;
488
+ if (terminalStatus(job.status)) {
489
+ if (!terminalStatus(previousStatus))
490
+ options.onJobTerminal?.();
491
+ rememberFleetJob(state, job);
492
+ if (!nestedRefreshFailed && !hasLiveNestedDescendants(job.nestedChildren) && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) {
493
+ scheduleCleanup(job.asyncId);
494
+ }
495
+ }
496
+ return widgetRenderKey(job, widgetExpanded) !== widgetStateBefore;
497
+ }
498
+ if (job.status === "queued") {
499
+ job.status = "running";
500
+ job.updatedAt = Date.now();
501
+ runningJobIds.add(job.asyncId);
502
+ }
503
+ } catch (error) {
504
+ if (!bridgeSweepAttempted) {
505
+ try {
506
+ bridgeSweepAttempted = true;
507
+ serviceExternalJobBridgeRequests(job.asyncDir);
508
+ } catch (bridgeError) {
509
+ console.error(`Failed to service external job bridge for '${job.asyncDir}':`, bridgeError);
510
+ }
511
+ }
512
+ if (job.status !== "failed") {
513
+ console.error(`Failed to read async status for '${job.asyncDir}':`, error);
514
+ job.status = "failed";
515
+ job.updatedAt = Date.now();
516
+ }
517
+ runningJobIds.delete(job.asyncId);
518
+ rememberFleetJob(state, job);
519
+ if (!hasLiveNestedDescendants(job.nestedChildren) && !state.cleanupTimers.has(job.asyncId))
520
+ scheduleCleanup(job.asyncId);
521
+ }
522
+ return widgetRenderKey(job, widgetExpanded) !== widgetStateBefore;
523
+ };
524
+ const scheduleJobRefresh = (asyncId, delayMs = EVENT_REFRESH_DEBOUNCE_MS) => {
525
+ if (refreshTimers.has(asyncId))
526
+ return;
527
+ const timer = setTimeout(() => {
528
+ refreshTimers.delete(asyncId);
529
+ const job = state.asyncJobs.get(asyncId);
530
+ if (job && refreshJob(job))
531
+ scheduleLastWidgetRerender();
532
+ }, delayMs);
533
+ timer.unref?.();
534
+ refreshTimers.set(asyncId, timer);
535
+ };
536
+ const watchJob = (job) => {
537
+ if (!useNativeWatcher())
538
+ return;
539
+ const watched = jobWatchers.get(job.asyncId) ?? { watchers: new Map };
540
+ const active = job.status === "queued" || job.status === "running";
541
+ const watchPath = (watchPath) => {
542
+ if (watched.watchers.has(watchPath))
543
+ return true;
544
+ const nestedEventSink = job.nestedRoute?.eventSink === watchPath;
545
+ let watcher;
546
+ try {
547
+ watcher = watch(resolveWatchPath(watchPath), (_event, file) => {
548
+ const rawFileName = file?.toString();
549
+ const fileName = rawFileName ?? path.basename(watchPath);
550
+ const nestedEventFile = nestedEventSink && (!rawFileName || rawFileName.endsWith(".json") || rawFileName.endsWith(".jsonl"));
551
+ if (fileName === "status.json" || fileName === "events.jsonl" || fileName === EXTERNAL_JOB_BRIDGE_REQUEST_DIR || fileName.endsWith(".json") || nestedEventFile) {
552
+ scheduleJobRefresh(job.asyncId);
553
+ }
554
+ if (watchPath !== job.asyncDir && !nestedEventSink) {
555
+ watcher.close();
556
+ watched.watchers.delete(watchPath);
557
+ watchJob(job);
558
+ }
559
+ });
560
+ } catch {
561
+ return false;
562
+ }
563
+ watcher.on("error", () => {
564
+ watcher.close();
565
+ watched.watchers.delete(watchPath);
566
+ if (watchPath.endsWith("status.json") || nestedEventSink)
567
+ watchJob(job);
568
+ });
569
+ watcher.unref?.();
570
+ watched.watchers.set(watchPath, watcher);
571
+ return true;
572
+ };
573
+ watchPath(job.asyncDir);
574
+ const statusPath = path.join(job.asyncDir, "status.json");
575
+ const hadStatusWatcher = watched.watchers.has(statusPath);
576
+ const statusWatched = watchPath(statusPath);
577
+ if (statusWatched && !hadStatusWatcher)
578
+ scheduleJobRefresh(job.asyncId);
579
+ watchPath(path.join(job.asyncDir, "events.jsonl"));
580
+ watchPath(path.join(job.asyncDir, EXTERNAL_JOB_BRIDGE_REQUEST_DIR));
581
+ if (job.nestedRoute)
582
+ watchPath(job.nestedRoute.eventSink);
583
+ if (!statusWatched && active && !watched.retryTimer) {
584
+ watched.retryTimer = setTimeout(() => {
585
+ watched.retryTimer = undefined;
586
+ const current = state.asyncJobs.get(job.asyncId);
587
+ if (current)
588
+ watchJob(current);
589
+ }, WATCH_ATTACHMENT_RETRY_MS);
590
+ watched.retryTimer.unref?.();
591
+ } else if (statusWatched && watched.retryTimer) {
592
+ clearTimeout(watched.retryTimer);
593
+ watched.retryTimer = undefined;
594
+ }
595
+ if (watched.watchers.size > 0 || watched.retryTimer)
596
+ jobWatchers.set(job.asyncId, watched);
597
+ else
598
+ jobWatchers.delete(job.asyncId);
599
+ };
600
+ const watchAsyncRoot = () => {
601
+ if (!useNativeWatcher())
602
+ return;
603
+ if (rootWatcher)
604
+ return;
605
+ try {
606
+ rootWatcher = watch(resolveWatchPath(asyncDirRoot), (_event, file) => {
607
+ const runDirName = file?.toString();
608
+ for (const job of state.asyncJobs.values()) {
609
+ if (jobWatchers.has(job.asyncId) || runDirName && path.basename(job.asyncDir) !== runDirName)
610
+ continue;
611
+ watchJob(job);
612
+ if (jobWatchers.has(job.asyncId))
613
+ scheduleJobRefresh(job.asyncId);
614
+ }
615
+ });
616
+ rootWatcher.on("error", () => {
617
+ rootWatcher?.close();
618
+ rootWatcher = undefined;
619
+ });
620
+ rootWatcher.unref?.();
621
+ } catch {}
622
+ };
204
623
  const ensurePoller = () => {
624
+ watchAsyncRoot();
205
625
  if (state.poller)
206
626
  return;
627
+ nextLivenessAt = Date.now() + livenessIntervalMs;
207
628
  state.poller = setInterval(() => {
208
629
  if (state.asyncJobs.size === 0) {
209
- if (state.lastUiContext?.hasUI)
210
- rerenderWidget(state.lastUiContext, []);
211
- if (state.poller) {
630
+ rerenderLastWidget([]);
631
+ if (state.poller)
212
632
  clearInterval(state.poller);
213
- state.poller = null;
214
- }
633
+ state.poller = null;
634
+ rootWatcher?.close();
635
+ rootWatcher = undefined;
215
636
  return;
216
637
  }
217
- let widgetChanged = false;
218
- for (const job of state.asyncJobs.values()) {
219
- const widgetStateBefore = widgetRenderKey(job);
220
- let nestedRefreshFailed = false;
221
- const refreshNestedProjection = () => {
222
- try {
223
- updateAsyncJobNestedProjection(job);
224
- } catch (error) {
225
- nestedRefreshFailed = true;
226
- console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
227
- }
228
- };
229
- const reconcileNestedDescendants = () => {
230
- try {
231
- if (job.nestedRoute)
232
- reconcileNestedAsyncDescendants(job.nestedRoute, { resultsDir, kill: options.kill, now: options.now });
233
- } catch (error) {
234
- nestedRefreshFailed = true;
235
- console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
236
- }
237
- refreshNestedProjection();
238
- };
239
- try {
240
- emitNewControlEvents(job);
241
- reconcileNestedDescendants();
242
- const reconciliation = reconcileAsyncRun(job.asyncDir, {
243
- resultsDir,
244
- kill: options.kill,
245
- now: options.now,
246
- startedRun: {
247
- runId: job.asyncId,
248
- pid: job.pid,
249
- sessionId: job.sessionId,
250
- mode: job.mode,
251
- agents: job.agents,
252
- chainStepCount: job.chainStepCount,
253
- parallelGroups: job.parallelGroups,
254
- startedAt: job.startedAt,
255
- sessionFile: job.sessionFile
256
- }
257
- });
258
- const status = reconciliation.status ?? readStatus(job.asyncDir);
259
- if (status) {
260
- const previousStatus = job.status;
261
- job.status = status.state;
262
- if (job.status !== "complete" && job.status !== "failed" && job.status !== "paused")
263
- cancelCleanup(job.asyncId);
264
- job.sessionId = status.sessionId ?? job.sessionId;
265
- job.activityState = status.activityState;
266
- job.lastActivityAt = status.lastActivityAt ?? job.lastActivityAt;
267
- job.currentTool = status.currentTool;
268
- job.currentToolStartedAt = status.currentToolStartedAt;
269
- job.currentPath = status.currentPath;
270
- job.turnCount = status.turnCount ?? job.turnCount;
271
- job.toolCount = status.toolCount ?? job.toolCount;
272
- job.mode = status.mode;
273
- job.currentStep = status.currentStep ?? job.currentStep;
274
- job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
275
- job.startedAt = status.startedAt ?? job.startedAt;
276
- if (status.lastUpdate !== undefined)
277
- job.updatedAt = status.lastUpdate;
278
- if (status.steps?.length) {
279
- const groups = normalizeParallelGroups(status.parallelGroups, status.steps.length, status.chainStepCount ?? status.steps.length);
280
- job.parallelGroups = groups.length ? groups : job.parallelGroups;
281
- job.hasParallelGroups = groups.length > 0 || job.hasParallelGroups;
282
- const activeGroup = status.currentStep !== undefined ? groups.find((group) => status.currentStep >= group.start && status.currentStep < group.start + group.count) : undefined;
283
- const visibleSteps = activeGroup ? status.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count).map((step, index) => ({ ...step, index: activeGroup.start + index })) : status.steps.map((step, index) => ({ ...step, index }));
284
- job.activeParallelGroup = Boolean(activeGroup);
285
- job.agents = visibleSteps.map((step) => step.agent);
286
- job.steps = visibleSteps;
287
- refreshNestedProjection();
288
- job.stepsTotal = visibleSteps.length;
289
- job.runningSteps = visibleSteps.filter((step) => step.status === "running").length;
290
- job.completedSteps = visibleSteps.filter((step) => step.status === "complete" || step.status === "completed").length;
291
- if (status.state === "complete")
292
- job.completedSteps = visibleSteps.length;
293
- }
294
- job.sessionDir = status.sessionDir ?? job.sessionDir;
295
- job.outputFile = status.outputFile ?? job.outputFile;
296
- job.totalTokens = status.totalTokens ?? job.totalTokens;
297
- job.timeoutMs = status.timeoutMs ?? job.timeoutMs;
298
- job.deadlineAt = status.deadlineAt ?? job.deadlineAt;
299
- job.timedOut = status.timedOut ?? job.timedOut;
300
- job.turnBudget = status.turnBudget ?? job.turnBudget;
301
- job.turnBudgetExceeded = status.turnBudgetExceeded ?? job.turnBudgetExceeded;
302
- job.wrapUpRequested = status.wrapUpRequested ?? job.wrapUpRequested;
303
- job.sessionFile = status.sessionFile ?? job.sessionFile;
304
- if ((job.status === "complete" || job.status === "failed" || job.status === "paused") && !nestedRefreshFailed && !hasLiveNestedDescendants(job.nestedChildren) && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) {
305
- scheduleCleanup(job.asyncId);
306
- }
307
- if (widgetRenderKey(job) !== widgetStateBefore)
308
- widgetChanged = true;
309
- continue;
310
- }
311
- if (job.status === "queued") {
312
- job.status = "running";
313
- job.updatedAt = Date.now();
314
- }
315
- } catch (error) {
316
- if (job.status !== "failed") {
317
- console.error(`Failed to read async status for '${job.asyncDir}':`, error);
318
- job.status = "failed";
319
- job.updatedAt = Date.now();
320
- }
321
- if (!hasLiveNestedDescendants(job.nestedChildren) && !state.cleanupTimers.has(job.asyncId)) {
322
- scheduleCleanup(job.asyncId);
323
- }
638
+ const now = Date.now();
639
+ if (now >= nextLivenessAt) {
640
+ nextLivenessAt = now + livenessIntervalMs;
641
+ let widgetChanged = false;
642
+ for (const job of state.asyncJobs.values()) {
643
+ watchJob(job);
644
+ if (refreshJob(job))
645
+ widgetChanged = true;
324
646
  }
325
- if (widgetRenderKey(job) !== widgetStateBefore)
326
- widgetChanged = true;
647
+ if (widgetChanged)
648
+ rerenderLastWidget();
649
+ }
650
+ if (runningJobIds.size > 0 && now >= nextWidgetAnimationAt) {
651
+ nextWidgetAnimationAt = now + WIDGET_ANIMATION_INTERVAL_MS;
652
+ requestLastWidgetRender();
327
653
  }
328
- if (widgetChanged && state.lastUiContext?.hasUI)
329
- rerenderWidget(state.lastUiContext);
330
- }, pollIntervalMs);
654
+ }, Math.min(WIDGET_ANIMATION_INTERVAL_MS, livenessIntervalMs));
331
655
  state.poller.unref?.();
332
656
  };
333
657
  const handleStarted = (data) => {
@@ -343,13 +667,20 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
343
667
  const firstGroup = validParallelGroups.find((group) => group.start === 0);
344
668
  const firstGroupCount = firstGroup?.count;
345
669
  const agents = firstGroupCount && firstGroupCount > 0 ? rawAgents?.slice(0, firstGroupCount) : rawAgents;
670
+ const sessionRoot = state.liveAsyncSessionRoots?.get(info.id);
671
+ state.liveAsyncSessionRoots?.delete(info.id);
672
+ externalJobBridgeRuns.delete(info.id);
346
673
  state.asyncJobs.set(info.id, {
347
674
  asyncId: info.id,
348
675
  asyncDir,
676
+ ...typeof info.cwd === "string" ? { cwd: path.resolve(info.cwd) } : {},
677
+ ...sessionRoot ? { sessionRoot } : {},
349
678
  status: "queued",
350
679
  pid: typeof info.pid === "number" ? info.pid : undefined,
351
680
  ...typeof info.sessionId === "string" ? { sessionId: info.sessionId } : {},
681
+ ...typeof info.completionOwnerId === "string" ? { completionOwnerId: info.completionOwnerId } : {},
352
682
  mode: info.mode ?? (info.chain ? "chain" : "single"),
683
+ description: info.goal ?? info.task,
353
684
  agents,
354
685
  chainStepCount: info.chainStepCount,
355
686
  parallelGroups: validParallelGroups,
@@ -362,12 +693,17 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
362
693
  timeoutMs: info.timeoutMs,
363
694
  deadlineAt: info.deadlineAt,
364
695
  turnBudget: info.turnBudget,
696
+ parentWorkflowRunId: info.parentWorkflowRunId,
697
+ workflowKey: info.workflowKey,
698
+ ...info.mode === "workflow" && info.workflowGraph ? { workflowGraph: info.workflowGraph } : {},
365
699
  controlEventCursor: 0
366
700
  });
701
+ const job = state.asyncJobs.get(info.id);
702
+ rememberFleetJob(state, job);
703
+ watchJob(job);
704
+ scheduleJobRefresh(info.id, 0);
367
705
  ensurePoller();
368
- if (state.lastUiContext) {
369
- rerenderWidget(state.lastUiContext);
370
- }
706
+ rerenderLastWidget();
371
707
  };
372
708
  const handleComplete = (data) => {
373
709
  const result = data;
@@ -379,10 +715,15 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
379
715
  const job = state.asyncJobs.get(asyncId);
380
716
  let nestedRefreshFailed = false;
381
717
  if (job) {
382
- job.status = result.success ? "complete" : "failed";
718
+ job.status = result.state ?? (result.success ? "complete" : "failed");
719
+ runningJobIds.delete(asyncId);
720
+ job.stopped = result.stopped ?? job.stopped;
383
721
  job.updatedAt = Date.now();
384
- if (result.asyncDir)
722
+ if (result.asyncDir && result.asyncDir !== job.asyncDir) {
723
+ closeJobWatcher(asyncId);
385
724
  job.asyncDir = result.asyncDir;
725
+ watchJob(job);
726
+ }
386
727
  try {
387
728
  updateAsyncJobNestedProjection(job);
388
729
  } catch (error) {
@@ -390,19 +731,39 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
390
731
  console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
391
732
  }
392
733
  }
393
- if (state.lastUiContext) {
394
- rerenderWidget(state.lastUiContext);
395
- }
734
+ if (job)
735
+ rememberFleetJob(state, job);
736
+ rerenderLastWidget();
396
737
  if (!nestedRefreshFailed && !hasLiveNestedDescendants(job?.nestedChildren))
397
738
  scheduleCleanup(asyncId);
398
739
  };
740
+ const dispose = () => {
741
+ if (state.poller)
742
+ clearInterval(state.poller);
743
+ state.poller = null;
744
+ rootWatcher?.close();
745
+ rootWatcher = undefined;
746
+ for (const asyncId of jobWatchers.keys())
747
+ closeJobWatcher(asyncId);
748
+ for (const timer of refreshTimers.values())
749
+ clearTimeout(timer);
750
+ refreshTimers.clear();
751
+ if (widgetRerenderTimer)
752
+ clearTimeout(widgetRerenderTimer);
753
+ widgetRerenderTimer = undefined;
754
+ runningJobIds.clear();
755
+ externalJobBridgeRuns.clear();
756
+ };
399
757
  const resetJobs = (ctx) => {
400
- for (const timer of state.cleanupTimers.values()) {
758
+ dispose();
759
+ state.statusProjectionSessionId = null;
760
+ for (const timer of state.cleanupTimers.values())
401
761
  clearTimeout(timer);
402
- }
403
762
  state.cleanupTimers.clear();
404
763
  state.asyncJobs.clear();
764
+ state.fleetJobs?.clear();
405
765
  state.foregroundControls?.clear();
766
+ state.liveAsyncSessionRoots?.clear();
406
767
  state.lastForegroundControlId = null;
407
768
  state.resultFileCoalescer.clear();
408
769
  if (ctx?.hasUI) {
@@ -413,6 +774,7 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
413
774
  const restoreActiveJobs = (ctx) => {
414
775
  if (ctx?.hasUI)
415
776
  state.lastUiContext = ctx;
777
+ state.statusProjectionSessionId = null;
416
778
  if (!state.currentSessionId)
417
779
  return;
418
780
  let runs;
@@ -423,13 +785,18 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
423
785
  return;
424
786
  }
425
787
  for (const run of runs) {
426
- state.asyncJobs.set(run.id, summaryToJob(run));
788
+ const job = summaryToJob(run);
789
+ state.asyncJobs.set(run.id, job);
790
+ if (job.status === "running")
791
+ runningJobIds.add(job.asyncId);
792
+ rememberFleetJob(state, job);
793
+ watchJob(job);
427
794
  }
795
+ state.statusProjectionSessionId = state.currentSessionId;
428
796
  if (runs.length === 0)
429
797
  return;
430
798
  ensurePoller();
431
- if (state.lastUiContext?.hasUI)
432
- rerenderWidget(state.lastUiContext);
799
+ rerenderLastWidget();
433
800
  };
434
- return { ensurePoller, handleStarted, handleComplete, resetJobs, restoreActiveJobs };
801
+ return { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs, dispose };
435
802
  }