@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
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  decodeKittyPrintable,
3
+ Key,
3
4
  matchesKey as piMatchesKey,
4
5
  truncateToWidth as piTruncateToWidth,
5
6
  visibleWidth,
6
7
  type KeyId,
7
8
  } from "@earendil-works/pi-tui";
8
9
 
9
- export { visibleWidth };
10
+ export { Key, visibleWidth };
10
11
 
11
12
  const ANSI_ESCAPE_RE = /^\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\)|_[^\x1b]*(?:\x1b\\))/;
12
13
  const MODIFY_OTHER_KEYS_RE = /^\x1b\[27;(\d+);(\d+)~$/;
@@ -14,6 +15,19 @@ const SHIFT_MODIFIER = 1;
14
15
  const LOCK_MODIFIER_MASK = 64 + 128;
15
16
  const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
16
17
 
18
+ export function graphemes(text: string): string[] {
19
+ return Array.from(segmenter.segment(text), (s) => s.segment);
20
+ }
21
+
22
+ export interface GraphemeSegment {
23
+ segment: string;
24
+ index: number;
25
+ }
26
+
27
+ export function graphemeSegments(text: string): GraphemeSegment[] {
28
+ return Array.from(segmenter.segment(text), (s) => ({ segment: s.segment, index: s.index }));
29
+ }
30
+
17
31
  function readAnsiCode(text: string, offset: number): string | null {
18
32
  const match = ANSI_ESCAPE_RE.exec(text.slice(offset));
19
33
  return match?.[0] ?? null;
@@ -37,9 +51,9 @@ export function truncateToWidth(
37
51
  return piTruncateToWidth(text, width, suffix, false);
38
52
  }
39
53
 
40
- /** Use pi-tui's key parser/matcher while preserving the local string API. */
41
- export function matchesKey(data: string, key: string): boolean {
42
- return data === key || piMatchesKey(data, key as KeyId);
54
+ /** Use pi-tui's key parser/matcher with typed key identifiers. */
55
+ export function matchesKey(data: string, key: KeyId): boolean {
56
+ return data === key || piMatchesKey(data, key);
43
57
  }
44
58
 
45
59
  function decodeModifyOtherKeysPrintable(data: string): string | undefined {
@@ -67,6 +81,86 @@ export function decodePrintableKey(data: string): string | undefined {
67
81
  return decodeKittyPrintable(data) ?? decodeModifyOtherKeysPrintable(data);
68
82
  }
69
83
 
84
+ /**
85
+ * Trim review values without flattening authored line breaks.
86
+ * Empty or whitespace-only values render as an explicit placeholder.
87
+ */
88
+ export function formatReviewValue(value: string): string {
89
+ const trimmed = value.trim();
90
+ return trimmed.length === 0 ? "<empty>" : trimmed;
91
+ }
92
+
93
+ /**
94
+ * Wrap plain text by terminal cell width while preserving paragraph breaks.
95
+ * Within each paragraph, runs of whitespace collapse to a single space; this
96
+ * keeps labels/descriptions readable but is not intended for preformatted text.
97
+ */
98
+ export function wrapPlainText(text: string, width: number): string[] {
99
+ const budget = Math.max(1, Math.floor(width));
100
+ const out: string[] = [];
101
+ for (const paragraph of text.split(/\r?\n/)) {
102
+ const words = paragraph.trim().split(/\s+/).filter((word) => word.length > 0);
103
+ if (words.length === 0) {
104
+ out.push("");
105
+ continue;
106
+ }
107
+ let line = "";
108
+ for (const word of words) {
109
+ if (visibleWidth(word) > budget) {
110
+ if (line.length > 0) {
111
+ out.push(line);
112
+ line = "";
113
+ }
114
+ let chunk = "";
115
+ for (const g of graphemes(word)) {
116
+ if (chunk.length > 0 && visibleWidth(chunk) + visibleWidth(g) > budget) {
117
+ out.push(chunk);
118
+ chunk = g;
119
+ } else {
120
+ chunk += g;
121
+ }
122
+ }
123
+ line = chunk;
124
+ } else if (line.length === 0) {
125
+ line = word;
126
+ } else if (visibleWidth(line) + 1 + visibleWidth(word) <= budget) {
127
+ line += " " + word;
128
+ } else {
129
+ out.push(line);
130
+ line = word;
131
+ }
132
+ }
133
+ if (line.length > 0) out.push(line);
134
+ }
135
+ return out.length > 0 ? out : [""];
136
+ }
137
+
138
+ export interface WrappedPrefixedLinesOpts {
139
+ text: string;
140
+ width: number;
141
+ plainPrefix: string;
142
+ firstPrefix: string;
143
+ continuationPrefix?: string;
144
+ suffix?: string;
145
+ preserveAnsi?: boolean;
146
+ styleLine?: (line: string, index: number) => string;
147
+ }
148
+
149
+ /**
150
+ * Shared ask-style row wrapper for numbered choices and review value rows.
151
+ * Computes the wrapped text budget from the visible prefix width, then clips
152
+ * the composed ANSI row so narrow terminals never overflow.
153
+ */
154
+ export function renderWrappedPrefixedLines(opts: WrappedPrefixedLinesOpts): string[] {
155
+ const continuationPrefix = opts.continuationPrefix ?? " ".repeat(visibleWidth(opts.plainPrefix));
156
+ const textBudget = Math.max(1, opts.width - visibleWidth(opts.plainPrefix));
157
+ return wrapPlainText(opts.text, textBudget).map((line, index) => {
158
+ const prefix = index === 0 ? opts.firstPrefix : continuationPrefix;
159
+ const styled = opts.styleLine ? opts.styleLine(line, index) : line;
160
+ return truncateToWidth(prefix + styled, opts.width, opts.suffix ?? "…", opts.preserveAnsi ?? true);
161
+ });
162
+ }
163
+
70
164
  export function sliceColumns(
71
165
  line: string,
72
166
  startCol: number,
@@ -1,15 +1,13 @@
1
1
  /**
2
2
  * Above-editor background-workflow widget.
3
3
  *
4
- * Visual contract (DESIGN.md §5 · orchestrator-panel-ui.png):
5
- * - 3-row outline-pill band header: `[ BACKGROUND ]` accent pill,
6
- * `N runs` subtitle in textMuted, right-aligned status-icon badges
7
- * (`✓ n n ○ n ✗ n`). Same chrome vocabulary as the orchestrator
8
- * overlay header pi-subagents widget identity, atomic palette.
9
- * - Two-line entry per run:
10
- * line 1: `<status glyph> <short id> <bold name>`
11
- * line 2: `<dim mode · progress · duration>`
12
- * - Blank line between entries; trailing blank trimmed.
4
+ * Visual contract (DESIGN.md §5):
5
+ * - One transparent rounded `BACKGROUND` panel with `N runs` and status
6
+ * badges (`✓ n ● n ○ n ✗ n`) in the title.
7
+ * - One compact rounded card per run:
8
+ * title: `<status glyph> <short id> <name>`
9
+ * row 1: `<dim mode · progress · duration>`
10
+ * - Blank line between cards; trailing blank trimmed.
13
11
  * - Collapsed single-line form below 80 cells:
14
12
  * `▾ N background · X ●` in dim+warning.
15
13
  *
@@ -21,8 +19,7 @@
21
19
  *
22
20
  * cross-ref:
23
21
  * - github.com/nicobailon/pi-subagents src/tui/render.ts buildWidgetLines
24
- * - src/tui/header.ts renderBandHeader
25
- * - orchestrator-panel-ui.png
22
+ * - src/tui/chat-surface.ts renderRoundedBoxLines
26
23
  */
27
24
 
28
25
  import type {
@@ -31,8 +28,8 @@ import type {
31
28
  } from "../shared/store-types.js";
32
29
  import { elapsedRunMs } from "../shared/timing.js";
33
30
  import type { PiTheme } from "./store-widget-installer.js";
34
- import { renderBandHeader } from "./header.js";
35
- import type { BandBadge } from "./header.js";
31
+ import { renderRoundedBoxLines } from "./chat-surface.js";
32
+ import type { FlatBandBadge } from "./chat-surface.js";
36
33
  import { deriveGraphTheme } from "./graph-theme.js";
37
34
  import type { GraphTheme } from "./graph-theme.js";
38
35
  import { hexToAnsi, RESET, BOLD } from "./color-utils.js";
@@ -43,7 +40,8 @@ import { hexToAnsi, RESET, BOLD } from "./color-utils.js";
43
40
 
44
41
  const SHORT_ID_LEN = 6;
45
42
  const MAX_VISIBLE_RUNS = 4;
46
- const RECENT_ENDED_WINDOW_MS = 30_000;
43
+ export const RECENT_ENDED_WINDOW_MS = 30_000;
44
+ const WIDGET_CLOCK_REFRESH_MS = 1_000;
47
45
  const COLLAPSED_BREAKPOINT_COLS = 80;
48
46
 
49
47
  // ---------------------------------------------------------------------------
@@ -75,6 +73,7 @@ function recentlyEnded(run: RunSnapshot, now: number): boolean {
75
73
 
76
74
  interface RunCounts {
77
75
  active: number;
76
+ paused: number;
78
77
  done: number;
79
78
  failed: number;
80
79
  /** Runs with a pending HIL prompt — surfaced as a separate badge so the
@@ -83,18 +82,43 @@ interface RunCounts {
83
82
  }
84
83
 
85
84
  function countRuns(runs: readonly RunSnapshot[]): RunCounts {
86
- const counts: RunCounts = { active: 0, done: 0, failed: 0, awaiting: 0 };
85
+ const counts: RunCounts = { active: 0, paused: 0, done: 0, failed: 0, awaiting: 0 };
87
86
  for (const r of runs) {
88
- if (r.endedAt === undefined) counts.active++;
87
+ if (r.endedAt === undefined && r.status === "paused") counts.paused++;
88
+ else if (r.endedAt === undefined) counts.active++;
89
89
  else if (r.status === "completed") counts.done++;
90
90
  else if (r.status === "failed" || r.status === "killed") counts.failed++;
91
- if (r.endedAt === undefined && r.pendingPrompt !== undefined) {
91
+ if (
92
+ r.endedAt === undefined &&
93
+ (r.pendingPrompt !== undefined || r.stages.some((s) => s.status === "awaiting_input"))
94
+ ) {
92
95
  counts.awaiting++;
93
96
  }
94
97
  }
95
98
  return counts;
96
99
  }
97
100
 
101
+ function msUntilNextClockTick(now: number): number {
102
+ const remainder = now % WIDGET_CLOCK_REFRESH_MS;
103
+ return remainder === 0 ? WIDGET_CLOCK_REFRESH_MS : WIDGET_CLOCK_REFRESH_MS - remainder;
104
+ }
105
+
106
+ export function nextWidgetRefreshDelayMs(
107
+ snap: StoreSnapshot,
108
+ now = Date.now(),
109
+ ): number | undefined {
110
+ const display = selectDisplayRuns(snap, now);
111
+ if (display.length === 0) return undefined;
112
+
113
+ const hasLiveClock = display.some((run) => run.endedAt === undefined && run.status !== "paused");
114
+ const clockDelay = hasLiveClock ? msUntilNextClockTick(now) : undefined;
115
+ const expiryDelays = display
116
+ .filter((run) => run.endedAt !== undefined)
117
+ .map((run) => Math.max(1, run.endedAt! + RECENT_ENDED_WINDOW_MS - now + 1));
118
+ const delays = [clockDelay, ...expiryDelays].filter((delay): delay is number => delay !== undefined);
119
+ return delays.length === 0 ? undefined : Math.min(...delays);
120
+ }
121
+
98
122
  function selectDisplayRuns(snap: StoreSnapshot, now: number): RunSnapshot[] {
99
123
  const all = snap.runs as readonly RunSnapshot[];
100
124
  const active = all.filter((r) => isActive(r));
@@ -118,6 +142,8 @@ function statusGlyph(run: RunSnapshot): string {
118
142
  switch (run.status) {
119
143
  case "running":
120
144
  return "●";
145
+ case "paused":
146
+ return "❚❚";
121
147
  case "completed":
122
148
  return "✓";
123
149
  case "failed":
@@ -133,6 +159,7 @@ function statusGlyph(run: RunSnapshot): string {
133
159
  function statusFg(run: RunSnapshot, theme: GraphTheme): string {
134
160
  switch (run.status) {
135
161
  case "running":
162
+ case "paused":
136
163
  return theme.warning;
137
164
  case "completed":
138
165
  return theme.success;
@@ -161,11 +188,11 @@ function progressLabel(run: RunSnapshot): string | undefined {
161
188
 
162
189
  function elapsedLabel(run: RunSnapshot, now: number): string {
163
190
  if (run.endedAt !== undefined) {
164
- const ago = formatDuration(now - run.endedAt);
165
- if (run.status === "completed") return `complete · ${ago} ago`;
166
- if (run.status === "failed") return `failed · ${ago} ago`;
167
- if (run.status === "killed") return `killed · ${ago} ago`;
168
- return `${run.status} · ${ago} ago`;
191
+ const elapsed = formatDuration(elapsedRunMs(run, run.endedAt));
192
+ if (run.status === "completed") return `complete · ${elapsed}`;
193
+ if (run.status === "failed") return `failed · ${elapsed}`;
194
+ if (run.status === "killed") return `killed · ${elapsed}`;
195
+ return `${run.status} · ${elapsed}`;
169
196
  }
170
197
  if (run.startedAt != null) return formatDuration(elapsedRunMs(run, now));
171
198
  return "";
@@ -187,16 +214,26 @@ function metaLine(run: RunSnapshot, now: number): string {
187
214
  // Count badges for the band header
188
215
  // ---------------------------------------------------------------------------
189
216
 
190
- function countBadges(counts: RunCounts, theme: GraphTheme): BandBadge[] {
191
- const badges: BandBadge[] = [];
192
- if (counts.active > 0) badges.push({ text: `● ${counts.active}`, fg: theme.warning });
193
- // Awaiting input is shown in Sky per DESIGN.md status semantics: it's a
194
- // "live, waiting for human" signal — distinct from running (yellow) and
195
- // completed (green). Position right after active so it reads as "N of M
196
- // running need you".
197
- if (counts.awaiting > 0) badges.push({ text: `↵ ${counts.awaiting}`, fg: theme.info });
198
- if (counts.done > 0) badges.push({ text: `✓ ${counts.done}`, fg: theme.success });
199
- if (counts.failed > 0) badges.push({ text: `✗ ${counts.failed}`, fg: theme.error });
217
+ function countBadges(counts: RunCounts, theme: GraphTheme): FlatBandBadge[] {
218
+ const badges: FlatBandBadge[] = [];
219
+ if (counts.active > 0) {
220
+ badges.push({ text: `● ${counts.active} running`, fg: theme.warning });
221
+ }
222
+ if (counts.paused > 0) {
223
+ badges.push({ text: `❚❚ ${counts.paused} paused`, fg: theme.warning });
224
+ }
225
+ // Awaiting input is shown in Sky per DESIGN.md status semantics: a live
226
+ // human-in-the-loop request that requires attention. Spell it out in the
227
+ // widget title instead of relying on the ↵ glyph alone.
228
+ if (counts.awaiting > 0) {
229
+ badges.push({ text: `↵ ${counts.awaiting} needs attention (attach to workflow with \`/workflow connect\`)`, fg: theme.info });
230
+ }
231
+ if (counts.done > 0) {
232
+ badges.push({ text: `✓ ${counts.done} complete`, fg: theme.success });
233
+ }
234
+ if (counts.failed > 0) {
235
+ badges.push({ text: `✗ ${counts.failed} failed`, fg: theme.error });
236
+ }
200
237
  return badges;
201
238
  }
202
239
 
@@ -246,14 +283,16 @@ function themedCollapsed(
246
283
  const dim = hexToAnsi(theme.dim);
247
284
  const muted = hexToAnsi(theme.textMuted);
248
285
  const warning = hexToAnsi(theme.warning);
249
- const total = counts.active + counts.done + counts.failed;
286
+ const total = counts.active + counts.paused + counts.done + counts.failed;
250
287
  const active = counts.active;
251
- return ` ${mauve}▾${RESET} ${muted}${total} background${RESET}${dim} · ${RESET}${warning}${active} ●${RESET}`;
288
+ const paused = counts.paused > 0 ? `${dim} · ${RESET}${warning}${counts.paused} ❚❚${RESET}` : "";
289
+ return ` ${mauve}▾${RESET} ${muted}${total} background${RESET}${dim} · ${RESET}${warning}${active} ●${RESET}${paused}`;
252
290
  }
253
291
 
254
292
  function plainCollapsed(counts: RunCounts): string {
255
- const total = counts.active + counts.done + counts.failed;
256
- return ` ▾ ${total} background · ${counts.active} ●`;
293
+ const total = counts.active + counts.paused + counts.done + counts.failed;
294
+ const paused = counts.paused > 0 ? ` · ${counts.paused} ❚❚` : "";
295
+ return ` ▾ ${total} background · ${counts.active} ●${paused}`;
257
296
  }
258
297
 
259
298
  // ---------------------------------------------------------------------------
@@ -283,7 +322,8 @@ export function buildThemedWidgetLines(
283
322
  // Active + recently-ended dominate the badge counts so a finished run
284
323
  // visually persists for a beat before dropping off.
285
324
  const visibleCounts: RunCounts = {
286
- active: counts.active,
325
+ active: display.filter((r) => r.endedAt === undefined && r.status !== "paused").length,
326
+ paused: display.filter((r) => r.endedAt === undefined && r.status === "paused").length,
287
327
  done: display.filter((r) => r.endedAt !== undefined && r.status === "completed").length,
288
328
  failed: display.filter((r) => r.endedAt !== undefined && (r.status === "failed" || r.status === "killed")).length,
289
329
  awaiting: counts.awaiting,
@@ -297,47 +337,29 @@ export function buildThemedWidgetLines(
297
337
  return [themed ? themedCollapsed(visibleCounts, graphTheme) : plainCollapsed(visibleCounts)];
298
338
  }
299
339
 
300
- const total = counts.active + counts.done + counts.failed;
340
+ const total = counts.active + counts.paused + counts.done + counts.failed;
301
341
  const subtitle = `${total} run${total === 1 ? "" : "s"}`;
302
342
 
303
- const lines: string[] = [];
304
-
305
- if (themed) {
306
- const badges = countBadges(visibleCounts, graphTheme);
307
- // Cap the chrome width to ~min(width, 64) so a wide terminal doesn't
308
- // stretch the band across the whole pane.
309
- const chromeWidth = Math.min(width, 64);
310
- lines.push(...renderBandHeader({
311
- label: "BACKGROUND",
312
- subtitle,
313
- badges,
314
- width: chromeWidth,
315
- theme: graphTheme,
316
- }));
317
- } else {
318
- // Plain band: 3 rows mirroring the outline-pill shape, ASCII-only.
319
- const innerLen = " BACKGROUND ".length;
320
- const inner = "─".repeat(innerLen);
321
- const badges = countBadges(visibleCounts, graphTheme)
322
- .map((b) => b.text)
323
- .join(" ");
324
- const subtitleSeg = ` ${subtitle}`;
325
- const badgeTail = badges ? ` ${badges}` : "";
326
- lines.push(` ╭${inner}╮${subtitleSeg.replace(/./g, " ")}${badgeTail.replace(/./g, " ")}`);
327
- lines.push(` │ BACKGROUND │${subtitleSeg}${badgeTail}`);
328
- lines.push(` ╰${inner}╯${subtitleSeg.replace(/./g, " ")}${badgeTail.replace(/./g, " ")}`);
329
- }
343
+ const badges = countBadges(visibleCounts, graphTheme).map((b) => b.text).join(" ");
344
+ const title = `BACKGROUND ${subtitle}${badges ? ` ${badges}` : ""}`;
345
+ const body: string[] = [];
330
346
 
331
347
  for (let i = 0; i < display.length; i++) {
332
348
  const run = display[i]!;
333
349
  const runLines = themed
334
350
  ? themedRunLines(run, now, graphTheme)
335
351
  : plainRunLines(run, now);
336
- lines.push(...runLines);
337
- if (i < display.length - 1) lines.push("");
352
+ body.push(...runLines);
353
+ if (i < display.length - 1) body.push("");
338
354
  }
339
355
 
340
- return lines;
356
+ return renderRoundedBoxLines({
357
+ title,
358
+ bodyLines: body,
359
+ accent: themed ? graphTheme.border : undefined,
360
+ theme: themed ? graphTheme : undefined,
361
+ width,
362
+ });
341
363
  }
342
364
 
343
365
  /**
@@ -30,6 +30,7 @@ import type {
30
30
  StageControlHandle,
31
31
  StageControlRegistry,
32
32
  } from "../runs/foreground/stage-control-registry.js";
33
+ import type { StageUiBroker } from "../shared/stage-ui-broker.js";
33
34
 
34
35
  /**
35
36
  * Surface used to write Pi's footer/status tag while the attach pane is
@@ -52,6 +53,8 @@ export interface WorkflowAttachPaneOpts {
52
53
  * the user attaches to a node. Defaults to the singleton registry.
53
54
  */
54
55
  stageControlRegistry?: StageControlRegistry;
56
+ /** Broker used to route stage-local custom UI such as ask_user_question into attached chats. */
57
+ stageUiBroker?: StageUiBroker;
55
58
  /**
56
59
  * Optional UI status surface. When present, attaching/detaching
57
60
  * updates the `pi-workflows` status tag with `<workflow>/<stage>`.
@@ -67,11 +70,12 @@ export interface WorkflowAttachPaneOpts {
67
70
  onPromptResolve?: (runId: string, promptId: string, response: unknown) => void;
68
71
  /** Live pi-tui host objects used by attached stage chat to reuse coding-agent editor UI. */
69
72
  piTui?: TUI;
73
+ piTheme?: unknown;
70
74
  piKeybindings?: unknown;
71
75
  /** Host custom editor factory installed by extensions via ctx.ui.setEditorComponent(). */
72
76
  piEditorFactory?: (tui: TUI, theme: EditorTheme, keybindings: unknown) => EditorComponent;
73
77
  /** Parent chat rendering settings and extension renderers, inherited from the host UI. */
74
- getChatRenderSettings?: () => Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd" | "markdownTheme">> | undefined;
78
+ getChatRenderSettings?: () => Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">> | undefined;
75
79
  /** Parent footer data provider, inherited so attached chats can render the core coding-agent footer. */
76
80
  footerData?: ReadonlyFooterDataProvider;
77
81
  /**
@@ -113,6 +117,7 @@ export class WorkflowAttachPane implements Component {
113
117
  private theme: GraphTheme;
114
118
  private runId: string | null;
115
119
  private registry: StageControlRegistry | undefined;
120
+ private stageUiBroker: StageUiBroker | undefined;
116
121
  private uiStatus: AttachUiStatusSurface | undefined;
117
122
  private onClose: () => void;
118
123
  private onHide?: () => void;
@@ -122,9 +127,10 @@ export class WorkflowAttachPane implements Component {
122
127
  private hostRequestRender?: () => void;
123
128
  private setMouseScrollTracking?: (enabled: boolean) => void;
124
129
  private piTui?: TUI;
130
+ private piTheme?: unknown;
125
131
  private piKeybindings?: unknown;
126
132
  private piEditorFactory?: (tui: TUI, theme: EditorTheme, keybindings: unknown) => EditorComponent;
127
- private getChatRenderSettings?: () => Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd" | "markdownTheme">> | undefined;
133
+ private getChatRenderSettings?: () => Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">> | undefined;
128
134
  private footerData?: ReadonlyFooterDataProvider;
129
135
 
130
136
  private mode: WorkflowAttachPaneMode = "graph";
@@ -138,6 +144,7 @@ export class WorkflowAttachPane implements Component {
138
144
  this.theme = opts.graphTheme;
139
145
  this.runId = opts.runId;
140
146
  this.registry = opts.stageControlRegistry;
147
+ this.stageUiBroker = opts.stageUiBroker;
141
148
  this.uiStatus = opts.uiStatus;
142
149
  this.onClose = opts.onClose;
143
150
  this.onHide = opts.onHide;
@@ -147,6 +154,7 @@ export class WorkflowAttachPane implements Component {
147
154
  this.hostRequestRender = opts.requestRender;
148
155
  this.setMouseScrollTracking = opts.setMouseScrollTracking;
149
156
  this.piTui = opts.piTui;
157
+ this.piTheme = opts.piTheme;
150
158
  this.piKeybindings = opts.piKeybindings;
151
159
  this.piEditorFactory = opts.piEditorFactory;
152
160
  this.getChatRenderSettings = opts.getChatRenderSettings;
@@ -226,11 +234,13 @@ export class WorkflowAttachPane implements Component {
226
234
  onClose: this.onClose,
227
235
  requestRender: this.hostRequestRender,
228
236
  piTui: this.piTui,
237
+ piTheme: this.piTheme,
229
238
  piKeybindings: this.piKeybindings,
230
239
  piEditorFactory: this.piEditorFactory,
231
240
  getChatRenderSettings: this.getChatRenderSettings,
232
241
  footerData: this.footerData,
233
242
  getViewportRows: this.getViewportRows,
243
+ stageUiBroker: this.stageUiBroker,
234
244
  });
235
245
  this.store.recordStageAttached(runId, stageId, true);
236
246
  this.mode = "stage-chat";
@@ -286,6 +296,17 @@ export class WorkflowAttachPane implements Component {
286
296
  this.uiStatus?.setStatus?.(STATUS_KEY, value);
287
297
  }
288
298
 
299
+ setVisible(visible: boolean): void {
300
+ if (this.mode === "stage-chat" && this.runId && this.lastAttachedStageId) {
301
+ this.store.recordStageAttached(this.runId, this.lastAttachedStageId, visible);
302
+ if (visible) this._setAttachedStatus(this.runId, this.lastAttachedStageId);
303
+ else this.uiStatus?.setStatus?.(STATUS_KEY, undefined);
304
+ return;
305
+ }
306
+ if (visible) this._setBaseStatus();
307
+ else this.uiStatus?.setStatus?.(STATUS_KEY, undefined);
308
+ }
309
+
289
310
  private _syncMouseScrollTracking(): void {
290
311
  this.setMouseScrollTracking?.(this.wantsMouseScrollTracking());
291
312
  }
@@ -1,13 +1,12 @@
1
1
  /**
2
- * `/workflow list` catalogue — chat-surface vocabulary from ui/mockups.html §3.
2
+ * `/workflow list` catalogue — rounded workflow-tool output surface.
3
3
  *
4
4
  * Visual contract:
5
- * - One full-width `[ WORKFLOWS ]` mauve band (catalogue accent distinct
6
- * from the blue live-run accent used by `[ BACKGROUND ]` / `[ DISPATCHED ]`).
7
- * - One card per workflow:
8
- * row 1: stripe · [tag workflow-name]
9
- * row 2: stripe · muted description
10
- * row 3: ▎ stripe · dim "inputs" · signature
5
+ * - One rounded `WORKFLOWS` panel with the registered count in the title.
6
+ * - One rounded card per workflow:
7
+ * title: workflow name
8
+ * row 1: muted description
9
+ * row 2: dim "inputs" · signature
11
10
  * - Hint rows pointing at `/workflow <name> …` and `/workflow inputs <name>`.
12
11
  *
13
12
  * Truncation policy (ui/mockups.html §4):
@@ -24,13 +23,12 @@
24
23
 
25
24
  import type { GraphTheme } from "./graph-theme.js";
26
25
  import {
27
- renderFlatBand,
28
- renderTaggedCard,
29
26
  renderHintRows,
27
+ renderRoundedBox,
30
28
  ELLIPSIS,
31
29
  chatWidth,
32
30
  } from "./chat-surface.js";
33
- import { hexToAnsi, RESET } from "./color-utils.js";
31
+ import { hexToAnsi, RESET, BOLD } from "./color-utils.js";
34
32
  import { visibleWidth, truncateToWidth } from "./text-helpers.js";
35
33
 
36
34
  const INLINE_INPUT_LIMIT = 3;
@@ -58,91 +56,69 @@ export interface RenderWorkflowListOpts {
58
56
  }
59
57
 
60
58
  /**
61
- * Render the workflow catalogue as a `[ WORKFLOWS ]` band followed by
62
- * one card per registered workflow.
59
+ * Render the workflow catalogue as a rounded `WORKFLOWS` panel with
60
+ * structured rows for each registered workflow.
63
61
  */
64
62
  export function renderWorkflowList(
65
63
  entries: readonly WorkflowListEntry[],
66
64
  opts: RenderWorkflowListOpts = {},
67
65
  ): string {
68
- const lines: string[] = [];
66
+ const width = effectiveWidth(opts.width);
67
+ const bodyWidth = Math.max(20, width - 4);
68
+ const body: string[] = [];
69
69
  const subtitle = `${entries.length} registered`;
70
70
  const accent = opts.theme?.mauve;
71
71
 
72
- lines.push(renderFlatBand({
73
- label: "WORKFLOWS",
74
- subtitle,
75
- accent,
76
- theme: opts.theme,
77
- width: opts.width,
78
- }));
79
- // Blank line after the band — mirrors the mockup's `.band { padding-bottom }`
80
- // + first `.card { margin-top }` (ui/mockups.html §3). The band reads as
81
- // a header, not as the first card's row 0.
82
- lines.push("");
83
-
84
72
  if (entries.length === 0) {
85
- // The blank line emitted after the band already provides spacing
86
- // before the empty-state line; no extra spacer needed.
87
- lines.push(emptyState(opts.theme));
88
- return lines.join("\n");
89
- }
90
-
91
- // Blank line between cards mirrors the mockup's `.card { margin: 0.25rem 1.1rem }`
92
- // top/bottom margin — without it the three workflow rows collapse into one
93
- // visual block (see ui/Screenshot 2026-05-12 "Notice how the spacing is off").
94
- for (let i = 0; i < entries.length; i++) {
95
- if (i > 0) lines.push("");
96
- lines.push(renderWorkflowCard(entries[i]!, opts));
73
+ body.push(` ${emptyState(opts.theme)} `);
74
+ } else {
75
+ for (let i = 0; i < entries.length; i++) {
76
+ if (i > 0) body.push("");
77
+ body.push(...renderWorkflowEntry(entries[i]!, { ...opts, width: bodyWidth }));
78
+ }
97
79
  }
98
80
 
99
81
  if (opts.showHints !== false) {
100
- lines.push("");
101
- lines.push(
102
- renderHintRows(
82
+ body.push("");
83
+ body.push(
84
+ ...renderHintRows(
103
85
  [
104
86
  { command: "/workflow <name> …", hint: "run a workflow" },
105
87
  { command: "/workflow inputs <name>", hint: "inspect input schema" },
106
88
  ],
107
89
  opts.theme,
108
- ),
90
+ ).split("\n").map((line) => ` ${line} `),
109
91
  );
110
92
  }
111
93
 
112
- return lines.join("\n");
94
+ return renderRoundedBox({
95
+ title: `WORKFLOWS ${subtitle}`,
96
+ bodyLines: body,
97
+ accent,
98
+ theme: opts.theme,
99
+ width,
100
+ });
113
101
  }
114
102
 
115
- function renderWorkflowCard(
103
+ function renderWorkflowEntry(
116
104
  entry: WorkflowListEntry,
117
105
  opts: RenderWorkflowListOpts,
118
- ): string {
106
+ ): string[] {
119
107
  const theme = opts.theme;
120
- const cardWidth = effectiveWidth(opts.width);
121
- const stripPrefix = 3; // "▎ "
122
- const interior = Math.max(8, cardWidth - stripPrefix - 1);
123
- const accent = theme?.mauve ?? "#000000";
124
-
125
- // The card primitive treats the tag as fixed-width chrome, so clamp the
126
- // workflow name before handing it off. Budget by visible cells to keep CJK /
127
- // emoji workflow names inside the requested line width in both modes.
128
- const tagBudget = Math.max(8, Math.min(TAG_NAME_BUDGET, cardWidth - stripPrefix - 6));
129
- const tag = truncateToWidth(entry.name, tagBudget, ELLIPSIS);
130
-
131
- // Row 2: description (muted)
132
- const descBudget = Math.max(8, interior - 2);
108
+ const width = effectiveWidth(opts.width);
109
+ const interior = Math.max(8, width - 2);
110
+ const titleBudget = Math.max(8, Math.min(TAG_NAME_BUDGET, interior - 2));
111
+ const title = truncateToWidth(entry.name, titleBudget, ELLIPSIS);
112
+ const titleLine = theme
113
+ ? ` ${hexToAnsi(theme.text)}${BOLD}${title}${RESET} `
114
+ : ` ${title} `;
115
+
116
+ const descBudget = Math.max(8, interior - 4);
133
117
  const description = truncateToWidth(entry.description, descBudget, ELLIPSIS);
134
- const row2 = paintMuted(description, theme);
118
+ const row1 = ` ${paintMuted(description, theme)} `;
119
+ const row2 = ` ${inputsSignatureRow(entry.inputs, interior - 4, theme)} `;
135
120
 
136
- // Row 3: inputs signature
137
- const row3 = inputsSignatureRow(entry.inputs, interior, theme);
138
-
139
- return renderTaggedCard({
140
- tag,
141
- bodyRows: [row2, row3],
142
- accent,
143
- width: opts.width,
144
- theme,
145
- });
121
+ return [titleLine, row1, row2];
146
122
  }
147
123
 
148
124
  function inputsSignatureRow(