pi-agent-flow 0.4.0 → 0.4.3
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 +0 -5
- package/index.ts +0 -4
- package/package.json +1 -1
- package/render-utils.ts +29 -39
- package/render.ts +26 -24
- package/runner-events.js +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
<p align="center"><code>pi install npm:pi-agent-flow</code></p>
|
|
2
2
|
<p align="center"><strong>Pi Agent Flow</strong> is a flow-state delegation extension for the <a href="https://pi.dev">Pi coding agent</a> that runs locally in your terminal.</p>
|
|
3
|
-
<p align="center">
|
|
4
|
-
<img src="https://github.com/user-attachments/assets/pi-agent-flow-demo.png" alt="Pi Agent Flow demo" width="80%" />
|
|
5
|
-
</p>
|
|
6
|
-
<br/>
|
|
7
3
|
|
|
8
4
|
---
|
|
9
5
|
|
|
@@ -162,7 +158,6 @@ flow [explore] accomplished
|
|
|
162
158
|
## Docs
|
|
163
159
|
|
|
164
160
|
- [**Pi Documentation**](https://pi.dev)
|
|
165
|
-
- [**Contributing**](./docs/contributing.md) *(if available)*
|
|
166
161
|
- [**License**](./LICENSE)
|
|
167
162
|
|
|
168
163
|
This repository is licensed under the [MIT License](LICENSE).
|
package/index.ts
CHANGED
|
@@ -284,10 +284,6 @@ function makeFlowDetailsFactory(projectFlowsDir: string | null) {
|
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
function formatFlowNames(flows: FlowConfig[]): string {
|
|
288
|
-
return flows.map((f) => `${f.name} (${f.source})`).join(", ") || "none";
|
|
289
|
-
}
|
|
290
|
-
|
|
291
287
|
function getFlowCycleViolations(
|
|
292
288
|
requestedNames: Set<string>,
|
|
293
289
|
ancestorFlowStack: string[],
|
package/package.json
CHANGED
package/render-utils.ts
CHANGED
|
@@ -4,27 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import type { UsageStats } from "./types.js";
|
|
6
6
|
|
|
7
|
-
export function formatTokens(count: number): string {
|
|
8
|
-
if (count < 1000) return count.toString();
|
|
9
|
-
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
10
|
-
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
11
|
-
return `${(count / 1000000).toFixed(1)}M`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function formatFlowUsage(usage: Partial<UsageStats>, model?: string): string {
|
|
15
|
-
const parts: string[] = [];
|
|
16
|
-
if (usage.toolCalls && usage.toolCalls > 0) parts.push(`${usage.toolCalls} calls`);
|
|
17
|
-
if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
|
|
18
|
-
if (usage.input) parts.push(`↑${formatTokens(usage.input)}`);
|
|
19
|
-
if (usage.output) parts.push(`↓${formatTokens(usage.output)}`);
|
|
20
|
-
if (usage.cacheRead) parts.push(`CR:${formatTokens(usage.cacheRead)}`);
|
|
21
|
-
if (usage.cacheWrite) parts.push(`CW:${formatTokens(usage.cacheWrite)}`);
|
|
22
|
-
if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
|
|
23
|
-
if (usage.contextTokens && usage.contextTokens > 0) parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
|
|
24
|
-
if (model) parts.push(`model:${model}`);
|
|
25
|
-
return parts.join(" ");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
7
|
/**
|
|
29
8
|
* Format a token count to exactly 5 characters with leading spaces.
|
|
30
9
|
* Shifts from k to M when value would exceed 5 chars.
|
|
@@ -58,30 +37,35 @@ export function formatFlowTypeName(type: string): string {
|
|
|
58
37
|
return lower + ".".repeat(targetWidth - lower.length);
|
|
59
38
|
}
|
|
60
39
|
|
|
61
|
-
export function formatCompactStats(usage: Partial<UsageStats>, model?: string): string {
|
|
40
|
+
export function formatCompactStats(usage: Partial<UsageStats>, model?: string, maxWidth?: number): string {
|
|
62
41
|
const parts: string[] = [];
|
|
63
42
|
parts.push(`${formatFixedTokens(usage.input || 0)}↑`);
|
|
64
43
|
parts.push(`${formatFixedTokens(usage.output || 0)}↓`);
|
|
65
44
|
if (usage.cacheRead) parts.push(`cr:${formatFixedTokens(usage.cacheRead)}`);
|
|
66
45
|
if (usage.contextTokens && usage.contextTokens > 0) parts.push(`ctx:${formatFixedTokens(usage.contextTokens)}`);
|
|
67
|
-
return `[ ${parts.join(" ")} ]${model ? ` ─ ${model}` : ""}`;
|
|
68
|
-
}
|
|
69
46
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
47
|
+
let result = `[ ${parts.join(" ")} ]${model ? ` ─ ${model}` : ""}`;
|
|
48
|
+
|
|
49
|
+
if (maxWidth && visibleLength(result) > maxWidth) {
|
|
50
|
+
// Drop model first
|
|
51
|
+
let narrow = `[ ${parts.join(" ")} ]`;
|
|
52
|
+
if (visibleLength(narrow) <= maxWidth) return narrow;
|
|
53
|
+
|
|
54
|
+
// Drop context tokens
|
|
55
|
+
const narrowParts = parts.slice();
|
|
56
|
+
const ctxIndex = narrowParts.findIndex((p) => p.startsWith("ctx:"));
|
|
57
|
+
if (ctxIndex !== -1) narrowParts.splice(ctxIndex, 1);
|
|
58
|
+
narrow = `[ ${narrowParts.join(" ")} ]`;
|
|
59
|
+
if (visibleLength(narrow) <= maxWidth) return narrow;
|
|
80
60
|
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
// Bare minimum (just input/output)
|
|
62
|
+
narrow = `[ ${parts[0]} ${parts[1]} ]`;
|
|
63
|
+
if (visibleLength(narrow) <= maxWidth) return narrow;
|
|
83
64
|
|
|
84
|
-
|
|
65
|
+
return truncateChars(result, maxWidth);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result;
|
|
85
69
|
}
|
|
86
70
|
|
|
87
71
|
/** Regex matching ANSI escape sequences. */
|
|
@@ -105,12 +89,18 @@ export function getTruncationBudget(prefixLength: number): number {
|
|
|
105
89
|
|
|
106
90
|
/**
|
|
107
91
|
* Truncate an ANSI-colored string to at most `max` visible characters,
|
|
108
|
-
* preserving ANSI codes in the kept portions
|
|
109
|
-
* the
|
|
92
|
+
* preserving ANSI codes in the kept portions. Does not inject reset codes
|
|
93
|
+
* — the caller is responsible for closing any open styles.
|
|
110
94
|
*/
|
|
111
95
|
function truncateAnsi(text: string, max: number): string {
|
|
112
96
|
if (visibleLength(text) <= max) return text;
|
|
113
97
|
|
|
98
|
+
if (max < 6) {
|
|
99
|
+
// Not enough room for " ... " — just truncate without ellipsis
|
|
100
|
+
const { raw } = takeVisible(text, max);
|
|
101
|
+
return raw;
|
|
102
|
+
}
|
|
103
|
+
|
|
114
104
|
const head = Math.ceil(max * 0.6);
|
|
115
105
|
const tail = max - head - 5; // 5 = " ... ".length
|
|
116
106
|
|
package/render.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
|
|
10
|
-
import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
|
10
|
+
import { Container, Markdown, Spacer, Text, TruncatedText } from "@mariozechner/pi-tui";
|
|
11
11
|
import { getFlowSummaryText } from "./runner-events.js";
|
|
12
12
|
import {
|
|
13
13
|
type DisplayItem,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
isFlowError,
|
|
23
23
|
isFlowSuccess,
|
|
24
24
|
} from "./types.js";
|
|
25
|
-
import {
|
|
25
|
+
import { formatFixedTokens, formatCompactStats, formatFlowTypeName } from "./render-utils.js";
|
|
26
26
|
|
|
27
27
|
function shortenPath(p: string): string {
|
|
28
28
|
const home = os.homedir();
|
|
@@ -217,8 +217,6 @@ function renderFlowExpanded(
|
|
|
217
217
|
return container;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
222
220
|
function renderFlowCollapsed(
|
|
223
221
|
r: SingleResult,
|
|
224
222
|
icon: string,
|
|
@@ -226,36 +224,39 @@ function renderFlowCollapsed(
|
|
|
226
224
|
flowOutput: string,
|
|
227
225
|
theme: FlowTheme,
|
|
228
226
|
streamingText?: string,
|
|
229
|
-
):
|
|
230
|
-
const
|
|
227
|
+
): Container {
|
|
228
|
+
const container = new Container();
|
|
229
|
+
const maxWidth = process.stdout.columns ?? 80;
|
|
230
|
+
const stats = formatCompactStats(r.usage, r.model, maxWidth);
|
|
231
231
|
const typeName = formatFlowTypeName(r.type);
|
|
232
|
-
let
|
|
233
|
-
if (error && r.stopReason)
|
|
232
|
+
let header = `${theme.fg("accent", theme.bold(typeName))} ${theme.fg("dim", "─")} ${theme.fg("dim", stats)}`;
|
|
233
|
+
if (error && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
234
|
+
container.addChild(new TruncatedText(header, 0, 0));
|
|
234
235
|
|
|
235
236
|
// dir: line (intent/objective)
|
|
236
237
|
if (r.intent) {
|
|
237
|
-
|
|
238
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", "├─ dir:")} ${theme.fg("dim", r.intent)}`, 0, 0));
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
// exe: line (last tool call)
|
|
241
242
|
const lastTool = getLastToolCall(r.messages);
|
|
242
243
|
if (lastTool) {
|
|
243
244
|
const exeStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
|
|
244
|
-
|
|
245
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", "├─ exe:")} ${exeStr}`, 0, 0));
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
// log: line (last assistant text or streaming)
|
|
248
249
|
if (flowOutput) {
|
|
249
|
-
|
|
250
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", flowOutput)}`, 0, 0));
|
|
250
251
|
} else if (streamingText) {
|
|
251
|
-
|
|
252
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", streamingText)}`, 0, 0));
|
|
252
253
|
} else if (error && r.errorMessage) {
|
|
253
|
-
|
|
254
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("error", r.errorMessage)}`, 0, 0));
|
|
254
255
|
} else {
|
|
255
|
-
|
|
256
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", "[n/a]")}`, 0, 0));
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
return
|
|
259
|
+
return container;
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
// ---------------------------------------------------------------------------
|
|
@@ -348,11 +349,12 @@ function renderActivityPanel(
|
|
|
348
349
|
theme: FlowTheme,
|
|
349
350
|
): Container {
|
|
350
351
|
const container = new Container();
|
|
352
|
+
const maxWidth = process.stdout.columns ?? 80;
|
|
351
353
|
|
|
352
354
|
for (let i = 0; i < results.length; i++) {
|
|
353
355
|
const r = results[i];
|
|
354
356
|
const isLast = i === results.length - 1;
|
|
355
|
-
const stats = formatCompactStats(r.usage, r.model);
|
|
357
|
+
const stats = formatCompactStats(r.usage, r.model, maxWidth);
|
|
356
358
|
const error = isFlowError(r);
|
|
357
359
|
const typeName = formatFlowTypeName(r.type);
|
|
358
360
|
|
|
@@ -362,40 +364,40 @@ function renderActivityPanel(
|
|
|
362
364
|
if (error && r.stopReason) {
|
|
363
365
|
headerLine += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
364
366
|
}
|
|
365
|
-
container.addChild(new
|
|
367
|
+
container.addChild(new TruncatedText(headerLine, 0, 0));
|
|
366
368
|
|
|
367
369
|
// Continuation indent for sub-lines
|
|
368
370
|
const indent = isLast ? " " : "│ ";
|
|
369
371
|
|
|
370
372
|
// dir: line (intent/objective)
|
|
371
373
|
if (r.intent) {
|
|
372
|
-
container.addChild(new
|
|
374
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", indent + "├─ dir:")} ${theme.fg("dim", r.intent)}`, 0, 0));
|
|
373
375
|
}
|
|
374
376
|
|
|
375
377
|
// exe: line (last tool call)
|
|
376
378
|
const lastTool = getLastToolCall(r.messages);
|
|
377
379
|
if (lastTool) {
|
|
378
380
|
const exeStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
|
|
379
|
-
container.addChild(new
|
|
381
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", indent + "├─ exe:")} ${exeStr}`, 0, 0));
|
|
380
382
|
}
|
|
381
383
|
|
|
382
384
|
// log: line (last assistant text)
|
|
383
385
|
const lastText = getLastAssistantText(r.messages);
|
|
384
386
|
if (lastText) {
|
|
385
|
-
container.addChild(new
|
|
387
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ log:")} ${theme.fg("dim", lastText)}`, 0, 0));
|
|
386
388
|
} else if (error && r.errorMessage) {
|
|
387
|
-
container.addChild(new
|
|
389
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ log:")} ${theme.fg("error", r.errorMessage)}`, 0, 0));
|
|
388
390
|
} else {
|
|
389
|
-
container.addChild(new
|
|
391
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ log:")} ${theme.fg("dim", "[n/a]")}`, 0, 0));
|
|
390
392
|
}
|
|
391
393
|
|
|
392
394
|
// Add blank line separator between flows (with continuation pipe)
|
|
393
395
|
if (!isLast) {
|
|
394
|
-
container.addChild(new
|
|
396
|
+
container.addChild(new TruncatedText(theme.fg("dim", "│"), 0, 0));
|
|
395
397
|
}
|
|
396
398
|
}
|
|
397
399
|
|
|
398
|
-
container.addChild(new
|
|
400
|
+
container.addChild(new TruncatedText(theme.fg("muted", "(Ctrl+O to expand tool traces)"), 0, 0));
|
|
399
401
|
|
|
400
402
|
return container;
|
|
401
403
|
}
|
package/runner-events.js
CHANGED
|
@@ -174,7 +174,7 @@ function addFlowAssistantMessages(result, messages) {
|
|
|
174
174
|
return changed;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
function processFlowEvent(event, result) {
|
|
178
178
|
if (!event || typeof event !== "object") return false;
|
|
179
179
|
|
|
180
180
|
switch (event.type) {
|