pi-agent-flow 2.0.2 → 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 +1 -1
- package/agents/audit.md +24 -17
- package/agents/build.md +1 -1
- package/agents/craft.md +1 -1
- package/agents/debug.md +1 -1
- package/agents/ideas.md +1 -1
- package/agents/scout.md +8 -8
- package/agents/trace.md +23 -0
- package/dist/batch/batch-bash.d.ts +10 -0
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +35 -2
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +6 -3
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/execute.js +2 -2
- package/dist/batch/execute.js.map +1 -1
- package/dist/batch/index.d.ts +9 -11
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +88 -58
- 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 +332 -17
- package/dist/batch/render.js.map +1 -1
- package/dist/batch/summary.d.ts.map +1 -1
- package/dist/batch/summary.js +22 -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/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 +2 -1
- package/dist/flow/continuation.js.map +1 -1
- package/dist/{core → flow}/depth.d.ts +1 -1
- package/dist/{core → flow}/depth.d.ts.map +1 -1
- package/dist/{core → flow}/depth.js.map +1 -1
- package/dist/{core → flow}/executor.d.ts +39 -19
- 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 +2 -2
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +1 -1
- 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/{core/flow.d.ts → flow/runner.d.ts} +10 -13
- package/dist/flow/runner.d.ts.map +1 -0
- package/dist/{core/flow.js → flow/runner.js} +35 -34
- 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 +39 -21
- package/dist/flow/settings-command.js.map +1 -1
- package/dist/{core → flow}/transition.d.ts.map +1 -1
- package/dist/{core → 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 +1 -1
- package/dist/flow/warp.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -188
- 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 +2 -2
- package/dist/snapshot/runner-events.d.ts.map +1 -1
- package/dist/snapshot/runner-events.js +48 -4
- 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 +2 -2
- package/dist/steering/flow-prompt.d.ts.map +1 -1
- package/dist/steering/flow-prompt.js +14 -33
- 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 +3 -2
- package/dist/steering/sliding-prompt.js.map +1 -1
- package/dist/tools/ask-user.d.ts +2 -2
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +1 -1
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/timed-bash.js +1 -1
- 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 -125
- 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.js +1 -1
- package/dist/tui/render-utils.js.map +1 -1
- package/dist/tui/render.d.ts +32 -1
- package/dist/tui/render.d.ts.map +1 -1
- package/dist/tui/render.js +666 -170
- 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 +2 -1
- package/dist/tui/scramble/index.d.ts.map +1 -1
- package/dist/tui/scramble/index.js +1 -1
- package/dist/tui/scramble/index.js.map +1 -1
- package/dist/tui/scramble/manager.d.ts +1 -1
- package/dist/tui/scramble/manager.d.ts.map +1 -1
- package/dist/tui/scramble/manager.js +24 -19
- package/dist/tui/scramble/manager.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.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 +9 -9
- 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/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 -1824
- 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}/depth.js +0 -0
- /package/dist/{core → flow}/session-registry.d.ts +0 -0
- /package/dist/{core → flow}/session-registry.js +0 -0
- /package/dist/{core → flow}/transition.d.ts +0 -0
- /package/dist/{core → flow}/transition.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, formatTps } 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) {
|
|
@@ -176,13 +374,13 @@ export function renderFlowResult(result, expanded, theme, args, config) {
|
|
|
176
374
|
let resolvedToolCallId;
|
|
177
375
|
if (args?.state) {
|
|
178
376
|
const s = args.state;
|
|
179
|
-
resolvedToolCallId = s.
|
|
377
|
+
resolvedToolCallId = s.__widgetId;
|
|
180
378
|
if (!resolvedToolCallId) {
|
|
181
379
|
resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
|
|
182
380
|
if (!resolvedToolCallId) {
|
|
183
381
|
resolvedToolCallId = getAnonymousFlowId();
|
|
184
382
|
}
|
|
185
|
-
s.
|
|
383
|
+
s.__widgetId = resolvedToolCallId;
|
|
186
384
|
}
|
|
187
385
|
}
|
|
188
386
|
else {
|
|
@@ -275,6 +473,140 @@ export function renderFlowResult(result, expanded, theme, args, config) {
|
|
|
275
473
|
return container;
|
|
276
474
|
}
|
|
277
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
|
+
// ---------------------------------------------------------------------------
|
|
278
610
|
// Single flow result
|
|
279
611
|
// ---------------------------------------------------------------------------
|
|
280
612
|
export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCallId, config) {
|
|
@@ -284,7 +616,7 @@ export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCa
|
|
|
284
616
|
const displayItems = getFlowDisplayItems(r.messages);
|
|
285
617
|
const flowOutput = getFlowOutput(r.messages);
|
|
286
618
|
const now = Date.now();
|
|
287
|
-
const isComplete = r
|
|
619
|
+
const isComplete = isFlowStatusComplete(r);
|
|
288
620
|
if (expanded) {
|
|
289
621
|
return renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText, config);
|
|
290
622
|
}
|
|
@@ -294,20 +626,24 @@ function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id,
|
|
|
294
626
|
const mdTheme = getMarkdownTheme();
|
|
295
627
|
const container = new Container();
|
|
296
628
|
const typeName = formatFlowTypeName(r.type);
|
|
297
|
-
|
|
629
|
+
const initialDot = flowStatusIcon(r, theme);
|
|
630
|
+
let header = `${initialDot} ${applyRole("flowName", typeName, theme, config)}`;
|
|
298
631
|
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
299
632
|
if (errorSegment)
|
|
300
633
|
header += ` ${theme.fg("error", errorSegment)}`;
|
|
301
|
-
const
|
|
634
|
+
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
635
|
+
const plainHeader = dotPlaceholder + typeName + errorSegment;
|
|
302
636
|
const headerSegments = [
|
|
637
|
+
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
|
|
303
638
|
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
304
639
|
];
|
|
305
640
|
if (errorSegment) {
|
|
306
641
|
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
307
642
|
}
|
|
308
643
|
container.addChild(new DynamicScrambleText(header, () => {
|
|
309
|
-
const
|
|
310
|
-
|
|
644
|
+
const now = Date.now();
|
|
645
|
+
const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete);
|
|
646
|
+
return reconstructHeader(result.content, headerSegments);
|
|
311
647
|
}));
|
|
312
648
|
if (error && r.errorMessage) {
|
|
313
649
|
container.addChild(new Text(scrambleManager.renderStatic(theme.fg("error", `Error: ${r.errorMessage}`)), 0, 0));
|
|
@@ -398,7 +734,10 @@ function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id,
|
|
|
398
734
|
container.addChild(new Spacer(1));
|
|
399
735
|
}
|
|
400
736
|
// Output: animate streaming text; show clean markdown when complete
|
|
401
|
-
if (
|
|
737
|
+
if (isFlowAwaiting(r)) {
|
|
738
|
+
container.addChild(new Text(applyRole("prefixLabel", "[awaiting...]", theme, config), 0, 0));
|
|
739
|
+
}
|
|
740
|
+
else if (!isComplete && streamingText != null) {
|
|
402
741
|
const msgBudget = getTruncationBudget(0);
|
|
403
742
|
const displayMsg = tailText(stripAnsi(streamingText), msgBudget);
|
|
404
743
|
container.addChild(new DynamicScrambleText(displayMsg, () => {
|
|
@@ -451,8 +790,8 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
|
|
|
451
790
|
const maxWidth = process.stdout.columns ?? 80;
|
|
452
791
|
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
453
792
|
const modelLabel = formatModelLabel(r.model);
|
|
454
|
-
const headerPrefixLen = visibleLength(typeName) + visibleLength(modelLabel ? `
|
|
455
|
-
const isComplete = r
|
|
793
|
+
const headerPrefixLen = visibleLength(typeName) + visibleLength(modelLabel ? ` ${modelLabel} · ` : " ");
|
|
794
|
+
const isComplete = isFlowStatusComplete(r);
|
|
456
795
|
// Build header stats: ctxLabel · t/s
|
|
457
796
|
const statsParts = [];
|
|
458
797
|
if (r.maxContextTokens !== undefined || r.usage.contextTokens > 0) {
|
|
@@ -470,14 +809,17 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
|
|
|
470
809
|
displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
|
|
471
810
|
}
|
|
472
811
|
}
|
|
473
|
-
const modelSegment = modelLabel ? `
|
|
812
|
+
const modelSegment = modelLabel ? ` ${modelLabel} · ` : " ";
|
|
474
813
|
const statsSegment = stripAnsi(displayStats);
|
|
475
814
|
const errorSegment = error && r.stopReason ? ` [${r.stopReason}]` : "";
|
|
476
|
-
|
|
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)}`;
|
|
477
817
|
if (errorSegment)
|
|
478
818
|
header += ` ${theme.fg("error", errorSegment)}`;
|
|
479
|
-
const
|
|
819
|
+
const dotPlaceholder = stripAnsi(initialDot) + ' ';
|
|
820
|
+
const plainHeader = dotPlaceholder + typeName + modelSegment + statsSegment + errorSegment;
|
|
480
821
|
const headerSegments = [
|
|
822
|
+
{ text: dotPlaceholder, style: (_s) => getScintillatingStatusDot(r, theme, Date.now(), id) + " " },
|
|
481
823
|
{ text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
|
|
482
824
|
{ text: modelSegment, style: (s) => applyRole("modelName", s, theme, config) },
|
|
483
825
|
{ text: statsSegment, style: (s) => applyRole("stats", s, theme, config) },
|
|
@@ -486,8 +828,9 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
|
|
|
486
828
|
headerSegments.push({ text: errorSegment, style: (s) => theme.fg("error", s) });
|
|
487
829
|
}
|
|
488
830
|
container.addChild(new DynamicScrambleText(header, () => {
|
|
489
|
-
const
|
|
490
|
-
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
const result = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete, true);
|
|
833
|
+
return reconstructHeader(result.content, headerSegments);
|
|
491
834
|
}, true));
|
|
492
835
|
// aim: line — glitch on text change
|
|
493
836
|
if (r.aim) {
|
|
@@ -495,77 +838,102 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
|
|
|
495
838
|
const aimLabel = ` aim ▸ `;
|
|
496
839
|
const aimPrefix = `${aimTree}${aimLabel}`;
|
|
497
840
|
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
498
|
-
const displayAim = truncateChars(lowerFirstWord(r.aim), budget);
|
|
499
|
-
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)}`, () => {
|
|
500
843
|
const now = Date.now();
|
|
501
844
|
const freshAimLabel = ` aim ▸ `;
|
|
502
845
|
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
503
846
|
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
504
|
-
const freshText = truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
847
|
+
const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
505
848
|
const result = scrambleManager.updateAim(id, freshText, now, isComplete, true);
|
|
506
|
-
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)}`;
|
|
507
850
|
}, true));
|
|
508
851
|
}
|
|
509
852
|
// act: line (last tool call with count)
|
|
510
853
|
const lastTool = getLastToolCall(r.messages);
|
|
511
854
|
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
512
|
-
const
|
|
855
|
+
const isLite = config?.bodyVerbosity !== "full";
|
|
856
|
+
const actTree = isLite ? "└─" : "├─";
|
|
513
857
|
const actLabel = ` cmd ▸ `;
|
|
514
858
|
const prefixStub = `${actTree}${actLabel}`;
|
|
515
859
|
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
516
860
|
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
517
|
-
const initialActContent = actFullText.length > budget ? tailText(actFullText, budget) : actFullText;
|
|
518
|
-
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)}`, () => {
|
|
519
863
|
const now = Date.now();
|
|
520
|
-
const displayAct = tailText(actFullText, budget);
|
|
521
|
-
const actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
|
|
522
864
|
const actLabel = ` cmd ▸ `;
|
|
523
865
|
const actPrefix = `${actTree}${actLabel}`;
|
|
524
|
-
|
|
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)}`;
|
|
525
870
|
}, true));
|
|
526
|
-
// msg: line (last assistant text or streaming)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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 = "└─";
|
|
561
911
|
const msgLabel = ` msg ▸ `;
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
}
|
|
569
937
|
if (isComplete) {
|
|
570
938
|
scrambleManager.completeFlow(id);
|
|
571
939
|
}
|
|
@@ -594,7 +962,7 @@ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now
|
|
|
594
962
|
for (let flowIdx = 0; flowIdx < results.length; flowIdx++) {
|
|
595
963
|
const r = results[flowIdx];
|
|
596
964
|
const flowId = `${baseId}#${flowIdx}`;
|
|
597
|
-
const isComplete = r
|
|
965
|
+
const isComplete = isFlowStatusComplete(r);
|
|
598
966
|
const displayItems = getFlowDisplayItems(r.messages);
|
|
599
967
|
const flowOutput = getFlowOutput(r.messages);
|
|
600
968
|
const typeName = formatFlowTypeName(r.type);
|
|
@@ -634,7 +1002,10 @@ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now
|
|
|
634
1002
|
}));
|
|
635
1003
|
}
|
|
636
1004
|
// Output: animate streaming text; show clean markdown when complete
|
|
637
|
-
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) {
|
|
638
1009
|
const streamingRaw = r.streamingText;
|
|
639
1010
|
const msgBudget = getTruncationBudget(0);
|
|
640
1011
|
const displayMsg = tailText(stripAnsi(streamingRaw), msgBudget);
|
|
@@ -685,17 +1056,113 @@ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now
|
|
|
685
1056
|
function renderActivityPanel(results, theme, baseId, config) {
|
|
686
1057
|
const idPrefix = baseId || "panel";
|
|
687
1058
|
const container = new Container();
|
|
688
|
-
const maxWidth = process.stdout.columns ?? 80;
|
|
689
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 = [];
|
|
690
1066
|
for (let i = 0; i < results.length; i++) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
|
699
1166
|
const statsParts = [];
|
|
700
1167
|
if (r.maxContextTokens !== undefined || r.usage.contextTokens > 0) {
|
|
701
1168
|
const ctxLabel = formatContextLabel(r.usage.contextTokens, r.maxContextTokens);
|
|
@@ -704,8 +1171,6 @@ function renderActivityPanel(results, theme, baseId, config) {
|
|
|
704
1171
|
const tpsFormatted = formatTps(r.usage.smoothedTps);
|
|
705
1172
|
statsParts.push(tpsFormatted);
|
|
706
1173
|
let displayStats = statsParts.join(" · ");
|
|
707
|
-
const flowComplete = r.exitCode !== -1;
|
|
708
|
-
// Flash TPS value when it changes
|
|
709
1174
|
const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
|
|
710
1175
|
if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
|
|
711
1176
|
const scrambledTps = scrambleManager.updateTps(flowId, tpsNum, now, flowComplete, true);
|
|
@@ -713,111 +1178,142 @@ function renderActivityPanel(results, theme, baseId, config) {
|
|
|
713
1178
|
displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
|
|
714
1179
|
}
|
|
715
1180
|
}
|
|
716
|
-
const
|
|
717
|
-
const
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
let headerLine = `${applyRole("treeChars", headerPrefix, theme, config)} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelSegment, theme, config)}${applyRole("stats", displayStats, theme, config)}`;
|
|
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)}`;
|
|
721
1185
|
if (errorSegment) {
|
|
722
1186
|
headerLine += ` ${theme.fg("error", errorSegment)}`;
|
|
723
1187
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole("aimContent", displayAim, theme, config)}`, () => {
|
|
748
|
-
const now = Date.now();
|
|
749
|
-
const freshAimLabel = ` aim ▸ `;
|
|
750
|
-
const freshAimPrefix = `${aimTree}${freshAimLabel}`;
|
|
751
|
-
const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
|
|
752
|
-
const freshText = truncateChars(lowerFirstWord(r.aim), freshBudget);
|
|
753
|
-
const result = scrambleManager.updateAim(flowId, freshText, now, flowComplete, true);
|
|
754
|
-
return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole("aimContent", result.content, theme, config)}`;
|
|
755
|
-
}, true));
|
|
756
|
-
}
|
|
757
|
-
// act: line (last tool call with count)
|
|
758
|
-
const lastTool = getLastToolCall(r.messages);
|
|
759
|
-
const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
|
|
760
|
-
const actTree = `${indent}├─`;
|
|
761
|
-
const actLabel = ` cmd ▸ `;
|
|
762
|
-
const prefixStub = `${actTree}${actLabel}`;
|
|
763
|
-
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
764
|
-
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
765
|
-
const initialActContent = actFullText.length > budget ? tailText(actFullText, budget) : actFullText;
|
|
766
|
-
container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", initialActContent, theme, config)}`, () => {
|
|
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)}`, () => {
|
|
767
1211
|
const now = Date.now();
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
const freshBudget = getTruncationBudget(visibleLength(
|
|
771
|
-
const
|
|
772
|
-
const
|
|
773
|
-
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)}`;
|
|
774
1218
|
}, true));
|
|
775
|
-
|
|
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) {
|
|
776
1241
|
const msgTree = `${indent}└─`;
|
|
777
1242
|
const msgLabel = ` msg ▸ `;
|
|
778
1243
|
const msgPrefixStub = `${msgTree}${msgLabel}`;
|
|
779
1244
|
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
780
|
-
const liveText = r.exitCode === -1 ? r.streamingText : undefined;
|
|
781
|
-
const lastText = liveText || getLastAssistantText(r.messages);
|
|
782
1245
|
let rawMsg;
|
|
783
1246
|
let useError = false;
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
rawMsg = stripAnsi(liveText_);
|
|
1247
|
+
if (isFlowAwaiting(r)) {
|
|
1248
|
+
rawMsg = "[awaiting...]";
|
|
787
1249
|
}
|
|
788
|
-
else if (
|
|
789
|
-
rawMsg =
|
|
1250
|
+
else if (r.status === "skipped") {
|
|
1251
|
+
rawMsg = "[skipped]";
|
|
790
1252
|
}
|
|
791
|
-
else if (
|
|
792
|
-
|
|
793
|
-
|
|
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
|
+
}
|
|
794
1264
|
}
|
|
795
1265
|
else {
|
|
796
|
-
|
|
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
|
+
}
|
|
797
1290
|
}
|
|
798
|
-
const initialNeedsTail =
|
|
1291
|
+
const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
|
|
799
1292
|
const initialDisplayMsg = initialNeedsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
|
|
800
|
-
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)}`, () => {
|
|
801
1294
|
const now = Date.now();
|
|
802
1295
|
const msgLabel = ` msg ▸ `;
|
|
803
1296
|
const msgPrefix = `${msgTree}${msgLabel}`;
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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);
|
|
808
1313
|
const result = scrambleManager.updateMsg(flowId, displayMsg, now, flowComplete, undefined, true);
|
|
809
|
-
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)}`;
|
|
810
1315
|
}, true));
|
|
811
|
-
if (flowComplete) {
|
|
812
|
-
scrambleManager.completeFlow(flowId);
|
|
813
|
-
}
|
|
814
|
-
// Add blank line separator between flows (with continuation pipe)
|
|
815
|
-
if (!isLast) {
|
|
816
|
-
container.addChild(new TruncatedText(applyRole("treeChars", "│", theme, config), 0, 0));
|
|
817
|
-
}
|
|
818
1316
|
}
|
|
819
|
-
container.addChild(new TruncatedText(applyRole("prefixLabel", "(Ctrl+O to expand tool traces)", theme, config), 0, 0));
|
|
820
|
-
return container;
|
|
821
1317
|
}
|
|
822
1318
|
function renderMultiFlowCollapsed(results, theme, baseId, config) {
|
|
823
1319
|
return renderActivityPanel(results, theme, baseId, config);
|