@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
@@ -9,6 +9,7 @@ import { renderInputsSchema } from "../shared/render-inputs-schema.js";
9
9
  import { WorkflowParametersSchema } from "./workflow-schema.js";
10
10
  import { renderRunBanner, renderRunSummary } from "./renderers.js";
11
11
  import type { RunEndPayload, RunStartPayload } from "./renderers.js";
12
+ import type { StageSnapshot, StageStatus, ToolEvent } from "../shared/store-types.js";
12
13
  import { store } from "../shared/store.js";
13
14
  import { restoreOnSessionStart } from "../shared/persistence-restore.js";
14
15
  import type { SessionManager } from "../shared/persistence-restore.js";
@@ -19,11 +20,13 @@ import {
19
20
  destroyAllRuns,
20
21
  resumeRun,
21
22
  pauseRun,
23
+ pauseAllRuns,
22
24
  interruptRun,
23
25
  interruptAllRuns,
24
26
  inspectRun,
25
27
  } from "../runs/background/status.js";
26
28
  import { cancellationRegistry } from "../runs/background/cancellation-registry.js";
29
+ import { stageControlRegistry } from "../runs/foreground/stage-control-registry.js";
27
30
  import { registerIntercomParentSession } from "../intercom/intercom-bridge.js";
28
31
  import { subscribeIntercomControl } from "../intercom/result-intercom.js";
29
32
  import { buildIntercomCallbacks } from "../intercom/intercom-routing.js";
