pi-squad 0.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/LICENSE +21 -0
- package/README.md +244 -0
- package/package.json +30 -0
- package/src/agent-pool.ts +445 -0
- package/src/agents/_defaults/architect.json +9 -0
- package/src/agents/_defaults/backend.json +9 -0
- package/src/agents/_defaults/debugger.json +9 -0
- package/src/agents/_defaults/devops.json +9 -0
- package/src/agents/_defaults/docs.json +9 -0
- package/src/agents/_defaults/frontend.json +9 -0
- package/src/agents/_defaults/fullstack.json +9 -0
- package/src/agents/_defaults/planner.json +9 -0
- package/src/agents/_defaults/qa.json +9 -0
- package/src/agents/_defaults/researcher.json +9 -0
- package/src/agents/_defaults/security.json +9 -0
- package/src/index.ts +1121 -0
- package/src/monitor.ts +204 -0
- package/src/panel/message-view.ts +232 -0
- package/src/panel/squad-panel.ts +383 -0
- package/src/panel/task-list.ts +264 -0
- package/src/planner.ts +275 -0
- package/src/protocol.ts +265 -0
- package/src/router.ts +207 -0
- package/src/scheduler.ts +732 -0
- package/src/skills/collaboration/SKILL.md +39 -0
- package/src/skills/squad-protocol/SKILL.md +65 -0
- package/src/skills/verification/SKILL.md +64 -0
- package/src/store.ts +458 -0
- package/src/supervisor.ts +143 -0
- package/src/types.ts +210 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* supervisor.ts — On-demand planner agent calls for quality review,
|
|
3
|
+
* plan revision, and conflict resolution.
|
|
4
|
+
*
|
|
5
|
+
* Spawns a short-lived planner agent (one-shot, --mode json) for
|
|
6
|
+
* decisions that need LLM judgment rather than mechanical rules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Squad, SupervisorResult, Task } from "./types.js";
|
|
10
|
+
import * as store from "./store.js";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Supervisor Calls
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Review a completed task's output.
|
|
18
|
+
* Returns approve/revise/escalate verdict.
|
|
19
|
+
*/
|
|
20
|
+
export async function reviewTaskCompletion(
|
|
21
|
+
squadId: string,
|
|
22
|
+
task: Task,
|
|
23
|
+
): Promise<SupervisorResult> {
|
|
24
|
+
const squad = store.loadSquad(squadId);
|
|
25
|
+
if (!squad) {
|
|
26
|
+
return { verdict: "approve", reason: "Squad not found, auto-approving" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const dependents = store
|
|
30
|
+
.loadAllTasks(squadId)
|
|
31
|
+
.filter((t) => t.depends.includes(task.id));
|
|
32
|
+
|
|
33
|
+
const prompt = `Review this completed task for the squad "${squad.goal}":
|
|
34
|
+
|
|
35
|
+
Task: ${task.title}
|
|
36
|
+
Description: ${task.description}
|
|
37
|
+
Agent: ${task.agent}
|
|
38
|
+
Output: ${task.output || "(no output)"}
|
|
39
|
+
|
|
40
|
+
Dependent tasks waiting on this:
|
|
41
|
+
${dependents.map((t) => `- ${t.title} (${t.agent})`).join("\n") || "(none)"}
|
|
42
|
+
|
|
43
|
+
Questions:
|
|
44
|
+
1. Does the output satisfy the task description?
|
|
45
|
+
2. Will dependent tasks have enough context from this output?
|
|
46
|
+
3. Any concerns before proceeding?
|
|
47
|
+
|
|
48
|
+
Respond with JSON only:
|
|
49
|
+
{"verdict":"approve"|"revise"|"escalate","reason":"...","feedback":"..."}`;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// For now, auto-approve. Full implementation would spawn planner in json mode.
|
|
53
|
+
// This avoids blocking the scheduler on a review for MVP.
|
|
54
|
+
return { verdict: "approve", reason: "Auto-approved (supervisor review not yet enabled)" };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return {
|
|
57
|
+
verdict: "approve",
|
|
58
|
+
reason: `Supervisor error: ${(error as Error).message}. Auto-approving.`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Analyze a stuck agent and suggest next steps.
|
|
65
|
+
*/
|
|
66
|
+
export async function analyzeStuckAgent(
|
|
67
|
+
squadId: string,
|
|
68
|
+
taskId: string,
|
|
69
|
+
agentName: string,
|
|
70
|
+
): Promise<{ action: "retry" | "reassign" | "escalate"; reason: string; suggestion?: string }> {
|
|
71
|
+
const task = store.loadTask(squadId, taskId);
|
|
72
|
+
const messages = store.loadMessages(squadId, taskId);
|
|
73
|
+
const recentMessages = messages.slice(-10);
|
|
74
|
+
|
|
75
|
+
// Simple heuristic for now
|
|
76
|
+
const errorMessages = recentMessages.filter((m) => m.type === "error");
|
|
77
|
+
if (errorMessages.length >= 3) {
|
|
78
|
+
return {
|
|
79
|
+
action: "escalate",
|
|
80
|
+
reason: `Agent ${agentName} has ${errorMessages.length} errors on task ${taskId}`,
|
|
81
|
+
suggestion: "Check the error messages and consider a different approach",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
action: "retry",
|
|
87
|
+
reason: `Agent ${agentName} appears stuck on ${taskId}, attempting retry`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Determine if a blocked agent's request warrants a new subtask.
|
|
93
|
+
*/
|
|
94
|
+
export async function analyzeBlockRequest(
|
|
95
|
+
squadId: string,
|
|
96
|
+
taskId: string,
|
|
97
|
+
agentName: string,
|
|
98
|
+
blockReason: string,
|
|
99
|
+
): Promise<{
|
|
100
|
+
action: "create_subtask" | "route_message" | "escalate";
|
|
101
|
+
subtask?: { title: string; agent: string; description: string };
|
|
102
|
+
targetAgent?: string;
|
|
103
|
+
message?: string;
|
|
104
|
+
}> {
|
|
105
|
+
// Simple pattern matching for common block reasons
|
|
106
|
+
const lower = blockReason.toLowerCase();
|
|
107
|
+
|
|
108
|
+
// If they mention a specific agent, route to them
|
|
109
|
+
const mentionMatch = lower.match(/@(\w+)/);
|
|
110
|
+
if (mentionMatch) {
|
|
111
|
+
const target = mentionMatch[1];
|
|
112
|
+
const projectCwd = store.loadSquad(squadId)?.cwd;
|
|
113
|
+
const agentDef = store.loadAgentDef(target, projectCwd);
|
|
114
|
+
if (agentDef) {
|
|
115
|
+
return {
|
|
116
|
+
action: "route_message",
|
|
117
|
+
targetAgent: target,
|
|
118
|
+
message: blockReason,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// If they mention needing something built/created, suggest a subtask
|
|
124
|
+
if (
|
|
125
|
+
lower.includes("need") &&
|
|
126
|
+
(lower.includes("create") || lower.includes("build") || lower.includes("implement"))
|
|
127
|
+
) {
|
|
128
|
+
return {
|
|
129
|
+
action: "create_subtask",
|
|
130
|
+
subtask: {
|
|
131
|
+
title: `Support: ${blockReason.slice(0, 60)}`,
|
|
132
|
+
agent: "fullstack",
|
|
133
|
+
description: `Created from block request by ${agentName} on task ${taskId}: ${blockReason}`,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Default: escalate to human
|
|
139
|
+
return {
|
|
140
|
+
action: "escalate",
|
|
141
|
+
message: `Agent ${agentName} blocked on ${taskId}: ${blockReason}`,
|
|
142
|
+
};
|
|
143
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Agent Definitions
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
export interface AgentDef {
|
|
6
|
+
/** Agent identifier, matches filename */
|
|
7
|
+
name: string;
|
|
8
|
+
/** One-line role title */
|
|
9
|
+
role: string;
|
|
10
|
+
/** What this agent does (used by planner to pick agents) */
|
|
11
|
+
description: string;
|
|
12
|
+
/** Override model (null = squad default or pi default) */
|
|
13
|
+
model: string | null;
|
|
14
|
+
/** Override tool list (null = all standard tools) */
|
|
15
|
+
tools: string[] | null;
|
|
16
|
+
/** Tags for planner's automatic agent matching */
|
|
17
|
+
tags: string[];
|
|
18
|
+
/** System prompt injected via --append-system-prompt */
|
|
19
|
+
prompt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Agent entry in squad.json — just overrides, references an AgentDef by key */
|
|
23
|
+
export interface SquadAgentEntry {
|
|
24
|
+
model?: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Squad
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
export type SquadStatus = "planning" | "running" | "paused" | "done" | "failed";
|
|
32
|
+
|
|
33
|
+
export interface SquadConfig {
|
|
34
|
+
maxConcurrency: number;
|
|
35
|
+
autoUnblock: boolean;
|
|
36
|
+
reviewOnComplete: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const DEFAULT_SQUAD_CONFIG: SquadConfig = {
|
|
40
|
+
maxConcurrency: 2,
|
|
41
|
+
autoUnblock: true,
|
|
42
|
+
reviewOnComplete: false,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export interface Squad {
|
|
46
|
+
id: string;
|
|
47
|
+
goal: string;
|
|
48
|
+
status: SquadStatus;
|
|
49
|
+
created: string;
|
|
50
|
+
cwd: string;
|
|
51
|
+
/** Agent name → overrides. Keys must exist in .pi/squad/agents/ */
|
|
52
|
+
agents: Record<string, SquadAgentEntry>;
|
|
53
|
+
config: SquadConfig;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Tasks
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
export type TaskStatus = "pending" | "blocked" | "in_progress" | "done" | "failed" | "suspended";
|
|
61
|
+
|
|
62
|
+
export interface TaskUsage {
|
|
63
|
+
inputTokens: number;
|
|
64
|
+
outputTokens: number;
|
|
65
|
+
cost: number;
|
|
66
|
+
turns: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface Task {
|
|
70
|
+
id: string;
|
|
71
|
+
title: string;
|
|
72
|
+
description: string;
|
|
73
|
+
agent: string;
|
|
74
|
+
status: TaskStatus;
|
|
75
|
+
depends: string[];
|
|
76
|
+
created: string;
|
|
77
|
+
started: string | null;
|
|
78
|
+
completed: string | null;
|
|
79
|
+
output: string | null;
|
|
80
|
+
error: string | null;
|
|
81
|
+
usage: TaskUsage;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Messages (JSONL entries)
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
export type MessageType = "status" | "text" | "tool" | "mention" | "reply" | "message" | "done" | "error";
|
|
89
|
+
|
|
90
|
+
export interface TaskMessage {
|
|
91
|
+
ts: string;
|
|
92
|
+
from: string;
|
|
93
|
+
type: MessageType;
|
|
94
|
+
text: string;
|
|
95
|
+
to?: string;
|
|
96
|
+
name?: string;
|
|
97
|
+
args?: Record<string, unknown>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Context (extension-maintained live state)
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
export interface ContextAgentState {
|
|
105
|
+
role: string;
|
|
106
|
+
status: "working" | "idle";
|
|
107
|
+
task: string | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ContextTaskState {
|
|
111
|
+
status: TaskStatus;
|
|
112
|
+
agent: string;
|
|
113
|
+
title: string;
|
|
114
|
+
output?: string;
|
|
115
|
+
blockedBy?: string[];
|
|
116
|
+
subtasks?: Record<string, ContextTaskState>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ContextActivity {
|
|
120
|
+
ts: string;
|
|
121
|
+
agent: string;
|
|
122
|
+
action: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface SquadContext {
|
|
126
|
+
goal: string;
|
|
127
|
+
status: SquadStatus;
|
|
128
|
+
elapsed: string;
|
|
129
|
+
costs: {
|
|
130
|
+
total: number;
|
|
131
|
+
byAgent: Record<string, number>;
|
|
132
|
+
};
|
|
133
|
+
agents: Record<string, ContextAgentState>;
|
|
134
|
+
tasks: Record<string, ContextTaskState>;
|
|
135
|
+
recentActivity: ContextActivity[];
|
|
136
|
+
modifiedFiles: Record<string, string[]>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Knowledge (JSONL entries)
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
export type KnowledgeType = "decision" | "convention" | "finding";
|
|
144
|
+
|
|
145
|
+
export interface KnowledgeEntry {
|
|
146
|
+
ts: string;
|
|
147
|
+
from: string;
|
|
148
|
+
squad?: string;
|
|
149
|
+
type: KnowledgeType;
|
|
150
|
+
text: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Scheduler
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
export interface AgentActivity {
|
|
158
|
+
taskId: string;
|
|
159
|
+
agentName: string;
|
|
160
|
+
lastOutputTs: number;
|
|
161
|
+
startedAt: number;
|
|
162
|
+
turnCount: number;
|
|
163
|
+
/** Ring buffer of recent tool call signatures for loop detection */
|
|
164
|
+
recentToolCalls: string[];
|
|
165
|
+
/** Set of file paths this agent has modified */
|
|
166
|
+
modifiedFiles: Set<string>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type HealthStatus = "healthy" | "idle_warning" | "stuck" | "looping" | "exceeded_ceiling";
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Supervisor
|
|
173
|
+
// ============================================================================
|
|
174
|
+
|
|
175
|
+
export type SupervisorVerdict = "approve" | "revise" | "escalate";
|
|
176
|
+
|
|
177
|
+
export interface SupervisorResult {
|
|
178
|
+
verdict: SupervisorVerdict;
|
|
179
|
+
reason: string;
|
|
180
|
+
feedback?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Planner
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
export interface PlannerOutput {
|
|
188
|
+
agents: Record<string, { model?: string }>;
|
|
189
|
+
tasks: Array<{
|
|
190
|
+
id: string;
|
|
191
|
+
title: string;
|
|
192
|
+
description: string;
|
|
193
|
+
agent: string;
|
|
194
|
+
depends: string[];
|
|
195
|
+
}>;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Panel
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
export type PanelView = "tasks" | "messages" | "agents";
|
|
203
|
+
|
|
204
|
+
export interface PanelState {
|
|
205
|
+
view: PanelView;
|
|
206
|
+
selectedTaskIndex: number;
|
|
207
|
+
selectedTaskId: string | null;
|
|
208
|
+
scrollOffset: number;
|
|
209
|
+
agentSelectedIndex: number;
|
|
210
|
+
}
|