@bastani/atomic 0.8.13-0 → 0.8.14-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/host-html-template.ts +1 -1
  4. package/dist/builtin/mcp/init.ts +15 -2
  5. package/dist/builtin/mcp/mcp-callback-server.ts +10 -9
  6. package/dist/builtin/mcp/package.json +1 -1
  7. package/dist/builtin/mcp/ui-session.ts +9 -6
  8. package/dist/builtin/subagents/CHANGELOG.md +8 -1
  9. package/dist/builtin/subagents/README.md +39 -32
  10. package/dist/builtin/subagents/package.json +1 -1
  11. package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
  12. package/dist/builtin/subagents/src/agents/agent-management.ts +6 -1
  13. package/dist/builtin/subagents/src/agents/agent-serializer.ts +2 -0
  14. package/dist/builtin/subagents/src/agents/agents.ts +44 -19
  15. package/dist/builtin/subagents/src/extension/config.ts +16 -0
  16. package/dist/builtin/subagents/src/extension/fanout-child.ts +246 -0
  17. package/dist/builtin/subagents/src/extension/index.ts +466 -603
  18. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +6 -4
  19. package/dist/builtin/subagents/src/intercom/result-intercom.ts +109 -1
  20. package/dist/builtin/subagents/src/runs/background/async-execution.ts +124 -19
  21. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +41 -6
  22. package/dist/builtin/subagents/src/runs/background/async-resume.ts +28 -15
  23. package/dist/builtin/subagents/src/runs/background/async-status.ts +60 -30
  24. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +111 -54
  25. package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +83 -0
  26. package/dist/builtin/subagents/src/runs/background/run-status.ts +79 -3
  27. package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +46 -1
  28. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +66 -14
  29. package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +10 -3
  30. package/dist/builtin/subagents/src/runs/foreground/execution.ts +14 -2
  31. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +320 -23
  32. package/dist/builtin/subagents/src/runs/shared/completion-guard.ts +23 -1
  33. package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +369 -0
  34. package/dist/builtin/subagents/src/runs/shared/nested-events.ts +935 -0
  35. package/dist/builtin/subagents/src/runs/shared/nested-path.ts +52 -0
  36. package/dist/builtin/subagents/src/runs/shared/nested-render.ts +115 -0
  37. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
  38. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +82 -9
  39. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +1 -1
  40. package/dist/builtin/subagents/src/runs/shared/single-output.ts +12 -2
  41. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +32 -10
  42. package/dist/builtin/subagents/src/runs/shared/worktree.ts +3 -2
  43. package/dist/builtin/subagents/src/shared/artifacts.ts +0 -1
  44. package/dist/builtin/subagents/src/shared/types.ts +96 -1
  45. package/dist/builtin/subagents/src/shared/utils.ts +10 -2
  46. package/dist/builtin/subagents/src/slash/slash-commands.ts +468 -625
  47. package/dist/builtin/subagents/src/tui/render.ts +1227 -2093
  48. package/dist/builtin/web-access/package.json +1 -1
  49. package/dist/builtin/workflows/CHANGELOG.md +24 -0
  50. package/dist/builtin/workflows/README.md +28 -11
  51. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +323 -40
  52. package/dist/builtin/workflows/builtin/ralph.ts +362 -176
  53. package/dist/builtin/workflows/package.json +2 -5
  54. package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
  55. package/dist/builtin/workflows/skills/skill-creator/LICENSE.txt +202 -0
  56. package/dist/builtin/workflows/skills/skill-creator/SKILL.md +489 -0
  57. package/dist/builtin/workflows/skills/skill-creator/agents/analyzer.md +274 -0
  58. package/dist/builtin/workflows/skills/skill-creator/agents/comparator.md +202 -0
  59. package/dist/builtin/workflows/skills/skill-creator/agents/grader.md +223 -0
  60. package/dist/builtin/workflows/skills/skill-creator/assets/eval_review.html +146 -0
  61. package/dist/builtin/workflows/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  62. package/dist/builtin/workflows/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  63. package/dist/builtin/workflows/skills/skill-creator/references/schemas.md +430 -0
  64. package/dist/builtin/workflows/skills/skill-creator/scripts/__init__.py +0 -0
  65. package/dist/builtin/workflows/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  66. package/dist/builtin/workflows/skills/skill-creator/scripts/generate_report.py +326 -0
  67. package/dist/builtin/workflows/skills/skill-creator/scripts/improve_description.py +247 -0
  68. package/dist/builtin/workflows/skills/skill-creator/scripts/package_skill.py +136 -0
  69. package/dist/builtin/workflows/skills/skill-creator/scripts/quick_validate.py +103 -0
  70. package/dist/builtin/workflows/skills/skill-creator/scripts/run_eval.py +310 -0
  71. package/dist/builtin/workflows/skills/skill-creator/scripts/run_loop.py +328 -0
  72. package/dist/builtin/workflows/skills/skill-creator/scripts/utils.py +47 -0
  73. package/dist/builtin/workflows/src/extension/index.ts +869 -93
  74. package/dist/builtin/workflows/src/extension/render-call.ts +34 -1
  75. package/dist/builtin/workflows/src/extension/render-result.ts +126 -21
  76. package/dist/builtin/workflows/src/extension/runtime.ts +91 -3
  77. package/dist/builtin/workflows/src/extension/wiring.ts +38 -12
  78. package/dist/builtin/workflows/src/extension/workflow-schema.ts +62 -5
  79. package/dist/builtin/workflows/src/runs/background/runner.ts +3 -3
  80. package/dist/builtin/workflows/src/runs/background/status.ts +42 -8
  81. package/dist/builtin/workflows/src/runs/foreground/executor.ts +410 -95
  82. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +5 -2
  83. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +8 -0
  84. package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +6 -4
  85. package/dist/builtin/workflows/src/runs/shared/worktree.ts +3 -2
  86. package/dist/builtin/workflows/src/shared/persistence-restore.ts +138 -5
  87. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +30 -0
  88. package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +78 -120
  89. package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +193 -0
  90. package/dist/builtin/workflows/src/shared/store-types.ts +26 -1
  91. package/dist/builtin/workflows/src/shared/store.ts +145 -17
  92. package/dist/builtin/workflows/src/shared/timing.ts +6 -2
  93. package/dist/builtin/workflows/src/shared/workflow-failures.ts +375 -0
  94. package/dist/builtin/workflows/src/tui/chat-surface.ts +68 -17
  95. package/dist/builtin/workflows/src/tui/connectors.ts +2 -2
  96. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +24 -26
  97. package/dist/builtin/workflows/src/tui/graph-canvas.ts +4 -8
  98. package/dist/builtin/workflows/src/tui/graph-view.ts +17 -14
  99. package/dist/builtin/workflows/src/tui/header.ts +38 -0
  100. package/dist/builtin/workflows/src/tui/inline-form-card.ts +161 -238
  101. package/dist/builtin/workflows/src/tui/inline-form-editor.ts +68 -73
  102. package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +2 -3
  103. package/dist/builtin/workflows/src/tui/inline-form-store.ts +2 -1
  104. package/dist/builtin/workflows/src/tui/inputs-overlay.ts +1 -3
  105. package/dist/builtin/workflows/src/tui/inputs-picker.ts +286 -399
  106. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +11 -0
  107. package/dist/builtin/workflows/src/tui/node-card.ts +2 -1
  108. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -1
  109. package/dist/builtin/workflows/src/tui/prompt-card.ts +46 -19
  110. package/dist/builtin/workflows/src/tui/run-detail.ts +63 -80
  111. package/dist/builtin/workflows/src/tui/session-confirm.ts +9 -3
  112. package/dist/builtin/workflows/src/tui/session-picker.ts +19 -16
  113. package/dist/builtin/workflows/src/tui/stage-chat-layout.ts +88 -0
  114. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +368 -879
  115. package/dist/builtin/workflows/src/tui/status-helpers.ts +4 -0
  116. package/dist/builtin/workflows/src/tui/status-list.ts +67 -75
  117. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +50 -12
  118. package/dist/builtin/workflows/src/tui/submit-pane.ts +164 -0
  119. package/dist/builtin/workflows/src/tui/switcher.ts +27 -4
  120. package/dist/builtin/workflows/src/tui/text-helpers.ts +98 -4
  121. package/dist/builtin/workflows/src/tui/widget.ts +90 -68
  122. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +23 -2
  123. package/dist/builtin/workflows/src/tui/workflow-list.ts +44 -68
  124. package/dist/cli/file-processor.d.ts.map +1 -1
  125. package/dist/cli/file-processor.js +2 -3
  126. package/dist/cli/file-processor.js.map +1 -1
  127. package/dist/config.d.ts.map +1 -1
  128. package/dist/config.js +3 -10
  129. package/dist/config.js.map +1 -1
  130. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  131. package/dist/core/agent-session-runtime.js +2 -1
  132. package/dist/core/agent-session-runtime.js.map +1 -1
  133. package/dist/core/agent-session-services.d.ts.map +1 -1
  134. package/dist/core/agent-session-services.js +3 -2
  135. package/dist/core/agent-session-services.js.map +1 -1
  136. package/dist/core/agent-session.d.ts +6 -0
  137. package/dist/core/agent-session.d.ts.map +1 -1
  138. package/dist/core/agent-session.js +16 -2
  139. package/dist/core/agent-session.js.map +1 -1
  140. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  141. package/dist/core/atomic-guide-command.js +8 -9
  142. package/dist/core/atomic-guide-command.js.map +1 -1
  143. package/dist/core/auth-storage.d.ts.map +1 -1
  144. package/dist/core/auth-storage.js +3 -2
  145. package/dist/core/auth-storage.js.map +1 -1
  146. package/dist/core/bash-executor.d.ts.map +1 -1
  147. package/dist/core/bash-executor.js +2 -1
  148. package/dist/core/bash-executor.js.map +1 -1
  149. package/dist/core/export-html/index.d.ts.map +1 -1
  150. package/dist/core/export-html/index.js +8 -6
  151. package/dist/core/export-html/index.js.map +1 -1
  152. package/dist/core/export-html/template.js +6 -3
  153. package/dist/core/extensions/loader.d.ts.map +1 -1
  154. package/dist/core/extensions/loader.js +12 -29
  155. package/dist/core/extensions/loader.js.map +1 -1
  156. package/dist/core/model-registry.d.ts.map +1 -1
  157. package/dist/core/model-registry.js +5 -1
  158. package/dist/core/model-registry.js.map +1 -1
  159. package/dist/core/package-manager.d.ts +8 -0
  160. package/dist/core/package-manager.d.ts.map +1 -1
  161. package/dist/core/package-manager.js +145 -58
  162. package/dist/core/package-manager.js.map +1 -1
  163. package/dist/core/prompt-templates.d.ts.map +1 -1
  164. package/dist/core/prompt-templates.js +6 -20
  165. package/dist/core/prompt-templates.js.map +1 -1
  166. package/dist/core/resource-loader.d.ts.map +1 -1
  167. package/dist/core/resource-loader.js +38 -31
  168. package/dist/core/resource-loader.js.map +1 -1
  169. package/dist/core/sdk.d.ts.map +1 -1
  170. package/dist/core/sdk.js +9 -4
  171. package/dist/core/sdk.js.map +1 -1
  172. package/dist/core/session-manager.d.ts.map +1 -1
  173. package/dist/core/session-manager.js +32 -24
  174. package/dist/core/session-manager.js.map +1 -1
  175. package/dist/core/settings-manager.d.ts.map +1 -1
  176. package/dist/core/settings-manager.js +8 -15
  177. package/dist/core/settings-manager.js.map +1 -1
  178. package/dist/core/skills.d.ts.map +1 -1
  179. package/dist/core/skills.js +8 -22
  180. package/dist/core/skills.js.map +1 -1
  181. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts +5 -4
  182. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
  183. package/dist/core/tools/ask-user-question/state/questionnaire-session.js +34 -11
  184. package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
  185. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +1 -0
  186. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
  187. package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
  188. package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
  189. package/dist/core/tools/ask-user-question/state/selectors/projections.js +1 -0
  190. package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
  191. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts +1 -2
  192. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
  193. package/dist/core/tools/ask-user-question/state/state-reducer.js +26 -9
  194. package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
  195. package/dist/core/tools/ask-user-question/state/state.d.ts +4 -0
  196. package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
  197. package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
  198. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts +1 -0
  199. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts.map +1 -1
  200. package/dist/core/tools/ask-user-question/view/components/option-list-view.js +1 -0
  201. package/dist/core/tools/ask-user-question/view/components/option-list-view.js.map +1 -1
  202. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts +9 -6
  203. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
  204. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +28 -7
  205. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
  206. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
  207. package/dist/core/tools/ask-user-question/view/props-adapter.js +4 -1
  208. package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
  209. package/dist/core/tools/bash.d.ts.map +1 -1
  210. package/dist/core/tools/bash.js +56 -53
  211. package/dist/core/tools/bash.js.map +1 -1
  212. package/dist/core/tools/edit-diff.d.ts +3 -1
  213. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  214. package/dist/core/tools/edit-diff.js +8 -1
  215. package/dist/core/tools/edit-diff.js.map +1 -1
  216. package/dist/core/tools/edit.d.ts +3 -1
  217. package/dist/core/tools/edit.d.ts.map +1 -1
  218. package/dist/core/tools/edit.js +44 -81
  219. package/dist/core/tools/edit.js.map +1 -1
  220. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  221. package/dist/core/tools/file-mutation-queue.js +27 -12
  222. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  223. package/dist/core/tools/find.d.ts.map +1 -1
  224. package/dist/core/tools/find.js +2 -3
  225. package/dist/core/tools/find.js.map +1 -1
  226. package/dist/core/tools/grep.d.ts.map +1 -1
  227. package/dist/core/tools/grep.js +3 -3
  228. package/dist/core/tools/grep.js.map +1 -1
  229. package/dist/core/tools/ls.d.ts.map +1 -1
  230. package/dist/core/tools/ls.js +5 -5
  231. package/dist/core/tools/ls.js.map +1 -1
  232. package/dist/core/tools/output-accumulator.d.ts +2 -0
  233. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  234. package/dist/core/tools/output-accumulator.js +11 -4
  235. package/dist/core/tools/output-accumulator.js.map +1 -1
  236. package/dist/core/tools/path-utils.d.ts +2 -0
  237. package/dist/core/tools/path-utils.d.ts.map +1 -1
  238. package/dist/core/tools/path-utils.js +39 -21
  239. package/dist/core/tools/path-utils.js.map +1 -1
  240. package/dist/core/tools/read.d.ts.map +1 -1
  241. package/dist/core/tools/read.js +9 -8
  242. package/dist/core/tools/read.js.map +1 -1
  243. package/dist/core/tools/truncate.d.ts.map +1 -1
  244. package/dist/core/tools/truncate.js +12 -2
  245. package/dist/core/tools/truncate.js.map +1 -1
  246. package/dist/core/tools/write.d.ts.map +1 -1
  247. package/dist/core/tools/write.js +20 -35
  248. package/dist/core/tools/write.js.map +1 -1
  249. package/dist/index.d.ts +2 -1
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +4 -1
  252. package/dist/index.js.map +1 -1
  253. package/dist/main.d.ts.map +1 -1
  254. package/dist/main.js +5 -6
  255. package/dist/main.js.map +1 -1
  256. package/dist/modes/interactive/chat-input-actions.d.ts +24 -0
  257. package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -0
  258. package/dist/modes/interactive/chat-input-actions.js +179 -0
  259. package/dist/modes/interactive/chat-input-actions.js.map +1 -0
  260. package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -0
  261. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  262. package/dist/modes/interactive/components/chat-message-renderer.js +14 -3
  263. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  264. package/dist/modes/interactive/components/chat-session-host.d.ts +157 -0
  265. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -0
  266. package/dist/modes/interactive/components/chat-session-host.js +1007 -0
  267. package/dist/modes/interactive/components/chat-session-host.js.map +1 -0
  268. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  269. package/dist/modes/interactive/components/config-selector.js +1 -1
  270. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  271. package/dist/modes/interactive/components/footer.d.ts +1 -0
  272. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  273. package/dist/modes/interactive/components/footer.js +14 -5
  274. package/dist/modes/interactive/components/footer.js.map +1 -1
  275. package/dist/modes/interactive/components/index.d.ts +1 -0
  276. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  277. package/dist/modes/interactive/components/index.js +1 -0
  278. package/dist/modes/interactive/components/index.js.map +1 -1
  279. package/dist/modes/interactive/components/login-dialog.d.ts +9 -1
  280. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/login-dialog.js +29 -4
  282. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  283. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  284. package/dist/modes/interactive/interactive-mode.js +18 -67
  285. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  286. package/dist/utils/child-process.d.ts +1 -0
  287. package/dist/utils/child-process.d.ts.map +1 -1
  288. package/dist/utils/child-process.js +8 -0
  289. package/dist/utils/child-process.js.map +1 -1
  290. package/dist/utils/clipboard-native.d.ts +3 -1
  291. package/dist/utils/clipboard-native.d.ts.map +1 -1
  292. package/dist/utils/clipboard-native.js +14 -8
  293. package/dist/utils/clipboard-native.js.map +1 -1
  294. package/dist/utils/image-resize-core.d.ts +30 -0
  295. package/dist/utils/image-resize-core.d.ts.map +1 -0
  296. package/dist/utils/image-resize-core.js +124 -0
  297. package/dist/utils/image-resize-core.js.map +1 -0
  298. package/dist/utils/image-resize-worker.d.ts +2 -0
  299. package/dist/utils/image-resize-worker.d.ts.map +1 -0
  300. package/dist/utils/image-resize-worker.js +31 -0
  301. package/dist/utils/image-resize-worker.js.map +1 -0
  302. package/dist/utils/image-resize.d.ts +7 -27
  303. package/dist/utils/image-resize.d.ts.map +1 -1
  304. package/dist/utils/image-resize.js +75 -115
  305. package/dist/utils/image-resize.js.map +1 -1
  306. package/dist/utils/paths.d.ts +16 -1
  307. package/dist/utils/paths.d.ts.map +1 -1
  308. package/dist/utils/paths.js +49 -7
  309. package/dist/utils/paths.js.map +1 -1
  310. package/docs/changelog.mdx +29 -0
  311. package/docs/compaction.md +1 -1
  312. package/docs/custom-provider.md +2 -2
  313. package/docs/development.md +1 -1
  314. package/docs/docs.json +98 -143
  315. package/docs/extensions.md +29 -16
  316. package/docs/favicon.svg +29 -0
  317. package/docs/images/interactive-mode.png +0 -0
  318. package/docs/images/tree-view.png +0 -0
  319. package/docs/images/workflow-command.png +0 -0
  320. package/docs/images/workflow-graph.png +0 -0
  321. package/docs/images/workflow-input-picker.png +0 -0
  322. package/docs/images/workflow-list.png +0 -0
  323. package/docs/index.md +10 -1
  324. package/docs/logo.svg +59 -0
  325. package/docs/packages.md +3 -3
  326. package/docs/providers.md +1 -1
  327. package/docs/quickstart.md +98 -2
  328. package/docs/rpc.md +8 -8
  329. package/docs/sdk.md +23 -12
  330. package/docs/sessions.md +1 -1
  331. package/docs/skills.md +15 -1
  332. package/docs/termux.md +11 -1
  333. package/docs/themes.md +6 -6
  334. package/docs/tui.md +18 -18
  335. package/docs/usage.md +1 -1
  336. package/docs/workflows.md +172 -2
  337. package/examples/extensions/subagent/index.ts +2 -1
  338. package/package.json +6 -6
  339. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/SKILL.md +0 -0
  340. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/element-attributes.md +0 -0
  341. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/playwright-tests.md +0 -0
  342. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/request-mocking.md +0 -0
  343. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/running-code.md +0 -0
  344. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/session-management.md +0 -0
  345. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/spec-driven-testing.md +0 -0
  346. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/storage-state.md +0 -0
  347. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/test-generation.md +0 -0
  348. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/tracing.md +0 -0
  349. /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/video-recording.md +0 -0
  350. /package/dist/builtin/{workflows → subagents}/skills/tdd/SKILL.md +0 -0
  351. /package/dist/builtin/{workflows → subagents}/skills/tdd/deep-modules.md +0 -0
  352. /package/dist/builtin/{workflows → subagents}/skills/tdd/interface-design.md +0 -0
  353. /package/dist/builtin/{workflows → subagents}/skills/tdd/mocking.md +0 -0
  354. /package/dist/builtin/{workflows → subagents}/skills/tdd/refactoring.md +0 -0
  355. /package/dist/builtin/{workflows → subagents}/skills/tdd/tests.md +0 -0
