@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
@@ -28,6 +28,8 @@ export function statusColor(
28
28
  return theme.error;
29
29
  case "blocked":
30
30
  return theme.dim;
31
+ case "skipped":
32
+ return theme.dim;
31
33
  case "pending":
32
34
  default:
33
35
  return theme.dim;
@@ -41,6 +43,8 @@ export function statusIcon(status: StageStatus | RunStatus): string {
41
43
  return "○";
42
44
  case "blocked":
43
45
  return "↑";
46
+ case "skipped":
47
+ return "⊘";
44
48
  case "running":
45
49
  return "●";
46
50
  case "paused":
@@ -1,19 +1,19 @@
1
1
  /**
2
- * `/workflow status` list — chat-surface vocabulary from ui/mockups.html.
2
+ * `/workflow status` list — rounded workflow-tool output surface.
3
3
  *
4
- * Visual contract (ui/mockups.html §2 · DESIGN.md §5):
5
- * - One full-width `[ BACKGROUND ]` flat band with subtitle and badges.
6
- * - One **card** per run (replaces the indented per-stage rows):
7
- * row 1: ▎ stripe · [tag runId] · bold workflow · state badge
8
- * row 2: ▎ stripe · mode · progress strip · meta
9
- * - Status colour is carried by the stripe, the tag, and the trailing
10
- * badge — never by body text.
4
+ * Visual contract (DESIGN.md §5):
5
+ * - One rounded `BACKGROUND` panel with subtitle and count badges.
6
+ * - One rounded card per run (replaces the indented per-stage rows):
7
+ * title: runId · workflow · state badge
8
+ * row 1: mode · progress strip · meta
9
+ * - Status colour is carried by the card border and state badge semantics,
10
+ * never by decorative body text.
11
11
  * - One trailing hint row pointing at `/workflow status <id>` for the
12
12
  * most-recently-active run; full per-stage detail moves into
13
13
  * `/workflow status <id>` ({@link renderRunDetail}).
14
14
  *
15
- * Plain mode (theme omitted) preserves the shape: `▎ [ BACKGROUND ]`
16
- * band, `│ [tag] title` cards, ASCII bracket cells `[✓][●][○][✗]`.
15
+ * Plain mode (theme omitted) preserves the rounded panel/card shape without
16
+ * ANSI escapes, with ASCII bracket cells `[✓][●][○][✗]`.
17
17
  *
18
18
  * Powers:
19
19
  * - `renderResult({ action: "status" })` (LLM tool path)
@@ -31,15 +31,14 @@ import { elapsedRunMs, elapsedStageMs } from "../shared/timing.js";
31
31
  import type { GraphTheme } from "./graph-theme.js";
32
32
  import { fmtDuration } from "./status-helpers.js";
33
33
  import {
34
- renderFlatBand,
35
- renderTaggedCard,
36
34
  renderHintRows,
35
+ renderRoundedBox,
37
36
  progressStrip,
38
37
  ELLIPSIS,
39
38
  chatWidth,
40
39
  } from "./chat-surface.js";
41
40
  import type { FlatBandBadge } from "./chat-surface.js";
42
- import { hexToAnsi, RESET } from "./color-utils.js";
41
+ import { hexToAnsi, RESET, BOLD } from "./color-utils.js";
43
42
  import { visibleWidth, truncateToWidth } from "./text-helpers.js";
44
43
 
45
44
  const SHORT_ID_LEN = 6;
@@ -58,15 +57,16 @@ export interface RenderStatusListOpts {
58
57
  }
59
58
 
60
59
  /**
61
- * Render a list of run snapshots as the canonical `[ BACKGROUND ]` chat
62
- * surface: one band plus one card per run.
60
+ * Render a list of run snapshots as the canonical rounded `BACKGROUND`
61
+ * surface: one panel plus one card per run.
63
62
  */
64
63
  export function renderStatusList(
65
64
  runs: readonly RunSnapshot[],
66
65
  opts: RenderStatusListOpts = {},
67
66
  ): string {
68
67
  const now = opts.now ?? Date.now();
69
- const width = opts.width;
68
+ const width = effectiveWidth(opts.width);
69
+ const cardWidth = Math.max(20, width - 4);
70
70
 
71
71
  // The list shows active + recently-ended runs together. Sorting:
72
72
  // active first, then ended, each bucket by startedAt desc.
@@ -79,99 +79,80 @@ export function renderStatusList(
79
79
  ? themedBadges(counts, opts.theme)
80
80
  : plainBadges(counts);
81
81
 
82
- const lines: string[] = [];
83
- lines.push(renderFlatBand({
84
- label: "BACKGROUND",
85
- subtitle,
86
- badges,
87
- theme: opts.theme,
88
- width,
89
- }));
90
- // Blank line after the band — same header-vs-content separation used by
91
- // /workflow list. Without it the band visually fuses into the first card.
92
- lines.push("");
82
+ const body: string[] = [];
93
83
 
94
84
  if (sorted.length === 0) {
95
- lines.push(emptyStateLine(opts.theme));
96
- return lines.join("\n");
97
- }
98
-
99
- // Blank line between run cards mirrors the mockup's per-card top/bottom
100
- // margin (ui/mockups.html §2). Without it five runs collapse into one
101
- // visual block.
102
- for (let i = 0; i < sorted.length; i++) {
103
- if (i > 0) lines.push("");
104
- lines.push(renderRunCard(sorted[i]!, now, width, opts.theme));
85
+ body.push(` ${emptyStateLine(opts.theme)} `);
86
+ } else {
87
+ for (let i = 0; i < sorted.length; i++) {
88
+ if (i > 0) body.push("");
89
+ body.push(...renderRunEntry(sorted[i]!, now, cardWidth, opts.theme));
90
+ }
105
91
  }
106
92
 
107
93
  if (opts.showDetailHint !== false && sorted.length > 0) {
108
94
  const sid = shortId(sorted[0]!.id);
109
- lines.push("");
110
- lines.push(
111
- renderHintRows(
95
+ body.push("");
96
+ body.push(
97
+ ...renderHintRows(
112
98
  [{ command: `/workflow status ${sid}`, hint: "drill into a run" }],
113
99
  opts.theme,
114
- ),
100
+ ).split("\n").map((line) => ` ${line} `),
115
101
  );
116
102
  }
117
103
 
118
- return lines.join("\n");
104
+ const badgeText = badges && badges.length > 0 ? ` ${badges.map((b) => b.text).join(" ")}` : "";
105
+ return renderRoundedBox({
106
+ title: `BACKGROUND ${subtitle}${badgeText}`,
107
+ bodyLines: body,
108
+ theme: opts.theme,
109
+ width,
110
+ });
119
111
  }
120
112
 
121
113
  // ---------------------------------------------------------------------------
122
114
  // Run card
123
115
  // ---------------------------------------------------------------------------
124
116
 
125
- function renderRunCard(
117
+ function renderRunEntry(
126
118
  run: RunSnapshot,
127
119
  now: number,
128
- width: number | undefined,
120
+ width: number,
129
121
  theme?: GraphTheme,
130
- ): string {
122
+ ): string[] {
131
123
  const sid = shortId(run.id);
132
- const stateAccent = runAccent(run, theme);
133
124
  const trailing = runTrailing(run, theme);
134
-
135
125
  const mode = run.stages.length > 1 ? "chain " : "single";
136
- // Row 2 budget: the card body row sits past the stripe (3 cells) and
137
- // shares its width with a mode label, the progress strip, and a meta
138
- // tail. Reserve ~3 cells for the meta separator and ellipsis padding.
139
- const cardWidth = effectiveWidth(width);
140
- const stripPrefixW = 4; // leading chat pad + "▎ "
141
- const modeW = mode.length + 4; // 4-space separator after mode
142
- const interior = cardWidth - stripPrefixW;
126
+ const bodyWidth = effectiveWidth(width);
127
+ const interior = Math.max(8, bodyWidth - 4);
143
128
  const rawMeta = runCardMeta(run, now);
144
- // Row 2 is caller-rendered body content; clamp the metadata tail before it
145
- // reaches renderTaggedCard so long/wide stage names cannot overflow.
129
+ const modeW = mode.length + 4;
146
130
  const maxMetaW = Math.max(0, interior - modeW - 3);
147
131
  const meta = truncateToWidth(rawMeta, maxMetaW, ELLIPSIS);
148
132
  const metaW = visibleWidth(meta);
149
- const stripBudget = Math.max(0, cardWidth - stripPrefixW - modeW - metaW - 4);
133
+ const stripBudget = Math.max(0, interior - modeW - metaW - 2);
150
134
 
151
- const cells = stageCells(run);
152
- const strip = progressStrip(cells, stripBudget, theme);
153
- const stripVisibleW = visibleWidth(strip);
154
-
155
- const usedLeftW = modeW + stripVisibleW;
135
+ const strip = progressStrip(stageCells(run), stripBudget, theme);
136
+ const usedLeftW = modeW + visibleWidth(strip);
156
137
  const gap = Math.max(metaW > 0 ? 1 : 0, interior - usedLeftW - metaW);
157
138
 
139
+ const glyph = statusIconForRun(run);
140
+ const glyphFg = theme ? hexToAnsi(runAccent(run, theme)) : "";
141
+ const accent = theme ? hexToAnsi(theme.accent) : "";
142
+ const text = theme ? hexToAnsi(theme.text) : "";
158
143
  const muted = theme ? hexToAnsi(theme.textMuted) : "";
159
144
  const dim = theme ? hexToAnsi(theme.dim) : "";
160
145
  const reset = theme ? RESET : "";
161
146
 
147
+ const name = truncateToWidth(run.name, Math.max(MIN_TITLE_BUDGET, interior - visibleWidth(sid) - visibleWidth(trailing?.text ?? "") - 8), ELLIPSIS);
148
+ const line1 = theme
149
+ ? ` ${glyphFg}${glyph}${RESET} ${accent}${sid}${RESET} ${text}${BOLD}${name}${RESET} ${glyphFg}${trailing?.text ?? ""}${RESET} `
150
+ : ` ${glyph} ${sid} ${name} ${trailing?.text ?? ""} `;
162
151
  const modeSeg = theme ? `${muted}${mode}${reset}` : mode;
163
152
  const metaSeg = theme ? `${dim}${meta}${reset}` : meta;
164
- const row2 = `${modeSeg} ${strip}${" ".repeat(gap)}${metaSeg}`;
165
-
166
- return renderTaggedCard({
167
- tag: sid,
168
- title: run.name,
169
- trailing,
170
- bodyRows: [row2],
171
- accent: stateAccent,
172
- width,
173
- theme,
174
- });
153
+ const line2 = ` ${modeSeg} ${strip}${" ".repeat(gap)}${metaSeg} `;
154
+
155
+ return [line1, line2];
175
156
  }
176
157
 
177
158
  function runAccent(run: RunSnapshot, theme?: GraphTheme): string {
@@ -207,7 +188,7 @@ function runCardMeta(run: RunSnapshot, now: number): string {
207
188
  const isChain = run.stages.length > 1;
208
189
  const total = run.stages.length;
209
190
  const done = run.stages.filter(
210
- (s) => s.status === "completed" || s.status === "failed",
191
+ (s) => s.status === "completed" || s.status === "failed" || s.status === "skipped",
211
192
  ).length;
212
193
  const ago = run.endedAt !== undefined
213
194
  ? `${fmtDuration(now - run.endedAt)} ago`
@@ -258,7 +239,7 @@ function lastStageDuration(run: RunSnapshot, now: number): string | undefined {
258
239
  // Pick a representative stage duration: the most-recent terminal stage,
259
240
  // or the running stage if everything's still in flight.
260
241
  const candidate =
261
- [...run.stages].reverse().find((s) => s.status === "completed" || s.status === "failed") ??
242
+ [...run.stages].reverse().find((s) => s.status === "completed" || s.status === "failed" || s.status === "skipped") ??
262
243
  run.stages.find((s) => s.status === "running");
263
244
  if (!candidate) return undefined;
264
245
  return stageDurationString(candidate, now);
@@ -359,5 +340,16 @@ function emptyStateLine(theme?: GraphTheme): string {
359
340
  return ` ${hexToAnsi(theme.dim)}no in-flight runs${RESET}`;
360
341
  }
361
342
 
343
+ function statusIconForRun(run: RunSnapshot): string {
344
+ switch (run.status) {
345
+ case "completed": return "✓";
346
+ case "running": return "●";
347
+ case "failed": return "✗";
348
+ case "killed": return "⊘";
349
+ case "pending":
350
+ default: return "○";
351
+ }
352
+ }
353
+
362
354
  // Re-export for callers that need to inspect width budgeting.
363
355
  export { MIN_TITLE_BUDGET };
@@ -13,20 +13,19 @@
13
13
  * 2. After `setWidget` we call `ui.requestRender()` to flush the new
14
14
  * content immediately; pi-subagents does the same in its
15
15
  * `rerenderWidget` helper.
16
- * 3. The widget contents are static per snapshot (no spinner, no live
17
- * ticker) so we do not run an animation timer. Store mutations from
18
- * stage / tool-execution events are the sole render trigger — this
19
- * matches every other workflow status surface (`/workflow status`,
20
- * `renderRunDetail`, the orchestrator overlay) which all render
21
- * instantly without animation.
16
+ * 3. The widget contents are static per snapshot (no spinner), but the
17
+ * rendered lines include wall-clock labels (`3s`, `complete · 4s ago`)
18
+ * and recent-ended visibility. We therefore keep one lightweight
19
+ * one-shot refresh timer while the widget is visible, matching other
20
+ * live Atomic widgets without reintroducing a high-frequency spinner.
22
21
  * 4. The factory builds a pi-tui `Container` of `Text` children styled
23
22
  * via pi's runtime `Theme` (theme.fg, theme.bold). This is what
24
23
  * makes the widget visually distinct from chat content.
25
24
  */
26
25
 
27
26
  import type { Store } from "../shared/store.js";
28
- import type { StoreSnapshot, RunSnapshot } from "../shared/store-types.js";
29
- import { buildThemedWidgetLines } from "./widget.js";
27
+ import type { StoreSnapshot } from "../shared/store-types.js";
28
+ import { buildThemedWidgetLines, nextWidgetRefreshDelayMs } from "./widget.js";
30
29
 
31
30
  export interface PiTheme {
32
31
  fg(color: string, text: string): string;
@@ -52,6 +51,20 @@ interface UiSlice {
52
51
  requestRender?: () => void;
53
52
  }
54
53
 
54
+ interface TimerHandle {
55
+ unref?: () => void;
56
+ }
57
+
58
+ interface TimerApi {
59
+ setTimeout(handler: () => void, delayMs: number): TimerHandle;
60
+ clearTimeout(handle: TimerHandle): void;
61
+ }
62
+
63
+ const defaultTimerApi: TimerApi = {
64
+ setTimeout: (handler, delayMs) => setTimeout(handler, delayMs) as TimerHandle,
65
+ clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
66
+ };
67
+
55
68
  export interface LiveWidgetAPI {
56
69
  ui?: UiSlice;
57
70
  on?: (event: string, handler: (payload: unknown, context?: unknown) => void) => void;
@@ -84,22 +97,44 @@ function widgetFactory(snap: StoreSnapshot): WidgetFactory {
84
97
  export function installStoreWidget(
85
98
  pi: LiveWidgetAPI,
86
99
  storeInstance: Store,
100
+ timers: TimerApi = defaultTimerApi,
87
101
  ): () => void {
88
102
  const ui = pi.ui;
89
103
  if (!ui?.setWidget) return () => {};
90
104
 
105
+ let disposed = false;
106
+ let refreshTimer: TimerHandle | undefined;
107
+
108
+ const clearRefreshTimer = (): void => {
109
+ if (refreshTimer === undefined) return;
110
+ timers.clearTimeout(refreshTimer);
111
+ refreshTimer = undefined;
112
+ };
113
+
114
+ const scheduleRefresh = (snap: StoreSnapshot): void => {
115
+ const delayMs = nextWidgetRefreshDelayMs(snap);
116
+ if (delayMs === undefined) return;
117
+ refreshTimer = timers.setTimeout(() => {
118
+ refreshTimer = undefined;
119
+ rerender();
120
+ }, delayMs);
121
+ refreshTimer.unref?.();
122
+ };
123
+
91
124
  const rerender = (): void => {
125
+ if (disposed) return;
126
+ clearRefreshTimer();
92
127
  try {
93
128
  const snap = storeInstance.snapshot();
94
- const hasActiveRuns = (snap.runs as RunSnapshot[]).some(
95
- (r) => r.endedAt === undefined,
96
- );
97
- if (!hasActiveRuns) {
129
+ const previewLines = buildThemedWidgetLines(snap, undefined);
130
+ if (previewLines.length === 0) {
98
131
  ui.setWidget?.(WIDGET_KEY, undefined);
132
+ ui.requestRender?.();
99
133
  return;
100
134
  }
101
135
  ui.setWidget?.(WIDGET_KEY, widgetFactory(snap), { placement: "aboveEditor" });
102
136
  ui.requestRender?.();
137
+ scheduleRefresh(snap);
103
138
  } catch (err) {
104
139
  if (isStale(err)) return;
105
140
  throw err;
@@ -110,9 +145,12 @@ export function installStoreWidget(
110
145
  rerender();
111
146
 
112
147
  return () => {
148
+ disposed = true;
149
+ clearRefreshTimer();
113
150
  unsubscribe();
114
151
  try {
115
152
  ui.setWidget?.(WIDGET_KEY, undefined);
153
+ ui.requestRender?.();
116
154
  } catch (err) {
117
155
  if (!isStale(err)) throw err;
118
156
  }
@@ -0,0 +1,164 @@
1
+ import type { WorkflowInputEntry } from "../extension/render-result.js";
2
+ import type { GraphTheme } from "./graph-theme.js";
3
+ import { BOLD, hexBg, hexToAnsi, paint, RESET } from "./color-utils.js";
4
+ import {
5
+ formatReviewValue,
6
+ renderWrappedPrefixedLines,
7
+ truncateToWidth,
8
+ visibleWidth,
9
+ wrapPlainText,
10
+ } from "./text-helpers.js";
11
+
12
+ export interface SubmitPaneRenderOpts {
13
+ workflowName: string;
14
+ fields: readonly WorkflowInputEntry[];
15
+ rawText: Record<string, string>;
16
+ theme: GraphTheme;
17
+ width: number;
18
+ }
19
+
20
+ export interface SubmitControlsRenderOpts {
21
+ invalidFieldNames: readonly string[];
22
+ submitFocused: boolean;
23
+ theme: GraphTheme;
24
+ width: number;
25
+ }
26
+
27
+ export function renderWorkflowFormFooterHints(theme: GraphTheme, width: number): string {
28
+ const hints = [
29
+ "Enter to select · ↑/↓ to navigate · Tab through questions to Submit · Esc to cancel",
30
+ "Enter · ↑/↓ · Tab to Submit · Esc",
31
+ "Enter · Tab · Esc",
32
+ "Esc",
33
+ ];
34
+ const selected = hints.find((hint) => visibleWidth(hint) <= width) ?? hints[hints.length - 1]!;
35
+ return paint(truncateToWidth(selected, width, "…"), theme.dim);
36
+ }
37
+
38
+ export function renderAskChoiceRows(
39
+ index: number,
40
+ label: string,
41
+ active: boolean,
42
+ theme: GraphTheme,
43
+ width: number,
44
+ ): string[] {
45
+ const plainPrefix = `${active ? "❯ " : " "}${index}. `;
46
+ const firstPrefix = `${active ? paint("❯ ", theme.accent) : " "}${index}. `;
47
+ return renderWrappedPrefixedLines({
48
+ text: label,
49
+ width,
50
+ plainPrefix,
51
+ firstPrefix,
52
+ styleLine: (line) => active
53
+ ? paint(line, theme.accent, { bold: true })
54
+ : paint(line, theme.textMuted),
55
+ });
56
+ }
57
+
58
+ export function renderSubmitReview(opts: SubmitPaneRenderOpts): string[] {
59
+ const { workflowName, fields, rawText, theme, width } = opts;
60
+ const out: string[] = [paint("Review your inputs", theme.accent, { bold: true }), ""];
61
+ out.push(truncateToWidth(paint("/workflow ", theme.dim) + paint(workflowName, theme.text), width, "…", true));
62
+ for (const field of fields) {
63
+ out.push(truncateToWidth(paint(" ● ", theme.dim) + paint(field.name, theme.textMuted), width, "…", true));
64
+ out.push(...renderReviewValueRows(field, rawText[field.name] ?? "", theme, width));
65
+ }
66
+ return out;
67
+ }
68
+
69
+ export function renderSubmitControls(opts: SubmitControlsRenderOpts): string[] {
70
+ const { invalidFieldNames, submitFocused, theme, width } = opts;
71
+ const invalidCount = invalidFieldNames.length;
72
+ const submitLabel = "SUBMIT";
73
+ const lines: string[] = [];
74
+ if (invalidCount > 0) {
75
+ lines.push(
76
+ ...wrapPlainText(
77
+ `Answer remaining inputs before submitting: ${invalidFieldNames.join(", ")}`,
78
+ width,
79
+ ).map((line) => paint(line, theme.warning)),
80
+ "",
81
+ );
82
+ }
83
+ lines.push(...renderSubmitToolbar(submitLabel, submitFocused, theme, width));
84
+ return lines;
85
+ }
86
+
87
+ function renderSubmitToolbar(
88
+ label: string,
89
+ focused: boolean,
90
+ theme: GraphTheme,
91
+ width: number,
92
+ ): string[] {
93
+ const chromeBg = hexBg(theme.backgroundPanel);
94
+ const button = renderCompactSubmitButton(label, focused, theme, chromeBg);
95
+ const textFg = hexToAnsi(theme.text);
96
+ const mutedFg = hexToAnsi(theme.textMuted);
97
+ const dimFg = hexToAnsi(theme.dim);
98
+ const hint = (key: string, description: string): string =>
99
+ `${chromeBg}${textFg}${BOLD}${key}${RESET}${chromeBg}${mutedFg} ${description}${RESET}${chromeBg}`;
100
+ const hints = [
101
+ hint("enter", "Submit"),
102
+ hint("tab", "Next"),
103
+ hint("shift+tab", "Prev"),
104
+ hint("esc", "Cancel"),
105
+ ].join(`${chromeBg}${dimFg} · ${RESET}${chromeBg}`);
106
+ const leftPad = 1;
107
+ const gap = 2;
108
+ const rightPad = 1;
109
+ const hintBudget = Math.max(0, width - leftPad - button.visibleWidth - gap - rightPad);
110
+ const fittedHints = truncateToWidth(hints, hintBudget, "…", true);
111
+ const hintWidth = visibleWidth(fittedHints);
112
+ const filler = Math.max(0, width - leftPad - button.visibleWidth - gap - hintWidth - rightPad);
113
+ const line = `${chromeBg} ${button.text}${chromeBg}${" ".repeat(gap)}${fittedHints}${chromeBg}${" ".repeat(filler)} ${RESET}`;
114
+ const clipped = truncateToWidth(line, width, "", true);
115
+ return [`${clipped}${chromeBg}${" ".repeat(Math.max(0, width - visibleWidth(clipped)))}${RESET}`];
116
+ }
117
+
118
+ function renderCompactSubmitButton(
119
+ label: string,
120
+ focused: boolean,
121
+ theme: GraphTheme,
122
+ chromeBg: string,
123
+ ): { text: string; visibleWidth: number } {
124
+ const plain = ` ${label} `;
125
+ if (focused) {
126
+ const accentBg = hexBg(theme.accent);
127
+ const fg = hexToAnsi(theme.backgroundPanel);
128
+ return {
129
+ text: `${accentBg}${fg}${BOLD}${plain}${RESET}${chromeBg}`,
130
+ visibleWidth: visibleWidth(plain),
131
+ };
132
+ }
133
+ const labelFg = hexToAnsi(theme.accent);
134
+ return {
135
+ text: `${chromeBg}${labelFg}${BOLD}${plain}${RESET}${chromeBg}`,
136
+ visibleWidth: visibleWidth(plain),
137
+ };
138
+ }
139
+
140
+ function renderReviewValueRows(
141
+ field: WorkflowInputEntry,
142
+ raw: string,
143
+ theme: GraphTheme,
144
+ width: number,
145
+ ): string[] {
146
+ const plainPrefix = " → ";
147
+ const firstPrefix = " " + paint("→ ", theme.dim);
148
+ return renderWrappedPrefixedLines({
149
+ text: formatFieldReviewValue(field, raw),
150
+ width,
151
+ plainPrefix,
152
+ firstPrefix,
153
+ styleLine: (line) => paint(line, theme.text),
154
+ });
155
+ }
156
+
157
+ function formatFieldReviewValue(field: WorkflowInputEntry, raw: string): string {
158
+ if (field.type === "boolean") {
159
+ const normalized = raw.trim().toLowerCase();
160
+ if (normalized.length === 0) return formatReviewValue(raw);
161
+ return normalized === "true" || normalized === "1" ? "on" : "off";
162
+ }
163
+ return formatReviewValue(raw);
164
+ }
@@ -15,7 +15,7 @@ import type { StageSnapshot } from "../shared/store-types.js";
15
15
  import type { GraphTheme } from "./graph-theme.js";
16
16
  import { statusIcon, statusColor } from "./status-helpers.js";
17
17
  import { hexToAnsi, hexBg, RESET, BOLD } from "./color-utils.js";
18
- import { truncateToWidth, visibleWidth } from "./text-helpers.js";
18
+ import { sliceColumns, truncateToWidth, visibleWidth } from "./text-helpers.js";
19
19
 
20
20
  export interface SwitcherState {
21
21
  query: string;
@@ -45,6 +45,25 @@ function padVisible(s: string, width: number): string {
45
45
  return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
46
46
  }
47
47
 
48
+ /**
49
+ * Truncate without appending any ANSI reset before the suffix.
50
+ *
51
+ * Selected rows are wrapped in a single accent run after row text is composed;
52
+ * inserting RESET before the ellipsis would prematurely end that highlight.
53
+ */
54
+ function truncateToWidthWithoutReset(text: string, width: number, suffix = ""): string {
55
+ const targetWidth = Math.max(0, width);
56
+ if (visibleWidth(text) <= targetWidth) return text;
57
+ if (targetWidth === 0) return "";
58
+
59
+ const suffixWidth = visibleWidth(suffix);
60
+ if (suffixWidth >= targetWidth) {
61
+ return sliceColumns(suffix, 0, targetWidth, true);
62
+ }
63
+
64
+ return `${sliceColumns(text, 0, targetWidth - suffixWidth, true)}${suffix}`;
65
+ }
66
+
48
67
  function statusText(status: StageSnapshot["status"]): string {
49
68
  return status === "awaiting_input"
50
69
  ? "awaiting"
@@ -94,8 +113,12 @@ export function renderSwitcher(
94
113
  `${dim}${hint}${RESET}${panelBg}`;
95
114
  lines.push(`${border}│${RESET}${padVisible(header, innerWidth)}${border}│${RESET}`);
96
115
 
97
- // Quiet rule under header
98
- lines.push(`${border}├${"─".repeat(innerWidth)}┤${RESET}`);
116
+ // Quiet rule under header. Keep it inside the rounded panel instead of
117
+ // joining into the side borders with square tee glyphs.
118
+ const rule = innerWidth <= 2
119
+ ? " ".repeat(innerWidth)
120
+ : " " + `${dim}${"─".repeat(innerWidth - 2)}${RESET}${panelBg}` + " ";
121
+ lines.push(`${border}│${RESET}${panelBg}${rule}${border}│${RESET}`);
99
122
 
100
123
  // Stage list — viewport of 8 around selection
101
124
  const maxVisible = 8;
@@ -119,7 +142,7 @@ export function renderSwitcher(
119
142
  if (isSelected) {
120
143
  const metaWidth = visibleWidth(label);
121
144
  const nameBudget = Math.max(4, innerWidth - visibleWidth(` ${icon} `) - metaWidth - 2);
122
- const selectedName = truncateToWidth(stage.name, nameBudget, "…");
145
+ const selectedName = truncateToWidthWithoutReset(stage.name, nameBudget, "…");
123
146
  const prefix = ` ${icon} ${selectedName}`;
124
147
  const visibleRow = `${prefix}${" ".repeat(Math.max(1, innerWidth - visibleWidth(`${prefix}${label}`) - 1))}${label} `;
125
148
  const padded = padVisible(visibleRow, innerWidth);