pi-agent-flow 1.0.7 → 1.1.0

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,11 +1,12 @@
1
1
  {
2
2
  "name": "pi-agent-flow",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "description": "Flow-state delegation extension for Pi coding agent.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "files": [
8
8
  "index.ts",
9
+ "web-tool.ts",
9
10
  "agents.ts",
10
11
  "config.ts",
11
12
  "flow.ts",
@@ -15,6 +16,7 @@
15
16
  "render.ts",
16
17
  "render-utils.ts",
17
18
  "types.ts",
19
+ "batch.ts",
18
20
  "agents/",
19
21
  "README.md"
20
22
  ],
@@ -35,7 +37,7 @@
35
37
  "url": "https://github.com/tuanhung303/pi-agent-flow"
36
38
  },
37
39
  "engines": {
38
- "node": ">=18"
40
+ "node": ">=20.19.0"
39
41
  },
40
42
  "scripts": {
41
43
  "lint": "tsc --noEmit",
@@ -43,7 +45,9 @@
43
45
  },
44
46
  "devDependencies": {
45
47
  "typescript": "^5.0.0",
46
- "vitest": "^3.2.4"
48
+ "vitest": "^3.2.4",
49
+ "@types/jsdom": "^28.0.1",
50
+ "@types/turndown": "^5.0.6"
47
51
  },
48
52
  "peerDependencies": {
49
53
  "@mariozechner/pi-agent-core": ">=0.37.0",
@@ -68,5 +72,9 @@
68
72
  "@sinclair/typebox": {
69
73
  "optional": true
70
74
  }
75
+ },
76
+ "dependencies": {
77
+ "jsdom": "^29.1.0",
78
+ "turndown": "^7.2.4"
71
79
  }
72
80
  }
package/render-utils.ts CHANGED
@@ -27,12 +27,12 @@ export function formatFixedTokens(count: number): string {
27
27
  }
28
28
 
29
29
  /**
30
- * Format flow type name to fixed width (10 chars) in lowercase with space padding.
31
- * Examples: "debug" → "debug ", "architect" → "architect ", "brainstorm" → "brainstorm"
30
+ * Format flow type name to fixed width (5 chars) in lowercase with space padding.
31
+ * Examples: "debug" → "debug", "scout" → "scout", "build" → "build"
32
32
  */
33
33
  export function formatFlowTypeName(type: string): string {
34
34
  const lower = type.toLowerCase();
35
- const targetWidth = 10;
35
+ const targetWidth = 5;
36
36
  if (lower.length >= targetWidth) return lower.slice(0, targetWidth);
37
37
  return lower.padEnd(targetWidth, " ");
38
38
  }
package/render.ts CHANGED
@@ -66,6 +66,26 @@ function formatFlowToolCall(toolName: string, args: Record<string, unknown>, fg:
66
66
  return fg("muted", "find ") + fg("accent", (args.pattern || "*") as string) + fg("dim", ` in ${shortenPath((args.path || ".") as string)}`);
67
67
  case "grep":
68
68
  return fg("muted", "grep ") + fg("accent", `/${(args.pattern || "") as string}/`) + fg("dim", ` in ${shortenPath((args.path || ".") as string)}`);
69
+ case "batch": {
70
+ const ops = Array.isArray(args.o) ? args.o : Array.isArray(args.op) ? args.op : Array.isArray(args.operations) ? args.operations : Array.isArray(args) ? args : [];
71
+ if (ops.length === 0) return fg("muted", "batch (empty)");
72
+ const parts: string[] = [];
73
+ for (const op of ops) {
74
+ const opObj = op as Record<string, unknown>;
75
+ const opName = (opObj.o ?? opObj.op ?? "?") as string;
76
+ const opPath = (opObj.p ?? opObj.path ?? "?") as string;
77
+ const shortPath = shortenPath(opPath);
78
+ if (opName === "edit") {
79
+ const edits = (opObj.e ?? opObj.edits) as unknown[] | undefined;
80
+ const blockInfo = edits && edits.length > 1 ? ` (${edits.length} blocks)` : "";
81
+ parts.push(`edit ${shortPath}${blockInfo}`);
82
+ } else {
83
+ parts.push(`${opName} ${shortPath}`);
84
+ }
85
+ }
86
+ const summary = parts.length <= 3 ? parts.join(", ") : `${parts.slice(0, 2).join(", ")} +${parts.length - 2} more`;
87
+ return fg("muted", "batch ") + fg("accent", summary);
88
+ }
69
89
  default:
70
90
  return fg("accent", toolName) + fg("dim", ` ${JSON.stringify(args)}`);
71
91
  }
@@ -107,6 +127,16 @@ function flowStatusIcon(r: SingleResult, theme: { fg: ThemeFg }): string {
107
127
  return isFlowError(r) ? theme.fg("error", "✗") : theme.fg("success", "✓");
108
128
  }
