@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
@@ -33,7 +33,14 @@ import type {
33
33
  WorkflowModelCatalogPort,
34
34
  } from "../../shared/types.js";
35
35
  import type { InternalStageContext, StageAdapters } from "./stage-runner.js";
36
- import type { RunStatus, StageNotice, StageSnapshot, RunSnapshot, WorkflowOverlayAdapter } from "../../shared/store-types.js";
36
+ import type {
37
+ RunStatus,
38
+ StageNotice,
39
+ StageSnapshot,
40
+ RunSnapshot,
41
+ WorkflowOverlayAdapter,
42
+ WorkflowFailureKind,
43
+ } from "../../shared/store-types.js";
37
44
  import type { StageControlHandle, StageControlRegistry, AgentSessionEventListener } from "./stage-control-registry.js";
38
45
  import type { Store } from "../../shared/store.js";
39
46
  import type { CancellationRegistry } from "../background/cancellation-registry.js";
@@ -59,9 +66,16 @@ import {
59
66
  appendRunEnd,
60
67
  } from "../../shared/persistence-session-entries.js";
61
68
  import { validateWorkflowModels } from "../shared/model-fallback.js";
69
+ import type { WorkflowFailure } from "../../shared/workflow-failures.js";
70
+ import { classifyWorkflowFailure } from "../../shared/workflow-failures.js";
62
71
 
63
72
  export interface ResolvedInputs extends Record<string, unknown> {}
64
73
 