@@ -205,6 +208,7 @@ export interface PiToolOpts<TArgs, TDetails> {
205
208
  label: string;
206
209
  description: string;
207
210
  parameters: unknown; // TypeBox TSchema — pi consumes it opaquely
211
+ renderShell?: "default" | "self";
208
212
  /**
209
213
  * Pi calls execute positionally: `(toolCallId, params, signal, onUpdate, ctx)`.
210
214
  * cross-ref: pi-coding-agent dist/core/extensions/types.d.ts ToolDefinition.execute
@@ -272,9 +276,8 @@ export interface ExtensionAPI {
272
276
  renderer: (payload: unknown) => string,
273
277
  ) => void;
274
278
  /**
275
- * Inject a custom message into the chat history. Used by the inline
276
- * workflow input form to emit a sticky card under `customType:
277
- * "workflows:input-form"`. The card stays in scrollback and is
279
+ * Inject a custom message into chat history. Used by inline workflow surfaces
280
+ * such as `workflows:input-form`; cards stay in scrollback and are
278
281
  * re-rendered by the registered renderer on every `tui.requestRender()`.
279
282
  */
280
283
  sendMessage?: <T = unknown>(
@@ -397,9 +400,15 @@ export interface WorkflowToolArgs extends StageOptions {
397
400
  | "list"
398
401
  | "get"
399
402
  | "status"
403
+ | "stages"
404
+ | "stage"
405
+ | "transcript"
406
+ | "send"
407
+ | "pause"
400
408
  | "interrupt"
401
409
  | "kill"
402
410
  | "resume"
411
+ | "reload"
403
412
  | "inputs";
404
413
  /** Canonical run identifier or unique prefix for status/interrupt/kill/resume. */
405
414
  runId?: string;
@@ -409,6 +418,16 @@ export interface WorkflowToolArgs extends StageOptions {
409
418
  stageId?: string;
410
419
  /** Optional message forwarded when resuming paused work. */
411
420
  message?: string;
421
+ statusFilter?: StageStatus | "all";
422
+ format?: "text" | "json";
423
+ limit?: number;
424
+ tail?: number;
425
+ includeToolOutput?: boolean;
426
+ text?: string;
427
+ response?: unknown;
428
+ delivery?: "auto" | "answer" | "prompt" | "steer" | "followUp" | "resume";
429
+ promptId?: string;
430
+ reason?: string;
412
431
  /** Direct single-task mode, or root task string when chain is present. */
413
432
  task?: WorkflowDirectTaskItem | string;
414
433
  /** Direct top-level parallel mode. */
@@ -510,6 +529,136 @@ function workflowRunResultFromDetails(
510
529
  };
511
530
  }
512
531
 
532
+ function stringifyWorkflowToolResult(result: WorkflowToolResult): string {
533
+ return JSON.stringify(result, null, 2);
534
+ }
535
+
536
+ function compactWorkflowToolMessage(
537
+ result: Extract<WorkflowToolResult, {
538
+ action: "send" | "pause" | "reload" | "interrupt" | "kill" | "resume";
539
+ }>,
540
+ ): string {
541
+ if (result.action === "reload") {
542
+ return `${result.action}: ${result.status} — ${result.message}`;
543
+ }
544
+ const target = [
545
+ result.runId,
546
+ result.action === "send" ? result.stageId : undefined,
547
+ ].filter((part): part is string => part !== undefined && part.length > 0)
548
+ .join("/");
549
+ return `${result.action}:${target ? ` ${target}` : ""} ${result.status} — ${result.message}`;
550
+ }
551
+
552
+ function renderTranscriptToolContent(
553
+ result: Extract<WorkflowToolResult, { action: "transcript" }>,
554
+ ): string {
555
+ const lines = [
556
+ `action: transcript`,
557
+ `runId: ${result.runId}`,
558
+ `stageId: ${result.stageId}`,
559
+ `source: ${result.source}`,
560
+ `truncated: ${result.truncated}`,
561
+ ];
562
+ if (result.sessionId) lines.push(`sessionId: ${result.sessionId}`);
563
+ if (result.sessionFile) lines.push(`sessionFile: ${result.sessionFile}`);
564
+ if (result.entries.length === 0) {
565
+ lines.push("entries: none");
566
+ return lines.join("\n");
567
+ }
568
+ lines.push("entries:");
569
+ result.entries.forEach((entry, index) => {
570
+ const metadata = [
571
+ `[${index + 1}]`,
572
+ `role=${entry.role}`,
573
+ entry.toolName ? `tool=${entry.toolName}` : undefined,
574
+ entry.timestamp !== undefined ? `timestamp=${entry.timestamp}` : undefined,
575
+ ].filter((part): part is string => part !== undefined);
576
+ lines.push(metadata.join(" "));
577
+ if (entry.text !== undefined) lines.push(entry.text);
578
+ if (entry.output !== undefined) {
579
+ lines.push("tool output:");
580
+ lines.push(entry.output);
581
+ }
582
+ if (entry.text === undefined && entry.output === undefined) {
583
+ lines.push("(no body)");
584
+ }
585
+ });
586
+ return lines.join("\n");
587
+ }
588
+
589
+ function renderStagesToolContent(
590
+ result: Extract<WorkflowToolResult, { action: "stages" }>,
591
+ ): string {
592
+ const lines = [
593
+ "action: stages",
594
+ `runId: ${result.runId}`,
595
+ `filter: ${result.filter}`,
596
+ ];
597
+ if (result.error) lines.push(`error: ${result.error}`);
598
+ if (result.stages.length === 0) {
599
+ lines.push("stages: none");
600
+ return lines.join("\n");
601
+ }
602
+ lines.push("stages:");
603
+ result.stages.forEach((stage, index) => {
604
+ lines.push(`[${index + 1}] ${stage.name} (${stage.id}) ${stage.status}`);
605
+ if (stage.sessionId) lines.push(`sessionId: ${stage.sessionId}`);
606
+ if (stage.sessionFile) lines.push(`sessionFile: ${stage.sessionFile}`);
607
+ if (stage.error) lines.push(`error: ${stage.error}`);
608
+ if (stage.awaitingInputSince !== undefined) {
609
+ lines.push(`awaitingInputSince: ${stage.awaitingInputSince}`);
610
+ }
611
+ if (stage.pendingPrompt !== undefined) {
612
+ lines.push("pendingPrompt:");
613
+ lines.push(JSON.stringify(stage.pendingPrompt, null, 2));
614
+ }
615
+ });
616
+ return lines.join("\n");
617
+ }
618
+
619
+ function renderStageToolContent(
620
+ result: Extract<WorkflowToolResult, { action: "stage" }>,
621
+ ): string {
622
+ const lines = ["action: stage", `runId: ${result.runId}`];
623
+ if (result.error || result.stage === undefined) {
624
+ lines.push(`error: ${result.error ?? "stage not found"}`);
625
+ return lines.join("\n");
626
+ }
627
+ lines.push("stage:");
628
+ lines.push(JSON.stringify(result.stage, null, 2));
629
+ return lines.join("\n");
630
+ }
631
+
632
+ function renderWorkflowToolContent(
633
+ result: WorkflowToolResult,
634
+ args: WorkflowToolArgs,
635
+ ): string {
636
+ if (args.format === "json") return stringifyWorkflowToolResult(result);
637
+
638
+ switch (result.action) {
639
+ case "transcript":
640
+ return renderTranscriptToolContent(result);
641
+ case "stages":
642
+ return renderStagesToolContent(result);
643
+ case "stage":
644
+ return renderStageToolContent(result);
645
+ case "send":
646
+ case "pause":
647
+ case "reload":
648
+ case "interrupt":
649
+ case "kill":
650
+ case "resume":
651
+ return compactWorkflowToolMessage(result);
652
+ case "list":
653
+ case "status":
654
+ case "statusDetail":
655
+ case "inputs":
656
+ case "get":
657
+ case "run":
658
+ return stringifyWorkflowToolResult(result);
659
+ }
660
+ }
661
+
513
662
  function workflowGetResult(
514
663
  runtime: ExtensionRuntime,
515
664
  args: WorkflowToolArgs,
@@ -549,6 +698,243 @@ function workflowGetResult(
549
698
  };
550
699
  }
551
700
 
701
+ // ---------------------------------------------------------------------------
702
+ // Stage tool helpers
703
+ // ---------------------------------------------------------------------------
704
+
705
+ type WorkflowStageSummary = {
706
+ id: string;
707
+ name: string;
708
+ status: StageStatus;
709
+ sessionId?: string;
710
+ sessionFile?: string;
711
+ error?: string;
712
+ awaitingInputSince?: number;
713
+ pendingPrompt?: StageSnapshot["pendingPrompt"];
714
+ };
715
+
716
+ type WorkflowTranscriptEntry = {
717
+ role: string;
718
+ text?: string;
719
+ toolName?: string;
720
+ output?: string;
721
+ timestamp?: number;
722
+ };
723
+
724
+ type MessageContentBlock = { readonly type?: string; readonly text?: string };
725
+ type MessageLike = {
726
+ readonly role?: string;
727
+ readonly content?: string | readonly MessageContentBlock[];
728
+ readonly name?: string;
729
+ readonly toolName?: string;
730
+ readonly timestamp?: number;
731
+ readonly createdAt?: number;
732
+ };
733
+
734
+ function cloneStage(stage: StageSnapshot): StageSnapshot {
735
+ return structuredClone(stage);
736
+ }
737
+
738
+ function summarizeStage(stage: StageSnapshot): WorkflowStageSummary {
739
+ return {
740
+ id: stage.id,
741
+ name: stage.name,
742
+ status: stage.status,
743
+ sessionId: stage.sessionId,
744
+ sessionFile: stage.sessionFile,
745
+ error: stage.error,
746
+ awaitingInputSince: stage.awaitingInputSince,
747
+ pendingPrompt: stage.pendingPrompt === undefined
748
+ ? undefined
749
+ : structuredClone(stage.pendingPrompt),
750
+ };
751
+ }
752
+
753
+ const DEFAULT_TRANSCRIPT_LIMIT = 50;
754
+
755
+ function boundedCount(args: WorkflowToolArgs): number {
756
+ const raw = args.tail ?? args.limit;
757
+ if (raw === undefined) return DEFAULT_TRANSCRIPT_LIMIT;
758
+ if (!Number.isFinite(raw) || raw <= 0) return 0;
759
+ return Math.floor(raw);
760
+ }
761
+
762
+ function applyEntryLimit<T>(
763
+ entries: readonly T[],
764
+ args: WorkflowToolArgs,
765
+ ): { entries: T[]; truncated: boolean } {
766
+ const count = boundedCount(args);
767
+ if (count === 0) {
768
+ return { entries: [], truncated: false };
769
+ }
770
+ if (entries.length <= count) {
771
+ return { entries: [...entries], truncated: false };
772
+ }
773
+ return { entries: entries.slice(entries.length - count), truncated: true };
774
+ }
775
+
776
+ function messageText(content: MessageLike["content"]): string | undefined {
777
+ if (typeof content === "string") return content;
778
+ if (!Array.isArray(content)) return undefined;
779
+ let sawTextBlock = false;
780
+ const text = content
781
+ .map((block) => {
782
+ if (block.type === "text" && typeof block.text === "string") {
783
+ sawTextBlock = true;
784
+ return block.text;
785
+ }
786
+ return "";
787
+ })
788
+ .join("");
789
+ return sawTextBlock ? text : undefined;
790
+ }
791
+
792
+ function transcriptEntryFromMessage(message: MessageLike): WorkflowTranscriptEntry {
793
+ const entry: WorkflowTranscriptEntry = { role: message.role ?? "unknown" };
794
+ const text = messageText(message.content);
795
+ if (text !== undefined) entry.text = text;
796
+ const toolName = message.toolName ?? message.name;
797
+ if (toolName !== undefined) entry.toolName = toolName;
798
+ const timestamp = message.timestamp ?? message.createdAt;
799
+ if (timestamp !== undefined) entry.timestamp = timestamp;
800
+ return entry;
801
+ }
802
+
803
+ function transcriptEntriesFromToolEvents(
804
+ events: readonly ToolEvent[],
805
+ includeOutput: boolean,
806
+ ): WorkflowTranscriptEntry[] {
807
+ return events.map((event) => ({
808
+ role: "tool",
809
+ toolName: event.name,
810
+ output: includeOutput ? event.output : undefined,
811
+ timestamp: event.endedAt ?? event.startedAt,
812
+ }));
813
+ }
814
+
815
+ function hasPayloadProperty(args: WorkflowToolArgs): boolean {
816
+ return (
817
+ args.text !== undefined ||
818
+ args.response !== undefined ||
819
+ args.message !== undefined
820
+ );
821
+ }
822
+
823
+ function promptPayloadFromArgs(args: WorkflowToolArgs): unknown {
824
+ if (args.response !== undefined) return args.response;
825
+ if (args.text !== undefined) return args.text;
826
+ return args.message;
827
+ }
828
+
829
+ function textPayloadFromArgs(args: WorkflowToolArgs): string | undefined {
830
+ if (args.text !== undefined) return args.text;
831
+ if (typeof args.response === "string") {
832
+ return args.response;
833
+ }
834
+ if (args.message !== undefined) return args.message;
835
+ return undefined;
836
+ }
837
+
838
+ type WorkflowSendToolResult = Extract<WorkflowToolResult, { action: "send" }>;
839
+
840
+ function workflowSendResult(
841
+ runId: string,
842
+ stageId: string,
843
+ delivery: WorkflowSendToolResult["delivery"],
844
+ status: WorkflowSendToolResult["status"],
845
+ message: string,
846
+ ): WorkflowSendToolResult {
847
+ return { action: "send", runId, stageId, delivery, status, message };
848
+ }
849
+
850
+ function sortTranscriptEntriesChronologically(
851
+ entries: readonly WorkflowTranscriptEntry[],
852
+ ): WorkflowTranscriptEntry[] {
853
+ return entries
854
+ .map((entry, index) => ({ entry, index }))
855
+ .sort((a, b) => {
856
+ const aTimestamp = a.entry.timestamp;
857
+ const bTimestamp = b.entry.timestamp;
858
+ if (
859
+ typeof aTimestamp === "number" &&
860
+ typeof bTimestamp === "number" &&
861
+ aTimestamp !== bTimestamp
862
+ ) {
863
+ return aTimestamp - bTimestamp;
864
+ }
865
+ return a.index - b.index;
866
+ })
867
+ .map(({ entry }) => entry);
868
+ }
869
+
870
+ function terminalTranscriptEntry(
871
+ role: "assistant" | "notice",
872
+ text: string,
873
+ endedAt: number | undefined,
874
+ ): WorkflowTranscriptEntry {
875
+ const entry: WorkflowTranscriptEntry = { role, text };
876
+ if (endedAt !== undefined) entry.timestamp = endedAt;
877
+ return entry;
878
+ }
879
+
880
+ function snapshotTranscriptEntries(
881
+ snapshot: StageSnapshot | undefined,
882
+ includeOutput: boolean,
883
+ ): WorkflowTranscriptEntry[] {
884
+ if (snapshot === undefined) return [];
885
+ const entries: WorkflowTranscriptEntry[] = [
886
+ ...transcriptEntriesFromToolEvents(snapshot.toolEvents ?? [], includeOutput),
887
+ ];
888
+ if (snapshot.result !== undefined) {
889
+ entries.push(terminalTranscriptEntry("assistant", snapshot.result, snapshot.endedAt));
890
+ }
891
+ if (snapshot.error !== undefined) {
892
+ entries.push(terminalTranscriptEntry("notice", snapshot.error, snapshot.endedAt));
893
+ }
894
+ return sortTranscriptEntriesChronologically(entries);
895
+ }
896
+
897
+ function stageFailureMessage(
898
+ runId: string,
899
+ resultReason: string,
900
+ action: "pause" | "interrupt",
901
+ ): string {
902
+ switch (resultReason) {
903
+ case "not_found":
904
+ return `Run not found: ${runId}`;
905
+ case "already_ended":
906
+ return `Run already ended: ${runId}`;
907
+ case "stage_not_found":
908
+ return `Stage not found for run: ${runId}`;
909
+ default:
910
+ return `No active stages to ${action} for run: ${runId}`;
911
+ }
912
+ }
913
+
914
+ function inFlightRunCount(): number {
915
+ return store.runs().filter((run) => run.endedAt === undefined).length;
916
+ }
917
+
918
+ function reloadBlockedMessage(count = inFlightRunCount()): string {
919
+ return `Reload skipped: ${count} workflow run(s) still in flight. Wait for them to finish, or pause/kill them before reloading workflow resources.`;
920
+ }
921
+
922
+ function allStageConflictMessage(action: "pause" | "interrupt" | "kill"): string {
923
+ return `Cannot ${action} --all with a stageId; omit stageId or target a single run.`;
924
+ }
925
+
926
+ class WorkflowReloadBlockedError extends Error {
927
+ constructor(message: string) {
928
+ super(message);
929
+ this.name = "WorkflowReloadBlockedError";
930
+ }
931
+ }
932
+
933
+ function reloadFailureMessage(error: unknown): string {
934
+ if (error instanceof WorkflowReloadBlockedError) return error.message;
935
+ return `Reload failed: ${error instanceof Error ? error.message : String(error)}`;
936
+ }
937
+
552
938
  // ---------------------------------------------------------------------------
553
939
  // Tool execute — dispatch with real registry for list/inputs/run (Phase E)
554
940
  // + real status/interrupt/resume (Phase D)
@@ -557,6 +943,7 @@ function workflowGetResult(
557
943
  export function makeExecuteWorkflowTool(
558
944
  runtime: ExtensionRuntime | ((ctx: PiExecuteContext) => ExtensionRuntime),
559
945
  getPersistence: () => WorkflowPersistencePort | undefined,
946
+ reloadWorkflowResources: () => Promise<void> | void,
560
947
  ) {
561
948
  return async function executeWorkflowTool(
562
949
  args: WorkflowToolArgs,
@@ -614,15 +1001,322 @@ export function makeExecuteWorkflowTool(
614
1001
  const snapshots = store.runs().filter((r) => r.endedAt === undefined);
615
1002
  return {
616
1003
  action: "status",
617
- snapshots: snapshots.map(
618
- (s) => JSON.parse(JSON.stringify(s)) as typeof s,
619
- ),
1004
+ snapshots: snapshots.map((snapshot) => structuredClone(snapshot)),
1005
+ };
1006
+ }
1007
+
1008
+ case "stages": {
1009
+ const target = resolveToolRunTarget(args, "No active run to inspect.");
1010
+ const filter = args.statusFilter ?? "all";
1011
+ if (target.kind === "all") {
1012
+ return {
1013
+ action: "stages",
1014
+ runId: "--all",
1015
+ filter,
1016
+ stages: [],
1017
+ error: "Stage listing requires a single run.",
1018
+ };
1019
+ }
1020
+ if (target.kind === "ambiguous") {
1021
+ return {
1022
+ action: "stages",
1023
+ runId: target.target,
1024
+ filter,
1025
+ stages: [],
1026
+ error: ambiguousRunMessage(target.target, target.matches),
1027
+ };
1028
+ }
1029
+ if (target.kind === "not_found") {
1030
+ return {
1031
+ action: "stages",
1032
+ runId: target.target,
1033
+ filter,
1034
+ stages: [],
1035
+ error: target.message,
1036
+ };
1037
+ }
1038
+ const run = store.runs().find((r) => r.id === target.runId);
1039
+ const stages = (run?.stages ?? [])
1040
+ .filter((stage) => filter === "all" || stage.status === filter)
1041
+ .map(summarizeStage);
1042
+ return { action: "stages", runId: target.runId, filter, stages };
1043
+ }
1044
+
1045
+ case "stage": {
1046
+ const target = resolveToolRunTarget(args, "No active run to inspect.");
1047
+ if (target.kind === "all") {
1048
+ return {
1049
+ action: "stage",
1050
+ runId: "--all",
1051
+ error: "Stage inspection requires a single run.",
1052
+ };
1053
+ }
1054
+ if (target.kind === "ambiguous") {
1055
+ return {
1056
+ action: "stage",
1057
+ runId: target.target,
1058
+ error: ambiguousRunMessage(target.target, target.matches),
1059
+ };
1060
+ }
1061
+ if (target.kind === "not_found") {
1062
+ return {
1063
+ action: "stage",
1064
+ runId: target.target,
1065
+ error: target.message,
1066
+ };
1067
+ }
1068
+ const stage = resolveToolStageTarget(target.runId, args.stageId);
1069
+ if (!stage.ok || stage.stageId === undefined) {
1070
+ return {
1071
+ action: "stage",
1072
+ runId: target.runId,
1073
+ error: stage.ok
1074
+ ? "Stage id, prefix, or name is required."
1075
+ : stage.message,
1076
+ };
1077
+ }
1078
+ const run = store.runs().find((r) => r.id === target.runId);
1079
+ const snapshot = run?.stages.find((s) => s.id === stage.stageId);
1080
+ return snapshot
1081
+ ? { action: "stage", runId: target.runId, stage: cloneStage(snapshot) }
1082
+ : {
1083
+ action: "stage",
1084
+ runId: target.runId,
1085
+ error: `Stage not found in run ${target.runId.slice(0, 8)}: ${stage.stageId}`,
1086
+ };
1087
+ }
1088
+
1089
+ case "transcript": {
1090
+ const target = resolveToolRunTarget(args, "No active run to inspect.");
1091
+ if (target.kind === "all") {
1092
+ return {
1093
+ action: "transcript",
1094
+ runId: "--all",
1095
+ stageId: "",
1096
+ source: "error",
1097
+ entries: [],
1098
+ truncated: false,
1099
+ };
1100
+ }
1101
+ if (target.kind === "ambiguous") {
1102
+ return {
1103
+ action: "transcript",
1104
+ runId: target.target,
1105
+ stageId: "",
1106
+ source: "error",
1107
+ entries: [
1108
+ { role: "notice", text: ambiguousRunMessage(target.target, target.matches) },
1109
+ ],
1110
+ truncated: false,
1111
+ };
1112
+ }
1113
+ if (target.kind === "not_found") {
1114
+ return {
1115
+ action: "transcript",
1116
+ runId: target.target,
1117
+ stageId: "",
1118
+ source: "error",
1119
+ entries: [{ role: "notice", text: target.message }],
1120
+ truncated: false,
1121
+ };
1122
+ }
1123
+ const stage = resolveToolStageTarget(target.runId, args.stageId);
1124
+ if (!stage.ok || stage.stageId === undefined) {
1125
+ return {
1126
+ action: "transcript",
1127
+ runId: target.runId,
1128
+ stageId: "",
1129
+ source: "error",
1130
+ entries: [
1131
+ {
1132
+ role: "notice",
1133
+ text: stage.ok
1134
+ ? "Stage id, prefix, or name is required."
1135
+ : stage.message,
1136
+ },
1137
+ ],
1138
+ truncated: false,
1139
+ };
1140
+ }
1141
+ const run = store.runs().find((r) => r.id === target.runId);
1142
+ const snapshot = run?.stages.find((s) => s.id === stage.stageId);
1143
+ const liveHandle = stageControlRegistry.get(target.runId, stage.stageId);
1144
+ if (liveHandle !== undefined) {
1145
+ const limited = applyEntryLimit(
1146
+ liveHandle.messages.map((m) => transcriptEntryFromMessage(m as MessageLike)),
1147
+ args,
1148
+ );
1149
+ return {
1150
+ action: "transcript",
1151
+ runId: target.runId,
1152
+ stageId: stage.stageId,
1153
+ source: "live",
1154
+ entries: limited.entries,
1155
+ truncated: limited.truncated,
1156
+ sessionId: liveHandle.sessionId,
1157
+ sessionFile: liveHandle.sessionFile,
1158
+ };
1159
+ }
1160
+ const fallback = snapshotTranscriptEntries(snapshot, args.includeToolOutput === true);
1161
+ const limited = applyEntryLimit(fallback, args);
1162
+ return {
1163
+ action: "transcript",
1164
+ runId: target.runId,
1165
+ stageId: stage.stageId,
1166
+ source: "snapshot",
1167
+ entries: limited.entries,
1168
+ truncated: limited.truncated,
1169
+ sessionId: snapshot?.sessionId,
1170
+ sessionFile: snapshot?.sessionFile,
1171
+ };
1172
+ }
1173
+
1174
+ case "send": {
1175
+ const target = resolveToolRunTarget(args, "No active run to message.");
1176
+ const requestedDelivery = args.delivery ?? "auto";
1177
+ if (target.kind === "all") {
1178
+ return workflowSendResult("--all", "", requestedDelivery, "noop", "Send requires a single run.");
1179
+ }
1180
+ if (target.kind === "ambiguous") {
1181
+ return workflowSendResult(target.target, "", requestedDelivery, "noop", ambiguousRunMessage(target.target, target.matches));
1182
+ }
1183
+ if (target.kind === "not_found") {
1184
+ return workflowSendResult(target.target, "", requestedDelivery, "noop", target.message);
1185
+ }
1186
+ const stage = resolveToolStageTarget(target.runId, args.stageId);
1187
+ if (!stage.ok || stage.stageId === undefined) {
1188
+ return workflowSendResult(
1189
+ target.runId,
1190
+ "",
1191
+ requestedDelivery,
1192
+ "noop",
1193
+ stage.ok ? "Stage id, prefix, or name is required." : stage.message,
1194
+ );
1195
+ }
1196
+ const run = store.runs().find((r) => r.id === target.runId);
1197
+ const snapshot = run?.stages.find((s) => s.id === stage.stageId);
1198
+ const targetsPrompt =
1199
+ requestedDelivery === "answer" ||
1200
+ args.promptId !== undefined ||
1201
+ (requestedDelivery === "auto" && snapshot?.pendingPrompt !== undefined);
1202
+ if (targetsPrompt) {
1203
+ const promptId = args.promptId ?? snapshot?.pendingPrompt?.id;
1204
+ if (promptId === undefined) {
1205
+ return workflowSendResult(target.runId, stage.stageId, "answer", "noop", "No pending prompt to answer.");
1206
+ }
1207
+ if (!hasPayloadProperty(args)) {
1208
+ return workflowSendResult(target.runId, stage.stageId, "answer", "noop", "Send requires text, response, or message.");
1209
+ }
1210
+ const ok = store.resolveStagePendingPrompt(target.runId, stage.stageId, promptId, promptPayloadFromArgs(args));
1211
+ return workflowSendResult(
1212
+ target.runId,
1213
+ stage.stageId,
1214
+ "answer",
1215
+ ok ? "ok" : "noop",
1216
+ ok ? `Answered prompt ${promptId}.` : `No matching pending prompt ${promptId}.`,
1217
+ );
1218
+ }
1219
+ const text = textPayloadFromArgs(args);
1220
+ if (text === undefined) {
1221
+ return workflowSendResult(target.runId, stage.stageId, requestedDelivery, "noop", "Send requires text, response, or message.");
1222
+ }
1223
+ const handle = stageControlRegistry.get(target.runId, stage.stageId);
1224
+ if (handle === undefined) {
1225
+ return workflowSendResult(target.runId, stage.stageId, requestedDelivery, "noop", "No live handle for stage.");
1226
+ }
1227
+ if (requestedDelivery === "resume" || (requestedDelivery === "auto" && handle.status === "paused")) {
1228
+ await handle.resume(text);
1229
+ return workflowSendResult(target.runId, stage.stageId, "resume", "ok", "Resumed stage with message.");
1230
+ }
1231
+ if (requestedDelivery === "steer" || (requestedDelivery === "auto" && handle.isStreaming)) {
1232
+ await handle.steer(text);
1233
+ return workflowSendResult(target.runId, stage.stageId, "steer", "ok", "Steered live stage.");
1234
+ }
1235
+ if (requestedDelivery === "prompt") {
1236
+ await handle.prompt(text);
1237
+ return workflowSendResult(target.runId, stage.stageId, "prompt", "ok", "Prompt sent to stage.");
1238
+ }
1239
+ await handle.followUp(text);
1240
+ return workflowSendResult(target.runId, stage.stageId, "followUp", "ok", "Follow-up queued for stage.");
1241
+ }
1242
+
1243
+ case "pause": {
1244
+ const target = resolveToolRunTarget(args, "No in-flight runs to pause.");
1245
+ if (target.kind === "all") {
1246
+ if (args.stageId !== undefined && args.stageId.length > 0) {
1247
+ return {
1248
+ action,
1249
+ runId: "--all",
1250
+ status: "noop",
1251
+ message: allStageConflictMessage("pause"),
1252
+ };
1253
+ }
1254
+ const results = pauseAllRuns();
1255
+ const paused = results.filter((r) => r.ok).length;
1256
+ return {
1257
+ action,
1258
+ runId: "--all",
1259
+ status: paused > 0 ? "paused" : "noop",
1260
+ message: paused > 0
1261
+ ? `Paused ${paused} run(s).`
1262
+ : "No in-flight runs to pause.",
1263
+ };
1264
+ }
1265
+ if (target.kind === "ambiguous") return { action, runId: target.target, status: "noop", message: ambiguousRunMessage(target.target, target.matches) };
1266
+ if (target.kind === "not_found") return { action, runId: target.target, status: "noop", message: target.message };
1267
+ const stage = resolveToolStageTarget(target.runId, args.stageId);
1268
+ if (!stage.ok) return { action, runId: target.runId, status: "noop", message: stage.message };
1269
+ const result = pauseRun(target.runId, { stageId: stage.stageId });
1270
+ return result.ok
1271
+ ? { action, runId: result.runId, status: "paused", message: `Paused ${result.paused.length} stage(s) on run ${result.runId.slice(0, 8)}.` }
1272
+ : {
1273
+ action,
1274
+ runId: target.runId,
1275
+ status: "noop",
1276
+ message: stageFailureMessage(target.runId, result.reason, "pause"),
1277
+ };
1278
+ }
1279
+
1280
+ case "reload": {
1281
+ // Fast UX check; reloadWorkflowResourcesNow re-checks inside the
1282
+ // serialized reload queue and remains the authoritative TOCTOU guard.
1283
+ const activeRuns = inFlightRunCount();
1284
+ if (activeRuns > 0) {
1285
+ return {
1286
+ action: "reload",
1287
+ status: "noop",
1288
+ message: reloadBlockedMessage(activeRuns),
1289
+ };
1290
+ }
1291
+ try {
1292
+ await reloadWorkflowResources();
1293
+ } catch (error) {
1294
+ return {
1295
+ action: "reload",
1296
+ status: "noop",
1297
+ message: reloadFailureMessage(error),
1298
+ };
1299
+ }
1300
+ return {
1301
+ action: "reload",
1302
+ status: "ok",
1303
+ message: args.reason?.trim()
1304
+ ? `Reloaded workflow resources (${args.reason.trim()}).`
1305
+ : "Reloaded workflow resources.",
620
1306
  };
621
1307
  }
622
1308
 
623
1309
  case "kill": {
624
1310
  const target = resolveToolRunTarget(args, "No in-flight runs to kill.");
625
1311
  if (target.kind === "all") {
1312
+ if (args.stageId !== undefined && args.stageId.length > 0) {
1313
+ return {
1314
+ action,
1315
+ runId: "--all",
1316
+ status: "noop",
1317
+ message: allStageConflictMessage("kill"),
1318
+ };
1319
+ }
626
1320
  const results = destroyAllRuns({
627
1321
  cancellation: cancellationRegistry,
628
1322
  persistence: getPersistence(),
@@ -668,6 +1362,14 @@ export function makeExecuteWorkflowTool(
668
1362
  // Interrupt is resumable: it pauses live work and keeps runs in history/status.
669
1363
  const target = resolveToolRunTarget(args, "No in-flight runs to interrupt.");
670
1364
  if (target.kind === "all") {
1365
+ if (args.stageId !== undefined && args.stageId.length > 0) {
1366
+ return {
1367
+ action,
1368
+ runId: "--all",
1369
+ status: "noop",
1370
+ message: allStageConflictMessage("interrupt"),
1371
+ };
1372
+ }
671
1373
  const results = interruptAllRuns();
672
1374
  const interrupted = results.filter((r) => r.ok).length;
673
1375
  return {
@@ -686,27 +1388,26 @@ export function makeExecuteWorkflowTool(
686
1388
  if (target.kind === "not_found") {
687
1389
  return { action, runId: target.target, status: "noop", message: target.message };
688
1390
  }
689
- const result = interruptRun(target.runId);
1391
+ const stage = resolveToolStageTarget(target.runId, args.stageId);
1392
+ if (!stage.ok) {
1393
+ return { action, runId: target.runId, status: "noop", message: stage.message };
1394
+ }
1395
+ const result = interruptRun(target.runId, { stageId: stage.stageId });
690
1396
  if (result.ok) {
691
1397
  return {
692
1398
  action,
693
1399
  runId: result.runId,
694
1400
  status: "paused",
695
- message: `Run ${result.runId} interrupted and can be resumed.`,
1401
+ message: stage.stageId
1402
+ ? `Stage ${stage.stageId} interrupted on run ${result.runId} and can be resumed.`
1403
+ : `Run ${result.runId} interrupted and can be resumed.`,
696
1404
  };
697
1405
  }
698
1406
  return {
699
1407
  action,
700
1408
  runId: target.runId,
701
1409
  status: "noop",
702
- message:
703
- result.reason === "not_found"
704
- ? `Run not found: ${target.runId}`
705
- : result.reason === "already_ended"
706
- ? `Run already ended: ${target.runId}`
707
- : result.reason === "stage_not_found"
708
- ? `Stage not found for run: ${target.runId}`
709
- : `No active stages to interrupt for run: ${target.runId}`,
1410
+ message: stageFailureMessage(target.runId, result.reason, "interrupt"),
710
1411
  };
711
1412
  }
712
1413
 
@@ -729,13 +1430,22 @@ export function makeExecuteWorkflowTool(
729
1430
  const isPaused =
730
1431
  run?.status === "paused" ||
731
1432
  (run?.stages.some((s) => s.status === "paused") ?? false);
1433
+ if (!isPaused && run?.status === "failed" && run.endedAt !== undefined && run.resumable !== false) {
1434
+ const continuation = activeRuntime.resumeFailedRun(target.runId, stage.stageId);
1435
+ return {
1436
+ action: "resume",
1437
+ runId: continuation.ok ? continuation.runId : target.runId,
1438
+ status: continuation.ok ? "running" : "noop",
1439
+ message: continuation.message,
1440
+ };
1441
+ }
732
1442
  const result = resumeRun(target.runId, { stageId: stage.stageId, message: args.message });
733
1443
  if (result.ok) {
734
- const message = isPaused
1444
+ const message = result.message ?? (isPaused
735
1445
  ? result.resumed.length === 0
736
1446
  ? `No paused stages on run ${result.runId.slice(0, 8)}.`
737
1447
  : `Resumed ${result.resumed.length} stage(s) on run ${result.runId.slice(0, 8)}${args.message ? ` with message: "${args.message}"` : ""}.`
738
- : `Snapshot available: run ${result.runId} (${result.snapshot.name}) — status: ${result.snapshot.status}, stages: ${result.snapshot.stages.length}`;
1448
+ : `Snapshot available: run ${result.runId} (${result.snapshot.name}) — status: ${result.snapshot.status}, stages: ${result.snapshot.stages.length}`);
739
1449
  return {
740
1450
  action: "resume",
741
1451
  runId: result.runId,
@@ -946,16 +1656,34 @@ type ToolStageTarget =
946
1656
  | { ok: true; stageId?: string }
947
1657
  | { ok: false; message: string };
948
1658
 
949
- function resolveToolStageTarget(runId: string, stageTarget?: string): ToolStageTarget {
1659
+ function stageMatchesIdentifier(stage: { readonly id: string; readonly name: string }, target: string): boolean {
1660
+ return stage.id === target || stage.name === target || stage.id.startsWith(target);
1661
+ }
1662
+
1663
+ function stageMatchLabel(stage: { readonly id: string; readonly name: string }): string {
1664
+ return `${stage.name} (${stage.id.slice(0, 12)})`;
1665
+ }
1666
+
1667
+ function resolveStageTarget(runId: string, stageTarget?: string): ToolStageTarget {
950
1668
  const target = stageTarget?.trim();
951
1669
  if (!target) return { ok: true };
952
1670
 
953
1671
  const run = store.runs().find((r) => r.id === runId);
954
- const stage = run?.stages.find(
955
- (s) => s.id === target || s.id.startsWith(target) || s.name === target,
956
- );
957
- if (!stage) return { ok: false, message: `Stage not found in run ${runId.slice(0, 8)}: ${target}` };
958
- return { ok: true, stageId: stage.id };
1672
+ const exactId = run?.stages.find((stage) => stage.id === target);
1673
+ if (exactId !== undefined) return { ok: true, stageId: exactId.id };
1674
+
1675
+ const exactNames = run?.stages.filter((stage) => stage.name === target) ?? [];
1676
+ if (exactNames.length === 1) return { ok: true, stageId: exactNames[0]!.id };
1677
+ if (exactNames.length > 1) return { ok: false, message: `Ambiguous stage identifier "${target}" matches: ${exactNames.map(stageMatchLabel).join(", ")}` };
1678
+
1679
+ const matches = run?.stages.filter((stage) => stageMatchesIdentifier(stage, target)) ?? [];
1680
+ if (matches.length === 0) return { ok: false, message: `Stage not found in run ${runId.slice(0, 8)}: ${target}` };
1681
+ if (matches.length > 1) return { ok: false, message: `Ambiguous stage identifier "${target}" matches: ${matches.map(stageMatchLabel).join(", ")}` };
1682
+ return { ok: true, stageId: matches[0]!.id };
1683
+ }
1684
+
1685
+ function resolveToolStageTarget(runId: string, stageTarget?: string): ToolStageTarget {
1686
+ return resolveStageTarget(runId, stageTarget);
959
1687
  }
960
1688
 
961
1689
  function ambiguousRunMessage(target: string, matches: readonly string[]): string {
@@ -1255,6 +1983,9 @@ function factory(pi: ExtensionAPI): void {
1255
1983
  runDirect(args) {
1256
1984
  return runtimeRef.current.runDirect(args);
1257
1985
  },
1986
+ resumeFailedRun(sourceRunId, stageId) {
1987
+ return runtimeRef.current.resumeFailedRun(sourceRunId, stageId);
1988
+ },
1258
1989
  };
1259
1990
 
1260
1991
  function modelFullId(model: PiRuntimeModel): string {
@@ -1314,10 +2045,84 @@ function factory(pi: ExtensionAPI): void {
1314
2045
  }
1315
2046
 
1316
2047
  let intercomControlUnsubscribe: (() => void) | null = null;
2048
+ let workflowReloadQueue: Promise<void> = Promise.resolve();
2049
+
2050
+ async function reloadWorkflowResources(options?: { allowInFlight?: boolean }): Promise<void> {
2051
+ const reload = workflowReloadQueue.then(() => reloadWorkflowResourcesNow(options));
2052
+ workflowReloadQueue = reload.catch(() => {});
2053
+ await reload;
2054
+ }
2055
+
2056
+ async function reloadWorkflowResourcesNow(options?: { allowInFlight?: boolean }): Promise<void> {
2057
+ const activeRuns = inFlightRunCount();
2058
+ if (options?.allowInFlight !== true) {
2059
+ if (activeRuns > 0) {
2060
+ throw new WorkflowReloadBlockedError(reloadBlockedMessage(activeRuns));
2061
+ }
2062
+ } else if (activeRuns > 0 && process.env.ATOMIC_WORKFLOW_DEBUG === "1") {
2063
+ console.warn(
2064
+ `Workflow reload bypassed in-flight guard with ${activeRuns} active run(s).`,
2065
+ );
2066
+ }
2067
+
2068
+ const configResult = await loadWorkflowConfig();
2069
+ configLoadRef.current = configResult;
2070
+
2071
+ // Build scope-aware DiscoveryConfig: global entries → globalWorkflows (resolved
2072
+ // under <homeDir>/.atomic/agent), project entries → projectWorkflows (resolved under
2073
+ // projectRoot). Project keys override global keys. Paths pre-resolved to absolute.
2074
+ const { homedir } = await import("node:os");
2075
+ const hasGlobal = configResult.globalConfig != null;
2076
+ const hasProject = configResult.projectConfig != null;
2077
+ const discoveryConfig =
2078
+ hasGlobal || hasProject
2079
+ ? toScopedDiscoveryConfig(
2080
+ configResult.globalConfig ?? null,
2081
+ configResult.projectConfig ?? null,
2082
+ { projectRoot: process.cwd(), homeDir: homedir() },
2083
+ )
2084
+ : undefined;
2085
+
2086
+ const packageWorkflowPaths = (pi.getWorkflowResources?.() ?? [])
2087
+ .filter((resource) => resource.enabled !== false)
2088
+ .map((resource) => resource.path);
2089
+ const result = await discoverWorkflows({ config: discoveryConfig, packageWorkflowPaths });
2090
+ discoveryRef.current = result;
2091
+
2092
+ // Resolve effective config (fills in all defaults) and build WorkflowRuntimeConfig.
2093
+ const effectiveConfig = withWorkflowDefaults(configResult.config ?? {});
2094
+ runtimeConfigRef.current = {
2095
+ maxDepth: effectiveConfig.maxDepth,
2096
+ defaultConcurrency: effectiveConfig.defaultConcurrency,
2097
+ persistRuns: effectiveConfig.persistRuns,
2098
+ statusFile: effectiveConfig.statusFile,
2099
+ resumeInFlight: effectiveConfig.resumeInFlight,
2100
+ };
2101
+
2102
+ // Replace status writer with one that reflects the resolved config.
2103
+ // Unsubscribe the prior (no-op) writer before creating the new one.
2104
+ statusWriterRef.unsubscribe();
2105
+ statusWriterRef = createStatusWriter(store, runtimeConfigRef.current);
2106
+
2107
+ persistenceRef.current = makePersistencePort(
2108
+ pi,
2109
+ effectiveConfig.persistRuns,
2110
+ );
2111
+ runtimeRef.current = createExtensionRuntime({
2112
+ registry: result.registry,
2113
+ adapters,
2114
+ cancellation: cancellationRegistry,
2115
+ persistence: persistenceRef.current,
2116
+ mcp: mcpPort,
2117
+ intercom: intercomPort,
2118
+ config: runtimeConfigRef.current,
2119
+ });
2120
+ }
1317
2121
 
1318
2122
  const executeWorkflowTool = makeExecuteWorkflowTool(
1319
2123
  (ctx) => runtimeForContext(ctx),
1320
2124
  () => persistenceRef.current,
2125
+ reloadWorkflowResources,
1321
2126
  );
1322
2127
  let storeWidgetUnsubscribe: (() => void) | null = null;
1323
2128
 
@@ -1327,59 +2132,7 @@ function factory(pi: ExtensionAPI): void {
1327
2132
  // Load startup config before discovery so workflow paths and tunables are applied.
1328
2133
  const discoveryPromise = pi.disableAsyncDiscovery
1329
2134
  ? Promise.resolve()
1330
- : loadWorkflowConfig().then(async (configResult) => {
1331
- configLoadRef.current = configResult;
1332
-
1333
- // Build scope-aware DiscoveryConfig: global entries → globalWorkflows (resolved
1334
- // under <homeDir>/.atomic/agent), project entries → projectWorkflows (resolved under
1335
- // projectRoot). Project keys override global keys. Paths pre-resolved to absolute.
1336
- const { homedir } = await import("node:os");
1337
- const hasGlobal = configResult.globalConfig != null;
1338
- const hasProject = configResult.projectConfig != null;
1339
- const discoveryConfig =
1340
- hasGlobal || hasProject
1341
- ? toScopedDiscoveryConfig(
1342
- configResult.globalConfig ?? null,
1343
- configResult.projectConfig ?? null,
1344
- { projectRoot: process.cwd(), homeDir: homedir() },
1345
- )
1346
- : undefined;
1347
-
1348
- const packageWorkflowPaths = (pi.getWorkflowResources?.() ?? [])
1349
- .filter((resource) => resource.enabled !== false)
1350
- .map((resource) => resource.path);
1351
- const result = await discoverWorkflows({ config: discoveryConfig, packageWorkflowPaths });
1352
- discoveryRef.current = result;
1353
-
1354
- // Resolve effective config (fills in all defaults) and build WorkflowRuntimeConfig.
1355
- const effectiveConfig = withWorkflowDefaults(configResult.config ?? {});
1356
- runtimeConfigRef.current = {
1357
- maxDepth: effectiveConfig.maxDepth,
1358
- defaultConcurrency: effectiveConfig.defaultConcurrency,
1359
- persistRuns: effectiveConfig.persistRuns,
1360
- statusFile: effectiveConfig.statusFile,
1361
- resumeInFlight: effectiveConfig.resumeInFlight,
1362
- };
1363
-
1364
- // Replace status writer with one that reflects the resolved config.
1365
- // Unsubscribe the prior (no-op) writer before creating the new one.
1366
- statusWriterRef.unsubscribe();
1367
- statusWriterRef = createStatusWriter(store, runtimeConfigRef.current);
1368
-
1369
- persistenceRef.current = makePersistencePort(
1370
- pi,
1371
- effectiveConfig.persistRuns,
1372
- );
1373
- runtimeRef.current = createExtensionRuntime({
1374
- registry: result.registry,
1375
- adapters,
1376
- cancellation: cancellationRegistry,
1377
- persistence: persistenceRef.current,
1378
- mcp: mcpPort,
1379
- intercom: intercomPort,
1380
- config: runtimeConfigRef.current,
1381
- });
1382
- });
2135
+ : reloadWorkflowResources({ allowInFlight: true });
1383
2136
 
1384
2137
  // -------------------------------------------------------------------------
1385
2138
  // 1. Register the `workflow` tool
@@ -1395,12 +2148,13 @@ function factory(pi: ExtensionAPI): void {
1395
2148
  label: "workflow",
1396
2149
  description: "Run a defined multi-stage workflow by name.",
1397
2150
  parameters: workflowParameters,
2151
+ renderShell: "self",
1398
2152
  execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => {
1399
2153
  // Overlay is opt-in via F2 / ctrl+h; do not auto-open from a
1400
2154
  // tool-call dispatch path.
1401
2155
  const details = await executeWorkflowTool(params, ctx);
1402
2156
  return {
1403
- content: [{ type: "text", text: renderResult(details, {}) }],
2157
+ content: [{ type: "text", text: renderWorkflowToolContent(details, params) }],
1404
2158
  details,
1405
2159
  };
1406
2160
  },
@@ -1850,22 +2604,20 @@ function factory(pi: ExtensionAPI): void {
1850
2604
  }
1851
2605
  let stageId: string | undefined;
1852
2606
  const run = store.runs().find((r) => r.id === runId);
1853
- if (stageTarget) {
1854
- const stage = run?.stages.find(
1855
- (s) =>
1856
- s.id === stageTarget ||
1857
- s.id.startsWith(stageTarget) ||
1858
- s.name === stageTarget,
1859
- );
1860
- if (!stage) {
1861
- print(`Stage not found in run ${runId.slice(0, 8)}: ${stageTarget}`);
1862
- return true;
1863
- }
1864
- stageId = stage.id;
2607
+ const resolvedStage = resolveStageTarget(runId, stageTarget);
2608
+ if (!resolvedStage.ok) {
2609
+ print(resolvedStage.message);
2610
+ return true;
1865
2611
  }
2612
+ stageId = resolvedStage.stageId;
1866
2613
  const isPaused =
1867
2614
  run?.status === "paused" ||
1868
2615
  (run?.stages.some((s) => s.status === "paused") ?? false);
2616
+ if (!isPaused && run?.status === "failed" && run.endedAt !== undefined && run.resumable !== false) {
2617
+ const continuation = runtimeForContext(ctx).resumeFailedRun(runId, stageId);
2618
+ print(continuation.message);
2619
+ return true;
2620
+ }
1869
2621
  const result = resumeRun(runId, { stageId, message });
1870
2622
  if (!result.ok) {
1871
2623
  print(`Run not found: ${runId.slice(0, 8)}`);
@@ -1875,7 +2627,7 @@ function factory(pi: ExtensionAPI): void {
1875
2627
  // Non-paused fallback: reopen the orchestrator overlay as before.
1876
2628
  overlay.open(result.runId, overlaySurfaceFromContext(ctx));
1877
2629
  print(
1878
- `Snapshot available: run ${result.runId} (${result.snapshot.name}) \u2014 status: ${result.snapshot.status}, stages: ${result.snapshot.stages.length}`,
2630
+ result.message ?? `Snapshot available: run ${result.runId} (${result.snapshot.name}) \u2014 status: ${result.snapshot.status}, stages: ${result.snapshot.stages.length}`,
1879
2631
  );
1880
2632
  return true;
1881
2633
  }
@@ -1901,7 +2653,7 @@ function factory(pi: ExtensionAPI): void {
1901
2653
  "workflow",
1902
2654
  {
1903
2655
  description:
1904
- "Run or inspect pi workflows. Usage: /workflow <name> [key=value…] | /workflow [list|status|connect|attach|interrupt|kill|pause|resume|inputs] [args]",
2656
+ "Run or inspect pi workflows. Usage: /workflow <name> [key=value…] | /workflow [list|status|connect|attach|interrupt|kill|pause|resume|inputs|reload] [args]",
1905
2657
  handler: async (args: string, ctx: PiCommandContext) => {
1906
2658
  const print = (msg: string): void => ctx.ui.notify(msg, "info");
1907
2659
  // Quote-aware split so `prompt="map the codebase"` stays a single
@@ -1989,6 +2741,26 @@ function factory(pi: ExtensionAPI): void {
1989
2741
  return;
1990
2742
  }
1991
2743
 
2744
+ // -----------------------------------------------------------------------
2745
+ // reload — refresh workflow resources in-process when no workflows are
2746
+ // currently running. Reload swaps runtime/persistence wiring, so doing it
2747
+ // mid-flight would split active runs across old and new resources.
2748
+ // -----------------------------------------------------------------------
2749
+ if (subcommand === "reload") {
2750
+ const activeRuns = inFlightRunCount();
2751
+ if (activeRuns > 0) {
2752
+ print(reloadBlockedMessage(activeRuns));
2753
+ return;
2754
+ }
2755
+ try {
2756
+ await reloadWorkflowResources();
2757
+ print("Reloaded workflow resources.");
2758
+ } catch (error) {
2759
+ print(reloadFailureMessage(error));
2760
+ }
2761
+ return;
2762
+ }
2763
+
1992
2764
  // -----------------------------------------------------------------------
1993
2765
  // interrupt — top-level chat fast path (no confirmation overlay).
1994
2766
  // -----------------------------------------------------------------------
@@ -2180,7 +2952,6 @@ function factory(pi: ExtensionAPI): void {
2180
2952
  });
2181
2953
  }
2182
2954
  if (pickerResult.kind === "cancel") {
2183
- print(`Cancelled. /workflow ${workflowName} not started.`);
2184
2955
  return;
2185
2956
  }
2186
2957
  if (pickerResult.kind === "run") {
@@ -2315,6 +3086,11 @@ function factory(pi: ExtensionAPI): void {
2315
3086
  label: "inputs",
2316
3087
  description: "Show a workflow's input schema",
2317
3088
  },
3089
+ {
3090
+ value: "reload ",
3091
+ label: "reload",
3092
+ description: "Reload workflow resources",
3093
+ },
2318
3094
  ];
2319
3095
 
2320
3096
  const parts = partial.trim().split(/\s+/).filter(Boolean);