pi-agent-flow 1.8.1 → 1.8.3

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 (154) hide show
  1. package/README.md +4 -30
  2. package/agents/audit.md +1 -2
  3. package/agents/build.md +1 -0
  4. package/agents/craft.md +12 -8
  5. package/agents/debug.md +2 -2
  6. package/agents/ideas.md +1 -0
  7. package/agents/scout.md +1 -0
  8. package/dist/agents.d.ts +41 -0
  9. package/dist/agents.d.ts.map +1 -0
  10. package/dist/agents.js +283 -0
  11. package/dist/agents.js.map +1 -0
  12. package/dist/batch/batch-bash.d.ts +87 -0
  13. package/dist/batch/batch-bash.d.ts.map +1 -0
  14. package/dist/batch/batch-bash.js +369 -0
  15. package/dist/batch/batch-bash.js.map +1 -0
  16. package/dist/batch/constants.d.ts +100 -0
  17. package/dist/batch/constants.d.ts.map +1 -0
  18. package/dist/batch/constants.js +15 -0
  19. package/dist/batch/constants.js.map +1 -0
  20. package/dist/batch/execute.d.ts +21 -0
  21. package/dist/batch/execute.d.ts.map +1 -0
  22. package/dist/batch/execute.js +440 -0
  23. package/dist/batch/execute.js.map +1 -0
  24. package/dist/batch/fuzzy-edit.d.ts +29 -0
  25. package/dist/batch/fuzzy-edit.d.ts.map +1 -0
  26. package/dist/batch/fuzzy-edit.js +257 -0
  27. package/dist/batch/fuzzy-edit.js.map +1 -0
  28. package/dist/batch/index.d.ts +85 -0
  29. package/dist/batch/index.d.ts.map +1 -0
  30. package/dist/batch/index.js +422 -0
  31. package/dist/batch/index.js.map +1 -0
  32. package/dist/batch/render.d.ts +14 -0
  33. package/dist/batch/render.d.ts.map +1 -0
  34. package/dist/batch/render.js +74 -0
  35. package/dist/batch/render.js.map +1 -0
  36. package/dist/batch/symbols.d.ts +9 -0
  37. package/dist/batch/symbols.d.ts.map +1 -0
  38. package/dist/batch/symbols.js +310 -0
  39. package/dist/batch/symbols.js.map +1 -0
  40. package/dist/batch.d.ts +12 -0
  41. package/dist/batch.d.ts.map +1 -0
  42. package/dist/batch.js +11 -0
  43. package/dist/batch.js.map +1 -0
  44. package/dist/cli-args.d.ts +27 -0
  45. package/dist/cli-args.d.ts.map +1 -0
  46. package/dist/cli-args.js +265 -0
  47. package/dist/cli-args.js.map +1 -0
  48. package/dist/config.d.ts +58 -0
  49. package/dist/config.d.ts.map +1 -0
  50. package/dist/config.js +296 -0
  51. package/dist/config.js.map +1 -0
  52. package/dist/depth.d.ts +25 -0
  53. package/dist/depth.d.ts.map +1 -0
  54. package/dist/depth.js +160 -0
  55. package/dist/depth.js.map +1 -0
  56. package/dist/executor.d.ts +87 -0
  57. package/dist/executor.d.ts.map +1 -0
  58. package/dist/executor.js +295 -0
  59. package/dist/executor.js.map +1 -0
  60. package/dist/flow-prompt.d.ts +23 -0
  61. package/dist/flow-prompt.d.ts.map +1 -0
  62. package/dist/flow-prompt.js +99 -0
  63. package/dist/flow-prompt.js.map +1 -0
  64. package/dist/flow.d.ts +76 -0
  65. package/dist/flow.d.ts.map +1 -0
  66. package/dist/flow.js +704 -0
  67. package/dist/flow.js.map +1 -0
  68. package/dist/index.d.ts +10 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +327 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/reasoning-strip.d.ts +26 -0
  73. package/dist/reasoning-strip.d.ts.map +1 -0
  74. package/dist/reasoning-strip.js +58 -0
  75. package/dist/reasoning-strip.js.map +1 -0
  76. package/dist/render-utils.d.ts +42 -0
  77. package/dist/render-utils.d.ts.map +1 -0
  78. package/dist/render-utils.js +182 -0
  79. package/dist/render-utils.js.map +1 -0
  80. package/dist/render.d.ts +24 -0
  81. package/dist/render.d.ts.map +1 -0
  82. package/dist/render.js +409 -0
  83. package/dist/render.js.map +1 -0
  84. package/dist/runner-events.d.ts +59 -0
  85. package/dist/runner-events.d.ts.map +1 -0
  86. package/dist/runner-events.js +539 -0
  87. package/dist/runner-events.js.map +1 -0
  88. package/dist/session-mode.d.ts +10 -0
  89. package/dist/session-mode.d.ts.map +1 -0
  90. package/dist/session-mode.js +25 -0
  91. package/dist/session-mode.js.map +1 -0
  92. package/dist/settings-resolver.d.ts +28 -0
  93. package/dist/settings-resolver.d.ts.map +1 -0
  94. package/dist/settings-resolver.js +148 -0
  95. package/dist/settings-resolver.js.map +1 -0
  96. package/dist/sliding-prompt.d.ts +40 -0
  97. package/dist/sliding-prompt.d.ts.map +1 -0
  98. package/dist/sliding-prompt.js +121 -0
  99. package/dist/sliding-prompt.js.map +1 -0
  100. package/dist/snapshot.d.ts +29 -0
  101. package/dist/snapshot.d.ts.map +1 -0
  102. package/dist/snapshot.js +199 -0
  103. package/dist/snapshot.js.map +1 -0
  104. package/dist/structured-output.d.ts +36 -0
  105. package/dist/structured-output.d.ts.map +1 -0
  106. package/dist/structured-output.js +244 -0
  107. package/dist/structured-output.js.map +1 -0
  108. package/dist/timed-bash.d.ts +45 -0
  109. package/dist/timed-bash.d.ts.map +1 -0
  110. package/dist/timed-bash.js +219 -0
  111. package/dist/timed-bash.js.map +1 -0
  112. package/dist/tool-utils.d.ts +20 -0
  113. package/dist/tool-utils.d.ts.map +1 -0
  114. package/dist/tool-utils.js +38 -0
  115. package/dist/tool-utils.js.map +1 -0
  116. package/dist/transitions.d.ts +39 -0
  117. package/dist/transitions.d.ts.map +1 -0
  118. package/dist/transitions.js +59 -0
  119. package/dist/transitions.js.map +1 -0
  120. package/dist/types.d.ts +207 -0
  121. package/dist/types.d.ts.map +1 -0
  122. package/dist/types.js +143 -0
  123. package/dist/types.js.map +1 -0
  124. package/dist/web-tool.d.ts +35 -0
  125. package/dist/web-tool.d.ts.map +1 -0
  126. package/dist/web-tool.js +545 -0
  127. package/dist/web-tool.js.map +1 -0
  128. package/package.json +7 -5
  129. package/src/agents.ts +0 -299
  130. package/src/ambient.d.ts +0 -107
  131. package/src/batch/batch-bash.ts +0 -443
  132. package/src/batch/constants.ts +0 -128
  133. package/src/batch/execute.ts +0 -551
  134. package/src/batch/fuzzy-edit.ts +0 -323
  135. package/src/batch/index.ts +0 -494
  136. package/src/batch/render.ts +0 -81
  137. package/src/batch/symbols.ts +0 -341
  138. package/src/batch.ts +0 -28
  139. package/src/cli-args.ts +0 -315
  140. package/src/config.ts +0 -391
  141. package/src/executor.ts +0 -445
  142. package/src/flow.ts +0 -834
  143. package/src/hooks.ts +0 -294
  144. package/src/index.ts +0 -1132
  145. package/src/render-utils.ts +0 -205
  146. package/src/render.ts +0 -524
  147. package/src/runner-events.ts +0 -692
  148. package/src/session-mode.ts +0 -33
  149. package/src/sliding-prompt.ts +0 -144
  150. package/src/structured-output.ts +0 -195
  151. package/src/timed-bash.ts +0 -270
  152. package/src/transitions.ts +0 -86
  153. package/src/types.ts +0 -386
  154. package/src/web-tool.ts +0 -663