74
+ export interface RunContinuationOpts {
75
+ readonly source: RunSnapshot;
76
+ readonly resumeFromStageId: string;
77
+ }
78
+
65
79
  export interface RunOpts {
66
80
  adapters?: StageAdapters;
67
81
  /** HIL adapter injected by the pi runtime or test harness. */
@@ -113,6 +127,8 @@ export interface RunOpts {
113
127
  * the runId before starting the background promise.
114
128
  */
115
129
  runId?: string;
130
+ /** Replay completed stages from a failed source run, then resume at this stage. */
131
+ continuation?: RunContinuationOpts;
116
132
  onRunStart?: (snapshot: RunSnapshot) => void;
117
133
  onStageStart?: (runId: string, snapshot: StageSnapshot) => void;
118
134
  onStageEnd?: (runId: string, snapshot: StageSnapshot) => void;
@@ -519,16 +535,22 @@ async function mapParallelSteps<T>(
519
535
  concurrency: number | undefined,
520
536
  failFast: boolean | undefined,
521
537
  mapper: (step: WorkflowTaskStep) => Promise<T>,
538
+ onFirstFailure?: (error: unknown) => void,
522
539
  ): Promise<T[]> {
523
540
  const limit = positiveConcurrency(concurrency) ?? steps.length;
541
+ const failFastEnabled = failFast !== false;
524
542
  const results = new Array<T>(steps.length);
525
543
  const failures: Array<{ readonly index: number; readonly error: unknown }> = [];
526
544
  let nextIndex = 0;
527
545
  let firstFailure: unknown;
546
+ let rejectFirstFailure: (reason: unknown) => void = () => {};
547
+ const firstFailurePromise = new Promise<never>((_, reject) => {
548
+ rejectFirstFailure = reject;
549
+ });
528
550
 
529
551
  async function worker(): Promise<void> {
530
552
  while (true) {
531
- if (failFast !== false && firstFailure !== undefined) return;
553
+ if (failFastEnabled && firstFailure !== undefined) return;
532
554
  const index = nextIndex;
533
555
  nextIndex += 1;
534
556
  const step = steps[index];
@@ -537,15 +559,29 @@ async function mapParallelSteps<T>(
537
559
  results[index] = await mapper(step);
538
560
  } catch (err) {
539
561
  failures.push({ index, error: err });
540
- firstFailure ??= err;
541
- if (failFast !== false) throw err;
562
+ if (firstFailure === undefined) {
563
+ firstFailure = err;
564
+ onFirstFailure?.(err);
565
+ if (failFastEnabled) rejectFirstFailure(err);
566
+ }
567
+ if (failFastEnabled) return;
542
568
  }
543
569
  }
544
570
  }
545
571
 
546
- await Promise.all(
547
- Array.from({ length: Math.min(limit, steps.length) }, () => worker()),
548
- );
572
+ const workers = Array.from({ length: Math.min(limit, steps.length) }, () => worker());
573
+ const allWorkers = Promise.all(workers);
574
+
575
+ if (!failFastEnabled) {
576
+ await allWorkers;
577
+ } else {
578
+ try {
579
+ await Promise.race([allWorkers, firstFailurePromise]);
580
+ } catch (err) {
581
+ void allWorkers.catch(() => {});
582
+ throw err;
583
+ }
584
+ }
549
585
 
550
586
  if (failures.length > 0) {
551
587
  throw new AggregateError(
@@ -723,6 +759,10 @@ async function writeDirectOutput(
723
759
  };
724
760
  }
725
761
 
762
+ function directFailureMessage(error: unknown): string {
763
+ return classifyWorkflowFailure(error).userMessage;
764
+ }
765
+
726
766
  function failedDirectDetails(
727
767
  mode: WorkflowDetails["mode"],
728
768
  runId: string,
@@ -738,7 +778,7 @@ function failedDirectDetails(
738
778
  ...(options.context !== undefined ? { context: options.context } : {}),
739
779
  results: [],
740
780
  progress: { completed: 0, total },
741
- error: error instanceof Error ? error.message : String(error),
781
+ error: directFailureMessage(error),
742
782
  };
743
783
  }
744
784
 
@@ -1013,6 +1053,11 @@ function appendRunEndWhenRecorded(
1013
1053
  readonly runId: string;
1014
1054
  readonly status: RunStatus;
1015
1055
  readonly result?: Record<string, unknown>;
1056
+ readonly error?: string;
1057
+ readonly failureKind?: WorkflowFailureKind;
1058
+ readonly failureMessage?: string;
1059
+ readonly failedStageId?: string;
1060
+ readonly resumable?: boolean;
1016
1061
  readonly ts: number;
1017
1062
  },
1018
1063
  ): void {
@@ -1020,6 +1065,103 @@ function appendRunEndWhenRecorded(
1020
1065
  appendRunEnd(persistence, payload);
1021
1066
  }
1022
1067
 
1068
+ interface RunFailureMetadata {
1069
+ readonly errorMessage: string;
1070
+ readonly failureKind: WorkflowFailureKind;
1071
+ readonly failureMessage: string;
1072
+ readonly failedStageId?: string;
1073
+ readonly resumable: boolean;
1074
+ }
1075
+
1076
+ function applyFailureToStage(stage: StageSnapshot, failure: WorkflowFailure): void {
1077
+ stage.status = "failed";
1078
+ stage.error = failure.userMessage;
1079
+ stage.failureKind = failure.kind;
1080
+ stage.failureMessage = failure.message;
1081
+ }
1082
+
1083
+ function runFailureMetadata(err: unknown, stages: readonly StageSnapshot[]): RunFailureMetadata {
1084
+ const classified = classifyWorkflowFailure(err);
1085
+ const failedStage = stages.find((stage) => stage.status === "failed");
1086
+ const failureKind = failedStage?.failureKind ?? classified.kind;
1087
+
1088
+ return {
1089
+ errorMessage: classified.userMessage,
1090
+ failureKind,
1091
+ failureMessage: failedStage?.failureMessage ?? classified.message,
1092
+ ...(failedStage !== undefined ? { failedStageId: failedStage.id } : {}),
1093
+ resumable: classified.resumable,
1094
+ };
1095
+ }
1096
+
1097
+ type ContinuationReplayDecision =
1098
+ | { readonly kind: "execute"; readonly source?: StageSnapshot }
1099
+ | { readonly kind: "replay"; readonly source: StageSnapshot };
1100
+
1101
+ interface ContinuationReplayIndex {
1102
+ decide(stageName: string, parentIds: readonly string[], stageId: string): ContinuationReplayDecision;
1103
+ }
1104
+
1105
+ function sameStringSet(left: readonly string[], right: readonly string[]): boolean {
1106
+ if (left.length !== right.length) return false;
1107
+ const rightSet = new Set(right);
1108
+ return left.every((value) => rightSet.has(value));
1109
+ }
1110
+
1111
+ function createContinuationReplayIndex(continuation: RunContinuationOpts | undefined): ContinuationReplayIndex {
1112
+ if (continuation === undefined) return { decide: () => ({ kind: "execute" }) };
1113
+ const resumeStage = continuation.source.stages.find((stage) => stage.id === continuation.resumeFromStageId);
1114
+ if (resumeStage === undefined) {
1115
+ throw new Error(`pi-workflows: insufficient_state: resume stage ${continuation.resumeFromStageId} was not found in source run ${continuation.source.id}`);
1116
+ }
1117
+
1118
+ const stagesByName = new Map<string, StageSnapshot[]>();
1119
+ for (const stage of continuation.source.stages) {
1120
+ const stages = stagesByName.get(stage.name);
1121
+ if (stages === undefined) {
1122
+ stagesByName.set(stage.name, [stage]);
1123
+ } else {
1124
+ stages.push(stage);
1125
+ }
1126
+ }
1127
+
1128
+ const consumedSourceStageIds = new Set<string>();
1129
+ const sourceStageIdByContinuationStageId = new Map<string, string>();
1130
+ return {
1131
+ decide(stageName: string, parentIds: readonly string[], stageId: string): ContinuationReplayDecision {
1132
+ const candidates = stagesByName.get(stageName)?.filter((stage) => !consumedSourceStageIds.has(stage.id)) ?? [];
1133
+ if (candidates.length === 0) return { kind: "execute" };
1134
+
1135
+ const translatedParentIds = parentIds.map((parentId) => sourceStageIdByContinuationStageId.get(parentId));
1136
+ const hasUnmappedParent = translatedParentIds.some((parentId) => parentId === undefined);
1137
+ const matches = hasUnmappedParent
1138
+ ? []
1139
+ : candidates.filter((stage) => sameStringSet(translatedParentIds as string[], stage.parentIds));
1140
+
1141
+ if (matches.length !== 1) {
1142
+ const reason = matches.length === 0 ? "mismatch" : "ambiguous";
1143
+ throw new Error(`pi-workflows: insufficient_state: replay topology ${reason} for stage "${stageName}" in source run ${continuation.source.id}`);
1144
+ }
1145
+
1146
+ const source = matches[0]!;
1147
+ consumedSourceStageIds.add(source.id);
1148
+ sourceStageIdByContinuationStageId.set(stageId, source.id);
1149
+ if (source.status === "completed") return { kind: "replay", source };
1150
+ return { kind: "execute", source };
1151
+ },
1152
+ };
1153
+ }
1154
+
1155
+ interface ParallelFailFastStage {
1156
+ readonly skip: () => void;
1157
+ }
1158
+
1159
+ interface ParallelFailFastScope {
1160
+ failed: boolean;
1161
+ firstFailure?: unknown;
1162
+ readonly activeStages: Map<string, ParallelFailFastStage>;
1163
+ }
1164
+
1023
1165
  // ---------------------------------------------------------------------------
1024
1166
  // Shared killed finalizer — used for catch-abort and post-body abort check
1025
1167
  // ---------------------------------------------------------------------------
@@ -1031,17 +1173,25 @@ function finalizeKilled(
1031
1173
  persistence: WorkflowPersistencePort | undefined,
1032
1174
  onRunEnd: RunOpts["onRunEnd"],
1033
1175
  ): RunResult {
1034
- const recorded = activeStore.recordRunEnd(runId, "killed", undefined, "workflow killed");
1035
- onRunEnd?.(runId, "killed", undefined, "workflow killed");
1176
+ const errorMessage = "workflow killed";
1177
+ const metadata = {
1178
+ failureKind: "cancelled" as const,
1179
+ failureMessage: errorMessage,
1180
+ resumable: false,
1181
+ };
1182
+ const recorded = activeStore.recordRunEnd(runId, "killed", undefined, errorMessage, metadata);
1183
+ onRunEnd?.(runId, "killed", undefined, errorMessage);
1036
1184
  appendRunEndWhenRecorded(persistence, recorded, {
1037
1185
  runId,
1038
1186
  status: "killed",
1187
+ error: errorMessage,
1188
+ ...metadata,
1039
1189
  ts: Date.now(),
1040
1190
  });
1041
1191
  return {
1042
1192
  runId,
1043
1193
  status: "killed",
1044
- error: "workflow killed",
1194
+ error: errorMessage,
1045
1195
  stages: [...runSnapshot.stages],
1046
1196
  };
1047
1197
  }
@@ -1080,6 +1230,7 @@ export async function run<TInputs extends Record<string, unknown>>(
1080
1230
 
1081
1231
  // 2. Generate runId (or use pre-allocated seam from caller)
1082
1232
  const runId = opts.runId ?? crypto.randomUUID();
1233
+ const replayIndex = createContinuationReplayIndex(opts.continuation);
1083
1234
 
1084
1235
  // 2a. Create own AbortController; forward caller signal if provided
1085
1236
  const ownController = new AbortController();
@@ -1100,6 +1251,10 @@ export async function run<TInputs extends Record<string, unknown>>(
1100
1251
  status: "running",
1101
1252
  stages: [],
1102
1253
  startedAt: Date.now(),
1254
+ ...(opts.continuation !== undefined ? {
1255
+ resumedFromRunId: opts.continuation.source.id,
1256
+ resumeFromStageId: opts.continuation.resumeFromStageId,
1257
+ } : {}),
1103
1258
  };
1104
1259
 
1105
1260
  activeStore.recordRunStart(runSnapshot);
@@ -1120,6 +1275,8 @@ export async function run<TInputs extends Record<string, unknown>>(
1120
1275
  runId,
1121
1276
  name: def.name,
1122
1277
  inputs: resolvedInputs,
1278
+ ...(runSnapshot.resumedFromRunId !== undefined ? { resumedFromRunId: runSnapshot.resumedFromRunId } : {}),
1279
+ ...(runSnapshot.resumeFromStageId !== undefined ? { resumeFromStageId: runSnapshot.resumeFromStageId } : {}),
1123
1280
  ts: runSnapshot.startedAt,
1124
1281
  });
1125
1282
  }
@@ -1147,7 +1304,7 @@ export async function run<TInputs extends Record<string, unknown>>(
1147
1304
  };
1148
1305
 
1149
1306
  const isTerminalStage = (stage: StageSnapshot): boolean =>
1150
- stage.status === "completed" || stage.status === "failed";
1307
+ stage.status === "completed" || stage.status === "failed" || stage.status === "skipped";
1151
1308
 
1152
1309
  const stageById = (stageId: string): StageSnapshot | undefined =>
1153
1310
  runSnapshot.stages.find((stage) => stage.id === stageId);
@@ -1280,7 +1437,7 @@ export async function run<TInputs extends Record<string, unknown>>(
1280
1437
  inputs: resolvedInputs as TInputs,
1281
1438
  ui: opts.ui ?? makeUnavailableUIContext(),
1282
1439
 
1283
- stage(name: string, options?: StageOptions) {
1440
+ stage(name: string, options?: StageOptions, stageFailFastScope?: ParallelFailFastScope) {
1284
1441
  // a. Generate stageId
1285
1442
  const stageId = crypto.randomUUID();
1286
1443
 
@@ -1288,12 +1445,24 @@ export async function run<TInputs extends Record<string, unknown>>(
1288
1445
  const parentIds = tracker.onSpawn(stageId, name);
1289
1446
 
1290
1447
  // c. Create StageSnapshot as "pending"
1448
+ const replayDecision = replayIndex.decide(name, parentIds, stageId);
1449
+ const replaySource = replayDecision.kind === "replay" ? replayDecision.source : undefined;
1450
+ const shouldReplay = replaySource !== undefined;
1451
+
1291
1452
  const stageSnapshot: StageSnapshot = {
1292
1453
  id: stageId,
1293
1454
  name,
1294
- status: "pending",
1455
+ status: shouldReplay ? "completed" : "pending",
1295
1456
  parentIds: Object.freeze(parentIds),
1296
1457
  toolEvents: [],
1458
+ ...(shouldReplay ? {
1459
+ startedAt: Date.now(),
1460
+ endedAt: Date.now(),
1461
+ durationMs: 0,
1462
+ ...(replaySource.result !== undefined ? { result: replaySource.result } : {}),
1463
+ replayedFromStageId: replaySource.id,
1464
+ replayed: true,
1465
+ } : {}),
1297
1466
  // Store mcp scope options on snapshot when provided
1298
1467
  ...(options?.mcp !== undefined
1299
1468
  ? { mcpScope: { allow: options.mcp.allow ?? null, deny: options.mcp.deny ?? null } }
@@ -1301,9 +1470,87 @@ export async function run<TInputs extends Record<string, unknown>>(
1301
1470
  // Mark attachable up-front: the live stage handle is registered
1302
1471
  // below before the first onStageStart fires, so consumers that
1303
1472
  // hook onStageStart see `attachable: true` for the pending stage.
1304
- attachable: true,
1473
+ attachable: !shouldReplay,
1305
1474
  };
1306
1475
 
1476
+ let stageStartEntryAppended = false;
1477
+ const appendStageStartOnce = (): void => {
1478
+ if (!opts.persistence || stageStartEntryAppended) return;
1479
+ stageStartEntryAppended = true;
1480
+ appendStageStart(opts.persistence, {
1481
+ runId,
1482
+ stageId,
1483
+ name,
1484
+ parentIds: stageSnapshot.parentIds,
1485
+ ...(stageSnapshot.replayedFromStageId !== undefined ? { replayedFromStageId: stageSnapshot.replayedFromStageId } : {}),
1486
+ ...(stageSnapshot.replayed !== undefined ? { replayed: stageSnapshot.replayed } : {}),
1487
+ ts: stageSnapshot.startedAt ?? Date.now(),
1488
+ });
1489
+ };
1490
+
1491
+ if (shouldReplay) {
1492
+ activeStore.recordStageStart(runId, stageSnapshot);
1493
+ opts.onStageStart?.(runId, stageSnapshot);
1494
+ appendStageStartOnce();
1495
+ let replayFinalized = false;
1496
+ const finalizeReplayStage = (): void => {
1497
+ if (replayFinalized) return;
1498
+ replayFinalized = true;
1499
+ activeStore.recordStageEnd(runId, stageSnapshot);
1500
+ opts.onStageEnd?.(runId, stageSnapshot);
1501
+ if (opts.persistence) {
1502
+ appendStageEnd(opts.persistence, {
1503
+ runId,
1504
+ stageId,
1505
+ status: "completed",
1506
+ durationMs: 0,
1507
+ ...(stageSnapshot.result !== undefined ? { summary: stageSnapshot.result } : {}),
1508
+ replayedFromStageId: replaySource.id,
1509
+ replayed: true,
1510
+ });
1511
+ }
1512
+ tracker.onSettle(stageId);
1513
+ };
1514
+ const replayResult = replaySource.result ?? "";
1515
+ const replayText = async (): Promise<string> => {
1516
+ await Promise.resolve();
1517
+ finalizeReplayStage();
1518
+ return replayResult;
1519
+ };
1520
+ const rejectReplayMutation = (action: string): never => {
1521
+ throw new Error(`pi-workflows: replayed stage "${name}" cannot ${action}`);
1522
+ };
1523
+ const replayContext: StageContext & Pick<InternalStageContext, "__modelFallbackMeta"> = {
1524
+ name,
1525
+ prompt: replayText,
1526
+ complete: replayText,
1527
+ steer: async () => rejectReplayMutation("steer"),
1528
+ followUp: async () => rejectReplayMutation("follow up"),
1529
+ subscribe: () => () => {},
1530
+ get sessionFile() { return replaySource.sessionFile; },
1531
+ get sessionId() { return replaySource.sessionId ?? ""; },
1532
+ setModel: async () => rejectReplayMutation("set model"),
1533
+ setThinkingLevel: () => rejectReplayMutation("set thinking level"),
1534
+ cycleModel: async () => rejectReplayMutation("cycle model"),
1535
+ cycleThinkingLevel: () => rejectReplayMutation("cycle thinking level"),
1536
+ get agent() { return undefined as never; },
1537
+ get model() { return replaySource.model as never; },
1538
+ get thinkingLevel() { return undefined as never; },
1539
+ get messages() { return [] as never; },
1540
+ get isStreaming() { return false; },
1541
+ navigateTree: async () => rejectReplayMutation("navigate conversation tree"),
1542
+ compact: async () => rejectReplayMutation("compact"),
1543
+ abortCompaction: () => rejectReplayMutation("abort compaction"),
1544
+ abort: async () => rejectReplayMutation("abort"),
1545
+ __modelFallbackMeta: () => ({
1546
+ ...(replaySource.model !== undefined ? { model: replaySource.model } : {}),
1547
+ ...(replaySource.attemptedModels !== undefined ? { attemptedModels: replaySource.attemptedModels } : {}),
1548
+ ...(replaySource.modelAttempts !== undefined ? { modelAttempts: replaySource.modelAttempts } : {}),
1549
+ }),
1550
+ };
1551
+ return replayContext;
1552
+ }
1553
+
1307
1554
  // d. Create inner AgentSession-like StageContext (raw, without lifecycle wrapping).
1308
1555
  // Must come before the registry registration because the handle
1309
1556
  // delegates to it for every operation.
@@ -1349,6 +1596,47 @@ export async function run<TInputs extends Record<string, unknown>>(
1349
1596
  unregisterStageHandle();
1350
1597
  await disposeInnerContext();
1351
1598
  };
1599
+ const hasQueuedLiveWork = (): boolean =>
1600
+ innerCtx.isStreaming || innerCtx.__pendingMessageCount() > 0 || activeAskUserQuestionCalls.size > 0;
1601
+ const releaseLiveHandleWhenIdle = async (): Promise<void> => {
1602
+ dropStageControlHandle();
1603
+ if (!hasQueuedLiveWork()) {
1604
+ await releaseLiveHandle();
1605
+ return;
1606
+ }
1607
+
1608
+ // The queued-work branch installs asynchronous cleanup and returns once
1609
+ // the release watcher is armed. Inner-context events normally trigger
1610
+ // the subscription when streaming/pending-message counters change, but
1611
+ // SDK prompt/tool cleanup can also drain after the stage has stopped
1612
+ // emitting workflow-visible events. The unref'd 250 ms interval is a
1613
+ // fallback for that silent drain path and is cleared as soon as the
1614
+ // handle becomes idle.
1615
+ let unsubscribe = (): void => {};
1616
+ let pollTimer: ReturnType<typeof setInterval> | undefined;
1617
+ const cleanupWatcher = (): void => {
1618
+ unsubscribe();
1619
+ if (pollTimer !== undefined) {
1620
+ clearInterval(pollTimer);
1621
+ pollTimer = undefined;
1622
+ }
1623
+ };
1624
+ const releaseIfIdle = (): void => {
1625
+ if (liveHandleReleased) {
1626
+ cleanupWatcher();
1627
+ return;
1628
+ }
1629
+ if (hasQueuedLiveWork()) return;
1630
+ cleanupWatcher();
1631
+ void releaseLiveHandle().catch((error: unknown) => {
1632
+ console.debug("pi-workflows: failed to release idle stage handle", error);
1633
+ });
1634
+ };
1635
+ unsubscribe = innerCtx.subscribe(() => queueMicrotask(releaseIfIdle));
1636
+ pollTimer = setInterval(releaseIfIdle, 250);
1637
+ pollTimer.unref?.();
1638
+ releaseIfIdle();
1639
+ };
1352
1640
 
1353
1641
  // e. Register a live stage-control handle so attached panes can
1354
1642
  // prompt/steer/pause/resume the underlying Pi session lazily.
@@ -1372,6 +1660,9 @@ export async function run<TInputs extends Record<string, unknown>>(
1372
1660
  get isStreaming() {
1373
1661
  return innerCtx.isStreaming;
1374
1662
  },
1663
+ get isDisposed() {
1664
+ return liveHandleReleased;
1665
+ },
1375
1666
  get messages() {
1376
1667
  return innerCtx.messages;
1377
1668
  },
@@ -1421,6 +1712,59 @@ export async function run<TInputs extends Record<string, unknown>>(
1421
1712
  return innerCtx.subscribe(listener);
1422
1713
  },
1423
1714
  };
1715
+ let stageFinalized = false;
1716
+ const finalizeStageSnapshot = (): boolean => {
1717
+ if (stageFinalized) return false;
1718
+ stageFinalized = true;
1719
+ stageSnapshot.endedAt = Date.now();
1720
+ stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
1721
+
1722
+ const finalModelMeta = innerCtx.__modelFallbackMeta();
1723
+ if (finalModelMeta.model !== undefined) stageSnapshot.model = finalModelMeta.model;
1724
+ if (finalModelMeta.attemptedModels !== undefined) stageSnapshot.attemptedModels = finalModelMeta.attemptedModels;
1725
+ if (finalModelMeta.modelAttempts !== undefined) stageSnapshot.modelAttempts = finalModelMeta.modelAttempts;
1726
+
1727
+ activeStore.recordStageEnd(runId, stageSnapshot);
1728
+ opts.onStageEnd?.(runId, stageSnapshot);
1729
+
1730
+ if (opts.persistence) {
1731
+ appendStageStartOnce();
1732
+ appendStageEnd(opts.persistence, {
1733
+ runId,
1734
+ stageId,
1735
+ status: stageSnapshot.status,
1736
+ durationMs: stageSnapshot.durationMs,
1737
+ ...(stageSnapshot.error !== undefined ? { error: stageSnapshot.error } : {}),
1738
+ ...(stageSnapshot.failureKind !== undefined ? { failureKind: stageSnapshot.failureKind } : {}),
1739
+ ...(stageSnapshot.failureMessage !== undefined ? { failureMessage: stageSnapshot.failureMessage } : {}),
1740
+ ...(stageSnapshot.skippedReason !== undefined ? { skippedReason: stageSnapshot.skippedReason } : {}),
1741
+ ...(stageSnapshot.result !== undefined && stageSnapshot.status === "completed" ? { summary: stageSnapshot.result } : {}),
1742
+ ...(stageSnapshot.replayedFromStageId !== undefined ? { replayedFromStageId: stageSnapshot.replayedFromStageId } : {}),
1743
+ ...(stageSnapshot.replayed !== undefined ? { replayed: stageSnapshot.replayed } : {}),
1744
+ });
1745
+ }
1746
+
1747
+ stageFailFastScope?.activeStages.delete(stageId);
1748
+ tracker.onSettle(stageId);
1749
+ return true;
1750
+ };
1751
+ let skippedForParallelFailFast = false;
1752
+ const markSkippedForParallelFailFast = (): void => {
1753
+ skippedForParallelFailFast = true;
1754
+ stageSnapshot.status = "skipped";
1755
+ stageSnapshot.skippedReason = "fail-fast";
1756
+ };
1757
+ const parallelFailFastError = (): unknown =>
1758
+ stageFailFastScope?.firstFailure ?? new Error("pi-workflows: skipped after parallel fail-fast");
1759
+ const skipForParallelFailFast = (): void => {
1760
+ if (isTerminalStage(stageSnapshot)) return;
1761
+ markSkippedForParallelFailFast();
1762
+ finalizeStageSnapshot();
1763
+ void innerCtx.abort().catch(() => {});
1764
+ void releaseLiveHandleWhenIdle().catch(() => {});
1765
+ };
1766
+ stageFailFastScope?.activeStages.set(stageId, { skip: skipForParallelFailFast });
1767
+
1424
1768
  let stageControlDropped = false;
1425
1769
  dropStageControlHandle = (): void => {
1426
1770
  if (stageControlDropped) return;
@@ -1454,12 +1798,18 @@ export async function run<TInputs extends Record<string, unknown>>(
1454
1798
 
1455
1799
  const runTrackedStageCall = async (call: () => Promise<string>): Promise<string> => {
1456
1800
  await waitForStageRelease();
1801
+ if (stageFinalized) {
1802
+ throw parallelFailFastError();
1803
+ }
1457
1804
 
1458
1805
  // Block here until a concurrency slot is available for this run.
1459
1806
  await limiter.acquire();
1460
1807
 
1461
1808
  try {
1462
1809
  await waitForStageRelease();
1810
+ if (stageFinalized) {
1811
+ throw parallelFailFastError();
1812
+ }
1463
1813
  } catch (err) {
1464
1814
  limiter.release();
1465
1815
  throw err;
@@ -1470,15 +1820,7 @@ export async function run<TInputs extends Record<string, unknown>>(
1470
1820
  activeStore.recordStageStart(runId, stageSnapshot);
1471
1821
 
1472
1822
  // Persistence: append stage.start entry
1473
- if (opts.persistence) {
1474
- appendStageStart(opts.persistence, {
1475
- runId,
1476
- stageId,
1477
- name,
1478
- parentIds: stageSnapshot.parentIds,
1479
- ts: stageSnapshot.startedAt,
1480
- });
1481
- }
1823
+ appendStageStartOnce();
1482
1824
 
1483
1825
  const mcpAllow = options?.mcp?.allow ?? null;
1484
1826
  const mcpDeny = options?.mcp?.deny ?? null;
@@ -1513,6 +1855,13 @@ export async function run<TInputs extends Record<string, unknown>>(
1513
1855
  if (modelMeta.attemptedModels !== undefined) stageSnapshot.attemptedModels = modelMeta.attemptedModels;
1514
1856
  if (modelMeta.modelAttempts !== undefined) stageSnapshot.modelAttempts = modelMeta.modelAttempts;
1515
1857
  }
1858
+ if (stageFailFastScope?.failed === true && stageFailFastScope.activeStages.has(stageId)) {
1859
+ markSkippedForParallelFailFast();
1860
+ throw parallelFailFastError();
1861
+ }
1862
+ if (stageFinalized) {
1863
+ throw parallelFailFastError();
1864
+ }
1516
1865
  stageSnapshot.status = "completed";
1517
1866
  const assistantText = innerCtx.__getLastAssistantText();
1518
1867
  if (assistantText !== undefined) {
@@ -1520,77 +1869,24 @@ export async function run<TInputs extends Record<string, unknown>>(
1520
1869
  }
1521
1870
  return result;
1522
1871
  } catch (err) {
1523
- if (!ownController.signal.aborted) {
1524
- stageSnapshot.status = "failed";
1525
- stageSnapshot.error = err instanceof Error ? err.message : String(err);
1872
+ if (!ownController.signal.aborted && !skippedForParallelFailFast) {
1873
+ applyFailureToStage(stageSnapshot, classifyWorkflowFailure(err));
1526
1874
  }
1527
1875
  throw err;
1528
1876
  } finally {
1529
- stageSnapshot.endedAt = Date.now();
1530
- stageSnapshot.durationMs = elapsedStageMs(stageSnapshot, stageSnapshot.endedAt);
1531
-
1532
- const finalModelMeta = innerCtx.__modelFallbackMeta();
1533
- if (finalModelMeta.model !== undefined) stageSnapshot.model = finalModelMeta.model;
1534
- if (finalModelMeta.attemptedModels !== undefined) stageSnapshot.attemptedModels = finalModelMeta.attemptedModels;
1535
- if (finalModelMeta.modelAttempts !== undefined) stageSnapshot.modelAttempts = finalModelMeta.modelAttempts;
1536
-
1537
1877
  if (opts.mcp && hasMcpScope) {
1538
1878
  opts.mcp.clearScope(stageId);
1539
1879
  }
1540
1880
 
1541
- activeStore.recordStageEnd(runId, stageSnapshot);
1542
- opts.onStageEnd?.(runId, stageSnapshot);
1543
-
1544
- // Persistence: append stage.end entry
1545
- if (opts.persistence) {
1546
- appendStageEnd(opts.persistence, {
1547
- runId,
1548
- stageId,
1549
- status: stageSnapshot.status,
1550
- durationMs: stageSnapshot.durationMs,
1551
- });
1552
- }
1553
-
1554
- tracker.onSettle(stageId);
1881
+ finalizeStageSnapshot();
1555
1882
  // The stage has finished participating in workflow scheduling. Drop it
1556
- // from run-level pause/resume and cascade-pause lookups immediately,
1557
- // but keep the SDK session alive below while an attached chat pane is
1558
- // still using its direct handle.
1559
- dropStageControlHandle();
1560
- if (stageSnapshot.attached === true) {
1561
- let unsubscribeDetach: (() => void) | undefined;
1562
- let abortListener: (() => void) | undefined;
1563
- const releaseWhenDetached = (force = false): void => {
1564
- const currentRun = activeStore.runs().find((r) => r.id === runId);
1565
- const currentStage = currentRun?.stages.find((s) => s.id === stageId);
1566
- if (!force && currentStage?.attached === true) return;
1567
- unsubscribeDetach?.();
1568
- unsubscribeDetach = undefined;
1569
- if (abortListener) {
1570
- ownController.signal.removeEventListener("abort", abortListener);
1571
- abortListener = undefined;
1572
- }
1573
- void releaseLiveHandle().catch(() => {});
1574
- };
1575
- unsubscribeDetach = activeStore.subscribe(() => releaseWhenDetached());
1576
- abortListener = () => releaseWhenDetached(true);
1577
- if (ownController.signal.aborted) releaseWhenDetached(true);
1578
- else {
1579
- ownController.signal.addEventListener(
1580
- "abort",
1581
- abortListener,
1582
- { once: true },
1583
- );
1584
- }
1585
- releaseWhenDetached();
1586
- limiter.release();
1587
- } else {
1588
- try {
1589
- await releaseLiveHandle();
1590
- } finally {
1591
- limiter.release();
1592
- }
1593
- }
1883
+ // from run-level pause/resume and cascade-pause lookups immediately.
1884
+ // If no SDK queue/active input remains, release the live chat handle so
1885
+ // the node reopens as a read-only archived session. Queued messages keep
1886
+ // the direct handle alive only until the SDK reports that the queue has
1887
+ // drained.
1888
+ await releaseLiveHandleWhenIdle().catch(() => {});
1889
+ limiter.release();
1594
1890
  }
1595
1891
  };
1596
1892
 
@@ -1678,9 +1974,13 @@ export async function run<TInputs extends Record<string, unknown>>(
1678
1974
  return stageContext;
1679
1975
  },
1680
1976
 
1681
- async task(name: string, options: WorkflowTaskOptions): Promise<WorkflowTaskResult> {
1977
+ async task(name: string, options: WorkflowTaskOptions, stageFailFastScope?: ParallelFailFastScope): Promise<WorkflowTaskResult> {
1682
1978
  const runTaskOnce = async (taskOptions: WorkflowTaskOptions): Promise<WorkflowTaskResult> => {
1683
- const stage = ctx.stage(name, taskStageOptions(taskOptions));
1979
+ const stage = (ctx.stage as typeof ctx.stage & ((stageName: string, stageOptions?: StageOptions, scope?: ParallelFailFastScope) => StageContext))(
1980
+ name,
1981
+ taskStageOptions(taskOptions),
1982
+ stageFailFastScope,
1983
+ );
1684
1984
  const rawText = await stage.prompt(
1685
1985
  applyTaskContext(`${taskReadInstruction(taskOptions)}${taskPrompt(taskOptions)}`, taskPrevious(taskOptions)),
1686
1986
  taskPromptOptions(taskOptions),
@@ -1744,12 +2044,23 @@ export async function run<TInputs extends Record<string, unknown>>(
1744
2044
 
1745
2045
  async parallel(steps: readonly WorkflowTaskStep[], options: WorkflowParallelOptions = {}): Promise<WorkflowTaskResult[]> {
1746
2046
  const fallback = parallelFallbackTask(steps, options);
2047
+ const failFastScope: ParallelFailFastScope | undefined = options.failFast === false
2048
+ ? undefined
2049
+ : { failed: false, activeStages: new Map<string, ParallelFailFastStage>() };
1747
2050
  return mapParallelSteps(steps, options.concurrency, options.failFast, async (step) => {
1748
2051
  const prompt = replaceTaskPlaceholder(step.prompt ?? step.task ?? fallback, options.task ?? fallback);
1749
- return ctx.task(
2052
+ return await (ctx.task as typeof ctx.task & ((taskName: string, taskOptions: WorkflowTaskOptions, scope?: ParallelFailFastScope) => Promise<WorkflowTaskResult>))(
1750
2053
  step.name,
1751
2054
  taskWithSharedDefaults(taskOptionsFromStep(step, prompt, taskPrevious(step)), options),
2055
+ failFastScope,
1752
2056
  );
2057
+ }, (error) => {
2058
+ if (failFastScope === undefined) return;
2059
+ failFastScope.failed = true;
2060
+ failFastScope.firstFailure = error;
2061
+ for (const stage of failFastScope.activeStages.values()) {
2062
+ stage.skip();
2063
+ }
1753
2064
  });
1754
2065
  },
1755
2066
  };
@@ -1792,21 +2103,25 @@ export async function run<TInputs extends Record<string, unknown>>(
1792
2103
  return finalizeKilled(runId, runSnapshot, activeStore, opts.persistence, opts.onRunEnd);
1793
2104
  }
1794
2105
 
1795
- const errorMessage = err instanceof Error ? err.message : String(err);
1796
-
1797
- const recorded = activeStore.recordRunEnd(runId, "failed", undefined, errorMessage);
1798
- opts.onRunEnd?.(runId, "failed", undefined, errorMessage);
2106
+ const metadata = runFailureMetadata(err, runSnapshot.stages);
2107
+ const recorded = activeStore.recordRunEnd(runId, "failed", undefined, metadata.errorMessage, metadata);
2108
+ opts.onRunEnd?.(runId, "failed", undefined, metadata.errorMessage);
1799
2109
 
1800
2110
  appendRunEndWhenRecorded(opts.persistence, recorded, {
1801
2111
  runId,
1802
2112
  status: "failed",
2113
+ error: metadata.errorMessage,
2114
+ failureKind: metadata.failureKind,
2115
+ failureMessage: metadata.failureMessage,
2116
+ ...(metadata.failedStageId !== undefined ? { failedStageId: metadata.failedStageId } : {}),
2117
+ resumable: metadata.resumable,
1803
2118
  ts: Date.now(),
1804
2119
  });
1805
2120
 
1806
2121
  return {
1807
2122
  runId,
1808
2123
  status: "failed",
1809
- error: errorMessage,
2124
+ error: metadata.errorMessage,
1810
2125
  stages: [...runSnapshot.stages],
1811
2126
  };
1812
2127
  } finally {