@bastani/atomic 0.8.13 → 0.8.14

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 +23 -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
@@ -38,6 +38,17 @@ export interface KeybindingsLike {
38
38
  matches(data: string, action: string): boolean;
39
39
  }
40
40
 
41
+ export const TUI_ACTION = {
42
+ editorCursorUp: "tui.editor.cursorUp",
43
+ editorCursorDown: "tui.editor.cursorDown",
44
+ editorCursorLeft: "tui.editor.cursorLeft",
45
+ editorCursorRight: "tui.editor.cursorRight",
46
+ inputSubmit: "tui.input.submit",
47
+ selectUp: "tui.select.up",
48
+ selectDown: "tui.select.down",
49
+ selectConfirm: "tui.select.confirm",
50
+ } as const;
51
+
41
52
  /** Runtime guard for hosts that wire the keybindings manager. */
42
53
  export function isKeybindingsLike(kb: unknown): kb is KeybindingsLike {
43
54
  return (
@@ -71,6 +71,8 @@ function pickBorder(
71
71
  return theme.error;
72
72
  case "blocked":
73
73
  return theme.dim;
74
+ case "skipped":
75
+ return theme.dim;
74
76
  case "pending":
75
77
  default:
76
78
  // Pending has no semantic colour; the focused-tab carries the
@@ -117,7 +119,6 @@ function durationText(stage: StageSnapshot): string {
117
119
  }
118
120
 
119
121
  function metaText(stage: StageSnapshot): string {
120
- if (stage.model) return stage.model;
121
122
  const deps = stage.parentIds.length;
122
123
  if (deps === 0) return "root";
123
124
  return deps === 1 ? "1 dep" : `${deps} deps`;
@@ -23,6 +23,7 @@ import { destroyRun } from "../runs/background/status.js";
23
23
  import { cancellationRegistry } from "../runs/background/cancellation-registry.js";
24
24
  import { stageControlRegistry as defaultStageControlRegistry } from "../runs/foreground/stage-control-registry.js";
25
25
  import type { StageControlRegistry } from "../runs/foreground/stage-control-registry.js";
26
+ import type { StageUiBroker } from "../shared/stage-ui-broker.js";
26
27
  import type {
27
28
  PiCustomComponent,
28
29
  PiCustomOverlayFactoryTui,
@@ -35,7 +36,7 @@ import type {
35
36
  PiTheme,
36
37
  } from "../extension/wiring.js";
37
38
 
38
- export type OverlayChatRenderSettings = Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd" | "markdownTheme">>;
39
+ export type OverlayChatRenderSettings = Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">>;
39
40
 
40
41
  export interface OverlayUISurface {
41
42
  custom?: PiCustomOverlayFunction;
@@ -105,6 +106,8 @@ export interface BuildGraphOverlayAdapterOpts {
105
106
  * Defaults to the singleton registry registered alongside the store.
106
107
  */
107
108
  stageControlRegistry?: StageControlRegistry;
109
+ /** Broker used to route stage-local custom UI into attached stage chats. */
110
+ stageUiBroker?: StageUiBroker;
108
111
  /**
109
112
  * Destructive kill hook used by graph-mode `q`. The extension factory
110
113
  * supplies this so persistence can record a terminal event before the run is
@@ -119,6 +122,7 @@ export function buildGraphOverlayAdapter(
119
122
  buildOpts: BuildGraphOverlayAdapterOpts = {},
120
123
  ): GraphOverlayPort {
121
124
  const registry = buildOpts.stageControlRegistry ?? defaultStageControlRegistry;
125
+ const stageUiBroker = buildOpts.stageUiBroker;
122
126
  const killRun = buildOpts.onKillRun ?? ((id: string): void => {
123
127
  destroyRun(id, { store, cancellation: cancellationRegistry });
124
128
  });
@@ -161,6 +165,7 @@ export function buildGraphOverlayAdapter(
161
165
  function hideMounted(): void {
162
166
  setMouseScrollTracking(false);
163
167
  if (currentHandle) {
168
+ currentView?.setVisible(false);
164
169
  currentHandle.setHidden(true);
165
170
  currentHandle.unfocus();
166
171
  return;
@@ -203,6 +208,7 @@ export function buildGraphOverlayAdapter(
203
208
  // Already mounted but hidden — flip visibility without remounting.
204
209
  if (mounted && currentHandle?.isHidden()) {
205
210
  currentView?.retarget(runId, stageId);
211
+ currentView?.setVisible(true);
206
212
  setMouseScrollTracking(currentView?.wantsMouseScrollTracking() ?? true);
207
213
  currentHandle.setHidden(false);
208
214
  currentHandle.focus();
@@ -242,6 +248,7 @@ export function buildGraphOverlayAdapter(
242
248
  graphTheme: deriveGraphThemeFromPiTheme(theme),
243
249
  runId,
244
250
  stageControlRegistry: registry,
251
+ stageUiBroker,
245
252
  uiStatus,
246
253
  onClose: finish,
247
254
  onHide: hideMounted,
@@ -296,6 +303,7 @@ export function buildGraphOverlayAdapter(
296
303
  // no scroll-pollution).
297
304
  if (mounted && currentHandle) {
298
305
  const nowHidden = !currentHandle.isHidden();
306
+ currentView?.setVisible(!nowHidden);
299
307
  setMouseScrollTracking(
300
308
  nowHidden ? false : currentView?.wantsMouseScrollTracking() ?? true,
301
309
  );
@@ -12,9 +12,9 @@
12
12
  * │ │
13
13
  * │ <message> │
14
14
  * │ │
15
- * │ response ─────────────────────────────┐
15
+ * │ response ─────────────────────────────╮
16
16
  * │ │ <text input / choice cycler> │ │
17
- * │ └───────────────────────────────────────┘
17
+ * │ ╰───────────────────────────────────────╯
18
18
  * │ │
19
19
  * │ ↵ submit · esc skip │
20
20
  * ╰───────────────────────────────────────────────╯
@@ -37,7 +37,7 @@ import {
37
37
  import type { PendingPrompt } from "../shared/store-types.js";
38
38
  import type { GraphTheme } from "./graph-theme.js";
39
39
  import { hexToAnsi, hexBg, paint, RESET, BOLD } from "./color-utils.js";
40
- import { matchesKey } from "./text-helpers.js";
40
+ import { Key, matchesKey } from "./text-helpers.js";
41
41
 
42
42
  // ---------------------------------------------------------------------------
43
43
  // State
@@ -99,7 +99,7 @@ export function handlePromptCardInput(
99
99
  data: string,
100
100
  state: PromptCardState,
101
101
  ): PromptCardAction {
102
- if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
102
+ if (matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.escape)) {
103
103
  return { kind: "cancel" };
104
104
  }
105
105
 
@@ -119,17 +119,17 @@ function handleConfirm(
119
119
  data: string,
120
120
  state: PromptCardState,
121
121
  ): PromptCardAction {
122
- if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "space") || matchesKey(data, "tab")) {
122
+ if (matchesKey(data, Key.left) || matchesKey(data, Key.right) || matchesKey(data, Key.space) || matchesKey(data, Key.tab)) {
123
123
  state.confirmValue = !state.confirmValue;
124
124
  return { kind: "noop" };
125
125
  }
126
- if (data === "y" || data === "Y") {
126
+ if (matchesKey(data, "y") || matchesKey(data, Key.shift("y"))) {
127
127
  return { kind: "submit", response: true };
128
128
  }
129
- if (data === "n" || data === "N") {
129
+ if (matchesKey(data, "n") || matchesKey(data, Key.shift("n"))) {
130
130
  return { kind: "submit", response: false };
131
131
  }
132
- if (matchesKey(data, "enter")) {
132
+ if (matchesKey(data, Key.enter)) {
133
133
  return { kind: "submit", response: state.confirmValue };
134
134
  }
135
135
  return { kind: "noop" };
@@ -138,7 +138,7 @@ function handleConfirm(
138
138
  function handleSelect(data: string, state: PromptCardState): PromptCardAction {
139
139
  const choices = state.prompt.choices ?? [];
140
140
  if (choices.length === 0) {
141
- if (matchesKey(data, "enter")) {
141
+ if (matchesKey(data, Key.enter)) {
142
142
  return { kind: "submit", response: "" };
143
143
  }
144
144
  return { kind: "noop" };
@@ -155,24 +155,24 @@ function handleSelect(data: string, state: PromptCardState): PromptCardAction {
155
155
  }
156
156
 
157
157
  function handleInput(data: string, state: PromptCardState): PromptCardAction {
158
- if (matchesKey(data, "enter")) {
158
+ if (matchesKey(data, Key.enter)) {
159
159
  return { kind: "submit", response: state.rawText };
160
160
  }
161
161
  return applyTextEdit(data, state);
162
162
  }
163
163
 
164
164
  function handleEditor(data: string, state: PromptCardState): PromptCardAction {
165
- if (matchesKey(data, "tab") || matchesKey(data, "shift+tab")) {
165
+ if (matchesKey(data, Key.tab) || matchesKey(data, Key.shift("tab"))) {
166
166
  state.editorSubmitFocused = !state.editorSubmitFocused;
167
167
  return { kind: "noop" };
168
168
  }
169
169
  if (state.editorSubmitFocused) {
170
- if (matchesKey(data, "enter")) {
170
+ if (matchesKey(data, Key.enter)) {
171
171
  return { kind: "submit", response: state.rawText };
172
172
  }
173
173
  return { kind: "noop" };
174
174
  }
175
- if (matchesKey(data, "enter")) {
175
+ if (matchesKey(data, Key.enter)) {
176
176
  state.rawText = state.rawText.slice(0, state.caret) + "\n" + state.rawText.slice(state.caret);
177
177
  state.caret += 1;
178
178
  return { kind: "noop" };
@@ -184,15 +184,15 @@ function applyTextEdit(
184
184
  data: string,
185
185
  state: PromptCardState,
186
186
  ): PromptCardAction {
187
- if (matchesKey(data, "left")) {
187
+ if (matchesKey(data, Key.left)) {
188
188
  state.caret = previousGraphemeBoundary(state.rawText, state.caret);
189
189
  return { kind: "noop" };
190
190
  }
191
- if (matchesKey(data, "right")) {
191
+ if (matchesKey(data, Key.right)) {
192
192
  state.caret = nextGraphemeBoundary(state.rawText, state.caret);
193
193
  return { kind: "noop" };
194
194
  }
195
- if (matchesKey(data, "backspace")) {
195
+ if (matchesKey(data, Key.backspace)) {
196
196
  if (state.caret > 0) {
197
197
  const prev = previousGraphemeBoundary(state.rawText, state.caret);
198
198
  state.rawText = state.rawText.slice(0, prev) + state.rawText.slice(state.caret);
@@ -335,8 +335,8 @@ function normalizeSelectKeyData(data: string): string {
335
335
  // The historical prompt card accepted left/right as select aliases; feed the
336
336
  // corresponding vertical key into pi-tui's SelectList so it owns the actual
337
337
  // wrap/clamp/selection update behavior.
338
- if (matchesKey(data, "right")) return "\x1b[B";
339
- if (matchesKey(data, "left")) return "\x1b[A";
338
+ if (matchesKey(data, Key.right)) return "\x1b[B";
339
+ if (matchesKey(data, Key.left)) return "\x1b[A";
340
340
  return data;
341
341
  }
342
342
 
@@ -372,7 +372,7 @@ export function renderPromptCard(opts: PromptCardRenderOpts): string[] {
372
372
  }
373
373
  lines.push(makePaddedRow(bg, borderColor, innerWidth, ""));
374
374
 
375
- const fieldLines = renderResponseField(state, theme, innerWidth - 4, opts.cursorOn);
375
+ const fieldLines = renderResponseFieldBox(state, theme, innerWidth - 4, opts.cursorOn);
376
376
  for (const fl of fieldLines) {
377
377
  lines.push(makePaddedRow(bg, borderColor, innerWidth, " " + fl));
378
378
  }
@@ -424,6 +424,33 @@ function wrapText(text: string, width: number): string[] {
424
424
  return wrapTextWithAnsi(text, width);
425
425
  }
426
426
 
427
+ function renderResponseFieldBox(
428
+ state: PromptCardState,
429
+ theme: GraphTheme,
430
+ usable: number,
431
+ cursorOn: boolean,
432
+ ): string[] {
433
+ const boxWidth = Math.max(4, usable);
434
+ const contentWidth = Math.max(1, boxWidth - 2);
435
+ const borderColor = theme.accent;
436
+ const label = " response ";
437
+ const labelText = paint(label, theme.textMuted, { bold: true });
438
+ const labelW = visibleWidth(labelText);
439
+ const topFill = Math.max(0, boxWidth - labelW - 2);
440
+ const rows = renderResponseField(state, theme, contentWidth, cursorOn);
441
+ return [
442
+ paint("╭", borderColor) + labelText + paint("─".repeat(topFill) + "╮", borderColor),
443
+ ...rows.map((row) => makeFieldRow(row, contentWidth, borderColor)),
444
+ paint("╰" + "─".repeat(Math.max(0, boxWidth - 2)) + "╯", borderColor),
445
+ ];
446
+ }
447
+
448
+ function makeFieldRow(content: string, width: number, borderColor: string): string {
449
+ const clipped = truncateToWidth(content, width, "", true);
450
+ const padded = clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
451
+ return paint("│", borderColor) + padded + paint("│", borderColor);
452
+ }
453
+
427
454
  function renderResponseField(
428
455
  state: PromptCardState,
429
456
  theme: GraphTheme,
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Per-run detail block — the "▎ RUN <id>" surface.
2
+ * Per-run detail block surface.
3
3
  *
4
4
  * Pairs with the status-list overview ({@link renderSessionList}) and the
5
- * above-editor widget. Emits a 3-row band header, key/value run summary,
6
- * a STAGES section, and a ▎ ARTIFACTS section, all rendered against the
7
- * canonical Catppuccin Mocha palette.
5
+ * above-editor widget. Emits a rounded run panel, key/value run summary,
6
+ * rounded stage/artifact cards, all rendered against the canonical
7
+ * Catppuccin Mocha palette.
8
8
  *
9
9
  * Two output modes:
10
10
  * - **themed** ANSI Catppuccin chrome (theme supplied)
@@ -13,7 +13,7 @@
13
13
  * cross-ref:
14
14
  * - github.com/nicobailon/pi-subagents src/runs/background/run-status.ts
15
15
  * inspectSubagentStatus — the source UX pattern
16
- * - DESIGN.md §5 Section Labels (`▎ LABEL`)
16
+ * - DESIGN.md §5 Section Labels
17
17
  * - orchestrator-panel-ui.png — band-header chrome
18
18
  */
19
19
 
@@ -21,8 +21,8 @@ import type { RunDetail } from "../runs/background/status.js";
21
21
  import type { StageSnapshot } from "../shared/store-types.js";
22
22
  import { elapsedRunMs, elapsedStageMs } from "../shared/timing.js";
23
23
  import type { GraphTheme } from "./graph-theme.js";
24
- import { renderBandHeader } from "./header.js";
25
- import type { BandBadge } from "./header.js";
24
+ import { renderRoundedBox } from "./chat-surface.js";
25
+ import type { FlatBandBadge } from "./chat-surface.js";
26
26
  import { fmtDuration, statusIcon, statusColor } from "./status-helpers.js";
27
27
  import { hexToAnsi, RESET, BOLD } from "./color-utils.js";
28
28
  import { truncateToWidth, visibleWidth } from "./text-helpers.js";
@@ -62,58 +62,44 @@ function renderPlain(detail: RunDetail, now: number, width: number): string {
62
62
 
63
63
  const sid = shortId(detail.runId);
64
64
  const stateBadge = stateLabel(detail);
65
- const innerLabel = ` RUN ${sid} `;
66
- const inner = "─".repeat(innerLabel.length);
67
- out.push(` ╭${inner}╮`);
68
- const headerTailW = visibleWidth(` │${innerLabel}│ `) + visibleWidth(` ${stateBadge}`);
69
- const headerName = truncateToWidth(detail.name, Math.max(1, width - headerTailW), "…");
70
- out.push(` │${innerLabel}│ ${headerName} ${stateBadge}`);
71
- out.push(` ╰${inner}╯`);
72
- out.push("");
73
65
 
74
- // Summary key/value lines
75
66
  for (const [k, v] of summaryRows(detail, now)) {
76
67
  if (v === undefined) continue;
77
- const value = truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…");
78
- out.push(` ${pad(k, KEY_COL)}${value}`);
68
+ const value = truncateToWidth(v, Math.max(1, width - 4 - KEY_COL), "…");
69
+ out.push(` ${pad(k, KEY_COL)}${value} `);
79
70
  }
80
71
  out.push("");
81
72
 
82
- // Stages
83
- out.push("▎ STAGES");
84
- out.push("");
73
+ out.push(" STAGES ");
85
74
  if (detail.stages.length === 0) {
86
- out.push(" (no stages recorded yet)");
75
+ out.push(" (no stages recorded yet) ");
87
76
  } else {
88
77
  for (const stage of detail.stages) {
89
- out.push(` ${stageLinePlain(stage, now, width - 2)}`);
90
- if (stage.error) {
91
- const err = truncateToWidth(stage.error.split("\n")[0] ?? "", Math.max(1, width - STAGE_NAME_COL - 11), "…");
92
- out.push(` ${" ".repeat(STAGE_NAME_COL + 2)}error ${err}`);
93
- }
78
+ out.push(...renderStageRowsPlain(stage, now, width - 4));
94
79
  }
95
80
  }
96
81
  out.push("");
97
82
 
98
- // Artifacts (best-effort: result + error)
99
83
  const artifactRows = artifactRowsFor(detail);
100
84
  if (artifactRows.length > 0) {
101
- out.push(" ARTIFACTS");
102
- out.push("");
85
+ out.push(" ARTIFACTS ");
103
86
  for (const [k, v] of artifactRows) {
104
- out.push(` ${pad(k, KEY_COL)}${truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…")}`);
87
+ out.push(` ${pad(k, KEY_COL)}${truncateToWidth(v, Math.max(1, width - 4 - KEY_COL), "…")} `);
105
88
  }
106
89
  out.push("");
107
90
  }
108
91
 
109
- // Action hints
110
92
  if (detail.endedAt === undefined) {
111
- out.push(truncateToWidth(` ▸ workflow interrupt id=${sid} cancel`, width, "…"));
93
+ out.push(truncateToWidth(` ▸ workflow interrupt id=${sid} cancel `, width - 2, "…"));
112
94
  } else {
113
- out.push(truncateToWidth(` ▸ workflow resume id=${sid} reopen graph`, width, "…"));
95
+ out.push(truncateToWidth(` ▸ workflow resume id=${sid} reopen graph `, width - 2, "…"));
114
96
  }
115
97
 
116
- return out.join("\n");
98
+ return renderRoundedBox({
99
+ title: `RUN ${sid} ${detail.name} ${stateBadge}`,
100
+ bodyLines: out,
101
+ width,
102
+ });
117
103
  }
118
104
 
119
105
  // ---------------------------------------------------------------------------
@@ -122,7 +108,6 @@ function renderPlain(detail: RunDetail, now: number, width: number): string {
122
108
 
123
109
  function renderThemed(detail: RunDetail, now: number, theme: GraphTheme, width: number): string {
124
110
  const out: string[] = [];
125
- const mauve = hexToAnsi(theme.mauve);
126
111
  const muted = hexToAnsi(theme.textMuted);
127
112
  const dim = hexToAnsi(theme.dim);
128
113
  const text = hexToAnsi(theme.text);
@@ -130,68 +115,51 @@ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme, width:
130
115
 
131
116
  const sid = shortId(detail.runId);
132
117
  const badges = stateBadges(detail, theme);
133
- // Band-header chrome lives within ~64 cells regardless of terminal width so
134
- // it visually echoes the orchestrator overlay (which never spans the whole
135
- // pane horizontally either).
136
- out.push(...renderBandHeader({
137
- label: `RUN ${sid}`,
138
- subtitle: detail.name,
139
- badges,
140
- width: Math.min(64, width),
141
- theme,
142
- }));
143
- out.push("");
144
118
 
145
- // Summary key/value
146
119
  for (const [k, v] of summaryRows(detail, now)) {
147
120
  if (v === undefined) continue;
148
- const value = truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…");
149
- out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${text}${value}${RESET}`);
121
+ const value = truncateToWidth(v, Math.max(1, width - 4 - KEY_COL), "…");
122
+ out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${text}${value}${RESET} `);
150
123
  }
151
124
  out.push("");
152
125
 
153
- // Stages section
154
- out.push(`${mauve}▎${RESET} ${muted}${BOLD}STAGES${RESET}`);
155
- out.push("");
126
+ out.push(` ${muted}${BOLD}STAGES${RESET} `);
156
127
  if (detail.stages.length === 0) {
157
- out.push(` ${dim}(no stages recorded yet)${RESET}`);
128
+ out.push(` ${dim}(no stages recorded yet)${RESET} `);
158
129
  } else {
159
130
  for (const stage of detail.stages) {
160
- out.push(" " + stageLineThemed(stage, now, theme, width - 2));
161
- if (stage.error) {
162
- const errFg = hexToAnsi(theme.error);
163
- const err = truncateToWidth(stage.error.split("\n")[0] ?? "", Math.max(1, width - STAGE_NAME_COL - 13), "…");
164
- out.push(
165
- ` ${" ".repeat(STAGE_NAME_COL + 2)}${muted}error${RESET} ${errFg}${err}${RESET}`,
166
- );
167
- }
131
+ out.push(...renderStageRowsThemed(stage, now, theme, width - 4));
168
132
  }
169
133
  }
170
134
  out.push("");
171
135
 
172
- // Artifacts section
173
136
  const artifactRows = artifactRowsFor(detail);
174
137
  if (artifactRows.length > 0) {
175
- out.push(`${mauve}▎${RESET} ${muted}${BOLD}ARTIFACTS${RESET}`);
176
- out.push("");
138
+ out.push(` ${muted}${BOLD}ARTIFACTS${RESET} `);
177
139
  for (const [k, v] of artifactRows) {
178
- out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${dim}${truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…")}${RESET}`);
140
+ out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${dim}${truncateToWidth(v, Math.max(1, width - 4 - KEY_COL), "…")}${RESET} `);
179
141
  }
180
142
  out.push("");
181
143
  }
182
144
 
183
- // Action hints
184
145
  if (detail.endedAt === undefined) {
185
146
  out.push(
186
- truncateToWidth(` ${dim}▸${RESET} ${accent}workflow interrupt id=${sid}${RESET}${dim} cancel${RESET}`, width, "…"),
147
+ truncateToWidth(` ${dim}▸${RESET} ${accent}workflow interrupt id=${sid}${RESET}${dim} cancel${RESET} `, width - 2, "…"),
187
148
  );
188
149
  } else {
189
150
  out.push(
190
- truncateToWidth(` ${dim}▸${RESET} ${accent}workflow resume id=${sid}${RESET}${dim} reopen graph${RESET}`, width, "…"),
151
+ truncateToWidth(` ${dim}▸${RESET} ${accent}workflow resume id=${sid}${RESET}${dim} reopen graph${RESET} `, width - 2, "…"),
191
152
  );
192
153
  }
193
154
 
194
- return out.join("\n");
155
+ const badgeText = badges.length > 0 ? ` ${badges.map((b) => b.text).join(" ")}` : "";
156
+ return renderRoundedBox({
157
+ title: `RUN ${sid} ${detail.name}${badgeText}`,
158
+ bodyLines: out,
159
+ accent: theme.accent,
160
+ theme,
161
+ width,
162
+ });
195
163
  }
196
164
 
197
165
  // ---------------------------------------------------------------------------
@@ -199,19 +167,16 @@ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme, width:
199
167
  // ---------------------------------------------------------------------------
200
168
 
201
169
  function summaryRows(detail: RunDetail, now: number): Array<[string, string | undefined]> {
202
- const startedAgo = formatRelative(now - detail.startedAt);
203
- const updatedAt = detail.endedAt ?? detail.startedAt;
204
- const updatedAgo = formatRelative(now - updatedAt);
205
170
  const duration = elapsedRunMs(detail, now);
206
171
 
207
172
  const rows: Array<[string, string | undefined]> = [
208
173
  ["workflow", detail.name],
209
174
  ["state", statePlain(detail)],
210
175
  ["mode", detail.mode === "chain" ? `chain · ${detail.stages.length} stages` : "single"],
211
- ["started", `${formatTime(detail.startedAt)} (${startedAgo} ago)`],
176
+ ["started", formatTime(detail.startedAt)],
212
177
  ];
213
178
  if (detail.endedAt !== undefined) {
214
- rows.push(["ended", `${formatTime(detail.endedAt)} (${updatedAgo} ago)`]);
179
+ rows.push(["ended", formatTime(detail.endedAt)]);
215
180
  rows.push(["duration", fmtDuration(duration)]);
216
181
  } else {
217
182
  rows.push(["elapsed", fmtDuration(duration)]);
@@ -276,6 +241,28 @@ function stageLineThemed(stage: StageSnapshot, now: number, theme: GraphTheme, w
276
241
  );
277
242
  }
278
243
 
244
+ function renderStageRowsPlain(stage: StageSnapshot, now: number, width: number): string[] {
245
+ const rows = [` ${stageLinePlain(stage, now, Math.max(1, width - 2))} `];
246
+ if (stage.error) {
247
+ rows.push(` error ${truncateToWidth(stage.error.split("\n")[0] ?? "", Math.max(1, width - 10), "…")} `);
248
+ }
249
+ return rows;
250
+ }
251
+
252
+ function renderStageRowsThemed(
253
+ stage: StageSnapshot,
254
+ now: number,
255
+ theme: GraphTheme,
256
+ width: number,
257
+ ): string[] {
258
+ const rows = [` ${stageLineThemed(stage, now, theme, Math.max(1, width - 2))} `];
259
+ if (stage.error) {
260
+ const errFg = hexToAnsi(theme.error);
261
+ rows.push(` ${hexToAnsi(theme.textMuted)}error${RESET} ${errFg}${truncateToWidth(stage.error.split("\n")[0] ?? "", Math.max(1, width - 12), "…")}${RESET} `);
262
+ }
263
+ return rows;
264
+ }
265
+
279
266
  function stageDurationString(stage: StageSnapshot, now: number): string | undefined {
280
267
  const elapsed = elapsedStageMs(stage, now);
281
268
  return elapsed === undefined ? undefined : fmtDuration(elapsed);
@@ -298,7 +285,7 @@ function stageActivityString(stage: StageSnapshot): string | undefined {
298
285
  // State badges + plain-text equivalents
299
286
  // ---------------------------------------------------------------------------
300
287
 
301
- function stateBadges(detail: RunDetail, theme: GraphTheme): BandBadge[] {
288
+ function stateBadges(detail: RunDetail, theme: GraphTheme): FlatBandBadge[] {
302
289
  switch (detail.status) {
303
290
  case "running":
304
291
  return [{ text: "● running", fg: theme.warning }];
@@ -350,7 +337,3 @@ function formatTime(ms: number): string {
350
337
  const ss = String(d.getUTCSeconds()).padStart(2, "0");
351
338
  return `${hh}:${mm}:${ss}`;
352
339
  }
353
-
354
- function formatRelative(ms: number): string {
355
- return fmtDuration(Math.max(0, ms));
356
- }
@@ -193,12 +193,18 @@ export function handleKillConfirmInput(
193
193
  state: KillConfirmState,
194
194
  ): KillConfirmAction {
195
195
  // Direct shortcuts bypass focus.
196
- if (data === "y" || data === "Y") return { kind: "confirm" };
197
- if (data === "n" || data === "N") return { kind: "cancel" };
196
+ if (matchesKey(data, "y") || matchesKey(data, Key.shift("y"))) return { kind: "confirm" };
197
+ if (matchesKey(data, "n") || matchesKey(data, Key.shift("n"))) return { kind: "cancel" };
198
198
  if (matchesKey(data, Key.escape)) return { kind: "cancel" };
199
199
 
200
200
  // Tab / arrows toggle focus.
201
- if (matchesKey(data, Key.tab) || matchesKey(data, Key.right) || matchesKey(data, Key.left) || data === "h" || data === "l") {
201
+ if (
202
+ matchesKey(data, Key.tab) ||
203
+ matchesKey(data, Key.right) ||
204
+ matchesKey(data, Key.left) ||
205
+ matchesKey(data, "h") ||
206
+ matchesKey(data, "l")
207
+ ) {
202
208
  state.focusedButton = state.focusedButton === 0 ? 1 : 0;
203
209
  return { kind: "noop" };
204
210
  }
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Visual contract (DESIGN.md §5 Picker Rows + pi-subagents/src/tui/render-helpers.ts):
6
6
  * - Rounded `╭─ Title ─╮` chrome in `border` colour with title in `accent`.
7
- * - Section header rows use the mauve `▎` glyph (matches GraphView).
7
+ * - Section header rows use a simple two-space label indent (matches GraphView).
8
8
  * - Selected row: `picker-row-selected` token (blue bg, surface0 fg, bold).
9
9
  * - Footer: dim hints, active key letters in `text`.
10
10
  *
@@ -24,7 +24,10 @@ import type { GraphTheme } from "./graph-theme.js";
24
24
  import { keyText } from "@bastani/atomic";
25
25
  import { fmtDuration, statusIcon, statusColor } from "./status-helpers.js";
26
26
  import { hexToAnsi, hexBg, RESET, BOLD } from "./color-utils.js";
27
- import { matchesKey, truncateToWidth, visibleWidth } from "./text-helpers.js";
27
+ import { Key, matchesKey, truncateToWidth, visibleWidth } from "./text-helpers.js";
28
+
29
+ const ESCAPE_CODE = 0x1b;
30
+ const DOUBLE_ESCAPE_SEQUENCE = String.fromCharCode(ESCAPE_CODE, ESCAPE_CODE);
28
31
 
29
32
  // ---------------------------------------------------------------------------
30
33
  // State + filtering
@@ -182,7 +185,7 @@ function renderSectionRow(label: string, inner: number, theme: GraphTheme): stri
182
185
  const panelBg = hexBg(theme.bg);
183
186
  const mauve = hexToAnsi(theme.mauve);
184
187
  const muted = hexToAnsi(theme.textMuted);
185
- const content = ` ${mauve}▎${RESET}${panelBg} ${muted}${BOLD}${label}${RESET}`;
188
+ const content = ` ${mauve} ${RESET}${panelBg} ${muted}${BOLD}${label}${RESET}`;
186
189
  return `${border}│${RESET}${panelBg}${padTo(content, inner)}${RESET}${border}│${RESET}`;
187
190
  }
188
191
 
@@ -195,14 +198,14 @@ function renderFilterRow(inner: number, theme: GraphTheme, state: SessionPickerS
195
198
  const accent = hexToAnsi(theme.accent);
196
199
  const cursor = state.filterFocused ? `${accent}▌${RESET}${panelBg}` : "";
197
200
  const label = state.filterFocused ? `${accent}filter` : `${muted}filter`;
198
- const prefixPlain = "filter ";
201
+ const prefixPlain = " filter ";
199
202
  const valueBudget = Math.max(1, inner - visibleWidth(prefixPlain) - (state.filterFocused ? 1 : 0));
200
203
  const rawValue = state.query || "(type to filter by name or id)";
201
204
  const shownValue = truncateToWidth(rawValue, valueBudget, "…");
202
205
  const value = state.query
203
206
  ? `${text}${shownValue}${RESET}${panelBg}`
204
207
  : `${muted}${shownValue}${RESET}${panelBg}`;
205
- const content = ` ${mauve}▎${RESET}${panelBg} ${label}${RESET}${panelBg} ${value}${cursor}`;
208
+ const content = ` ${mauve} ${RESET}${panelBg} ${label}${RESET}${panelBg} ${value}${cursor}`;
206
209
  return `${border}│${RESET}${panelBg}${padTo(content, inner)}${RESET}${border}│${RESET}`;
207
210
  }
208
211
 
@@ -342,15 +345,15 @@ export function handleSessionPickerInput(
342
345
  ): SessionPickerAction {
343
346
  // Filter mode — typed chars feed the query, Enter/Esc exit.
344
347
  if (state.filterFocused) {
345
- if (matchesKey(data, "escape") || data === "\x1b\x1b") {
348
+ if (matchesKey(data, Key.escape) || data === DOUBLE_ESCAPE_SEQUENCE) {
346
349
  state.filterFocused = false;
347
350
  return { kind: "noop" };
348
351
  }
349
- if (matchesKey(data, "enter")) {
352
+ if (matchesKey(data, Key.enter)) {
350
353
  state.filterFocused = false;
351
354
  return { kind: "noop" };
352
355
  }
353
- if (matchesKey(data, "backspace")) {
356
+ if (matchesKey(data, Key.backspace)) {
354
357
  state.query = state.query.slice(0, -1);
355
358
  state.selectedIndex = 0;
356
359
  return { kind: "noop" };
@@ -364,36 +367,36 @@ export function handleSessionPickerInput(
364
367
  }
365
368
 
366
369
  // Navigation mode.
367
- if (data === "/") {
370
+ if (matchesKey(data, "/")) {
368
371
  state.filterFocused = true;
369
372
  return { kind: "noop" };
370
373
  }
371
- if (matchesKey(data, "escape")) return { kind: "close" };
372
- if (data === "q" || data === "Q") return { kind: "close" };
373
- if (data === "a" || data === "A") {
374
+ if (matchesKey(data, Key.escape)) return { kind: "close" };
375
+ if (matchesKey(data, "q") || matchesKey(data, Key.shift("q"))) return { kind: "close" };
376
+ if (matchesKey(data, "a") || matchesKey(data, Key.shift("a"))) {
374
377
  state.includeAll = !state.includeAll;
375
378
  state.selectedIndex = 0;
376
379
  return { kind: "noop" };
377
380
  }
378
381
 
379
382
  // Arrows + j/k.
380
- if (matchesKey(data, "down") || data === "j") {
383
+ if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
381
384
  state.selectedIndex = Math.min(state.selectedIndex + 1, Math.max(0, rows.length - 1));
382
385
  return { kind: "noop" };
383
386
  }
384
- if (matchesKey(data, "up") || data === "k") {
387
+ if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
385
388
  if (rows.length > 0 && state.selectedIndex === 0) return { kind: "noop" };
386
389
  state.selectedIndex = Math.max(state.selectedIndex - 1, 0);
387
390
  return { kind: "noop" };
388
391
  }
389
392
 
390
- if (matchesKey(data, "enter")) {
393
+ if (matchesKey(data, Key.enter)) {
391
394
  const row = rows[state.selectedIndex];
392
395
  if (!row) return { kind: "noop" };
393
396
  return { kind: "connect", runId: row.run.id };
394
397
  }
395
398
  // `x` = kill. Avoids collision with vim's `k` = up.
396
- if (data === "x" || data === "X") {
399
+ if (matchesKey(data, "x") || matchesKey(data, Key.shift("x"))) {
397
400
  const row = rows[state.selectedIndex];
398
401
  if (!row) return { kind: "noop" };
399
402
  return { kind: "kill", runId: row.run.id };