pi-agent-flow 0.1.0-alpha.4 → 0.1.0-alpha.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-agent-flow",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "Flow-state delegation extension for Pi coding agent.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -11,6 +11,7 @@
11
11
  "runner-cli.js",
12
12
  "runner-events.js",
13
13
  "render.ts",
14
+ "render-utils.ts",
14
15
  "types.ts",
15
16
  "agents/",
16
17
  "README.md"
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Pure utility functions for rendering — extracted for testability.
3
+ */
4
+
5
+ import type { UsageStats } from "./types.js";
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);
25
+ return parts.join(" ");
26
+ }
27
+
28
+ export function formatCompactStats(usage: Partial<UsageStats>, model?: string): string {
29
+ const io: string[] = [];
30
+ if (usage.input) io.push(`↑${formatTokens(usage.input)}`);
31
+ if (usage.output) io.push(`↓${formatTokens(usage.output)}`);
32
+ const meta: string[] = [];
33
+ if (usage.cacheRead) meta.push(`CR:${formatTokens(usage.cacheRead)}`);
34
+ if (usage.contextTokens && usage.contextTokens > 0) meta.push(`ctx:${formatTokens(usage.contextTokens)}`);
35
+ if (model) meta.push(model);
36
+ const parts: string[] = [];
37
+ if (io.length) parts.push(io.join(" "));
38
+ if (meta.length) parts.push(meta.join(" "));
39
+ return parts.join(" │ ");
40
+ }
41
+
42
+ export function truncateChars(text: string, max: number): string {
43
+ if (text.length <= max) return text;
44
+ const head = Math.ceil(max * 0.6);
45
+ const tail = max - head - 3;
46
+ return text.slice(0, head) + " ... " + text.slice(-tail);
47
+ }
48
+
49
+ export function tailText(text: string, max: number): string {
50
+ const flat = text.replace(/[\n\r\t]+/g, " ").replace(/ +/g, " ").trim();
51
+ if (flat.length <= max) return flat;
52
+ return flat.slice(-max);
53
+ }
package/render.ts CHANGED
@@ -235,10 +235,8 @@ function renderFlowCollapsed(
235
235
  if (error && r.stopReason) text += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
236
236
 
237
237
  // Intent line
238
- const hasOutput = !!(flowOutput || streamingText || (error && r.errorMessage));
239
238
  if (r.intent) {
240
- const prefix = hasOutput ? "" : "└";
241
- text += `\n${theme.fg("dim", `${prefix}─ int:`)} ${theme.fg("dim", truncateChars(r.intent, 40))}`;
239
+ text += `\n${theme.fg("dim", `├─ int:`)} ${theme.fg("dim", truncateChars(r.intent, 40))}`;
242
240
  }
243
241
 
244
242
  // Output line
@@ -248,6 +246,8 @@ function renderFlowCollapsed(
248
246
  text += `\n${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", tailText(streamingText, 40))}`;
249
247
  } else if (error && r.errorMessage) {
250
248
  text += `\n${theme.fg("dim", "└─ msg:")} ${theme.fg("error", truncateChars(r.errorMessage, 25))}`;
249
+ } else {
250
+ text += `\n${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", "[n/a]")}`;
251
251
  }
252
252
 
253
253
  return new Text(text, 0, 0);
@@ -270,7 +270,7 @@ function renderMultiFlowResult(
270
270
  if (expanded) {
271
271
  return renderMultiFlowExpanded(results, successCount, icon, theme);
272
272
  }
273
- return renderMultiFlowCollapsed(results, successCount, icon, theme);
273
+ return renderMultiFlowCollapsed(results, theme);
274
274
  }
275
275
 
276
276
  function renderMultiFlowExpanded(
@@ -324,11 +324,9 @@ function renderMultiFlowExpanded(
324
324
 
325
325
  function renderMultiFlowCollapsed(
326
326
  results: SingleResult[],
327
- successCount: number,
328
- icon: string,
329
327
  theme: FlowTheme,
330
328
  ): Text {
331
- let text = `${icon} ${theme.fg("toolTitle", theme.bold("flow "))}${theme.fg("accent", `${successCount}/${results.length} flows`)}`;
329
+ let text = "";
332
330
 
333
331
  for (let i = 0; i < results.length; i++) {
334
332
  const r = results[i];
@@ -339,22 +337,22 @@ function renderMultiFlowCollapsed(
339
337
 
340
338
  // Header line
341
339
  const headerPrefix = isLast ? "└─" : "├─";
342
- let line = `\n${theme.fg("dim", headerPrefix)} ${theme.bg("selectedBg", theme.fg("accent", theme.bold(r.type)))}`;
340
+ let line = `${theme.fg("dim", headerPrefix)} ${theme.bg("selectedBg", theme.fg("accent", theme.bold(r.type)))}`;
343
341
  if (stats) {
344
342
  line += ` ${theme.fg("dim", "─")} ${theme.fg("dim", stats)}`;
345
343
  }
346
344
  if (error && r.stopReason) {
347
345
  line += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
348
346
  }
347
+ if (i > 0) text += "\n";
349
348
  text += line;
350
349
 
351
350
  // Continuation indent for sub-lines
352
351
  const indent = isLast ? " " : "│ ";
353
352
 
354
353
  // Intent line
355
- const hasOutput = !!(flowOutput || (error && r.errorMessage));
356
354
  if (r.intent) {
357
- const intentPrefix = hasOutput ? "├─" : "└─";
355
+ const intentPrefix = hasMsg ? "├─" : "└─";
358
356
  text += `\n${theme.fg("dim", indent + intentPrefix + " int:")} ${theme.fg("dim", truncateChars(r.intent, 40))}`;
359
357
  }
360
358
 
@@ -363,6 +361,8 @@ function renderMultiFlowCollapsed(
363
361
  text += `\n${theme.fg("dim", indent + "└─ msg:")} ${renderFlowReport(truncateChars(flowOutput, 25), theme)}`;
364
362
  } else if (error && r.errorMessage) {
365
363
  text += `\n${theme.fg("dim", indent + "└─ msg:")} ${theme.fg("error", truncateChars(r.errorMessage, 25))}`;
364
+ } else {
365
+ text += `\n${theme.fg("dim", indent + "└─ msg:")} ${theme.fg("dim", "[n/a]")}`;
366
366
  }
367
367
  }
368
368