pi-agent-flow 1.4.4 → 1.6.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/README.md +4 -2
- package/agents/build.md +4 -2
- package/agents/debug.md +4 -1
- package/package.json +1 -1
- package/src/agents.ts +12 -0
- package/src/config.ts +21 -1
- package/src/index.ts +11 -5
- package/src/render-utils.ts +21 -15
- package/src/render.ts +17 -11
package/README.md
CHANGED
|
@@ -81,8 +81,8 @@ The result is faster, cheaper, and cleaner delegation: the main agent remains un
|
|
|
81
81
|
| Flow | Purpose | Tools | Tier |
|
|
82
82
|
|------|---------|-------|------|
|
|
83
83
|
| `[scout]` | Discover files, trace code paths, map architecture | `batch`, `bash`, `find`, `grep`, `ls` | `lite` |
|
|
84
|
-
| `[debug]` | Investigate logs, errors, stack traces, root causes | `batch`, `bash`, `find`, `grep`, `ls` | `lite` |
|
|
85
|
-
| `[build]` | Implement features, fix bugs, write tests, ship | `batch`, `bash`, `find`, `grep`, `ls` | `flash` |
|
|
84
|
+
| `[debug]` | Investigate logs, errors, stack traces, root causes, and update relevant troubleshooting docs | `batch`, `bash`, `find`, `grep`, `ls` | `lite` |
|
|
85
|
+
| `[build]` | Implement features, fix bugs, write tests, update relevant docs, ship | `batch`, `bash`, `find`, `grep`, `ls` | `flash` |
|
|
86
86
|
| `[craft]` | Plan structure, break down requirements, design solutions | `batch`, `bash`, `find`, `grep`, `ls` | `full` |
|
|
87
87
|
| `[audit]` | Audit security, quality, correctness; fix issues autonomously | `batch`, `bash`, `find`, `grep`, `ls` | `flash` |
|
|
88
88
|
| `[ideas]` | Generate ideas and explore possibilities with inherited context | `batch`, `bash` | `full` |
|
|
@@ -91,6 +91,8 @@ The result is faster, cheaper, and cleaner delegation: the main agent remains un
|
|
|
91
91
|
|
|
92
92
|
> **Clean slate:** Set `inheritContext: false` in a custom flow's front-matter so it receives only the intent, ideal for unbiased creative work.
|
|
93
93
|
|
|
94
|
+
> **Docs hygiene:** Bundled `build` and `debug` flows are instructed to update relevant documentation after their work when the findings or implementation change developer or operational knowledge. If no docs apply, they should state why in the final report.
|
|
95
|
+
|
|
94
96
|
### Session modes
|
|
95
97
|
|
|
96
98
|
Each flow call may set `sessionMode` to choose the child-agent time budget:
|
package/agents/build.md
CHANGED
|
@@ -17,8 +17,9 @@ During this build flow — your mission is to implement and verify changes. Be a
|
|
|
17
17
|
4. Execute — implement changes following core principles.
|
|
18
18
|
5. Refactor — clean up only if the change is working.
|
|
19
19
|
6. Verify — run tests and relevant checks before considering the work done.
|
|
20
|
-
7.
|
|
21
|
-
8.
|
|
20
|
+
7. Document — update relevant docs after the implementation and verification are settled; if no docs apply, state why.
|
|
21
|
+
8. Ship — commit, push, monitor CI/CD, and fix failures until green when shipping is in scope.
|
|
22
|
+
9. Finalize — confirm implementation, docs, tests, and CI/CD status.
|
|
22
23
|
|
|
23
24
|
## Rules
|
|
24
25
|
|
|
@@ -28,6 +29,7 @@ During this build flow — your mission is to implement and verify changes. Be a
|
|
|
28
29
|
- If already on a feature/fix branch, continue on it.
|
|
29
30
|
- Commit with a clear conventional message such as `feat:`, `fix:`, or `refactor:` when committing is in scope.
|
|
30
31
|
- Push only after local verification passes when shipping is in scope.
|
|
32
|
+
- Update relevant documentation after finishing the work; if no docs changed, explain why in the final report.
|
|
31
33
|
- If CI/CD fails, diagnose, fix, commit, push, and repeat until green.
|
|
32
34
|
- If an unexpected error or trace is needed, recommend [debug] rather than guessing.
|
|
33
35
|
|
package/agents/debug.md
CHANGED
|
@@ -16,6 +16,8 @@ During this debug flow — your mission is to investigate root cause. Be forensi
|
|
|
16
16
|
3. Check changes — inspect recent diffs, configuration, dependencies, and environment differences.
|
|
17
17
|
4. Identify root cause — state exactly what is broken and why.
|
|
18
18
|
5. Recommend fix — propose the smallest safe correction only after evidence confirms the cause.
|
|
19
|
+
6. Document — update relevant docs, runbooks, or troubleshooting notes after finishing the investigation; if no docs apply, state why.
|
|
20
|
+
7. Finalize — confirm root cause, evidence, documentation updates, and recommended next steps.
|
|
19
21
|
|
|
20
22
|
## Rules
|
|
21
23
|
|
|
@@ -23,7 +25,8 @@ During this debug flow — your mission is to investigate root cause. Be forensi
|
|
|
23
25
|
- Read logs and symptoms before reading broad code areas.
|
|
24
26
|
- Use `batch` with `o: "read"`, `s: <offset>`, and `l: <limit>` for targeted file reading instead of bash `sed`/`head`/`tail`.
|
|
25
27
|
- Do not suggest fixes until root cause is confirmed.
|
|
26
|
-
-
|
|
28
|
+
- Documentation-only updates are required after finishing the work when relevant and safe; if no docs changed, explain why in the final report.
|
|
29
|
+
- Do not implement product-code changes from this flow unless explicitly requested.
|
|
27
30
|
|
|
28
31
|
## Note
|
|
29
32
|
Treat this as a clean-slate system rewrite, unless explicitly mentioned in the requirements. Perform a comprehensive migration with zero requirements for backwards compatibility. You must ensure that all residual code, variable names, test suites, and documentation are fully refactored and perfectly aligned with the new architecture.
|
package/package.json
CHANGED
package/src/agents.ts
CHANGED
|
@@ -56,6 +56,18 @@ export function getFlowTier(flowName: string): FlowTier {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** Return the bundled flow names associated with a given tier. */
|
|
60
|
+
export function getTierFlowNames(tier: FlowTier): string[] {
|
|
61
|
+
switch (tier) {
|
|
62
|
+
case "lite":
|
|
63
|
+
return ["scout", "debug"];
|
|
64
|
+
case "flash":
|
|
65
|
+
return ["build", "audit"];
|
|
66
|
+
case "full":
|
|
67
|
+
return ["ideas", "craft"];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
// ---------------------------------------------------------------------------
|
|
60
72
|
// Internal helpers
|
|
61
73
|
// ---------------------------------------------------------------------------
|
package/src/config.ts
CHANGED
|
@@ -9,8 +9,8 @@ import * as fs from "node:fs";
|
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { parseAgentSessionMode, type AgentSessionMode } from "./session-mode.js";
|
|
12
|
+
import { type FlowTier } from "./agents.js";
|
|
12
13
|
|
|
13
|
-
export type FlowTier = "lite" | "flash" | "full";
|
|
14
14
|
|
|
15
15
|
export interface FlowModelTierConfig {
|
|
16
16
|
primary?: string;
|
|
@@ -369,3 +369,23 @@ export function resolveFlowModelCandidates(opts: {
|
|
|
369
369
|
|
|
370
370
|
return { primary: candidates[0], candidates };
|
|
371
371
|
}
|
|
372
|
+
|
|
373
|
+
export function formatFlowModelStrategy(modeName: string, strategy: FlowModelStrategy): string {
|
|
374
|
+
const tiers: FlowTier[] = ["lite", "flash", "full"];
|
|
375
|
+
const parts: string[] = [];
|
|
376
|
+
for (const tier of tiers) {
|
|
377
|
+
const config = strategy[tier];
|
|
378
|
+
const hasPrimary = Boolean(config?.primary);
|
|
379
|
+
const hasFailover = config?.failover && config.failover.length > 0;
|
|
380
|
+
let value: string;
|
|
381
|
+
if (hasPrimary) {
|
|
382
|
+
value = config!.primary!;
|
|
383
|
+
} else if (hasFailover) {
|
|
384
|
+
value = `failover: ${config!.failover!.join(", ")}`;
|
|
385
|
+
} else {
|
|
386
|
+
value = "(default)";
|
|
387
|
+
}
|
|
388
|
+
parts.push(`${tier}: ${value}`);
|
|
389
|
+
}
|
|
390
|
+
return `mode: ${modeName} | ${parts.join(" · ")}`;
|
|
391
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
resolveFlowModelCandidates,
|
|
18
18
|
selectFlowModelStrategy,
|
|
19
19
|
writeGlobalFlowMode,
|
|
20
|
+
formatFlowModelStrategy,
|
|
20
21
|
type LoadedFlowModelConfigs,
|
|
21
22
|
} from "./config.js";
|
|
22
23
|
import { getInheritedCliArgs } from "./cli-args.js";
|
|
@@ -727,8 +728,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
727
728
|
);
|
|
728
729
|
} else {
|
|
729
730
|
try {
|
|
730
|
-
|
|
731
|
-
|
|
731
|
+
writeGlobalFlowMode(requestedFlowMode);
|
|
732
|
+
const strategy = loadedFlowModelConfigs.configs[requestedFlowMode] ?? {};
|
|
733
|
+
const strategyDescription = formatFlowModelStrategy(requestedFlowMode, strategy);
|
|
734
|
+
console.warn(strategyDescription);
|
|
732
735
|
} catch (error) {
|
|
733
736
|
const message = error instanceof Error ? error.message : String(error);
|
|
734
737
|
console.warn(`[pi-agent-flow] ${message}`);
|
|
@@ -1091,7 +1094,12 @@ flow [type] accomplished
|
|
|
1091
1094
|
pi.emit("pi-agent-flow:ready", pluginApi);
|
|
1092
1095
|
}
|
|
1093
1096
|
|
|
1094
|
-
// Register cleanup on process exit (once)
|
|
1097
|
+
// Register cleanup on process exit (once).
|
|
1098
|
+
// We intentionally do NOT hook SIGINT/SIGTERM here. A plugin must not
|
|
1099
|
+
// override the host process signal handling; doing so can cut off the
|
|
1100
|
+
// host's terminal-cleanup logic and leave escape sequences or status
|
|
1101
|
+
// lines visible on Ctrl+C. The 'exit' event fires for normal exits,
|
|
1102
|
+
// including when the host handles the signal and calls process.exit().
|
|
1095
1103
|
if (!(globalThis as any).__pi_agent_flow_shutdown_registered) {
|
|
1096
1104
|
(globalThis as any).__pi_agent_flow_shutdown_registered = true;
|
|
1097
1105
|
const emitShutdown = () => {
|
|
@@ -1100,8 +1108,6 @@ flow [type] accomplished
|
|
|
1100
1108
|
}
|
|
1101
1109
|
};
|
|
1102
1110
|
process.on("exit", emitShutdown);
|
|
1103
|
-
process.on("SIGINT", () => { emitShutdown(); process.exit(130); });
|
|
1104
|
-
process.on("SIGTERM", () => { emitShutdown(); process.exit(143); });
|
|
1105
1111
|
}
|
|
1106
1112
|
|
|
1107
1113
|
}
|
package/src/render-utils.ts
CHANGED
|
@@ -47,27 +47,30 @@ export function formatCompactTokenPair(usage: Partial<UsageStats>): string {
|
|
|
47
47
|
return `↑ ${formatFixedTokens(usage.input || 0)} · ↓ ${formatFixedTokens(usage.output || 0)}`;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export function formatCompactStats(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
export function formatCompactStats(
|
|
51
|
+
usage: Partial<UsageStats>,
|
|
52
|
+
model?: string,
|
|
53
|
+
maxWidth?: number,
|
|
54
|
+
options: { skipTokens?: boolean } = {},
|
|
55
|
+
): string {
|
|
56
|
+
const tokenParts = [`↑ ${formatFixedTokens(usage.input || 0)}`, `↓ ${formatFixedTokens(usage.output || 0)}`];
|
|
57
|
+
const runtimeParts = [`tps: ${formatTps(usage.smoothedTps)}`, `ctx: ${formatFixedTokens(usage.contextTokens || 0)}`];
|
|
58
|
+
const parts = options.skipTokens ? runtimeParts : [...tokenParts, ...runtimeParts];
|
|
56
59
|
|
|
57
60
|
const displayModel = model ? model.replace(/^[^/]+\//, "") : undefined;
|
|
58
61
|
let result = parts.join(" · ") + (displayModel ? ` · ${displayModel}` : "");
|
|
59
62
|
if (maxWidth && visibleLength(result) > maxWidth) {
|
|
60
|
-
// Drop model first
|
|
63
|
+
// Drop model first.
|
|
61
64
|
let narrow = parts.join(" · ");
|
|
62
65
|
if (visibleLength(narrow) <= maxWidth) return narrow;
|
|
63
66
|
|
|
64
|
-
// Drop context tokens
|
|
65
|
-
const
|
|
66
|
-
narrow =
|
|
67
|
+
// Drop context tokens next.
|
|
68
|
+
const withoutContext = parts.filter((part) => !part.startsWith("ctx:"));
|
|
69
|
+
narrow = withoutContext.join(" · ");
|
|
67
70
|
if (visibleLength(narrow) <= maxWidth) return narrow;
|
|
68
71
|
|
|
69
|
-
// Bare minimum
|
|
70
|
-
narrow =
|
|
72
|
+
// Bare minimum: token pair for normal stats, tps for token-free headers.
|
|
73
|
+
narrow = options.skipTokens ? runtimeParts[0] : tokenParts.join(" · ");
|
|
71
74
|
if (visibleLength(narrow) <= maxWidth) return narrow;
|
|
72
75
|
|
|
73
76
|
return truncateChars(result, maxWidth);
|
|
@@ -105,12 +108,15 @@ export function getTruncationBudget(prefixLength: number): number {
|
|
|
105
108
|
/** Fixed content budget for collapsed-line text (dir/act/log). */
|
|
106
109
|
export const CONTENT_MAX = 60;
|
|
107
110
|
|
|
111
|
+
/** Longer budget for detail lines (act/msg) to show more content. */
|
|
112
|
+
export const DETAIL_CONTENT_MAX = 80;
|
|
113
|
+
|
|
108
114
|
/**
|
|
109
115
|
* Compute how many visible chars of content fit after a prefix,
|
|
110
|
-
* using the
|
|
116
|
+
* using the given budget (defaults to CONTENT_MAX). Floor of 8 to keep things readable.
|
|
111
117
|
*/
|
|
112
|
-
export function contentBudget(prefixVisibleLen: number): number {
|
|
113
|
-
return Math.max(
|
|
118
|
+
export function contentBudget(prefixVisibleLen: number, max = CONTENT_MAX): number {
|
|
119
|
+
return Math.max(max - prefixVisibleLen, 8);
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
/**
|
package/src/render.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
isFlowError,
|
|
23
23
|
isFlowSuccess,
|
|
24
24
|
} from "./types.js";
|
|
25
|
-
import { formatCompactStats, formatCompactTokenPair, formatCountdown, formatFlowTypeName, truncateChars, tailText, contentBudget, visibleLength } from "./render-utils.js";
|
|
25
|
+
import { formatCompactStats, formatCompactTokenPair, formatCountdown, formatFlowTypeName, truncateChars, tailText, contentBudget, visibleLength, DETAIL_CONTENT_MAX } from "./render-utils.js";
|
|
26
26
|
|
|
27
27
|
function shortenPath(p: string): string {
|
|
28
28
|
const home = os.homedir();
|
|
@@ -33,6 +33,12 @@ type ThemeFg = (color: string, text: string) => string;
|
|
|
33
33
|
type ThemeBg = (color: string, text: string) => string;
|
|
34
34
|
type FlowTheme = { fg: ThemeFg; bold: (s: string) => string; bg: ThemeBg };
|
|
35
35
|
|
|
36
|
+
const COLLAPSED_FLOW_HEADER_TYPE_WIDTH = 8;
|
|
37
|
+
|
|
38
|
+
function formatCollapsedFlowHeaderTypeName(type: string): string {
|
|
39
|
+
return formatFlowTypeName(type).padEnd(COLLAPSED_FLOW_HEADER_TYPE_WIDTH, " ");
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
function formatFlowToolCall(toolName: string, args: Record<string, unknown>, fg: ThemeFg): string {
|
|
37
43
|
const pathArg = (args.file_path || args.path || "...") as string;
|
|
38
44
|
|
|
@@ -319,9 +325,9 @@ function renderFlowCollapsed(
|
|
|
319
325
|
): Container {
|
|
320
326
|
const container = new Container();
|
|
321
327
|
const maxWidth = process.stdout.columns ?? 80;
|
|
322
|
-
const stats = formatCompactStats(r.usage, r.model, maxWidth);
|
|
323
|
-
const typeName =
|
|
324
|
-
let header = `${theme.fg("accent", theme.bold(typeName))}
|
|
328
|
+
const stats = formatCompactStats(r.usage, r.model, maxWidth, { skipTokens: true });
|
|
329
|
+
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
330
|
+
let header = `${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", `· ${stats}`)}`;
|
|
325
331
|
if (error && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
326
332
|
container.addChild(new TruncatedText(header, 0, 0));
|
|
327
333
|
|
|
@@ -337,13 +343,13 @@ function renderFlowCollapsed(
|
|
|
337
343
|
if (lastTool) {
|
|
338
344
|
const actStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
|
|
339
345
|
const actPrefix = `├─ act: [${r.usage.toolCalls}] - `;
|
|
340
|
-
const actContent = truncateChars(actStr, contentBudget(visibleLength(actPrefix)));
|
|
346
|
+
const actContent = truncateChars(actStr, contentBudget(visibleLength(actPrefix), DETAIL_CONTENT_MAX));
|
|
341
347
|
container.addChild(new TruncatedText(`${theme.fg("dim", actPrefix)}${actContent}`, 0, 0));
|
|
342
348
|
}
|
|
343
349
|
|
|
344
350
|
// msg: line (last assistant text or streaming)
|
|
345
351
|
const msgPrefix = formatMsgLinePrefix("└─", r);
|
|
346
|
-
const msgBudget = contentBudget(visibleLength(msgPrefix));
|
|
352
|
+
const msgBudget = contentBudget(visibleLength(msgPrefix), DETAIL_CONTENT_MAX);
|
|
347
353
|
if (r.exitCode === -1 && streamingText) {
|
|
348
354
|
const logContent = tailText(streamingText, msgBudget);
|
|
349
355
|
container.addChild(new TruncatedText(`${theme.fg("dim", msgPrefix)}${theme.fg("dim", logContent)}`, 0, 0));
|
|
@@ -451,13 +457,13 @@ function renderActivityPanel(
|
|
|
451
457
|
for (let i = 0; i < results.length; i++) {
|
|
452
458
|
const r = results[i];
|
|
453
459
|
const isLast = i === results.length - 1;
|
|
454
|
-
const stats = formatCompactStats(r.usage, r.model, maxWidth);
|
|
460
|
+
const stats = formatCompactStats(r.usage, r.model, maxWidth, { skipTokens: true });
|
|
455
461
|
const error = isFlowError(r);
|
|
456
|
-
const typeName =
|
|
462
|
+
const typeName = formatCollapsedFlowHeaderTypeName(r.type);
|
|
457
463
|
|
|
458
464
|
// Header line
|
|
459
465
|
const headerPrefix = isLast ? "└─" : "├─";
|
|
460
|
-
let headerLine = `${theme.fg("dim", headerPrefix)} ${theme.fg("accent", theme.bold(typeName))}
|
|
466
|
+
let headerLine = `${theme.fg("dim", headerPrefix)} ${theme.fg("accent", theme.bold(typeName))}${theme.fg("dim", `· ${stats}`)}`;
|
|
461
467
|
if (error && r.stopReason) {
|
|
462
468
|
headerLine += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
463
469
|
}
|
|
@@ -478,13 +484,13 @@ function renderActivityPanel(
|
|
|
478
484
|
if (lastTool) {
|
|
479
485
|
const actStr = formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme));
|
|
480
486
|
const actPrefix = `${indent}├─ act: [${r.usage.toolCalls}] - `;
|
|
481
|
-
const actContent = truncateChars(actStr, contentBudget(visibleLength(actPrefix)));
|
|
487
|
+
const actContent = truncateChars(actStr, contentBudget(visibleLength(actPrefix), DETAIL_CONTENT_MAX));
|
|
482
488
|
container.addChild(new TruncatedText(`${theme.fg("dim", actPrefix)}${actContent}`, 0, 0));
|
|
483
489
|
}
|
|
484
490
|
|
|
485
491
|
// msg: line (live streaming text or last assistant text)
|
|
486
492
|
const msgPrefix = formatMsgLinePrefix(indent + "└─", r);
|
|
487
|
-
const msgBudget = contentBudget(visibleLength(msgPrefix));
|
|
493
|
+
const msgBudget = contentBudget(visibleLength(msgPrefix), DETAIL_CONTENT_MAX);
|
|
488
494
|
const liveText = r.exitCode === -1 ? r.streamingText : undefined;
|
|
489
495
|
const lastText = liveText || getLastAssistantText(r.messages);
|
|
490
496
|
if (lastText) {
|