pi-agent-flow 2.0.1 → 2.0.5
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 +126 -489
- package/agents/audit.md +24 -15
- package/agents/build.md +4 -3
- package/agents/craft.md +4 -4
- package/agents/debug.md +5 -4
- package/agents/ideas.md +6 -5
- package/agents/scout.md +10 -8
- package/agents/trace.md +23 -0
- package/dist/batch/apply-patch.d.ts +60 -0
- package/dist/batch/apply-patch.d.ts.map +1 -0
- package/dist/batch/apply-patch.js +477 -0
- package/dist/batch/apply-patch.js.map +1 -0
- package/dist/batch/batch-bash.d.ts +10 -6
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +59 -11
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +38 -6
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/constants.js +26 -4
- package/dist/batch/constants.js.map +1 -1
- package/dist/batch/execute.d.ts +8 -2
- package/dist/batch/execute.d.ts.map +1 -1
- package/dist/batch/execute.js +222 -67
- package/dist/batch/execute.js.map +1 -1
- package/dist/batch/fuzzy-edit.d.ts +4 -1
- package/dist/batch/fuzzy-edit.d.ts.map +1 -1
- package/dist/batch/fuzzy-edit.js +7 -2
- package/dist/batch/fuzzy-edit.js.map +1 -1
- package/dist/batch/index.d.ts +10 -24
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +120 -120
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/render.d.ts +22 -5
- package/dist/batch/render.d.ts.map +1 -1
- package/dist/batch/render.js +353 -15
- package/dist/batch/render.js.map +1 -1
- package/dist/batch/summary.d.ts.map +1 -1
- package/dist/batch/summary.js +26 -4
- package/dist/batch/summary.js.map +1 -1
- package/dist/config/config.d.ts +5 -4
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +15 -5
- package/dist/config/config.js.map +1 -1
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +7 -1
- package/dist/config/models.js.map +1 -1
- package/dist/config/settings-resolver.d.ts +5 -4
- package/dist/config/settings-resolver.d.ts.map +1 -1
- package/dist/config/settings-resolver.js +50 -21
- package/dist/config/settings-resolver.js.map +1 -1
- package/dist/core2/snapshot.d.ts +21 -0
- package/dist/core2/snapshot.d.ts.map +1 -0
- package/dist/core2/snapshot.js +214 -0
- package/dist/core2/snapshot.js.map +1 -0
- package/dist/{core → flow}/agents.d.ts.map +1 -1
- package/dist/{core → flow}/agents.js +5 -2
- package/dist/{core → flow}/agents.js.map +1 -1
- package/dist/flow/auto-warp.d.ts +1 -1
- package/dist/flow/auto-warp.js +1 -1
- package/dist/flow/command.d.ts +1 -1
- package/dist/flow/command.d.ts.map +1 -1
- package/dist/flow/complexity.d.ts +20 -0
- package/dist/flow/complexity.d.ts.map +1 -0
- package/dist/flow/complexity.js +34 -0
- package/dist/flow/complexity.js.map +1 -0
- package/dist/flow/continuation.d.ts +1 -1
- package/dist/flow/continuation.d.ts.map +1 -1
- package/dist/flow/continuation.js +4 -3
- package/dist/flow/continuation.js.map +1 -1
- package/dist/{core → flow}/depth.d.ts +4 -4
- package/dist/{core → flow}/depth.d.ts.map +1 -1
- package/dist/{core → flow}/depth.js +5 -5
- package/dist/{core → flow}/depth.js.map +1 -1
- package/dist/{core → flow}/executor.d.ts +42 -22
- package/dist/flow/executor.d.ts.map +1 -0
- package/dist/flow/executor.js +727 -0
- package/dist/flow/executor.js.map +1 -0
- package/dist/flow/index.d.ts +4 -4
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +4 -4
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/loop-command.d.ts +1 -1
- package/dist/flow/loop-command.d.ts.map +1 -1
- package/dist/flow/loop-command.js +3 -0
- package/dist/flow/loop-command.js.map +1 -1
- package/dist/{core/flow.d.ts → flow/runner.d.ts} +20 -16
- package/dist/flow/runner.d.ts.map +1 -0
- package/dist/{core/flow.js → flow/runner.js} +105 -61
- package/dist/flow/runner.js.map +1 -0
- package/dist/{core → flow}/session-registry.d.ts.map +1 -1
- package/dist/{core → flow}/session-registry.js.map +1 -1
- package/dist/flow/settings-command.d.ts +3 -3
- package/dist/flow/settings-command.d.ts.map +1 -1
- package/dist/flow/settings-command.js +43 -22
- package/dist/flow/settings-command.js.map +1 -1
- package/dist/{core/delegation.d.ts → flow/transition.d.ts} +8 -8
- package/dist/{core/delegation.d.ts.map → flow/transition.d.ts.map} +1 -1
- package/dist/{core/delegation.js → flow/transition.js} +12 -12
- package/dist/{core/delegation.js.map → flow/transition.js.map} +1 -1
- package/dist/flow/types.d.ts +4 -0
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/flow/warp.d.ts +15 -0
- package/dist/flow/warp.d.ts.map +1 -0
- package/dist/flow/warp.js +207 -0
- package/dist/flow/warp.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +237 -89
- package/dist/index.js.map +1 -1
- package/dist/notify/notify.d.ts +1 -1
- package/dist/notify/notify.d.ts.map +1 -1
- package/dist/notify/notify.js +1 -1
- package/dist/snapshot/cli-args.d.ts +2 -2
- package/dist/snapshot/cli-args.d.ts.map +1 -1
- package/dist/snapshot/cli-args.js +21 -5
- package/dist/snapshot/cli-args.js.map +1 -1
- package/dist/snapshot/runner-events.d.ts +7 -2
- package/dist/snapshot/runner-events.d.ts.map +1 -1
- package/dist/snapshot/runner-events.js +137 -19
- package/dist/snapshot/runner-events.js.map +1 -1
- package/dist/snapshot/structured-output.d.ts +1 -1
- package/dist/snapshot/structured-output.d.ts.map +1 -1
- package/dist/snapshot/structured-output.js +20 -2
- package/dist/snapshot/structured-output.js.map +1 -1
- package/dist/steering/flow-prompt.d.ts +4 -4
- package/dist/steering/flow-prompt.d.ts.map +1 -1
- package/dist/steering/flow-prompt.js +18 -37
- package/dist/steering/flow-prompt.js.map +1 -1
- package/dist/steering/sliding-prompt.d.ts.map +1 -1
- package/dist/steering/sliding-prompt.js +8 -7
- package/dist/steering/sliding-prompt.js.map +1 -1
- package/dist/steering/tool-utils.d.ts +31 -8
- package/dist/steering/tool-utils.d.ts.map +1 -1
- package/dist/steering/tool-utils.js +63 -30
- package/dist/steering/tool-utils.js.map +1 -1
- package/dist/tools/ask-user.d.ts +2 -19
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +14 -38
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/timed-bash.d.ts +1 -1
- package/dist/tools/timed-bash.d.ts.map +1 -1
- package/dist/tools/timed-bash.js +11 -9
- package/dist/tools/timed-bash.js.map +1 -1
- package/dist/tools/trace.d.ts +34 -0
- package/dist/tools/trace.d.ts.map +1 -0
- package/dist/tools/trace.js +180 -0
- package/dist/tools/trace.js.map +1 -0
- package/dist/tools/web-ops.d.ts +85 -0
- package/dist/tools/web-ops.d.ts.map +1 -0
- package/dist/tools/{web-tool.js → web-ops.js} +51 -127
- package/dist/tools/web-ops.js.map +1 -0
- package/dist/tui/flow-colors.d.ts +1 -0
- package/dist/tui/flow-colors.d.ts.map +1 -1
- package/dist/tui/flow-colors.js +2 -2
- package/dist/tui/flow-colors.js.map +1 -1
- package/dist/tui/render-utils.d.ts.map +1 -1
- package/dist/tui/render-utils.js +2 -4
- package/dist/tui/render-utils.js.map +1 -1
- package/dist/tui/render.d.ts +41 -1
- package/dist/tui/render.d.ts.map +1 -1
- package/dist/tui/render.js +724 -189
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/scramble/algorithm.d.ts +4 -2
- package/dist/tui/scramble/algorithm.d.ts.map +1 -1
- package/dist/tui/scramble/algorithm.js +44 -12
- package/dist/tui/scramble/algorithm.js.map +1 -1
- package/dist/tui/scramble/constants.d.ts +3 -0
- package/dist/tui/scramble/constants.d.ts.map +1 -1
- package/dist/tui/scramble/constants.js +4 -1
- package/dist/tui/scramble/constants.js.map +1 -1
- package/dist/tui/scramble/index.d.ts +3 -2
- package/dist/tui/scramble/index.d.ts.map +1 -1
- package/dist/tui/scramble/index.js +2 -2
- package/dist/tui/scramble/index.js.map +1 -1
- package/dist/tui/scramble/manager.d.ts +2 -2
- package/dist/tui/scramble/manager.d.ts.map +1 -1
- package/dist/tui/scramble/manager.js +37 -20
- package/dist/tui/scramble/manager.js.map +1 -1
- package/dist/tui/scramble/utils.js +1 -1
- package/dist/tui/scramble/utils.js.map +1 -1
- package/dist/types/flow.d.ts +17 -1
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/flow.js +11 -2
- package/dist/types/flow.js.map +1 -1
- package/dist/types/output.d.ts +11 -36
- package/dist/types/output.d.ts.map +1 -1
- package/dist/types/output.js +1 -1
- package/dist/types/ui.d.ts +1 -1
- package/dist/types/ui.d.ts.map +1 -1
- package/package.json +10 -10
- package/dist/core/executor.d.ts.map +0 -1
- package/dist/core/executor.js +0 -378
- package/dist/core/executor.js.map +0 -1
- package/dist/core/flow.d.ts.map +0 -1
- package/dist/core/flow.js.map +0 -1
- package/dist/core/session-mode.d.ts +0 -11
- package/dist/core/session-mode.d.ts.map +0 -1
- package/dist/core/session-mode.js +0 -26
- package/dist/core/session-mode.js.map +0 -1
- package/dist/core/transitions.d.ts +0 -39
- package/dist/core/transitions.d.ts.map +0 -1
- package/dist/core/transitions.js +0 -59
- package/dist/core/transitions.js.map +0 -1
- package/dist/flow/perform-warp.d.ts +0 -28
- package/dist/flow/perform-warp.d.ts.map +0 -1
- package/dist/flow/perform-warp.js +0 -127
- package/dist/flow/perform-warp.js.map +0 -1
- package/dist/flow/warp-command.d.ts +0 -8
- package/dist/flow/warp-command.d.ts.map +0 -1
- package/dist/flow/warp-command.js +0 -144
- package/dist/flow/warp-command.js.map +0 -1
- package/dist/flow/warp-utils.d.ts +0 -11
- package/dist/flow/warp-utils.d.ts.map +0 -1
- package/dist/flow/warp-utils.js +0 -187
- package/dist/flow/warp-utils.js.map +0 -1
- package/dist/snapshot/index.d.ts +0 -2
- package/dist/snapshot/index.d.ts.map +0 -1
- package/dist/snapshot/index.js +0 -2
- package/dist/snapshot/index.js.map +0 -1
- package/dist/snapshot/reasoning-strip.d.ts +0 -22
- package/dist/snapshot/reasoning-strip.d.ts.map +0 -1
- package/dist/snapshot/reasoning-strip.js +0 -58
- package/dist/snapshot/reasoning-strip.js.map +0 -1
- package/dist/snapshot/snapshot.d.ts +0 -77
- package/dist/snapshot/snapshot.d.ts.map +0 -1
- package/dist/snapshot/snapshot.js +0 -1791
- package/dist/snapshot/snapshot.js.map +0 -1
- package/dist/tools/web-tool.d.ts +0 -46
- package/dist/tools/web-tool.d.ts.map +0 -1
- package/dist/tools/web-tool.js.map +0 -1
- /package/dist/{core → flow}/agents.d.ts +0 -0
- /package/dist/{core → flow}/session-registry.d.ts +0 -0
- /package/dist/{core → flow}/session-registry.js +0 -0
package/dist/tui/render.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* Expanded view adds raw tool call traces.
|
|
6
6
|
*/
|
|
7
7
|
import * as os from "node:os";
|
|
8
|
-
import { getMarkdownTheme } from "@
|
|
9
|
-
import { Container, Markdown, Spacer, Text
|
|
8
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
10
10
|
import { getFlowSummaryText } from "../snapshot/runner-events.js";
|
|
11
11
|
import { aggregateFlowUsage, getFlowOutput, isFlowError, isFlowSuccess, } from "../types/flow.js";
|
|
12
|
-
import { getFlowDisplayItems, getLastToolCall,
|
|
12
|
+
import { getFlowDisplayItems, getLastToolCall, } from "../types/ui.js";
|
|
13
13
|
import { formatBatchOpsSummary } from "../batch/summary.js";
|
|
14
|
-
import { scrambleManager, runScrambleTimer, DynamicScrambleText, getLiveText } from "./scramble/index.js";
|
|
14
|
+
import { scrambleManager, runScrambleTimer, DynamicScrambleText, getLiveText, hashNoise, THIN_BRAILLE_SPARK } from "./scramble/index.js";
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
// Anonymous flow-id counter — prevents scramble-state collisions when multiple
|
|
17
17
|
// flow widgets share the screen and toolCallId is absent from result/args.
|
|
@@ -31,7 +31,7 @@ function getLiveTextWithFallback(id) {
|
|
|
31
31
|
const fallbackId = id.includes("#") ? "collapsed" + id.slice(id.indexOf("#")) : "collapsed";
|
|
32
32
|
return getLiveText(fallbackId);
|
|
33
33
|
}
|
|
34
|
-
import { formatCompactStats, formatFlowTypeName, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi, formatModelLabel, formatContextLabel } from "./render-utils.js";
|
|
34
|
+
import { formatCompactStats, formatFlowTypeName, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi, formatModelLabel, formatContextLabel, formatTps, italic } from "./render-utils.js";
|
|
35
35
|
function shortenPath(p) {
|
|
36
36
|
const home = os.homedir();
|
|
37
37
|
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
@@ -104,10 +104,208 @@ function renderFlowReport(output, theme, config) {
|
|
|
104
104
|
const lines = splitOutputLines(output);
|
|
105
105
|
return lines.map((line) => applyRole("actContent", line, theme, config)).join("\n");
|
|
106
106
|
}
|
|
107
|
+
function getFlowStatus(r) {
|
|
108
|
+
return r.status ?? (r.exitCode === -1 ? "running" : r.exitCode === 0 ? "done" : "error");
|
|
109
|
+
}
|
|
110
|
+
function isFlowStatusComplete(r) {
|
|
111
|
+
const status = getFlowStatus(r);
|
|
112
|
+
return status === "done" || status === "error" || status === "skipped";
|
|
113
|
+
}
|
|
114
|
+
function isFlowRunning(r) {
|
|
115
|
+
const status = getFlowStatus(r);
|
|
116
|
+
return status === "running" || status === "pending";
|
|
117
|
+
}
|
|
118
|
+
function isFlowAwaiting(r) {
|
|
119
|
+
return getFlowStatus(r) === "awaiting";
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Detect audit-loop groups.
|
|
123
|
+
*
|
|
124
|
+
* When the executor stamps `auditLoopGroupId` on results, grouping is
|
|
125
|
+
* explicit and works regardless of array layout (no contiguity required).
|
|
126
|
+
*
|
|
127
|
+
* When no `auditLoopGroupId` is present (legacy / hand-crafted results),
|
|
128
|
+
* we fall back to contiguity-based detection: N contiguous builds with
|
|
129
|
+
* `pingPongMeta` followed immediately by an audit with
|
|
130
|
+
* `auditParentType === "build"`.
|
|
131
|
+
*/
|
|
132
|
+
export function detectGroups(results) {
|
|
133
|
+
const groups = [];
|
|
134
|
+
const rootIndices = [];
|
|
135
|
+
// Phase 1: explicit grouping by auditLoopGroupId
|
|
136
|
+
const groupMap = new Map();
|
|
137
|
+
const ungroupedIndices = [];
|
|
138
|
+
for (let i = 0; i < results.length; i++) {
|
|
139
|
+
const r = results[i];
|
|
140
|
+
if (r.auditLoopGroupId !== undefined) {
|
|
141
|
+
let g = groupMap.get(r.auditLoopGroupId);
|
|
142
|
+
if (!g) {
|
|
143
|
+
g = { buildIndices: [], auditIndex: -1 };
|
|
144
|
+
groupMap.set(r.auditLoopGroupId, g);
|
|
145
|
+
}
|
|
146
|
+
if (r.pingPongMeta) {
|
|
147
|
+
g.buildIndices.push(i);
|
|
148
|
+
}
|
|
149
|
+
else if (r.auditParentType === "build") {
|
|
150
|
+
g.auditIndex = i;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
ungroupedIndices.push(i);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const g of groupMap.values()) {
|
|
158
|
+
if (g.auditIndex !== -1) {
|
|
159
|
+
groups.push({ buildIndices: g.buildIndices, auditIndex: g.auditIndex });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// Orphaned builds with groupId but no audit capstone
|
|
163
|
+
rootIndices.push(...g.buildIndices);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Phase 2: legacy fallback on ungrouped results (contiguity-based)
|
|
167
|
+
let i = 0;
|
|
168
|
+
while (i < ungroupedIndices.length) {
|
|
169
|
+
const idx = ungroupedIndices[i];
|
|
170
|
+
const r = results[idx];
|
|
171
|
+
if (r.pingPongMeta) {
|
|
172
|
+
const buildIndices = [];
|
|
173
|
+
while (i < ungroupedIndices.length && results[ungroupedIndices[i]].pingPongMeta) {
|
|
174
|
+
buildIndices.push(ungroupedIndices[i]);
|
|
175
|
+
i++;
|
|
176
|
+
}
|
|
177
|
+
if (i < ungroupedIndices.length && results[ungroupedIndices[i]].auditParentType === "build") {
|
|
178
|
+
groups.push({ buildIndices, auditIndex: ungroupedIndices[i] });
|
|
179
|
+
i++;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
rootIndices.push(...buildIndices);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (r.auditParentType === "build" && i > 0 && results[ungroupedIndices[i - 1]].pingPongMeta) {
|
|
186
|
+
i++; // orphan audit already consumed
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
rootIndices.push(idx);
|
|
190
|
+
i++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { groups, rootIndices };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get the status icon dot for a result (● ○ ✗ ⊘).
|
|
197
|
+
*/
|
|
107
198
|
function flowStatusIcon(r, theme) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
199
|
+
const status = getFlowStatus(r);
|
|
200
|
+
switch (status) {
|
|
201
|
+
case "running":
|
|
202
|
+
case "pending":
|
|
203
|
+
return theme.fg("warning", "●");
|
|
204
|
+
case "awaiting":
|
|
205
|
+
return theme.fg("muted", "○");
|
|
206
|
+
case "done":
|
|
207
|
+
return theme.fg("success", "●");
|
|
208
|
+
case "error":
|
|
209
|
+
return theme.fg("error", "✗");
|
|
210
|
+
case "skipped":
|
|
211
|
+
return theme.fg("muted", "⊘");
|
|
212
|
+
default:
|
|
213
|
+
return theme.fg("muted", "?");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function hashStrToSeed(s) {
|
|
217
|
+
let h = 2166136261;
|
|
218
|
+
for (let i = 0; i < s.length; i++) {
|
|
219
|
+
h ^= s.charCodeAt(i);
|
|
220
|
+
h = Math.imul(h, 16777619);
|
|
221
|
+
}
|
|
222
|
+
return h >>> 0;
|
|
223
|
+
}
|
|
224
|
+
function getScintillatingStatusDot(r, theme, now, flowId) {
|
|
225
|
+
const status = getFlowStatus(r);
|
|
226
|
+
switch (status) {
|
|
227
|
+
case "running":
|
|
228
|
+
case "pending": {
|
|
229
|
+
const isPending = status === "pending";
|
|
230
|
+
const seed = hashStrToSeed(flowId || r.type);
|
|
231
|
+
const bucketSize = isPending ? 7000 : 5000;
|
|
232
|
+
const bucket = Math.floor(now / bucketSize);
|
|
233
|
+
const t = now % bucketSize;
|
|
234
|
+
const burstCount = isPending
|
|
235
|
+
? 1 + Math.floor(hashNoise(seed, bucket, 0, 0x5a4f) * 2) // 1-2
|
|
236
|
+
: 2 + Math.floor(hashNoise(seed, bucket, 0, 0x5a4f) * 2); // 2-3
|
|
237
|
+
let cursor = 50;
|
|
238
|
+
for (let b = 0; b < burstCount; b++) {
|
|
239
|
+
const gap = isPending
|
|
240
|
+
? 800 + Math.floor(hashNoise(seed, bucket, b * 4, 0xb8a0) * 1400) // 800-2200ms
|
|
241
|
+
: 500 + Math.floor(hashNoise(seed, bucket, b * 4, 0xb8a0) * 1300); // 500-1800ms
|
|
242
|
+
cursor += gap;
|
|
243
|
+
const duration = isPending
|
|
244
|
+
? 80 + Math.floor(hashNoise(seed, bucket, b * 4 + 1, 0xc0de) * 170) // 80-250ms
|
|
245
|
+
: 100 + Math.floor(hashNoise(seed, bucket, b * 4 + 1, 0xc0de) * 250); // 100-350ms
|
|
246
|
+
const burstStart = cursor;
|
|
247
|
+
const burstEnd = cursor + duration;
|
|
248
|
+
cursor = burstEnd;
|
|
249
|
+
if (t >= burstStart && t < burstEnd) {
|
|
250
|
+
const tInBurst = t - burstStart;
|
|
251
|
+
const tick = 12 + Math.floor(hashNoise(seed, bucket, b * 4 + 3, 0xd1a0) * 10); // 12-22ms per stutter step
|
|
252
|
+
// Vary stutter depth: 3-tick ○●○ or 5-tick ○●○●○ per burst
|
|
253
|
+
const rawStutterTicks = hashNoise(seed, bucket, b * 4 + 2, 0xe7a1) > 0.5 ? 5 : 3;
|
|
254
|
+
const stutterLen5 = tick * 5;
|
|
255
|
+
const onRunMax5 = duration - stutterLen5 - 5;
|
|
256
|
+
const stutterTicks = (rawStutterTicks === 5 && onRunMax5 >= tick) ? 5 : 3;
|
|
257
|
+
const stutterLen = tick * stutterTicks;
|
|
258
|
+
const onRunMax = duration - stutterLen - 5;
|
|
259
|
+
const onRun = Math.max(tick, Math.min(Math.floor(duration * (0.35 + hashNoise(seed, bucket, b * 4 + 2, 0xf1c0) * 0.3)), onRunMax));
|
|
260
|
+
const cycleLen = onRun + stutterLen;
|
|
261
|
+
const phaseInCycle = tInBurst % cycleLen;
|
|
262
|
+
const cycleIdx = Math.floor(tInBurst / cycleLen);
|
|
263
|
+
// Helper: dip ○ with occasional sparkle
|
|
264
|
+
const dipDot = (dipIndex) => {
|
|
265
|
+
if (hashNoise(seed, bucket, cycleIdx + dipIndex * 100, 0x5ab0) < 0.05) {
|
|
266
|
+
const sparkIdx = Math.floor(hashNoise(seed, bucket, cycleIdx + dipIndex * 100, 0x5b1) * THIN_BRAILLE_SPARK.length);
|
|
267
|
+
return theme.fg("muted", THIN_BRAILLE_SPARK[sparkIdx]);
|
|
268
|
+
}
|
|
269
|
+
return theme.fg("muted", "○");
|
|
270
|
+
};
|
|
271
|
+
if (phaseInCycle < onRun) {
|
|
272
|
+
// Sustained bright ●
|
|
273
|
+
return theme.fg("warning", "●");
|
|
274
|
+
}
|
|
275
|
+
else if (phaseInCycle < onRun + tick) {
|
|
276
|
+
return dipDot(0); // ○ dip 1
|
|
277
|
+
}
|
|
278
|
+
else if (phaseInCycle < onRun + tick * 2) {
|
|
279
|
+
return theme.fg("warning", "●"); // ● flash 1
|
|
280
|
+
}
|
|
281
|
+
else if (phaseInCycle < onRun + tick * 3) {
|
|
282
|
+
return dipDot(1); // ○ dip 2
|
|
283
|
+
}
|
|
284
|
+
else if (stutterTicks >= 5 && phaseInCycle < onRun + tick * 4) {
|
|
285
|
+
return theme.fg("warning", "●"); // ● flash 2 (5-tick only)
|
|
286
|
+
}
|
|
287
|
+
else if (stutterTicks >= 5 && phaseInCycle < onRun + tick * 5) {
|
|
288
|
+
return dipDot(2); // ○ dip 3 (5-tick only)
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Fallback: shouldn't reach if scheduling is correct
|
|
292
|
+
return theme.fg("warning", "●");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return theme.fg("warning", "●");
|
|
297
|
+
}
|
|
298
|
+
case "awaiting":
|
|
299
|
+
return theme.fg("muted", "○");
|
|
300
|
+
case "done":
|
|
301
|
+
return theme.fg("success", "●");
|
|
302
|
+
case "error":
|
|
303
|
+
return theme.fg("error", "✗");
|
|
304
|
+
case "skipped":
|
|
305
|
+
return theme.fg("muted", "⊘");
|
|
306
|
+
default:
|
|
307
|
+
return theme.fg("muted", "?");
|
|
308
|
+
}
|
|
111
309
|
}
|
|
112
310
|
/** Center a label in a fixed-width header using em-dashes. Total width = 20. */
|
|
113
311
|
function sectionHeader(label) {
|
|
@@ -118,6 +316,24 @@ function sectionHeader(label) {
|
|
|
118
316
|
const right = "─".repeat(Math.ceil(side));
|
|
119
317
|
return `${left} ${label} ${right}`;
|
|
120
318
|
}
|
|
319
|
+
/** Reconstruct multi-segment ANSI styles on a flat string by splitting at
|
|
320
|
+
* original segment boundaries and re-applying each segment's style function.
|
|
321
|
+
*/
|
|
322
|
+
export function reconstructHeader(content, segments) {
|
|
323
|
+
let offset = 0;
|
|
324
|
+
const parts = [];
|
|
325
|
+
for (const seg of segments) {
|
|
326
|
+
const len = seg.text.length;
|
|
327
|
+
if (offset >= content.length)
|
|
328
|
+
break;
|
|
329
|
+
parts.push(seg.style(content.slice(offset, offset + len)));
|
|
330
|
+
offset += len;
|
|
331
|
+
}
|
|
332
|
+
if (offset < content.length) {
|
|
333
|
+
parts.push(content.slice(offset));
|
|
334
|
+
}
|
|
335
|
+
return parts.join("");
|
|
336
|
+
}
|
|
121
337
|
// ---------------------------------------------------------------------------
|
|
122
338
|
// renderFlowCall — shown while the flow is being invoked
|
|
123
339
|
// ---------------------------------------------------------------------------
|
|
@@ -158,13 +374,13 @@ export function renderFlowResult(result, expanded, theme, args, config) {
|
|
|
158
374
|
let resolvedToolCallId;
|
|
159
375
|
if (args?.state) {
|
|
160
376
|
const s = args.state;
|
|
161
|
-
resolvedToolCallId = s.
|
|
377
|
+
resolvedToolCallId = s.__widgetId;
|
|
162
378
|
if (!resolvedToolCallId) {
|
|
163
379
|
resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
|
|
164
380
|
if (!resolvedToolCallId) {
|
|
165
381
|
resolvedToolCallId = getAnonymousFlowId();
|
|
166
382
|
}
|
|
167
|
-
s.
|
|
383
|
+
s.__widgetId = resolvedToolCallId;
|
|
168
384
|
}
|
|
169
385
|
}
|
|
170
386
|
else {
|
|
@@ -257,6 +473,140 @@ export function renderFlowResult(result, expanded, theme, args, config) {
|
|
|
257
473
|
return container;
|
|
258
474
|
}
|
|
259
475
|
// ---------------------------------------------------------------------------
|
|
476
|
+
// Trace rendering — simplified, no model, inline stats
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
export function renderTraceCall(_args, _theme, _config) {
|
|
479
|
+
// Trace call frame is invisible — the result frame shows 'trace <aim>'.
|
|
480
|
+
// Returning an empty Container avoids a duplicate 'trace' line.
|
|
481
|
+
return new Container();
|
|
482
|
+
}
|
|
483
|
+
export function renderTraceResult(result, expanded, theme, args, config) {
|
|
484
|
+
const details = result.details;
|
|
485
|
+
const streamingText = result.content?.[0]?.type === "text" ? result.content[0].text : undefined;
|
|
486
|
+
// Resolve id (same pattern as renderFlowResult)
|
|
487
|
+
let resolvedToolCallId;
|
|
488
|
+
if (args?.state) {
|
|
489
|
+
const s = args.state;
|
|
490
|
+
resolvedToolCallId = s.__widgetId;
|
|
491
|
+
if (!resolvedToolCallId) {
|
|
492
|
+
resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
|
|
493
|
+
if (!resolvedToolCallId) {
|
|
494
|
+
resolvedToolCallId = getAnonymousFlowId();
|
|
495
|
+
}
|
|
496
|
+
s.__widgetId = resolvedToolCallId;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
|
|
501
|
+
}
|
|
502
|
+
const id = resolvedToolCallId || "trace";
|
|
503
|
+
const now = Date.now();
|
|
504
|
+
let container = new Container();
|
|
505
|
+
// Get the SingleResult
|
|
506
|
+
let r;
|
|
507
|
+
if (details?.results && details.results.length > 0) {
|
|
508
|
+
r = details.results[0];
|
|
509
|
+
}
|
|
510
|
+
const isComplete = r ? isFlowStatusComplete(r) : false;
|
|
511
|
+
// Header line: ● trace · <aim> · <stats>
|
|
512
|
+
const typeName = formatFlowTypeName("trace");
|
|
513
|
+
const aimText = r?.aim || r?.intent || streamingText || "trace";
|
|
514
|
+
const initialDot = r ? flowStatusIcon(r, theme) : theme.fg("success", "●");
|
|
515
|
+
const dotPlaceholder = stripAnsi(initialDot) + " ";
|
|
516
|
+
const statsParts = [];
|
|
517
|
+
if (r) {
|
|
518
|
+
if (r.maxContextTokens !== undefined || r.usage.contextTokens > 0) {
|
|
519
|
+
statsParts.push(formatContextLabel(r.usage.contextTokens, r.maxContextTokens));
|
|
520
|
+
}
|
|
521
|
+
statsParts.push(formatTps(r.usage.smoothedTps));
|
|
522
|
+
}
|
|
523
|
+
const displayStats = statsParts.length > 0 ? " · " + statsParts.join(" · ") : "";
|
|
524
|
+
const statsPlain = stripAnsi(displayStats);
|
|
525
|
+
const headerPlain = `${dotPlaceholder}${typeName}${statsPlain}`;
|
|
526
|
+
const headerSegments = [
|
|
527
|
+
{ text: dotPlaceholder, style: (_s) => (r ? getScintillatingStatusDot(r, theme, Date.now(), id) : initialDot) + " " },
|
|
528
|
+
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
529
|
+
];
|
|
530
|
+
if (statsPlain) {
|
|
531
|
+
headerSegments.push({ text: displayStats, style: (s) => applyRole("stats", s, theme, config) });
|
|
532
|
+
}
|
|
533
|
+
container.addChild(new DynamicScrambleText(`${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("stats", displayStats, theme, config)}`, () => {
|
|
534
|
+
const now2 = Date.now();
|
|
535
|
+
const result2 = scrambleManager.updateText(id, "header", headerPlain, now2, isComplete, true);
|
|
536
|
+
return reconstructHeader(result2.content, headerSegments);
|
|
537
|
+
}, true));
|
|
538
|
+
// Cmd line: └─ cmd ▸ <last tool call>
|
|
539
|
+
const actTree = "└─";
|
|
540
|
+
const actLabel = ` cmd ▸ `;
|
|
541
|
+
if (r?.messages && r.messages.length > 0) {
|
|
542
|
+
const lastTool = getLastToolCall(r.messages);
|
|
543
|
+
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
544
|
+
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
545
|
+
const actInitial = `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", italic(actFullText), theme, config)}`;
|
|
546
|
+
container.addChild(new DynamicScrambleText(actInitial, () => {
|
|
547
|
+
const now2 = Date.now();
|
|
548
|
+
const freshAct = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
549
|
+
const freshPlain = stripAnsi(lowerFirstWord(freshAct));
|
|
550
|
+
const result2 = scrambleManager.updateAct(id, freshPlain, now2, isComplete, true);
|
|
551
|
+
const content = result2.content;
|
|
552
|
+
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", italic(content), theme, config)}`;
|
|
553
|
+
}, true));
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
// No messages yet — show awaiting
|
|
557
|
+
const actInitial = `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("prefixLabel", "[awaiting...]", theme, config)}`;
|
|
558
|
+
container.addChild(new DynamicScrambleText(actInitial, () => {
|
|
559
|
+
const now2 = Date.now();
|
|
560
|
+
const plain = "[awaiting...]";
|
|
561
|
+
const result2 = scrambleManager.updateAct(id, plain, now2, isComplete, true);
|
|
562
|
+
const content = result2.content;
|
|
563
|
+
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole((r && isFlowAwaiting(r)) ? "prefixLabel" : "actContent", italic(content), theme, config)}`;
|
|
564
|
+
}, true));
|
|
565
|
+
}
|
|
566
|
+
// Expanded view: add full output
|
|
567
|
+
if (expanded) {
|
|
568
|
+
const flowOutput = streamingText;
|
|
569
|
+
if (flowOutput) {
|
|
570
|
+
container.addChild(new Spacer(1));
|
|
571
|
+
container.addChild(new Markdown(flowOutput, 0, 0, getMarkdownTheme()));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// In-place mutation pattern (same as renderFlowResult)
|
|
575
|
+
if (args?.state) {
|
|
576
|
+
const s = args.state;
|
|
577
|
+
if (!s.__rootContainer) {
|
|
578
|
+
if (container instanceof Container) {
|
|
579
|
+
s.__rootContainer = container;
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
const root = new Container();
|
|
583
|
+
root.addChild(container);
|
|
584
|
+
s.__rootContainer = root;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else if (container !== s.__rootContainer) {
|
|
588
|
+
const root = s.__rootContainer;
|
|
589
|
+
root.clear();
|
|
590
|
+
if (container instanceof Container) {
|
|
591
|
+
const children = [...container.children];
|
|
592
|
+
for (const child of children) {
|
|
593
|
+
root.addChild(child);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
root.addChild(container);
|
|
598
|
+
}
|
|
599
|
+
root.invalidate();
|
|
600
|
+
container = root;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (isComplete) {
|
|
604
|
+
scrambleManager.completeFlow(id);
|
|
605
|
+
}
|
|
606
|
+
runScrambleTimer(args, id);
|
|
607
|
+
return container;
|
|
608
|
+
}
|
|
609
|
+
// ---------------------------------------------------------------------------
|
|
260
610
|
// Single flow result
|
|
261
611
|
// ---------------------------------------------------------------------------
|
|
262
612
|
export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCallId, config) {
|
|
@@ -266,7 +616,7 @@ export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCa
|
|
|
266
616
|
const displayItems = getFlowDisplayItems(r.messages);
|
|
267
617
|
const flowOutput = getFlowOutput(r.messages);
|
|
268
618
|
const now = Date.now();
|
|
269
|
-
const isComplete = r
|
|
619
|
+
const isComplete = isFlowStatusComplete(r);
|
|
270
620
|
if (expanded) {
|
|
271
621
|
return renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText, config);
|
|
272
622
|
}
|
|
@@ -275,15 +625,25 @@ export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCa
|
|
|
275
625
|
function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText, config) {
|
|
276
626
|
const mdTheme = getMarkdownTheme();
|
|
277
627
|
const container = new Container();
|
|
278
|
-
// Header: uppercase type name with dots, no icon, no source
|
|
279
628
|
const typeName = formatFlowTypeName(r.type);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
629
|
+
const initialDot = flowStatusIcon(r, theme);
|
|
630
|
+
let header = `${initialDot} ${applyRole("flowName", typeName, theme, config)}`;
|
|
631
|
+
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
632
|
+
if (errorSegment)
|
|
633
|
+
header += ` ${theme.fg("error", errorSegment)}`;
|
|
634
|
+
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
635
|
+
const plainHeader = dotPlaceholder + typeName + errorSegment;
|
|
636
|
+
const headerSegments = [
|
|
637
|
+
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
|
|
638
|
+
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
639
|
+
];
|
|
640
|
+
if (errorSegment) {
|
|
641
|
+
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
642
|
+
}
|
|
284
643
|
container.addChild(new DynamicScrambleText(header, () => {
|
|
285
|
-
const
|
|
286
|
-
|
|
644
|
+
const now = Date.now();
|
|
645
|
+
const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete);
|
|
646
|
+
return reconstructHeader(result.content, headerSegments);
|
|
287
647
|
}));
|
|
288
648
|
if (error && r.errorMessage) {
|
|
289
649
|
container.addChild(new Text(scrambleManager.renderStatic(theme.fg("error", `Error: ${r.errorMessage}`)), 0, 0));
|
|
@@ -374,7 +734,10 @@ function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id,
|
|
|
374
734
|
container.addChild(new Spacer(1));
|
|
375
735
|
}
|
|
376
736
|
// Output: animate streaming text; show clean markdown when complete
|
|
377
|
-
if (
|
|
737
|
+
if (isFlowAwaiting(r)) {
|
|
738
|
+
container.addChild(new Text(applyRole("prefixLabel", "[awaiting...]", theme, config), 0, 0));
|
|
739
|
+
}
|
|
740
|
+
else if (!isComplete && streamingText != null) {
|
|
378
741
|
const msgBudget = getTruncationBudget(0);
|
|
379
742
|
const displayMsg = tailText(stripAnsi(streamingText), msgBudget);
|
|
380
743
|
container.addChild(new DynamicScrambleText(displayMsg, () => {
|
|
@@ -427,36 +790,47 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
|
|
|
427
790
|
const maxWidth = process.stdout.columns ?? 80;
|
|
428
791
|
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
429
792
|
const modelLabel = formatModelLabel(r.model);
|
|
430
|
-
const headerPrefixLen = visibleLength(typeName) + visibleLength(modelLabel ? `
|
|
431
|
-
const isComplete = r
|
|
793
|
+
const headerPrefixLen = visibleLength(typeName) + visibleLength(modelLabel ? ` ${modelLabel} · ` : " ");
|
|
794
|
+
const isComplete = isFlowStatusComplete(r);
|
|
432
795
|
// Build header stats: ctxLabel · t/s
|
|
433
796
|
const statsParts = [];
|
|
434
797
|
if (r.maxContextTokens !== undefined || r.usage.contextTokens > 0) {
|
|
435
798
|
const ctxLabel = formatContextLabel(r.usage.contextTokens, r.maxContextTokens);
|
|
436
799
|
statsParts.push(ctxLabel);
|
|
437
800
|
}
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
if (tpsDisplay)
|
|
441
|
-
statsParts.push(`${tpsDisplay} t/s`);
|
|
442
|
-
else
|
|
443
|
-
statsParts.push("---- t/s");
|
|
801
|
+
const tpsFormatted = formatTps(r.usage.smoothedTps);
|
|
802
|
+
statsParts.push(tpsFormatted);
|
|
444
803
|
let displayStats = statsParts.join(" · ");
|
|
445
804
|
// Flash TPS value when it changes
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
805
|
+
const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
|
|
806
|
+
if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
|
|
807
|
+
const scrambledTps = scrambleManager.updateTps(id, tpsNum, now, isComplete, true);
|
|
808
|
+
if (scrambledTps !== tpsNum) {
|
|
809
|
+
displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
|
|
450
810
|
}
|
|
451
811
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
812
|
+
const modelSegment = modelLabel ? ` ${modelLabel} · ` : " ";
|
|
813
|
+
const statsSegment = stripAnsi(displayStats);
|
|
814
|
+
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
815
|
+
const initialDot = flowStatusIcon(r, theme);
|
|
816
|
+
let header = `${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelSegment, theme, config)}${applyRole("stats", displayStats, theme, config)}`;
|
|
817
|
+
if (errorSegment)
|
|
818
|
+
header += ` ${theme.fg("error", errorSegment)}`;
|
|
819
|
+
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
820
|
+
const plainHeader = dotPlaceholder + typeName + modelSegment + statsSegment + errorSegment;
|
|
821
|
+
const headerSegments = [
|
|
822
|
+
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
|
|
823
|
+
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
824
|
+
{ text: modelSegment, style: (s) => applyRole("modelName", s, theme, config) },
|
|
825
|
+
{ text: statsSegment, style: (s) => applyRole("stats", s, theme, config) },
|
|
826
|
+
];
|
|
827
|
+
if (errorSegment) {
|
|
828
|
+
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
829
|
+
}
|
|
457
830
|
container.addChild(new DynamicScrambleText(header, () => {
|
|
458
|
-
const
|
|
459
|
-
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete, true);
|
|
833
|
+
return reconstructHeader(result.content, headerSegments);
|
|
460
834
|
}, true));
|
|
461
835
|
// aim: line — glitch on text change
|
|
462
836
|
if (r.aim) {
|
|
@@ -464,77 +838,102 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
|
|
|
464
838
|
const aimLabel = ` aim ▸ `;
|
|
465
839
|
const aimPrefix = `${aimTree}${aimLabel}`;
|
|
466
840
|
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
467
|
-
const displayAim = truncateChars(lowerFirstWord(r.aim), budget);
|
|
468
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole("aimContent", displayAim, theme, config)}`, () => {
|
|
841
|
+
const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
|
|
842
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(displayAim), theme, config)}`, () => {
|
|
469
843
|
const now = Date.now();
|
|
470
844
|
const freshAimLabel = ` aim ▸ `;
|
|
471
845
|
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
472
846
|
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
473
|
-
const freshText = truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
847
|
+
const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
474
848
|
const result = scrambleManager.updateAim(id, freshText, now, isComplete, true);
|
|
475
|
-
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole("aimContent", result.content, theme, config)}`;
|
|
849
|
+
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(result.content), theme, config)}`;
|
|
476
850
|
}, true));
|
|
477
851
|
}
|
|
478
852
|
// act: line (last tool call with count)
|
|
479
853
|
const lastTool = getLastToolCall(r.messages);
|
|
480
854
|
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
481
|
-
const
|
|
855
|
+
const isLite = config?.bodyVerbosity !== "full";
|
|
856
|
+
const actTree = isLite ? "└─" : "├─";
|
|
482
857
|
const actLabel = ` cmd ▸ `;
|
|
483
858
|
const prefixStub = `${actTree}${actLabel}`;
|
|
484
859
|
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
485
860
|
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
486
|
-
const initialActContent = actFullText.length > budget ? tailText(actFullText, budget) : actFullText;
|
|
487
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", initialActContent, theme, config)}`, () => {
|
|
861
|
+
const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
|
|
862
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(initialActContent), theme, config)}`, () => {
|
|
488
863
|
const now = Date.now();
|
|
489
|
-
const displayAct = tailText(actFullText, budget);
|
|
490
|
-
const actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
|
|
491
864
|
const actLabel = ` cmd ▸ `;
|
|
492
865
|
const actPrefix = `${actTree}${actLabel}`;
|
|
493
|
-
|
|
866
|
+
const freshBudget = getTruncationBudget(visibleLength(actPrefix));
|
|
867
|
+
const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
|
|
868
|
+
const actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
|
|
869
|
+
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(actContent), theme, config)}`;
|
|
494
870
|
}, true));
|
|
495
|
-
// msg: line (last assistant text or streaming)
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
871
|
+
// msg: line (last assistant text or streaming) — full mode only
|
|
872
|
+
if (!isLite) {
|
|
873
|
+
const msgPrefixStub = `└─ msg ▸ `;
|
|
874
|
+
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
875
|
+
let rawMsg;
|
|
876
|
+
let useError = false;
|
|
877
|
+
if (isFlowAwaiting(r)) {
|
|
878
|
+
rawMsg = "[awaiting...]";
|
|
879
|
+
}
|
|
880
|
+
else if (r.status === "skipped") {
|
|
881
|
+
rawMsg = "[skipped]";
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
const liveMsgText = isFlowRunning(r) ? getLiveTextWithFallback(id) : undefined;
|
|
885
|
+
if (liveMsgText != null) {
|
|
886
|
+
rawMsg = stripAnsi(liveMsgText);
|
|
887
|
+
}
|
|
888
|
+
else if (isFlowRunning(r) && streamingText != null) {
|
|
889
|
+
rawMsg = stripAnsi(streamingText);
|
|
890
|
+
}
|
|
891
|
+
else if (r.structuredOutput?.summary) {
|
|
892
|
+
rawMsg = stripAnsi(r.structuredOutput.summary);
|
|
893
|
+
}
|
|
894
|
+
else if (flowOutput) {
|
|
895
|
+
rawMsg = stripAnsi(flowOutput);
|
|
896
|
+
}
|
|
897
|
+
else if (error && r.errorMessage) {
|
|
898
|
+
rawMsg = stripAnsi(r.errorMessage);
|
|
899
|
+
useError = true;
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
const summary = getFlowSummaryText(r);
|
|
903
|
+
rawMsg = stripAnsi(summary) || "[n/a]";
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (streamingText != null || getLiveTextWithFallback(id) != null);
|
|
907
|
+
const initialMsgContent = initialNeedsTail
|
|
908
|
+
? tailText(rawMsg, msgBudget)
|
|
909
|
+
: truncateChars(rawMsg, msgBudget);
|
|
910
|
+
const msgTree = "└─";
|
|
530
911
|
const msgLabel = ` msg ▸ `;
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
912
|
+
const initialMsgPrefix = `${msgTree}${msgLabel}`;
|
|
913
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(initialMsgContent), theme, config)}`, () => {
|
|
914
|
+
const now = Date.now();
|
|
915
|
+
const msgLabel = ` msg ▸ `;
|
|
916
|
+
const msgPrefix = `${msgTree}${msgLabel}`;
|
|
917
|
+
let freshRawMsg;
|
|
918
|
+
let needsTail;
|
|
919
|
+
if (isFlowAwaiting(r)) {
|
|
920
|
+
freshRawMsg = "[awaiting...]";
|
|
921
|
+
needsTail = false;
|
|
922
|
+
}
|
|
923
|
+
else if (r.status === "skipped") {
|
|
924
|
+
freshRawMsg = "[skipped]";
|
|
925
|
+
needsTail = false;
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
const isRunningNow = isFlowRunning(r);
|
|
929
|
+
freshRawMsg = (isRunningNow ? getLiveTextWithFallback(id) : undefined) ?? rawMsg;
|
|
930
|
+
needsTail = isRunningNow && (streamingText != null || getLiveTextWithFallback(id) != null);
|
|
931
|
+
}
|
|
932
|
+
const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
|
|
933
|
+
const result = scrambleManager.updateMsg(id, displayMsg, now, isComplete, undefined, true);
|
|
934
|
+
return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(result.content), theme, config)}`;
|
|
935
|
+
}, true));
|
|
936
|
+
}
|
|
538
937
|
if (isComplete) {
|
|
539
938
|
scrambleManager.completeFlow(id);
|
|
540
939
|
}
|
|
@@ -563,7 +962,7 @@ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now
|
|
|
563
962
|
for (let flowIdx = 0; flowIdx < results.length; flowIdx++) {
|
|
564
963
|
const r = results[flowIdx];
|
|
565
964
|
const flowId = `${baseId}#${flowIdx}`;
|
|
566
|
-
const isComplete = r
|
|
965
|
+
const isComplete = isFlowStatusComplete(r);
|
|
567
966
|
const displayItems = getFlowDisplayItems(r.messages);
|
|
568
967
|
const flowOutput = getFlowOutput(r.messages);
|
|
569
968
|
const typeName = formatFlowTypeName(r.type);
|
|
@@ -603,7 +1002,10 @@ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now
|
|
|
603
1002
|
}));
|
|
604
1003
|
}
|
|
605
1004
|
// Output: animate streaming text; show clean markdown when complete
|
|
606
|
-
if (
|
|
1005
|
+
if (isFlowAwaiting(r)) {
|
|
1006
|
+
container.addChild(new Text(applyRole("prefixLabel", "[awaiting...]", theme, config), 0, 0));
|
|
1007
|
+
}
|
|
1008
|
+
else if (!isComplete && r.streamingText != null) {
|
|
607
1009
|
const streamingRaw = r.streamingText;
|
|
608
1010
|
const msgBudget = getTruncationBudget(0);
|
|
609
1011
|
const displayMsg = tailText(stripAnsi(streamingRaw), msgBudget);
|
|
@@ -654,131 +1056,264 @@ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now
|
|
|
654
1056
|
function renderActivityPanel(results, theme, baseId, config) {
|
|
655
1057
|
const idPrefix = baseId || "panel";
|
|
656
1058
|
const container = new Container();
|
|
657
|
-
const maxWidth = process.stdout.columns ?? 80;
|
|
658
1059
|
const now = Date.now();
|
|
1060
|
+
const { groups, rootIndices } = detectGroups(results);
|
|
1061
|
+
// Build ordered list of "root items" — each is either a standalone flow index
|
|
1062
|
+
// or a group index (rendered in original order).
|
|
1063
|
+
let groupCursor = 0;
|
|
1064
|
+
let rootCursor = 0;
|
|
1065
|
+
const orderedItems = [];
|
|
659
1066
|
for (let i = 0; i < results.length; i++) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1067
|
+
// Is this index the start of a group?
|
|
1068
|
+
if (groupCursor < groups.length) {
|
|
1069
|
+
const g = groups[groupCursor];
|
|
1070
|
+
if (g.buildIndices[0] === i || g.auditIndex === i) {
|
|
1071
|
+
orderedItems.push({ kind: "group", groupIndex: groupCursor });
|
|
1072
|
+
groupCursor++;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
// Is this a standalone flow?
|
|
1077
|
+
if (rootCursor < rootIndices.length && rootIndices[rootCursor] === i) {
|
|
1078
|
+
orderedItems.push({ kind: "flow", index: i });
|
|
1079
|
+
rootCursor++;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
for (let itemIdx = 0; itemIdx < orderedItems.length; itemIdx++) {
|
|
1083
|
+
const item = orderedItems[itemIdx];
|
|
1084
|
+
const isLastRoot = itemIdx === orderedItems.length - 1;
|
|
1085
|
+
if (item.kind === "flow") {
|
|
1086
|
+
renderStandaloneFlow(container, results[item.index], item.index, idPrefix, theme, now, config, isLastRoot);
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
renderGroup(container, groups[item.groupIndex], results, idPrefix, theme, now, config, isLastRoot);
|
|
1090
|
+
}
|
|
1091
|
+
// No blank line separator between root items — compact tree
|
|
1092
|
+
}
|
|
1093
|
+
return container;
|
|
1094
|
+
}
|
|
1095
|
+
// ---------------------------------------------------------------------------
|
|
1096
|
+
// Standalone flow (rendered at depth 0)
|
|
1097
|
+
// ---------------------------------------------------------------------------
|
|
1098
|
+
function renderStandaloneFlow(container, r, index, idPrefix, theme, now, config, isLastRoot = false) {
|
|
1099
|
+
const flowId = `${idPrefix}#${index}`;
|
|
1100
|
+
const headerPrefix = isLastRoot ? "└─" : "├─";
|
|
1101
|
+
const childPrefix = isLastRoot ? " " : "│ ";
|
|
1102
|
+
renderFlowHeader(container, r, flowId, headerPrefix, theme, now, config);
|
|
1103
|
+
renderFlowBody(container, r, flowId, childPrefix, theme, now, config);
|
|
1104
|
+
if (isFlowStatusComplete(r)) {
|
|
1105
|
+
scrambleManager.completeFlow(flowId);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
// ---------------------------------------------------------------------------
|
|
1109
|
+
// Group rendering (rendered at depth 1)
|
|
1110
|
+
// ---------------------------------------------------------------------------
|
|
1111
|
+
function renderGroup(container, group, results, idPrefix, theme, now, config, isLastRoot = false) {
|
|
1112
|
+
// ─── Group header line ───
|
|
1113
|
+
const headerPrefix = isLastRoot ? "" : "├─";
|
|
1114
|
+
const headerText = `${headerPrefix}${headerPrefix ? ' ' : ''}audit-loop`;
|
|
1115
|
+
container.addChild(new Text(applyRole("treeChars", headerText, theme, config), 0, 0));
|
|
1116
|
+
// ─── Build children ───
|
|
1117
|
+
for (let b = 0; b < group.buildIndices.length; b++) {
|
|
1118
|
+
const buildIdx = group.buildIndices[b];
|
|
1119
|
+
const r = results[buildIdx];
|
|
1120
|
+
const flowId = `${idPrefix}#${buildIdx}`;
|
|
1121
|
+
const isLastBuild = b === group.buildIndices.length - 1;
|
|
1122
|
+
// Audit always follows the last build, so every build uses ├─; only audit gets └─
|
|
1123
|
+
const buildHeaderPrefix = isLastRoot ? "├─" : "│ ├─";
|
|
1124
|
+
const buildChildPrefix = isLastRoot ? "│ " : "│ │ "; // All builds: audit follows, tree line continues
|
|
1125
|
+
renderFlowHeader(container, r, flowId, buildHeaderPrefix, theme, now, config);
|
|
1126
|
+
renderFlowBody(container, r, flowId, buildChildPrefix, theme, now, config);
|
|
1127
|
+
if (isFlowStatusComplete(r)) {
|
|
1128
|
+
scrambleManager.completeFlow(flowId);
|
|
1129
|
+
}
|
|
1130
|
+
// No blank line between builds or before audit capstone — compact tree
|
|
1131
|
+
}
|
|
1132
|
+
// ─── Audit capstone ───
|
|
1133
|
+
const auditIdx = group.auditIndex;
|
|
1134
|
+
const auditResult = results[auditIdx];
|
|
1135
|
+
const auditFlowId = `${idPrefix}#${auditIdx}`;
|
|
1136
|
+
const auditHeaderPrefix = isLastRoot ? "└─" : "│ └─";
|
|
1137
|
+
const auditChildPrefix = isLastRoot ? " " : "│ ";
|
|
1138
|
+
renderFlowHeader(container, auditResult, auditFlowId, auditHeaderPrefix, theme, now, config);
|
|
1139
|
+
renderFlowBody(container, auditResult, auditFlowId, auditChildPrefix, theme, now, config);
|
|
1140
|
+
if (isFlowStatusComplete(auditResult)) {
|
|
1141
|
+
scrambleManager.completeFlow(auditFlowId);
|
|
1142
|
+
}
|
|
1143
|
+
// No extra spacer — renderActivityPanel handles uniform inter-item spacing
|
|
1144
|
+
}
|
|
1145
|
+
// ---------------------------------------------------------------------------
|
|
1146
|
+
// Shared flow rendering helpers
|
|
1147
|
+
// ---------------------------------------------------------------------------
|
|
1148
|
+
function renderFlowHeader(container, r, flowId, headerPrefix, theme, now, config) {
|
|
1149
|
+
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
1150
|
+
const modelLabel = formatModelLabel(r.model);
|
|
1151
|
+
const isComplete = isFlowStatusComplete(r);
|
|
1152
|
+
const flowComplete = isComplete;
|
|
1153
|
+
const error = isFlowError(r);
|
|
1154
|
+
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
1155
|
+
const initialDot = flowStatusIcon(r, theme);
|
|
1156
|
+
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
1157
|
+
let headerLine;
|
|
1158
|
+
let plainHeader;
|
|
1159
|
+
const headerSegments = [
|
|
1160
|
+
{ text: headerPrefix + " ", style: (s) => applyRole("treeChars", s, theme, config) },
|
|
1161
|
+
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), flowId) + " " },
|
|
1162
|
+
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
1163
|
+
];
|
|
1164
|
+
{
|
|
1165
|
+
// Standard flow: model + stats
|
|
668
1166
|
const statsParts = [];
|
|
669
1167
|
if (r.maxContextTokens !== undefined || r.usage.contextTokens > 0) {
|
|
670
1168
|
const ctxLabel = formatContextLabel(r.usage.contextTokens, r.maxContextTokens);
|
|
671
1169
|
statsParts.push(ctxLabel);
|
|
672
1170
|
}
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
if (tpsDisplay)
|
|
676
|
-
statsParts.push(`${tpsDisplay} t/s`);
|
|
677
|
-
else
|
|
678
|
-
statsParts.push("---- t/s");
|
|
1171
|
+
const tpsFormatted = formatTps(r.usage.smoothedTps);
|
|
1172
|
+
statsParts.push(tpsFormatted);
|
|
679
1173
|
let displayStats = statsParts.join(" · ");
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (
|
|
692
|
-
headerLine += ` ${theme.fg("error",
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
// act: line (last tool call with count)
|
|
719
|
-
const lastTool = getLastToolCall(r.messages);
|
|
720
|
-
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
721
|
-
const actTree = `${indent}├─`;
|
|
722
|
-
const actLabel = ` cmd ▸ `;
|
|
723
|
-
const prefixStub = `${actTree}${actLabel}`;
|
|
724
|
-
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
725
|
-
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
726
|
-
const initialActContent = actFullText.length > budget ? tailText(actFullText, budget) : actFullText;
|
|
727
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", initialActContent, theme, config)}`, () => {
|
|
1174
|
+
const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
|
|
1175
|
+
if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
|
|
1176
|
+
const scrambledTps = scrambleManager.updateTps(flowId, tpsNum, now, flowComplete, true);
|
|
1177
|
+
if (scrambledTps !== tpsNum) {
|
|
1178
|
+
displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const modelSegment = modelLabel ? ` · ${modelLabel}` : "";
|
|
1182
|
+
const statsSegment = ` · ${displayStats}`;
|
|
1183
|
+
const statsPlain = stripAnsi(statsSegment);
|
|
1184
|
+
headerLine = `${applyRole("treeChars", headerPrefix, theme, config)} ${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelSegment, theme, config)}${applyRole("stats", statsSegment, theme, config)}`;
|
|
1185
|
+
if (errorSegment) {
|
|
1186
|
+
headerLine += ` ${theme.fg("error", errorSegment)}`;
|
|
1187
|
+
}
|
|
1188
|
+
plainHeader = headerPrefix + " " + dotPlaceholder + typeName + modelSegment + statsPlain + errorSegment;
|
|
1189
|
+
headerSegments.push({ text: modelSegment, style: (s) => applyRole("modelName", s, theme, config) }, { text: statsPlain, style: (s) => applyRole("stats", s, theme, config) });
|
|
1190
|
+
}
|
|
1191
|
+
if (errorSegment) {
|
|
1192
|
+
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
1193
|
+
}
|
|
1194
|
+
container.addChild(new DynamicScrambleText(headerLine, () => {
|
|
1195
|
+
const now = Date.now();
|
|
1196
|
+
const result = scrambleManager.updateText(flowId, 'header', plainHeader, now, flowComplete, true);
|
|
1197
|
+
return reconstructHeader(result.content, headerSegments);
|
|
1198
|
+
}, true));
|
|
1199
|
+
}
|
|
1200
|
+
function renderFlowBody(container, r, flowId, indent, theme, now, config) {
|
|
1201
|
+
const isComplete = isFlowStatusComplete(r);
|
|
1202
|
+
const flowComplete = isComplete;
|
|
1203
|
+
// aim: line — glitch on text change
|
|
1204
|
+
if (r.aim) {
|
|
1205
|
+
const aimTree = indent + "├─";
|
|
1206
|
+
const aimLabel = ` aim ▸ `;
|
|
1207
|
+
const aimPrefix = `${aimTree}${aimLabel}`;
|
|
1208
|
+
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
1209
|
+
const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
|
|
1210
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(displayAim), theme, config)}`, () => {
|
|
728
1211
|
const now = Date.now();
|
|
729
|
-
const
|
|
730
|
-
const
|
|
731
|
-
const freshBudget = getTruncationBudget(visibleLength(
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
return `${applyRole("treeChars",
|
|
1212
|
+
const freshAimLabel = ` aim ▸ `;
|
|
1213
|
+
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
1214
|
+
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
1215
|
+
const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
1216
|
+
const result = scrambleManager.updateAim(flowId, freshText, now, flowComplete, true);
|
|
1217
|
+
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(result.content), theme, config)}`;
|
|
735
1218
|
}, true));
|
|
736
|
-
|
|
1219
|
+
}
|
|
1220
|
+
// act: line (last tool call with count)
|
|
1221
|
+
const lastTool = getLastToolCall(r.messages);
|
|
1222
|
+
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
1223
|
+
const isLite = config?.bodyVerbosity !== "full";
|
|
1224
|
+
const actTree = isLite ? `${indent}└─` : `${indent}├─`;
|
|
1225
|
+
const actLabel = ` cmd ▸ `;
|
|
1226
|
+
const prefixStub = `${actTree}${actLabel}`;
|
|
1227
|
+
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
1228
|
+
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
1229
|
+
const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
|
|
1230
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(initialActContent), theme, config)}`, () => {
|
|
1231
|
+
const now = Date.now();
|
|
1232
|
+
const actLabel = ` cmd ▸ `;
|
|
1233
|
+
const actPrefix = `${actTree}${actLabel}`;
|
|
1234
|
+
const freshBudget = getTruncationBudget(visibleLength(actPrefix));
|
|
1235
|
+
const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
|
|
1236
|
+
const actContent = scrambleManager.updateAct(flowId, displayAct, now, flowComplete, true).content;
|
|
1237
|
+
return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(actContent), theme, config)}`;
|
|
1238
|
+
}, true));
|
|
1239
|
+
// msg: line (live streaming text or last assistant text) — full mode only
|
|
1240
|
+
if (!isLite) {
|
|
737
1241
|
const msgTree = `${indent}└─`;
|
|
738
1242
|
const msgLabel = ` msg ▸ `;
|
|
739
1243
|
const msgPrefixStub = `${msgTree}${msgLabel}`;
|
|
740
1244
|
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
741
|
-
const liveText = r.exitCode === -1 ? r.streamingText : undefined;
|
|
742
|
-
const lastText = liveText || getLastAssistantText(r.messages);
|
|
743
1245
|
let rawMsg;
|
|
744
1246
|
let useError = false;
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
rawMsg = stripAnsi(liveText_);
|
|
1247
|
+
if (isFlowAwaiting(r)) {
|
|
1248
|
+
rawMsg = "[awaiting...]";
|
|
748
1249
|
}
|
|
749
|
-
else if (
|
|
750
|
-
rawMsg =
|
|
1250
|
+
else if (r.status === "skipped") {
|
|
1251
|
+
rawMsg = "[skipped]";
|
|
751
1252
|
}
|
|
752
|
-
else if (
|
|
753
|
-
|
|
754
|
-
|
|
1253
|
+
else if (isFlowStatusComplete(r) && !isFlowRunning(r)) {
|
|
1254
|
+
if (isFlowError(r) && r.errorMessage) {
|
|
1255
|
+
rawMsg = stripAnsi(r.errorMessage);
|
|
1256
|
+
useError = true;
|
|
1257
|
+
}
|
|
1258
|
+
else if (r.pingPongMeta && r.pingPongMeta.finalVerdict === "pass") {
|
|
1259
|
+
rawMsg = "[approved]";
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
rawMsg = "[finished]";
|
|
1263
|
+
}
|
|
755
1264
|
}
|
|
756
1265
|
else {
|
|
757
|
-
|
|
1266
|
+
const liveMsgText = isFlowRunning(r) ? getLiveTextWithFallback(flowId) : undefined;
|
|
1267
|
+
if (liveMsgText != null) {
|
|
1268
|
+
rawMsg = stripAnsi(liveMsgText);
|
|
1269
|
+
}
|
|
1270
|
+
else if (isFlowRunning(r) && r.streamingText != null) {
|
|
1271
|
+
rawMsg = stripAnsi(r.streamingText);
|
|
1272
|
+
}
|
|
1273
|
+
else if (r.structuredOutput?.summary) {
|
|
1274
|
+
rawMsg = stripAnsi(r.structuredOutput.summary);
|
|
1275
|
+
}
|
|
1276
|
+
else {
|
|
1277
|
+
const flowOutput = getFlowOutput(r.messages);
|
|
1278
|
+
if (flowOutput) {
|
|
1279
|
+
rawMsg = stripAnsi(flowOutput);
|
|
1280
|
+
}
|
|
1281
|
+
else if (isFlowError(r) && r.errorMessage) {
|
|
1282
|
+
rawMsg = stripAnsi(r.errorMessage);
|
|
1283
|
+
useError = true;
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
const summary = getFlowSummaryText(r);
|
|
1287
|
+
rawMsg = stripAnsi(summary) || "[n/a]";
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
758
1290
|
}
|
|
759
|
-
const initialNeedsTail =
|
|
1291
|
+
const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
|
|
760
1292
|
const initialDisplayMsg = initialNeedsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
|
|
761
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", initialDisplayMsg, theme, config)}`, () => {
|
|
1293
|
+
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(initialDisplayMsg), theme, config)}`, () => {
|
|
762
1294
|
const now = Date.now();
|
|
763
1295
|
const msgLabel = ` msg ▸ `;
|
|
764
1296
|
const msgPrefix = `${msgTree}${msgLabel}`;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1297
|
+
let freshRawMsg;
|
|
1298
|
+
let needsTail;
|
|
1299
|
+
if (isFlowAwaiting(r)) {
|
|
1300
|
+
freshRawMsg = "[awaiting...]";
|
|
1301
|
+
needsTail = false;
|
|
1302
|
+
}
|
|
1303
|
+
else if (r.status === "skipped") {
|
|
1304
|
+
freshRawMsg = "[skipped]";
|
|
1305
|
+
needsTail = false;
|
|
1306
|
+
}
|
|
1307
|
+
else {
|
|
1308
|
+
const isRunningNow = isFlowRunning(r);
|
|
1309
|
+
freshRawMsg = (isRunningNow ? getLiveTextWithFallback(flowId) : undefined) ?? rawMsg;
|
|
1310
|
+
needsTail = isRunningNow && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
|
|
1311
|
+
}
|
|
1312
|
+
const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
|
|
769
1313
|
const result = scrambleManager.updateMsg(flowId, displayMsg, now, flowComplete, undefined, true);
|
|
770
|
-
return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", result.content, theme, config)}`;
|
|
1314
|
+
return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(result.content), theme, config)}`;
|
|
771
1315
|
}, true));
|
|
772
|
-
if (flowComplete) {
|
|
773
|
-
scrambleManager.completeFlow(flowId);
|
|
774
|
-
}
|
|
775
|
-
// Add blank line separator between flows (with continuation pipe)
|
|
776
|
-
if (!isLast) {
|
|
777
|
-
container.addChild(new TruncatedText(applyRole("treeChars", "│", theme, config), 0, 0));
|
|
778
|
-
}
|
|
779
1316
|
}
|
|
780
|
-
container.addChild(new TruncatedText(applyRole("prefixLabel", "(Ctrl+O to expand tool traces)", theme, config), 0, 0));
|
|
781
|
-
return container;
|
|
782
1317
|
}
|
|
783
1318
|
function renderMultiFlowCollapsed(results, theme, baseId, config) {
|
|
784
1319
|
return renderActivityPanel(results, theme, baseId, config);
|