@bastani/atomic 0.8.4 → 0.8.5

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 (245) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +24 -23
  3. package/dist/builtin/intercom/README.md +5 -5
  4. package/dist/builtin/intercom/index.ts +1 -1
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/intercom/ui/compose.ts +19 -1
  7. package/dist/builtin/intercom/ui/session-list.ts +19 -1
  8. package/dist/builtin/mcp/README.md +3 -3
  9. package/dist/builtin/mcp/commands.ts +1 -1
  10. package/dist/builtin/mcp/host-html-template.ts +1 -1
  11. package/dist/builtin/mcp/mcp-panel.ts +14 -14
  12. package/dist/builtin/mcp/mcp-setup-panel.ts +4 -4
  13. package/dist/builtin/mcp/package.json +1 -1
  14. package/dist/builtin/mcp/tool-result-renderer.ts +1 -1
  15. package/dist/builtin/subagents/README.md +3 -3
  16. package/dist/builtin/subagents/package.json +1 -1
  17. package/dist/builtin/subagents/src/tui/render.ts +1844 -1062
  18. package/dist/builtin/web-access/README.md +1 -1
  19. package/dist/builtin/web-access/curator-page.ts +2 -2
  20. package/dist/builtin/web-access/index.ts +1 -1
  21. package/dist/builtin/web-access/package.json +1 -1
  22. package/dist/builtin/workflows/README.md +34 -7
  23. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +23 -4
  24. package/dist/builtin/workflows/builtin/ralph.ts +1 -1
  25. package/dist/builtin/workflows/package.json +1 -1
  26. package/dist/builtin/workflows/skills/workflow/SKILL.md +75 -16
  27. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +34 -11
  28. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +111 -20
  29. package/dist/builtin/workflows/src/extension/discovery.ts +32 -4
  30. package/dist/builtin/workflows/src/extension/index.ts +347 -63
  31. package/dist/builtin/workflows/src/extension/render-call.ts +3 -1
  32. package/dist/builtin/workflows/src/extension/render-result.ts +7 -0
  33. package/dist/builtin/workflows/src/extension/runtime.ts +4 -2
  34. package/dist/builtin/workflows/src/extension/wiring.ts +32 -8
  35. package/dist/builtin/workflows/src/extension/workflow-schema.ts +36 -14
  36. package/dist/builtin/workflows/src/runs/background/runner.ts +2 -2
  37. package/dist/builtin/workflows/src/runs/background/status.ts +89 -0
  38. package/dist/builtin/workflows/src/runs/foreground/executor.ts +338 -78
  39. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +2 -0
  40. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +55 -7
  41. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +146 -10
  42. package/dist/builtin/workflows/src/shared/store.ts +29 -0
  43. package/dist/builtin/workflows/src/shared/types.ts +25 -4
  44. package/dist/builtin/workflows/src/tui/graph-canvas.ts +69 -2
  45. package/dist/builtin/workflows/src/tui/graph-view.ts +97 -182
  46. package/dist/builtin/workflows/src/tui/header.ts +36 -20
  47. package/dist/builtin/workflows/src/tui/inline-form-card.ts +129 -46
  48. package/dist/builtin/workflows/src/tui/inline-form-editor.ts +111 -36
  49. package/dist/builtin/workflows/src/tui/inputs-picker.ts +311 -91
  50. package/dist/builtin/workflows/src/tui/layout.ts +1 -1
  51. package/dist/builtin/workflows/src/tui/node-card.ts +66 -37
  52. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +20 -6
  53. package/dist/builtin/workflows/src/tui/prompt-card.ts +262 -85
  54. package/dist/builtin/workflows/src/tui/run-detail.ts +50 -31
  55. package/dist/builtin/workflows/src/tui/session-confirm.ts +21 -14
  56. package/dist/builtin/workflows/src/tui/session-picker.ts +35 -26
  57. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +531 -960
  58. package/dist/builtin/workflows/src/tui/status-helpers.ts +6 -0
  59. package/dist/builtin/workflows/src/tui/status-list.ts +8 -4
  60. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +7 -2
  61. package/dist/builtin/workflows/src/tui/switcher.ts +55 -25
  62. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +33 -1
  63. package/dist/builtin/workflows/src/tui/workflow-list.ts +10 -6
  64. package/dist/cli/args.d.ts.map +1 -1
  65. package/dist/cli/args.js +1 -1
  66. package/dist/cli/args.js.map +1 -1
  67. package/dist/config.d.ts.map +1 -1
  68. package/dist/config.js +20 -6
  69. package/dist/config.js.map +1 -1
  70. package/dist/core/agent-session-services.d.ts +3 -3
  71. package/dist/core/agent-session-services.d.ts.map +1 -1
  72. package/dist/core/agent-session-services.js.map +1 -1
  73. package/dist/core/agent-session.d.ts +7 -7
  74. package/dist/core/agent-session.d.ts.map +1 -1
  75. package/dist/core/agent-session.js.map +1 -1
  76. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  77. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  78. package/dist/core/compaction/branch-summarization.js.map +1 -1
  79. package/dist/core/compaction/compaction.d.ts +3 -3
  80. package/dist/core/compaction/compaction.d.ts.map +1 -1
  81. package/dist/core/compaction/compaction.js.map +1 -1
  82. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  83. package/dist/core/export-html/tool-renderer.js.map +1 -1
  84. package/dist/core/extensions/loader.d.ts +3 -2
  85. package/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/dist/core/extensions/loader.js +24 -12
  87. package/dist/core/extensions/loader.js.map +1 -1
  88. package/dist/core/extensions/runner.d.ts.map +1 -1
  89. package/dist/core/extensions/runner.js +6 -0
  90. package/dist/core/extensions/runner.js.map +1 -1
  91. package/dist/core/extensions/types.d.ts +28 -17
  92. package/dist/core/extensions/types.d.ts.map +1 -1
  93. package/dist/core/extensions/types.js.map +1 -1
  94. package/dist/core/package-manager.d.ts +1 -0
  95. package/dist/core/package-manager.d.ts.map +1 -1
  96. package/dist/core/package-manager.js +65 -28
  97. package/dist/core/package-manager.js.map +1 -1
  98. package/dist/core/resource-loader.d.ts.map +1 -1
  99. package/dist/core/resource-loader.js +13 -5
  100. package/dist/core/resource-loader.js.map +1 -1
  101. package/dist/core/sdk.d.ts +3 -3
  102. package/dist/core/sdk.d.ts.map +1 -1
  103. package/dist/core/sdk.js.map +1 -1
  104. package/dist/core/session-manager.d.ts.map +1 -1
  105. package/dist/core/session-manager.js +1 -1
  106. package/dist/core/session-manager.js.map +1 -1
  107. package/dist/core/settings-manager.d.ts +2 -0
  108. package/dist/core/settings-manager.d.ts.map +1 -1
  109. package/dist/core/settings-manager.js.map +1 -1
  110. package/dist/core/slash-commands.d.ts.map +1 -1
  111. package/dist/core/slash-commands.js +1 -1
  112. package/dist/core/slash-commands.js.map +1 -1
  113. package/dist/core/system-prompt.d.ts.map +1 -1
  114. package/dist/core/system-prompt.js +5 -3
  115. package/dist/core/system-prompt.js.map +1 -1
  116. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
  117. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
  118. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -1
  119. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
  120. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
  121. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
  122. package/dist/core/tools/ask-user-question/view/dialog-builder.js +6 -6
  123. package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
  124. package/dist/core/tools/bash.d.ts.map +1 -1
  125. package/dist/core/tools/bash.js +1 -1
  126. package/dist/core/tools/bash.js.map +1 -1
  127. package/dist/core/tools/find.d.ts.map +1 -1
  128. package/dist/core/tools/find.js +1 -1
  129. package/dist/core/tools/find.js.map +1 -1
  130. package/dist/core/tools/grep.d.ts.map +1 -1
  131. package/dist/core/tools/grep.js +7 -4
  132. package/dist/core/tools/grep.js.map +1 -1
  133. package/dist/core/tools/index.d.ts +3 -2
  134. package/dist/core/tools/index.d.ts.map +1 -1
  135. package/dist/core/tools/index.js.map +1 -1
  136. package/dist/core/tools/ls.d.ts.map +1 -1
  137. package/dist/core/tools/ls.js +3 -2
  138. package/dist/core/tools/ls.js.map +1 -1
  139. package/dist/core/tools/read.d.ts.map +1 -1
  140. package/dist/core/tools/read.js +2 -2
  141. package/dist/core/tools/read.js.map +1 -1
  142. package/dist/core/tools/render-utils.d.ts +2 -1
  143. package/dist/core/tools/render-utils.d.ts.map +1 -1
  144. package/dist/core/tools/render-utils.js.map +1 -1
  145. package/dist/core/tools/todos.d.ts.map +1 -1
  146. package/dist/core/tools/todos.js +1 -1
  147. package/dist/core/tools/todos.js.map +1 -1
  148. package/dist/core/tools/tool-definition-wrapper.d.ts +4 -3
  149. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  150. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  151. package/dist/core/tools/write.d.ts.map +1 -1
  152. package/dist/core/tools/write.js +1 -1
  153. package/dist/core/tools/write.js.map +1 -1
  154. package/dist/index.d.ts +2 -1
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js +2 -1
  157. package/dist/index.js.map +1 -1
  158. package/dist/main.d.ts.map +1 -1
  159. package/dist/main.js +2 -2
  160. package/dist/main.js.map +1 -1
  161. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  162. package/dist/modes/interactive/components/assistant-message.js +3 -3
  163. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  164. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/bash-execution.js +3 -3
  166. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  167. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  168. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  169. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  170. package/dist/modes/interactive/components/chat-message-renderer.d.ts +2 -1
  171. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  172. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  173. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  175. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  176. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/config-selector.js +1 -1
  178. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  179. package/dist/modes/interactive/components/custom-editor.d.ts +3 -0
  180. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/custom-editor.js +13 -3
  182. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  183. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/footer.js +1 -1
  185. package/dist/modes/interactive/components/footer.js.map +1 -1
  186. package/dist/modes/interactive/components/index.d.ts +2 -1
  187. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  188. package/dist/modes/interactive/components/index.js +2 -1
  189. package/dist/modes/interactive/components/index.js.map +1 -1
  190. package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
  191. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  192. package/dist/modes/interactive/components/keybinding-hints.js +47 -5
  193. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  194. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/login-dialog.js +5 -5
  196. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  197. package/dist/modes/interactive/components/model-selector.d.ts +3 -3
  198. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  200. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
  201. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/scoped-models-selector.js +7 -7
  203. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  204. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/session-selector.js +8 -8
  206. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  207. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/settings-selector.js +3 -3
  209. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  210. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
  212. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  213. package/dist/modes/interactive/components/tool-execution.d.ts +10 -12
  214. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/tool-execution.js +3 -3
  216. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  217. package/dist/modes/interactive/components/working-status.d.ts +25 -0
  218. package/dist/modes/interactive/components/working-status.d.ts.map +1 -0
  219. package/dist/modes/interactive/components/working-status.js +28 -0
  220. package/dist/modes/interactive/components/working-status.js.map +1 -0
  221. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  222. package/dist/modes/interactive/interactive-mode.js +8 -7
  223. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  224. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  225. package/dist/modes/rpc/rpc-mode.js +8 -0
  226. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  227. package/dist/modes/rpc/rpc-types.d.ts +5 -5
  228. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  229. package/dist/modes/rpc/rpc-types.js.map +1 -1
  230. package/dist/utils/tools-manager.d.ts.map +1 -1
  231. package/dist/utils/tools-manager.js.map +1 -1
  232. package/docs/development.md +2 -2
  233. package/docs/extensions.md +7 -7
  234. package/docs/packages.md +11 -8
  235. package/docs/quickstart.md +2 -2
  236. package/docs/rpc.md +1 -1
  237. package/docs/sdk.md +14 -11
  238. package/docs/session-format.md +1 -1
  239. package/docs/sessions.md +10 -10
  240. package/docs/settings.md +1 -1
  241. package/docs/terminal-setup.md +9 -9
  242. package/docs/tmux.md +10 -10
  243. package/docs/tui.md +2 -2
  244. package/docs/usage.md +9 -9
  245. package/package.json +6 -1
