pi-agent-flow 1.8.40 → 2.0.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.
- package/README.md +33 -37
- package/agents/audit.md +18 -22
- package/agents/build.md +20 -22
- package/agents/craft.md +20 -27
- package/agents/debug.md +21 -28
- package/agents/ideas.md +18 -101
- package/agents/scout.md +15 -19
- package/dist/batch/batch-bash.d.ts +2 -2
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +3 -3
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +19 -5
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/constants.js +4 -3
- package/dist/batch/constants.js.map +1 -1
- package/dist/batch/execute.d.ts +0 -1
- package/dist/batch/execute.d.ts.map +1 -1
- package/dist/batch/execute.js +97 -6
- package/dist/batch/execute.js.map +1 -1
- package/dist/batch/fuzzy-edit.d.ts +0 -6
- package/dist/batch/fuzzy-edit.d.ts.map +1 -1
- package/dist/batch/fuzzy-edit.js +1 -1
- package/dist/batch/fuzzy-edit.js.map +1 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +87 -16
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/render.d.ts +0 -1
- package/dist/batch/render.d.ts.map +1 -1
- package/dist/batch/render.js +7 -101
- package/dist/batch/render.js.map +1 -1
- package/dist/batch/summary.d.ts +5 -0
- package/dist/batch/summary.d.ts.map +1 -0
- package/dist/batch/summary.js +101 -0
- package/dist/batch/summary.js.map +1 -0
- package/dist/{config.d.ts → config/config.d.ts} +34 -2
- package/dist/config/config.d.ts.map +1 -0
- package/dist/{config.js → config/config.js} +157 -9
- package/dist/config/config.js.map +1 -0
- package/dist/config/log.d.ts +27 -0
- package/dist/config/log.d.ts.map +1 -0
- package/dist/config/log.js +104 -0
- package/dist/config/log.js.map +1 -0
- package/dist/{settings-resolver.d.ts → config/settings-resolver.d.ts} +9 -2
- package/dist/config/settings-resolver.d.ts.map +1 -0
- package/dist/config/settings-resolver.js +275 -0
- package/dist/config/settings-resolver.js.map +1 -0
- package/dist/core/agents.d.ts.map +1 -0
- package/dist/{agents.js → core/agents.js} +11 -10
- package/dist/core/agents.js.map +1 -0
- package/dist/core/delegation.d.ts +24 -0
- package/dist/core/delegation.d.ts.map +1 -0
- package/dist/core/delegation.js +55 -0
- package/dist/core/delegation.js.map +1 -0
- package/dist/core/depth.d.ts.map +1 -0
- package/dist/{depth.js → core/depth.js} +9 -8
- package/dist/core/depth.js.map +1 -0
- package/dist/{executor.d.ts → core/executor.d.ts} +11 -3
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/{executor.js → core/executor.js} +49 -14
- package/dist/core/executor.js.map +1 -0
- package/dist/{flow.d.ts → core/flow.d.ts} +4 -1
- package/dist/core/flow.d.ts.map +1 -0
- package/dist/{flow.js → core/flow.js} +110 -45
- package/dist/core/flow.js.map +1 -0
- package/dist/{session-mode.d.ts → core/session-mode.d.ts} +2 -1
- package/dist/core/session-mode.d.ts.map +1 -0
- package/dist/{session-mode.js → core/session-mode.js} +1 -1
- package/dist/core/session-mode.js.map +1 -0
- package/dist/core/session-registry.d.ts +16 -0
- package/dist/core/session-registry.d.ts.map +1 -0
- package/dist/core/session-registry.js +30 -0
- package/dist/core/session-registry.js.map +1 -0
- package/dist/core/transitions.d.ts.map +1 -0
- package/dist/{transitions.js → core/transitions.js} +1 -1
- package/dist/core/transitions.js.map +1 -0
- package/dist/flow/command.d.ts +8 -0
- package/dist/flow/command.d.ts.map +1 -0
- package/dist/flow/command.js +189 -0
- package/dist/flow/command.js.map +1 -0
- package/dist/flow/continuation.d.ts +16 -0
- package/dist/flow/continuation.d.ts.map +1 -0
- package/dist/flow/continuation.js +151 -0
- package/dist/flow/continuation.js.map +1 -0
- package/dist/flow/index.d.ts +15 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +22 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/flow/settings-command.d.ts +51 -0
- package/dist/flow/settings-command.d.ts.map +1 -0
- package/dist/flow/settings-command.js +851 -0
- package/dist/flow/settings-command.js.map +1 -0
- package/dist/flow/store.d.ts +26 -0
- package/dist/flow/store.d.ts.map +1 -0
- package/dist/flow/store.js +158 -0
- package/dist/flow/store.js.map +1 -0
- package/dist/flow/template-strings.d.ts +8 -0
- package/dist/flow/template-strings.d.ts.map +1 -0
- package/dist/flow/template-strings.js +39 -0
- package/dist/flow/template-strings.js.map +1 -0
- package/dist/flow/types.d.ts +55 -0
- package/dist/flow/types.d.ts.map +1 -0
- package/dist/flow/types.js +5 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/flow/warp-command.d.ts +9 -0
- package/dist/flow/warp-command.d.ts.map +1 -0
- package/dist/flow/warp-command.js +405 -0
- package/dist/flow/warp-command.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +103 -29
- package/dist/index.js.map +1 -1
- package/dist/{notify-state.d.ts → notify/notify-state.d.ts} +2 -1
- package/dist/notify/notify-state.d.ts.map +1 -0
- package/dist/notify/notify-state.js.map +1 -0
- package/dist/notify/notify.d.ts.map +1 -0
- package/dist/{notify.js → notify/notify.js} +3 -2
- package/dist/notify/notify.js.map +1 -0
- package/dist/{cli-args.d.ts → snapshot/cli-args.d.ts} +3 -2
- package/dist/snapshot/cli-args.d.ts.map +1 -0
- package/dist/{cli-args.js → snapshot/cli-args.js} +1 -1
- package/dist/snapshot/cli-args.js.map +1 -0
- package/dist/snapshot/index.d.ts +2 -0
- package/dist/snapshot/index.d.ts.map +1 -0
- package/dist/snapshot/index.js +2 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/{reasoning-strip.d.ts → snapshot/reasoning-strip.d.ts} +0 -4
- package/dist/snapshot/reasoning-strip.d.ts.map +1 -0
- package/dist/{reasoning-strip.js → snapshot/reasoning-strip.js} +2 -2
- package/dist/snapshot/reasoning-strip.js.map +1 -0
- package/dist/snapshot/runner-events.d.ts.map +1 -0
- package/dist/{runner-events.js → snapshot/runner-events.js} +1 -1
- package/dist/snapshot/runner-events.js.map +1 -0
- package/dist/{snapshot.d.ts → snapshot/snapshot.d.ts} +5 -2
- package/dist/snapshot/snapshot.d.ts.map +1 -0
- package/dist/{snapshot.js → snapshot/snapshot.js} +166 -35
- package/dist/snapshot/snapshot.js.map +1 -0
- package/dist/{structured-output.d.ts → snapshot/structured-output.d.ts} +1 -1
- package/dist/snapshot/structured-output.d.ts.map +1 -0
- package/dist/snapshot/structured-output.js.map +1 -0
- package/dist/{flow-prompt.d.ts → steering/flow-prompt.d.ts} +2 -2
- package/dist/steering/flow-prompt.d.ts.map +1 -0
- package/dist/{flow-prompt.js → steering/flow-prompt.js} +1 -1
- package/dist/steering/flow-prompt.js.map +1 -0
- package/dist/{sliding-prompt.d.ts → steering/sliding-prompt.d.ts} +8 -7
- package/dist/steering/sliding-prompt.d.ts.map +1 -0
- package/dist/{sliding-prompt.js → steering/sliding-prompt.js} +15 -64
- package/dist/steering/sliding-prompt.js.map +1 -0
- package/dist/{tool-utils.d.ts → steering/tool-utils.d.ts} +1 -0
- package/dist/steering/tool-utils.d.ts.map +1 -0
- package/dist/{tool-utils.js → steering/tool-utils.js} +10 -3
- package/dist/steering/tool-utils.js.map +1 -0
- package/dist/{ask-user.d.ts → tools/ask-user.d.ts} +3 -15
- package/dist/tools/ask-user.d.ts.map +1 -0
- package/dist/tools/ask-user.js +778 -0
- package/dist/tools/ask-user.js.map +1 -0
- package/dist/{timed-bash.d.ts → tools/timed-bash.d.ts} +2 -7
- package/dist/tools/timed-bash.d.ts.map +1 -0
- package/dist/{timed-bash.js → tools/timed-bash.js} +2 -2
- package/dist/tools/timed-bash.js.map +1 -0
- package/dist/{web-tool.d.ts → tools/web-tool.d.ts} +1 -1
- package/dist/tools/web-tool.d.ts.map +1 -0
- package/dist/{web-tool.js → tools/web-tool.js} +8 -7
- package/dist/tools/web-tool.js.map +1 -0
- package/dist/tui/flow-colors.d.ts +55 -0
- package/dist/tui/flow-colors.d.ts.map +1 -0
- package/dist/tui/flow-colors.js +22 -0
- package/dist/tui/flow-colors.js.map +1 -0
- package/dist/{render-utils.d.ts → tui/render-utils.d.ts} +1 -1
- package/dist/tui/render-utils.d.ts.map +1 -0
- package/dist/{render-utils.js → tui/render-utils.js} +3 -3
- package/dist/tui/render-utils.js.map +1 -0
- package/dist/tui/render.d.ts +21 -0
- package/dist/tui/render.d.ts.map +1 -0
- package/dist/tui/render.js +813 -0
- package/dist/tui/render.js.map +1 -0
- package/dist/tui/scramble/algorithm.d.ts +7 -0
- package/dist/tui/scramble/algorithm.d.ts.map +1 -0
- package/dist/tui/scramble/algorithm.js +227 -0
- package/dist/tui/scramble/algorithm.js.map +1 -0
- package/dist/tui/scramble/constants.d.ts +99 -0
- package/dist/tui/scramble/constants.d.ts.map +1 -0
- package/dist/tui/scramble/constants.js +101 -0
- package/dist/tui/scramble/constants.js.map +1 -0
- package/dist/tui/scramble/index.d.ts +6 -0
- package/dist/tui/scramble/index.d.ts.map +1 -0
- package/dist/tui/scramble/index.js +6 -0
- package/dist/tui/scramble/index.js.map +1 -0
- package/dist/tui/scramble/manager.d.ts +48 -0
- package/dist/tui/scramble/manager.d.ts.map +1 -0
- package/dist/tui/scramble/manager.js +959 -0
- package/dist/tui/scramble/manager.js.map +1 -0
- package/dist/tui/scramble/utils.d.ts +18 -0
- package/dist/tui/scramble/utils.d.ts.map +1 -0
- package/dist/tui/scramble/utils.js +145 -0
- package/dist/tui/scramble/utils.js.map +1 -0
- package/dist/tui/single-select-layout.d.ts +17 -0
- package/dist/tui/single-select-layout.d.ts.map +1 -0
- package/dist/{single-select-layout.js → tui/single-select-layout.js} +8 -25
- package/dist/tui/single-select-layout.js.map +1 -0
- package/dist/types/flow.d.ts +110 -0
- package/dist/types/flow.d.ts.map +1 -0
- package/dist/{types.js → types/flow.js} +3 -54
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/output.d.ts +104 -0
- package/dist/types/output.d.ts.map +1 -0
- package/dist/types/output.js +5 -0
- package/dist/types/output.js.map +1 -0
- package/dist/types/ui.d.ts +24 -0
- package/dist/types/ui.d.ts.map +1 -0
- package/dist/types/ui.js +55 -0
- package/dist/types/ui.js.map +1 -0
- package/package.json +1 -1
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/ask-user.d.ts.map +0 -1
- package/dist/ask-user.js +0 -1405
- package/dist/ask-user.js.map +0 -1
- package/dist/batch.d.ts +0 -12
- package/dist/batch.d.ts.map +0 -1
- package/dist/batch.js +0 -11
- package/dist/batch.js.map +0 -1
- package/dist/cli-args.d.ts.map +0 -1
- package/dist/cli-args.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/depth.d.ts.map +0 -1
- package/dist/depth.js.map +0 -1
- package/dist/executor.d.ts.map +0 -1
- package/dist/executor.js.map +0 -1
- package/dist/flow-prompt.d.ts.map +0 -1
- package/dist/flow-prompt.js.map +0 -1
- package/dist/flow.d.ts.map +0 -1
- package/dist/flow.js.map +0 -1
- package/dist/notify-state.d.ts.map +0 -1
- package/dist/notify-state.js.map +0 -1
- package/dist/notify.d.ts.map +0 -1
- package/dist/notify.js.map +0 -1
- package/dist/reasoning-strip.d.ts.map +0 -1
- package/dist/reasoning-strip.js.map +0 -1
- package/dist/render-utils.d.ts.map +0 -1
- package/dist/render-utils.js.map +0 -1
- package/dist/render.d.ts +0 -24
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js +0 -592
- package/dist/render.js.map +0 -1
- package/dist/runner-events.d.ts.map +0 -1
- package/dist/runner-events.js.map +0 -1
- package/dist/scramble.d.ts +0 -183
- package/dist/scramble.d.ts.map +0 -1
- package/dist/scramble.js +0 -2478
- package/dist/scramble.js.map +0 -1
- package/dist/session-mode.d.ts.map +0 -1
- package/dist/session-mode.js.map +0 -1
- package/dist/settings-resolver.d.ts.map +0 -1
- package/dist/settings-resolver.js +0 -148
- package/dist/settings-resolver.js.map +0 -1
- package/dist/single-select-layout.d.ts +0 -20
- package/dist/single-select-layout.d.ts.map +0 -1
- package/dist/single-select-layout.js.map +0 -1
- package/dist/sliding-prompt.d.ts.map +0 -1
- package/dist/sliding-prompt.js.map +0 -1
- package/dist/snapshot.d.ts.map +0 -1
- package/dist/snapshot.js.map +0 -1
- package/dist/spec-mode.d.ts +0 -13
- package/dist/spec-mode.d.ts.map +0 -1
- package/dist/spec-mode.js +0 -90
- package/dist/spec-mode.js.map +0 -1
- package/dist/structured-output.d.ts.map +0 -1
- package/dist/structured-output.js.map +0 -1
- package/dist/timed-bash.d.ts.map +0 -1
- package/dist/timed-bash.js.map +0 -1
- package/dist/tool-utils.d.ts.map +0 -1
- package/dist/tool-utils.js.map +0 -1
- package/dist/transitions.d.ts.map +0 -1
- package/dist/transitions.js.map +0 -1
- package/dist/types.d.ts +0 -224
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/web-tool.d.ts.map +0 -1
- package/dist/web-tool.js.map +0 -1
- /package/dist/{agents.d.ts → core/agents.d.ts} +0 -0
- /package/dist/{depth.d.ts → core/depth.d.ts} +0 -0
- /package/dist/{transitions.d.ts → core/transitions.d.ts} +0 -0
- /package/dist/{notify-state.js → notify/notify-state.js} +0 -0
- /package/dist/{notify.d.ts → notify/notify.d.ts} +0 -0
- /package/dist/{runner-events.d.ts → snapshot/runner-events.d.ts} +0 -0
- /package/dist/{structured-output.js → snapshot/structured-output.js} +0 -0
|
@@ -0,0 +1,813 @@
|
|
|
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
|
+
import * as os from "node:os";
|
|
8
|
+
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import { Container, Markdown, Spacer, Text, TruncatedText } from "@mariozechner/pi-tui";
|
|
10
|
+
import { getFlowSummaryText } from "../snapshot/runner-events.js";
|
|
11
|
+
import { aggregateFlowUsage, getFlowOutput, isFlowError, isFlowSuccess, } from "../types/flow.js";
|
|
12
|
+
import { getFlowDisplayItems, getLastToolCall, getLastAssistantText, } from "../types/ui.js";
|
|
13
|
+
import { formatBatchOpsSummary } from "../batch/summary.js";
|
|
14
|
+
import { scrambleManager, runScrambleTimer, DynamicScrambleText, getLiveText } from "./scramble/index.js";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Anonymous flow-id counter — prevents scramble-state collisions when multiple
|
|
17
|
+
// flow widgets share the screen and toolCallId is absent from result/args.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
let anonFlowIdCounter = 0;
|
|
20
|
+
function getAnonymousFlowId() {
|
|
21
|
+
return `flow-${++anonFlowIdCounter}`;
|
|
22
|
+
}
|
|
23
|
+
/** Reset the anonymous counter — call in tests for deterministic ids. */
|
|
24
|
+
export function resetAnonymousFlowIdCounter() {
|
|
25
|
+
anonFlowIdCounter = 0;
|
|
26
|
+
}
|
|
27
|
+
function getLiveTextWithFallback(id) {
|
|
28
|
+
const value = getLiveText(id);
|
|
29
|
+
if (value !== undefined)
|
|
30
|
+
return value;
|
|
31
|
+
const fallbackId = id.includes("#") ? "collapsed" + id.slice(id.indexOf("#")) : "collapsed";
|
|
32
|
+
return getLiveText(fallbackId);
|
|
33
|
+
}
|
|
34
|
+
import { formatCompactStats, formatCompactTokenPair, formatCountdown, formatFlowTypeName, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi } from "./render-utils.js";
|
|
35
|
+
function shortenPath(p) {
|
|
36
|
+
const home = os.homedir();
|
|
37
|
+
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
38
|
+
}
|
|
39
|
+
import { applyRole, } from "./flow-colors.js";
|
|
40
|
+
function formatCollapsedFlowHeaderTypeName(type) {
|
|
41
|
+
return type.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
function formatFlowToolCall(toolName, args, fg) {
|
|
44
|
+
const pathArg = (args.file_path || args.path || "...");
|
|
45
|
+
switch (toolName) {
|
|
46
|
+
case "bash": {
|
|
47
|
+
const cmd = (args.command || "...").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;
|
|
53
|
+
const limit = args.limit;
|
|
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 || "").split("\n").length;
|
|
63
|
+
let text = fg("muted", "write ") + fg("accent", shortenPath(pathArg));
|
|
64
|
+
if (lines > 1)
|
|
65
|
+
text += fg("dim", ` (${lines} lines)`);
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
case "edit":
|
|
69
|
+
return fg("muted", "edit ") + fg("accent", shortenPath(pathArg));
|
|
70
|
+
case "ls":
|
|
71
|
+
return fg("muted", "ls ") + fg("accent", shortenPath((args.path || ".")));
|
|
72
|
+
case "find":
|
|
73
|
+
return fg("muted", "find ") + fg("accent", (args.pattern || "*")) + fg("dim", ` in ${shortenPath((args.path || "."))}`);
|
|
74
|
+
case "grep":
|
|
75
|
+
return fg("muted", "grep ") + fg("accent", `/${(args.pattern || "")}/`) + fg("dim", ` in ${shortenPath((args.path || "."))}`);
|
|
76
|
+
case "batch":
|
|
77
|
+
case "batch_read": {
|
|
78
|
+
const summary = formatBatchOpsSummary(args);
|
|
79
|
+
return fg("muted", `${toolName} `) + fg("accent", summary);
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
return fg("accent", toolName) + fg("dim", ` ${JSON.stringify(args)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Shared rendering building blocks
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
function splitOutputLines(text) {
|
|
89
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
90
|
+
if (lines.length > 1 && lines[lines.length - 1] === "")
|
|
91
|
+
lines.pop();
|
|
92
|
+
return lines;
|
|
93
|
+
}
|
|
94
|
+
function renderToolTraces(items, theme, config) {
|
|
95
|
+
const lines = [];
|
|
96
|
+
for (const item of items) {
|
|
97
|
+
if (item.type === "toolCall") {
|
|
98
|
+
lines.push(applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
function renderFlowReport(output, theme, config) {
|
|
104
|
+
const lines = splitOutputLines(output);
|
|
105
|
+
return lines.map((line) => applyRole("actContent", line, theme, config)).join("\n");
|
|
106
|
+
}
|
|
107
|
+
function flowStatusIcon(r, theme) {
|
|
108
|
+
if (r.exitCode === -1)
|
|
109
|
+
return theme.fg("warning", "(pending)");
|
|
110
|
+
return isFlowError(r) ? theme.fg("error", "(error)") : theme.fg("success", "(done)");
|
|
111
|
+
}
|
|
112
|
+
/** Center a label in a fixed-width header using em-dashes. Total width = 20. */
|
|
113
|
+
function sectionHeader(label) {
|
|
114
|
+
const total = 20;
|
|
115
|
+
const innerLen = label.length + 2; // account for spaces around label
|
|
116
|
+
const side = (total - innerLen) / 2;
|
|
117
|
+
const left = "─".repeat(Math.floor(side));
|
|
118
|
+
const right = "─".repeat(Math.ceil(side));
|
|
119
|
+
return `${left} ${label} ${right}`;
|
|
120
|
+
}
|
|
121
|
+
function getLiveCountdown(r) {
|
|
122
|
+
if (r.exitCode !== -1 || typeof r.deadlineAtMs !== "number")
|
|
123
|
+
return undefined;
|
|
124
|
+
return formatCountdown(r.deadlineAtMs - Date.now());
|
|
125
|
+
}
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// renderFlowCall — shown while the flow is being invoked
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
export function renderFlowCall(args, theme, config) {
|
|
130
|
+
let container = new Text("", 0, 0);
|
|
131
|
+
// In-place mutation pattern: reuse the stored root container
|
|
132
|
+
// so the TUI host's cached reference stays valid.
|
|
133
|
+
if (args?.state) {
|
|
134
|
+
const s = args.state;
|
|
135
|
+
if (!s.__rootContainer) {
|
|
136
|
+
const root = new Container();
|
|
137
|
+
root.addChild(container);
|
|
138
|
+
s.__rootContainer = root;
|
|
139
|
+
container = root;
|
|
140
|
+
}
|
|
141
|
+
else if (container !== s.__rootContainer) {
|
|
142
|
+
const root = s.__rootContainer;
|
|
143
|
+
root.clear();
|
|
144
|
+
root.addChild(container);
|
|
145
|
+
root.invalidate();
|
|
146
|
+
container = root;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return container;
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// renderFlowResult — shown after the flow completes
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
export function renderFlowResult(result, expanded, theme, args, config) {
|
|
155
|
+
const details = result.details;
|
|
156
|
+
const streamingText = result.content?.[0]?.type === "text" ? result.content[0].text : undefined;
|
|
157
|
+
// Resolve a stable id for this flow widget. Once an id is stored in
|
|
158
|
+
// state we keep reusing it to prevent mid-render id switches that would
|
|
159
|
+
// reset scramble animation state. On first render we prefer result._toolCallId,
|
|
160
|
+
// then args inputs, then a per-state anonymous counter.
|
|
161
|
+
// This prevents scramble-state collisions when multiple flow widgets are
|
|
162
|
+
// visible simultaneously (e.g. sequential flows) and toolCallId is absent.
|
|
163
|
+
let resolvedToolCallId;
|
|
164
|
+
if (args?.state) {
|
|
165
|
+
const s = args.state;
|
|
166
|
+
resolvedToolCallId = s.__flowId;
|
|
167
|
+
if (!resolvedToolCallId) {
|
|
168
|
+
resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
|
|
169
|
+
if (!resolvedToolCallId) {
|
|
170
|
+
resolvedToolCallId = getAnonymousFlowId();
|
|
171
|
+
}
|
|
172
|
+
s.__flowId = resolvedToolCallId;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
|
|
177
|
+
}
|
|
178
|
+
let container;
|
|
179
|
+
if (!details || details.results.length === 0) {
|
|
180
|
+
// Ghost Dashboard: render a placeholder status line during the zero state
|
|
181
|
+
const flowRequest = args?.flow?.[0];
|
|
182
|
+
if (flowRequest) {
|
|
183
|
+
const ghostResult = {
|
|
184
|
+
type: flowRequest.type || "unknown",
|
|
185
|
+
agentSource: "user",
|
|
186
|
+
intent: flowRequest.intent || "Processing...",
|
|
187
|
+
aim: flowRequest.aim || flowRequest.intent || "Processing...",
|
|
188
|
+
acceptance: flowRequest.acceptance,
|
|
189
|
+
exitCode: -1, // In progress
|
|
190
|
+
messages: [],
|
|
191
|
+
stderr: "",
|
|
192
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0, toolCalls: 0 },
|
|
193
|
+
};
|
|
194
|
+
const ghostId = resolvedToolCallId || 'ghost';
|
|
195
|
+
if (expanded) {
|
|
196
|
+
const now = Date.now();
|
|
197
|
+
container = renderFlowExpanded(ghostResult, flowStatusIcon(ghostResult, theme), false, getFlowDisplayItems([]), getFlowOutput([]), theme, ghostId, now, false, streamingText || "", config);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
container = renderFlowCollapsed(ghostResult, flowStatusIcon(ghostResult, theme), false, streamingText || "", theme, undefined, ghostId, config);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
container = new Text(scrambleManager.renderStatic(streamingText || ""), 0, 0);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (details.results.length === 1) {
|
|
208
|
+
container = renderSingleFlowResult(details.results[0], expanded, theme, streamingText, resolvedToolCallId, config);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
container = renderMultiFlowResult(details, expanded, theme, resolvedToolCallId, config);
|
|
212
|
+
}
|
|
213
|
+
// In-place mutation pattern: reuse the stored root container
|
|
214
|
+
// so the TUI host's cached reference stays valid.
|
|
215
|
+
if (args?.state) {
|
|
216
|
+
const s = args.state;
|
|
217
|
+
if (!s.__rootContainer) {
|
|
218
|
+
// First render: store the container (always wrap Text in a Container for consistency)
|
|
219
|
+
if (container instanceof Container) {
|
|
220
|
+
s.__rootContainer = container;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const root = new Container();
|
|
224
|
+
root.addChild(container);
|
|
225
|
+
s.__rootContainer = root;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (container !== s.__rootContainer) {
|
|
229
|
+
// Subsequent renders: transfer children to the stored container.
|
|
230
|
+
// Use a snapshot of the children array so the loop remains safe even if
|
|
231
|
+
// addChild() mutates the source array (removes from old parent).
|
|
232
|
+
const root = s.__rootContainer;
|
|
233
|
+
root.clear();
|
|
234
|
+
if (container instanceof Container) {
|
|
235
|
+
const children = [...container.children];
|
|
236
|
+
for (const child of children) {
|
|
237
|
+
root.addChild(child);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// container is a Text — wrap it as a child
|
|
242
|
+
root.addChild(container);
|
|
243
|
+
}
|
|
244
|
+
root.invalidate();
|
|
245
|
+
container = root;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Scramble animation timer — shared helper so any renderer can animate.
|
|
249
|
+
// Use resolvedToolCallId so the timer scope matches the state scope.
|
|
250
|
+
let timerId;
|
|
251
|
+
if (!details || details.results.length === 0) {
|
|
252
|
+
const flowRequest = args?.flow?.[0];
|
|
253
|
+
timerId = resolvedToolCallId || (flowRequest ? 'ghost' : 'single');
|
|
254
|
+
}
|
|
255
|
+
else if (details.results.length === 1) {
|
|
256
|
+
timerId = resolvedToolCallId || 'single';
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
timerId = resolvedToolCallId || 'multi';
|
|
260
|
+
}
|
|
261
|
+
runScrambleTimer(args, timerId);
|
|
262
|
+
return container;
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Single flow result
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCallId, config) {
|
|
268
|
+
const id = toolCallId || "single";
|
|
269
|
+
const error = isFlowError(r);
|
|
270
|
+
const icon = flowStatusIcon(r, theme);
|
|
271
|
+
const displayItems = getFlowDisplayItems(r.messages);
|
|
272
|
+
const flowOutput = getFlowOutput(r.messages);
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
const isComplete = r.exitCode !== -1;
|
|
275
|
+
if (expanded) {
|
|
276
|
+
return renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText, config);
|
|
277
|
+
}
|
|
278
|
+
return renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, id, config);
|
|
279
|
+
}
|
|
280
|
+
function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText, config) {
|
|
281
|
+
const mdTheme = getMarkdownTheme();
|
|
282
|
+
const container = new Container();
|
|
283
|
+
// Header: uppercase type name with dots, no icon, no source
|
|
284
|
+
const typeName = formatFlowTypeName(r.type);
|
|
285
|
+
let header = applyRole("flowName", typeName, theme, config);
|
|
286
|
+
if (error && r.stopReason)
|
|
287
|
+
header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
288
|
+
const plainHeader = typeName + (error && r.stopReason ? ` [${r.stopReason}]` : "");
|
|
289
|
+
container.addChild(new DynamicScrambleText(header, () => {
|
|
290
|
+
const result = scrambleManager.updateText(id, 'header', plainHeader, Date.now(), isComplete);
|
|
291
|
+
return result.isAnimating ? applyRole("flowName", result.content, theme, config) : header;
|
|
292
|
+
}));
|
|
293
|
+
if (error && r.errorMessage) {
|
|
294
|
+
container.addChild(new Text(scrambleManager.renderStatic(theme.fg("error", `Error: ${r.errorMessage}`)), 0, 0));
|
|
295
|
+
}
|
|
296
|
+
// Stats: dashboard format
|
|
297
|
+
const inlineStats = formatCompactStats(r.usage, r.model);
|
|
298
|
+
container.addChild(new DynamicScrambleText(applyRole("stats", inlineStats, theme, config), () => {
|
|
299
|
+
const result = scrambleManager.updateText(id, 'stats', stripAnsi(inlineStats), Date.now(), isComplete);
|
|
300
|
+
return result.isAnimating ? applyRole("stats", result.content, theme, config) : applyRole("stats", inlineStats, theme, config);
|
|
301
|
+
}));
|
|
302
|
+
// Intent — column-aware truncation (budget recalculated inside closure for resize handling)
|
|
303
|
+
const intentBudget = getTruncationBudget(0);
|
|
304
|
+
const displayIntent = truncateChars(r.intent, intentBudget);
|
|
305
|
+
container.addChild(new Spacer(1));
|
|
306
|
+
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("intent"), theme, config), 0, 0));
|
|
307
|
+
container.addChild(new DynamicScrambleText(applyRole("aimContent", displayIntent, theme, config), () => {
|
|
308
|
+
const budget = getTruncationBudget(0);
|
|
309
|
+
const text = truncateChars(r.intent, budget);
|
|
310
|
+
const result = scrambleManager.updateText(id, 'intent', text, Date.now(), isComplete);
|
|
311
|
+
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
|
|
312
|
+
}));
|
|
313
|
+
// Acceptance
|
|
314
|
+
if (r.acceptance) {
|
|
315
|
+
const acceptanceRaw = r.acceptance;
|
|
316
|
+
const acceptanceBudget = getTruncationBudget(0);
|
|
317
|
+
const acceptanceText = truncateChars(acceptanceRaw, acceptanceBudget);
|
|
318
|
+
container.addChild(new Spacer(1));
|
|
319
|
+
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("acceptance"), theme, config), 0, 0));
|
|
320
|
+
container.addChild(new DynamicScrambleText(applyRole("aimContent", acceptanceText, theme, config), () => {
|
|
321
|
+
const budget = getTruncationBudget(0);
|
|
322
|
+
const text = truncateChars(acceptanceRaw, budget);
|
|
323
|
+
const result = scrambleManager.updateText(id, 'acceptance', text, Date.now(), isComplete);
|
|
324
|
+
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
// Flow report (structured output)
|
|
328
|
+
container.addChild(new Spacer(1));
|
|
329
|
+
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("report"), theme, config), 0, 0));
|
|
330
|
+
// Structured output summary (compact badge when available)
|
|
331
|
+
if (r.structuredOutput) {
|
|
332
|
+
const so = r.structuredOutput;
|
|
333
|
+
const statusColor = so.status === "complete" ? "success" : so.status === "partial" ? "warning" : "error";
|
|
334
|
+
const statusText = `[${so.status}] ${so.summary}`;
|
|
335
|
+
const statusStatic = `${theme.fg(statusColor, `[${so.status}]`)} ${applyRole("aimContent", so.summary, theme, config)}`;
|
|
336
|
+
container.addChild(new DynamicScrambleText(statusStatic, () => {
|
|
337
|
+
const result = scrambleManager.updateText(id, 'report-status', statusText, Date.now(), isComplete, false);
|
|
338
|
+
return result.isAnimating ? `${theme.fg(statusColor, result.content.split(' ')[0])} ${applyRole("aimContent", result.content.slice(result.content.indexOf(' ') + 1), theme, config)}` : statusStatic;
|
|
339
|
+
}));
|
|
340
|
+
if (so.files.length > 0) {
|
|
341
|
+
const filesText = `Files: ${so.files.map((f) => f.path).join(", ")}`;
|
|
342
|
+
container.addChild(new DynamicScrambleText(applyRole("aimContent", filesText, theme, config), () => {
|
|
343
|
+
const result = scrambleManager.updateText(id, 'report-files', filesText, Date.now(), isComplete, false);
|
|
344
|
+
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", filesText, theme, config);
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
if (so.commands?.length > 0) {
|
|
348
|
+
const cmdLabels = so.commands.map((c) => {
|
|
349
|
+
const short = c.command.length > 30 ? c.command.slice(0, 30) + "..." : c.command;
|
|
350
|
+
return `${c.tool ?? "cmd"}: ${short}`;
|
|
351
|
+
});
|
|
352
|
+
const commandsText = `Commands: ${cmdLabels.join(", ")}`;
|
|
353
|
+
container.addChild(new DynamicScrambleText(applyRole("aimContent", commandsText, theme, config), () => {
|
|
354
|
+
const result = scrambleManager.updateText(id, 'report-commands', commandsText, Date.now(), isComplete, false);
|
|
355
|
+
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", commandsText, theme, config);
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
if (so.notDone.length > 0) {
|
|
359
|
+
const notDoneText = `Not Done: ${so.notDone.map((item) => {
|
|
360
|
+
const details = [
|
|
361
|
+
item.reason ? `reason: ${item.reason}` : undefined,
|
|
362
|
+
item.blocker ? `blocker: ${item.blocker}` : undefined,
|
|
363
|
+
item.nextStep ? `next: ${item.nextStep}` : undefined,
|
|
364
|
+
].filter(Boolean).join("; ");
|
|
365
|
+
return details ? `${item.item} (${details})` : item.item;
|
|
366
|
+
}).join("; ")}`;
|
|
367
|
+
container.addChild(new DynamicScrambleText(applyRole("aimContent", notDoneText, theme, config), () => {
|
|
368
|
+
const result = scrambleManager.updateText(id, 'report-notDone', notDoneText, Date.now(), isComplete, false);
|
|
369
|
+
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", notDoneText, theme, config);
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
if (so.nextSteps.length > 0) {
|
|
373
|
+
const nextStepsText = `Next: ${so.nextSteps.join("; ")}`;
|
|
374
|
+
container.addChild(new DynamicScrambleText(applyRole("aimContent", nextStepsText, theme, config), () => {
|
|
375
|
+
const result = scrambleManager.updateText(id, 'report-nextSteps', nextStepsText, Date.now(), isComplete, false);
|
|
376
|
+
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", nextStepsText, theme, config);
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
container.addChild(new Spacer(1));
|
|
380
|
+
}
|
|
381
|
+
// Output: animate streaming text; show clean markdown when complete
|
|
382
|
+
if (!isComplete && streamingText != null) {
|
|
383
|
+
const msgBudget = getTruncationBudget(0);
|
|
384
|
+
const displayMsg = tailText(stripAnsi(streamingText), msgBudget);
|
|
385
|
+
container.addChild(new DynamicScrambleText(displayMsg, () => {
|
|
386
|
+
const budget = getTruncationBudget(0);
|
|
387
|
+
const freshStreamingText = getLiveTextWithFallback(id) ?? streamingText;
|
|
388
|
+
const text = tailText(stripAnsi(freshStreamingText), budget);
|
|
389
|
+
return scrambleManager.updateMsg(id, text, Date.now(), isComplete, undefined, true).content;
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
else if (flowOutput) {
|
|
393
|
+
container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
const summary = getFlowSummaryText(r);
|
|
397
|
+
container.addChild(new DynamicScrambleText(applyRole("msgContent", summary, theme, config), () => {
|
|
398
|
+
const result = scrambleManager.updateText(id, 'output-summary', summary, Date.now(), isComplete, false);
|
|
399
|
+
return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", summary, theme, config);
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
// Tool traces (expanded only) — per-line scramble
|
|
403
|
+
const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
|
|
404
|
+
if (toolCallItems.length > 0) {
|
|
405
|
+
container.addChild(new Spacer(1));
|
|
406
|
+
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("tool calls"), theme, config), 0, 0));
|
|
407
|
+
const toolPrefixLen = visibleLength("→ ");
|
|
408
|
+
const toolBudget = getTruncationBudget(toolPrefixLen);
|
|
409
|
+
for (let i = 0; i < toolCallItems.length; i++) {
|
|
410
|
+
const item = toolCallItems[i];
|
|
411
|
+
const lineText = applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
|
|
412
|
+
const plainText = stripAnsi(lineText);
|
|
413
|
+
const displayTool = truncateChars(plainText, toolBudget);
|
|
414
|
+
const initialScrambled = scrambleManager.updateText(id, `tool#${i}`, displayTool, now, isComplete).content;
|
|
415
|
+
container.addChild(new DynamicScrambleText(initialScrambled, () => {
|
|
416
|
+
const budget = getTruncationBudget(toolPrefixLen);
|
|
417
|
+
const freshPlain = stripAnsi(lineText);
|
|
418
|
+
const text = truncateChars(freshPlain, budget);
|
|
419
|
+
return scrambleManager.updateText(id, `tool#${i}`, text, Date.now(), isComplete).content;
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (isComplete) {
|
|
424
|
+
scrambleManager.completeFlow(id);
|
|
425
|
+
}
|
|
426
|
+
return container;
|
|
427
|
+
}
|
|
428
|
+
function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, toolCallId, config) {
|
|
429
|
+
const id = toolCallId || "collapsed";
|
|
430
|
+
const now = Date.now();
|
|
431
|
+
const container = new Container();
|
|
432
|
+
const maxWidth = process.stdout.columns ?? 80;
|
|
433
|
+
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
434
|
+
const modelLabel = r.model ? r.model.replace(/^[^/]+\//, "").toLowerCase() : "";
|
|
435
|
+
const headerPrefixLen = visibleLength(typeName) + visibleLength(modelLabel ? ` ${modelLabel} · ` : " ");
|
|
436
|
+
const stats = formatCompactStats(r.usage, r.model, Math.max(maxWidth - headerPrefixLen, 20), { skipTokens: true, skipContext: true, hideModel: true });
|
|
437
|
+
const isComplete = r.exitCode !== -1;
|
|
438
|
+
// Flash TPS value when it changes
|
|
439
|
+
const tpsMatch = stats.match(/tps:\s*(\S+)/);
|
|
440
|
+
let displayStats = stats;
|
|
441
|
+
if (tpsMatch) {
|
|
442
|
+
const scrambledTps = scrambleManager.updateTps(id, tpsMatch[1], now, isComplete, true);
|
|
443
|
+
if (scrambledTps !== tpsMatch[1]) {
|
|
444
|
+
displayStats = stats.replace(tpsMatch[1], scrambledTps);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
let header = `${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelLabel ? ` ${modelLabel} · ` : " ", theme, config)}${applyRole("stats", displayStats, theme, config)}`;
|
|
448
|
+
if (error && r.stopReason)
|
|
449
|
+
header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
450
|
+
// Scramble header on first render; show full styled header when complete
|
|
451
|
+
const plainHeader = typeName + (modelLabel ? ` ${modelLabel} · ` : " ") + stripAnsi(displayStats) + (error && r.stopReason ? ` [${r.stopReason}]` : "");
|
|
452
|
+
container.addChild(new DynamicScrambleText(header, () => {
|
|
453
|
+
const result = scrambleManager.updateText(id, 'header', plainHeader, Date.now(), isComplete, true);
|
|
454
|
+
return result.isAnimating ? applyRole("flowName", result.content, theme, config) : header;
|
|
455
|
+
}, true));
|
|
456
|
+
// aim: line — glitch on text change
|
|
457
|
+
if (r.aim) {
|
|
458
|
+
const countdown = getLiveCountdown(r);
|
|
459
|
+
const aimTree = "├─";
|
|
460
|
+
const aimLabel = countdown
|
|
461
|
+
? ` aim ▸ ${countdown} · `
|
|
462
|
+
: ` aim ▸ `;
|
|
463
|
+
const aimPrefix = `${aimTree}${aimLabel}`;
|
|
464
|
+
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
465
|
+
const displayAim = truncateChars(lowerFirstWord(r.aim), budget);
|
|
466
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole("aimContent", displayAim, theme, config)}`, () => {
|
|
467
|
+
const now = Date.now();
|
|
468
|
+
const freshCountdown = getLiveCountdown(r);
|
|
469
|
+
const freshAimLabel = freshCountdown
|
|
470
|
+
? ` aim ▸ ${freshCountdown} · `
|
|
471
|
+
: ` aim ▸ `;
|
|
472
|
+
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
473
|
+
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
474
|
+
const freshText = truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
475
|
+
const result = scrambleManager.updateAim(id, freshText, now, isComplete, true);
|
|
476
|
+
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole("aimContent", result.content, theme, config)}`;
|
|
477
|
+
}, true));
|
|
478
|
+
}
|
|
479
|
+
// act: line (last tool call with count)
|
|
480
|
+
const lastTool = getLastToolCall(r.messages);
|
|
481
|
+
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
482
|
+
const actTree = "├─";
|
|
483
|
+
const actLabel = ` act ▸ ${r.usage.toolCalls} · `;
|
|
484
|
+
const prefixStub = `${actTree}${actLabel}`;
|
|
485
|
+
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
486
|
+
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
487
|
+
const initialActContent = actFullText.length > budget ? actFullText.slice(0, budget) : actFullText;
|
|
488
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", initialActContent, theme, config)}`, () => {
|
|
489
|
+
const now = Date.now();
|
|
490
|
+
const displayAct = truncateChars(actFullText, budget);
|
|
491
|
+
const actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
|
|
492
|
+
let actKpi = String(r.usage.toolCalls);
|
|
493
|
+
const scrambledActKpi = scrambleManager.updateActKpi(id, actKpi, now, isComplete, true);
|
|
494
|
+
if (scrambledActKpi !== actKpi) {
|
|
495
|
+
actKpi = scrambledActKpi;
|
|
496
|
+
}
|
|
497
|
+
const actLabel = ` act ▸ ${actKpi} · `;
|
|
498
|
+
const actPrefix = `${actTree}${actLabel}`;
|
|
499
|
+
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", actContent, theme, config)}`;
|
|
500
|
+
}, true));
|
|
501
|
+
// msg: line (last assistant text or streaming)
|
|
502
|
+
let msgKpi = formatCompactTokenPair(r.usage);
|
|
503
|
+
const scrambledMsgKpi = scrambleManager.updateMsgKpi(id, msgKpi, now, isComplete, false);
|
|
504
|
+
if (scrambledMsgKpi !== msgKpi) {
|
|
505
|
+
msgKpi = scrambledMsgKpi;
|
|
506
|
+
}
|
|
507
|
+
const msgPrefixStub = `└─ msg ▸ ${msgKpi} `;
|
|
508
|
+
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
509
|
+
let rawMsg;
|
|
510
|
+
let useError = false;
|
|
511
|
+
const liveMsgText = r.exitCode === -1 ? getLiveTextWithFallback(id) : undefined;
|
|
512
|
+
if (liveMsgText != null) {
|
|
513
|
+
rawMsg = stripAnsi(liveMsgText);
|
|
514
|
+
}
|
|
515
|
+
else if (r.exitCode === -1 && streamingText != null) {
|
|
516
|
+
rawMsg = stripAnsi(streamingText);
|
|
517
|
+
}
|
|
518
|
+
else if (r.structuredOutput?.summary) {
|
|
519
|
+
rawMsg = stripAnsi(r.structuredOutput.summary);
|
|
520
|
+
}
|
|
521
|
+
else if (flowOutput) {
|
|
522
|
+
rawMsg = stripAnsi(flowOutput);
|
|
523
|
+
}
|
|
524
|
+
else if (error && r.errorMessage) {
|
|
525
|
+
rawMsg = stripAnsi(r.errorMessage);
|
|
526
|
+
useError = true;
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
const summary = getFlowSummaryText(r);
|
|
530
|
+
rawMsg = stripAnsi(summary) || "[n/a]";
|
|
531
|
+
}
|
|
532
|
+
const initialNeedsTail = r.exitCode === -1 || streamingText != null || liveMsgText != null;
|
|
533
|
+
const initialMsgContent = initialNeedsTail
|
|
534
|
+
? tailText(rawMsg, msgBudget)
|
|
535
|
+
: truncateChars(rawMsg, msgBudget);
|
|
536
|
+
const msgTree = "└─";
|
|
537
|
+
const msgLabel = ` msg ▸ ${msgKpi} `;
|
|
538
|
+
const initialMsgPrefix = `${msgTree}${msgLabel}`;
|
|
539
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", initialMsgContent, theme, config)}`, () => {
|
|
540
|
+
const now = Date.now();
|
|
541
|
+
let msgKpi = formatCompactTokenPair(r.usage);
|
|
542
|
+
const scrambledMsgKpi = scrambleManager.updateMsgKpi(id, msgKpi, now, isComplete, false);
|
|
543
|
+
if (scrambledMsgKpi !== msgKpi) {
|
|
544
|
+
msgKpi = scrambledMsgKpi;
|
|
545
|
+
}
|
|
546
|
+
const msgLabel = ` msg ▸ ${msgKpi} `;
|
|
547
|
+
const msgPrefix = `${msgTree}${msgLabel}`;
|
|
548
|
+
const freshRawMsg = (r.exitCode === -1 ? getLiveTextWithFallback(id) : undefined) ?? rawMsg;
|
|
549
|
+
const needsTail = r.exitCode === -1 || streamingText != null;
|
|
550
|
+
const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
|
|
551
|
+
const result = scrambleManager.updateMsg(id, displayMsg, now, isComplete, undefined, true);
|
|
552
|
+
return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", result.content, theme, config)}`;
|
|
553
|
+
}, true));
|
|
554
|
+
if (isComplete) {
|
|
555
|
+
scrambleManager.completeFlow(id);
|
|
556
|
+
}
|
|
557
|
+
return container;
|
|
558
|
+
}
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
// Multi-flow result
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
function renderMultiFlowResult(details, expanded, theme, toolCallId, config) {
|
|
563
|
+
const baseId = toolCallId || "multi";
|
|
564
|
+
const results = details.results;
|
|
565
|
+
const successCount = results.filter((r) => isFlowSuccess(r)).length;
|
|
566
|
+
const failCount = results.filter((r) => isFlowError(r)).length;
|
|
567
|
+
const icon = failCount > 0 ? theme.fg("warning", "(!)") : theme.fg("success", "(ok)");
|
|
568
|
+
const now = Date.now();
|
|
569
|
+
if (expanded) {
|
|
570
|
+
return renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now, config);
|
|
571
|
+
}
|
|
572
|
+
return renderMultiFlowCollapsed(results, theme, baseId, config);
|
|
573
|
+
}
|
|
574
|
+
function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now, config) {
|
|
575
|
+
const mdTheme = getMarkdownTheme();
|
|
576
|
+
const container = new Container();
|
|
577
|
+
// Summary: just show count, no icon
|
|
578
|
+
container.addChild(new Text(applyRole("flowName", `${results.length} flows`, theme, config), 0, 0));
|
|
579
|
+
for (let flowIdx = 0; flowIdx < results.length; flowIdx++) {
|
|
580
|
+
const r = results[flowIdx];
|
|
581
|
+
const flowId = `${baseId}#${flowIdx}`;
|
|
582
|
+
const isComplete = r.exitCode !== -1;
|
|
583
|
+
const displayItems = getFlowDisplayItems(r.messages);
|
|
584
|
+
const flowOutput = getFlowOutput(r.messages);
|
|
585
|
+
const typeName = formatFlowTypeName(r.type);
|
|
586
|
+
container.addChild(new Spacer(1));
|
|
587
|
+
// Per-flow header: ─── EXPLORER (no icon)
|
|
588
|
+
const headerStatic = applyRole("prefixLabel", sectionHeader(typeName), theme, config);
|
|
589
|
+
container.addChild(new DynamicScrambleText(headerStatic, () => {
|
|
590
|
+
const result = scrambleManager.updateText(flowId, 'header', typeName, Date.now(), isComplete, true);
|
|
591
|
+
return result.isAnimating ? applyRole("prefixLabel", result.content, theme, config) : headerStatic;
|
|
592
|
+
}));
|
|
593
|
+
// Stats: dashboard format
|
|
594
|
+
const flowStats = formatCompactStats(r.usage, r.model);
|
|
595
|
+
container.addChild(new DynamicScrambleText(applyRole("stats", flowStats, theme, config), () => {
|
|
596
|
+
const result = scrambleManager.updateText(flowId, 'stats', stripAnsi(flowStats), Date.now(), isComplete, true);
|
|
597
|
+
return result.isAnimating ? applyRole("stats", result.content, theme, config) : applyRole("stats", flowStats, theme, config);
|
|
598
|
+
}));
|
|
599
|
+
// Intent: just show text, no prefix (budget computed dynamically for resize recalculation)
|
|
600
|
+
const intentBudget = getTruncationBudget(0);
|
|
601
|
+
const displayIntent = truncateChars(r.intent, intentBudget);
|
|
602
|
+
container.addChild(new DynamicScrambleText(applyRole("aimContent", displayIntent, theme, config), () => {
|
|
603
|
+
const budget = getTruncationBudget(0);
|
|
604
|
+
const text = truncateChars(r.intent, budget);
|
|
605
|
+
const result = scrambleManager.updateText(flowId, 'intent', text, Date.now(), isComplete, true);
|
|
606
|
+
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
|
|
607
|
+
}));
|
|
608
|
+
if (r.acceptance) {
|
|
609
|
+
const acceptanceRaw = r.acceptance;
|
|
610
|
+
const acceptancePrefix = "Acceptance: ";
|
|
611
|
+
const acceptanceBudget = getTruncationBudget(visibleLength(acceptancePrefix));
|
|
612
|
+
const acceptanceText = truncateChars(acceptanceRaw, acceptanceBudget);
|
|
613
|
+
const acceptanceStatic = applyRole("aimContent", `${acceptancePrefix}${acceptanceText}`, theme, config);
|
|
614
|
+
container.addChild(new DynamicScrambleText(acceptanceStatic, () => {
|
|
615
|
+
const budget = getTruncationBudget(visibleLength(acceptancePrefix));
|
|
616
|
+
const text = truncateChars(acceptanceRaw, budget);
|
|
617
|
+
const result = scrambleManager.updateText(flowId, 'acceptance', text, Date.now(), isComplete, true);
|
|
618
|
+
return result.isAnimating ? applyRole("aimContent", `${acceptancePrefix}${result.content}`, theme, config) : applyRole("aimContent", `${acceptancePrefix}${text}`, theme, config);
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
621
|
+
// Output: animate streaming text; show clean markdown when complete
|
|
622
|
+
if (!isComplete && r.streamingText != null) {
|
|
623
|
+
const streamingRaw = r.streamingText;
|
|
624
|
+
const msgBudget = getTruncationBudget(0);
|
|
625
|
+
const displayMsg = tailText(stripAnsi(streamingRaw), msgBudget);
|
|
626
|
+
container.addChild(new DynamicScrambleText(displayMsg, () => {
|
|
627
|
+
const budget = getTruncationBudget(0);
|
|
628
|
+
const freshStreamingText = getLiveTextWithFallback(flowId) ?? streamingRaw;
|
|
629
|
+
const text = tailText(stripAnsi(freshStreamingText), budget);
|
|
630
|
+
return scrambleManager.updateMsg(flowId, text, Date.now(), isComplete, undefined, true).content;
|
|
631
|
+
}));
|
|
632
|
+
}
|
|
633
|
+
else if (flowOutput) {
|
|
634
|
+
container.addChild(new Spacer(1));
|
|
635
|
+
container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
|
|
636
|
+
}
|
|
637
|
+
// Tool traces in expanded view — per-line scramble
|
|
638
|
+
const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
|
|
639
|
+
if (toolCallItems.length > 0) {
|
|
640
|
+
container.addChild(new Spacer(1));
|
|
641
|
+
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("tool calls"), theme, config), 0, 0));
|
|
642
|
+
const toolPrefixLen = visibleLength("→ ");
|
|
643
|
+
const toolBudget = getTruncationBudget(toolPrefixLen);
|
|
644
|
+
for (let i = 0; i < toolCallItems.length; i++) {
|
|
645
|
+
const item = toolCallItems[i];
|
|
646
|
+
const lineText = applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
|
|
647
|
+
const plainText = stripAnsi(lineText);
|
|
648
|
+
const displayTool = truncateChars(plainText, toolBudget);
|
|
649
|
+
const initialScrambled = scrambleManager.updateText(flowId, `tool#${i}`, displayTool, now, isComplete).content;
|
|
650
|
+
container.addChild(new DynamicScrambleText(initialScrambled, () => {
|
|
651
|
+
const budget = getTruncationBudget(toolPrefixLen);
|
|
652
|
+
const freshPlain = stripAnsi(lineText);
|
|
653
|
+
const text = truncateChars(freshPlain, budget);
|
|
654
|
+
return scrambleManager.updateText(flowId, `tool#${i}`, text, Date.now(), isComplete).content;
|
|
655
|
+
}));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (isComplete) {
|
|
659
|
+
scrambleManager.completeFlow(flowId);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Total stats: dashboard format
|
|
663
|
+
const totalUsage = aggregateFlowUsage(results);
|
|
664
|
+
const totalModel = results[0]?.model;
|
|
665
|
+
const totalStats = formatCompactStats(totalUsage, totalModel);
|
|
666
|
+
container.addChild(new Spacer(1));
|
|
667
|
+
container.addChild(new Text(applyRole("stats", totalStats, theme, config), 0, 0));
|
|
668
|
+
return container;
|
|
669
|
+
}
|
|
670
|
+
function renderActivityPanel(results, theme, baseId, config) {
|
|
671
|
+
const idPrefix = baseId || "panel";
|
|
672
|
+
const container = new Container();
|
|
673
|
+
const maxWidth = process.stdout.columns ?? 80;
|
|
674
|
+
const now = Date.now();
|
|
675
|
+
for (let i = 0; i < results.length; i++) {
|
|
676
|
+
const r = results[i];
|
|
677
|
+
const isLast = i === results.length - 1;
|
|
678
|
+
const flowId = `${idPrefix}#${i}`;
|
|
679
|
+
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
680
|
+
const modelLabel = r.model ? r.model.replace(/^[^/]+\//, "").toLowerCase() : "";
|
|
681
|
+
const headerPrefix = isLast ? "└─" : "├─";
|
|
682
|
+
const headerPrefixLen = visibleLength(headerPrefix) + 1 + visibleLength(typeName) + visibleLength(modelLabel ? ` ${modelLabel} · ` : " ");
|
|
683
|
+
const stats = formatCompactStats(r.usage, r.model, Math.max(maxWidth - headerPrefixLen, 20), { skipTokens: true, skipContext: true, hideModel: true });
|
|
684
|
+
// Flash TPS value when it changes
|
|
685
|
+
const tpsMatch = stats.match(/tps:\s*(\S+)/);
|
|
686
|
+
const flowComplete = r.exitCode !== -1;
|
|
687
|
+
let displayStats = stats;
|
|
688
|
+
if (tpsMatch) {
|
|
689
|
+
const scrambledTps = scrambleManager.updateTps(flowId, tpsMatch[1], now, flowComplete, true);
|
|
690
|
+
if (scrambledTps !== tpsMatch[1]) {
|
|
691
|
+
displayStats = stats.replace(tpsMatch[1], scrambledTps);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const error = isFlowError(r);
|
|
695
|
+
// Header line
|
|
696
|
+
let headerLine = `${applyRole("treeChars", headerPrefix, theme, config)} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelLabel ? ` ${modelLabel} · ` : " ", theme, config)}${applyRole("stats", displayStats, theme, config)}`;
|
|
697
|
+
if (error && r.stopReason) {
|
|
698
|
+
headerLine += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
699
|
+
}
|
|
700
|
+
const plainHeader = headerPrefix + " " + typeName + (modelLabel ? ` ${modelLabel} · ` : " ") + stripAnsi(displayStats) + (error && r.stopReason ? ` [${r.stopReason}]` : "");
|
|
701
|
+
container.addChild(new DynamicScrambleText(headerLine, () => {
|
|
702
|
+
const result = scrambleManager.updateText(flowId, 'header', plainHeader, Date.now(), flowComplete, true);
|
|
703
|
+
return result.isAnimating ? applyRole("flowName", result.content, theme, config) : headerLine;
|
|
704
|
+
}, true));
|
|
705
|
+
// Continuation indent for sub-lines
|
|
706
|
+
const indent = isLast ? " " : "│ ";
|
|
707
|
+
// aim: line — glitch on text change
|
|
708
|
+
if (r.aim) {
|
|
709
|
+
const countdown = getLiveCountdown(r);
|
|
710
|
+
const aimTree = indent + "├─";
|
|
711
|
+
const aimLabel = countdown
|
|
712
|
+
? ` aim ▸ ${countdown} · `
|
|
713
|
+
: ` aim ▸ `;
|
|
714
|
+
const aimPrefix = `${aimTree}${aimLabel}`;
|
|
715
|
+
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
716
|
+
const displayAim = truncateChars(lowerFirstWord(r.aim), budget);
|
|
717
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole("aimContent", displayAim, theme, config)}`, () => {
|
|
718
|
+
const now = Date.now();
|
|
719
|
+
const freshCountdown = getLiveCountdown(r);
|
|
720
|
+
const freshAimLabel = freshCountdown
|
|
721
|
+
? ` aim ▸ ${freshCountdown} · `
|
|
722
|
+
: ` aim ▸ `;
|
|
723
|
+
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
724
|
+
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
725
|
+
const freshText = truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
726
|
+
const result = scrambleManager.updateAim(flowId, freshText, now, flowComplete, true);
|
|
727
|
+
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole("aimContent", result.content, theme, config)}`;
|
|
728
|
+
}, true));
|
|
729
|
+
}
|
|
730
|
+
// act: line (last tool call with count)
|
|
731
|
+
const lastTool = getLastToolCall(r.messages);
|
|
732
|
+
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
733
|
+
const actTree = `${indent}├─`;
|
|
734
|
+
const actLabel = ` act ▸ ${r.usage.toolCalls} · `;
|
|
735
|
+
const prefixStub = `${actTree}${actLabel}`;
|
|
736
|
+
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
737
|
+
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
738
|
+
const initialActContent = actFullText.length > budget ? actFullText.slice(0, budget) : actFullText;
|
|
739
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", initialActContent, theme, config)}`, () => {
|
|
740
|
+
const now = Date.now();
|
|
741
|
+
let actKpi = String(r.usage.toolCalls);
|
|
742
|
+
const scrambledActKpi = scrambleManager.updateActKpi(flowId, actKpi, now, flowComplete, false);
|
|
743
|
+
if (scrambledActKpi !== actKpi) {
|
|
744
|
+
actKpi = scrambledActKpi;
|
|
745
|
+
}
|
|
746
|
+
const actLabel = ` act ▸ ${actKpi} · `;
|
|
747
|
+
const actPrefix = `${actTree}${actLabel}`;
|
|
748
|
+
const freshBudget = getTruncationBudget(visibleLength(actPrefix));
|
|
749
|
+
const displayAct = truncateChars(actFullText, freshBudget);
|
|
750
|
+
const actContent = scrambleManager.updateAct(flowId, displayAct, now, flowComplete, true).content;
|
|
751
|
+
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", actContent, theme, config)}`;
|
|
752
|
+
}, true));
|
|
753
|
+
// msg: line (live streaming text or last assistant text)
|
|
754
|
+
let msgKpi = formatCompactTokenPair(r.usage);
|
|
755
|
+
const scrambledMsgKpi = scrambleManager.updateMsgKpi(flowId, msgKpi, now, flowComplete, false);
|
|
756
|
+
if (scrambledMsgKpi !== msgKpi) {
|
|
757
|
+
msgKpi = scrambledMsgKpi;
|
|
758
|
+
}
|
|
759
|
+
const msgTree = `${indent}└─`;
|
|
760
|
+
const msgLabel = ` msg ▸ ${msgKpi} `;
|
|
761
|
+
const msgPrefixStub = `${msgTree}${msgLabel}`;
|
|
762
|
+
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
763
|
+
const liveText = r.exitCode === -1 ? r.streamingText : undefined;
|
|
764
|
+
const lastText = liveText || getLastAssistantText(r.messages);
|
|
765
|
+
let rawMsg;
|
|
766
|
+
let useError = false;
|
|
767
|
+
const liveText_ = flowComplete ? undefined : getLiveTextWithFallback(flowId);
|
|
768
|
+
if (liveText_ != null) {
|
|
769
|
+
rawMsg = stripAnsi(liveText_);
|
|
770
|
+
}
|
|
771
|
+
else if (lastText) {
|
|
772
|
+
rawMsg = stripAnsi(lastText);
|
|
773
|
+
}
|
|
774
|
+
else if (error && r.errorMessage) {
|
|
775
|
+
rawMsg = stripAnsi(r.errorMessage);
|
|
776
|
+
useError = true;
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
rawMsg = "[n/a]";
|
|
780
|
+
}
|
|
781
|
+
const initialNeedsTail = Boolean(liveText_ || liveText || lastText);
|
|
782
|
+
const initialDisplayMsg = initialNeedsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
|
|
783
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", initialDisplayMsg, theme, config)}`, () => {
|
|
784
|
+
const now = Date.now();
|
|
785
|
+
let msgKpi = formatCompactTokenPair(r.usage);
|
|
786
|
+
const scrambledMsgKpi = scrambleManager.updateMsgKpi(flowId, msgKpi, now, flowComplete, false);
|
|
787
|
+
if (scrambledMsgKpi !== msgKpi) {
|
|
788
|
+
msgKpi = scrambledMsgKpi;
|
|
789
|
+
}
|
|
790
|
+
const msgLabel = ` msg ▸ ${msgKpi} `;
|
|
791
|
+
const msgPrefix = `${msgTree}${msgLabel}`;
|
|
792
|
+
const freshBudget = getTruncationBudget(visibleLength(msgPrefix));
|
|
793
|
+
const freshRawMsg = flowComplete ? rawMsg : (getLiveTextWithFallback(flowId) ?? rawMsg);
|
|
794
|
+
const needsTail = Boolean(getLiveTextWithFallback(flowId) || liveText || lastText);
|
|
795
|
+
const displayMsg = needsTail ? tailText(freshRawMsg, freshBudget) : truncateChars(freshRawMsg, freshBudget);
|
|
796
|
+
const result = scrambleManager.updateMsg(flowId, displayMsg, now, flowComplete, undefined, true);
|
|
797
|
+
return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", result.content, theme, config)}`;
|
|
798
|
+
}, true));
|
|
799
|
+
if (flowComplete) {
|
|
800
|
+
scrambleManager.completeFlow(flowId);
|
|
801
|
+
}
|
|
802
|
+
// Add blank line separator between flows (with continuation pipe)
|
|
803
|
+
if (!isLast) {
|
|
804
|
+
container.addChild(new TruncatedText(applyRole("treeChars", "│", theme, config), 0, 0));
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
container.addChild(new TruncatedText(applyRole("prefixLabel", "(Ctrl+O to expand tool traces)", theme, config), 0, 0));
|
|
808
|
+
return container;
|
|
809
|
+
}
|
|
810
|
+
function renderMultiFlowCollapsed(results, theme, baseId, config) {
|
|
811
|
+
return renderActivityPanel(results, theme, baseId, config);
|
|
812
|
+
}
|
|
813
|
+
//# sourceMappingURL=render.js.map
|