package/src/render.ts DELETED
@@ -1,524 +0,0 @@
1
- /**
2
- * TUI rendering for flow-state tool calls and results.
3
- *
4
- * Option B: collapsed view shows structured report (Summary/Done/Not Done/Next Steps).
5
- * Expanded view adds raw tool call traces.
6
- */
7
-
8
- import * as os from "node:os";
9
- import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
10
- import { Container, Markdown, Spacer, Text, TruncatedText } from "@mariozechner/pi-tui";
11
- import { getFlowSummaryText } from "./runner-events.js";
12
- import {
13
- type DisplayItem,
14
- type SingleResult,
15
- type FlowDetails,
16
- type UsageStats,
17
- aggregateFlowUsage,
18
- getFlowDisplayItems,
19
- getFlowOutput,
20
- getLastToolCall,
21
- getLastAssistantText,
22
- isFlowError,
23
- isFlowSuccess,
24
- } from "./types.js";
25
- import { formatCompactStats, formatCompactTokenPair, formatCountdown, formatFlowTypeName, truncateChars, tailText, contentBudget, visibleLength, DETAIL_CONTENT_MAX } from "./render-utils.js";
26
-
27
- function shortenPath(p: string): string {
28
- const home = os.homedir();
29
- return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
30
- }
31
-
32
- type ThemeFg = (color: string, text: string) => string;
33
- type ThemeBg = (color: string, text: string) => string;
34
- type FlowTheme = { fg: ThemeFg; bold: (s: string) => string; bg: ThemeBg };
35
-
36
- const COLLAPSED_FLOW_HEADER_TYPE_WIDTH = 8;
37
-
38
- function formatCollapsedFlowHeaderTypeName(type: string): string {
39
- return formatFlowTypeName(type).padEnd(COLLAPSED_FLOW_HEADER_TYPE_WIDTH, " ");
40
- }
41
-
42
- function formatFlowToolCall(toolName: string, args: Record<string, unknown>, fg: ThemeFg): string {
43
- const pathArg = (args.file_path || args.path || "...") as string;
44
-
45
- switch (toolName) {
46
- case "bash": {
47
- const cmd = ((args.command as string) || "...").replace(/[\n\r\t]+/g, " ").replace(/ +/g, " ").trim();
48
- return fg("muted", "$ ") + fg("toolOutput", cmd);
49
- }
50
- case "read": {
51
- let text = fg("accent", shortenPath(pathArg));
52
- const offset = args.offset as number | undefined;
53
- const limit = args.limit as number | undefined;
54
- if (offset !== undefined || limit !== undefined) {
55
- const start = offset ?? 1;
56
- const end = limit !== undefined ? start + limit - 1 : "";
57
- text += fg("warning", `:${start}${end ? `-${end}` : ""}`);
58
- }
59
- return fg("muted", "read ") + text;
60
- }
61
- case "write": {
62
- const lines = ((args.content || "") as string).split("\n").length;
63
- let text = fg("muted", "write ") + fg("accent", shortenPath(pathArg));
64
- if (lines > 1) text += fg("dim", ` (${lines} lines)`);
65
- return text;
66
- }
67
- case "edit":
68
- return fg("muted", "edit ") + fg("accent", shortenPath(pathArg));
69
- case "ls":
70
- return fg("muted", "ls ") + fg("accent", shortenPath((args.path || ".") as string));
71
- case "find":
72
- return fg("muted", "find ") + fg("accent", (args.pattern || "*") as string) + fg("dim", ` in ${shortenPath((args.path || ".") as string)}`);
73
- case "grep":
74
- return fg("muted", "grep ") + fg("accent", `/${(args.pattern || "") as string}/`) + fg("dim", ` in ${shortenPath((args.path || ".") as string)}`);
75
- case "batch":
76
- case "batch_read": {
77
- const ops = Array.isArray(args.o) ? args.o : Array.isArray(args.op) ? args.op : Array.isArray(args.operations) ? args.operations : Array.isArray(args) ? args : [];
78
- if (ops.length === 0) return fg("muted", `${toolName} (empty)`);
79
- const parts: string[] = [];
80
- for (const op of ops) {
81
- const opObj = op as Record<string, unknown>;
82
- const opName = (opObj.o ?? opObj.op ?? "?") as string;
83
- const opPath = (opObj.p ?? opObj.path ?? "?") as string;
84
- const shortPath = shortenPath(opPath);
85
- if (opName === "edit") {
86
- const edits = (opObj.e ?? opObj.edits) as unknown[] | undefined;
87
- const blockInfo = edits && edits.length > 1 ? ` (${edits.length} blocks)` : "";
88
- parts.push(`edit ${shortPath}${blockInfo}`);
89
- } else {
90
- parts.push(`${opName} ${shortPath}`);
91
- }
92
- }
93
- const summary = parts.length <= 3 ? parts.join(", ") : `${parts.slice(0, 2).join(", ")} +${parts.length - 2} more`;
94
- return fg("muted", `${toolName} `) + fg("accent", summary);
95
- }
96
- default:
97
- return fg("accent", toolName) + fg("dim", ` ${JSON.stringify(args)}`);
98
- }
99
- }
100
-
101
- // ---------------------------------------------------------------------------
102
- // Shared rendering building blocks
103
- // ---------------------------------------------------------------------------
104
-
105
- function splitOutputLines(text: string): string[] {
106
- const lines = text.replace(/\r\n?/g, "\n").split("\n");
107
- if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
108
- return lines;
109
- }
110
-
111
- function renderToolTraces(
112
- items: DisplayItem[],
113
- theme: { fg: ThemeFg },
114
- ): string {
115
- const lines: string[] = [];
116
- for (const item of items) {
117
- if (item.type === "toolCall") {
118
- lines.push(theme.fg("muted", "→ ") + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme)));
119
- }
120
- }
121
- return lines.join("\n");
122
- }
123
-
124
- function renderFlowReport(
125
- output: string,
126
- theme: { fg: ThemeFg },
127
- ): string {
128
- const lines = splitOutputLines(output);
129
- return lines.map((line) => theme.fg("toolOutput", line)).join("\n");
130
- }
131
-
132
- function flowStatusIcon(r: SingleResult, theme: { fg: ThemeFg }): string {
133
- if (r.exitCode === -1) return theme.fg("warning", "⏳");
134
- return isFlowError(r) ? theme.fg("error", "✗") : theme.fg("success", "✓");
135
- }
136
-
137
- /** Center a label in a fixed-width header using em-dashes. Total width = 20. */
138
- function sectionHeader(label: string): string {
139
- const total = 20;
140
- const innerLen = label.length + 2; // account for spaces around label
141
- const side = (total - innerLen) / 2;
142
- const left = "─".repeat(Math.floor(side));
143
- const right = "─".repeat(Math.ceil(side));
144
- return `${left} ${label} ${right}`;
145
- }
146
-
147
- function getLiveCountdown(r: SingleResult): string | undefined {
148
- if (r.exitCode !== -1 || typeof r.deadlineAtMs !== "number") return undefined;
149
- return formatCountdown(r.deadlineAtMs - Date.now());
150
- }
151
-
152
- function formatAimLinePrefix(treePrefix: string, r: SingleResult): string {
153
- const countdown = getLiveCountdown(r);
154
- return countdown ? `${treePrefix} aim: [${countdown}] - ` : `${treePrefix} aim: `;
155
- }
156
-
157
- function formatMsgLinePrefix(treePrefix: string, r: SingleResult): string {
158
- return `${treePrefix} msg: [${formatCompactTokenPair(r.usage)}] - `;
159
- }
160
-
161
- // ---------------------------------------------------------------------------
162
- // renderFlowCall — shown while the flow is being invoked
163
- // ---------------------------------------------------------------------------
164
-
165
- export function renderFlowCall(args: Record<string, any>, theme: FlowTheme): Text {
166
- const flows = args.flow as Array<{ type: string; intent: string }> | undefined;
167
-
168
- // Minimal — renderFlowResult owns the full display
169
- return new Text("", 0, 0);
170
- }
171
-
172
- // ---------------------------------------------------------------------------
173
- // renderFlowResult — shown after the flow completes
174
- // ---------------------------------------------------------------------------
175
-
176
- export function renderFlowResult(
177
- result: { content: Array<{ type: string; text?: string }>; details?: unknown },
178
- expanded: boolean,
179
- theme: FlowTheme,
180
- args?: Record<string, any>,
181
- ): Container | Text {
182
- const details = result.details as FlowDetails | undefined;
183
- const streamingText = result.content?.[0]?.type === "text" ? result.content[0].text : undefined;
184
-
185
- if (!details || details.results.length === 0) {
186
- // Ghost Dashboard: render a placeholder status line during the zero state
187
- const flowRequest = args?.flow?.[0];
188
- if (flowRequest) {
189
- const ghostResult: SingleResult = {
190
- type: flowRequest.type || "unknown",
191
- agentSource: "user",
192
- intent: flowRequest.intent || "Processing...",
193
- aim: flowRequest.aim || flowRequest.intent || "Processing...",
194
- exitCode: -1, // In progress
195
- messages: [],
196
- stderr: "",
197
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0, toolCalls: 0 },
198
- };
199
- return renderFlowCollapsed(ghostResult, flowStatusIcon(ghostResult, theme), false, streamingText || "", theme);
200
- }
201
- return new Text(streamingText || "", 0, 0);
202
- }
203
-
204
- if (details.results.length === 1) {
205
- return renderSingleFlowResult(details.results[0], expanded, theme, streamingText);
206
- }
207
-
208
- return renderMultiFlowResult(details, expanded, theme);
209
- }
210
-
211
- // ---------------------------------------------------------------------------
212
- // Single flow result
213
- // ---------------------------------------------------------------------------
214
-
215
- function renderSingleFlowResult(
216
- r: SingleResult,
217
- expanded: boolean,
218
- theme: FlowTheme,
219
- streamingText?: string,
220
- ): Container | Text {
221
- const error = isFlowError(r);
222
- const icon = flowStatusIcon(r, theme);
223
- const displayItems = getFlowDisplayItems(r.messages);
224
- const flowOutput = getFlowOutput(r.messages);
225
-
226
- if (expanded) {
227
- return renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme);
228
- }
229
- return renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText);
230
- }
231
-
232
- function renderFlowExpanded(
233
- r: SingleResult,
234
- icon: string,
235
- error: boolean,
236
- displayItems: DisplayItem[],
237
- flowOutput: string,
238
- theme: FlowTheme,
239
- ): Container {
240
- const mdTheme = getMarkdownTheme();
241
- const container = new Container();
242
-
243
- // Header: uppercase type name with dots, no icon, no source
244
- const typeName = formatFlowTypeName(r.type);
245
- let header = theme.fg("toolTitle", theme.bold(typeName));
246
- if (error && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
247
- container.addChild(new Text(header, 0, 0));
248
- if (error && r.errorMessage) {
249
- container.addChild(new Text(theme.fg("error", `Error: ${r.errorMessage}`), 0, 0));
250
- }
251
-
252
- // Stats: dashboard format
253
- const inlineStats = formatCompactStats(r.usage, r.model);
254
- container.addChild(new Text(theme.fg("dim", inlineStats), 0, 0));
255
-
256
- // Intent
257
- container.addChild(new Spacer(1));
258
- container.addChild(new Text(theme.fg("muted", sectionHeader("intent")), 0, 0));
259
- container.addChild(new Text(theme.fg("dim", r.intent), 0, 0));
260
-
261
- // Flow report (structured output)
262
- container.addChild(new Spacer(1));
263
- container.addChild(new Text(theme.fg("muted", sectionHeader("report")), 0, 0));
264
-
265
- // Structured output summary (compact badge when available)
266
- if (r.structuredOutput) {
267
- const so = r.structuredOutput;
268
- const statusColor = so.status === "complete" ? "success" : so.status === "partial" ? "warning" : "error";
269
- container.addChild(new Text(
270
- `${theme.fg(statusColor, `[${so.status}]`)} ${theme.fg("dim", so.summary)}`,
271
- 0, 0,
272
- ));
273
- if (so.files.length > 0) {
274
- container.addChild(new Text(theme.fg("dim", `Files: ${so.files.map((f) => f.path).join(", ")}`), 0, 0));
275
- }
276
- if (so.commands?.length > 0) {
277
- const cmdLabels = so.commands.map((c) => {
278
- const short = c.command.length > 30 ? c.command.slice(0, 30) + "..." : c.command;
279
- return `${c.tool ?? "cmd"}: ${short}`;
280
- });
281
- container.addChild(new Text(theme.fg("dim", `Commands: ${cmdLabels.join(", ")}`), 0, 0));
282
- }
283
- if (so.notDone.length > 0) {
284
- const notDoneText = so.notDone.map((item) => {
285
- const details = [
286
- item.reason ? `reason: ${item.reason}` : undefined,
287
- item.blocker ? `blocker: ${item.blocker}` : undefined,
288
- item.nextStep ? `next: ${item.nextStep}` : undefined,
289
- ].filter(Boolean).join("; ");
290
- return details ? `${item.item} (${details})` : item.item;
291
- }).join("; ");
292
- container.addChild(new Text(theme.fg("dim", `Not Done: ${notDoneText}`), 0, 0));
293
- }
294
- if (so.nextSteps.length > 0) {
295
- container.addChild(new Text(theme.fg("dim", `Next: ${so.nextSteps.join("; ")}`), 0, 0));
296
- }
297
- container.addChild(new Spacer(1));
298
- }
299
-
300
- if (flowOutput) {
301
- container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
302
- } else {
303
- const summary = getFlowSummaryText(r);
304
- container.addChild(new Text(theme.fg("muted", summary), 0, 0));
305
- }
306
-
307
- // Tool traces (expanded only)
308
- const toolTraces = renderToolTraces(displayItems, theme);
309
- if (toolTraces) {
310
- container.addChild(new Spacer(1));
311
- container.addChild(new Text(theme.fg("muted", sectionHeader("tool calls")), 0, 0));
312
- container.addChild(new Text(toolTraces, 0, 0));
313
- }
314
-
315
- return container;
316
- }
317
-
318
- function renderFlowCollapsed(
319
- r: SingleResult,
320
- icon: string,
321
- error: boolean,
322
- flowOutput: string,
323
- theme: FlowTheme,
324
- streamingText?: string,
325
- ): Container {
326
- const container = new Container();
327
- const maxWidth = process.stdout.columns ?? 80;
328
- const stats = formatCompactStats(r.usage, r.model, maxWidth, { skipTokens: true, skipContext: true, hideModel: true });
329
- const typeName = formatCollapsedFlowHeaderTypeName(r.type);
330
- const modelLabel = r.model ? r.model.replace(/^[^/]+\//, "") : "";
331
- let header = `${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", modelLabel ? ` ${modelLabel} - ` : " ")}${theme.fg("dim", stats)}`;
332
- if (error && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
333
- container.addChild(new TruncatedText(header, 0, 0));
334
-
335
- // aim: line (short headline)
336
- if (r.aim) {
337
- const aimPrefix = formatAimLinePrefix("├─", r);
338
- const dirContent = truncateChars(r.aim, contentBudget(visibleLength(aimPrefix)));
339
- container.addChild(new TruncatedText(`${theme.fg("dim", aimPrefix)}${theme.fg("dim", dirContent)}`, 0, 0));
340
- }
341
-
342
- // act: line (last tool call with count)
343
- const lastTool = getLastToolCall(r.messages);
344
- if (lastTool) {
345
- const actStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
346
- const actPrefix = `├─ act: [${r.usage.toolCalls}] - `;
347
- const actContent = truncateChars(actStr, contentBudget(visibleLength(actPrefix), DETAIL_CONTENT_MAX));
348
- container.addChild(new TruncatedText(`${theme.fg("dim", actPrefix)}${actContent}`, 0, 0));
349
- }
350
-
351
- // msg: line (last assistant text or streaming)
352
- const msgPrefix = formatMsgLinePrefix("└─", r);
353
- const msgBudget = contentBudget(visibleLength(msgPrefix), DETAIL_CONTENT_MAX);
354
- if (r.exitCode === -1 && streamingText) {
355
- const logContent = tailText(streamingText, msgBudget);
356
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", logContent)}`, 0, 0));
357
- } else if (r.structuredOutput?.summary) {
358
- const logContent = truncateChars(r.structuredOutput.summary, msgBudget);
359
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", logContent)}`, 0, 0));
360
- } else if (flowOutput) {
361
- const logContent = tailText(flowOutput, msgBudget);
362
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", logContent)}`, 0, 0));
363
- } else if (streamingText) {
364
- const logContent = tailText(streamingText, msgBudget);
365
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", logContent)}`, 0, 0));
366
- } else if (error && r.errorMessage) {
367
- const logContent = truncateChars(r.errorMessage, msgBudget);
368
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("error", logContent)}`, 0, 0));
369
- } else {
370
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", "[n/a]")}`, 0, 0));
371
- }
372
-
373
- return container;
374
- }
375
-
376
- // ---------------------------------------------------------------------------
377
- // Multi-flow result
378
- // ---------------------------------------------------------------------------
379
-
380
- function renderMultiFlowResult(
381
- details: FlowDetails,
382
- expanded: boolean,
383
- theme: FlowTheme,
384
- ): Container | Text {
385
- const results = details.results;
386
- const successCount = results.filter((r) => isFlowSuccess(r)).length;
387
- const failCount = results.filter((r) => isFlowError(r)).length;
388
- const icon = failCount > 0 ? theme.fg("warning", "◐") : theme.fg("success", "✓");
389
-
390
- if (expanded) {
391
- return renderMultiFlowExpanded(results, successCount, icon, theme);
392
- }
393
- return renderMultiFlowCollapsed(results, theme);
394
- }
395
-
396
- function renderMultiFlowExpanded(
397
- results: SingleResult[],
398
- successCount: number,
399
- icon: string,
400
- theme: FlowTheme,
401
- ): Container {
402
- const mdTheme = getMarkdownTheme();
403
- const container = new Container();
404
-
405
- // Summary: just show count, no icon
406
- container.addChild(new Text(
407
- theme.fg("accent", `${results.length} flows`),
408
- 0, 0,
409
- ));
410
-
411
- for (const r of results) {
412
- const displayItems = getFlowDisplayItems(r.messages);
413
- const flowOutput = getFlowOutput(r.messages);
414
- const typeName = formatFlowTypeName(r.type);
415
-
416
- container.addChild(new Spacer(1));
417
- // Per-flow header: ─── EXPLORER (no icon)
418
- container.addChild(new Text(theme.fg("muted", sectionHeader(typeName)), 0, 0));
419
-
420
- // Stats: dashboard format
421
- const flowStats = formatCompactStats(r.usage, r.model);
422
- container.addChild(new Text(theme.fg("dim", flowStats), 0, 0));
423
-
424
- // Intent: just show text, no prefix
425
- container.addChild(new Text(theme.fg("dim", r.intent), 0, 0));
426
-
427
- if (flowOutput) {
428
- container.addChild(new Spacer(1));
429
- container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
430
- }
431
-
432
- // Tool traces in expanded view
433
- const toolTraces = renderToolTraces(displayItems, theme);
434
- if (toolTraces) {
435
- container.addChild(new Spacer(1));
436
- container.addChild(new Text(theme.fg("muted", sectionHeader("tool calls")), 0, 0));
437
- container.addChild(new Text(toolTraces, 0, 0));
438
- }
439
- }
440
-
441
- // Total stats: dashboard format
442
- const totalUsage = aggregateFlowUsage(results);
443
- const totalModel = results[0]?.model;
444
- const totalStats = formatCompactStats(totalUsage, totalModel);
445
- container.addChild(new Spacer(1));
446
- container.addChild(new Text(theme.fg("dim", totalStats), 0, 0));
447
-
448
- return container;
449
- }
450
-
451
- function renderActivityPanel(
452
- results: SingleResult[],
453
- theme: FlowTheme,
454
- ): Container {
455
- const container = new Container();
456
- const maxWidth = process.stdout.columns ?? 80;
457
-
458
- for (let i = 0; i < results.length; i++) {
459
- const r = results[i];
460
- const isLast = i === results.length - 1;
461
- const stats = formatCompactStats(r.usage, r.model, maxWidth, { skipTokens: true, skipContext: true, hideModel: true });
462
- const error = isFlowError(r);
463
- const typeName = formatCollapsedFlowHeaderTypeName(r.type);
464
-
465
- // Header line
466
- const headerPrefix = isLast ? "└─" : "├─";
467
- const modelLabel = r.model ? r.model.replace(/^[^/]+\//, "") : "";
468
- let headerLine = `${theme.fg("dim", headerPrefix)} ${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", modelLabel ? ` ${modelLabel} - ` : " ")}${theme.fg("dim", stats)}`;
469
- if (error && r.stopReason) {
470
- headerLine += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
471
- }
472
- container.addChild(new TruncatedText(headerLine, 0, 0));
473
-
474
- // Continuation indent for sub-lines
475
- const indent = isLast ? " " : "│ ";
476
-
477
- // aim: line (short headline)
478
- if (r.aim) {
479
- const aimPrefix = formatAimLinePrefix(indent + "├─", r);
480
- const dirContent = truncateChars(r.aim, contentBudget(visibleLength(aimPrefix)));
481
- container.addChild(new TruncatedText(`${theme.fg("dim", aimPrefix)}${theme.fg("dim", dirContent)}`, 0, 0));
482
- }
483
-
484
- // act: line (last tool call with count)
485
- const lastTool = getLastToolCall(r.messages);
486
- if (lastTool) {
487
- const actStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
488
- const actPrefix = `${indent}├─ act: [${r.usage.toolCalls}] - `;
489
- const actContent = truncateChars(actStr, contentBudget(visibleLength(actPrefix), DETAIL_CONTENT_MAX));
490
- container.addChild(new TruncatedText(`${theme.fg("dim", actPrefix)}${actContent}`, 0, 0));
491
- }
492
-
493
- // msg: line (live streaming text or last assistant text)
494
- const msgPrefix = formatMsgLinePrefix(indent + "└─", r);
495
- const msgBudget = contentBudget(visibleLength(msgPrefix), DETAIL_CONTENT_MAX);
496
- const liveText = r.exitCode === -1 ? r.streamingText : undefined;
497
- const lastText = liveText || getLastAssistantText(r.messages);
498
- if (lastText) {
499
- const logContent = tailText(lastText, msgBudget);
500
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", logContent)}`, 0, 0));
501
- } else if (error && r.errorMessage) {
502
- const logContent = truncateChars(r.errorMessage, msgBudget);
503
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("error", logContent)}`, 0, 0));
504
- } else {
505
- container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", "[n/a]")}`, 0, 0));
506
- }
507
-
508
- // Add blank line separator between flows (with continuation pipe)
509
- if (!isLast) {
510
- container.addChild(new TruncatedText(theme.fg("dim", "│"), 0, 0));
511
- }
512
- }
513
-
514
- container.addChild(new TruncatedText(theme.fg("muted", "(Ctrl+O to expand tool traces)"), 0, 0));
515
-
516
- return container;
517
- }
518
-
519
- function renderMultiFlowCollapsed(
520
- results: SingleResult[],
521
- theme: FlowTheme,
522
- ): Container {
523
- return renderActivityPanel(results, theme);
524
- }