@@ -4,14 +4,13 @@
4
4
  * Visual contract (DESIGN.md §5):
5
5
  * - Rounded border `╭╮╰╯` only. No square or ASCII art.
6
6
  * - Border colour carries status. `running` pulses via sine lerp
7
- * against the dim border (focus locks the pulse). `completed` /
8
- * `failed` stay status-coloured regardless of focus. `pending`
9
- * sits on `borderDim` and lifts to `borderActive` when focused.
7
+ * against the dim border (focus locks the pulse). `paused`,
8
+ * `completed` / `failed` stay status-coloured regardless of focus.
9
+ * `pending` sits on `borderDim` and lifts to `borderActive` when focused.
10
10
  * - When focused, the centred title segment becomes a compact accent
11
- * "tab": `▸ name` painted with `theme.accent` bg + `theme.surface`
12
- * fg + bold. The tab is the only focus signal — the surrounding
13
- * border is reserved for status. No `[focused]` text, no glow,
14
- * no resize.
11
+ * tab painted with `theme.accent` bg + `theme.surface` fg + bold.
12
+ * The tab is the only focus signal — the surrounding border is
13
+ * reserved for status. No `[focused]` text, no glow, no resize.
15
14
  * - Single centred duration line in the body, coloured by status.
16
15
  *
