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 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 filename)
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
- │ stdout: teammate_proxy_request
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
- Child processes register proxy versions of all teammate tools. Each proxy:
196
- 1. Writes a `teammate_proxy_request` JSON line to stdout
197
- 2. Awaits the result via Node.js IPC (`process.on("message")`)
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 event parser intercepts these requests and executes them locally. The IPC channel is established via `stdio: ["pipe","pipe","pipe","ipc"]` at spawn time.
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:@pi-maestro/teammate
285
+ pi install npm:pi-maestro-teammate
247
286
  # or from local path
248
287
  pi install ./pi-teammate
249
288
  ```
@@ -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.3.0",
4
- "description": "Pi extension for teammate dispatch with P0 three-axis decoupling (name, reply_to)",
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
- "@earendil-works/pi-tui": "0.74.0",
41
- "typebox": "1.1.24"
47
+ "typebox": "^1.1.24"
42
48
  },
43
49
  "devDependencies": {
44
- "@earendil-works/pi-agent-core": "0.74.0",
45
- "@earendil-works/pi-ai": "0.74.0",
46
- "@earendil-works/pi-coding-agent": "0.74.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
  }
@@ -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
+ }