pi-agent-flow 1.8.28 → 1.8.30
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 +121 -19
- package/agents/build.md +0 -3
- package/agents/ideas.md +0 -4
- package/agents/scout.md +0 -3
- package/dist/agents.d.ts +0 -2
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +0 -11
- package/dist/agents.js.map +1 -1
- package/dist/ask-user.d.ts +1 -1
- package/dist/ask-user.d.ts.map +1 -1
- package/dist/ask-user.js +35 -9
- package/dist/ask-user.js.map +1 -1
- package/dist/batch/batch-bash.d.ts +5 -0
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +39 -5
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +2 -6
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/constants.js +2 -0
- package/dist/batch/constants.js.map +1 -1
- package/dist/batch/execute.js +4 -4
- package/dist/batch/render.d.ts +1 -1
- package/dist/batch/render.d.ts.map +1 -1
- package/dist/batch/render.js +19 -3
- package/dist/batch/render.js.map +1 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +6 -0
- package/dist/executor.js.map +1 -1
- package/dist/flow-prompt.d.ts +3 -2
- package/dist/flow-prompt.d.ts.map +1 -1
- package/dist/flow-prompt.js +3 -5
- package/dist/flow-prompt.js.map +1 -1
- package/dist/flow.d.ts +0 -1
- package/dist/flow.d.ts.map +1 -1
- package/dist/flow.js +1 -5
- package/dist/flow.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -20
- package/dist/index.js.map +1 -1
- package/dist/notify-state.d.ts +33 -0
- package/dist/notify-state.d.ts.map +1 -0
- package/dist/notify-state.js +38 -0
- package/dist/notify-state.js.map +1 -0
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +69 -2
- package/dist/notify.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +280 -92
- package/dist/render.js.map +1 -1
- package/dist/scramble.d.ts +171 -0
- package/dist/scramble.d.ts.map +1 -0
- package/dist/scramble.js +2236 -0
- package/dist/scramble.js.map +1 -0
- package/dist/settings-resolver.js +2 -2
- package/dist/settings-resolver.js.map +1 -1
- package/dist/single-select-layout.js +1 -1
- package/dist/snapshot.d.ts +1 -6
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +38 -17
- package/dist/snapshot.js.map +1 -1
- package/dist/spec-mode.d.ts +13 -0
- package/dist/spec-mode.d.ts.map +1 -0
- package/dist/spec-mode.js +90 -0
- package/dist/spec-mode.js.map +1 -0
- package/dist/steering-hint.d.ts +45 -0
- package/dist/steering-hint.d.ts.map +1 -0
- package/dist/steering-hint.js +186 -0
- package/dist/steering-hint.js.map +1 -0
- package/dist/tool-utils.d.ts +0 -1
- package/dist/tool-utils.d.ts.map +1 -1
- package/dist/tool-utils.js +0 -7
- package/dist/tool-utils.js.map +1 -1
- package/dist/transitions.js +1 -1
- package/dist/transitions.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/web-tool.d.ts +13 -2
- package/dist/web-tool.d.ts.map +1 -1
- package/dist/web-tool.js +54 -11
- package/dist/web-tool.js.map +1 -1
- package/package.json +1 -1
- package/dist/sliding-prompt.d.ts +0 -40
- package/dist/sliding-prompt.d.ts.map +0 -1
- package/dist/sliding-prompt.js +0 -121
- package/dist/sliding-prompt.js.map +0 -1
package/dist/render.js
CHANGED
|
@@ -10,7 +10,8 @@ import { Container, Markdown, Spacer, Text, TruncatedText } from "@mariozechner/
|
|
|
10
10
|
import { getFlowSummaryText } from "./runner-events.js";
|
|
11
11
|
import { aggregateFlowUsage, getFlowDisplayItems, getFlowOutput, getLastToolCall, getLastAssistantText, isFlowError, isFlowSuccess, } from "./types.js";
|
|
12
12
|
import { formatBatchOpsSummary } from "./batch/render.js";
|
|
13
|
-
import {
|
|
13
|
+
import { scrambleManager, runScrambleTimer } from "./scramble.js";
|
|
14
|
+
import { formatCompactStats, formatCompactTokenPair, formatCountdown, formatFlowTypeName, italic, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi } from "./render-utils.js";
|
|
14
15
|
function shortenPath(p) {
|
|
15
16
|
const home = os.homedir();
|
|
16
17
|
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
@@ -85,7 +86,7 @@ function renderFlowReport(output, theme) {
|
|
|
85
86
|
function flowStatusIcon(r, theme) {
|
|
86
87
|
if (r.exitCode === -1)
|
|
87
88
|
return theme.fg("warning", "⏳");
|
|
88
|
-
return isFlowError(r) ? theme.fg("error", "
|
|
89
|
+
return isFlowError(r) ? theme.fg("error", "✖") : theme.fg("success", "✔");
|
|
89
90
|
}
|
|
90
91
|
/** Center a label in a fixed-width header using em-dashes. Total width = 20. */
|
|
91
92
|
function sectionHeader(label) {
|
|
@@ -101,15 +102,6 @@ function getLiveCountdown(r) {
|
|
|
101
102
|
return undefined;
|
|
102
103
|
return formatCountdown(r.deadlineAtMs - Date.now());
|
|
103
104
|
}
|
|
104
|
-
function formatAimLinePrefix(treePrefix, r) {
|
|
105
|
-
const countdown = getLiveCountdown(r);
|
|
106
|
-
const aimLabel = "aim:";
|
|
107
|
-
return countdown ? `${treePrefix} ${aimLabel} [${countdown}] - ` : `${treePrefix} ${aimLabel} `;
|
|
108
|
-
}
|
|
109
|
-
function formatMsgLinePrefix(treePrefix, r) {
|
|
110
|
-
const msgLabel = "msg:";
|
|
111
|
-
return `${treePrefix} ${msgLabel} [${formatCompactTokenPair(r.usage)}] - `;
|
|
112
|
-
}
|
|
113
105
|
// ---------------------------------------------------------------------------
|
|
114
106
|
// renderFlowCall — shown while the flow is being invoked
|
|
115
107
|
// ---------------------------------------------------------------------------
|
|
@@ -124,6 +116,7 @@ export function renderFlowCall(args, theme) {
|
|
|
124
116
|
export function renderFlowResult(result, expanded, theme, args) {
|
|
125
117
|
const details = result.details;
|
|
126
118
|
const streamingText = result.content?.[0]?.type === "text" ? result.content[0].text : undefined;
|
|
119
|
+
let container;
|
|
127
120
|
if (!details || details.results.length === 0) {
|
|
128
121
|
// Ghost Dashboard: render a placeholder status line during the zero state
|
|
129
122
|
const flowRequest = args?.flow?.[0];
|
|
@@ -139,29 +132,45 @@ export function renderFlowResult(result, expanded, theme, args) {
|
|
|
139
132
|
stderr: "",
|
|
140
133
|
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0, toolCalls: 0 },
|
|
141
134
|
};
|
|
142
|
-
|
|
135
|
+
if (expanded) {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
container = renderFlowExpanded(ghostResult, flowStatusIcon(ghostResult, theme), false, getFlowDisplayItems([]), getFlowOutput([]), theme, "ghost", now, false, streamingText || "");
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
container = renderFlowCollapsed(ghostResult, flowStatusIcon(ghostResult, theme), false, streamingText || "", theme);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
container = new Text(streamingText || "", 0, 0);
|
|
143
145
|
}
|
|
144
|
-
return new Text(streamingText || "", 0, 0);
|
|
145
146
|
}
|
|
146
|
-
if (details.results.length === 1) {
|
|
147
|
-
|
|
147
|
+
else if (details.results.length === 1) {
|
|
148
|
+
container = renderSingleFlowResult(details.results[0], expanded, theme, streamingText);
|
|
148
149
|
}
|
|
149
|
-
|
|
150
|
+
else {
|
|
151
|
+
container = renderMultiFlowResult(details, expanded, theme);
|
|
152
|
+
}
|
|
153
|
+
// Scramble animation timer — shared helper so any renderer can animate.
|
|
154
|
+
runScrambleTimer(args);
|
|
155
|
+
return container;
|
|
150
156
|
}
|
|
151
157
|
// ---------------------------------------------------------------------------
|
|
152
158
|
// Single flow result
|
|
153
159
|
// ---------------------------------------------------------------------------
|
|
154
|
-
function renderSingleFlowResult(r, expanded, theme, streamingText) {
|
|
160
|
+
function renderSingleFlowResult(r, expanded, theme, streamingText, toolCallId) {
|
|
161
|
+
const id = toolCallId || "single";
|
|
155
162
|
const error = isFlowError(r);
|
|
156
163
|
const icon = flowStatusIcon(r, theme);
|
|
157
164
|
const displayItems = getFlowDisplayItems(r.messages);
|
|
158
165
|
const flowOutput = getFlowOutput(r.messages);
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
const isComplete = r.exitCode !== -1;
|
|
159
168
|
if (expanded) {
|
|
160
|
-
return renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme);
|
|
169
|
+
return renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText);
|
|
161
170
|
}
|
|
162
|
-
return renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText);
|
|
171
|
+
return renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, id);
|
|
163
172
|
}
|
|
164
|
-
function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme) {
|
|
173
|
+
function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id, now, isComplete, streamingText) {
|
|
165
174
|
const mdTheme = getMarkdownTheme();
|
|
166
175
|
const container = new Container();
|
|
167
176
|
// Header: uppercase type name with dots, no icon, no source
|
|
@@ -169,22 +178,27 @@ function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme) {
|
|
|
169
178
|
let header = theme.fg("toolTitle", theme.bold(typeName));
|
|
170
179
|
if (error && r.stopReason)
|
|
171
180
|
header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
172
|
-
|
|
181
|
+
const plainHeader = typeName + (error && r.stopReason ? ` [${r.stopReason}]` : "");
|
|
182
|
+
const headerResult = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete);
|
|
183
|
+
container.addChild(new Text(headerResult.isAnimating ? theme.fg("toolTitle", headerResult.content) : header, 0, 0));
|
|
173
184
|
if (error && r.errorMessage) {
|
|
174
185
|
container.addChild(new Text(theme.fg("error", `Error: ${r.errorMessage}`), 0, 0));
|
|
175
186
|
}
|
|
176
187
|
// Stats: dashboard format
|
|
177
188
|
const inlineStats = formatCompactStats(r.usage, r.model);
|
|
178
|
-
|
|
189
|
+
const statsResult = scrambleManager.updateText(id, 'stats', stripAnsi(inlineStats), now, isComplete);
|
|
190
|
+
container.addChild(new Text(statsResult.isAnimating ? theme.fg("dim", statsResult.content) : theme.fg("dim", inlineStats), 0, 0));
|
|
179
191
|
// Intent
|
|
180
192
|
container.addChild(new Spacer(1));
|
|
181
193
|
container.addChild(new Text(theme.fg("muted", sectionHeader("intent")), 0, 0));
|
|
182
|
-
|
|
194
|
+
const intentResult = scrambleManager.updateText(id, 'intent', r.intent, now, isComplete);
|
|
195
|
+
container.addChild(new Text(intentResult.isAnimating ? theme.fg("dim", intentResult.content) : theme.fg("dim", r.intent), 0, 0));
|
|
183
196
|
// Acceptance
|
|
184
197
|
if (r.acceptance) {
|
|
185
198
|
container.addChild(new Spacer(1));
|
|
186
199
|
container.addChild(new Text(theme.fg("muted", sectionHeader("acceptance")), 0, 0));
|
|
187
|
-
|
|
200
|
+
const acceptanceResult = scrambleManager.updateText(id, 'acceptance', r.acceptance, now, isComplete);
|
|
201
|
+
container.addChild(new Text(acceptanceResult.isAnimating ? theme.fg("dim", acceptanceResult.content) : theme.fg("dim", r.acceptance), 0, 0));
|
|
188
202
|
}
|
|
189
203
|
// Flow report (structured output)
|
|
190
204
|
container.addChild(new Spacer(1));
|
|
@@ -193,144 +207,253 @@ function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme) {
|
|
|
193
207
|
if (r.structuredOutput) {
|
|
194
208
|
const so = r.structuredOutput;
|
|
195
209
|
const statusColor = so.status === "complete" ? "success" : so.status === "partial" ? "warning" : "error";
|
|
196
|
-
|
|
210
|
+
const statusText = `[${so.status}] ${so.summary}`;
|
|
211
|
+
const statusResult = scrambleManager.updateText(id, 'report-status', statusText, now, isComplete, false);
|
|
212
|
+
container.addChild(new Text(statusResult.isAnimating ? `${theme.fg(statusColor, statusResult.content.split(' ')[0])} ${theme.fg("dim", statusResult.content.slice(statusResult.content.indexOf(' ') + 1))}` : `${theme.fg(statusColor, `[${so.status}]`)} ${theme.fg("dim", so.summary)}`, 0, 0));
|
|
197
213
|
if (so.files.length > 0) {
|
|
198
|
-
|
|
214
|
+
const filesText = `Files: ${so.files.map((f) => f.path).join(", ")}`;
|
|
215
|
+
const filesResult = scrambleManager.updateText(id, 'report-files', filesText, now, isComplete, false);
|
|
216
|
+
container.addChild(new Text(filesResult.isAnimating ? theme.fg("dim", filesResult.content) : theme.fg("dim", filesText), 0, 0));
|
|
199
217
|
}
|
|
200
218
|
if (so.commands?.length > 0) {
|
|
201
219
|
const cmdLabels = so.commands.map((c) => {
|
|
202
220
|
const short = c.command.length > 30 ? c.command.slice(0, 30) + "..." : c.command;
|
|
203
221
|
return `${c.tool ?? "cmd"}: ${short}`;
|
|
204
222
|
});
|
|
205
|
-
|
|
223
|
+
const commandsText = `Commands: ${cmdLabels.join(", ")}`;
|
|
224
|
+
const commandsResult = scrambleManager.updateText(id, 'report-commands', commandsText, now, isComplete, false);
|
|
225
|
+
container.addChild(new Text(commandsResult.isAnimating ? theme.fg("dim", commandsResult.content) : theme.fg("dim", commandsText), 0, 0));
|
|
206
226
|
}
|
|
207
227
|
if (so.notDone.length > 0) {
|
|
208
|
-
const notDoneText = so.notDone.map((item) => {
|
|
228
|
+
const notDoneText = `Not Done: ${so.notDone.map((item) => {
|
|
209
229
|
const details = [
|
|
210
230
|
item.reason ? `reason: ${item.reason}` : undefined,
|
|
211
231
|
item.blocker ? `blocker: ${item.blocker}` : undefined,
|
|
212
232
|
item.nextStep ? `next: ${item.nextStep}` : undefined,
|
|
213
233
|
].filter(Boolean).join("; ");
|
|
214
234
|
return details ? `${item.item} (${details})` : item.item;
|
|
215
|
-
}).join("; ")
|
|
216
|
-
|
|
235
|
+
}).join("; ")}`;
|
|
236
|
+
const notDoneResult = scrambleManager.updateText(id, 'report-notDone', notDoneText, now, isComplete, false);
|
|
237
|
+
container.addChild(new Text(notDoneResult.isAnimating ? theme.fg("dim", notDoneResult.content) : theme.fg("dim", notDoneText), 0, 0));
|
|
217
238
|
}
|
|
218
239
|
if (so.nextSteps.length > 0) {
|
|
219
|
-
|
|
240
|
+
const nextStepsText = `Next: ${so.nextSteps.join("; ")}`;
|
|
241
|
+
const nextStepsResult = scrambleManager.updateText(id, 'report-nextSteps', nextStepsText, now, isComplete, false);
|
|
242
|
+
container.addChild(new Text(nextStepsResult.isAnimating ? theme.fg("dim", nextStepsResult.content) : theme.fg("dim", nextStepsText), 0, 0));
|
|
220
243
|
}
|
|
221
244
|
container.addChild(new Spacer(1));
|
|
222
245
|
}
|
|
223
|
-
|
|
246
|
+
// Output: animate streaming text; show clean markdown when complete
|
|
247
|
+
if (!isComplete && streamingText) {
|
|
248
|
+
const scrambled = scrambleManager.updateMsg(id, stripAnsi(streamingText), now, isComplete).content;
|
|
249
|
+
container.addChild(new Text(scrambled, 0, 0));
|
|
250
|
+
}
|
|
251
|
+
else if (flowOutput) {
|
|
224
252
|
container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
|
|
225
253
|
}
|
|
226
254
|
else {
|
|
227
255
|
const summary = getFlowSummaryText(r);
|
|
228
|
-
|
|
256
|
+
const summaryResult = scrambleManager.updateText(id, 'output-summary', summary, now, isComplete, false);
|
|
257
|
+
container.addChild(new Text(summaryResult.isAnimating ? theme.fg("muted", summaryResult.content) : theme.fg("muted", summary), 0, 0));
|
|
229
258
|
}
|
|
230
|
-
// Tool traces (expanded only)
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
259
|
+
// Tool traces (expanded only) — per-line scramble
|
|
260
|
+
const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
|
|
261
|
+
if (toolCallItems.length > 0) {
|
|
233
262
|
container.addChild(new Spacer(1));
|
|
234
263
|
container.addChild(new Text(theme.fg("muted", sectionHeader("tool calls")), 0, 0));
|
|
235
|
-
|
|
264
|
+
for (let i = 0; i < toolCallItems.length; i++) {
|
|
265
|
+
const item = toolCallItems[i];
|
|
266
|
+
const lineText = theme.fg("muted", "→ ") + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
|
|
267
|
+
const plainText = stripAnsi(lineText);
|
|
268
|
+
const scrambled = scrambleManager.updateText(id, `tool#${i}`, plainText, now, isComplete).content;
|
|
269
|
+
container.addChild(new Text(scrambled, 0, 0));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (isComplete) {
|
|
273
|
+
scrambleManager.completeFlow(id);
|
|
236
274
|
}
|
|
237
275
|
return container;
|
|
238
276
|
}
|
|
239
|
-
function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText) {
|
|
277
|
+
function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, toolCallId) {
|
|
278
|
+
const id = toolCallId || "collapsed";
|
|
279
|
+
const now = Date.now();
|
|
240
280
|
const container = new Container();
|
|
241
281
|
const maxWidth = process.stdout.columns ?? 80;
|
|
242
282
|
const stats = formatCompactStats(r.usage, r.model, maxWidth, { skipTokens: true, skipContext: true, hideModel: true });
|
|
283
|
+
const isComplete = r.exitCode !== -1;
|
|
284
|
+
// Flash TPS value when it changes
|
|
285
|
+
const tpsMatch = stats.match(/tps:\s*(\S+)/);
|
|
286
|
+
let displayStats = stats;
|
|
287
|
+
if (tpsMatch) {
|
|
288
|
+
const scrambledTps = scrambleManager.updateTps(id, tpsMatch[1], now, isComplete, true);
|
|
289
|
+
if (scrambledTps !== tpsMatch[1]) {
|
|
290
|
+
displayStats = stats.replace(tpsMatch[1], scrambledTps);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
243
293
|
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
244
294
|
const modelLabel = r.model ? r.model.replace(/^[^/]+\//, "").toLowerCase() : "";
|
|
245
|
-
let header = `${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", modelLabel ? ` - ${modelLabel} - ` : " - ")}${theme.fg("dim",
|
|
295
|
+
let header = `${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", modelLabel ? ` - ${modelLabel} - ` : " - ")}${theme.fg("dim", displayStats)}`;
|
|
246
296
|
if (error && r.stopReason)
|
|
247
297
|
header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
248
|
-
|
|
249
|
-
|
|
298
|
+
// Scramble header on first render; show full styled header when complete
|
|
299
|
+
const plainHeader = typeName + (modelLabel ? ` - ${modelLabel} - ` : " - ") + stripAnsi(displayStats) + (error && r.stopReason ? ` [${r.stopReason}]` : "");
|
|
300
|
+
const headerResult = scrambleManager.updateText(id, 'header', plainHeader, now, isComplete, true);
|
|
301
|
+
const headerDisplay = headerResult.isAnimating ? theme.fg("accent", headerResult.content) : header;
|
|
302
|
+
container.addChild(new TruncatedText(headerDisplay, 0, 0));
|
|
303
|
+
// aim: line — cascade/ripple/illuminate on text change
|
|
250
304
|
if (r.aim) {
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
|
|
305
|
+
const countdown = getLiveCountdown(r);
|
|
306
|
+
const treePrefix = "├─";
|
|
307
|
+
const aimPrefix = countdown
|
|
308
|
+
? `${treePrefix} aim: [${countdown}] - `
|
|
309
|
+
: `${treePrefix} aim: `;
|
|
310
|
+
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
311
|
+
const displayAim = truncateChars(lowerFirstWord(r.aim), budget);
|
|
312
|
+
const aimResult = scrambleManager.updateAim(id, displayAim, now, isComplete, true);
|
|
313
|
+
const aimContent = aimResult.content;
|
|
314
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", aimPrefix)}${theme.fg("dim", italic(aimContent))}`, 0, 0));
|
|
254
315
|
}
|
|
255
316
|
// act: line (last tool call with count)
|
|
256
317
|
const lastTool = getLastToolCall(r.messages);
|
|
257
318
|
if (lastTool) {
|
|
258
319
|
const actStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
|
|
259
|
-
const
|
|
260
|
-
const
|
|
320
|
+
const prefixStub = `├─ act: [${r.usage.toolCalls}] - `;
|
|
321
|
+
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
322
|
+
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
323
|
+
let actContent;
|
|
324
|
+
if (scrambleManager.getMode() === 'stream') {
|
|
325
|
+
actContent = scrambleManager.streamAct(id, actFullText, now, isComplete, budget);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
const displayAct = truncateChars(actFullText, budget);
|
|
329
|
+
actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
|
|
330
|
+
}
|
|
331
|
+
let actKpi = String(r.usage.toolCalls);
|
|
332
|
+
const scrambledActKpi = scrambleManager.updateActKpi(id, actKpi, now, isComplete, true);
|
|
333
|
+
if (scrambledActKpi !== actKpi) {
|
|
334
|
+
actKpi = scrambledActKpi;
|
|
335
|
+
}
|
|
336
|
+
const actPrefix = `├─ act: [${actKpi}] - `;
|
|
261
337
|
container.addChild(new TruncatedText(`${theme.fg("dim", actPrefix)}${italic(actContent)}`, 0, 0));
|
|
262
338
|
}
|
|
263
339
|
// msg: line (last assistant text or streaming)
|
|
264
|
-
|
|
265
|
-
const
|
|
340
|
+
let msgKpi = formatCompactTokenPair(r.usage);
|
|
341
|
+
const scrambledMsgKpi = scrambleManager.updateMsgKpi(id, msgKpi, now, isComplete, false);
|
|
342
|
+
if (scrambledMsgKpi !== msgKpi) {
|
|
343
|
+
msgKpi = scrambledMsgKpi;
|
|
344
|
+
}
|
|
345
|
+
const msgPrefixStub = `└─ msg: [${msgKpi}] - `;
|
|
346
|
+
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
347
|
+
let rawMsg;
|
|
348
|
+
let useError = false;
|
|
266
349
|
if (r.exitCode === -1 && streamingText) {
|
|
267
|
-
|
|
268
|
-
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", italic(logContent))}`, 0, 0));
|
|
350
|
+
rawMsg = stripAnsi(streamingText);
|
|
269
351
|
}
|
|
270
352
|
else if (r.structuredOutput?.summary) {
|
|
271
|
-
|
|
272
|
-
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", italic(logContent))}`, 0, 0));
|
|
353
|
+
rawMsg = stripAnsi(r.structuredOutput.summary);
|
|
273
354
|
}
|
|
274
355
|
else if (flowOutput) {
|
|
275
|
-
|
|
276
|
-
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", italic(logContent))}`, 0, 0));
|
|
356
|
+
rawMsg = stripAnsi(flowOutput);
|
|
277
357
|
}
|
|
278
358
|
else if (streamingText) {
|
|
279
|
-
|
|
280
|
-
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", italic(logContent))}`, 0, 0));
|
|
359
|
+
rawMsg = stripAnsi(streamingText);
|
|
281
360
|
}
|
|
282
361
|
else if (error && r.errorMessage) {
|
|
283
|
-
|
|
284
|
-
|
|
362
|
+
rawMsg = stripAnsi(r.errorMessage);
|
|
363
|
+
useError = true;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
rawMsg = "[n/a]";
|
|
367
|
+
}
|
|
368
|
+
let msgContent;
|
|
369
|
+
if (scrambleManager.getMode() === 'stream') {
|
|
370
|
+
msgContent = scrambleManager.streamMsg(id, rawMsg, now, isComplete, msgBudget);
|
|
285
371
|
}
|
|
286
372
|
else {
|
|
287
|
-
|
|
373
|
+
// For active (incomplete) flows, pass full text to keep animation stable.
|
|
374
|
+
// TruncatedText handles display truncation. Completed flows truncate as before.
|
|
375
|
+
if (!isComplete) {
|
|
376
|
+
msgContent = scrambleManager.updateMsg(id, rawMsg, now, isComplete, undefined, true).content;
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const needsTail = (r.exitCode === -1 && streamingText) || streamingText;
|
|
380
|
+
const displayMsg = needsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
|
|
381
|
+
msgContent = scrambleManager.updateMsg(id, displayMsg, now, isComplete, undefined, true).content;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const msgPrefix = `└─ msg: [${msgKpi}] - `;
|
|
385
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg(useError ? "error" : "dim", italic(msgContent))}`, 0, 0));
|
|
386
|
+
if (isComplete) {
|
|
387
|
+
scrambleManager.completeFlow(id);
|
|
288
388
|
}
|
|
289
389
|
return container;
|
|
290
390
|
}
|
|
291
391
|
// ---------------------------------------------------------------------------
|
|
292
392
|
// Multi-flow result
|
|
293
393
|
// ---------------------------------------------------------------------------
|
|
294
|
-
function renderMultiFlowResult(details, expanded, theme) {
|
|
394
|
+
function renderMultiFlowResult(details, expanded, theme, toolCallId) {
|
|
395
|
+
const baseId = toolCallId || "multi";
|
|
295
396
|
const results = details.results;
|
|
296
397
|
const successCount = results.filter((r) => isFlowSuccess(r)).length;
|
|
297
398
|
const failCount = results.filter((r) => isFlowError(r)).length;
|
|
298
|
-
const icon = failCount > 0 ? theme.fg("warning", "◐") : theme.fg("success", "
|
|
399
|
+
const icon = failCount > 0 ? theme.fg("warning", "◐") : theme.fg("success", "✔");
|
|
400
|
+
const now = Date.now();
|
|
299
401
|
if (expanded) {
|
|
300
|
-
return renderMultiFlowExpanded(results, successCount, icon, theme);
|
|
402
|
+
return renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now);
|
|
301
403
|
}
|
|
302
|
-
return renderMultiFlowCollapsed(results, theme);
|
|
404
|
+
return renderMultiFlowCollapsed(results, theme, baseId);
|
|
303
405
|
}
|
|
304
|
-
function renderMultiFlowExpanded(results, successCount, icon, theme) {
|
|
406
|
+
function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now) {
|
|
305
407
|
const mdTheme = getMarkdownTheme();
|
|
306
408
|
const container = new Container();
|
|
307
409
|
// Summary: just show count, no icon
|
|
308
410
|
container.addChild(new Text(theme.fg("accent", `${results.length} flows`), 0, 0));
|
|
309
|
-
for (
|
|
411
|
+
for (let flowIdx = 0; flowIdx < results.length; flowIdx++) {
|
|
412
|
+
const r = results[flowIdx];
|
|
413
|
+
const flowId = `${baseId}#${flowIdx}`;
|
|
414
|
+
const isComplete = r.exitCode !== -1;
|
|
310
415
|
const displayItems = getFlowDisplayItems(r.messages);
|
|
311
416
|
const flowOutput = getFlowOutput(r.messages);
|
|
312
417
|
const typeName = formatFlowTypeName(r.type);
|
|
313
418
|
container.addChild(new Spacer(1));
|
|
314
419
|
// Per-flow header: ─── EXPLORER (no icon)
|
|
315
|
-
|
|
420
|
+
const headerResult = scrambleManager.updateText(flowId, 'header', typeName, now, isComplete, true);
|
|
421
|
+
container.addChild(new Text(headerResult.isAnimating ? theme.fg("muted", headerResult.content) : theme.fg("muted", sectionHeader(typeName)), 0, 0));
|
|
316
422
|
// Stats: dashboard format
|
|
317
423
|
const flowStats = formatCompactStats(r.usage, r.model);
|
|
318
|
-
|
|
424
|
+
const statsResult = scrambleManager.updateText(flowId, 'stats', stripAnsi(flowStats), now, isComplete, true);
|
|
425
|
+
container.addChild(new Text(statsResult.isAnimating ? theme.fg("dim", statsResult.content) : theme.fg("dim", flowStats), 0, 0));
|
|
319
426
|
// Intent: just show text, no prefix
|
|
320
|
-
|
|
427
|
+
const intentResult = scrambleManager.updateText(flowId, 'intent', r.intent, now, isComplete, true);
|
|
428
|
+
container.addChild(new Text(intentResult.isAnimating ? theme.fg("dim", intentResult.content) : theme.fg("dim", r.intent), 0, 0));
|
|
321
429
|
if (r.acceptance) {
|
|
322
|
-
|
|
430
|
+
const acceptanceResult = scrambleManager.updateText(flowId, 'acceptance', r.acceptance, now, isComplete, true);
|
|
431
|
+
container.addChild(new Text(acceptanceResult.isAnimating ? theme.fg("dim", acceptanceResult.content) : theme.fg("dim", `Acceptance: ${r.acceptance}`), 0, 0));
|
|
323
432
|
}
|
|
324
|
-
|
|
433
|
+
// Output: animate streaming text; show clean markdown when complete
|
|
434
|
+
if (!isComplete && r.streamingText) {
|
|
435
|
+
const scrambled = scrambleManager.updateMsg(flowId, stripAnsi(r.streamingText), now, isComplete, undefined, true).content;
|
|
436
|
+
container.addChild(new Text(scrambled, 0, 0));
|
|
437
|
+
}
|
|
438
|
+
else if (flowOutput) {
|
|
325
439
|
container.addChild(new Spacer(1));
|
|
326
440
|
container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
|
|
327
441
|
}
|
|
328
|
-
// Tool traces in expanded view
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
442
|
+
// Tool traces in expanded view — per-line scramble
|
|
443
|
+
const toolCallItems = displayItems.filter((item) => item.type === "toolCall");
|
|
444
|
+
if (toolCallItems.length > 0) {
|
|
331
445
|
container.addChild(new Spacer(1));
|
|
332
446
|
container.addChild(new Text(theme.fg("muted", sectionHeader("tool calls")), 0, 0));
|
|
333
|
-
|
|
447
|
+
for (let i = 0; i < toolCallItems.length; i++) {
|
|
448
|
+
const item = toolCallItems[i];
|
|
449
|
+
const lineText = theme.fg("muted", "→ ") + formatFlowToolCall(item.name, item.args, theme.fg.bind(theme));
|
|
450
|
+
const plainText = stripAnsi(lineText);
|
|
451
|
+
const scrambled = scrambleManager.updateText(flowId, `tool#${i}`, plainText, now, isComplete).content;
|
|
452
|
+
container.addChild(new Text(scrambled, 0, 0));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (isComplete) {
|
|
456
|
+
scrambleManager.completeFlow(flowId);
|
|
334
457
|
}
|
|
335
458
|
}
|
|
336
459
|
// Total stats: dashboard format
|
|
@@ -341,54 +464,119 @@ function renderMultiFlowExpanded(results, successCount, icon, theme) {
|
|
|
341
464
|
container.addChild(new Text(theme.fg("dim", totalStats), 0, 0));
|
|
342
465
|
return container;
|
|
343
466
|
}
|
|
344
|
-
function renderActivityPanel(results, theme) {
|
|
467
|
+
function renderActivityPanel(results, theme, baseId) {
|
|
468
|
+
const idPrefix = baseId || "panel";
|
|
345
469
|
const container = new Container();
|
|
346
470
|
const maxWidth = process.stdout.columns ?? 80;
|
|
471
|
+
const now = Date.now();
|
|
347
472
|
for (let i = 0; i < results.length; i++) {
|
|
348
473
|
const r = results[i];
|
|
349
474
|
const isLast = i === results.length - 1;
|
|
475
|
+
const flowId = `${idPrefix}#${i}`;
|
|
350
476
|
const stats = formatCompactStats(r.usage, r.model, maxWidth, { skipTokens: true, skipContext: true, hideModel: true });
|
|
477
|
+
// Flash TPS value when it changes
|
|
478
|
+
const tpsMatch = stats.match(/tps:\s*(\S+)/);
|
|
479
|
+
const flowComplete = r.exitCode !== -1;
|
|
480
|
+
let displayStats = stats;
|
|
481
|
+
if (tpsMatch) {
|
|
482
|
+
const scrambledTps = scrambleManager.updateTps(flowId, tpsMatch[1], now, flowComplete, true);
|
|
483
|
+
if (scrambledTps !== tpsMatch[1]) {
|
|
484
|
+
displayStats = stats.replace(tpsMatch[1], scrambledTps);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
351
487
|
const error = isFlowError(r);
|
|
352
488
|
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
353
489
|
// Header line
|
|
354
490
|
const headerPrefix = isLast ? "└─" : "├─";
|
|
355
491
|
const modelLabel = r.model ? r.model.replace(/^[^/]+\//, "").toLowerCase() : "";
|
|
356
|
-
let headerLine = `${theme.fg("dim", headerPrefix)} ${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", modelLabel ? ` - ${modelLabel} - ` : " - ")}${theme.fg("dim",
|
|
492
|
+
let headerLine = `${theme.fg("dim", headerPrefix)} ${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", modelLabel ? ` - ${modelLabel} - ` : " - ")}${theme.fg("dim", displayStats)}`;
|
|
357
493
|
if (error && r.stopReason) {
|
|
358
494
|
headerLine += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
359
495
|
}
|
|
360
|
-
|
|
496
|
+
const plainHeader = headerPrefix + " " + typeName + (modelLabel ? ` - ${modelLabel} - ` : " - ") + stripAnsi(displayStats) + (error && r.stopReason ? ` [${r.stopReason}]` : "");
|
|
497
|
+
const headerResult = scrambleManager.updateText(flowId, 'header', plainHeader, now, flowComplete, true);
|
|
498
|
+
const headerDisplay = headerResult.isAnimating ? theme.fg("accent", headerResult.content) : headerLine;
|
|
499
|
+
container.addChild(new TruncatedText(headerDisplay, 0, 0));
|
|
361
500
|
// Continuation indent for sub-lines
|
|
362
501
|
const indent = isLast ? " " : "│ ";
|
|
363
|
-
// aim: line
|
|
502
|
+
// aim: line — cascade/ripple/illuminate on text change
|
|
364
503
|
if (r.aim) {
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
|
|
504
|
+
const countdown = getLiveCountdown(r);
|
|
505
|
+
const treePrefix = indent + "├─";
|
|
506
|
+
const aimPrefix = countdown
|
|
507
|
+
? `${treePrefix} aim: [${countdown}] - `
|
|
508
|
+
: `${treePrefix} aim: `;
|
|
509
|
+
const budget = getTruncationBudget(visibleLength(aimPrefix));
|
|
510
|
+
const displayAim = truncateChars(lowerFirstWord(r.aim), budget);
|
|
511
|
+
const aimResult = scrambleManager.updateAim(flowId, displayAim, now, flowComplete, true);
|
|
512
|
+
const aimContent = aimResult.content;
|
|
513
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", aimPrefix)}${theme.fg("dim", italic(aimContent))}`, 0, 0));
|
|
368
514
|
}
|
|
369
515
|
// act: line (last tool call with count)
|
|
370
516
|
const lastTool = getLastToolCall(r.messages);
|
|
371
517
|
if (lastTool) {
|
|
372
518
|
const actStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
|
|
373
|
-
const
|
|
374
|
-
const
|
|
519
|
+
const prefixStub = `${indent}├─ act: [${r.usage.toolCalls}] - `;
|
|
520
|
+
const budget = getTruncationBudget(visibleLength(prefixStub));
|
|
521
|
+
const actFullText = stripAnsi(lowerFirstWord(actStr));
|
|
522
|
+
let actContent;
|
|
523
|
+
if (scrambleManager.getMode() === 'stream') {
|
|
524
|
+
actContent = scrambleManager.streamAct(flowId, actFullText, now, flowComplete, budget);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
const displayAct = truncateChars(actFullText, budget);
|
|
528
|
+
actContent = scrambleManager.updateAct(flowId, displayAct, now, flowComplete, true).content;
|
|
529
|
+
}
|
|
530
|
+
let actKpi = String(r.usage.toolCalls);
|
|
531
|
+
const scrambledActKpi = scrambleManager.updateActKpi(flowId, actKpi, now, flowComplete, false);
|
|
532
|
+
if (scrambledActKpi !== actKpi) {
|
|
533
|
+
actKpi = scrambledActKpi;
|
|
534
|
+
}
|
|
535
|
+
const actPrefix = `${indent}├─ act: [${actKpi}] - `;
|
|
375
536
|
container.addChild(new TruncatedText(`${theme.fg("dim", actPrefix)}${italic(actContent)}`, 0, 0));
|
|
376
537
|
}
|
|
377
538
|
// msg: line (live streaming text or last assistant text)
|
|
378
|
-
|
|
379
|
-
const
|
|
539
|
+
let msgKpi = formatCompactTokenPair(r.usage);
|
|
540
|
+
const scrambledMsgKpi = scrambleManager.updateMsgKpi(flowId, msgKpi, now, flowComplete, false);
|
|
541
|
+
if (scrambledMsgKpi !== msgKpi) {
|
|
542
|
+
msgKpi = scrambledMsgKpi;
|
|
543
|
+
}
|
|
544
|
+
const msgPrefixStub = `${indent}└─ msg: [${msgKpi}] - `;
|
|
545
|
+
const msgBudget = getTruncationBudget(visibleLength(msgPrefixStub));
|
|
380
546
|
const liveText = r.exitCode === -1 ? r.streamingText : undefined;
|
|
381
547
|
const lastText = liveText || getLastAssistantText(r.messages);
|
|
548
|
+
let rawMsg;
|
|
549
|
+
let useError = false;
|
|
382
550
|
if (lastText) {
|
|
383
|
-
|
|
384
|
-
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", italic(logContent))}`, 0, 0));
|
|
551
|
+
rawMsg = stripAnsi(lastText);
|
|
385
552
|
}
|
|
386
553
|
else if (error && r.errorMessage) {
|
|
387
|
-
|
|
388
|
-
|
|
554
|
+
rawMsg = stripAnsi(r.errorMessage);
|
|
555
|
+
useError = true;
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
rawMsg = "[n/a]";
|
|
559
|
+
}
|
|
560
|
+
let msgContent;
|
|
561
|
+
if (scrambleManager.getMode() === 'stream') {
|
|
562
|
+
msgContent = scrambleManager.streamMsg(flowId, rawMsg, now, flowComplete, msgBudget);
|
|
389
563
|
}
|
|
390
564
|
else {
|
|
391
|
-
|
|
565
|
+
// For active (incomplete) flows, pass full text to keep animation stable.
|
|
566
|
+
// TruncatedText handles display truncation. Completed flows truncate as before.
|
|
567
|
+
if (!flowComplete) {
|
|
568
|
+
msgContent = scrambleManager.updateMsg(flowId, rawMsg, now, flowComplete, undefined, true).content;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
const needsTail = Boolean(liveText || lastText);
|
|
572
|
+
const displayMsg = needsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
|
|
573
|
+
msgContent = scrambleManager.updateMsg(flowId, displayMsg, now, flowComplete).content;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const msgPrefix = `${indent}└─ msg: [${msgKpi}] - `;
|
|
577
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg(useError ? "error" : "dim", italic(msgContent))}`, 0, 0));
|
|
578
|
+
if (flowComplete) {
|
|
579
|
+
scrambleManager.completeFlow(flowId);
|
|
392
580
|
}
|
|
393
581
|
// Add blank line separator between flows (with continuation pipe)
|
|
394
582
|
if (!isLast) {
|
|
@@ -398,7 +586,7 @@ function renderActivityPanel(results, theme) {
|
|
|
398
586
|
container.addChild(new TruncatedText(theme.fg("muted", "(Ctrl+O to expand tool traces)"), 0, 0));
|
|
399
587
|
return container;
|
|
400
588
|
}
|
|
401
|
-
function renderMultiFlowCollapsed(results, theme) {
|
|
402
|
-
return renderActivityPanel(results, theme);
|
|
589
|
+
function renderMultiFlowCollapsed(results, theme, baseId) {
|
|
590
|
+
return renderActivityPanel(results, theme, baseId);
|
|
403
591
|
}
|
|
404
592
|
//# sourceMappingURL=render.js.map
|