@@ -2,14 +2,15 @@ import { execSync } from "node:child_process";
2
2
  import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
- import { CONFIG_DIR_NAME as CONFIG_DIR } from "@bastani/atomic";
5
+ import { getProjectConfigDirs } from "@bastani/atomic";
6
6
  import type { AgentConfig } from "../agents/agents.ts";
7
7
  import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "../shared/types.ts";
8
+ import { getAgentDir } from "../shared/utils.ts";
8
9
 
9
10
  const PI_INTERCOM_PACKAGE_NAME = "pi-intercom";
10
11
 
11
12
  function defaultAgentDir(): string {
12
- return path.join(os.homedir(), CONFIG_DIR, "agent");
13
+ return getAgentDir();
13
14
  }
14
15
 
15
16
  function defaultIntercomExtensionDir(agentDir = defaultAgentDir()): string {
@@ -168,8 +169,9 @@ function packageEntryAllowsExtensions(entry: unknown): boolean {
168
169
  function findNearestProjectConfigDir(cwd: string): string | undefined {
169
170
  let current = path.resolve(cwd);
170
171
  while (true) {
171
- const configDir = path.join(current, CONFIG_DIR);
172
- if (fs.existsSync(path.join(configDir, "settings.json"))) return configDir;
172
+ for (const configDir of getProjectConfigDirs(current)) {
173
+ if (fs.existsSync(path.join(configDir, "settings.json"))) return configDir;
174
+ }
173
175
  const parent = path.dirname(current);
174
176
  if (parent === current) return undefined;
175
177
  current = parent;
@@ -3,6 +3,8 @@ import * as fs from "node:fs";
3
3
  import {
4
4
  type Details,
5
5
  type IntercomEventBus,
6
+ type NestedRunSummary,
7
+ type PublicNestedRunSummary,
6
8
  type SingleResult,
7
9
  type SubagentResultIntercomChild,
8
10
  type SubagentResultIntercomPayload,
@@ -60,6 +62,110 @@ function resolveGroupedStatus(children: SubagentResultIntercomChild[]): Subagent
60
62
  return "failed";
61
63
  }
62
64
 
65
+ function compactNestedRun(run: NestedRunSummary | PublicNestedRunSummary, depth = 0): PublicNestedRunSummary {
66
+ return {
67
+ id: run.id,
68
+ parentRunId: run.parentRunId,
69
+ ...(run.parentStepIndex !== undefined ? { parentStepIndex: run.parentStepIndex } : {}),
70
+ ...(run.parentAgent ? { parentAgent: run.parentAgent } : {}),
71
+ depth: run.depth,
72
+ path: run.path.slice(0, 4).map((part) => ({
73
+ runId: part.runId,
74
+ ...(part.stepIndex !== undefined ? { stepIndex: part.stepIndex } : {}),
75
+ ...(part.agent ? { agent: part.agent } : {}),
76
+ })),
77
+ ...(run.asyncDir ? { asyncDir: run.asyncDir } : {}),
78
+ ...(run.sessionId ? { sessionId: run.sessionId } : {}),
79
+ ...(run.sessionFile ? { sessionFile: run.sessionFile } : {}),
80
+ ...(run.intercomTarget ? { intercomTarget: run.intercomTarget } : {}),
81
+ ...(run.ownerIntercomTarget ? { ownerIntercomTarget: run.ownerIntercomTarget } : {}),
82
+ ...(run.leafIntercomTarget ? { leafIntercomTarget: run.leafIntercomTarget } : {}),
83
+ ...(run.ownerState ? { ownerState: run.ownerState } : {}),
84
+ ...(run.mode ? { mode: run.mode } : {}),
85
+ state: run.state,
86
+ ...(run.agent ? { agent: run.agent } : {}),
87
+ ...(run.agents?.length ? { agents: run.agents.slice(0, 12) } : {}),
88
+ ...(run.currentStep !== undefined ? { currentStep: run.currentStep } : {}),
89
+ ...(run.chainStepCount !== undefined ? { chainStepCount: run.chainStepCount } : {}),
90
+ ...(run.parallelGroups?.length ? { parallelGroups: run.parallelGroups.slice(0, 8) } : {}),
91
+ ...(run.activityState ? { activityState: run.activityState } : {}),
92
+ ...(run.lastActivityAt !== undefined ? { lastActivityAt: run.lastActivityAt } : {}),
93
+ ...(run.currentTool ? { currentTool: run.currentTool } : {}),
94
+ ...(run.currentToolStartedAt !== undefined ? { currentToolStartedAt: run.currentToolStartedAt } : {}),
95
+ ...(run.currentPath ? { currentPath: run.currentPath } : {}),
96
+ ...(run.turnCount !== undefined ? { turnCount: run.turnCount } : {}),
97
+ ...(run.toolCount !== undefined ? { toolCount: run.toolCount } : {}),
98
+ ...(run.totalTokens ? { totalTokens: run.totalTokens } : {}),
99
+ ...(run.startedAt !== undefined ? { startedAt: run.startedAt } : {}),
100
+ ...(run.endedAt !== undefined ? { endedAt: run.endedAt } : {}),
101
+ ...(run.lastUpdate !== undefined ? { lastUpdate: run.lastUpdate } : {}),
102
+ ...(run.error ? { error: run.error } : {}),
103
+ ...(run.steps?.length ? { steps: run.steps.slice(0, 12).map((step) => ({
104
+ agent: step.agent,
105
+ status: step.status,
106
+ ...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
107
+ ...(step.activityState ? { activityState: step.activityState } : {}),
108
+ ...(step.lastActivityAt !== undefined ? { lastActivityAt: step.lastActivityAt } : {}),
109
+ ...(step.currentTool ? { currentTool: step.currentTool } : {}),
110
+ ...(step.currentToolStartedAt !== undefined ? { currentToolStartedAt: step.currentToolStartedAt } : {}),
111
+ ...(step.currentPath ? { currentPath: step.currentPath } : {}),
112
+ ...(step.turnCount !== undefined ? { turnCount: step.turnCount } : {}),
113
+ ...(step.toolCount !== undefined ? { toolCount: step.toolCount } : {}),
114
+ ...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
115
+ ...(step.endedAt !== undefined ? { endedAt: step.endedAt } : {}),
116
+ ...(step.error ? { error: step.error } : {}),
117
+ ...(depth < 2 && step.children?.length ? { children: step.children.slice(0, 8).map((child) => compactNestedRun(child, depth + 1)) } : {}),
118
+ })) } : {}),
119
+ ...(depth < 2 && run.children?.length ? { children: run.children.slice(0, 8).map((child) => compactNestedRun(child, depth + 1)) } : {}),
120
+ };
121
+ }
122
+
123
+ export function compactNestedResultChildren(children: Array<NestedRunSummary | PublicNestedRunSummary> | undefined): PublicNestedRunSummary[] | undefined {
124
+ if (!children?.length) return undefined;
125
+ return children.slice(0, 16).map((child) => compactNestedRun(child));
126
+ }
127
+
128
+ export function attachNestedChildrenToResultChildren(
129
+ runId: string,
130
+ children: SubagentResultIntercomChild[],
131
+ nestedChildren: NestedRunSummary[] | undefined,
132
+ ): SubagentResultIntercomChild[] {
133
+ const compact = compactNestedResultChildren(nestedChildren);
134
+ if (!compact?.length) return children.map((child) => ({ ...child, children: compactNestedResultChildren(child.children) }));
135
+ return children.map((child, index) => {
136
+ const childIndex = child.index ?? index;
137
+ const alreadyAttachedIds = new Set(child.children?.map((nested) => nested.id) ?? []);
138
+ const attached = compact.filter((nested) => nested.parentRunId === runId && nested.parentStepIndex === childIndex && !alreadyAttachedIds.has(nested.id));
139
+ const fallbackAttached = children.length === 1
140
+ ? compact.filter((nested) => nested.parentRunId === runId && nested.parentStepIndex === undefined && !alreadyAttachedIds.has(nested.id))
141
+ : [];
142
+ const merged = compactNestedResultChildren([...(child.children ?? []), ...attached, ...fallbackAttached]);
143
+ return merged?.length ? { ...child, children: merged } : { ...child, children: undefined };
144
+ });
145
+ }
146
+
147
+ function formatNestedResultLines(children: PublicNestedRunSummary[] | undefined): string[] {
148
+ if (!children?.length) return [];
149
+ const lines = ["Nested subagents:"];
150
+ let remaining = 10;
151
+ const append = (runs: PublicNestedRunSummary[] | undefined, indent: string): void => {
152
+ for (const run of runs ?? []) {
153
+ if (remaining <= 0) {
154
+ lines.push(`${indent}↳ +more nested runs; inspect status for full tree`);
155
+ return;
156
+ }
157
+ remaining--;
158
+ const label = run.agent ?? run.agents?.join("+") ?? run.id;
159
+ lines.push(`${indent}↳ ${label} — ${run.state} [${run.id}]`);
160
+ if (run.sessionFile) lines.push(`${indent} Session: ${run.sessionFile}`);
161
+ append(run.children, `${indent} `);
162
+ for (const step of run.steps ?? []) append(step.children, `${indent} `);
163
+ }
164
+ };
165
+ append(children, "");
166
+ return lines;
167
+ }
168
+
63
169
  interface GroupedResultIntercomMessageInput {
64
170
  to: string;
65
171
  runId: string;
@@ -128,6 +234,7 @@ function formatSubagentResultIntercomMessage(input: {
128
234
  if (child.intercomTarget) lines.push(`${input.source === "async" ? "Previous intercom target" : "Run intercom target"}: ${child.intercomTarget}`);
129
235
  if (child.artifactPath) lines.push(`Output artifact: ${child.artifactPath}`);
130
236
  if (child.sessionPath) lines.push(`Session: ${child.sessionPath}`);
237
+ lines.push(...formatNestedResultLines(child.children));
131
238
  lines.push("Summary:");
132
239
  lines.push(child.summary);
133
240
  }
@@ -139,6 +246,7 @@ export function buildSubagentResultIntercomPayload(input: GroupedResultIntercomM
139
246
  const children = input.children.map((child) => ({
140
247
  ...child,
141
248
  summary: child.summary.trim() || "(no output)",
249
+ children: compactNestedResultChildren(child.children),
142
250
  }));
143
251
  const status = resolveGroupedStatus(children);
144
252
  const summary = formatStatusCounts(countStatuses(children));
@@ -169,7 +277,7 @@ export async function deliverSubagentResultIntercomEvent(
169
277
  payload: SubagentResultIntercomPayload,
170
278
  timeoutMs = 500,
171
279
  ): Promise<boolean> {
172
- return deliverSubagentIntercomMessageEvent(events, payload.to, payload.message, timeoutMs, payload);
280
+ return deliverSubagentIntercomMessageEvent(events, payload.to, payload.message, timeoutMs, payload as unknown as Record<string, unknown>);
173
281
  }
174
282
 
175
283
  export async function deliverSubagentIntercomMessageEvent(
@@ -8,10 +8,10 @@ import * as os from "node:os";
8
8
  import * as path from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { createRequire } from "node:module";
11
- import type { ExtensionAPI } from "@bastani/atomic";
11
+ import { APP_NAME, getEnvValue, type ExtensionAPI } from "@bastani/atomic";
12
12
  import type { AgentConfig } from "../../agents/agents.ts";
13
- import { applyThinkingSuffix } from "../shared/pi-args.ts";
14
- import { injectSingleOutputInstruction, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
13
+ import { applyThinkingSuffix, SUBAGENT_INTERCOM_SESSION_NAME_ENV } from "../shared/pi-args.ts";
14
+ import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
15
15
  import { buildChainInstructions, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
16
16
  import type { RunnerStep } from "../shared/parallel-utils.ts";
17
17
  import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
@@ -24,6 +24,7 @@ import {
24
24
  type ArtifactConfig,
25
25
  type Details,
26
26
  type MaxOutputConfig,
27
+ type NestedRouteInfo,
27
28
  type ResolvedControlConfig,
28
29
  type SubagentRunMode,
29
30
  ASYNC_DIR,
@@ -33,6 +34,7 @@ import {
33
34
  getAsyncConfigPath,
34
35
  resolveChildMaxSubagentDepth,
35
36
  } from "../../shared/types.ts";
37
+ import { NESTED_RUNS_DIR, nestedResultsPath, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
36
38
 
37
39
  const require = createRequire(import.meta.url);
38
40
  const piPackageRoot = resolvePiPackageRoot();
@@ -112,6 +114,7 @@ interface AsyncChainParams {
112
114
  controlConfig?: ResolvedControlConfig;
113
115
  controlIntercomTarget?: string;
114
116
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
117
+ nestedRoute?: NestedRouteInfo;
115
118
  }
116
119
 
117
120
  interface AsyncSingleParams {
@@ -127,7 +130,7 @@ interface AsyncSingleParams {
127
130
  sessionRoot?: string;
128
131
  sessionFile?: string;
129
132
  skills?: string[];
130
- output?: string | false;
133
+ output?: string | boolean;
131
134
  outputMode?: "inline" | "file-only";
132
135
  modelOverride?: string;
133
136
  availableModels?: AvailableModelInfo[];
@@ -137,6 +140,7 @@ interface AsyncSingleParams {
137
140
  controlConfig?: ResolvedControlConfig;
138
141
  controlIntercomTarget?: string;
139
142
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
143
+ nestedRoute?: NestedRouteInfo;
140
144
  }
141
145
 
142
146
  interface AsyncExecutionResult {
@@ -165,6 +169,13 @@ export function isAsyncAvailable(): boolean {
165
169
  /**
166
170
  * Spawn the async runner process
167
171
  */
172
+ export function writeAsyncRunnerConfig(cfg: object, suffix: string): string {
173
+ fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
174
+ const cfgPath = getAsyncConfigPath(suffix);
175
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg), { mode: 0o600 });
176
+ return cfgPath;
177
+ }
178
+
168
179
  function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number; error?: string } {
169
180
  if (!jitiCliPath) {
170
181
  return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
@@ -179,9 +190,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
179
190
  return { error: `cwd does not exist: ${cwd}` };
180
191
  }
181
192
 
182
- fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
183
- const cfgPath = getAsyncConfigPath(suffix);
184
- fs.writeFileSync(cfgPath, JSON.stringify(cfg));
193
+ const cfgPath = writeAsyncRunnerConfig(cfg, suffix);
185
194
  const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
186
195
 
187
196
  const proc = spawn(process.execPath, [jitiCliPath, runner, cfgPath], {
@@ -191,7 +200,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
191
200
  windowsHide: true,
192
201
  });
193
202
  proc.on("error", (error) => {
194
- console.error(`[pi-subagents] async spawn failed: ${error.message}`);
203
+ console.error(`[${APP_NAME}-subagents] async spawn failed: ${error.message}`);
195
204
  });
196
205
  if (typeof proc.pid !== "number") {
197
206
  return { error: `async runner did not produce a pid for cwd: ${cwd}` };
@@ -237,6 +246,7 @@ export function executeAsyncChain(
237
246
  controlConfig,
238
247
  controlIntercomTarget,
239
248
  childIntercomTarget,
249
+ nestedRoute,
240
250
  } = params;
241
251
  const resultMode = params.resultMode ?? "chain";
242
252
  const chainSkills = params.chainSkills ?? [];
@@ -262,7 +272,11 @@ export function executeAsyncChain(
262
272
  }
263
273
  }
264
274
 
265
- const asyncDir = path.join(ASYNC_DIR, id);
275
+ const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
276
+ const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
277
+ const asyncDir = inheritedNestedRoute
278
+ ? path.join(NESTED_RUNS_DIR, inheritedNestedRoute.rootRunId, id)
279
+ : path.join(ASYNC_DIR, id);
266
280
  try {
267
281
  fs.mkdirSync(asyncDir, { recursive: true });
268
282
  } catch (error) {
@@ -318,12 +332,13 @@ export function executeAsyncChain(
318
332
  cwd: stepCwd,
319
333
  model,
320
334
  thinking: resolveEffectiveThinking(model, a.thinking),
321
- modelCandidates: buildModelCandidates(behavior.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider, ctx.currentModel).map((candidate) =>
322
- applyThinkingSuffix(candidate, a.thinking),
323
- ),
335
+ modelCandidates: buildModelCandidates(behavior.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider, ctx.currentModel)
336
+ .map((candidate) => applyThinkingSuffix(candidate, a.thinking))
337
+ .filter((candidate): candidate is string => typeof candidate === "string"),
324
338
  tools: a.tools,
325
339
  extensions: a.extensions,
326
340
  mcpDirectTools: a.mcpDirectTools,
341
+ completionGuard: a.completionGuard,
327
342
  systemPrompt,
328
343
  systemPromptMode: a.systemPromptMode,
329
344
  inheritProjectContext: a.inheritProjectContext,
@@ -393,7 +408,7 @@ export function executeAsyncChain(
393
408
  {
394
409
  id,
395
410
  steps,
396
- resultPath: path.join(RESULTS_DIR, `${id}.json`),
411
+ resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(RESULTS_DIR, `${id}.json`),
397
412
  cwd: runnerCwd,
398
413
  placeholder: "{previous}",
399
414
  maxOutput,
@@ -411,6 +426,13 @@ export function executeAsyncChain(
411
426
  controlIntercomTarget,
412
427
  childIntercomTargets,
413
428
  resultMode,
429
+ nestedRoute: nestedRoute ?? inheritedNestedRoute,
430
+ nestedSelf: inheritedNestedRoute && nestedAddress ? {
431
+ parentRunId: nestedAddress.parentRunId,
432
+ parentStepIndex: nestedAddress.parentStepIndex,
433
+ depth: nestedAddress.depth,
434
+ path: nestedAddress.path,
435
+ } : undefined,
414
436
  },
415
437
  id,
416
438
  runnerCwd,
@@ -443,6 +465,40 @@ export function executeAsyncChain(
443
465
  flatStepStart++;
444
466
  }
445
467
  }
468
+ if (inheritedNestedRoute && nestedAddress) {
469
+ const now = Date.now();
470
+ try {
471
+ writeNestedEvent(inheritedNestedRoute, {
472
+ type: "subagent.nested.started",
473
+ ts: now,
474
+ parentRunId: nestedAddress.parentRunId,
475
+ parentStepIndex: nestedAddress.parentStepIndex,
476
+ child: {
477
+ id,
478
+ parentRunId: nestedAddress.parentRunId,
479
+ parentStepIndex: nestedAddress.parentStepIndex,
480
+ depth: nestedAddress.depth,
481
+ path: nestedAddress.path,
482
+ asyncDir,
483
+ pid: spawnResult.pid,
484
+ ownerIntercomTarget: getEnvValue(SUBAGENT_INTERCOM_SESSION_NAME_ENV),
485
+ leafIntercomTarget: childIntercomTargets?.[0],
486
+ intercomTarget: childIntercomTargets?.[0],
487
+ ownerState: "live",
488
+ mode: resultMode,
489
+ state: "running",
490
+ agent: firstAgents[0],
491
+ agents: flatAgents,
492
+ chainStepCount: chain.length,
493
+ parallelGroups,
494
+ startedAt: now,
495
+ lastUpdate: now,
496
+ },
497
+ });
498
+ } catch (error) {
499
+ console.error("Failed to emit nested async start event:", error);
500
+ }
501
+ }
446
502
  ctx.pi.events.emit(SUBAGENT_ASYNC_STARTED_EVENT, {
447
503
  id,
448
504
  pid: spawnResult.pid,
@@ -460,6 +516,7 @@ export function executeAsyncChain(
460
516
  parallelGroups,
461
517
  cwd: runnerCwd,
462
518
  asyncDir,
519
+ nestedRoute,
463
520
  });
464
521
  }
465
522
 
@@ -499,6 +556,7 @@ export function executeAsyncSingle(
499
556
  controlConfig,
500
557
  controlIntercomTarget,
501
558
  childIntercomTarget,
559
+ nestedRoute,
502
560
  } = params;
503
561
  const task = params.task ?? "";
504
562
  const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
@@ -512,7 +570,11 @@ export function executeAsyncSingle(
512
570
  systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
513
571
  }
514
572
 
515
- const asyncDir = path.join(ASYNC_DIR, id);
573
+ const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
574
+ const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
575
+ const asyncDir = inheritedNestedRoute
576
+ ? path.join(NESTED_RUNS_DIR, inheritedNestedRoute.rootRunId, id)
577
+ : path.join(ASYNC_DIR, id);
516
578
  try {
517
579
  fs.mkdirSync(asyncDir, { recursive: true });
518
580
  } catch (error) {
@@ -524,7 +586,8 @@ export function executeAsyncSingle(
524
586
  };
525
587
  }
526
588
 
527
- const outputPath = resolveSingleOutputPath(params.output, ctx.cwd, runnerCwd);
589
+ const effectiveOutput = normalizeSingleOutputOverride(params.output, agentConfig.output);
590
+ const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, runnerCwd);
528
591
  const outputMode = params.outputMode ?? "inline";
529
592
  const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
530
593
  if (validationError) return formatAsyncStartError("single", validationError);
@@ -545,12 +608,13 @@ export function executeAsyncSingle(
545
608
  cwd: runnerCwd,
546
609
  model,
547
610
  thinking: resolveEffectiveThinking(model, agentConfig.thinking),
548
- modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, ctx.currentModel).map((candidate) =>
549
- applyThinkingSuffix(candidate, agentConfig.thinking),
550
- ),
611
+ modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, ctx.currentModel)
612
+ .map((candidate) => applyThinkingSuffix(candidate, agentConfig.thinking))
613
+ .filter((candidate): candidate is string => typeof candidate === "string"),
551
614
  tools: agentConfig.tools,
552
615
  extensions: agentConfig.extensions,
553
616
  mcpDirectTools: agentConfig.mcpDirectTools,
617
+ completionGuard: agentConfig.completionGuard,
554
618
  systemPrompt,
555
619
  systemPromptMode: agentConfig.systemPromptMode,
556
620
  inheritProjectContext: agentConfig.inheritProjectContext,
@@ -562,7 +626,7 @@ export function executeAsyncSingle(
562
626
  maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
563
627
  },
564
628
  ],
565
- resultPath: path.join(RESULTS_DIR, `${id}.json`),
629
+ resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : path.join(RESULTS_DIR, `${id}.json`),
566
630
  cwd: runnerCwd,
567
631
  placeholder: "{previous}",
568
632
  maxOutput,
@@ -580,6 +644,13 @@ export function executeAsyncSingle(
580
644
  controlIntercomTarget,
581
645
  childIntercomTargets: childIntercomTarget ? [childIntercomTarget(agent, 0)] : undefined,
582
646
  resultMode: "single",
647
+ nestedRoute: nestedRoute ?? inheritedNestedRoute,
648
+ nestedSelf: inheritedNestedRoute && nestedAddress ? {
649
+ parentRunId: nestedAddress.parentRunId,
650
+ parentStepIndex: nestedAddress.parentStepIndex,
651
+ depth: nestedAddress.depth,
652
+ path: nestedAddress.path,
653
+ } : undefined,
583
654
  },
584
655
  id,
585
656
  runnerCwd,
@@ -594,6 +665,39 @@ export function executeAsyncSingle(
594
665
  }
595
666
 
596
667
  if (spawnResult.pid) {
668
+ if (inheritedNestedRoute && nestedAddress) {
669
+ const now = Date.now();
670
+ try {
671
+ writeNestedEvent(inheritedNestedRoute, {
672
+ type: "subagent.nested.started",
673
+ ts: now,
674
+ parentRunId: nestedAddress.parentRunId,
675
+ parentStepIndex: nestedAddress.parentStepIndex,
676
+ child: {
677
+ id,
678
+ parentRunId: nestedAddress.parentRunId,
679
+ parentStepIndex: nestedAddress.parentStepIndex,
680
+ depth: nestedAddress.depth,
681
+ path: nestedAddress.path,
682
+ asyncDir,
683
+ pid: spawnResult.pid,
684
+ ownerIntercomTarget: getEnvValue(SUBAGENT_INTERCOM_SESSION_NAME_ENV),
685
+ leafIntercomTarget: childIntercomTarget?.(agent, 0),
686
+ intercomTarget: childIntercomTarget?.(agent, 0),
687
+ ownerState: "live",
688
+ mode: "single",
689
+ state: "running",
690
+ agent,
691
+ agents: [agent],
692
+ chainStepCount: 1,
693
+ startedAt: now,
694
+ lastUpdate: now,
695
+ },
696
+ });
697
+ } catch (error) {
698
+ console.error("Failed to emit nested async start event:", error);
699
+ }
700
+ }
597
701
  ctx.pi.events.emit(SUBAGENT_ASYNC_STARTED_EVENT, {
598
702
  id,
599
703
  pid: spawnResult.pid,
@@ -603,6 +707,7 @@ export function executeAsyncSingle(
603
707
  task: task?.slice(0, 50),
604
708
  cwd: runnerCwd,
605
709
  asyncDir,
710
+ nestedRoute,
606
711
  });
607
712
  }
608
713
 
@@ -15,7 +15,8 @@ import {
15
15
  } from "../../shared/types.ts";
16
16
  import { readStatus } from "../../shared/utils.ts";
17
17
  import { normalizeParallelGroups } from "./parallel-groups.ts";
18
- import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
18
+ import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.ts";
19
+ import { hasLiveNestedDescendants, updateAsyncJobNestedProjection } from "../shared/nested-events.ts";
19
20
 
20
21
  interface AsyncJobTrackerOptions {
21
22
  completionRetentionMs?: number;
@@ -38,9 +39,14 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
38
39
  renderWidget(ctx, jobs);
39
40
  ctx.ui.requestRender?.();
40
41
  };
41
- const scheduleCleanup = (asyncId: string) => {
42
+ const cancelCleanup = (asyncId: string) => {
42
43
  const existingTimer = state.cleanupTimers.get(asyncId);
43
- if (existingTimer) clearTimeout(existingTimer);
44
+ if (!existingTimer) return;
45
+ clearTimeout(existingTimer);
46
+ state.cleanupTimers.delete(asyncId);
47
+ };
48
+ const scheduleCleanup = (asyncId: string) => {
49
+ cancelCleanup(asyncId);
44
50
  const timer = setTimeout(() => {
45
51
  state.cleanupTimers.delete(asyncId);
46
52
  state.asyncJobs.delete(asyncId);
@@ -121,8 +127,27 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
121
127
  let widgetChanged = false;
122
128
  for (const job of state.asyncJobs.values()) {
123
129
  const widgetStateBefore = widgetRenderKey(job);
130
+ let nestedRefreshFailed = false;
131
+ const refreshNestedProjection = () => {
132
+ try {
133
+ updateAsyncJobNestedProjection(job);
134
+ } catch (error) {
135
+ nestedRefreshFailed = true;
136
+ console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
137
+ }
138
+ };
139
+ const reconcileNestedDescendants = () => {
140
+ try {
141
+ if (job.nestedRoute) reconcileNestedAsyncDescendants(job.nestedRoute, { resultsDir, kill: options.kill, now: options.now });
142
+ } catch (error) {
143
+ nestedRefreshFailed = true;
144
+ console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
145
+ }
146
+ refreshNestedProjection();
147
+ };
124
148
  try {
125
149
  emitNewControlEvents(job);
150
+ reconcileNestedDescendants();
126
151
  const reconciliation = reconcileAsyncRun(job.asyncDir, {
127
152
  resultsDir,
128
153
  kill: options.kill,
@@ -143,6 +168,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
143
168
  if (status) {
144
169
  const previousStatus = job.status;
145
170
  job.status = status.state;
171
+ if (job.status !== "complete" && job.status !== "failed" && job.status !== "paused") cancelCleanup(job.asyncId);
146
172
  job.sessionId = status.sessionId ?? job.sessionId;
147
173
  job.activityState = status.activityState;
148
174
  job.lastActivityAt = status.lastActivityAt ?? job.lastActivityAt;
@@ -169,6 +195,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
169
195
  job.activeParallelGroup = Boolean(activeGroup);
170
196
  job.agents = visibleSteps.map((step) => step.agent);
171
197
  job.steps = visibleSteps;
198
+ refreshNestedProjection();
172
199
  job.stepsTotal = visibleSteps.length;
173
200
  job.runningSteps = visibleSteps.filter((step) => step.status === "running").length;
174
201
  job.completedSteps = visibleSteps.filter((step) => step.status === "complete" || step.status === "completed").length;
@@ -178,7 +205,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
178
205
  job.outputFile = status.outputFile ?? job.outputFile;
179
206
  job.totalTokens = status.totalTokens ?? job.totalTokens;
180
207
  job.sessionFile = status.sessionFile ?? job.sessionFile;
181
- if ((job.status === "complete" || job.status === "failed" || job.status === "paused") && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) {
208
+ if ((job.status === "complete" || job.status === "failed" || job.status === "paused") && !nestedRefreshFailed && !hasLiveNestedDescendants(job.nestedChildren) && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) {
182
209
  scheduleCleanup(job.asyncId);
183
210
  }
184
211
  if (widgetRenderKey(job) !== widgetStateBefore) widgetChanged = true;
@@ -194,7 +221,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
194
221
  job.status = "failed";
195
222
  job.updatedAt = Date.now();
196
223
  }
197
- if (!state.cleanupTimers.has(job.asyncId)) {
224
+ if (!hasLiveNestedDescendants(job.nestedChildren) && !state.cleanupTimers.has(job.asyncId)) {
198
225
  scheduleCleanup(job.asyncId);
199
226
  }
200
227
  }
@@ -228,6 +255,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
228
255
  agents,
229
256
  chainStepCount: info.chainStepCount,
230
257
  parallelGroups: validParallelGroups,
258
+ nestedRoute: info.nestedRoute,
231
259
  stepsTotal: firstGroupCount ?? agents?.length,
232
260
  hasParallelGroups: validParallelGroups.length > 0,
233
261
  activeParallelGroup: Boolean(firstGroupCount && firstGroupCount > 0),
@@ -245,15 +273,22 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
245
273
  const asyncId = result.id;
246
274
  if (!asyncId) return;
247
275
  const job = state.asyncJobs.get(asyncId);
276
+ let nestedRefreshFailed = false;
248
277
  if (job) {
249
278
  job.status = result.success ? "complete" : "failed";
250
279
  job.updatedAt = Date.now();
251
280
  if (result.asyncDir) job.asyncDir = result.asyncDir;
281
+ try {
282
+ updateAsyncJobNestedProjection(job);
283
+ } catch (error) {
284
+ nestedRefreshFailed = true;
285
+ console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
286
+ }
252
287
  }
253
288
  if (state.lastUiContext) {
254
289
  rerenderWidget(state.lastUiContext);
255
290
  }
256
- scheduleCleanup(asyncId);
291
+ if (!nestedRefreshFailed && !hasLiveNestedDescendants(job?.nestedChildren)) scheduleCleanup(asyncId);
257
292
  };
258
293
 
259
294
  const resetJobs = (ctx?: ExtensionContext) => {
@@ -149,6 +149,29 @@ function exactResultPath(resultsDir: string, runId: string): string | null {
149
149
  return fs.existsSync(resultPath) ? resultPath : null;
150
150
  }
151
151
 
152
+ export function findAsyncRunPrefixMatches(prefix: string, asyncDirRoot: string, resultsDir: string): Array<{ id: string; location: AsyncRunLocation }> {
153
+ const requestedId = assertRunId(prefix, "id");
154
+ if (!requestedId) return [];
155
+ const asyncRoot = path.resolve(asyncDirRoot);
156
+ const resultRoot = path.resolve(resultsDir);
157
+ const matchingIds = [...new Set([
158
+ ...prefixedRunIds(asyncRoot, requestedId),
159
+ ...prefixedRunIds(resultRoot, requestedId, ".json"),
160
+ ])].sort();
161
+ return matchingIds.map((id) => {
162
+ const asyncDir = path.join(asyncRoot, id);
163
+ assertInsideRoot(asyncRoot, asyncDir, "Async run directory");
164
+ return {
165
+ id,
166
+ location: {
167
+ asyncDir: fs.existsSync(asyncDir) ? asyncDir : null,
168
+ resultPath: exactResultPath(resultRoot, id),
169
+ resolvedId: id,
170
+ },
171
+ };
172
+ });
173
+ }
174
+
152
175
  export function resolveAsyncRunLocation(params: AsyncResumeParams, asyncDirRoot: string, resultsDir: string): AsyncRunLocation {
153
176
  const asyncRoot = path.resolve(asyncDirRoot);
154
177
  const resultRoot = path.resolve(resultsDir);
@@ -175,22 +198,12 @@ export function resolveAsyncRunLocation(params: AsyncResumeParams, asyncDirRoot:
175
198
  };
176
199
  }
177
200
 
178
- const matchingIds = [...new Set([
179
- ...prefixedRunIds(asyncRoot, requestedId),
180
- ...prefixedRunIds(resultRoot, requestedId, ".json"),
181
- ])].sort();
182
- if (matchingIds.length === 0) return { asyncDir: null, resultPath: null, resolvedId: requestedId };
183
- if (matchingIds.length > 1) {
184
- throw new Error(`Ambiguous async run id prefix '${requestedId}' matched: ${matchingIds.join(", ")}. Provide a longer id.`);
201
+ const matching = findAsyncRunPrefixMatches(requestedId, asyncRoot, resultRoot);
202
+ if (matching.length === 0) return { asyncDir: null, resultPath: null, resolvedId: requestedId };
203
+ if (matching.length > 1) {
204
+ throw new Error(`Ambiguous async run id prefix '${requestedId}' matched: ${matching.map((match) => match.id).join(", ")}. Provide a longer id.`);
185
205
  }
186
- const resolvedId = matchingIds[0]!;
187
- const asyncDir = path.join(asyncRoot, resolvedId);
188
- assertInsideRoot(asyncRoot, asyncDir, "Async run directory");
189
- return {
190
- asyncDir: fs.existsSync(asyncDir) ? asyncDir : null,
191
- resultPath: exactResultPath(resultRoot, resolvedId),
192
- resolvedId,
193
- };
206
+ return matching[0]!.location;
194
207
  }
195
208
 
196
209
  function resultState(result: AsyncResultFile): AsyncStatus["state"] {