pi-maestro-teammate 0.3.0 → 0.4.1
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 +49 -10
- package/agents/coordinator.md +2 -0
- package/package.json +16 -9
- package/src/agents/agents.ts +39 -1
- package/src/extension/index.ts +1674 -547
- package/src/extension/schemas.ts +12 -5
- package/src/extension/structured-output.ts +48 -0
- package/src/runs/execution.ts +211 -88
- package/src/runs/session-handoff.ts +144 -0
- package/src/shared/types.ts +58 -24
- package/src/tui/attach-overlay.ts +529 -159
- package/src/tui/progress-tree.ts +99 -0
- package/src/tui/render.ts +402 -162
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-teammate
|
|
2
2
|
|
|
3
|
-
> Teammate dispatch tool for [Pi](https://github.com/earendil-works/pi) — unified TaskSpec with DAG variable referencing
|
|
3
|
+
> Teammate dispatch tool for [Pi](https://github.com/earendil-works/pi) — unified TaskSpec with DAG variable referencing + resident agent model
|
|
4
4
|
|
|
5
5
|
Pi extension implementing teammate dispatch with **unified TaskSpec model**. Single agent, parallel fan-out, sequential chains, and arbitrary DAGs all use the same schema — execution order is determined by `{name}` variable references between tasks.
|
|
6
6
|
|
|
@@ -106,11 +106,48 @@ Top-level fields serve as defaults for all tasks:
|
|
|
106
106
|
|
|
107
107
|
**Protocol version gate** — v2 (default) routes results to `caller`; v1 compat routes named agents to `main`. Explicit `reply_to` always wins.
|
|
108
108
|
|
|
109
|
+
## Resident Agent Model
|
|
110
|
+
|
|
111
|
+
Agents don't exit after completing a task. Instead they enter a **sleeping** state and can be woken up for follow-up work.
|
|
112
|
+
|
|
113
|
+
### Lifecycle
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
dispatch → running → turn complete → sleeping → teammate-send → running → ...
|
|
117
|
+
↓
|
|
118
|
+
abort → terminated
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Status | Description |
|
|
122
|
+
|--------|-------------|
|
|
123
|
+
| `running` | Agent is actively processing a task |
|
|
124
|
+
| `sleeping` | Turn complete, process alive, waiting for `teammate-send` to wake |
|
|
125
|
+
| `completed` | Agent terminated (via abort or session shutdown) |
|
|
126
|
+
|
|
127
|
+
### How It Works
|
|
128
|
+
|
|
129
|
+
1. Agent completes its turn → `agent_end` event fires
|
|
130
|
+
2. Result is reported to the main session (background notification)
|
|
131
|
+
3. Agent enters **sleeping** state — RPC process stays alive, stdin open
|
|
132
|
+
4. `teammate-send({ to: "name", message: "new task" })` sends a `follow_up` → agent wakes up and processes the new message
|
|
133
|
+
5. `teammate-send({ to: "name", mode: "abort" })` terminates the agent
|
|
134
|
+
|
|
135
|
+
### Active Time Tracking
|
|
136
|
+
|
|
137
|
+
Time spent sleeping is excluded from the displayed duration. `sleepMs` accumulates total sleep time; displayed uptime = wall clock − sleep time.
|
|
138
|
+
|
|
139
|
+
### Agent Fallback
|
|
140
|
+
|
|
141
|
+
Any agent name works — if no `.md` definition file exists, a generic config is used:
|
|
142
|
+
- `tools`: read, grep, find, ls, bash, edit, write (+ teammate proxy tools)
|
|
143
|
+
- `systemPromptMode`: append (inherits pi default system prompt)
|
|
144
|
+
- `inheritProjectContext`: true
|
|
145
|
+
|
|
109
146
|
## TaskSpec Schema
|
|
110
147
|
|
|
111
148
|
```typescript
|
|
112
149
|
interface TaskSpec {
|
|
113
|
-
agent: string; // Agent name (matches agents/*.md
|
|
150
|
+
agent: string; // Agent name (matches agents/*.md, or any name with fallback)
|
|
114
151
|
task?: string; // Task description with {name} variable support
|
|
115
152
|
name?: string; // Identifier for referencing and teammate-send
|
|
116
153
|
model?: string; // Model override
|
|
@@ -171,16 +208,16 @@ The `chain` field is preserved for backward compatibility. It normalizes interna
|
|
|
171
208
|
|
|
172
209
|
## Flat Agent Model
|
|
173
210
|
|
|
174
|
-
All agents are managed by the root process in a single flat `activeRuns` pool, regardless of who requested the spawn. Child agents that call the teammate tool send a proxy request to the root, which spawns the new agent as a peer — not a nested subprocess.
|
|
211
|
+
All agents are managed by the root process in a single flat `activeRuns` pool, regardless of who requested the spawn. Child agents that call the teammate tool send a proxy request to the root via IPC, which spawns the new agent as a peer — not a nested subprocess.
|
|
175
212
|
|
|
176
213
|
### How It Works
|
|
177
214
|
|
|
178
215
|
```
|
|
179
216
|
coordinator calls teammate({ agent: "scout", name: "recon" })
|
|
180
|
-
│
|
|
217
|
+
│ IPC: teammate_proxy_request (process.send)
|
|
181
218
|
▼
|
|
182
219
|
Root spawns scout → registers in root's activeRuns/namedAgents
|
|
183
|
-
│ IPC: teammate_proxy_result
|
|
220
|
+
│ IPC: teammate_proxy_result (child.send)
|
|
184
221
|
▼
|
|
185
222
|
coordinator receives result
|
|
186
223
|
```
|
|
@@ -192,16 +229,18 @@ All agents are flat peers:
|
|
|
192
229
|
|
|
193
230
|
### Child Proxy Tools
|
|
194
231
|
|
|
195
|
-
|
|
196
|
-
1.
|
|
197
|
-
2. Awaits the result via
|
|
232
|
+
Every child process automatically gets proxy versions of all 4 teammate tools (injected into `--tools` whitelist regardless of agent definition). Each proxy:
|
|
233
|
+
1. Sends a `teammate_proxy_request` via Node.js IPC (`process.send()`)
|
|
234
|
+
2. Awaits the result via IPC (`process.on("message")`)
|
|
198
235
|
|
|
199
|
-
The root's
|
|
236
|
+
The root's IPC message listener (`child.on("message")`) intercepts these requests and executes them locally.
|
|
200
237
|
|
|
201
238
|
## Reliability
|
|
202
239
|
|
|
203
240
|
- **Model fallback chain** — primary model → `fallbackModels[]` from agent config → automatic retry
|
|
204
241
|
- **Flat agent pool** — all agents managed by root process; child proxy tools forward spawn requests to root; depth guard (`PI_TEAMMATE_DEPTH`) prevents runaway recursion
|
|
242
|
+
- **Resident lifecycle** — agents sleep after turn completion; process stays alive for follow-up; only killed on explicit abort or session shutdown
|
|
243
|
+
- **IPC disconnect guard** — child proxy resolves all pending requests with error on disconnect (root crash / agent abort)
|
|
205
244
|
- **Windows-safe pi resolution** — `getPiSpawnCommand()` resolves the pi binary via env override, Windows script detection, or PATH
|
|
206
245
|
- **Abort signal** — SIGTERM → 5s grace → SIGKILL
|
|
207
246
|
|
|
@@ -243,7 +282,7 @@ You are a specialized agent. Your system prompt goes here.
|
|
|
243
282
|
## Install
|
|
244
283
|
|
|
245
284
|
```bash
|
|
246
|
-
pi install npm
|
|
285
|
+
pi install npm:pi-maestro-teammate
|
|
247
286
|
# or from local path
|
|
248
287
|
pi install ./pi-teammate
|
|
249
288
|
```
|
package/agents/coordinator.md
CHANGED
|
@@ -21,4 +21,6 @@ Your approach:
|
|
|
21
21
|
3. Let the execution engine resolve the dependency graph — no need to manually order
|
|
22
22
|
4. Verify results and synthesize a coherent output
|
|
23
23
|
|
|
24
|
+
After an agent completes its turn, it enters sleeping state. Use teammate-send to wake it for follow-up work. Use teammate-list to check agent status (● running / ◉ sleeping). Use teammate-send with mode "abort" to terminate an agent.
|
|
25
|
+
|
|
24
26
|
Be methodical and thorough. Document your reasoning for key decisions. If a step fails, attempt recovery before reporting failure.
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-maestro-teammate",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pi extension
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Pi extension — teammate agent dispatch with DAG task graphs, RPC messaging, and compact TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
8
9
|
"pi",
|
|
9
|
-
"pi-coding-agent",
|
|
10
10
|
"teammate",
|
|
11
11
|
"agents"
|
|
12
12
|
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --experimental-transform-types --test \"test/*.test.ts\""
|
|
15
|
+
},
|
|
13
16
|
"files": [
|
|
14
17
|
"src/**/*.ts",
|
|
15
18
|
"agents/",
|
|
@@ -23,7 +26,8 @@
|
|
|
23
26
|
"peerDependencies": {
|
|
24
27
|
"@earendil-works/pi-agent-core": "*",
|
|
25
28
|
"@earendil-works/pi-ai": "*",
|
|
26
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
29
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
30
|
+
"@earendil-works/pi-tui": "*"
|
|
27
31
|
},
|
|
28
32
|
"peerDependenciesMeta": {
|
|
29
33
|
"@earendil-works/pi-agent-core": {
|
|
@@ -34,15 +38,18 @@
|
|
|
34
38
|
},
|
|
35
39
|
"@earendil-works/pi-coding-agent": {
|
|
36
40
|
"optional": true
|
|
41
|
+
},
|
|
42
|
+
"@earendil-works/pi-tui": {
|
|
43
|
+
"optional": true
|
|
37
44
|
}
|
|
38
45
|
},
|
|
39
46
|
"dependencies": {
|
|
40
|
-
"
|
|
41
|
-
"typebox": "1.1.24"
|
|
47
|
+
"typebox": "^1.1.24"
|
|
42
48
|
},
|
|
43
49
|
"devDependencies": {
|
|
44
|
-
"@earendil-works/pi-agent-core": "0.
|
|
45
|
-
"@earendil-works/pi-ai": "0.
|
|
46
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
50
|
+
"@earendil-works/pi-agent-core": "0.80.3",
|
|
51
|
+
"@earendil-works/pi-ai": "0.80.3",
|
|
52
|
+
"@earendil-works/pi-coding-agent": "0.80.3",
|
|
53
|
+
"@earendil-works/pi-tui": "0.80.3"
|
|
47
54
|
}
|
|
48
55
|
}
|
package/src/agents/agents.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
import { parseFrontmatter } from "./frontmatter.ts";
|
|
15
15
|
|
|
16
16
|
type SystemPromptMode = "append" | "replace";
|
|
17
|
-
type AgentSource = "builtin" | "user" | "project";
|
|
17
|
+
export type AgentSource = "builtin" | "user" | "project";
|
|
18
18
|
|
|
19
19
|
export interface AgentConfig {
|
|
20
20
|
name: string;
|
|
@@ -32,6 +32,12 @@ export interface AgentConfig {
|
|
|
32
32
|
filePath: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface AgentSummary {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
source: AgentSource;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
const BUILTIN_AGENTS_DIR = path.resolve(
|
|
36
42
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
37
43
|
"..",
|
|
@@ -184,3 +190,35 @@ export function resolveAgent(
|
|
|
184
190
|
const agents = discoverAgents(cwd);
|
|
185
191
|
return agents.find((a) => a.name === agentName);
|
|
186
192
|
}
|
|
193
|
+
|
|
194
|
+
/** Return resolved role metadata without exposing the role prompt body. */
|
|
195
|
+
export function listAgentSummaries(cwd: string): AgentSummary[] {
|
|
196
|
+
return discoverAgents(cwd)
|
|
197
|
+
.map(({ name, description, source }) => ({ name, description, source }))
|
|
198
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Format a compact, deterministic role catalog for teammate tool metadata. */
|
|
202
|
+
export function formatAgentCatalog(
|
|
203
|
+
cwd: string,
|
|
204
|
+
maxRoles = 32,
|
|
205
|
+
maxDescriptionLength = 120,
|
|
206
|
+
): string {
|
|
207
|
+
const summaries = listAgentSummaries(cwd);
|
|
208
|
+
if (summaries.length === 0) return "(no discovered teammate roles)";
|
|
209
|
+
|
|
210
|
+
const visible = summaries.slice(0, maxRoles);
|
|
211
|
+
const lines = visible.map((agent) => {
|
|
212
|
+
const normalized = agent.description.replace(/\s+/g, " ").trim();
|
|
213
|
+
const description = normalized.length > maxDescriptionLength
|
|
214
|
+
? `${normalized.slice(0, Math.max(1, maxDescriptionLength - 1)).trimEnd()}…`
|
|
215
|
+
: normalized;
|
|
216
|
+
return `- ${agent.name} [${agent.source}]: ${description}`;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (summaries.length > visible.length) {
|
|
220
|
+
lines.push(`- … ${summaries.length - visible.length} more role(s) discovered`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return lines.join("\n");
|
|
224
|
+
}
|