@bastani/atomic 0.8.4-0 → 0.8.5-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 (245) hide show
  1. package/CHANGELOG.md +16 -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
@@ -24,6 +24,7 @@ import { renderBandHeader } from "./header.js";
24
24
  import type { BandBadge } from "./header.js";
25
25
  import { fmtDuration, statusIcon, statusColor } from "./status-helpers.js";
26
26
  import { hexToAnsi, RESET, BOLD } from "./color-utils.js";
27
+ import { truncateToWidth, visibleWidth } from "./text-helpers.js";
27
28
 
28
29
  const SHORT_ID_LEN = 6;
29
30
  const STAGE_NAME_COL = 14;
@@ -34,6 +35,8 @@ export interface RenderRunDetailOpts {
34
35
  theme?: GraphTheme;
35
36
  /** Optional clock override for tests. */
36
37
  now?: number;
38
+ /** Optional render width (cells) for truncating long/wide values. */
39
+ width?: number;
37
40
  }
38
41
 
39
42
  /**
@@ -44,15 +47,16 @@ export function renderRunDetail(
44
47
  opts: RenderRunDetailOpts = {},
45
48
  ): string {
46
49
  const now = opts.now ?? Date.now();
47
- if (opts.theme === undefined) return renderPlain(detail, now);
48
- return renderThemed(detail, now, opts.theme);
50
+ const width = Math.max(32, opts.width ?? 80);
51
+ if (opts.theme === undefined) return renderPlain(detail, now, width);
52
+ return renderThemed(detail, now, opts.theme, width);
49
53
  }
50
54
 
51
55
  // ---------------------------------------------------------------------------
52
56
  // Plain renderer — used by tests and headless consumers
53
57
  // ---------------------------------------------------------------------------
54
58
 
55
- function renderPlain(detail: RunDetail, now: number): string {
59
+ function renderPlain(detail: RunDetail, now: number, width: number): string {
56
60
  const out: string[] = [];
57
61
 
58
62
  const sid = shortId(detail.runId);
@@ -60,14 +64,17 @@ function renderPlain(detail: RunDetail, now: number): string {
60
64
  const innerLabel = ` RUN ${sid} `;
61
65
  const inner = "─".repeat(innerLabel.length);
62
66
  out.push(` ╭${inner}╮`);
63
- out.push(` │${innerLabel}│ ${detail.name} ${stateBadge}`);
67
+ const headerTailW = visibleWidth(` │${innerLabel}│ `) + visibleWidth(` ${stateBadge}`);
68
+ const headerName = truncateToWidth(detail.name, Math.max(1, width - headerTailW), "…");
69
+ out.push(` │${innerLabel}│ ${headerName} ${stateBadge}`);
64
70
  out.push(` ╰${inner}╯`);
65
71
  out.push("");
66
72
 
67
73
  // Summary key/value lines
68
74
  for (const [k, v] of summaryRows(detail, now)) {
69
75
  if (v === undefined) continue;
70
- out.push(` ${pad(k, KEY_COL)}${v}`);
76
+ const value = truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…");
77
+ out.push(` ${pad(k, KEY_COL)}${value}`);
71
78
  }
72
79
  out.push("");
73
80
 
@@ -78,9 +85,10 @@ function renderPlain(detail: RunDetail, now: number): string {
78
85
  out.push(" (no stages recorded yet)");
79
86
  } else {
80
87
  for (const stage of detail.stages) {
81
- out.push(` ${stageLinePlain(stage, now)}`);
88
+ out.push(` ${stageLinePlain(stage, now, width - 2)}`);
82
89
  if (stage.error) {
83
- out.push(` ${" ".repeat(STAGE_NAME_COL + 2)}error ${stage.error.split("\n")[0]?.slice(0, 60) ?? ""}`);
90
+ const err = truncateToWidth(stage.error.split("\n")[0] ?? "", Math.max(1, width - STAGE_NAME_COL - 11), "");
91
+ out.push(` ${" ".repeat(STAGE_NAME_COL + 2)}error ${err}`);
84
92
  }
85
93
  }
86
94
  }
@@ -92,16 +100,16 @@ function renderPlain(detail: RunDetail, now: number): string {
92
100
  out.push("▎ ARTIFACTS");
93
101
  out.push("");
94
102
  for (const [k, v] of artifactRows) {
95
- out.push(` ${pad(k, KEY_COL)}${v}`);
103
+ out.push(` ${pad(k, KEY_COL)}${truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…")}`);
96
104
  }
97
105
  out.push("");
98
106
  }
99
107
 
100
108
  // Action hints
101
109
  if (detail.endedAt === undefined) {
102
- out.push(` ▸ workflow interrupt id=${sid} cancel`);
110
+ out.push(truncateToWidth(` ▸ workflow interrupt id=${sid} cancel`, width, "…"));
103
111
  } else {
104
- out.push(` ▸ workflow resume id=${sid} reopen graph`);
112
+ out.push(truncateToWidth(` ▸ workflow resume id=${sid} reopen graph`, width, "…"));
105
113
  }
106
114
 
107
115
  return out.join("\n");
@@ -111,7 +119,7 @@ function renderPlain(detail: RunDetail, now: number): string {
111
119
  // Themed renderer — ANSI Catppuccin chrome
112
120
  // ---------------------------------------------------------------------------
113
121
 
114
- function renderThemed(detail: RunDetail, now: number, theme: GraphTheme): string {
122
+ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme, width: number): string {
115
123
  const out: string[] = [];
116
124
  const mauve = hexToAnsi(theme.mauve);
117
125
  const muted = hexToAnsi(theme.textMuted);
@@ -128,7 +136,7 @@ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme): string
128
136
  label: `RUN ${sid}`,
129
137
  subtitle: detail.name,
130
138
  badges,
131
- width: 64,
139
+ width: Math.min(64, width),
132
140
  theme,
133
141
  }));
134
142
  out.push("");
@@ -136,7 +144,8 @@ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme): string
136
144
  // Summary key/value
137
145
  for (const [k, v] of summaryRows(detail, now)) {
138
146
  if (v === undefined) continue;
139
- out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${text}${v}${RESET}`);
147
+ const value = truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…");
148
+ out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${text}${value}${RESET}`);
140
149
  }
141
150
  out.push("");
142
151
 
@@ -147,11 +156,12 @@ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme): string
147
156
  out.push(` ${dim}(no stages recorded yet)${RESET}`);
148
157
  } else {
149
158
  for (const stage of detail.stages) {
150
- out.push(" " + stageLineThemed(stage, now, theme));
159
+ out.push(" " + stageLineThemed(stage, now, theme, width - 2));
151
160
  if (stage.error) {
152
161
  const errFg = hexToAnsi(theme.error);
162
+ const err = truncateToWidth(stage.error.split("\n")[0] ?? "", Math.max(1, width - STAGE_NAME_COL - 13), "…");
153
163
  out.push(
154
- ` ${" ".repeat(STAGE_NAME_COL + 2)}${muted}error${RESET} ${errFg}${stage.error.split("\n")[0]?.slice(0, 60) ?? ""}${RESET}`,
164
+ ` ${" ".repeat(STAGE_NAME_COL + 2)}${muted}error${RESET} ${errFg}${err}${RESET}`,
155
165
  );
156
166
  }
157
167
  }
@@ -164,7 +174,7 @@ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme): string
164
174
  out.push(`${mauve}▎${RESET} ${muted}${BOLD}ARTIFACTS${RESET}`);
165
175
  out.push("");
166
176
  for (const [k, v] of artifactRows) {
167
- out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${dim}${v}${RESET}`);
177
+ out.push(` ${muted}${pad(k, KEY_COL)}${RESET}${dim}${truncateToWidth(v, Math.max(1, width - 2 - KEY_COL), "…")}${RESET}`);
168
178
  }
169
179
  out.push("");
170
180
  }
@@ -172,11 +182,11 @@ function renderThemed(detail: RunDetail, now: number, theme: GraphTheme): string
172
182
  // Action hints
173
183
  if (detail.endedAt === undefined) {
174
184
  out.push(
175
- ` ${dim}▸${RESET} ${accent}workflow interrupt id=${sid}${RESET}${dim} cancel${RESET}`,
185
+ truncateToWidth(` ${dim}▸${RESET} ${accent}workflow interrupt id=${sid}${RESET}${dim} cancel${RESET}`, width, "…"),
176
186
  );
177
187
  } else {
178
188
  out.push(
179
- ` ${dim}▸${RESET} ${accent}workflow resume id=${sid}${RESET}${dim} reopen graph${RESET}`,
189
+ truncateToWidth(` ${dim}▸${RESET} ${accent}workflow resume id=${sid}${RESET}${dim} reopen graph${RESET}`, width, "…"),
180
190
  );
181
191
  }
182
192
 
@@ -206,7 +216,7 @@ function summaryRows(detail: RunDetail, now: number): Array<[string, string | un
206
216
  rows.push(["elapsed", fmtDuration(duration)]);
207
217
  }
208
218
  if (detail.error) {
209
- rows.push(["error", detail.error.split("\n")[0]?.slice(0, 60) ?? ""]);
219
+ rows.push(["error", detail.error.split("\n")[0] ?? ""]);
210
220
  }
211
221
  return rows;
212
222
  }
@@ -214,7 +224,7 @@ function summaryRows(detail: RunDetail, now: number): Array<[string, string | un
214
224
  function artifactRowsFor(detail: RunDetail): Array<[string, string]> {
215
225
  const rows: Array<[string, string]> = [];
216
226
  if (detail.result !== undefined && Object.keys(detail.result).length > 0) {
217
- rows.push(["result", JSON.stringify(detail.result).slice(0, 80)]);
227
+ rows.push(["result", JSON.stringify(detail.result)]);
218
228
  }
219
229
  const inputKeys = Object.keys(detail.inputs);
220
230
  if (inputKeys.length > 0) {
@@ -223,20 +233,22 @@ function artifactRowsFor(detail: RunDetail): Array<[string, string]> {
223
233
  return rows;
224
234
  }
225
235
 
226
- function stageLinePlain(stage: StageSnapshot, now: number): string {
236
+ function stageLinePlain(stage: StageSnapshot, now: number, width: number): string {
227
237
  const icon = statusIcon(stage.status);
228
238
  const dur = stageDurationString(stage, now);
229
239
  const activity = stageActivityString(stage);
240
+ const name = truncateToWidth(`${icon} ${stage.name}`, STAGE_NAME_COL + 2, "…");
241
+ const activityText = activity ? truncateToWidth(activity, 16, "…") : undefined;
230
242
  const parts = [
231
- pad(`${icon} ${stage.name}`, STAGE_NAME_COL + 2),
243
+ pad(name, STAGE_NAME_COL + 2),
232
244
  pad(stage.status, 10),
233
245
  ];
234
- if (activity) parts.push(pad(activity, 16));
246
+ if (activityText) parts.push(pad(activityText, 16));
235
247
  if (dur) parts.push(dur);
236
- return parts.join("");
248
+ return truncateToWidth(parts.join(""), width, "…");
237
249
  }
238
250
 
239
- function stageLineThemed(stage: StageSnapshot, now: number, theme: GraphTheme): string {
251
+ function stageLineThemed(stage: StageSnapshot, now: number, theme: GraphTheme, width: number): string {
240
252
  const icon = statusIcon(stage.status);
241
253
  const iconFg = hexToAnsi(statusColor(stage.status, theme));
242
254
  const text = hexToAnsi(theme.text);
@@ -247,14 +259,20 @@ function stageLineThemed(stage: StageSnapshot, now: number, theme: GraphTheme):
247
259
  const activity = stageActivityString(stage);
248
260
  const dur = stageDurationString(stage, now);
249
261
 
250
- const namePad = pad(stage.name, STAGE_NAME_COL);
262
+ const nameText = truncateToWidth(stage.name, STAGE_NAME_COL, "…");
263
+ const namePad = pad(nameText, STAGE_NAME_COL);
251
264
  const statePad = pad(stage.status, 10);
252
- const activitySeg = activity
253
- ? `${muted}${pad(activity, 16)}${RESET}`
265
+ const activityText = activity ? truncateToWidth(activity, 16, "…") : undefined;
266
+ const activitySeg = activityText
267
+ ? `${muted}${pad(activityText, 16)}${RESET}`
254
268
  : " ".repeat(16);
255
269
  const durSeg = dur ? `${dim}${dur}${RESET}` : "";
256
270
 
257
- return `${iconFg}${icon}${RESET} ${text}${namePad}${RESET} ${stateFg}${statePad}${RESET}${activitySeg}${durSeg}`;
271
+ return truncateToWidth(
272
+ `${iconFg}${icon}${RESET} ${text}${namePad}${RESET} ${stateFg}${statePad}${RESET}${activitySeg}${durSeg}`,
273
+ width,
274
+ "…",
275
+ );
258
276
  }
259
277
 
260
278
  function stageDurationString(stage: StageSnapshot, now: number): string | undefined {
@@ -322,8 +340,9 @@ function shortId(id: string): string {
322
340
  }
323
341
 
324
342
  function pad(s: string, n: number): string {
325
- if (s.length >= n) return s;
326
- return s + " ".repeat(n - s.length);
343
+ const width = visibleWidth(s);
344
+ if (width >= n) return s;
345
+ return s + " ".repeat(n - width);
327
346
  }
328
347
 
329
348
  function formatTime(ms: number): string {
@@ -18,11 +18,12 @@
18
18
  * - DESIGN.md §5 Components — destructive button
19
19
  */