17
16
  * Reuses the existing `paint(...)` color-utils helper (a thin wrapper
@@ -28,8 +27,9 @@
28
27
 
29
28
  import type { StageSnapshot, StageStatus } from "../shared/store-types.js";
30
29
  import type { GraphTheme } from "./graph-theme.js";
31
- import { fmtDuration } from "./status-helpers.js";
30
+ import { fmtDuration, statusIcon } from "./status-helpers.js";
32
31
  import { lerpColor, hexToAnsi, hexBg, paint, RESET, BOLD } from "./color-utils.js";
32
+ import { truncateToWidth, visibleWidth } from "./text-helpers.js";
33
33
  import { NODE_W, NODE_H } from "./layout.js";
34
34
 
35
35
  export interface NodeCardOpts {
@@ -43,11 +43,6 @@ export interface NodeCardOpts {
43
43
  stages?: readonly StageSnapshot[];
44
44
  }
45
45
 
46
- /** Glyph that prefixes the focused-tab title; matches the compact
47
- * cursor `❯` vocabulary used elsewhere but rotated to fit inline
48
- * inside the top border (`▸` reads as a small wedge in the slot). */
49
- const FOCUS_TAB_GLYPH = "▸";
50
-
51
46
  /** Sine-eased pulse `t ∈ [0, 1]`. Phase 0 ≈ quiet, 0.5 ≈ peak. */
52
47
  function pulseT(phase: number): number {
53
48
  return (Math.sin(phase * Math.PI * 2 - Math.PI / 2) + 1) / 2;
@@ -64,6 +59,8 @@ function pickBorder(
64
59
  // Focus locks the pulse at peak. Status colour wins either way.
65
60
  if (focused) return theme.warning;
66
61
  return lerpColor(theme.borderDim, theme.warning, pulseT(phase));
62
+ case "paused":
63
+ return theme.warning;
67
64
  case "awaiting_input":
68
65
  if (focused) return theme.info;
69
66
  return lerpColor(theme.borderDim, theme.info, pulseT(phase));
@@ -85,6 +82,8 @@ function durationColor(status: StageStatus, theme: GraphTheme): string {
85
82
  switch (status) {
86
83
  case "running":
87
84
  return theme.warning;
85
+ case "paused":
86
+ return theme.warning;
88
87
  case "awaiting_input":
89
88
  return theme.info;
90
89
  case "completed":
@@ -107,7 +106,7 @@ function blockedBadgeText(
107
106
 
108
107
  const upstream = stages?.find((s) => s.id === blockedBy)?.name ?? blockedBy;
109
108
  const withUpstream = `${base} by ${upstream}`;
110
- if (withUpstream.length <= width) return withUpstream;
109
+ if (visibleWidth(withUpstream) <= width) return withUpstream;
111
110
  return base;
112
111
  }
113
112
 
@@ -117,10 +116,28 @@ function durationText(stage: StageSnapshot): string {
117
116
  return "—";
118
117
  }
119
118
 
120
- function truncate(s: string, maxLen: number): string {
121
- if (maxLen <= 0) return "";
122
- if (s.length <= maxLen) return s;
123
- return s.slice(0, Math.max(1, maxLen - 1)) + "";
119
+ function metaText(stage: StageSnapshot): string {
120
+ if (stage.model) return stage.model;
121
+ const deps = stage.parentIds.length;
122
+ if (deps === 0) return "root";
123
+ return deps === 1 ? "1 dep" : `${deps} deps`;
124
+ }
125
+
126
+ function statusLabel(status: StageStatus): string {
127
+ switch (status) {
128
+ case "awaiting_input":
129
+ return "awaiting input";
130
+ case "completed":
131
+ return "complete";
132
+ default:
133
+ return status.replace(/_/g, " ");
134
+ }
135
+ }
136
+
137
+ function truncate(s: string, maxWidth: number): string {
138
+ if (maxWidth <= 0) return "";
139
+ if (visibleWidth(s) <= maxWidth) return s;
140
+ return truncateToWidth(s, maxWidth, "…");
124
141
  }
125
142
 
126
143
  /**
@@ -138,7 +155,8 @@ function centreColored(
138
155
  opts: { bold?: boolean } = {},
139
156
  ): string {
140
157
  const safe = truncate(content, width);
141
- const pad = width - safe.length;
158
+ const safeWidth = visibleWidth(safe);
159
+ const pad = Math.max(0, width - safeWidth);
142
160
  const left = Math.max(0, Math.floor(pad / 2));
143
161
  const right = Math.max(0, pad - left);
144
162
  const bold = opts.bold ? BOLD : "";
@@ -155,7 +173,7 @@ function centreColored(
155
173
  * width so the caller can pad the surrounding dashes correctly.
156
174
  *
157
175
  * When `focused`, the slot reads as a small accent-coloured tab:
158
- * `╭── stage ──╮`
176
+ * `╭── stage ──╮`
159
177
  * Otherwise it falls back to the historical bold-title shape:
160
178
  * `╭── stage ──╮`
161
179
  *
@@ -171,25 +189,23 @@ function buildTitleSlot(
171
189
  theme: GraphTheme,
172
190
  cardBg: string,
173
191
  ): { slot: string; visibleWidth: number } {
174
- const overhead = focused ? FOCUS_TAB_GLYPH.length + 1 /* space */ : 0;
175
- const maxName = Math.max(2, innerWidth - 4 - overhead);
192
+ const maxName = Math.max(2, innerWidth - 4);
176
193
  const safeName = truncate(name, maxName);
177
194
  if (focused) {
178
- // ` ▸ name ` — flanking spaces sit on the accent tab so the
179
- // pill reads as a single coloured run. Use `paint` to combine
180
- // bg + fg + bold + RESET in one ANSI sequence (re-priming the
181
- // card stratum afterwards so the dashes outside the slot stay
182
- // on the body bg).
183
- const tabText = ` ${FOCUS_TAB_GLYPH} ${safeName} `;
195
+ // Flanking spaces sit on the accent tab so the pill reads as a
196
+ // single coloured run. Use `paint` to combine bg + fg + bold +
197
+ // RESET in one ANSI sequence, then re-prime the card stratum so
198
+ // the dashes outside the slot stay on the body bg.
199
+ const tabText = ` ${safeName} `;
184
200
  const styled = `${paint(tabText, theme.surface, {
185
201
  bg: theme.accent,
186
202
  bold: true,
187
203
  })}${cardBg}`;
188
- return { slot: styled, visibleWidth: tabText.length };
204
+ return { slot: styled, visibleWidth: visibleWidth(tabText) };
189
205
  }
190
206
  const titleRaw = ` ${safeName} `;
191
207
  const styled = `${BOLD}${titleRaw}${RESET}${cardBg}`;
192
- return { slot: styled, visibleWidth: titleRaw.length };
208
+ return { slot: styled, visibleWidth: visibleWidth(titleRaw) };
193
209
  }
194
210
 
195
211
  /**
@@ -213,8 +229,8 @@ export function renderNodeCard(stage: StageSnapshot, opts: NodeCardOpts): string
213
229
  const bg = hexBg(theme.bg);
214
230
  const innerWidth = Math.max(2, width - 2);
215
231
 
216
- // Title sits inside the top border: ╭── name ──╮ or, when focused,
217
- // ╭── name ──╮ with an accent-coloured pill on the name slot.
232
+ // Title sits inside the top border. Focus adds an accent-coloured
233
+ // pill to the name slot without adding an extra glyph.
218
234
  const { slot: titleSlot, visibleWidth: titleVisibleWidth } = buildTitleSlot(
219
235
  stage.name,
220
236
  innerWidth,
@@ -231,25 +247,38 @@ export function renderNodeCard(stage: StageSnapshot, opts: NodeCardOpts): string
231
247
  const top = `${bg}${bc}╭${topMiddle}╮${RESET}`;
232
248
  const bottom = `${bg}${bc}╰${"─".repeat(innerWidth)}╯${RESET}`;
233
249
 
234
- // Interior — single centred duration line. Each `│` border is
235
- // followed by a `bg`-primed centred run so the inner cells stay on
236
- // the card stratum.
250
+ // Interior — compact status + duration. Each `│` border is followed
251
+ // by a `bg`-primed centred run so the inner cells stay on the card
252
+ // stratum without leaving the cards visually hollow.
237
253
  const bodyText =
238
254
  stage.status === "blocked"
239
255
  ? blockedBadgeText(stage, opts.stages, innerWidth)
256
+ : stage.status === "paused"
257
+ ? "❚❚ paused"
240
258
  : durationText(stage);
241
259
  const bodyHex = durationColor(stage.status, theme);
260
+ const statusText = `${statusIcon(stage.status)} ${statusLabel(stage.status)}`;
261
+ const statusLine =
262
+ `${bg}${bc}│${RESET}` +
263
+ centreColored(statusText, innerWidth, bodyHex, bg, {
264
+ bold: stage.status === "running" || stage.status === "awaiting_input",
265
+ }) +
266
+ `${bg}${bc}│${RESET}`;
242
267
  const durLine =
243
268
  `${bg}${bc}│${RESET}` +
244
269
  centreColored(bodyText, innerWidth, bodyHex, bg, {
245
270
  bold: stage.status === "blocked",
246
271
  }) +
247
272
  `${bg}${bc}│${RESET}`;
273
+ const metaLine =
274
+ `${bg}${bc}│${RESET}` +
275
+ centreColored(metaText(stage), innerWidth, theme.dim, bg) +
276
+ `${bg}${bc}│${RESET}`;
248
277
 
249
278
  const interior: string[] =
250
279
  stage.status === "awaiting_input"
251
280
  ? [
252
- durLine,
281
+ statusLine,
253
282
  `${bg}${bc}│${RESET}` +
254
283
  centreColored("waiting for response", innerWidth, theme.info, bg) +
255
284
  `${bg}${bc}│${RESET}`,
@@ -257,7 +286,7 @@ export function renderNodeCard(stage: StageSnapshot, opts: NodeCardOpts): string
257
286
  centreColored("↵ enter to respond", innerWidth, theme.dim, bg) +
258
287
  `${bg}${bc}│${RESET}`,
259
288
  ]
260
- : [durLine];
289
+ : [durLine, statusLine, metaLine];
261
290
 
262
291
  // Pad / clip to exactly `height` lines.
263
292
  const contentRows = Math.max(0, height - 2);
@@ -16,10 +16,10 @@
16
16
  */
17
17
 
18
18
  import type { Store } from "../shared/store.js";
19
- import type { ChatMessageRenderOptions } from "@bastani/atomic";
19
+ import type { ChatMessageRenderOptions, ReadonlyFooterDataProvider } from "@bastani/atomic";
20
20
  import { WorkflowAttachPane } from "./workflow-attach-pane.js";
21
21
  import { deriveGraphThemeFromPiTheme } from "./graph-theme.js";
22
- import { killRun } from "../runs/background/status.js";
22
+ 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";
@@ -41,6 +41,7 @@ export interface OverlayUISurface {
41
41
  custom?: PiCustomOverlayFunction;
42
42
  getEditorComponent?: () => PiEditorFactory | undefined;
43
43
  getChatRenderSettings?: () => OverlayChatRenderSettings | undefined;
44
+ getFooterDataProvider?: () => ReadonlyFooterDataProvider;
44
45
  }
45
46
 
46
47
  export interface OverlayPiSurface {
@@ -104,6 +105,12 @@ export interface BuildGraphOverlayAdapterOpts {
104
105
  * Defaults to the singleton registry registered alongside the store.
105
106
  */
106
107
  stageControlRegistry?: StageControlRegistry;
108
+ /**
109
+ * Destructive kill hook used by graph-mode `q`. The extension factory
110
+ * supplies this so persistence can record a terminal event before the run is
111
+ * removed from live history/status.
112
+ */
113
+ onKillRun?: (runId: string) => void;
107
114
  }
108
115
 
109
116
  export function buildGraphOverlayAdapter(
@@ -112,6 +119,9 @@ export function buildGraphOverlayAdapter(
112
119
  buildOpts: BuildGraphOverlayAdapterOpts = {},
113
120
  ): GraphOverlayPort {
114
121
  const registry = buildOpts.stageControlRegistry ?? defaultStageControlRegistry;
122
+ const killRun = buildOpts.onKillRun ?? ((id: string): void => {
123
+ destroyRun(id, { store, cancellation: cancellationRegistry });
124
+ });
115
125
  let currentView: WorkflowAttachPane | null = null;
116
126
  // pi-tui returns an OverlayHandle via `options.onHandle`. We hold onto
117
127
  // it so toggle() can flip `setHidden` rather than remounting the
@@ -192,12 +202,17 @@ export function buildGraphOverlayAdapter(
192
202
  ): void {
193
203
  // Already mounted but hidden — flip visibility without remounting.
194
204
  if (mounted && currentHandle?.isHidden()) {
205
+ currentView?.retarget(runId, stageId);
195
206
  setMouseScrollTracking(currentView?.wantsMouseScrollTracking() ?? true);
196
207
  currentHandle.setHidden(false);
197
208
  currentHandle.focus();
198
209
  return;
199
210
  }
200
- if (mounted) return; // already showing.
211
+ if (mounted) {
212
+ currentView?.retarget(runId, stageId);
213
+ setMouseScrollTracking(currentView?.wantsMouseScrollTracking() ?? true);
214
+ return;
215
+ }
201
216
 
202
217
  const ui = surface?.ui ?? pi.ui;
203
218
  const custom = ui?.custom;
@@ -230,15 +245,14 @@ export function buildGraphOverlayAdapter(
230
245
  uiStatus,
231
246
  onClose: finish,
232
247
  onHide: hideMounted,
233
- onKill: (id) => {
234
- killRun(id, { store, cancellation: cancellationRegistry });
235
- },
248
+ onKill: killRun,
236
249
  initialAttachStageId: stageId,
237
250
  piTui: tui,
238
251
  piTheme: theme,
239
252
  piKeybindings: keybindings,
240
253
  piEditorFactory: ui?.getEditorComponent?.(),
241
254
  getChatRenderSettings: ui?.getChatRenderSettings,
255
+ footerData: ui?.getFooterDataProvider?.(),
242
256
  // Pi-tui owns terminal dimensions; thread its row count down
243
257
  // so the overlay frame fills the actual viewport rather than
244
258
  // a hard-coded 32-row rectangle. Returning `undefined` keeps