109
129
 
130
+ /** Center a label in a fixed-width header using em-dashes. Total width = 20. */
131
+ function sectionHeader(label: string): string {
132
+ const total = 20;
133
+ const innerLen = label.length + 2; // account for spaces around label
134
+ const side = (total - innerLen) / 2;
135
+ const left = "─".repeat(Math.floor(side));
136
+ const right = "─".repeat(Math.ceil(side));
137
+ return `${left} ${label} ${right}`;
138
+ }
139
+
110
140
  // ---------------------------------------------------------------------------
111
141
  // renderFlowCall — shown while the flow is being invoked
112
142
  // ---------------------------------------------------------------------------
@@ -139,6 +169,7 @@ export function renderFlowResult(
139
169
  type: flowRequest.type || "unknown",
140
170
  agentSource: "user",
141
171
  intent: flowRequest.intent || "Processing...",
172
+ aim: flowRequest.aim || flowRequest.intent || "Processing...",
142
173
  exitCode: -1, // In progress
143
174
  messages: [],
144
175
  stderr: "",
@@ -203,12 +234,12 @@ function renderFlowExpanded(
203
234
 
204
235
  // Intent
205
236
  container.addChild(new Spacer(1));
206
- container.addChild(new Text(theme.fg("muted", "─── intent ───"), 0, 0));
237
+ container.addChild(new Text(theme.fg("muted", sectionHeader("intent")), 0, 0));
207
238
  container.addChild(new Text(theme.fg("dim", r.intent), 0, 0));
208
239
 
209
240
  // Flow report (structured output)
210
241
  container.addChild(new Spacer(1));
211
- container.addChild(new Text(theme.fg("muted", "─── report ───"), 0, 0));
242
+ container.addChild(new Text(theme.fg("muted", sectionHeader("report")), 0, 0));
212
243
  if (flowOutput) {
213
244
  container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
214
245
  } else {
@@ -220,7 +251,7 @@ function renderFlowExpanded(
220
251
  const toolTraces = renderToolTraces(displayItems, theme);
221
252
  if (toolTraces) {
222
253
  container.addChild(new Spacer(1));
223
- container.addChild(new Text(theme.fg("muted", "─── tool calls ───"), 0, 0));
254
+ container.addChild(new Text(theme.fg("muted", sectionHeader("tool calls")), 0, 0));
224
255
  container.addChild(new Text(toolTraces, 0, 0));
225
256
  }
226
257
 
@@ -239,14 +270,14 @@ function renderFlowCollapsed(
239
270
  const maxWidth = process.stdout.columns ?? 80;
240
271
  const stats = formatCompactStats(r.usage, r.model, maxWidth);
241
272
  const typeName = formatFlowTypeName(r.type);
242
- let header = `${theme.fg("accent", theme.bold(typeName))} ${theme.fg("dim", "─")} ${theme.fg("dim", stats)}`;
273
+ let header = `${theme.fg("accent", theme.bold(typeName))} ${theme.fg("dim", stats)}`;
243
274
  if (error && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
244
275
  container.addChild(new TruncatedText(header, 0, 0));
245
276
 
246
- // dir: line (intent/objective)
247
- if (r.intent) {
248
- const dirContent = truncateChars(r.intent, contentBudget(10));
249
- container.addChild(new TruncatedText(`${theme.fg("dim", "├─ dir:")} ${theme.fg("dim", dirContent)}`, 0, 0));
277
+ // aim: line (short headline)
278
+ if (r.aim) {
279
+ const dirContent = truncateChars(r.aim, contentBudget(10));
280
+ container.addChild(new TruncatedText(`${theme.fg("dim", "├─ aim:")} ${theme.fg("dim", dirContent)}`, 0, 0));
250
281
  }
251
282
 
252
283
  // act: line (last tool call with count)
@@ -258,18 +289,18 @@ function renderFlowCollapsed(
258
289
  container.addChild(new TruncatedText(`${theme.fg("dim", "├─ " + actPrefix)}${actContent}`, 0, 0));
259
290
  }
260
291
 
261
- // log: line (last assistant text or streaming)
292
+ // msg: line (last assistant text or streaming)
262
293
  if (flowOutput) {
263
294
  const logContent = truncateChars(flowOutput, contentBudget(10));
264
- container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", logContent)}`, 0, 0));
295
+ container.addChild(new TruncatedText(`${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", logContent)}`, 0, 0));
265
296
  } else if (streamingText) {
266
297
  const logContent = truncateChars(streamingText, contentBudget(10));
267
- container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", logContent)}`, 0, 0));
298
+ container.addChild(new TruncatedText(`${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", logContent)}`, 0, 0));
268
299
  } else if (error && r.errorMessage) {
269
300
  const logContent = truncateChars(r.errorMessage, contentBudget(10));
270
- container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("error", logContent)}`, 0, 0));
301
+ container.addChild(new TruncatedText(`${theme.fg("dim", "└─ msg:")} ${theme.fg("error", logContent)}`, 0, 0));
271
302
  } else {
272
- container.addChild(new TruncatedText(`${theme.fg("dim", "└─ log:")} ${theme.fg("dim", "[n/a]")}`, 0, 0));
303
+ container.addChild(new TruncatedText(`${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", "[n/a]")}`, 0, 0));
273
304
  }
274
305
 
275
306
  return container;
@@ -317,7 +348,7 @@ function renderMultiFlowExpanded(
317
348
 
318
349
  container.addChild(new Spacer(1));
319
350
  // Per-flow header: ─── EXPLORER (no icon)
320
- container.addChild(new Text(`${theme.fg("muted", "─── ")}${theme.fg("accent", typeName)}`, 0, 0));
351
+ container.addChild(new Text(theme.fg("muted", sectionHeader(typeName)), 0, 0));
321
352
 
322
353
  // Stats: dashboard format
323
354
  const flowStats = formatCompactStats(r.usage, r.model);
@@ -335,7 +366,7 @@ function renderMultiFlowExpanded(
335
366
  const toolTraces = renderToolTraces(displayItems, theme);
336
367
  if (toolTraces) {
337
368
  container.addChild(new Spacer(1));
338
- container.addChild(new Text(theme.fg("muted", "─── tool calls ───"), 0, 0));
369
+ container.addChild(new Text(theme.fg("muted", sectionHeader("tool calls")), 0, 0));
339
370
  container.addChild(new Text(toolTraces, 0, 0));
340
371
  }
341
372
  }
@@ -366,7 +397,7 @@ function renderActivityPanel(
366
397
 
367
398
  // Header line
368
399
  const headerPrefix = isLast ? "└─" : "├─";
369
- let headerLine = `${theme.fg("dim", headerPrefix)} ${theme.fg("accent", theme.bold(typeName))} ${theme.fg("dim", "─")} ${theme.fg("dim", stats)}`;
400
+ let headerLine = `${theme.fg("dim", headerPrefix)} ${theme.fg("accent", theme.bold(typeName))} ${theme.fg("dim", stats)}`;
370
401
  if (error && r.stopReason) {
371
402
  headerLine += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
372
403
  }
@@ -375,10 +406,10 @@ function renderActivityPanel(
375
406
  // Continuation indent for sub-lines
376
407
  const indent = isLast ? " " : "│ ";
377
408
 
378
- // dir: line (intent/objective)
379
- if (r.intent) {
380
- const dirContent = truncateChars(r.intent, contentBudget(10));
381
- container.addChild(new TruncatedText(`${theme.fg("dim", indent + "├─ dir:")} ${theme.fg("dim", dirContent)}`, 0, 0));
409
+ // aim: line (short headline)
410
+ if (r.aim) {
411
+ const dirContent = truncateChars(r.aim, contentBudget(10));
412
+ container.addChild(new TruncatedText(`${theme.fg("dim", indent + "├─ aim:")} ${theme.fg("dim", dirContent)}`, 0, 0));
382
413
  }
383
414
 
384
415
  // act: line (last tool call with count)
@@ -390,16 +421,16 @@ function renderActivityPanel(
390
421
  container.addChild(new TruncatedText(`${theme.fg("dim", indent + "├─ " + actPrefix)}${actContent}`, 0, 0));
391
422
  }
392
423
 
393
- // log: line (last assistant text)
424
+ // msg: line (last assistant text)
394
425
  const lastText = getLastAssistantText(r.messages);
395
426
  if (lastText) {
396
427
  const logContent = truncateChars(lastText, contentBudget(10));
397
- container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ log:")} ${theme.fg("dim", logContent)}`, 0, 0));
428
+ container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ msg:")} ${theme.fg("dim", logContent)}`, 0, 0));
398
429
  } else if (error && r.errorMessage) {
399
430
  const logContent = truncateChars(r.errorMessage, contentBudget(10));
400
- container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ log:")} ${theme.fg("error", logContent)}`, 0, 0));
431
+ container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ msg:")} ${theme.fg("error", logContent)}`, 0, 0));
401
432
  } else {
402
- container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ log:")} ${theme.fg("dim", "[n/a]")}`, 0, 0));
433
+ container.addChild(new TruncatedText(`${theme.fg("dim", indent + "└─ msg:")} ${theme.fg("dim", "[n/a]")}`, 0, 0));
403
434
  }
404
435
 
405
436
  // Add blank line separator between flows (with continuation pipe)