pi-agent-flow 0.4.0 → 0.4.2
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 +18 -16
- 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
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
isFlowError,
|
|
23
23
|
isFlowSuccess,
|
|
24
24
|
} from "./types.js";
|
|
25
|
-
import {
|
|
25
|
+
import { formatFixedTokens, formatCompactStats, formatFlowTypeName, truncateChars, tailText, getTruncationBudget } 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 Text(truncateChars(header, maxWidth), 0, 0));
|
|
234
235
|
|
|
235
236
|
// dir: line (intent/objective)
|
|
236
237
|
if (r.intent) {
|
|
237
|
-
|
|
238
|
+
container.addChild(new Text(`${theme.fg("dim", "├─ dir:")} ${theme.fg("dim", truncateChars(r.intent, getTruncationBudget(8)))}`, 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 Text(`${theme.fg("dim", "├─ exe:")} ${truncateChars(exeStr, getTruncationBudget(8))}`, 0, 0));
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
// log: line (last assistant text or streaming)
|
|
248
249
|
if (flowOutput) {
|
|
249
|
-
|
|
250
|
+
container.addChild(new Text(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", truncateChars(flowOutput, getTruncationBudget(8)))}`, 0, 0));
|
|
250
251
|
} else if (streamingText) {
|
|
251
|
-
|
|
252
|
+
container.addChild(new Text(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", tailText(streamingText, getTruncationBudget(8)))}`, 0, 0));
|
|
252
253
|
} else if (error && r.errorMessage) {
|
|
253
|
-
|
|
254
|
+
container.addChild(new Text(`${theme.fg("dim", "└─ log:")} ${theme.fg("error", truncateChars(r.errorMessage, getTruncationBudget(8)))}`, 0, 0));
|
|
254
255
|
} else {
|
|
255
|
-
|
|
256
|
+
container.addChild(new Text(`${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,7 +364,7 @@ function renderActivityPanel(
|
|
|
362
364
|
if (error && r.stopReason) {
|
|
363
365
|
headerLine += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
364
366
|
}
|
|
365
|
-
container.addChild(new Text(headerLine, 0, 0));
|
|
367
|
+
container.addChild(new Text(truncateChars(headerLine, maxWidth), 0, 0));
|
|
366
368
|
|
|
367
369
|
// Continuation indent for sub-lines
|
|
368
370
|
const indent = isLast ? " " : "│ ";
|
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) {
|