pi-agent-flow 2.2.14 → 2.2.15

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 (254) hide show
  1. package/README.md +12 -0
  2. package/dist/batch/apply-patch.d.ts +2 -1
  3. package/dist/batch/apply-patch.d.ts.map +1 -1
  4. package/dist/batch/batch-bash.d.ts.map +1 -1
  5. package/dist/batch/batch-bash.js +9 -43
  6. package/dist/batch/batch-bash.js.map +1 -1
  7. package/dist/batch/constants.d.ts +9 -1
  8. package/dist/batch/constants.d.ts.map +1 -1
  9. package/dist/batch/constants.js +7 -0
  10. package/dist/batch/constants.js.map +1 -1
  11. package/dist/batch/execute.d.ts +1 -13
  12. package/dist/batch/execute.d.ts.map +1 -1
  13. package/dist/batch/execute.js +87 -28
  14. package/dist/batch/execute.js.map +1 -1
  15. package/dist/batch/index.d.ts.map +1 -1
  16. package/dist/batch/index.js +16 -14
  17. package/dist/batch/index.js.map +1 -1
  18. package/dist/batch/render.d.ts.map +1 -1
  19. package/dist/batch/render.js.map +1 -1
  20. package/dist/batch/summary.d.ts +0 -6
  21. package/dist/batch/summary.d.ts.map +1 -1
  22. package/dist/batch/summary.js +3 -3
  23. package/dist/batch/summary.js.map +1 -1
  24. package/dist/batch/symbols.d.ts.map +1 -1
  25. package/dist/batch/symbols.js +3 -1
  26. package/dist/batch/symbols.js.map +1 -1
  27. package/dist/batch/truncate-output.d.ts +31 -0
  28. package/dist/batch/truncate-output.d.ts.map +1 -0
  29. package/dist/batch/truncate-output.js +120 -0
  30. package/dist/batch/truncate-output.js.map +1 -0
  31. package/dist/config/config.d.ts +2 -1
  32. package/dist/config/config.d.ts.map +1 -1
  33. package/dist/config/config.js +10 -24
  34. package/dist/config/config.js.map +1 -1
  35. package/dist/config/models.d.ts.map +1 -1
  36. package/dist/config/models.js +7 -5
  37. package/dist/config/models.js.map +1 -1
  38. package/dist/config/paths.d.ts +3 -0
  39. package/dist/config/paths.d.ts.map +1 -0
  40. package/dist/config/paths.js +9 -0
  41. package/dist/config/paths.js.map +1 -0
  42. package/dist/config/resolver-helpers.d.ts +44 -0
  43. package/dist/config/resolver-helpers.d.ts.map +1 -0
  44. package/dist/config/resolver-helpers.js +135 -0
  45. package/dist/config/resolver-helpers.js.map +1 -0
  46. package/dist/config/settings-resolver.d.ts +0 -6
  47. package/dist/config/settings-resolver.d.ts.map +1 -1
  48. package/dist/config/settings-resolver.js +27 -256
  49. package/dist/config/settings-resolver.js.map +1 -1
  50. package/dist/core2/snapshot.d.ts +0 -2
  51. package/dist/core2/snapshot.d.ts.map +1 -1
  52. package/dist/core2/snapshot.js +91 -24
  53. package/dist/core2/snapshot.js.map +1 -1
  54. package/dist/flow/agents.d.ts +1 -1
  55. package/dist/flow/agents.d.ts.map +1 -1
  56. package/dist/flow/agents.js +18 -9
  57. package/dist/flow/agents.js.map +1 -1
  58. package/dist/flow/audit-formatters.d.ts +20 -0
  59. package/dist/flow/audit-formatters.d.ts.map +1 -0
  60. package/dist/flow/audit-formatters.js +123 -0
  61. package/dist/flow/audit-formatters.js.map +1 -0
  62. package/dist/flow/complexity.d.ts +3 -2
  63. package/dist/flow/complexity.d.ts.map +1 -1
  64. package/dist/flow/complexity.js +1 -1
  65. package/dist/flow/complexity.js.map +1 -1
  66. package/dist/flow/continuation.d.ts.map +1 -1
  67. package/dist/flow/continuation.js +6 -4
  68. package/dist/flow/continuation.js.map +1 -1
  69. package/dist/flow/cycle-guard.d.ts +20 -0
  70. package/dist/flow/cycle-guard.d.ts.map +1 -0
  71. package/dist/flow/cycle-guard.js +56 -0
  72. package/dist/flow/cycle-guard.js.map +1 -0
  73. package/dist/flow/depth.d.ts +0 -1
  74. package/dist/flow/depth.d.ts.map +1 -1
  75. package/dist/flow/depth.js +3 -2
  76. package/dist/flow/depth.js.map +1 -1
  77. package/dist/flow/dump-io.d.ts +15 -0
  78. package/dist/flow/dump-io.d.ts.map +1 -0
  79. package/dist/flow/dump-io.js +169 -0
  80. package/dist/flow/dump-io.js.map +1 -0
  81. package/dist/flow/execute-single.d.ts +8 -0
  82. package/dist/flow/execute-single.d.ts.map +1 -0
  83. package/dist/flow/execute-single.js +132 -0
  84. package/dist/flow/execute-single.js.map +1 -0
  85. package/dist/flow/executor.d.ts +3 -62
  86. package/dist/flow/executor.d.ts.map +1 -1
  87. package/dist/flow/executor.js +36 -342
  88. package/dist/flow/executor.js.map +1 -1
  89. package/dist/flow/flow-args.d.ts +14 -0
  90. package/dist/flow/flow-args.d.ts.map +1 -0
  91. package/dist/flow/flow-args.js +119 -0
  92. package/dist/flow/flow-args.js.map +1 -0
  93. package/dist/flow/flow-live.d.ts +18 -0
  94. package/dist/flow/flow-live.d.ts.map +1 -0
  95. package/dist/flow/flow-live.js +46 -0
  96. package/dist/flow/flow-live.js.map +1 -0
  97. package/dist/flow/process-lifecycle.d.ts +28 -0
  98. package/dist/flow/process-lifecycle.d.ts.map +1 -0
  99. package/dist/flow/process-lifecycle.js +114 -0
  100. package/dist/flow/process-lifecycle.js.map +1 -0
  101. package/dist/flow/runner.d.ts +4 -54
  102. package/dist/flow/runner.d.ts.map +1 -1
  103. package/dist/flow/runner.js +55 -517
  104. package/dist/flow/runner.js.map +1 -1
  105. package/dist/flow/settings-command.d.ts +2 -24
  106. package/dist/flow/settings-command.d.ts.map +1 -1
  107. package/dist/flow/settings-command.js +14 -716
  108. package/dist/flow/settings-command.js.map +1 -1
  109. package/dist/flow/settings-handler.d.ts +9 -0
  110. package/dist/flow/settings-handler.d.ts.map +1 -0
  111. package/dist/flow/settings-handler.js +377 -0
  112. package/dist/flow/settings-handler.js.map +1 -0
  113. package/dist/flow/settings-items.d.ts +42 -0
  114. package/dist/flow/settings-items.d.ts.map +1 -0
  115. package/dist/flow/settings-items.js +380 -0
  116. package/dist/flow/settings-items.js.map +1 -0
  117. package/dist/flow/spawn-utils.d.ts +17 -0
  118. package/dist/flow/spawn-utils.d.ts.map +1 -0
  119. package/dist/flow/spawn-utils.js +40 -0
  120. package/dist/flow/spawn-utils.js.map +1 -0
  121. package/dist/flow/store.d.ts.map +1 -1
  122. package/dist/flow/store.js +2 -8
  123. package/dist/flow/store.js.map +1 -1
  124. package/dist/flow/transition.d.ts +0 -1
  125. package/dist/flow/transition.d.ts.map +1 -1
  126. package/dist/flow/transition.js +0 -3
  127. package/dist/flow/transition.js.map +1 -1
  128. package/dist/index.d.ts +3 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +94 -57
  131. package/dist/index.js.map +1 -1
  132. package/dist/io/atomic-write.d.ts +10 -0
  133. package/dist/io/atomic-write.d.ts.map +1 -0
  134. package/dist/io/atomic-write.js +30 -0
  135. package/dist/io/atomic-write.js.map +1 -0
  136. package/dist/notify/notify.d.ts.map +1 -1
  137. package/dist/notify/notify.js +1 -4
  138. package/dist/notify/notify.js.map +1 -1
  139. package/dist/snapshot/cli-args.d.ts +1 -2
  140. package/dist/snapshot/cli-args.d.ts.map +1 -1
  141. package/dist/snapshot/runner-events.d.ts +10 -0
  142. package/dist/snapshot/runner-events.d.ts.map +1 -1
  143. package/dist/snapshot/runner-events.js +112 -30
  144. package/dist/snapshot/runner-events.js.map +1 -1
  145. package/dist/snapshot/structured-output.d.ts.map +1 -1
  146. package/dist/snapshot/structured-output.js +3 -1
  147. package/dist/snapshot/structured-output.js.map +1 -1
  148. package/dist/snapshot/trace-output.d.ts.map +1 -1
  149. package/dist/snapshot/trace-output.js +8 -4
  150. package/dist/snapshot/trace-output.js.map +1 -1
  151. package/dist/steering/sliding-prompt.d.ts.map +1 -1
  152. package/dist/steering/sliding-prompt.js +12 -15
  153. package/dist/steering/sliding-prompt.js.map +1 -1
  154. package/dist/steering/tool-utils.d.ts +3 -3
  155. package/dist/steering/tool-utils.d.ts.map +1 -1
  156. package/dist/steering/tool-utils.js +3 -3
  157. package/dist/steering/tool-utils.js.map +1 -1
  158. package/dist/tools/ask-user.js.map +1 -1
  159. package/dist/tools/timed-bash.d.ts.map +1 -1
  160. package/dist/tools/timed-bash.js +8 -3
  161. package/dist/tools/timed-bash.js.map +1 -1
  162. package/dist/tools/trace.d.ts +0 -1
  163. package/dist/tools/trace.d.ts.map +1 -1
  164. package/dist/tools/trace.js +79 -62
  165. package/dist/tools/trace.js.map +1 -1
  166. package/dist/tools/web-ops.d.ts +0 -49
  167. package/dist/tools/web-ops.d.ts.map +1 -1
  168. package/dist/tools/web-ops.js +9 -9
  169. package/dist/tools/web-ops.js.map +1 -1
  170. package/dist/tui/body-render.d.ts +31 -0
  171. package/dist/tui/body-render.d.ts.map +1 -0
  172. package/dist/tui/body-render.js +589 -0
  173. package/dist/tui/body-render.js.map +1 -0
  174. package/dist/tui/context-display.d.ts +18 -0
  175. package/dist/tui/context-display.d.ts.map +1 -0
  176. package/dist/tui/context-display.js +29 -0
  177. package/dist/tui/context-display.js.map +1 -0
  178. package/dist/tui/flow-colors.d.ts +6 -1
  179. package/dist/tui/flow-colors.d.ts.map +1 -1
  180. package/dist/tui/flow-colors.js +1 -0
  181. package/dist/tui/flow-colors.js.map +1 -1
  182. package/dist/tui/flow-live-state.d.ts +36 -0
  183. package/dist/tui/flow-live-state.d.ts.map +1 -0
  184. package/dist/tui/flow-live-state.js +53 -0
  185. package/dist/tui/flow-live-state.js.map +1 -0
  186. package/dist/tui/grouping.d.ts +27 -0
  187. package/dist/tui/grouping.d.ts.map +1 -0
  188. package/dist/tui/grouping.js +191 -0
  189. package/dist/tui/grouping.js.map +1 -0
  190. package/dist/tui/header.d.ts +25 -0
  191. package/dist/tui/header.d.ts.map +1 -0
  192. package/dist/tui/header.js +95 -0
  193. package/dist/tui/header.js.map +1 -0
  194. package/dist/tui/render-utils.d.ts +1 -0
  195. package/dist/tui/render-utils.d.ts.map +1 -1
  196. package/dist/tui/render-utils.js +4 -5
  197. package/dist/tui/render-utils.js.map +1 -1
  198. package/dist/tui/render.d.ts +19 -33
  199. package/dist/tui/render.d.ts.map +1 -1
  200. package/dist/tui/render.js +112 -1014
  201. package/dist/tui/render.js.map +1 -1
  202. package/dist/tui/scramble/animation-status.d.ts +5 -0
  203. package/dist/tui/scramble/animation-status.d.ts.map +1 -0
  204. package/dist/tui/scramble/animation-status.js +93 -0
  205. package/dist/tui/scramble/animation-status.js.map +1 -0
  206. package/dist/tui/scramble/constants.d.ts +0 -4
  207. package/dist/tui/scramble/constants.d.ts.map +1 -1
  208. package/dist/tui/scramble/constants.js +3 -3
  209. package/dist/tui/scramble/constants.js.map +1 -1
  210. package/dist/tui/scramble/dynamic-text.d.ts +10 -0
  211. package/dist/tui/scramble/dynamic-text.d.ts.map +1 -0
  212. package/dist/tui/scramble/dynamic-text.js +19 -0
  213. package/dist/tui/scramble/dynamic-text.js.map +1 -0
  214. package/dist/tui/scramble/index.d.ts +8 -2
  215. package/dist/tui/scramble/index.d.ts.map +1 -1
  216. package/dist/tui/scramble/index.js +8 -1
  217. package/dist/tui/scramble/index.js.map +1 -1
  218. package/dist/tui/scramble/lifecycle.d.ts +4 -0
  219. package/dist/tui/scramble/lifecycle.d.ts.map +1 -0
  220. package/dist/tui/scramble/lifecycle.js +75 -0
  221. package/dist/tui/scramble/lifecycle.js.map +1 -0
  222. package/dist/tui/scramble/line-state.d.ts +5 -0
  223. package/dist/tui/scramble/line-state.d.ts.map +1 -0
  224. package/dist/tui/scramble/line-state.js +99 -0
  225. package/dist/tui/scramble/line-state.js.map +1 -0
  226. package/dist/tui/scramble/manager.d.ts +3 -14
  227. package/dist/tui/scramble/manager.d.ts.map +1 -1
  228. package/dist/tui/scramble/manager.js +22 -405
  229. package/dist/tui/scramble/manager.js.map +1 -1
  230. package/dist/tui/scramble/singleton.d.ts +3 -0
  231. package/dist/tui/scramble/singleton.d.ts.map +1 -0
  232. package/dist/tui/scramble/singleton.js +3 -0
  233. package/dist/tui/scramble/singleton.js.map +1 -0
  234. package/dist/tui/scramble/timer.d.ts +6 -0
  235. package/dist/tui/scramble/timer.d.ts.map +1 -0
  236. package/dist/tui/scramble/timer.js +28 -0
  237. package/dist/tui/scramble/timer.js.map +1 -0
  238. package/dist/tui/scramble/utils.d.ts +0 -2
  239. package/dist/tui/scramble/utils.d.ts.map +1 -1
  240. package/dist/tui/scramble/utils.js +0 -9
  241. package/dist/tui/scramble/utils.js.map +1 -1
  242. package/dist/tui/scramble/value-flash.d.ts +6 -0
  243. package/dist/tui/scramble/value-flash.d.ts.map +1 -0
  244. package/dist/tui/scramble/value-flash.js +141 -0
  245. package/dist/tui/scramble/value-flash.js.map +1 -0
  246. package/dist/tui/traces.d.ts +13 -0
  247. package/dist/tui/traces.d.ts.map +1 -0
  248. package/dist/tui/traces.js +97 -0
  249. package/dist/tui/traces.js.map +1 -0
  250. package/dist/types/ui.d.ts +0 -4
  251. package/dist/types/ui.d.ts.map +1 -1
  252. package/dist/types/ui.js +1 -30
  253. package/dist/types/ui.js.map +1 -1
  254. package/package.json +5 -1