20
20
 
21
+ import { keyText } from "@bastani/atomic";
21
22
  import type { RunSnapshot } from "../shared/store-types.js";
22
23
  import type { GraphTheme } from "./graph-theme.js";
23
24
  import { fmtDuration } from "./status-helpers.js";
24
25
  import { hexToAnsi, hexBg, RESET, BOLD } from "./color-utils.js";
25
- import { visibleWidth } from "./text-helpers.js";
26
+ import { truncateToWidth, visibleWidth } from "./text-helpers.js";
26
27
 
27
28
  export interface KillConfirmState {
28
29
  /** 0 = Cancel (focused by default), 1 = Kill. */
@@ -67,17 +68,18 @@ function renderFooter(width: number, theme: GraphTheme): string {
67
68
  const text = hexToAnsi(theme.text);
68
69
  const muted = hexToAnsi(theme.textMuted);
69
70
  const sep = `${dim} \u00b7 ${RESET}`;
70
- const hints: Array<[string, string]> = [
71
- ["y", "kill"],
72
- ["n", "cancel"],
73
- ["\u21b5", "confirm"],
74
- ["esc", "cancel"],
75
- ];
76
- const parts = hints.map(([k, l]) => `${text}${k}${RESET} ${muted}${l}${RESET}`);
77
- const line = parts.join(sep);
71
+ const hint = (key: string, label: string) => `${text}${key}${RESET} ${muted}${label}${RESET}`;
72
+ const line = [
73
+ hint("y", "Kill"),
74
+ hint("n", "Cancel"),
75
+ hint(keyText("tui.select.confirm"), "Confirm"),
76
+ hint(keyText("tui.select.cancel"), "Cancel"),
77
+ ].join(sep);
78
78
  const leftRule = "\u2500\u2500 ";
79
- const padLen = Math.max(1, inner - leftRule.length - visibleWidth(line) - 1);
80
- const innerContent = `${border}${leftRule}${RESET}${line}${border}${" ".repeat(padLen)}${RESET}`;
79
+ const lineBudget = Math.max(1, inner - visibleWidth(leftRule) - 1);
80
+ const clippedLine = truncateToWidth(line, lineBudget, "…");
81
+ const padLen = Math.max(1, inner - visibleWidth(leftRule) - visibleWidth(clippedLine) - 1);
82
+ const innerContent = `${border}${leftRule}${RESET}${clippedLine}${border}${" ".repeat(padLen)}${RESET}`;
81
83
  return `${border}\u2570${RESET}${padTo(innerContent, inner)}${border}\u256f${RESET}`;
82
84
  }
83
85
 
@@ -131,9 +133,14 @@ export function renderKillConfirm(opts: KillConfirmRenderOpts): string[] {
131
133
  lines.push(renderHeader(width, theme));
132
134
  lines.push(renderBlankRow(inner, theme));
133
135
 
134
- // Identity row: ⚠ <name> · <idShort>
136
+ // Identity row: ⚠ <name> · <idShort>. Keep the destructive dialog
137
+ // width-safe even for wide workflow names.
138
+ const identityPrefixW = visibleWidth(" ⚠ ");
139
+ const identitySuffixW = visibleWidth(` · ${idShort}`);
140
+ const nameBudget = Math.max(1, inner - identityPrefixW - identitySuffixW);
141
+ const name = truncateToWidth(run.name, nameBudget, "…");
135
142
  const identity =
136
- ` ${warning}\u26a0${RESET}${panelBg} ${text}${BOLD}${run.name}${RESET}${panelBg} ${dim}\u00b7${RESET}${panelBg} ${muted}${idShort}${RESET}`;
143
+ ` ${warning}\u26a0${RESET}${panelBg} ${text}${BOLD}${name}${RESET}${panelBg} ${dim}\u00b7${RESET}${panelBg} ${muted}${idShort}${RESET}`;
137
144
  lines.push(renderTextRow(inner, theme, identity));
138
145
 
139
146
  // Status sub-line.
@@ -152,7 +159,7 @@ export function renderKillConfirm(opts: KillConfirmRenderOpts): string[] {
152
159
  lines.push(renderTextRow(
153
160
  inner,
154
161
  theme,
155
- ` ${muted}The runId stays in history.${RESET}`,
162
+ ` ${muted}Removes the run from live history/status.${RESET}`,
156
163
  ));
157
164
  lines.push(renderBlankRow(inner, theme));
158
165
 
@@ -20,9 +20,10 @@
20
20
 
21
21
  import type { RunSnapshot } from "../shared/store-types.js";
22
22
  import type { GraphTheme } from "./graph-theme.js";
23
+ import { keyText } from "@bastani/atomic";
23
24
  import { fmtDuration, statusIcon, statusColor } from "./status-helpers.js";
24
25
  import { hexToAnsi, hexBg, RESET, BOLD } from "./color-utils.js";
25
- import { visibleWidth } from "./text-helpers.js";
26
+ import { truncateToWidth, visibleWidth } from "./text-helpers.js";
26
27
 
27
28
  // ---------------------------------------------------------------------------
28
29
  // State + filtering
@@ -141,32 +142,32 @@ function renderHintsRow(width: number, theme: GraphTheme, state: SessionPickerSt
141
142
  const text = hexToAnsi(theme.text);
142
143
  const muted = hexToAnsi(theme.textMuted);
143
144
  const sep = `${dim} · ${RESET}`;
145
+ const hint = (key: string, label: string) => `${text}${key}${RESET} ${muted}${label}${RESET}`;
144
146
 
145
- const hints: Array<[string, string]> = state.filterFocused
147
+ const parts: string[] = state.filterFocused
146
148
  ? [
147
- ["", "submit"],
148
- ["esc", "exit filter"],
149
+ hint(keyText("tui.select.confirm"), "Submit"),
150
+ hint(keyText("tui.select.cancel"), "Exit Filter"),
149
151
  ]
150
152
  : [
151
- ["↑↓", "navigate"],
152
- ["", "connect"],
153
- ["x", "kill"],
154
- ["a", state.includeAll ? "active only" : "all"],
155
- ["/", "filter"],
156
- ["esc", "close"],
153
+ hint(`${keyText("tui.select.up")}/${keyText("tui.select.down")}`, "Navigate"),
154
+ hint(keyText("tui.select.confirm"), "Connect"),
155
+ hint("x", "Kill"),
156
+ hint("a", state.includeAll ? "Active Only" : "All"),
157
+ hint("/", "Filter"),
158
+ hint(keyText("tui.select.cancel"), "Close"),
157
159
  ];
158
160
 
159
- const parts = hints.map(([k, l]) => `${text}${k}${RESET} ${muted}${l}${RESET}`);
160
161
  // Two-space indent matches `renderEmptyState` and the section-row
161
162
  // chrome, keeping the hint glyphs aligned with the panel interior
162
163
  // even though they live outside the box border.
163
164
  const line = " " + parts.join(sep);
164
- // The renderer clamps each line to `width` in the host composite
165
- // pass; explicit clip here keeps the test-time render() output
166
- // width-safe as well.
167
- const lineWidth = visibleWidth(line);
168
- if (lineWidth >= width) return line;
169
- return line + " ".repeat(width - lineWidth);
165
+ // Keep test-time render() output width-safe even before the overlay host
166
+ // gets a chance to composite/truncate it.
167
+ const clipped = truncateToWidth(line, width, "…");
168
+ const lineWidth = visibleWidth(clipped);
169
+ if (lineWidth >= width) return clipped;
170
+ return clipped + " ".repeat(width - lineWidth);
170
171
  }
171
172
 
172
173
  function renderBlankRow(inner: number, theme: GraphTheme): string {
@@ -193,9 +194,13 @@ function renderFilterRow(inner: number, theme: GraphTheme, state: SessionPickerS
193
194
  const accent = hexToAnsi(theme.accent);
194
195
  const cursor = state.filterFocused ? `${accent}▌${RESET}${panelBg}` : "";
195
196
  const label = state.filterFocused ? `${accent}filter` : `${muted}filter`;
197
+ const prefixPlain = " ▎ filter ";
198
+ const valueBudget = Math.max(1, inner - visibleWidth(prefixPlain) - (state.filterFocused ? 1 : 0));
199
+ const rawValue = state.query || "(type to filter by name or id)";
200
+ const shownValue = truncateToWidth(rawValue, valueBudget, "…");
196
201
  const value = state.query
197
- ? `${text}${state.query}${RESET}${panelBg}`
198
- : `${muted}(type to filter by name or id)${RESET}${panelBg}`;
202
+ ? `${text}${shownValue}${RESET}${panelBg}`
203
+ : `${muted}${shownValue}${RESET}${panelBg}`;
199
204
  const content = ` ${mauve}▎${RESET}${panelBg} ${label}${RESET}${panelBg} ${value}${cursor}`;
200
205
  return `${border}│${RESET}${panelBg}${padTo(content, inner)}${RESET}${border}│${RESET}`;
201
206
  }
@@ -232,15 +237,21 @@ function renderRunRow(
232
237
  const elapsed = fmtElapsed(run, now);
233
238
  const progress = stageProgress(run);
234
239
 
235
- // Layout columns: glyph(1) idShort(8) name(flex) elapsed(R) progress(R)
240
+ // Layout columns: glyph(1) idShort(8) name(flex) elapsed(R) progress(R).
241
+ // Name budgeting is done by visible cell width so wide workflow names
242
+ // cannot push the elapsed/progress columns through the right border.
236
243
  const elapsedCol = elapsed.padStart(8, " ");
237
244
  const progressCol = progress.padStart(10, " ");
245
+ const rightPlain = `${elapsedCol} ${progressCol} `;
246
+ const namePrefixW = visibleWidth(` ${icon} ${idShort} `);
247
+ const nameBudget = Math.max(1, inner - namePrefixW - visibleWidth(rightPlain) - 1);
248
+ const name = truncateToWidth(run.name, nameBudget, "…");
238
249
 
239
250
  if (isSelected) {
240
251
  const pillBg = hexBg(theme.accent);
241
252
  const pillFg = hexToAnsi(theme.backgroundElement);
242
- const left = ` ${icon} ${idShort} ${run.name}`;
243
- const right = `${elapsedCol} ${progressCol} `;
253
+ const left = ` ${icon} ${idShort} ${name}`;
254
+ const right = rightPlain;
244
255
  const gap = Math.max(1, inner - visibleWidth(left) - visibleWidth(right));
245
256
  const content = `${left}${" ".repeat(gap)}${right}`;
246
257
  return `${border}│${RESET}${pillBg}${pillFg}${BOLD}${padTo(content, inner)}${RESET}${border}│${RESET}`;
@@ -253,11 +264,9 @@ function renderRunRow(
253
264
  const muted = hexToAnsi(theme.textMuted);
254
265
 
255
266
  const left =
256
- ` ${iconColor}${icon}${RESET}${panelBg} ${dim}${idShort}${RESET}${panelBg} ${text}${run.name}${RESET}${panelBg}`;
267
+ ` ${iconColor}${icon}${RESET}${panelBg} ${dim}${idShort}${RESET}${panelBg} ${text}${name}${RESET}${panelBg}`;
257
268
  const right = `${muted}${elapsedCol}${RESET}${panelBg} ${dim}${progressCol}${RESET}${panelBg} `;
258
- const visLeft = 1 + 1 + 1 + 8 + 2 + run.name.length;
259
- const visRight = visibleWidth(elapsedCol) + 3 + visibleWidth(progressCol) + 1;
260
- const gap = Math.max(1, inner - visLeft - visRight);
269
+ const gap = Math.max(1, inner - visibleWidth(left) - visibleWidth(right));
261
270
  const content = `${left}${" ".repeat(gap)}${right}`;
262
271
  return `${border}│${RESET}${panelBg}${padTo(content, inner)}${RESET}${border}│${RESET}`;
263
272
  }