pi-agent-flow 2.2.14 → 2.2.16
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 +12 -0
- package/dist/batch/apply-patch.d.ts +2 -1
- package/dist/batch/apply-patch.d.ts.map +1 -1
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +9 -43
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +9 -1
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/constants.js +7 -0
- package/dist/batch/constants.js.map +1 -1
- package/dist/batch/execute.d.ts +1 -13
- package/dist/batch/execute.d.ts.map +1 -1
- package/dist/batch/execute.js +87 -28
- package/dist/batch/execute.js.map +1 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +16 -14
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/render.d.ts.map +1 -1
- package/dist/batch/render.js.map +1 -1
- package/dist/batch/summary.d.ts +0 -6
- package/dist/batch/summary.d.ts.map +1 -1
- package/dist/batch/summary.js +3 -3
- package/dist/batch/summary.js.map +1 -1
- package/dist/batch/symbols.d.ts.map +1 -1
- package/dist/batch/symbols.js +3 -1
- package/dist/batch/symbols.js.map +1 -1
- package/dist/batch/truncate-output.d.ts +31 -0
- package/dist/batch/truncate-output.d.ts.map +1 -0
- package/dist/batch/truncate-output.js +120 -0
- package/dist/batch/truncate-output.js.map +1 -0
- package/dist/config/config.d.ts +2 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +10 -24
- package/dist/config/config.js.map +1 -1
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +7 -5
- package/dist/config/models.js.map +1 -1
- package/dist/config/paths.d.ts +3 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +9 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/resolver-helpers.d.ts +44 -0
- package/dist/config/resolver-helpers.d.ts.map +1 -0
- package/dist/config/resolver-helpers.js +135 -0
- package/dist/config/resolver-helpers.js.map +1 -0
- package/dist/config/settings-resolver.d.ts +0 -6
- package/dist/config/settings-resolver.d.ts.map +1 -1
- package/dist/config/settings-resolver.js +27 -256
- package/dist/config/settings-resolver.js.map +1 -1
- package/dist/core2/snapshot.d.ts +0 -2
- package/dist/core2/snapshot.d.ts.map +1 -1
- package/dist/core2/snapshot.js +91 -24
- package/dist/core2/snapshot.js.map +1 -1
- package/dist/flow/agents.d.ts +1 -1
- package/dist/flow/agents.d.ts.map +1 -1
- package/dist/flow/agents.js +18 -9
- package/dist/flow/agents.js.map +1 -1
- package/dist/flow/audit-formatters.d.ts +20 -0
- package/dist/flow/audit-formatters.d.ts.map +1 -0
- package/dist/flow/audit-formatters.js +123 -0
- package/dist/flow/audit-formatters.js.map +1 -0
- package/dist/flow/complexity.d.ts +3 -2
- package/dist/flow/complexity.d.ts.map +1 -1
- package/dist/flow/complexity.js +1 -1
- package/dist/flow/complexity.js.map +1 -1
- package/dist/flow/continuation.d.ts.map +1 -1
- package/dist/flow/continuation.js +6 -4
- package/dist/flow/continuation.js.map +1 -1
- package/dist/flow/cycle-guard.d.ts +20 -0
- package/dist/flow/cycle-guard.d.ts.map +1 -0
- package/dist/flow/cycle-guard.js +56 -0
- package/dist/flow/cycle-guard.js.map +1 -0
- package/dist/flow/depth.d.ts +0 -1
- package/dist/flow/depth.d.ts.map +1 -1
- package/dist/flow/depth.js +3 -2
- package/dist/flow/depth.js.map +1 -1
- package/dist/flow/dump-io.d.ts +15 -0
- package/dist/flow/dump-io.d.ts.map +1 -0
- package/dist/flow/dump-io.js +169 -0
- package/dist/flow/dump-io.js.map +1 -0
- package/dist/flow/execute-single.d.ts +8 -0
- package/dist/flow/execute-single.d.ts.map +1 -0
- package/dist/flow/execute-single.js +132 -0
- package/dist/flow/execute-single.js.map +1 -0
- package/dist/flow/executor.d.ts +3 -62
- package/dist/flow/executor.d.ts.map +1 -1
- package/dist/flow/executor.js +36 -342
- package/dist/flow/executor.js.map +1 -1
- package/dist/flow/flow-args.d.ts +14 -0
- package/dist/flow/flow-args.d.ts.map +1 -0
- package/dist/flow/flow-args.js +119 -0
- package/dist/flow/flow-args.js.map +1 -0
- package/dist/flow/flow-live.d.ts +18 -0
- package/dist/flow/flow-live.d.ts.map +1 -0
- package/dist/flow/flow-live.js +46 -0
- package/dist/flow/flow-live.js.map +1 -0
- package/dist/flow/process-lifecycle.d.ts +28 -0
- package/dist/flow/process-lifecycle.d.ts.map +1 -0
- package/dist/flow/process-lifecycle.js +114 -0
- package/dist/flow/process-lifecycle.js.map +1 -0
- package/dist/flow/runner.d.ts +4 -54
- package/dist/flow/runner.d.ts.map +1 -1
- package/dist/flow/runner.js +55 -517
- package/dist/flow/runner.js.map +1 -1
- package/dist/flow/settings-command.d.ts +2 -24
- package/dist/flow/settings-command.d.ts.map +1 -1
- package/dist/flow/settings-command.js +14 -716
- package/dist/flow/settings-command.js.map +1 -1
- package/dist/flow/settings-handler.d.ts +9 -0
- package/dist/flow/settings-handler.d.ts.map +1 -0
- package/dist/flow/settings-handler.js +377 -0
- package/dist/flow/settings-handler.js.map +1 -0
- package/dist/flow/settings-items.d.ts +42 -0
- package/dist/flow/settings-items.d.ts.map +1 -0
- package/dist/flow/settings-items.js +380 -0
- package/dist/flow/settings-items.js.map +1 -0
- package/dist/flow/spawn-utils.d.ts +17 -0
- package/dist/flow/spawn-utils.d.ts.map +1 -0
- package/dist/flow/spawn-utils.js +40 -0
- package/dist/flow/spawn-utils.js.map +1 -0
- package/dist/flow/store.d.ts.map +1 -1
- package/dist/flow/store.js +2 -8
- package/dist/flow/store.js.map +1 -1
- package/dist/flow/transition.d.ts +0 -1
- package/dist/flow/transition.d.ts.map +1 -1
- package/dist/flow/transition.js +0 -3
- package/dist/flow/transition.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -57
- package/dist/index.js.map +1 -1
- package/dist/io/atomic-write.d.ts +10 -0
- package/dist/io/atomic-write.d.ts.map +1 -0
- package/dist/io/atomic-write.js +30 -0
- package/dist/io/atomic-write.js.map +1 -0
- package/dist/notify/notify.d.ts.map +1 -1
- package/dist/notify/notify.js +1 -4
- package/dist/notify/notify.js.map +1 -1
- package/dist/snapshot/cli-args.d.ts +1 -2
- package/dist/snapshot/cli-args.d.ts.map +1 -1
- package/dist/snapshot/runner-events.d.ts +10 -0
- package/dist/snapshot/runner-events.d.ts.map +1 -1
- package/dist/snapshot/runner-events.js +112 -30
- package/dist/snapshot/runner-events.js.map +1 -1
- package/dist/snapshot/structured-output.d.ts.map +1 -1
- package/dist/snapshot/structured-output.js +3 -1
- package/dist/snapshot/structured-output.js.map +1 -1
- package/dist/snapshot/trace-output.d.ts.map +1 -1
- package/dist/snapshot/trace-output.js +8 -4
- package/dist/snapshot/trace-output.js.map +1 -1
- package/dist/steering/sliding-prompt.d.ts.map +1 -1
- package/dist/steering/sliding-prompt.js +12 -15
- package/dist/steering/sliding-prompt.js.map +1 -1
- package/dist/steering/tool-utils.d.ts +3 -3
- package/dist/steering/tool-utils.d.ts.map +1 -1
- package/dist/steering/tool-utils.js +3 -3
- package/dist/steering/tool-utils.js.map +1 -1
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/timed-bash.d.ts.map +1 -1
- package/dist/tools/timed-bash.js +8 -3
- package/dist/tools/timed-bash.js.map +1 -1
- package/dist/tools/trace.d.ts +0 -1
- package/dist/tools/trace.d.ts.map +1 -1
- package/dist/tools/trace.js +79 -62
- package/dist/tools/trace.js.map +1 -1
- package/dist/tools/web-ops.d.ts +0 -49
- package/dist/tools/web-ops.d.ts.map +1 -1
- package/dist/tools/web-ops.js +9 -9
- package/dist/tools/web-ops.js.map +1 -1
- package/dist/tui/body-render.d.ts +31 -0
- package/dist/tui/body-render.d.ts.map +1 -0
- package/dist/tui/body-render.js +589 -0
- package/dist/tui/body-render.js.map +1 -0
- package/dist/tui/context-display.d.ts +18 -0
- package/dist/tui/context-display.d.ts.map +1 -0
- package/dist/tui/context-display.js +29 -0
- package/dist/tui/context-display.js.map +1 -0
- package/dist/tui/flow-colors.d.ts +6 -1
- package/dist/tui/flow-colors.d.ts.map +1 -1
- package/dist/tui/flow-colors.js +1 -0
- package/dist/tui/flow-colors.js.map +1 -1
- package/dist/tui/flow-live-state.d.ts +36 -0
- package/dist/tui/flow-live-state.d.ts.map +1 -0
- package/dist/tui/flow-live-state.js +53 -0
- package/dist/tui/flow-live-state.js.map +1 -0
- package/dist/tui/grouping.d.ts +27 -0
- package/dist/tui/grouping.d.ts.map +1 -0
- package/dist/tui/grouping.js +191 -0
- package/dist/tui/grouping.js.map +1 -0
- package/dist/tui/header.d.ts +25 -0
- package/dist/tui/header.d.ts.map +1 -0
- package/dist/tui/header.js +95 -0
- package/dist/tui/header.js.map +1 -0
- package/dist/tui/render-utils.d.ts +1 -0
- package/dist/tui/render-utils.d.ts.map +1 -1
- package/dist/tui/render-utils.js +4 -5
- package/dist/tui/render-utils.js.map +1 -1
- package/dist/tui/render.d.ts +19 -33
- package/dist/tui/render.d.ts.map +1 -1
- package/dist/tui/render.js +112 -1014
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/scramble/animation-status.d.ts +5 -0
- package/dist/tui/scramble/animation-status.d.ts.map +1 -0
- package/dist/tui/scramble/animation-status.js +93 -0
- package/dist/tui/scramble/animation-status.js.map +1 -0
- package/dist/tui/scramble/constants.d.ts +0 -4
- package/dist/tui/scramble/constants.d.ts.map +1 -1
- package/dist/tui/scramble/constants.js +3 -3
- package/dist/tui/scramble/constants.js.map +1 -1
- package/dist/tui/scramble/dynamic-text.d.ts +10 -0
- package/dist/tui/scramble/dynamic-text.d.ts.map +1 -0
- package/dist/tui/scramble/dynamic-text.js +19 -0
- package/dist/tui/scramble/dynamic-text.js.map +1 -0
- package/dist/tui/scramble/index.d.ts +8 -2
- package/dist/tui/scramble/index.d.ts.map +1 -1
- package/dist/tui/scramble/index.js +8 -1
- package/dist/tui/scramble/index.js.map +1 -1
- package/dist/tui/scramble/lifecycle.d.ts +4 -0
- package/dist/tui/scramble/lifecycle.d.ts.map +1 -0
- package/dist/tui/scramble/lifecycle.js +75 -0
- package/dist/tui/scramble/lifecycle.js.map +1 -0
- package/dist/tui/scramble/line-state.d.ts +5 -0
- package/dist/tui/scramble/line-state.d.ts.map +1 -0
- package/dist/tui/scramble/line-state.js +99 -0
- package/dist/tui/scramble/line-state.js.map +1 -0
- package/dist/tui/scramble/manager.d.ts +3 -14
- package/dist/tui/scramble/manager.d.ts.map +1 -1
- package/dist/tui/scramble/manager.js +22 -405
- package/dist/tui/scramble/manager.js.map +1 -1
- package/dist/tui/scramble/singleton.d.ts +3 -0
- package/dist/tui/scramble/singleton.d.ts.map +1 -0
- package/dist/tui/scramble/singleton.js +3 -0
- package/dist/tui/scramble/singleton.js.map +1 -0
- package/dist/tui/scramble/timer.d.ts +6 -0
- package/dist/tui/scramble/timer.d.ts.map +1 -0
- package/dist/tui/scramble/timer.js +28 -0
- package/dist/tui/scramble/timer.js.map +1 -0
- package/dist/tui/scramble/utils.d.ts +0 -2
- package/dist/tui/scramble/utils.d.ts.map +1 -1
- package/dist/tui/scramble/utils.js +0 -9
- package/dist/tui/scramble/utils.js.map +1 -1
- package/dist/tui/scramble/value-flash.d.ts +6 -0
- package/dist/tui/scramble/value-flash.d.ts.map +1 -0
- package/dist/tui/scramble/value-flash.js +141 -0
- package/dist/tui/scramble/value-flash.js.map +1 -0
- package/dist/tui/traces.d.ts +13 -0
- package/dist/tui/traces.d.ts.map +1 -0
- package/dist/tui/traces.js +97 -0
- package/dist/tui/traces.js.map +1 -0
- package/dist/types/ui.d.ts +0 -4
- package/dist/types/ui.d.ts.map +1 -1
- package/dist/types/ui.js +1 -30
- package/dist/types/ui.js.map +1 -1
- package/package.json +5 -1
package/dist/tui/render.js
CHANGED
|
@@ -4,14 +4,21 @@
|
|
|
4
4
|
* Option B: collapsed view shows structured report (Summary/Done/Not Done/Next Steps).
|
|
5
5
|
* Expanded view adds raw tool call traces.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
7
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
8
|
+
import { isFlowError, isFlowSuccess } from "../types/flow.js";
|
|
9
|
+
import { getFlowDisplayItems } from "../types/ui.js";
|
|
10
|
+
import { getFlowOutput } from "../types/flow.js";
|
|
11
|
+
import { scrambleManager, runScrambleTimer } from "./scramble/index.js";
|
|
12
|
+
import { buildBootPhaseSingleResult, getFlowLiveState } from "./flow-live-state.js";
|
|
13
|
+
import { applyRole } from "./flow-colors.js";
|
|
14
|
+
import { detectGroups, flowStatusIcon, isFlowStatusComplete, isFlowRunning, isFlowAwaiting, } from "./grouping.js";
|
|
15
|
+
import { renderFlowHeader, } from "./header.js";
|
|
16
|
+
import { renderFlowExpanded, renderFlowCollapsed, renderFlowBody, renderMultiFlowExpanded, } from "./body-render.js";
|
|
17
|
+
// Re-export all extracted symbols for backward compatibility
|
|
18
|
+
export { detectGroups, flowStatusIcon, getFlowStatus, isFlowStatusComplete, isFlowRunning, isFlowAwaiting, hashStrToSeed, getScintillatingStatusDot, } from "./grouping.js";
|
|
19
|
+
export { reconstructHeader, sectionHeader, renderFlowHeader, formatCollapsedFlowHeaderTypeName, } from "./header.js";
|
|
20
|
+
export { renderFlowExpanded, renderFlowCollapsed, renderFlowBody, renderMultiFlowExpanded, } from "./body-render.js";
|
|
21
|
+
export { getContentRole, applyScrambledContextLabel, getLiveTextWithFallback, shortenPath, formatFlowToolCall, splitOutputLines, renderToolTraces, renderFlowReport, } from "./traces.js";
|
|
15
22
|
// ---------------------------------------------------------------------------
|
|
16
23
|
// Anonymous flow-id counter — prevents scramble-state collisions when multiple
|
|
17
24
|
// flow widgets share the screen and toolCallId is absent from result/args.
|
|
@@ -24,327 +31,6 @@ function getAnonymousFlowId() {
|
|
|
24
31
|
export function resetAnonymousFlowIdCounter() {
|
|
25
32
|
anonFlowIdCounter = 0;
|
|
26
33
|
}
|
|
27
|
-
function getContentRole(baseRole, text, useError) {
|
|
28
|
-
if (useError)
|
|
29
|
-
return "msgError";
|
|
30
|
-
if (["[awaiting...]", "[skipped]", "[approved]", "[finished]"].includes(text)) {
|
|
31
|
-
return "placeholder";
|
|
32
|
-
}
|
|
33
|
-
return baseRole;
|
|
34
|
-
}
|
|
35
|
-
function getLiveTextWithFallback(id) {
|
|
36
|
-
const value = getLiveText(id);
|
|
37
|
-
if (value !== undefined)
|
|
38
|
-
return value;
|
|
39
|
-
const fallbackId = id.includes("#") ? "collapsed" + id.slice(id.indexOf("#")) : "collapsed";
|
|
40
|
-
return getLiveText(fallbackId);
|
|
41
|
-
}
|
|
42
|
-
import { formatCompactStats, formatFlowTypeName, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi, formatModelLabel, formatContextLabel, formatTps } from "./render-utils.js";
|
|
43
|
-
function shortenPath(p) {
|
|
44
|
-
const home = os.homedir();
|
|
45
|
-
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
46
|
-
}
|
|
47
|
-
import { applyRole, } from "./flow-colors.js";
|
|
48
|
-
function formatCollapsedFlowHeaderTypeName(type) {
|
|
49
|
-
return type.toLowerCase();
|
|
50
|
-
}
|
|
51
|
-
function formatFlowToolCall(toolName, args, fg) {
|
|
52
|
-
const pathArg = (args.file_path || args.path || "...");
|
|
53
|
-
switch (toolName) {
|
|
54
|
-
case "bash": {
|
|
55
|
-
const cmd = (args.command || "...").replace(/[\n\r\t]+/g, " ").replace(/ +/g, " ").trim();
|
|
56
|
-
return fg("muted", "$ ") + fg("toolOutput", cmd);
|
|
57
|
-
}
|
|
58
|
-
case "read": {
|
|
59
|
-
let text = fg("accent", shortenPath(pathArg));
|
|
60
|
-
const offset = args.offset;
|
|
61
|
-
const limit = args.limit;
|
|
62
|
-
if (offset !== undefined || limit !== undefined) {
|
|
63
|
-
const start = offset ?? 1;
|
|
64
|
-
const end = limit !== undefined ? start + limit - 1 : "";
|
|
65
|
-
text += fg("warning", `:${start}${end ? `-${end}` : ""}`);
|
|
66
|
-
}
|
|
67
|
-
return fg("muted", "read ") + text;
|
|
68
|
-
}
|
|
69
|
-
case "write": {
|
|
70
|
-
const lines = (args.content || "").split("\n").length;
|
|
71
|
-
let text = fg("muted", "write ") + fg("accent", shortenPath(pathArg));
|
|
72
|
-
if (lines > 1)
|
|
73
|
-
text += fg("dim", ` (${lines} lines)`);
|
|
74
|
-
return text;
|
|
75
|
-
}
|
|
76
|
-
case "edit":
|
|
77
|
-
return fg("muted", "edit ") + fg("accent", shortenPath(pathArg));
|
|
78
|
-
case "ls":
|
|
79
|
-
return fg("muted", "ls ") + fg("accent", shortenPath((args.path || ".")));
|
|
80
|
-
case "find":
|
|
81
|
-
return fg("muted", "find ") + fg("accent", (args.pattern || "*")) + fg("dim", ` in ${shortenPath((args.path || "."))}`);
|
|
82
|
-
case "grep":
|
|
83
|
-
return fg("muted", "grep ") + fg("accent", `/${(args.pattern || "")}/`) + fg("dim", ` in ${shortenPath((args.path || "."))}`);
|
|
84
|
-
case "batch":
|
|
85
|
-
case "batch_read": {
|
|
86
|
-
const summary = formatBatchOpsSummary(args);
|
|
87
|
-
return fg("muted", `${toolName} `) + fg("accent", summary);
|
|
88
|
-
}
|
|
89
|
-
default:
|
|
90
|
-
return fg("accent", toolName) + fg("dim", ` ${JSON.stringify(args)}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
// Shared rendering building blocks
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
function splitOutputLines(text) {
|
|
97
|
-
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
98
|
-
if (lines.length > 1 && lines[lines.length - 1] === "")
|
|
99
|
-
lines.pop();
|
|
100
|
-
return lines;
|
|
101
|
-
}
|
|
102
|
-
function renderToolTraces(items, theme, config) {
|
|
103
|
-
const lines = [];
|
|
104
|
-
for (const item of items) {
|
|
105
|
-
if (item.type === "toolCall") {
|
|
106
|
-
lines.push(applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme)));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return lines.join("\n");
|
|
110
|
-
}
|
|
111
|
-
function renderFlowReport(output, theme, config) {
|
|
112
|
-
const lines = splitOutputLines(output);
|
|
113
|
-
return lines.map((line) => applyRole("actContent", line, theme, config)).join("\n");
|
|
114
|
-
}
|
|
115
|
-
function getFlowStatus(r) {
|
|
116
|
-
return r.status ?? (r.exitCode === -1 ? "running" : r.exitCode === 0 ? "done" : "error");
|
|
117
|
-
}
|
|
118
|
-
function isFlowStatusComplete(r) {
|
|
119
|
-
const status = getFlowStatus(r);
|
|
120
|
-
return status === "done" || status === "error" || status === "skipped";
|
|
121
|
-
}
|
|
122
|
-
function isFlowRunning(r) {
|
|
123
|
-
const status = getFlowStatus(r);
|
|
124
|
-
return status === "running" || status === "pending";
|
|
125
|
-
}
|
|
126
|
-
function isFlowAwaiting(r) {
|
|
127
|
-
return getFlowStatus(r) === "awaiting";
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Detect audit-loop groups.
|
|
131
|
-
*
|
|
132
|
-
* When the executor stamps `auditLoopGroupId` on results, grouping is
|
|
133
|
-
* explicit and works regardless of array layout (no contiguity required).
|
|
134
|
-
*
|
|
135
|
-
* When no `auditLoopGroupId` is present (legacy / hand-crafted results),
|
|
136
|
-
* we fall back to contiguity-based detection: N contiguous builds with
|
|
137
|
-
* `pingPongMeta` followed immediately by an audit with
|
|
138
|
-
* `auditParentType === "build"`.
|
|
139
|
-
*/
|
|
140
|
-
export function detectGroups(results) {
|
|
141
|
-
const groups = [];
|
|
142
|
-
const rootIndices = [];
|
|
143
|
-
// Phase 1: explicit grouping by auditLoopGroupId
|
|
144
|
-
const groupMap = new Map();
|
|
145
|
-
const ungroupedIndices = [];
|
|
146
|
-
for (let i = 0; i < results.length; i++) {
|
|
147
|
-
const r = results[i];
|
|
148
|
-
if (r.auditLoopGroupId !== undefined) {
|
|
149
|
-
let g = groupMap.get(r.auditLoopGroupId);
|
|
150
|
-
if (!g) {
|
|
151
|
-
g = { buildIndices: [], auditIndex: -1 };
|
|
152
|
-
groupMap.set(r.auditLoopGroupId, g);
|
|
153
|
-
}
|
|
154
|
-
if (r.pingPongMeta) {
|
|
155
|
-
g.buildIndices.push(i);
|
|
156
|
-
}
|
|
157
|
-
else if (r.auditParentType === "build") {
|
|
158
|
-
g.auditIndex = i;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
ungroupedIndices.push(i);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
for (const g of groupMap.values()) {
|
|
166
|
-
if (g.auditIndex !== -1) {
|
|
167
|
-
groups.push({ buildIndices: g.buildIndices, auditIndex: g.auditIndex });
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
// Orphaned builds with groupId but no audit capstone
|
|
171
|
-
rootIndices.push(...g.buildIndices);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// Phase 2: legacy fallback on ungrouped results (contiguity-based)
|
|
175
|
-
let i = 0;
|
|
176
|
-
while (i < ungroupedIndices.length) {
|
|
177
|
-
const idx = ungroupedIndices[i];
|
|
178
|
-
const r = results[idx];
|
|
179
|
-
if (r.pingPongMeta) {
|
|
180
|
-
const buildIndices = [];
|
|
181
|
-
while (i < ungroupedIndices.length && results[ungroupedIndices[i]].pingPongMeta) {
|
|
182
|
-
buildIndices.push(ungroupedIndices[i]);
|
|
183
|
-
i++;
|
|
184
|
-
}
|
|
185
|
-
if (i < ungroupedIndices.length && results[ungroupedIndices[i]].auditParentType === "build") {
|
|
186
|
-
groups.push({ buildIndices, auditIndex: ungroupedIndices[i] });
|
|
187
|
-
i++;
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
rootIndices.push(...buildIndices);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else if (r.auditParentType === "build" && i > 0 && results[ungroupedIndices[i - 1]].pingPongMeta) {
|
|
194
|
-
i++; // orphan audit already consumed
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
rootIndices.push(idx);
|
|
198
|
-
i++;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return { groups, rootIndices };
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Get the status icon dot for a result (● ○ ✗ ⊘).
|
|
205
|
-
*/
|
|
206
|
-
function flowStatusIcon(r, theme) {
|
|
207
|
-
const status = getFlowStatus(r);
|
|
208
|
-
switch (status) {
|
|
209
|
-
case "running":
|
|
210
|
-
case "pending":
|
|
211
|
-
return theme.fg("warning", "●");
|
|
212
|
-
case "awaiting":
|
|
213
|
-
return theme.fg("muted", "○");
|
|
214
|
-
case "done":
|
|
215
|
-
return theme.fg("success", "●");
|
|
216
|
-
case "error":
|
|
217
|
-
return theme.fg("error", "✗");
|
|
218
|
-
case "skipped":
|
|
219
|
-
return theme.fg("muted", "⊘");
|
|
220
|
-
default:
|
|
221
|
-
return theme.fg("muted", "?");
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function hashStrToSeed(s) {
|
|
225
|
-
let h = 2166136261;
|
|
226
|
-
for (let i = 0; i < s.length; i++) {
|
|
227
|
-
h ^= s.charCodeAt(i);
|
|
228
|
-
h = Math.imul(h, 16777619);
|
|
229
|
-
}
|
|
230
|
-
return h >>> 0;
|
|
231
|
-
}
|
|
232
|
-
function getScintillatingStatusDot(r, theme, now, flowId) {
|
|
233
|
-
const status = getFlowStatus(r);
|
|
234
|
-
switch (status) {
|
|
235
|
-
case "running":
|
|
236
|
-
case "pending": {
|
|
237
|
-
const isPending = status === "pending";
|
|
238
|
-
const seed = hashStrToSeed(flowId || r.type);
|
|
239
|
-
const bucketSize = isPending ? 7000 : 5000;
|
|
240
|
-
const bucket = Math.floor(now / bucketSize);
|
|
241
|
-
const t = now % bucketSize;
|
|
242
|
-
const burstCount = isPending
|
|
243
|
-
? 1 + Math.floor(hashNoise(seed, bucket, 0, 0x5a4f) * 2) // 1-2
|
|
244
|
-
: 2 + Math.floor(hashNoise(seed, bucket, 0, 0x5a4f) * 2); // 2-3
|
|
245
|
-
let cursor = 50;
|
|
246
|
-
for (let b = 0; b < burstCount; b++) {
|
|
247
|
-
const gap = isPending
|
|
248
|
-
? 800 + Math.floor(hashNoise(seed, bucket, b * 4, 0xb8a0) * 1400) // 800-2200ms
|
|
249
|
-
: 500 + Math.floor(hashNoise(seed, bucket, b * 4, 0xb8a0) * 1300); // 500-1800ms
|
|
250
|
-
cursor += gap;
|
|
251
|
-
const duration = isPending
|
|
252
|
-
? 80 + Math.floor(hashNoise(seed, bucket, b * 4 + 1, 0xc0de) * 170) // 80-250ms
|
|
253
|
-
: 100 + Math.floor(hashNoise(seed, bucket, b * 4 + 1, 0xc0de) * 250); // 100-350ms
|
|
254
|
-
const burstStart = cursor;
|
|
255
|
-
const burstEnd = cursor + duration;
|
|
256
|
-
cursor = burstEnd;
|
|
257
|
-
if (t >= burstStart && t < burstEnd) {
|
|
258
|
-
const tInBurst = t - burstStart;
|
|
259
|
-
const tick = 12 + Math.floor(hashNoise(seed, bucket, b * 4 + 3, 0xd1a0) * 10); // 12-22ms per stutter step
|
|
260
|
-
// Vary stutter depth: 3-tick ○●○ or 5-tick ○●○●○ per burst
|
|
261
|
-
const rawStutterTicks = hashNoise(seed, bucket, b * 4 + 2, 0xe7a1) > 0.5 ? 5 : 3;
|
|
262
|
-
const stutterLen5 = tick * 5;
|
|
263
|
-
const onRunMax5 = duration - stutterLen5 - 5;
|
|
264
|
-
const stutterTicks = (rawStutterTicks === 5 && onRunMax5 >= tick) ? 5 : 3;
|
|
265
|
-
const stutterLen = tick * stutterTicks;
|
|
266
|
-
const onRunMax = duration - stutterLen - 5;
|
|
267
|
-
const onRun = Math.max(tick, Math.min(Math.floor(duration * (0.35 + hashNoise(seed, bucket, b * 4 + 2, 0xf1c0) * 0.3)), onRunMax));
|
|
268
|
-
const cycleLen = onRun + stutterLen;
|
|
269
|
-
const phaseInCycle = tInBurst % cycleLen;
|
|
270
|
-
const cycleIdx = Math.floor(tInBurst / cycleLen);
|
|
271
|
-
// Helper: dip ○ with occasional sparkle
|
|
272
|
-
const dipDot = (dipIndex) => {
|
|
273
|
-
if (hashNoise(seed, bucket, cycleIdx + dipIndex * 100, 0x5ab0) < 0.05) {
|
|
274
|
-
const sparkIdx = Math.floor(hashNoise(seed, bucket, cycleIdx + dipIndex * 100, 0x5b1) * THIN_BRAILLE_SPARK.length);
|
|
275
|
-
return theme.fg("muted", THIN_BRAILLE_SPARK[sparkIdx]);
|
|
276
|
-
}
|
|
277
|
-
return theme.fg("muted", "○");
|
|
278
|
-
};
|
|
279
|
-
if (phaseInCycle < onRun) {
|
|
280
|
-
// Sustained bright ●
|
|
281
|
-
return theme.fg("warning", "●");
|
|
282
|
-
}
|
|
283
|
-
else if (phaseInCycle < onRun + tick) {
|
|
284
|
-
return dipDot(0); // ○ dip 1
|
|
285
|
-
}
|
|
286
|
-
else if (phaseInCycle < onRun + tick * 2) {
|
|
287
|
-
return theme.fg("warning", "●"); // ● flash 1
|
|
288
|
-
}
|
|
289
|
-
else if (phaseInCycle < onRun + tick * 3) {
|
|
290
|
-
return dipDot(1); // ○ dip 2
|
|
291
|
-
}
|
|
292
|
-
else if (stutterTicks >= 5 && phaseInCycle < onRun + tick * 4) {
|
|
293
|
-
return theme.fg("warning", "●"); // ● flash 2 (5-tick only)
|
|
294
|
-
}
|
|
295
|
-
else if (stutterTicks >= 5 && phaseInCycle < onRun + tick * 5) {
|
|
296
|
-
return dipDot(2); // ○ dip 3 (5-tick only)
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
// Fallback: shouldn't reach if scheduling is correct
|
|
300
|
-
return theme.fg("warning", "●");
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return theme.fg("warning", "●");
|
|
305
|
-
}
|
|
306
|
-
case "awaiting":
|
|
307
|
-
return theme.fg("muted", "○");
|
|
308
|
-
case "done":
|
|
309
|
-
return theme.fg("success", "●");
|
|
310
|
-
case "error":
|
|
311
|
-
return theme.fg("error", "✗");
|
|
312
|
-
case "skipped":
|
|
313
|
-
return theme.fg("muted", "⊘");
|
|
314
|
-
default:
|
|
315
|
-
return theme.fg("muted", "?");
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
/** Center a label in a fixed-width header using em-dashes. Total width = 20. */
|
|
319
|
-
function sectionHeader(label) {
|
|
320
|
-
const total = 20;
|
|
321
|
-
const innerLen = label.length + 2; // account for spaces around label
|
|
322
|
-
const side = (total - innerLen) / 2;
|
|
323
|
-
const left = "─".repeat(Math.floor(side));
|
|
324
|
-
const right = "─".repeat(Math.ceil(side));
|
|
325
|
-
return `${left} ${label} ${right}`;
|
|
326
|
-
}
|
|
327
|
-
/** Reconstruct multi-segment ANSI styles on a flat string by splitting at
|
|
328
|
-
* original segment boundaries and re-applying each segment's style function.
|
|
329
|
-
*/
|
|
330
|
-
export function reconstructHeader(content, segments) {
|
|
331
|
-
let offset = 0;
|
|
332
|
-
const parts = [];
|
|
333
|
-
for (const seg of segments) {
|
|
334
|
-
const len = seg.text.length;
|
|
335
|
-
if (offset >= content.length)
|
|
336
|
-
break;
|
|
337
|
-
parts.push(seg.style(content.slice(offset, offset + len)));
|
|
338
|
-
offset += len;
|
|
339
|
-
}
|
|
340
|
-
if (offset < content.length) {
|
|
341
|
-
parts.push(content.slice(offset));
|
|
342
|
-
}
|
|
343
|
-
return parts.join("");
|
|
344
|
-
}
|
|
345
|
-
// ---------------------------------------------------------------------------
|
|
346
|
-
// renderFlowCall — shown while the flow is being invoked
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
34
|
export function renderFlowCall(args, theme, config) {
|
|
349
35
|
let container = new Text("", 0, 0);
|
|
350
36
|
// In-place mutation pattern: reuse the stored root container
|
|
@@ -357,7 +43,7 @@ export function renderFlowCall(args, theme, config) {
|
|
|
357
43
|
s.__rootContainer = root;
|
|
358
44
|
container = root;
|
|
359
45
|
}
|
|
360
|
-
else
|
|
46
|
+
else {
|
|
361
47
|
const root = s.__rootContainer;
|
|
362
48
|
root.clear();
|
|
363
49
|
root.addChild(container);
|
|
@@ -373,13 +59,13 @@ export function renderFlowCall(args, theme, config) {
|
|
|
373
59
|
export function renderFlowResult(result, expanded, theme, args, config) {
|
|
374
60
|
const details = result.details;
|
|
375
61
|
const streamingText = result.content?.[0]?.type === "text" ? result.content[0].text : undefined;
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
62
|
+
const resolvedIdForLive = result._toolCallId
|
|
63
|
+
|| args?.toolCallId
|
|
64
|
+
|| args?.id;
|
|
65
|
+
const live = getFlowLiveState(resolvedIdForLive);
|
|
66
|
+
const sharedContext = details?.sharedContext
|
|
67
|
+
?? args?.sharedContext
|
|
68
|
+
?? live?.sharedContext;
|
|
383
69
|
let resolvedToolCallId;
|
|
384
70
|
if (args?.state) {
|
|
385
71
|
const s = args.state;
|
|
@@ -396,41 +82,30 @@ export function renderFlowResult(result, expanded, theme, args, config) {
|
|
|
396
82
|
resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
|
|
397
83
|
}
|
|
398
84
|
let container;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
...(flowRequest.maxContextTokens !== undefined ? { maxContextTokens: flowRequest.maxContextTokens } : {}),
|
|
415
|
-
};
|
|
416
|
-
const ghostId = resolvedToolCallId || 'ghost';
|
|
417
|
-
if (expanded) {
|
|
418
|
-
const now = Date.now();
|
|
419
|
-
container = renderFlowExpanded(ghostResult, flowStatusIcon(ghostResult, theme), false, getFlowDisplayItems([]), getFlowOutput([]), theme, ghostId, now, false, streamingText || "", config, undefined);
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
container = renderFlowCollapsed(ghostResult, flowStatusIcon(ghostResult, theme), false, streamingText || "", theme, undefined, ghostId, config);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
container = new Text(scrambleManager.renderStatic(streamingText || ""), 0, 0);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
else if (details.results.length === 1) {
|
|
430
|
-
container = renderSingleFlowResult(details.results[0], expanded, theme, streamingText, resolvedToolCallId, config, sharedContext);
|
|
85
|
+
const flowRequest = args?.flow?.[0];
|
|
86
|
+
const bootResult = buildBootPhaseSingleResult(live, flowRequest);
|
|
87
|
+
const results = details?.results?.length
|
|
88
|
+
? details.results
|
|
89
|
+
: bootResult
|
|
90
|
+
? [bootResult]
|
|
91
|
+
: [];
|
|
92
|
+
if (results.length === 0) {
|
|
93
|
+
container = new Text(scrambleManager.renderStatic(streamingText || ""), 0, 0);
|
|
94
|
+
}
|
|
95
|
+
else if (results.length === 1) {
|
|
96
|
+
container = renderSingleFlowResult(results[0], expanded, theme, streamingText, resolvedToolCallId, config, sharedContext);
|
|
97
|
+
}
|
|
98
|
+
else if (details && details.results.length > 1) {
|
|
99
|
+
container = renderMultiFlowResult(details, expanded, theme, resolvedToolCallId, config, sharedContext);
|
|
431
100
|
}
|
|
432
101
|
else {
|
|
433
|
-
container = renderMultiFlowResult(
|
|
102
|
+
container = renderMultiFlowResult({
|
|
103
|
+
mode: "flow",
|
|
104
|
+
flowStyle: "fork",
|
|
105
|
+
projectAgentsDir: details?.projectAgentsDir ?? null,
|
|
106
|
+
results,
|
|
107
|
+
sharedContext,
|
|
108
|
+
}, expanded, theme, resolvedToolCallId, config, sharedContext);
|
|
434
109
|
}
|
|
435
110
|
// In-place mutation pattern: reuse the stored root container
|
|
436
111
|
// so the TUI host's cached reference stays valid.
|
|
@@ -447,7 +122,7 @@ export function renderFlowResult(result, expanded, theme, args, config) {
|
|
|
447
122
|
s.__rootContainer = root;
|
|
448
123
|
}
|
|
449
124
|
}
|
|
450
|
-
else
|
|
125
|
+
else {
|
|
451
126
|
// Subsequent renders: transfer children to the stored container.
|
|
452
127
|
// Use a snapshot of the children array so the loop remains safe even if
|
|
453
128
|
// addChild() mutates the source array (removes from old parent).
|
|
@@ -469,17 +144,8 @@ export function renderFlowResult(result, expanded, theme, args, config) {
|
|
|
469
144
|
}
|
|
470
145
|
// Scramble animation timer — shared helper so any renderer can animate.
|
|
471
146
|
// Use resolvedToolCallId so the timer scope matches the state scope.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
const flowRequest = args?.flow?.[0];
|
|
475
|
-
timerId = resolvedToolCallId || (flowRequest ? 'ghost' : 'single');
|
|
476
|
-
}
|
|
477
|
-
else if (details.results.length === 1) {
|
|
478
|
-
timerId = resolvedToolCallId || 'single';
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
timerId = resolvedToolCallId || 'multi';
|
|
482
|
-
}
|
|
147
|
+
const timerId = resolvedToolCallId
|
|
148
|
+
|| (results.length > 1 ? "multi" : results.length === 1 ? "single" : "empty");
|
|
483
149
|
runScrambleTimer(args, timerId);
|
|
484
150
|
return container;
|
|
485
151
|
}
|
|
@@ -499,342 +165,6 @@ export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCa
|
|
|
499
165
|
}
|
|
500
166
|
return renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, id, config, sharedContext);
|
|
501
167
|
}
|
|
502
|
-
function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText, config, sharedContext) {
|
|
503
|
-
const mdTheme = getMarkdownTheme();
|
|
504
|
-
const container = new Container();
|
|
505
|
-
const typeName = formatFlowTypeName(r.type);
|
|
506
|
-
if (sharedContext) {
|
|
507
|
-
container.addChild(new Text(applyRole("prefixLabel", "── shared context ──", theme, config), 0, 0));
|
|
508
|
-
container.addChild(new Text(applyRole("aimContent", sharedContext.preview, theme, config), 0, 0));
|
|
509
|
-
const statsParts = [];
|
|
510
|
-
statsParts.push(`${sharedContext.userMessageCount} user`);
|
|
511
|
-
statsParts.push(`${sharedContext.assistantMessageCount} assistant`);
|
|
512
|
-
statsParts.push(`${sharedContext.totalTokens} tokens`);
|
|
513
|
-
for (const [name, count] of Object.entries(sharedContext.toolCalls)) {
|
|
514
|
-
statsParts.push(`${count}× ${name}`);
|
|
515
|
-
}
|
|
516
|
-
container.addChild(new Text(applyRole("stats", statsParts.join(" · "), theme, config), 0, 0));
|
|
517
|
-
container.addChild(new Spacer(1));
|
|
518
|
-
container.addChild(new Text(applyRole("prefixLabel", sectionHeader(typeName), theme, config), 0, 0));
|
|
519
|
-
container.addChild(new Spacer(1));
|
|
520
|
-
}
|
|
521
|
-
const initialDot = flowStatusIcon(r, theme);
|
|
522
|
-
let header = `${initialDot} ${applyRole("flowName", typeName, theme, config)}`;
|
|
523
|
-
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
524
|
-
if (errorSegment)
|
|
525
|
-
header += ` ${theme.fg("error", errorSegment)}`;
|
|
526
|
-
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
527
|
-
const plainHeader = dotPlaceholder + typeName + errorSegment;
|
|
528
|
-
const headerSegments = [
|
|
529
|
-
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
|
|
530
|
-
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
531
|
-
];
|
|
532
|
-
if (errorSegment) {
|
|
533
|
-
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
534
|
-
}
|
|
535
|
-
container.addChild(new DynamicScrambleText(header, () => {
|
|
536
|
-
const now = Date.now();
|
|
537
|
-
const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete);
|
|
538
|
-
return reconstructHeader(result.content, headerSegments);
|
|
539
|
-
}));
|
|
540
|
-
if (error && r.errorMessage) {
|
|
541
|
-
container.addChild(new Text(scrambleManager.renderStatic(theme.fg("error", `Error: ${r.errorMessage}`)), 0, 0));
|
|
542
|
-
}
|
|
543
|
-
// Stats: dashboard format
|
|
544
|
-
const displayUsage = sharedContext ? { ...r.usage, input: sharedContext.totalTokens } : r.usage;
|
|
545
|
-
const inlineStats = formatCompactStats(displayUsage, r.model);
|
|
546
|
-
container.addChild(new DynamicScrambleText(applyRole("stats", inlineStats, theme, config), () => {
|
|
547
|
-
const result = scrambleManager.updateText(id, 'stats', stripAnsi(inlineStats), Date.now(), isComplete);
|
|
548
|
-
return result.isAnimating ? applyRole("stats", result.content, theme, config) : applyRole("stats", inlineStats, theme, config);
|
|
549
|
-
}));
|
|
550
|
-
// Intent — column-aware truncation (budget recalculated inside closure for resize handling)
|
|
551
|
-
const intentBudget = getTruncationBudget(0);
|
|
552
|
-
const displayIntent = truncateChars(r.intent, intentBudget);
|
|
553
|
-
container.addChild(new Spacer(1));
|
|
554
|
-
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("intent"), theme, config), 0, 0));
|
|
555
|
-
container.addChild(new DynamicScrambleText(applyRole("aimContent", displayIntent, theme, config), () => {
|
|
556
|
-
const budget = getTruncationBudget(0);
|
|
557
|
-
const text = truncateChars(r.intent, budget);
|
|
558
|
-
const result = scrambleManager.updateText(id, 'intent', text, Date.now(), isComplete);
|
|
559
|
-
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
|
|
560
|
-
}));
|
|
561
|
-
// Acceptance
|
|
562
|
-
if (r.acceptance) {
|
|
563
|
-
const acceptanceRaw = r.acceptance;
|
|
564
|
-
const acceptanceBudget = getTruncationBudget(0);
|
|
565
|
-
const acceptanceText = truncateChars(acceptanceRaw, acceptanceBudget);
|
|
566
|
-
container.addChild(new Spacer(1));
|
|
567
|
-
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("acceptance"), theme, config), 0, 0));
|
|
568
|
-
container.addChild(new DynamicScrambleText(applyRole("aimContent", acceptanceText, theme, config), () => {
|
|
569
|
-
const budget = getTruncationBudget(0);
|
|
570
|
-
const text = truncateChars(acceptanceRaw, budget);
|
|
571
|
-
const result = scrambleManager.updateText(id, 'acceptance', text, Date.now(), isComplete);
|
|
572
|
-
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
|
|
573
|
-
}));
|
|
574
|
-
}
|
|
575
|
-
// Flow report (structured output)
|
|
576
|
-
container.addChild(new Spacer(1));
|
|
577
|
-
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("report"), theme, config), 0, 0));
|
|
578
|
-
// Structured output summary (compact badge when available)
|
|
579
|
-
if (r.structuredOutput) {
|
|
580
|
-
const so = r.structuredOutput;
|
|
581
|
-
const statusColor = so.status === "complete" ? "success" : so.status === "partial" ? "warning" : "error";
|
|
582
|
-
const statusText = `[${so.status}] ${so.summary}`;
|
|
583
|
-
const statusStatic = `${theme.fg(statusColor, `[${so.status}]`)} ${applyRole("aimContent", so.summary, theme, config)}`;
|
|
584
|
-
container.addChild(new DynamicScrambleText(statusStatic, () => {
|
|
585
|
-
const result = scrambleManager.updateText(id, 'report-status', statusText, Date.now(), isComplete, false);
|
|
586
|
-
return result.isAnimating ? `${theme.fg(statusColor, result.content.split(' ')[0])} ${applyRole("aimContent", result.content.slice(result.content.indexOf(' ') + 1), theme, config)}` : statusStatic;
|
|
587
|
-
}));
|
|
588
|
-
if (so.files.length > 0) {
|
|
589
|
-
const filesText = `Files: ${so.files.map((f) => f.path).join(", ")}`;
|
|
590
|
-
container.addChild(new DynamicScrambleText(applyRole("aimContent", filesText, theme, config), () => {
|
|
591
|
-
const result = scrambleManager.updateText(id, 'report-files', filesText, Date.now(), isComplete, false);
|
|
592
|
-
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", filesText, theme, config);
|
|
593
|
-
}));
|
|
594
|
-
}
|
|
595
|
-
if (so.commands?.length > 0) {
|
|
596
|
-
const cmdLabels = so.commands.map((c) => {
|
|
597
|
-
const short = c.command.length > 30 ? c.command.slice(0, 30) + "..." : c.command;
|
|
598
|
-
return `${c.tool ?? "cmd"}: ${short}`;
|
|
599
|
-
});
|
|
600
|
-
const commandsText = `Commands: ${cmdLabels.join(", ")}`;
|
|
601
|
-
container.addChild(new DynamicScrambleText(applyRole("aimContent", commandsText, theme, config), () => {
|
|
602
|
-
const result = scrambleManager.updateText(id, 'report-commands', commandsText, Date.now(), isComplete, false);
|
|
603
|
-
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", commandsText, theme, config);
|
|
604
|
-
}));
|
|
605
|
-
}
|
|
606
|
-
if (so.notDone.length > 0) {
|
|
607
|
-
const notDoneText = `Not Done: ${so.notDone.map((item) => {
|
|
608
|
-
const details = [
|
|
609
|
-
item.reason ? `reason: ${item.reason}` : undefined,
|
|
610
|
-
item.blocker ? `blocker: ${item.blocker}` : undefined,
|
|
611
|
-
item.nextStep ? `next: ${item.nextStep}` : undefined,
|
|
612
|
-
].filter(Boolean).join("; ");
|
|
613
|
-
return details ? `${item.item} (${details})` : item.item;
|
|
614
|
-
}).join("; ")}`;
|
|
615
|
-
container.addChild(new DynamicScrambleText(applyRole("aimContent", notDoneText, theme, config), () => {
|
|
616
|
-
const result = scrambleManager.updateText(id, 'report-notDone', notDoneText, Date.now(), isComplete, false);
|
|
617
|
-
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", notDoneText, theme, config);
|
|
618
|
-
}));
|
|
619
|
-
}
|
|
620
|
-
if (so.nextSteps.length > 0) {
|
|
621
|
-
const nextStepsText = `Next: ${so.nextSteps.join("; ")}`;
|
|
622
|
-
container.addChild(new DynamicScrambleText(applyRole("aimContent", nextStepsText, theme, config), () => {
|
|
623
|
-
const result = scrambleManager.updateText(id, 'report-nextSteps', nextStepsText, Date.now(), isComplete, false);
|
|
624
|
-
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", nextStepsText, theme, config);
|
|
625
|
-
}));
|
|
626
|
-
}
|
|
627
|
-
container.addChild(new Spacer(1));
|
|
628
|
-
}
|
|
629
|
-
// Output: animate streaming text; show clean markdown when complete
|
|
630
|
-
if (isFlowAwaiting(r)) {
|
|
631
|
-
container.addChild(new Text(applyRole("placeholder", "[awaiting...]", theme, config), 0, 0));
|
|
632
|
-
}
|
|
633
|
-
else if (!isComplete && streamingText != null) {
|
|
634
|
-
const msgBudget = getTruncationBudget(0);
|
|
635
|
-
const displayMsg = tailText(stripAnsi(streamingText), msgBudget);
|
|
636
|
-
container.addChild(new DynamicScrambleText(applyRole("msgContent", displayMsg, theme, config), () => {
|
|
637
|
-
const budget = getTruncationBudget(0);
|
|
638
|
-
const freshStreamingText = getLiveTextWithFallback(id) ?? streamingText;
|
|
639
|
-
const text = tailText(stripAnsi(freshStreamingText), budget);
|
|
640
|
-
const result = scrambleManager.updateMsg(id, text, Date.now(), isComplete, undefined, true);
|
|
641
|
-
return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", text, theme, config);
|
|
642
|
-
}));
|
|
643
|
-
}
|
|
644
|
-
else if (flowOutput) {
|
|
645
|
-
container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
const summary = getFlowSummaryText(r);
|
|
649
|
-
container.addChild(new DynamicScrambleText(applyRole("msgContent", summary, theme, config), () => {
|
|
650
|
-
const result = scrambleManager.updateText(id, 'output-summary', summary, Date.now(), isComplete, false);
|
|
651
|
-
return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", summary, theme, config);
|
|
652
|
-
}));
|
|
653
|
-
}
|
|
654
|
-
// Tool traces (expanded only) — per-line scramble
|
|
655
|
-
const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
|
|
656
|
-
if (toolCallItems.length > 0) {
|
|
657
|
-
container.addChild(new Spacer(1));
|
|
658
|
-
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("tool calls"), theme, config), 0, 0));
|
|
659
|
-
const toolPrefixLen = visibleLength("→ ");
|
|
660
|
-
const toolBudget = getTruncationBudget(toolPrefixLen);
|
|
661
|
-
for (let i = 0; i < toolCallItems.length; i++) {
|
|
662
|
-
const item = toolCallItems[i];
|
|
663
|
-
const lineText = applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
|
|
664
|
-
const plainText = stripAnsi(lineText);
|
|
665
|
-
const displayTool = truncateChars(plainText, toolBudget);
|
|
666
|
-
const initialScrambled = scrambleManager.updateText(id, `tool#${i}`, displayTool, now, isComplete).content;
|
|
667
|
-
container.addChild(new DynamicScrambleText(initialScrambled, () => {
|
|
668
|
-
const budget = getTruncationBudget(toolPrefixLen);
|
|
669
|
-
const freshPlain = stripAnsi(lineText);
|
|
670
|
-
const text = truncateChars(freshPlain, budget);
|
|
671
|
-
return scrambleManager.updateText(id, `tool#${i}`, text, Date.now(), isComplete).content;
|
|
672
|
-
}));
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
if (isComplete) {
|
|
676
|
-
scrambleManager.completeFlow(id);
|
|
677
|
-
}
|
|
678
|
-
return container;
|
|
679
|
-
}
|
|
680
|
-
function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, toolCallId, config, sharedContext) {
|
|
681
|
-
const id = toolCallId || "collapsed";
|
|
682
|
-
const now = Date.now();
|
|
683
|
-
const container = new Container();
|
|
684
|
-
const maxWidth = process.stdout.columns ?? 80;
|
|
685
|
-
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
686
|
-
const modelLabel = formatModelLabel(r.model);
|
|
687
|
-
const headerPrefixLen = visibleLength(typeName) + visibleLength(modelLabel ? ` ${modelLabel} · ` : " ");
|
|
688
|
-
const isComplete = isFlowStatusComplete(r);
|
|
689
|
-
// Build header stats: ctxLabel · t/s
|
|
690
|
-
const statsParts = [];
|
|
691
|
-
const ctxTokens = sharedContext?.totalTokens ?? r.usage.contextTokens;
|
|
692
|
-
if (r.maxContextTokens !== undefined || ctxTokens > 0) {
|
|
693
|
-
const ctxLabel = formatContextLabel(ctxTokens, r.maxContextTokens);
|
|
694
|
-
statsParts.push(ctxLabel);
|
|
695
|
-
}
|
|
696
|
-
let displayStats = statsParts.join(" · ");
|
|
697
|
-
if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
|
|
698
|
-
const tpsFormatted = formatTps(r.usage.smoothedTps);
|
|
699
|
-
statsParts.push(tpsFormatted);
|
|
700
|
-
displayStats = statsParts.join(" · ");
|
|
701
|
-
// Flash TPS value when it changes
|
|
702
|
-
const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
|
|
703
|
-
const scrambledTps = scrambleManager.updateTps(id, tpsNum, now, isComplete, true);
|
|
704
|
-
if (scrambledTps !== tpsNum) {
|
|
705
|
-
displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
const modelSegment = modelLabel ? ` ${modelLabel} · ` : " ";
|
|
709
|
-
const statsSegment = stripAnsi(displayStats);
|
|
710
|
-
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
711
|
-
const initialDot = flowStatusIcon(r, theme);
|
|
712
|
-
let header = `${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelSegment, theme, config)}${applyRole("stats", displayStats, theme, config)}`;
|
|
713
|
-
if (errorSegment)
|
|
714
|
-
header += ` ${theme.fg("error", errorSegment)}`;
|
|
715
|
-
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
716
|
-
const plainHeader = dotPlaceholder + typeName + modelSegment + statsSegment + errorSegment;
|
|
717
|
-
const headerSegments = [
|
|
718
|
-
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
|
|
719
|
-
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
720
|
-
{ text: modelSegment, style: (s) => applyRole("modelName", s, theme, config) },
|
|
721
|
-
{ text: statsSegment, style: (s) => applyRole("stats", s, theme, config) },
|
|
722
|
-
];
|
|
723
|
-
if (errorSegment) {
|
|
724
|
-
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
725
|
-
}
|
|
726
|
-
container.addChild(new DynamicScrambleText(header, () => {
|
|
727
|
-
const now = Date.now();
|
|
728
|
-
const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete, true);
|
|
729
|
-
return reconstructHeader(result.content, headerSegments);
|
|
730
|
-
}, true));
|
|
731
|
-
// aim: line — glitch on text change
|
|
732
|
-
if (r.aim && r.type !== "trace") {
|
|
733
|
-
const aimTree = "├─";
|
|
734
|
-
const aimLabel = ` aim ▸ `;
|
|
735
|
-
const aimPrefix = `${aimTree}${aimLabel}`;
|
|
736
|
-
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
737
|
-
const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
|
|
738
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(getContentRole("aimContent", displayAim), displayAim, theme, config)}`, () => {
|
|
739
|
-
const now = Date.now();
|
|
740
|
-
const freshAimLabel = ` aim ▸ `;
|
|
741
|
-
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
742
|
-
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
743
|
-
const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
744
|
-
const result = scrambleManager.updateAim(id, freshText, now, isComplete, true);
|
|
745
|
-
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(getContentRole("aimContent", freshText), result.content, theme, config)}`;
|
|
746
|
-
}, true));
|
|
747
|
-
}
|
|
748
|
-
// act: line (last tool call with count)
|
|
749
|
-
const lastTool = getLastToolCall(r.messages);
|
|
750
|
-
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
751
|
-
const isLite = config?.bodyVerbosity !== "full";
|
|
752
|
-
const actTree = isLite ? "└─" : "├─";
|
|
753
|
-
const actLabel = ` cmd ▸ `;
|
|
754
|
-
const prefixStub = `${actTree}${actLabel}`;
|
|
755
|
-
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
756
|
-
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
757
|
-
const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
|
|
758
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", initialActContent), initialActContent, theme, config)}`, () => {
|
|
759
|
-
const now = Date.now();
|
|
760
|
-
const actLabel = ` cmd ▸ `;
|
|
761
|
-
const actPrefix = `${actTree}${actLabel}`;
|
|
762
|
-
const freshBudget = getTruncationBudget(visibleLength(actPrefix));
|
|
763
|
-
const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
|
|
764
|
-
const actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
|
|
765
|
-
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", displayAct), actContent, theme, config)}`;
|
|
766
|
-
}, true));
|
|
767
|
-
// msg: line (last assistant text or streaming) — full mode only
|
|
768
|
-
if (!isLite) {
|
|
769
|
-
const msgPrefixStub = `└─ msg ▸ `;
|
|
770
|
-
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
771
|
-
let rawMsg;
|
|
772
|
-
let useError = false;
|
|
773
|
-
if (isFlowAwaiting(r)) {
|
|
774
|
-
rawMsg = "[awaiting...]";
|
|
775
|
-
}
|
|
776
|
-
else if (r.status === "skipped") {
|
|
777
|
-
rawMsg = "[skipped]";
|
|
778
|
-
}
|
|
779
|
-
else {
|
|
780
|
-
const liveMsgText = isFlowRunning(r) ? getLiveTextWithFallback(id) : undefined;
|
|
781
|
-
if (liveMsgText != null) {
|
|
782
|
-
rawMsg = stripAnsi(liveMsgText);
|
|
783
|
-
}
|
|
784
|
-
else if (isFlowRunning(r) && streamingText != null) {
|
|
785
|
-
rawMsg = stripAnsi(streamingText);
|
|
786
|
-
}
|
|
787
|
-
else if (r.structuredOutput?.summary) {
|
|
788
|
-
rawMsg = stripAnsi(r.structuredOutput.summary);
|
|
789
|
-
}
|
|
790
|
-
else if (flowOutput) {
|
|
791
|
-
rawMsg = stripAnsi(flowOutput);
|
|
792
|
-
}
|
|
793
|
-
else if (error && r.errorMessage) {
|
|
794
|
-
rawMsg = stripAnsi(r.errorMessage);
|
|
795
|
-
useError = true;
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
const summary = getFlowSummaryText(r);
|
|
799
|
-
rawMsg = stripAnsi(summary) || "[n/a]";
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (streamingText != null || getLiveTextWithFallback(id) != null);
|
|
803
|
-
const initialMsgContent = initialNeedsTail
|
|
804
|
-
? tailText(rawMsg, msgBudget)
|
|
805
|
-
: truncateChars(rawMsg, msgBudget);
|
|
806
|
-
const msgTree = "└─";
|
|
807
|
-
const msgLabel = ` msg ▸ `;
|
|
808
|
-
const initialMsgPrefix = `${msgTree}${msgLabel}`;
|
|
809
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", initialMsgContent, useError), initialMsgContent, theme, config)}`, () => {
|
|
810
|
-
const now = Date.now();
|
|
811
|
-
const msgLabel = ` msg ▸ `;
|
|
812
|
-
const msgPrefix = `${msgTree}${msgLabel}`;
|
|
813
|
-
let freshRawMsg;
|
|
814
|
-
let needsTail;
|
|
815
|
-
if (isFlowAwaiting(r)) {
|
|
816
|
-
freshRawMsg = "[awaiting...]";
|
|
817
|
-
needsTail = false;
|
|
818
|
-
}
|
|
819
|
-
else if (r.status === "skipped") {
|
|
820
|
-
freshRawMsg = "[skipped]";
|
|
821
|
-
needsTail = false;
|
|
822
|
-
}
|
|
823
|
-
else {
|
|
824
|
-
const isRunningNow = isFlowRunning(r);
|
|
825
|
-
freshRawMsg = (isRunningNow ? getLiveTextWithFallback(id) : undefined) ?? rawMsg;
|
|
826
|
-
needsTail = isRunningNow && (streamingText != null || getLiveTextWithFallback(id) != null);
|
|
827
|
-
}
|
|
828
|
-
const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
|
|
829
|
-
const result = scrambleManager.updateMsg(id, displayMsg, now, isComplete, undefined, true);
|
|
830
|
-
return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", freshRawMsg, useError), result.content, theme, config)}`;
|
|
831
|
-
}, true));
|
|
832
|
-
}
|
|
833
|
-
if (isComplete) {
|
|
834
|
-
scrambleManager.completeFlow(id);
|
|
835
|
-
}
|
|
836
|
-
return container;
|
|
837
|
-
}
|
|
838
168
|
// ---------------------------------------------------------------------------
|
|
839
169
|
// Multi-flow result
|
|
840
170
|
// ---------------------------------------------------------------------------
|
|
@@ -850,119 +180,12 @@ function renderMultiFlowResult(details, expanded, theme, toolCallId, config, sha
|
|
|
850
180
|
}
|
|
851
181
|
return renderMultiFlowCollapsed(results, theme, baseId, config, sharedContext);
|
|
852
182
|
}
|
|
853
|
-
function
|
|
854
|
-
|
|
855
|
-
const container = new Container();
|
|
856
|
-
if (sharedContext) {
|
|
857
|
-
container.addChild(new Text(applyRole("prefixLabel", "── shared context ──", theme, config), 0, 0));
|
|
858
|
-
container.addChild(new Text(applyRole("aimContent", sharedContext.preview, theme, config), 0, 0));
|
|
859
|
-
const statsParts = [];
|
|
860
|
-
statsParts.push(`${sharedContext.userMessageCount} user`);
|
|
861
|
-
statsParts.push(`${sharedContext.assistantMessageCount} assistant`);
|
|
862
|
-
statsParts.push(`${sharedContext.totalTokens} tokens`);
|
|
863
|
-
for (const [name, count] of Object.entries(sharedContext.toolCalls)) {
|
|
864
|
-
statsParts.push(`${count}× ${name}`);
|
|
865
|
-
}
|
|
866
|
-
container.addChild(new Text(applyRole("stats", statsParts.join(" · "), theme, config), 0, 0));
|
|
867
|
-
container.addChild(new Spacer(1));
|
|
868
|
-
}
|
|
869
|
-
// Summary: just show count, no icon
|
|
870
|
-
container.addChild(new Text(applyRole("flowName", `${results.length} flows`, theme, config), 0, 0));
|
|
871
|
-
for (let flowIdx = 0; flowIdx < results.length; flowIdx++) {
|
|
872
|
-
const r = results[flowIdx];
|
|
873
|
-
const flowId = `${baseId}#${flowIdx}`;
|
|
874
|
-
const isComplete = isFlowStatusComplete(r);
|
|
875
|
-
const displayItems = getFlowDisplayItems(r.messages);
|
|
876
|
-
const flowOutput = getFlowOutput(r.messages);
|
|
877
|
-
const typeName = formatFlowTypeName(r.type);
|
|
878
|
-
container.addChild(new Spacer(1));
|
|
879
|
-
// Per-flow header: ─── EXPLORER (no icon)
|
|
880
|
-
const headerStatic = applyRole("prefixLabel", sectionHeader(typeName), theme, config);
|
|
881
|
-
container.addChild(new DynamicScrambleText(headerStatic, () => {
|
|
882
|
-
const result = scrambleManager.updateText(flowId, 'header', typeName, Date.now(), isComplete, true);
|
|
883
|
-
return result.isAnimating ? applyRole("prefixLabel", result.content, theme, config) : headerStatic;
|
|
884
|
-
}));
|
|
885
|
-
// Stats: dashboard format
|
|
886
|
-
const flowStats = formatCompactStats(r.usage, r.model);
|
|
887
|
-
container.addChild(new DynamicScrambleText(applyRole("stats", flowStats, theme, config), () => {
|
|
888
|
-
const result = scrambleManager.updateText(flowId, 'stats', stripAnsi(flowStats), Date.now(), isComplete, true);
|
|
889
|
-
return result.isAnimating ? applyRole("stats", result.content, theme, config) : applyRole("stats", flowStats, theme, config);
|
|
890
|
-
}));
|
|
891
|
-
// Intent: just show text, no prefix (budget computed dynamically for resize recalculation)
|
|
892
|
-
const intentBudget = getTruncationBudget(0);
|
|
893
|
-
const displayIntent = truncateChars(r.intent, intentBudget);
|
|
894
|
-
container.addChild(new DynamicScrambleText(applyRole("aimContent", displayIntent, theme, config), () => {
|
|
895
|
-
const budget = getTruncationBudget(0);
|
|
896
|
-
const text = truncateChars(r.intent, budget);
|
|
897
|
-
const result = scrambleManager.updateText(flowId, 'intent', text, Date.now(), isComplete, true);
|
|
898
|
-
return result.isAnimating ? applyRole("aimContent", result.content, theme, config) : applyRole("aimContent", text, theme, config);
|
|
899
|
-
}));
|
|
900
|
-
if (r.acceptance) {
|
|
901
|
-
const acceptanceRaw = r.acceptance;
|
|
902
|
-
const acceptancePrefix = "Acceptance: ";
|
|
903
|
-
const acceptanceBudget = getTruncationBudget(visibleLength(acceptancePrefix));
|
|
904
|
-
const acceptanceText = truncateChars(acceptanceRaw, acceptanceBudget);
|
|
905
|
-
const acceptanceStatic = applyRole("aimContent", `${acceptancePrefix}${acceptanceText}`, theme, config);
|
|
906
|
-
container.addChild(new DynamicScrambleText(acceptanceStatic, () => {
|
|
907
|
-
const budget = getTruncationBudget(visibleLength(acceptancePrefix));
|
|
908
|
-
const text = truncateChars(acceptanceRaw, budget);
|
|
909
|
-
const result = scrambleManager.updateText(flowId, 'acceptance', text, Date.now(), isComplete, true);
|
|
910
|
-
return result.isAnimating ? applyRole("aimContent", `${acceptancePrefix}${result.content}`, theme, config) : applyRole("aimContent", `${acceptancePrefix}${text}`, theme, config);
|
|
911
|
-
}));
|
|
912
|
-
}
|
|
913
|
-
// Output: animate streaming text; show clean markdown when complete
|
|
914
|
-
if (isFlowAwaiting(r)) {
|
|
915
|
-
container.addChild(new Text(applyRole("placeholder", "[awaiting...]", theme, config), 0, 0));
|
|
916
|
-
}
|
|
917
|
-
else if (!isComplete && r.streamingText != null) {
|
|
918
|
-
const streamingRaw = r.streamingText;
|
|
919
|
-
const msgBudget = getTruncationBudget(0);
|
|
920
|
-
const displayMsg = tailText(stripAnsi(streamingRaw), msgBudget);
|
|
921
|
-
container.addChild(new DynamicScrambleText(applyRole("msgContent", displayMsg, theme, config), () => {
|
|
922
|
-
const budget = getTruncationBudget(0);
|
|
923
|
-
const freshStreamingText = getLiveTextWithFallback(flowId) ?? streamingRaw;
|
|
924
|
-
const text = tailText(stripAnsi(freshStreamingText), budget);
|
|
925
|
-
const result = scrambleManager.updateMsg(flowId, text, Date.now(), isComplete, undefined, true);
|
|
926
|
-
return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", text, theme, config);
|
|
927
|
-
}));
|
|
928
|
-
}
|
|
929
|
-
else if (flowOutput) {
|
|
930
|
-
container.addChild(new Spacer(1));
|
|
931
|
-
container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
|
|
932
|
-
}
|
|
933
|
-
// Tool traces in expanded view — per-line scramble
|
|
934
|
-
const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
|
|
935
|
-
if (toolCallItems.length > 0) {
|
|
936
|
-
container.addChild(new Spacer(1));
|
|
937
|
-
container.addChild(new Text(applyRole("prefixLabel", sectionHeader("tool calls"), theme, config), 0, 0));
|
|
938
|
-
const toolPrefixLen = visibleLength("→ ");
|
|
939
|
-
const toolBudget = getTruncationBudget(toolPrefixLen);
|
|
940
|
-
for (let i = 0; i < toolCallItems.length; i++) {
|
|
941
|
-
const item = toolCallItems[i];
|
|
942
|
-
const lineText = applyRole("prefixLabel", "→ ", theme, config) + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
|
|
943
|
-
const plainText = stripAnsi(lineText);
|
|
944
|
-
const displayTool = truncateChars(plainText, toolBudget);
|
|
945
|
-
const initialScrambled = scrambleManager.updateText(flowId, `tool#${i}`, displayTool, now, isComplete).content;
|
|
946
|
-
container.addChild(new DynamicScrambleText(initialScrambled, () => {
|
|
947
|
-
const budget = getTruncationBudget(toolPrefixLen);
|
|
948
|
-
const freshPlain = stripAnsi(lineText);
|
|
949
|
-
const text = truncateChars(freshPlain, budget);
|
|
950
|
-
return scrambleManager.updateText(flowId, `tool#${i}`, text, Date.now(), isComplete).content;
|
|
951
|
-
}));
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
if (isComplete) {
|
|
955
|
-
scrambleManager.completeFlow(flowId);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
// Total stats: dashboard format
|
|
959
|
-
const totalUsage = aggregateFlowUsage(results);
|
|
960
|
-
const totalModel = results[0]?.model;
|
|
961
|
-
const totalStats = formatCompactStats(totalUsage, totalModel);
|
|
962
|
-
container.addChild(new Spacer(1));
|
|
963
|
-
container.addChild(new Text(applyRole("stats", totalStats, theme, config), 0, 0));
|
|
964
|
-
return container;
|
|
183
|
+
function renderMultiFlowCollapsed(results, theme, baseId, config, sharedContext) {
|
|
184
|
+
return renderActivityPanel(results, theme, baseId, config, sharedContext);
|
|
965
185
|
}
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Activity panel
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
966
189
|
function renderActivityPanel(results, theme, baseId, config, sharedContext) {
|
|
967
190
|
const idPrefix = baseId || "panel";
|
|
968
191
|
const container = new Container();
|
|
@@ -1020,9 +243,65 @@ function renderStandaloneFlow(container, r, index, idPrefix, theme, now, config,
|
|
|
1020
243
|
// ---------------------------------------------------------------------------
|
|
1021
244
|
function renderGroup(container, group, results, idPrefix, theme, now, config, isLastRoot = false, sharedContext) {
|
|
1022
245
|
// ─── Group header line ───
|
|
1023
|
-
|
|
1024
|
-
const
|
|
1025
|
-
|
|
246
|
+
// Collect all children for aggregate status computation
|
|
247
|
+
const childIndices = [...group.buildIndices, group.auditIndex];
|
|
248
|
+
const children = childIndices.map((idx) => results[idx]);
|
|
249
|
+
// Compute aggregate status icon
|
|
250
|
+
const anyRunning = children.some((r) => isFlowRunning(r));
|
|
251
|
+
const anyAwaiting = children.some((r) => isFlowAwaiting(r));
|
|
252
|
+
const anyError = children.some((r) => isFlowError(r));
|
|
253
|
+
const allComplete = children.every((r) => isFlowStatusComplete(r));
|
|
254
|
+
const buildChildren = group.buildIndices.map((idx) => results[idx]);
|
|
255
|
+
const anyPass = buildChildren.some((r) => r.pingPongMeta?.finalVerdict === "pass");
|
|
256
|
+
let aggregateIcon;
|
|
257
|
+
if (anyRunning) {
|
|
258
|
+
aggregateIcon = theme.fg("warning", "↻");
|
|
259
|
+
}
|
|
260
|
+
else if (anyAwaiting) {
|
|
261
|
+
aggregateIcon = theme.fg("muted", "○");
|
|
262
|
+
}
|
|
263
|
+
else if (allComplete && anyPass) {
|
|
264
|
+
aggregateIcon = theme.fg("success", "✓");
|
|
265
|
+
}
|
|
266
|
+
else if (anyError) {
|
|
267
|
+
aggregateIcon = theme.fg("error", "✗");
|
|
268
|
+
}
|
|
269
|
+
else if (allComplete) {
|
|
270
|
+
aggregateIcon = theme.fg("muted", "⊘");
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
aggregateIcon = theme.fg("muted", "?");
|
|
274
|
+
}
|
|
275
|
+
// Compute cycle counter badge from build metadata
|
|
276
|
+
const maxCycles = Math.max(...buildChildren.map((r) => r.pingPongMeta?.cycles ?? 0));
|
|
277
|
+
const hasPingPong = buildChildren.some((r) => r.pingPongMeta !== undefined);
|
|
278
|
+
let badgeText = "";
|
|
279
|
+
if (hasPingPong && maxCycles > 0) {
|
|
280
|
+
const anyIncomplete = children.some((r) => !isFlowStatusComplete(r));
|
|
281
|
+
if (anyIncomplete) {
|
|
282
|
+
badgeText = `cycle ${maxCycles}`;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const finalVerdict = buildChildren.find((r) => r.pingPongMeta?.finalVerdict)?.pingPongMeta?.finalVerdict ?? "";
|
|
286
|
+
const cyclesLabel = maxCycles === 1 ? "1 cycle" : `${maxCycles} cycles`;
|
|
287
|
+
badgeText = finalVerdict ? `${cyclesLabel} · ${finalVerdict}` : cyclesLabel;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Build header parts
|
|
291
|
+
const treePart = isLastRoot ? "" : "├─ ";
|
|
292
|
+
const label = " audit-loop";
|
|
293
|
+
const badge = badgeText ? ` · ${badgeText}` : "";
|
|
294
|
+
// Compute trailing rule to pad to ~50 visible chars
|
|
295
|
+
const targetWidth = 50;
|
|
296
|
+
const visibleWidth = treePart.length + 1 + label.length + badge.length;
|
|
297
|
+
const ruleWidth = Math.max(0, targetWidth - visibleWidth);
|
|
298
|
+
const rule = "─".repeat(ruleWidth);
|
|
299
|
+
const headerLine = applyRole("treeChars", treePart, theme, config) +
|
|
300
|
+
aggregateIcon +
|
|
301
|
+
applyRole("groupHeader", label, theme, config) +
|
|
302
|
+
applyRole("groupHeader", badge, theme, config) +
|
|
303
|
+
applyRole("treeChars", rule, theme, config);
|
|
304
|
+
container.addChild(new Text(headerLine, 0, 0));
|
|
1026
305
|
// ─── Build children ───
|
|
1027
306
|
for (let b = 0; b < group.buildIndices.length; b++) {
|
|
1028
307
|
const buildIdx = group.buildIndices[b];
|
|
@@ -1052,185 +331,4 @@ function renderGroup(container, group, results, idPrefix, theme, now, config, is
|
|
|
1052
331
|
}
|
|
1053
332
|
// No extra spacer — renderActivityPanel handles uniform inter-item spacing
|
|
1054
333
|
}
|
|
1055
|
-
// ---------------------------------------------------------------------------
|
|
1056
|
-
// Shared flow rendering helpers
|
|
1057
|
-
// ---------------------------------------------------------------------------
|
|
1058
|
-
function renderFlowHeader(container, r, flowId, headerPrefix, theme, now, config, sharedContext) {
|
|
1059
|
-
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
1060
|
-
const modelLabel = formatModelLabel(r.model);
|
|
1061
|
-
const isComplete = isFlowStatusComplete(r);
|
|
1062
|
-
const flowComplete = isComplete;
|
|
1063
|
-
const error = isFlowError(r);
|
|
1064
|
-
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
1065
|
-
const initialDot = flowStatusIcon(r, theme);
|
|
1066
|
-
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
1067
|
-
let headerLine;
|
|
1068
|
-
let plainHeader;
|
|
1069
|
-
const headerSegments = [
|
|
1070
|
-
{ text: headerPrefix + " ", style: (s) => applyRole("treeChars", s, theme, config) },
|
|
1071
|
-
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), flowId) + " " },
|
|
1072
|
-
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
1073
|
-
];
|
|
1074
|
-
{
|
|
1075
|
-
// Standard flow: model + stats
|
|
1076
|
-
const statsParts = [];
|
|
1077
|
-
if (r.maxContextTokens !== undefined || r.usage.contextTokens > 0) {
|
|
1078
|
-
const ctxTokens = sharedContext?.totalTokens ?? r.usage.contextTokens;
|
|
1079
|
-
const ctxLabel = formatContextLabel(ctxTokens, r.maxContextTokens);
|
|
1080
|
-
statsParts.push(ctxLabel);
|
|
1081
|
-
}
|
|
1082
|
-
let displayStats = statsParts.join(" · ");
|
|
1083
|
-
if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
|
|
1084
|
-
const tpsFormatted = formatTps(r.usage.smoothedTps);
|
|
1085
|
-
statsParts.push(tpsFormatted);
|
|
1086
|
-
displayStats = statsParts.join(" · ");
|
|
1087
|
-
const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
|
|
1088
|
-
const scrambledTps = scrambleManager.updateTps(flowId, tpsNum, now, flowComplete, true);
|
|
1089
|
-
if (scrambledTps !== tpsNum) {
|
|
1090
|
-
displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
const modelSegment = modelLabel ? ` ${modelLabel}` : "";
|
|
1094
|
-
const statsSegment = ` · ${displayStats}`;
|
|
1095
|
-
const statsPlain = stripAnsi(statsSegment);
|
|
1096
|
-
headerLine = `${applyRole("treeChars", headerPrefix, theme, config)} ${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelSegment, theme, config)}${applyRole("stats", statsSegment, theme, config)}`;
|
|
1097
|
-
if (errorSegment) {
|
|
1098
|
-
headerLine += ` ${theme.fg("error", errorSegment)}`;
|
|
1099
|
-
}
|
|
1100
|
-
plainHeader = headerPrefix + " " + dotPlaceholder + typeName + modelSegment + statsPlain + errorSegment;
|
|
1101
|
-
headerSegments.push({ text: modelSegment, style: (s) => applyRole("modelName", s, theme, config) }, { text: statsPlain, style: (s) => applyRole("stats", s, theme, config) });
|
|
1102
|
-
}
|
|
1103
|
-
if (errorSegment) {
|
|
1104
|
-
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
1105
|
-
}
|
|
1106
|
-
container.addChild(new DynamicScrambleText(headerLine, () => {
|
|
1107
|
-
const now = Date.now();
|
|
1108
|
-
const result = scrambleManager.updateText(flowId, 'header', plainHeader, now, flowComplete, true);
|
|
1109
|
-
return reconstructHeader(result.content, headerSegments);
|
|
1110
|
-
}, true));
|
|
1111
|
-
}
|
|
1112
|
-
function renderFlowBody(container, r, flowId, indent, theme, now, config) {
|
|
1113
|
-
const isComplete = isFlowStatusComplete(r);
|
|
1114
|
-
const flowComplete = isComplete;
|
|
1115
|
-
// aim: line — glitch on text change
|
|
1116
|
-
if (r.aim && r.type !== "trace") {
|
|
1117
|
-
const aimTree = indent + "├─";
|
|
1118
|
-
const aimLabel = ` aim ▸ `;
|
|
1119
|
-
const aimPrefix = `${aimTree}${aimLabel}`;
|
|
1120
|
-
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
1121
|
-
const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
|
|
1122
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(getContentRole("aimContent", displayAim), displayAim, theme, config)}`, () => {
|
|
1123
|
-
const now = Date.now();
|
|
1124
|
-
const freshAimLabel = ` aim ▸ `;
|
|
1125
|
-
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
1126
|
-
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
1127
|
-
const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
1128
|
-
const result = scrambleManager.updateAim(flowId, freshText, now, flowComplete, true);
|
|
1129
|
-
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(getContentRole("aimContent", freshText), result.content, theme, config)}`;
|
|
1130
|
-
}, true));
|
|
1131
|
-
}
|
|
1132
|
-
// act: line (last tool call with count)
|
|
1133
|
-
const lastTool = getLastToolCall(r.messages);
|
|
1134
|
-
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
1135
|
-
const isLite = config?.bodyVerbosity !== "full";
|
|
1136
|
-
const actTree = isLite ? `${indent}└─` : `${indent}├─`;
|
|
1137
|
-
const actLabel = ` cmd ▸ `;
|
|
1138
|
-
const prefixStub = `${actTree}${actLabel}`;
|
|
1139
|
-
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
1140
|
-
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
1141
|
-
const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
|
|
1142
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", initialActContent), initialActContent, theme, config)}`, () => {
|
|
1143
|
-
const now = Date.now();
|
|
1144
|
-
const actLabel = ` cmd ▸ `;
|
|
1145
|
-
const actPrefix = `${actTree}${actLabel}`;
|
|
1146
|
-
const freshBudget = getTruncationBudget(visibleLength(actPrefix));
|
|
1147
|
-
const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
|
|
1148
|
-
const actContent = scrambleManager.updateAct(flowId, displayAct, now, flowComplete, true).content;
|
|
1149
|
-
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", displayAct), actContent, theme, config)}`;
|
|
1150
|
-
}, true));
|
|
1151
|
-
// msg: line (live streaming text or last assistant text) — full mode only
|
|
1152
|
-
if (!isLite) {
|
|
1153
|
-
const msgTree = `${indent}└─`;
|
|
1154
|
-
const msgLabel = ` msg ▸ `;
|
|
1155
|
-
const msgPrefixStub = `${msgTree}${msgLabel}`;
|
|
1156
|
-
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
1157
|
-
let rawMsg;
|
|
1158
|
-
let useError = false;
|
|
1159
|
-
if (isFlowAwaiting(r)) {
|
|
1160
|
-
rawMsg = "[awaiting...]";
|
|
1161
|
-
}
|
|
1162
|
-
else if (r.status === "skipped") {
|
|
1163
|
-
rawMsg = "[skipped]";
|
|
1164
|
-
}
|
|
1165
|
-
else if (isFlowStatusComplete(r) && !isFlowRunning(r)) {
|
|
1166
|
-
if (isFlowError(r) && r.errorMessage) {
|
|
1167
|
-
rawMsg = stripAnsi(r.errorMessage);
|
|
1168
|
-
useError = true;
|
|
1169
|
-
}
|
|
1170
|
-
else if ((r.pingPongMeta?.finalVerdict === "pass" || r.structuredOutput?.verdict === "pass") && r.type === "audit") {
|
|
1171
|
-
rawMsg = "[approved]";
|
|
1172
|
-
}
|
|
1173
|
-
else if (r.type === "audit" && !r.structuredOutput?.summary && !getFlowOutput(r.messages)) {
|
|
1174
|
-
rawMsg = "[finished]";
|
|
1175
|
-
}
|
|
1176
|
-
else {
|
|
1177
|
-
rawMsg = stripAnsi(r.structuredOutput?.summary ?? getFlowOutput(r.messages) ?? getFlowSummaryText(r)) || "[n/a]";
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
else {
|
|
1181
|
-
const liveMsgText = isFlowRunning(r) ? getLiveTextWithFallback(flowId) : undefined;
|
|
1182
|
-
if (liveMsgText != null) {
|
|
1183
|
-
rawMsg = stripAnsi(liveMsgText);
|
|
1184
|
-
}
|
|
1185
|
-
else if (isFlowRunning(r) && r.streamingText != null) {
|
|
1186
|
-
rawMsg = stripAnsi(r.streamingText);
|
|
1187
|
-
}
|
|
1188
|
-
else if (r.structuredOutput?.summary) {
|
|
1189
|
-
rawMsg = stripAnsi(r.structuredOutput.summary);
|
|
1190
|
-
}
|
|
1191
|
-
else {
|
|
1192
|
-
const flowOutput = getFlowOutput(r.messages);
|
|
1193
|
-
if (flowOutput) {
|
|
1194
|
-
rawMsg = stripAnsi(flowOutput);
|
|
1195
|
-
}
|
|
1196
|
-
else if (isFlowError(r) && r.errorMessage) {
|
|
1197
|
-
rawMsg = stripAnsi(r.errorMessage);
|
|
1198
|
-
useError = true;
|
|
1199
|
-
}
|
|
1200
|
-
else {
|
|
1201
|
-
const summary = getFlowSummaryText(r);
|
|
1202
|
-
rawMsg = stripAnsi(summary) || "[n/a]";
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
|
|
1207
|
-
const initialDisplayMsg = initialNeedsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
|
|
1208
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", initialDisplayMsg, useError), initialDisplayMsg, theme, config)}`, () => {
|
|
1209
|
-
const now = Date.now();
|
|
1210
|
-
const msgLabel = ` msg ▸ `;
|
|
1211
|
-
const msgPrefix = `${msgTree}${msgLabel}`;
|
|
1212
|
-
let freshRawMsg;
|
|
1213
|
-
let needsTail;
|
|
1214
|
-
if (isFlowAwaiting(r)) {
|
|
1215
|
-
freshRawMsg = "[awaiting...]";
|
|
1216
|
-
needsTail = false;
|
|
1217
|
-
}
|
|
1218
|
-
else if (r.status === "skipped") {
|
|
1219
|
-
freshRawMsg = "[skipped]";
|
|
1220
|
-
needsTail = false;
|
|
1221
|
-
}
|
|
1222
|
-
else {
|
|
1223
|
-
const isRunningNow = isFlowRunning(r);
|
|
1224
|
-
freshRawMsg = (isRunningNow ? getLiveTextWithFallback(flowId) : undefined) ?? rawMsg;
|
|
1225
|
-
needsTail = isRunningNow && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
|
|
1226
|
-
}
|
|
1227
|
-
const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
|
|
1228
|
-
const result = scrambleManager.updateMsg(flowId, displayMsg, now, flowComplete, undefined, true);
|
|
1229
|
-
return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", freshRawMsg, useError), result.content, theme, config)}`;
|
|
1230
|
-
}, true));
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
function renderMultiFlowCollapsed(results, theme, baseId, config, sharedContext) {
|
|
1234
|
-
return renderActivityPanel(results, theme, baseId, config, sharedContext);
|
|
1235
|
-
}
|
|
1236
334
|
//# sourceMappingURL=render.js.map
|