pi-agent-fleet 0.2.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/LICENSE +21 -0
- package/README.md +122 -0
- package/examples/two-worker-fleet.json +21 -0
- package/package.json +52 -0
- package/src/contracts.ts +78 -0
- package/src/dag.ts +190 -0
- package/src/index.ts +600 -0
- package/src/placeholder.ts +1 -0
- package/src/prompts.ts +116 -0
- package/src/report.ts +81 -0
- package/src/runner.ts +95 -0
- package/src/scheduler.ts +226 -0
- package/src/state.ts +156 -0
- package/src/types.ts +109 -0
- package/src/ui.ts +30 -0
- package/src/viz.ts +36 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export type OutputKind = "markdown" | "file-exists" | "verdict" | "json" | "yaml";
|
|
2
|
+
|
|
3
|
+
export interface ContractOutput {
|
|
4
|
+
path: string;
|
|
5
|
+
kind: OutputKind;
|
|
6
|
+
required: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type WorkerType = "research" | "code-run" | "reviewer" | "write" | "read-only";
|
|
10
|
+
|
|
11
|
+
export type GateKind = "reviewer" | "none";
|
|
12
|
+
export type Verdict = "lgtm" | "iterate" | "escalate";
|
|
13
|
+
|
|
14
|
+
export interface LoopConfig {
|
|
15
|
+
gate: GateKind;
|
|
16
|
+
max_iterations: number;
|
|
17
|
+
lgtm_count: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WorkerSpec {
|
|
21
|
+
id: string;
|
|
22
|
+
type: WorkerType;
|
|
23
|
+
task: string;
|
|
24
|
+
model?: string;
|
|
25
|
+
depends_on: string[];
|
|
26
|
+
outputs: ContractOutput[];
|
|
27
|
+
iterate?: boolean;
|
|
28
|
+
worktree?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FleetConfig {
|
|
32
|
+
max_concurrent: number;
|
|
33
|
+
model: string;
|
|
34
|
+
warn_cost_usd?: number;
|
|
35
|
+
loop?: LoopConfig;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FleetSpec {
|
|
39
|
+
fleet_name: string;
|
|
40
|
+
type: "dag";
|
|
41
|
+
config: FleetConfig;
|
|
42
|
+
workers: WorkerSpec[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type NodeStatus =
|
|
46
|
+
| "pending" | "ready" | "running"
|
|
47
|
+
| "completed" | "failed" | "contract_failed" | "killed" | "blocked";
|
|
48
|
+
|
|
49
|
+
export const TERMINAL_NODE_STATUSES: ReadonlySet<NodeStatus> = new Set([
|
|
50
|
+
"completed", "failed", "contract_failed", "killed", "blocked",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
export type FleetStatus = "planned" | "running" | "paused" | "completed" | "failed" | "killed";
|
|
54
|
+
|
|
55
|
+
export interface ContractCheck {
|
|
56
|
+
path: string;
|
|
57
|
+
kind: OutputKind;
|
|
58
|
+
required: boolean;
|
|
59
|
+
ok: boolean;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ContractResult {
|
|
64
|
+
ok: boolean;
|
|
65
|
+
checks: ContractCheck[];
|
|
66
|
+
verdict?: Verdict;
|
|
67
|
+
verdict_body?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface NodeState {
|
|
71
|
+
status: NodeStatus;
|
|
72
|
+
started_at?: string;
|
|
73
|
+
ended_at?: string;
|
|
74
|
+
turns: number;
|
|
75
|
+
tokens: number;
|
|
76
|
+
cost_usd_estimate: number;
|
|
77
|
+
contract_result?: ContractResult;
|
|
78
|
+
produced_outputs: string[];
|
|
79
|
+
status_note?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface IterationSnapshot {
|
|
83
|
+
n: number;
|
|
84
|
+
verdict: Verdict | null;
|
|
85
|
+
verdict_body: string | null;
|
|
86
|
+
started_at: string;
|
|
87
|
+
ended_at: string;
|
|
88
|
+
nodes: Record<string, NodeState>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface FleetState {
|
|
92
|
+
fleet_name: string;
|
|
93
|
+
status: FleetStatus;
|
|
94
|
+
created_at: string;
|
|
95
|
+
cost_usd_estimate: number;
|
|
96
|
+
nodes: Record<string, NodeState>;
|
|
97
|
+
iteration: number;
|
|
98
|
+
lgtm_streak: number;
|
|
99
|
+
paused: boolean;
|
|
100
|
+
iterations: IterationSnapshot[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const WORKER_TYPE_TOOLS: Record<WorkerType, string[]> = {
|
|
104
|
+
"research": ["read", "grep", "find", "ls", "write", "web_search", "fetch_content"],
|
|
105
|
+
"code-run": ["read", "bash", "edit", "write", "grep", "find", "ls"],
|
|
106
|
+
"reviewer": ["read", "write", "grep", "find", "ls"],
|
|
107
|
+
"write": ["read", "write", "grep", "find", "ls"],
|
|
108
|
+
"read-only": ["read", "grep", "find", "ls"],
|
|
109
|
+
};
|
package/src/ui.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FleetSpec, FleetState, NodeStatus } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const ICON: Record<NodeStatus, string> = {
|
|
4
|
+
completed: "✓", failed: "✗", contract_failed: "✗",
|
|
5
|
+
running: "⠹", blocked: "⊘", killed: "⊘", pending: "○", ready: "○",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function buildWidgetLines(spec: FleetSpec, state: FleetState): string[] {
|
|
9
|
+
const done = spec.workers.filter((w) => state.nodes[w.id].status === "completed").length;
|
|
10
|
+
const loop = spec.config.loop;
|
|
11
|
+
let header: string;
|
|
12
|
+
if (loop) {
|
|
13
|
+
const lgtmCount = loop.lgtm_count ?? 1;
|
|
14
|
+
const lastVerdict = state.iterations.length > 0 ? state.iterations[state.iterations.length - 1].verdict : null;
|
|
15
|
+
const streakSegment = loop.gate === "reviewer" ? ` · streak ${state.lgtm_streak}/${lgtmCount}` : "";
|
|
16
|
+
header = `● fleet: ${spec.fleet_name} · iteration ${state.iteration}/${loop.max_iterations} · last verdict: ${lastVerdict ?? "—"}${streakSegment} (${done}/${spec.workers.length} done · $${state.cost_usd_estimate.toFixed(2)})`;
|
|
17
|
+
} else {
|
|
18
|
+
header = `● fleet: ${spec.fleet_name} (${done}/${spec.workers.length} done · $${state.cost_usd_estimate.toFixed(2)})`;
|
|
19
|
+
}
|
|
20
|
+
const lines = [header];
|
|
21
|
+
spec.workers.forEach((w, i) => {
|
|
22
|
+
const n = state.nodes[w.id];
|
|
23
|
+
const branch = i === spec.workers.length - 1 ? "└─" : "├─";
|
|
24
|
+
const detail = n.status === "running" ? ` · ${n.turns} turns · ${(n.tokens / 1000).toFixed(1)}k tok` : "";
|
|
25
|
+
const note = n.status_note ? ` · ${n.status_note}` : "";
|
|
26
|
+
const model = w.model ?? spec.config.model;
|
|
27
|
+
lines.push(`${branch} ${ICON[n.status]} ${w.id} (${model})${detail}${note}`);
|
|
28
|
+
});
|
|
29
|
+
return lines;
|
|
30
|
+
}
|
package/src/viz.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { topoLayers } from "./dag.js";
|
|
2
|
+
import type { FleetSpec, FleetState, NodeStatus } from "./types.js";
|
|
3
|
+
|
|
4
|
+
const ICON: Record<NodeStatus, string> = {
|
|
5
|
+
completed: "✓", failed: "✗", contract_failed: "✗",
|
|
6
|
+
running: "◌", blocked: "⊘", killed: "⊘",
|
|
7
|
+
pending: "○", ready: "○",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function renderDag(spec: FleetSpec, state?: FleetState): string {
|
|
11
|
+
const layers = topoLayers(spec);
|
|
12
|
+
const modelOf = (id: string): string => {
|
|
13
|
+
const w = spec.workers.find((x) => x.id === id);
|
|
14
|
+
return w?.model ?? spec.config.model;
|
|
15
|
+
};
|
|
16
|
+
const label = (id: string): string => {
|
|
17
|
+
const st = state?.nodes[id]?.status;
|
|
18
|
+
const base = `${id} (${modelOf(id)})`;
|
|
19
|
+
return st ? `${ICON[st]} ${base}` : base;
|
|
20
|
+
};
|
|
21
|
+
const lines: string[] = [];
|
|
22
|
+
const header = layers.map((l, i) => `layer ${i}: ${l.map(label).join(" ")}`).join("\n");
|
|
23
|
+
lines.push(header, "", "edges:");
|
|
24
|
+
for (const w of spec.workers) {
|
|
25
|
+
for (const d of w.depends_on) lines.push(` ${d} --▶ ${w.id}`);
|
|
26
|
+
}
|
|
27
|
+
if (spec.workers.every((w) => w.depends_on.length === 0)) lines.push(" (none — all parallel)");
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function dagNeedsFileFallback(spec: FleetSpec, termWidth: number): boolean {
|
|
32
|
+
if (spec.workers.length > 15) return true;
|
|
33
|
+
const layers = topoLayers(spec);
|
|
34
|
+
const widest = Math.max(...layers.map((l) => l.join(" ").length + 9));
|
|
35
|
+
return widest > termWidth;
|
|
36
|
+
}
|