@@ -0,0 +1,589 @@
1
+ import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
2
+ import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
3
+ import { applyRole } from "./flow-colors.js";
4
+ import { formatCompactStats, formatFlowTypeName, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi, formatModelLabel, resolveDisplayContextTokens, formatTps } from "./render-utils.js";
5
+ import { getFlowSummaryText } from "../snapshot/runner-events.js";
6
+ import { aggregateFlowUsage, getFlowOutput, isFlowError } from "../types/flow.js";
7
+ import { getFlowDisplayItems, getLastToolCall } from "../types/ui.js";
8
+ import { getFlowLiveState } from "./flow-live-state.js";
9
+ import { scrambleManager, DynamicScrambleText } from "./scramble/index.js";
10
+ import { sectionHeader, reconstructHeader, formatCollapsedFlowHeaderTypeName } from "./header.js";
11
+ import { flowStatusIcon, getScintillatingStatusDot, isFlowAwaiting, isFlowRunning, isFlowStatusComplete } from "./grouping.js";
12
+ import { getContentRole, formatFlowToolCall, applyScrambledContextLabel, getLiveTextWithFallback } from "./traces.js";
13
+ function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText, config, sharedContext) {
14
+ const mdTheme = getMarkdownTheme();
15
+ const container = new Container();
16
+ const typeName = formatFlowTypeName(r.type);
17
+ if (sharedContext) {
18
+ container.addChild(new Text(applyRole("prefixLabel", "── shared context ──", theme, config), 0, 0));
19
+ container.addChild(new Text(applyRole("aimContent", sharedContext.preview, theme, config), 0, 0));
20
+ const statsParts = [];
21
+ statsParts.push(`${sharedContext.userMessageCount} user`);
22
+ statsParts.push(`${sharedContext.assistantMessageCount} assistant`);
23
+ statsParts.push(`${sharedContext.totalTokens} tokens`);
24
+ for (const [name, count] of Object.entries(sharedContext.toolCalls)) {
25
+ statsParts.push(`${count}× ${name}`);
26
+ }
27
+ container.addChild(new Text(applyRole("stats", statsParts.join(" · "), theme, config), 0, 0));
28
+ container.addChild(new Spacer(1));
29
+ container.addChild(new Text(applyRole("prefixLabel", sectionHeader(typeName), theme, config), 0, 0));
30
+ container.addChild(new Spacer(1));
31
+ }
32
+ const initialDot = flowStatusIcon(r, theme);
33
+ let header = `${initialDot} ${applyRole("flowName", typeName, theme, config)}`;
34
+ const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
35
+ if (errorSegment)
36
+ header += ` ${theme.fg("error", errorSegment)}`;
37
+ const dotPlaceholder = stripAnsi(initialDot) + ' ';
38
+ const plainHeader = dotPlaceholder + typeName + errorSegment;
39
+ const headerSegments = [
40
+ { text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
41
+ { text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
42
+ ];
43
+ if (errorSegment) {
44
+ headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
45
+ }
46
+ container.addChild(new DynamicScrambleText(header, () => {
47
+ const now = Date.now();
48
+ const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete);
49
+ return reconstructHeader(result.content, headerSegments);
50
+ }));
51
+ if (error && r.errorMessage) {
52
+ container.addChild(new Text(scrambleManager.renderStatic(theme.fg("error", `Error: ${r.errorMessage}`)), 0, 0));
53
+ }
54
+ // Stats: dashboard format
55
+ const displayUsage = sharedContext ? { ...r.usage, input: sharedContext.totalTokens } : r.usage;
56
+ const inlineStats = formatCompactStats(displayUsage, r.model);
57
+ container.addChild(new DynamicScrambleText(applyRole("stats", inlineStats, theme, config), () => {
58
+ const result = scrambleManager.updateText(id, 'stats', stripAnsi(inlineStats), Date.now(), isComplete);
59
+ return result.isAnimating ? applyRole("stats", result.content, theme, config) : applyRole("stats", inlineStats, theme, config);
60
+ }));
61
+ // Intent — column-aware truncation (budget recalculated inside closure for resize handling)
62
+ const intentBudget = getTruncationBudget(0);
63
+ const displayIntent = truncateChars(r.intent, intentBudget);
64
+ container.addChild(new Spacer(1));
65
+ container.addChild(new Text(applyRole("prefixLabel", sectionHeader("intent"), theme, config), 0, 0));
66
+ container.addChild(new DynamicScrambleText(applyRole("aimContent", displayIntent, theme, config), () => {
67
+ const budget = getTruncationBudget(0);
68
+ const text = truncateChars(r.intent, budget);
69
+ const result = scrambleManager.updateText(id, 'intent', text, Date.now(), isComplete);
70
+ return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
71
+ }));
72
+ // Acceptance
73
+ if (r.acceptance) {
74
+ const acceptanceRaw = r.acceptance;
75
+ const acceptanceBudget = getTruncationBudget(0);
76
+ const acceptanceText = truncateChars(acceptanceRaw, acceptanceBudget);
77
+ container.addChild(new Spacer(1));
78
+ container.addChild(new Text(applyRole("prefixLabel", sectionHeader("acceptance"), theme, config), 0, 0));
79
+ container.addChild(new DynamicScrambleText(applyRole("aimContent", acceptanceText, theme, config), () => {
80
+ const budget = getTruncationBudget(0);
81
+ const text = truncateChars(acceptanceRaw, budget);
82
+ const result = scrambleManager.updateText(id, 'acceptance', text, Date.now(), isComplete);
83
+ return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
84
+ }));
85
+ }
86
+ // Flow report (structured output)
87
+ container.addChild(new Spacer(1));
88
+ container.addChild(new Text(applyRole("prefixLabel", sectionHeader("report"), theme, config), 0, 0));
89
+ // Structured output summary (compact badge when available)
90
+ if (r.structuredOutput) {
91
+ const so = r.structuredOutput;
92
+ const statusColor = so.status === "complete" ? "success" : so.status === "partial" ? "warning" : "error";
93
+ const statusText = `[${so.status}] ${so.summary}`;
94
+ const statusStatic = `${theme.fg(statusColor, `[${so.status}]`)} ${applyRole("aimContent", so.summary, theme, config)}`;
95
+ container.addChild(new DynamicScrambleText(statusStatic, () => {
96
+ const result = scrambleManager.updateText(id, 'report-status', statusText, Date.now(), isComplete, false);
97
+ return result.isAnimating ? `${theme.fg(statusColor, result.content.split(' ')[0])} ${applyRole("aimContent", result.content.slice(result.content.indexOf(' ') + 1), theme, config)}` : statusStatic;
98
+ }));
99
+ if (so.files.length > 0) {
100
+ const filesText = `Files: ${so.files.map((f) => f.path).join(", ")}`;
101
+ container.addChild(new DynamicScrambleText(applyRole("aimContent", filesText, theme, config), () => {
102
+ const result = scrambleManager.updateText(id, 'report-files', filesText, Date.now(), isComplete, false);
103
+ return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", filesText, theme, config);
104
+ }));
105
+ }
106
+ if (so.commands?.length > 0) {
107
+ const cmdLabels = so.commands.map((c) => {
108
+ const short = c.command.length > 30 ? c.command.slice(0, 30) + "..." : c.command;
109
+ return `${c.tool ?? "cmd"}: ${short}`;
110
+ });
111
+ const commandsText = `Commands: ${cmdLabels.join(", ")}`;
112
+ container.addChild(new DynamicScrambleText(applyRole("aimContent", commandsText, theme, config), () => {
113
+ const result = scrambleManager.updateText(id, 'report-commands', commandsText, Date.now(), isComplete, false);
114
+ return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", commandsText, theme, config);
115
+ }));
116
+ }
117
+ if (so.notDone.length > 0) {
118
+ const notDoneText = `Not Done: ${so.notDone.map((item) => {
119
+ const details = [
120
+ item.reason ? `reason: ${item.reason}` : undefined,
121
+ item.blocker ? `blocker: ${item.blocker}` : undefined,
122
+ item.nextStep ? `next: ${item.nextStep}` : undefined,
123
+ ].filter(Boolean).join("; ");
124
+ return details ? `${item.item} (${details})` : item.item;
125
+ }).join("; ")}`;
126
+ container.addChild(new DynamicScrambleText(applyRole("aimContent", notDoneText, theme, config), () => {
127
+ const result = scrambleManager.updateText(id, 'report-notDone', notDoneText, Date.now(), isComplete, false);
128
+ return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", notDoneText, theme, config);
129
+ }));
130
+ }
131
+ if (so.nextSteps.length > 0) {
132
+ const nextStepsText = `Next: ${so.nextSteps.join("; ")}`;
133
+ container.addChild(new DynamicScrambleText(applyRole("aimContent", nextStepsText, theme, config), () => {
134
+ const result = scrambleManager.updateText(id, 'report-nextSteps', nextStepsText, Date.now(), isComplete, false);
135
+ return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", nextStepsText, theme, config);
136
+ }));
137
+ }
138
+ container.addChild(new Spacer(1));
139
+ }
140
+ // Output: animate streaming text; show clean markdown when complete
141
+ if (isFlowAwaiting(r)) {
142
+ container.addChild(new Text(applyRole("placeholder", "[awaiting...]", theme, config), 0, 0));
143
+ }
144
+ else if (!isComplete && streamingText != null) {
145
+ const msgBudget = getTruncationBudget(0);
146
+ const displayMsg = tailText(stripAnsi(streamingText), msgBudget);
147
+ container.addChild(new DynamicScrambleText(applyRole("msgContent", displayMsg, theme, config), () => {
148
+ const budget = getTruncationBudget(0);
149
+ const freshStreamingText = getLiveTextWithFallback(id) ?? streamingText;
150
+ const text = tailText(stripAnsi(freshStreamingText), budget);
151
+ const result = scrambleManager.updateMsg(id, text, Date.now(), isComplete, undefined, true);
152
+ return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", text, theme, config);
153
+ }));
154
+ }
155
+ else if (flowOutput) {
156
+ container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
157
+ }
158
+ else {
159
+ const summary = getFlowSummaryText(r);
160
+ container.addChild(new DynamicScrambleText(applyRole("msgContent", summary, theme, config), () => {
161
+ const result = scrambleManager.updateText(id, 'output-summary', summary, Date.now(), isComplete, false);
162
+ return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", summary, theme, config);
163
+ }));
164
+ }
165
+ // Tool traces (expanded only) — per-line scramble
166
+ const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
167
+ if (toolCallItems.length > 0) {
168
+ container.addChild(new Spacer(1));
169
+ container.addChild(new Text(applyRole("prefixLabel", sectionHeader("tool calls"), theme, config), 0, 0));
170
+ const toolPrefixLen = visibleLength("→ ");
171
+ const toolBudget = getTruncationBudget(toolPrefixLen);
172
+ for (let i = 0; i < toolCallItems.length; i++) {
173
+ const item = toolCallItems[i];
174
+ const lineText = applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
175
+ const plainText = stripAnsi(lineText);
176
+ const displayTool = truncateChars(plainText, toolBudget);
177
+ const initialScrambled = scrambleManager.updateText(id, `tool#${i}`, displayTool, now, isComplete).content;
178
+ container.addChild(new DynamicScrambleText(initialScrambled, () => {
179
+ const budget = getTruncationBudget(toolPrefixLen);
180
+ const freshPlain = stripAnsi(lineText);
181
+ const text = truncateChars(freshPlain, budget);
182
+ return scrambleManager.updateText(id, `tool#${i}`, text, Date.now(), isComplete).content;
183
+ }));
184
+ }
185
+ }
186
+ if (isComplete) {
187
+ scrambleManager.completeFlow(id);
188
+ }
189
+ return container;
190
+ }
191
+ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, toolCallId, config, sharedContext) {
192
+ const id = toolCallId || "collapsed";
193
+ const now = Date.now();
194
+ const container = new Container();
195
+ const maxWidth = process.stdout.columns ?? 80;
196
+ const typeName = formatCollapsedFlowHeaderTypeName(r.type);
197
+ const modelLabel = formatModelLabel(r.model);
198
+ const headerPrefixLen = visibleLength(typeName) + visibleLength(modelLabel ? ` ${modelLabel} · ` : " ");
199
+ const isComplete = isFlowStatusComplete(r);
200
+ // Build header stats: ctxLabel · t/s
201
+ const statsParts = [];
202
+ const liveState = getFlowLiveState(id);
203
+ const ctxTokens = Math.max(resolveDisplayContextTokens(r.usage, sharedContext), liveState?.contextTokens ?? 0);
204
+ let displayStats = "";
205
+ if (r.maxContextTokens !== undefined || ctxTokens > 0) {
206
+ const ctxLabel = applyScrambledContextLabel(id, ctxTokens, r.maxContextTokens, now, isComplete);
207
+ statsParts.push(ctxLabel);
208
+ displayStats = statsParts.join(" · ");
209
+ }
210
+ if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
211
+ const tpsFormatted = formatTps(r.usage.smoothedTps);
212
+ statsParts.push(tpsFormatted);
213
+ displayStats = statsParts.join(" · ");
214
+ // Flash TPS value when it changes
215
+ const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
216
+ const scrambledTps = scrambleManager.updateHeaderMetric(id, "tps", tpsNum, now, isComplete, true);
217
+ if (scrambledTps !== tpsNum) {
218
+ displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
219
+ }
220
+ }
221
+ const modelSegment = modelLabel ? ` ${modelLabel} · ` : " ";
222
+ const statsSegment = stripAnsi(displayStats);
223
+ const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
224
+ const initialDot = flowStatusIcon(r, theme);
225
+ let header = `${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelSegment, theme, config)}${applyRole("stats", displayStats, theme, config)}`;
226
+ if (errorSegment)
227
+ header += ` ${theme.fg("error", errorSegment)}`;
228
+ const dotPlaceholder = stripAnsi(initialDot) + ' ';
229
+ const plainHeader = dotPlaceholder + typeName + modelSegment + statsSegment + errorSegment;
230
+ const headerSegments = [
231
+ { text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
232
+ { text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
233
+ { text: modelSegment, style: (s) => applyRole("modelName", s, theme, config) },
234
+ { text: statsSegment, style: (s) => applyRole("stats", s, theme, config) },
235
+ ];
236
+ if (errorSegment) {
237
+ headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
238
+ }
239
+ container.addChild(new DynamicScrambleText(header, () => {
240
+ const now = Date.now();
241
+ const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete, true);
242
+ return reconstructHeader(result.content, headerSegments);
243
+ }, true));
244
+ // aim: line — glitch on text change
245
+ if (r.aim && r.type !== "trace") {
246
+ const aimTree = "├─";
247
+ const aimLabel = ` aim ▸ `;
248
+ const aimPrefix = `${aimTree}${aimLabel}`;
249
+ const budget = getTruncationBudget(visibleLength(aimPrefix));
250
+ const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
251
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(getContentRole("aimContent", displayAim), displayAim, theme, config)}`, () => {
252
+ const now = Date.now();
253
+ const freshAimLabel = ` aim ▸ `;
254
+ const freshAimPrefix = `${aimTree}${freshAimLabel}`;
255
+ const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
256
+ const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
257
+ const result = scrambleManager.updateAim(id, freshText, now, isComplete, true);
258
+ return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(getContentRole("aimContent", freshText), result.content, theme, config)}`;
259
+ }, true));
260
+ }
261
+ // act: line (last tool call with count)
262
+ const lastTool = getLastToolCall(r.messages);
263
+ const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
264
+ const isLite = config?.bodyVerbosity !== "full";
265
+ const actTree = isLite ? "└─" : "├─";
266
+ const actLabel = ` cmd ▸ `;
267
+ const prefixStub = `${actTree}${actLabel}`;
268
+ const budget = getTruncationBudget(visibleLength(prefixStub));
269
+ const actFullText = stripAnsi(lowerFirstWord(actStr));
270
+ const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
271
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", initialActContent), initialActContent, theme, config)}`, () => {
272
+ const now = Date.now();
273
+ const actLabel = ` cmd ▸ `;
274
+ const actPrefix = `${actTree}${actLabel}`;
275
+ const freshBudget = getTruncationBudget(visibleLength(actPrefix));
276
+ const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
277
+ const actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
278
+ return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", displayAct), actContent, theme, config)}`;
279
+ }, true));
280
+ // msg: line (last assistant text or streaming) — full mode only
281
+ if (!isLite) {
282
+ const msgPrefixStub = `└─ msg ▸ `;
283
+ const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
284
+ let rawMsg;
285
+ let useError = false;
286
+ if (isFlowAwaiting(r)) {
287
+ rawMsg = "[awaiting...]";
288
+ }
289
+ else if (r.status === "skipped") {
290
+ rawMsg = "[skipped]";
291
+ }
292
+ else {
293
+ const liveMsgText = isFlowRunning(r) ? getLiveTextWithFallback(id) : undefined;
294
+ if (liveMsgText != null) {
295
+ rawMsg = stripAnsi(liveMsgText);
296
+ }
297
+ else if (isFlowRunning(r) && streamingText != null) {
298
+ rawMsg = stripAnsi(streamingText);
299
+ }
300
+ else if (r.structuredOutput?.summary) {
301
+ rawMsg = stripAnsi(r.structuredOutput.summary);
302
+ }
303
+ else if (flowOutput) {
304
+ rawMsg = stripAnsi(flowOutput);
305
+ }
306
+ else if (error && r.errorMessage) {
307
+ rawMsg = stripAnsi(r.errorMessage);
308
+ useError = true;
309
+ }
310
+ else {
311
+ const summary = getFlowSummaryText(r);
312
+ rawMsg = stripAnsi(summary) || "[n/a]";
313
+ }
314
+ }
315
+ const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (streamingText != null || getLiveTextWithFallback(id) != null);
316
+ const initialMsgContent = initialNeedsTail
317
+ ? tailText(rawMsg, msgBudget)
318
+ : truncateChars(rawMsg, msgBudget);
319
+ const msgTree = "└─";
320
+ const msgLabel = ` msg ▸ `;
321
+ const initialMsgPrefix = `${msgTree}${msgLabel}`;
322
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", initialMsgContent, useError), initialMsgContent, theme, config)}`, () => {
323
+ const now = Date.now();
324
+ const msgLabel = ` msg ▸ `;
325
+ const msgPrefix = `${msgTree}${msgLabel}`;
326
+ let freshRawMsg;
327
+ let needsTail;
328
+ if (isFlowAwaiting(r)) {
329
+ freshRawMsg = "[awaiting...]";
330
+ needsTail = false;
331
+ }
332
+ else if (r.status === "skipped") {
333
+ freshRawMsg = "[skipped]";
334
+ needsTail = false;
335
+ }
336
+ else {
337
+ const isRunningNow = isFlowRunning(r);
338
+ freshRawMsg = (isRunningNow ? getLiveTextWithFallback(id) : undefined) ?? rawMsg;
339
+ needsTail = isRunningNow && (streamingText != null || getLiveTextWithFallback(id) != null);
340
+ }
341
+ const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
342
+ const result = scrambleManager.updateMsg(id, displayMsg, now, isComplete, undefined, true);
343
+ return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", freshRawMsg, useError), result.content, theme, config)}`;
344
+ }, true));
345
+ }
346
+ if (isComplete) {
347
+ scrambleManager.completeFlow(id);
348
+ }
349
+ return container;
350
+ }
351
+ // ---------------------------------------------------------------------------
352
+ // Multi-flow result
353
+ // ---------------------------------------------------------------------------
354
+ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now, config, sharedContext) {
355
+ const mdTheme = getMarkdownTheme();
356
+ const container = new Container();
357
+ if (sharedContext) {
358
+ container.addChild(new Text(applyRole("prefixLabel", "── shared context ──", theme, config), 0, 0));
359
+ container.addChild(new Text(applyRole("aimContent", sharedContext.preview, theme, config), 0, 0));
360
+ const statsParts = [];
361
+ statsParts.push(`${sharedContext.userMessageCount} user`);
362
+ statsParts.push(`${sharedContext.assistantMessageCount} assistant`);
363
+ statsParts.push(`${sharedContext.totalTokens} tokens`);
364
+ for (const [name, count] of Object.entries(sharedContext.toolCalls)) {
365
+ statsParts.push(`${count}× ${name}`);
366
+ }
367
+ container.addChild(new Text(applyRole("stats", statsParts.join(" · "), theme, config), 0, 0));
368
+ container.addChild(new Spacer(1));
369
+ }
370
+ // Summary: just show count, no icon
371
+ container.addChild(new Text(applyRole("flowName", `${results.length} flows`, theme, config), 0, 0));
372
+ for (let flowIdx = 0; flowIdx < results.length; flowIdx++) {
373
+ const r = results[flowIdx];
374
+ const flowId = `${baseId}#${flowIdx}`;
375
+ const isComplete = isFlowStatusComplete(r);
376
+ const displayItems = getFlowDisplayItems(r.messages);
377
+ const flowOutput = getFlowOutput(r.messages);
378
+ const typeName = formatFlowTypeName(r.type);
379
+ container.addChild(new Spacer(1));
380
+ // Per-flow header: ─── EXPLORER (no icon)
381
+ const headerStatic = applyRole("prefixLabel", sectionHeader(typeName), theme, config);
382
+ container.addChild(new DynamicScrambleText(headerStatic, () => {
383
+ const result = scrambleManager.updateText(flowId, 'header', typeName, Date.now(), isComplete, true);
384
+ return result.isAnimating ? applyRole("prefixLabel", result.content, theme, config) : headerStatic;
385
+ }));
386
+ // Stats: dashboard format
387
+ const flowStats = formatCompactStats(r.usage, r.model);
388
+ container.addChild(new DynamicScrambleText(applyRole("stats", flowStats, theme, config), () => {
389
+ const result = scrambleManager.updateText(flowId, 'stats', stripAnsi(flowStats), Date.now(), isComplete, true);
390
+ return result.isAnimating ? applyRole("stats", result.content, theme, config) : applyRole("stats", flowStats, theme, config);
391
+ }));
392
+ // Intent: just show text, no prefix (budget computed dynamically for resize recalculation)
393
+ const intentBudget = getTruncationBudget(0);
394
+ const displayIntent = truncateChars(r.intent, intentBudget);
395
+ container.addChild(new DynamicScrambleText(applyRole("aimContent", displayIntent, theme, config), () => {
396
+ const budget = getTruncationBudget(0);
397
+ const text = truncateChars(r.intent, budget);
398
+ const result = scrambleManager.updateText(flowId, 'intent', text, Date.now(), isComplete, true);
399
+ return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
400
+ }));
401
+ if (r.acceptance) {
402
+ const acceptanceRaw = r.acceptance;
403
+ const acceptancePrefix = "Acceptance: ";
404
+ const acceptanceBudget = getTruncationBudget(visibleLength(acceptancePrefix));
405
+ const acceptanceText = truncateChars(acceptanceRaw, acceptanceBudget);
406
+ const acceptanceStatic = applyRole("aimContent", `${acceptancePrefix}${acceptanceText}`, theme, config);
407
+ container.addChild(new DynamicScrambleText(acceptanceStatic, () => {
408
+ const budget = getTruncationBudget(visibleLength(acceptancePrefix));
409
+ const text = truncateChars(acceptanceRaw, budget);
410
+ const result = scrambleManager.updateText(flowId, 'acceptance', text, Date.now(), isComplete, true);
411
+ return result.isAnimating ? applyRole("aimContent", `${acceptancePrefix}${result.content}`, theme, config) : applyRole("aimContent", `${acceptancePrefix}${text}`, theme, config);
412
+ }));
413
+ }
414
+ // Output: animate streaming text; show clean markdown when complete
415
+ if (isFlowAwaiting(r)) {
416
+ container.addChild(new Text(applyRole("placeholder", "[awaiting...]", theme, config), 0, 0));
417
+ }
418
+ else if (!isComplete && r.streamingText != null) {
419
+ const streamingRaw = r.streamingText;
420
+ const msgBudget = getTruncationBudget(0);
421
+ const displayMsg = tailText(stripAnsi(streamingRaw), msgBudget);
422
+ container.addChild(new DynamicScrambleText(applyRole("msgContent", displayMsg, theme, config), () => {
423
+ const budget = getTruncationBudget(0);
424
+ const freshStreamingText = getLiveTextWithFallback(flowId) ?? streamingRaw;
425
+ const text = tailText(stripAnsi(freshStreamingText), budget);
426
+ const result = scrambleManager.updateMsg(flowId, text, Date.now(), isComplete, undefined, true);
427
+ return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", text, theme, config);
428
+ }));
429
+ }
430
+ else if (flowOutput) {
431
+ container.addChild(new Spacer(1));
432
+ container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
433
+ }
434
+ // Tool traces in expanded view — per-line scramble
435
+ const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
436
+ if (toolCallItems.length > 0) {
437
+ container.addChild(new Spacer(1));
438
+ container.addChild(new Text(applyRole("prefixLabel", sectionHeader("tool calls"), theme, config), 0, 0));
439
+ const toolPrefixLen = visibleLength("→ ");
440
+ const toolBudget = getTruncationBudget(toolPrefixLen);
441
+ for (let i = 0; i < toolCallItems.length; i++) {
442
+ const item = toolCallItems[i];
443
+ const lineText = applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
444
+ const plainText = stripAnsi(lineText);
445
+ const displayTool = truncateChars(plainText, toolBudget);
446
+ const initialScrambled = scrambleManager.updateText(flowId, `tool#${i}`, displayTool, now, isComplete).content;
447
+ container.addChild(new DynamicScrambleText(initialScrambled, () => {
448
+ const budget = getTruncationBudget(toolPrefixLen);
449
+ const freshPlain = stripAnsi(lineText);
450
+ const text = truncateChars(freshPlain, budget);
451
+ return scrambleManager.updateText(flowId, `tool#${i}`, text, Date.now(), isComplete).content;
452
+ }));
453
+ }
454
+ }
455
+ if (isComplete) {
456
+ scrambleManager.completeFlow(flowId);
457
+ }
458
+ }
459
+ // Total stats: dashboard format
460
+ const totalUsage = aggregateFlowUsage(results);
461
+ const totalModel = results[0]?.model;
462
+ const totalStats = formatCompactStats(totalUsage, totalModel);
463
+ container.addChild(new Spacer(1));
464
+ container.addChild(new Text(applyRole("stats", totalStats, theme, config), 0, 0));
465
+ return container;
466
+ }
467
+ function renderFlowBody(container, r, flowId, indent, theme, now, config) {
468
+ const isComplete = isFlowStatusComplete(r);
469
+ const flowComplete = isComplete;
470
+ // aim: line — glitch on text change
471
+ if (r.aim && r.type !== "trace") {
472
+ const aimTree = indent + "├─";
473
+ const aimLabel = ` aim ▸ `;
474
+ const aimPrefix = `${aimTree}${aimLabel}`;
475
+ const budget = getTruncationBudget(visibleLength(aimPrefix));
476
+ const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
477
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(getContentRole("aimContent", displayAim), displayAim, theme, config)}`, () => {
478
+ const now = Date.now();
479
+ const freshAimLabel = ` aim ▸ `;
480
+ const freshAimPrefix = `${aimTree}${freshAimLabel}`;
481
+ const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
482
+ const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
483
+ const result = scrambleManager.updateAim(flowId, freshText, now, flowComplete, true);
484
+ return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(getContentRole("aimContent", freshText), result.content, theme, config)}`;
485
+ }, true));
486
+ }
487
+ // act: line (last tool call with count)
488
+ const lastTool = getLastToolCall(r.messages);
489
+ const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
490
+ const isLite = config?.bodyVerbosity !== "full";
491
+ const actTree = isLite ? `${indent}└─` : `${indent}├─`;
492
+ const actLabel = ` cmd ▸ `;
493
+ const prefixStub = `${actTree}${actLabel}`;
494
+ const budget = getTruncationBudget(visibleLength(prefixStub));
495
+ const actFullText = stripAnsi(lowerFirstWord(actStr));
496
+ const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
497
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", initialActContent), initialActContent, theme, config)}`, () => {
498
+ const now = Date.now();
499
+ const actLabel = ` cmd ▸ `;
500
+ const actPrefix = `${actTree}${actLabel}`;
501
+ const freshBudget = getTruncationBudget(visibleLength(actPrefix));
502
+ const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
503
+ const actContent = scrambleManager.updateAct(flowId, displayAct, now, flowComplete, true).content;
504
+ return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", displayAct), actContent, theme, config)}`;
505
+ }, true));
506
+ // msg: line (live streaming text or last assistant text) — full mode only
507
+ if (!isLite) {
508
+ const msgTree = `${indent}└─`;
509
+ const msgLabel = ` msg ▸ `;
510
+ const msgPrefixStub = `${msgTree}${msgLabel}`;
511
+ const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
512
+ let rawMsg;
513
+ let useError = false;
514
+ if (isFlowAwaiting(r)) {
515
+ rawMsg = "[awaiting...]";
516
+ }
517
+ else if (r.status === "skipped") {
518
+ rawMsg = "[skipped]";
519
+ }
520
+ else if (isFlowStatusComplete(r) && !isFlowRunning(r)) {
521
+ if (isFlowError(r) && r.errorMessage) {
522
+ rawMsg = stripAnsi(r.errorMessage);
523
+ useError = true;
524
+ }
525
+ else if ((r.pingPongMeta?.finalVerdict === "pass" || r.structuredOutput?.verdict === "pass") && r.type === "audit") {
526
+ rawMsg = "[approved]";
527
+ }
528
+ else if (r.type === "audit" && !r.structuredOutput?.summary && !getFlowOutput(r.messages)) {
529
+ rawMsg = "[finished]";
530
+ }
531
+ else {
532
+ rawMsg = stripAnsi(r.structuredOutput?.summary ?? getFlowOutput(r.messages) ?? getFlowSummaryText(r)) || "[n/a]";
533
+ }
534
+ }
535
+ else {
536
+ const liveMsgText = isFlowRunning(r) ? getLiveTextWithFallback(flowId) : undefined;
537
+ if (liveMsgText != null) {
538
+ rawMsg = stripAnsi(liveMsgText);
539
+ }
540
+ else if (isFlowRunning(r) && r.streamingText != null) {
541
+ rawMsg = stripAnsi(r.streamingText);
542
+ }
543
+ else if (r.structuredOutput?.summary) {
544
+ rawMsg = stripAnsi(r.structuredOutput.summary);
545
+ }
546
+ else {
547
+ const flowOutput = getFlowOutput(r.messages);
548
+ if (flowOutput) {
549
+ rawMsg = stripAnsi(flowOutput);
550
+ }
551
+ else if (isFlowError(r) && r.errorMessage) {
552
+ rawMsg = stripAnsi(r.errorMessage);
553
+ useError = true;
554
+ }
555
+ else {
556
+ const summary = getFlowSummaryText(r);
557
+ rawMsg = stripAnsi(summary) || "[n/a]";
558
+ }
559
+ }
560
+ }
561
+ const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
562
+ const initialDisplayMsg = initialNeedsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
563
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", initialDisplayMsg, useError), initialDisplayMsg, theme, config)}`, () => {
564
+ const now = Date.now();
565
+ const msgLabel = ` msg ▸ `;
566
+ const msgPrefix = `${msgTree}${msgLabel}`;
567
+ let freshRawMsg;
568
+ let needsTail;
569
+ if (isFlowAwaiting(r)) {
570
+ freshRawMsg = "[awaiting...]";
571
+ needsTail = false;
572
+ }
573
+ else if (r.status === "skipped") {
574
+ freshRawMsg = "[skipped]";
575
+ needsTail = false;
576
+ }
577
+ else {
578
+ const isRunningNow = isFlowRunning(r);
579
+ freshRawMsg = (isRunningNow ? getLiveTextWithFallback(flowId) : undefined) ?? rawMsg;
580
+ needsTail = isRunningNow && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
581
+ }
582
+ const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
583
+ const result = scrambleManager.updateMsg(flowId, displayMsg, now, flowComplete, undefined, true);
584
+ return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", freshRawMsg, useError), result.content, theme, config)}`;
585
+ }, true));
586
+ }
587
+ }
588
+ export { renderFlowExpanded, renderFlowCollapsed, renderFlowBody, renderMultiFlowExpanded };
589
+ //# sourceMappingURL=body-render.js.map