@mystilleef/pi-subagent 0.9.0 → 0.10.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 +68 -40
- package/package.json +8 -8
- package/src/agent/agent-cache.ts +1 -0
- package/src/agent/agents.ts +22 -1
- package/src/child/child-events.ts +2 -0
- package/src/child/process.ts +195 -83
- package/src/child/termination.ts +316 -1
- package/src/env.d.ts +2 -0
- package/src/notification/delivery.ts +231 -0
- package/src/notification/desktop-notification.ts +73 -0
- package/src/orchestration/run-command.ts +1 -1
- package/src/orchestration/subagent-orchestrator.ts +49 -79
- package/src/output/normalize.ts +2 -2
- package/src/output/ui.ts +17 -28
- package/src/progress/progress-format.ts +111 -0
- package/src/progress/progress-state.ts +20 -108
- package/src/progress/progress.ts +29 -27
- package/src/progress/result-details.ts +96 -33
- package/src/shared/types.ts +2 -0
- package/src/shared/utils.ts +27 -6
package/README.md
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Subagents for pi
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[
|
|
5
|
-
|
|
6
|
-
separate child Pi processes. Designed especially for the `SPAE`
|
|
7
|
-
framework, but doesn't require it.
|
|
8
|
-
|
|
9
|
-
## Action
|
|
10
|
-
|
|
11
|
-
Agents in
|
|
3
|
+
Designed to orchestrate agents for the
|
|
4
|
+
[SPAE framework](https://github.com/mystilleef/spae-framework). Agents
|
|
5
|
+
in
|
|
12
6
|
[action](https://raw.githubusercontent.com/mystilleef/pi-subagent/main/assets/parallel-agents-demo.mp4).
|
|
13
7
|
|
|
8
|
+
---
|
|
9
|
+
|
|
14
10
|
## Installation
|
|
15
11
|
|
|
16
12
|
**Install from `npm`:**
|
|
@@ -25,13 +21,19 @@ pi install npm:@mystilleef/pi-subagent
|
|
|
25
21
|
pi -e npm:@mystilleef/pi-subagent
|
|
26
22
|
```
|
|
27
23
|
|
|
24
|
+
---
|
|
25
|
+
|
|
28
26
|
## Features
|
|
29
27
|
|
|
30
28
|
- **Asynchronous:** Agents run in the background.
|
|
31
29
|
- **Parallel:** Run many agents simultaneously.
|
|
32
30
|
- **Isolated:** Each delegated task receives a separate context window.
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
31
|
+
- **Nested:** `Subagents` can spawn other `subagents`.
|
|
32
|
+
- **Simple:** No complex orchestration syntax.
|
|
33
|
+
- **Bloat-free:** No bundled agents. Write your own. Or use those from
|
|
34
|
+
the [SPAE Framework](https://github.com/mystilleef/spae-framework).
|
|
35
|
+
|
|
36
|
+
---
|
|
35
37
|
|
|
36
38
|
## Usage
|
|
37
39
|
|
|
@@ -70,12 +72,16 @@ commits; use the query agent to summarize the project.
|
|
|
70
72
|
/cancel-subagent
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
---
|
|
76
|
+
|
|
73
77
|
## _SPAE_ Workflow
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
[
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
The official extension for the
|
|
80
|
+
[SPAE Framework](https://github.com/mystilleef/spae-framework). The
|
|
81
|
+
framework provides pre-built agents and skills for a structured, or
|
|
82
|
+
orchestrated, workflow.
|
|
83
|
+
|
|
84
|
+
### Structured
|
|
79
85
|
|
|
80
86
|
| Phase | Agent | Purpose |
|
|
81
87
|
| ----- | ------------------------- | --------------------------------------------- |
|
|
@@ -85,32 +91,44 @@ structured workflow.
|
|
|
85
91
|
| 4 | `/run build` | Carry out tasks from `PLAN.md` |
|
|
86
92
|
| 5 | `/run verify` | Verify implementation against `SPEC.md` |
|
|
87
93
|
|
|
94
|
+
### Orchestrated
|
|
95
|
+
|
|
96
|
+
| Agent | Purpose |
|
|
97
|
+
| -------------------------------- | --------------------------------------------- |
|
|
98
|
+
| `/run orchestrate <requirement>` | Run all phases of the `SPAE` workflow |
|
|
99
|
+
| `/run prepare <requirement>` | Run preparatory phases of the `SPAE` workflow |
|
|
100
|
+
| `/run spawn` | Spawn build agents for each task in a plan |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
88
104
|
## Agent definitions
|
|
89
105
|
|
|
90
|
-
This
|
|
91
|
-
`frontmatter` and a Markdown system prompt body.
|
|
106
|
+
This extension ships no agents. Define agents as Markdown files with
|
|
107
|
+
YAML `frontmatter` and a Markdown system prompt body.
|
|
92
108
|
|
|
93
|
-
|
|
109
|
+
### Discovery locations
|
|
94
110
|
|
|
95
111
|
- User-global agents: `~/.pi/agents/*.md`
|
|
96
112
|
- Project-local agents: nearest `.pi/agents/*.md`
|
|
97
113
|
|
|
98
|
-
|
|
114
|
+
### Required front matter
|
|
99
115
|
|
|
100
116
|
```yaml
|
|
101
117
|
name: review
|
|
102
118
|
description: Review code for correctness and maintainability.
|
|
103
119
|
```
|
|
104
120
|
|
|
105
|
-
|
|
121
|
+
### Optional front matter
|
|
106
122
|
|
|
107
123
|
```yaml
|
|
108
124
|
tools: read, bash, edit
|
|
109
125
|
skills: code-review
|
|
110
126
|
thinking: medium
|
|
127
|
+
provider: deepseek
|
|
128
|
+
model: deepseek-v4-flash
|
|
111
129
|
```
|
|
112
130
|
|
|
113
|
-
|
|
131
|
+
### Accepted thinking values
|
|
114
132
|
|
|
115
133
|
- `off`
|
|
116
134
|
- `minimal`
|
|
@@ -119,6 +137,8 @@ thinking: medium
|
|
|
119
137
|
- `high`
|
|
120
138
|
- `xhigh`
|
|
121
139
|
|
|
140
|
+
---
|
|
141
|
+
|
|
122
142
|
## Tool
|
|
123
143
|
|
|
124
144
|
The extension also registers a `subagent` tool for model-driven
|
|
@@ -130,9 +150,11 @@ delegation.
|
|
|
130
150
|
- `task`: task prompt for the child agent.
|
|
131
151
|
- `agentScope`: optional lookup scope, one of `user`, `project`, or
|
|
132
152
|
`both`.
|
|
133
|
-
- `debug`: optional flag that requests child diagnostic details. Full
|
|
134
|
-
messages and raw internals require `PI_SUBAGENT_DEBUG_ENABLED=1`
|
|
135
|
-
host environment.
|
|
153
|
+
- `debug`: optional flag that requests child diagnostic details. Full
|
|
154
|
+
child messages and raw internals require `PI_SUBAGENT_DEBUG_ENABLED=1`
|
|
155
|
+
in the host environment.
|
|
156
|
+
|
|
157
|
+
---
|
|
136
158
|
|
|
137
159
|
## Security
|
|
138
160
|
|
|
@@ -144,39 +166,43 @@ executable automation.
|
|
|
144
166
|
|
|
145
167
|
- Review project-local agents before running them.
|
|
146
168
|
- Avoid delegating secrets unless the agent and tools need them.
|
|
147
|
-
- Treat child-agent prompts, tool arguments, stderr
|
|
148
|
-
potentially sensitive.
|
|
169
|
+
- Treat child-agent prompts, tool arguments, `stderr`, and debug
|
|
170
|
+
transcripts as potentially sensitive.
|
|
149
171
|
- Enable debug details only for trusted investigations. `debug: true` or
|
|
150
172
|
`/run --debug` can expose child conversation transcripts, termination
|
|
151
|
-
internals, and stderr only when the host explicitly sets
|
|
173
|
+
internals, and `stderr` only when the host explicitly sets
|
|
152
174
|
`PI_SUBAGENT_DEBUG_ENABLED=1`.
|
|
153
175
|
- Prefer trusted repositories for shared agent definitions.
|
|
154
176
|
- Remember that child agents can call their configured tools.
|
|
155
177
|
|
|
178
|
+
---
|
|
179
|
+
|
|
156
180
|
## Configuration and limits
|
|
157
181
|
|
|
158
182
|
**Environment variables:**
|
|
159
183
|
|
|
160
|
-
- `PI_SUBAGENT_DEPTH`: current nested subagent depth counter set
|
|
161
|
-
for child processes.
|
|
162
|
-
- `PI_SUBAGENT_MAX_DEPTH`: max nested subagent depth. Default: `3`.
|
|
163
|
-
above `10` clamp to the internal ceiling `10`; deeper nesting
|
|
164
|
-
cost, latency, and runaway delegation risk.
|
|
184
|
+
- `PI_SUBAGENT_DEPTH`: current nested subagent depth counter set
|
|
185
|
+
internally for child processes.
|
|
186
|
+
- `PI_SUBAGENT_MAX_DEPTH`: max nested subagent depth. Default: `3`.
|
|
187
|
+
Values above `10` clamp to the internal ceiling `10`; deeper nesting
|
|
188
|
+
increases cost, latency, and runaway delegation risk.
|
|
165
189
|
- `PI_SUBAGENT_AGENT_END_GRACE_MS`: child process grace period after
|
|
166
190
|
`agent_end` before forced termination. Default: `250`.
|
|
167
|
-
- `PI_SUBAGENT_MAX_STDERR_BYTES`: max captured child stderr bytes.
|
|
168
|
-
`10000`.
|
|
191
|
+
- `PI_SUBAGENT_MAX_STDERR_BYTES`: max captured child `stderr` bytes.
|
|
192
|
+
Default: `10000`.
|
|
169
193
|
- `PI_SUBAGENT_MAX_OUTPUT_BYTES`: max returned output bytes. Default:
|
|
170
194
|
`50000`.
|
|
171
195
|
- `PI_SUBAGENT_MAX_OUTPUT_LINES`: max returned output lines. Default:
|
|
172
196
|
`500`.
|
|
173
197
|
- `PI_SUBAGENT_DEBUG_ENABLED`: debug detail authorization. Set to `1` to
|
|
174
|
-
allow `debug: true` or `/run --debug` to include sanitized child
|
|
175
|
-
termination internals, and stderr
|
|
176
|
-
behavior.
|
|
198
|
+
allow `debug: true` or `/run --debug` to include sanitized child
|
|
199
|
+
messages, termination internals, and `stderr`; unset values keep
|
|
200
|
+
non-debug detail behavior.
|
|
201
|
+
|
|
202
|
+
Limit variables parse as positive integers. Empty, zero, negative,
|
|
203
|
+
decimal, `Infinity`, and non-numeric values fall back to defaults.
|
|
177
204
|
|
|
178
|
-
|
|
179
|
-
`Infinity`, and non-numeric values fall back to defaults.
|
|
205
|
+
---
|
|
180
206
|
|
|
181
207
|
## Troubleshooting
|
|
182
208
|
|
|
@@ -203,6 +229,8 @@ Limit variables parse as positive integers. Empty, zero, negative, decimal,
|
|
|
203
229
|
`PI_SUBAGENT_MAX_OUTPUT_LINES`.
|
|
204
230
|
- Ask the child agent for a shorter summary.
|
|
205
231
|
|
|
232
|
+
---
|
|
233
|
+
|
|
206
234
|
## Development
|
|
207
235
|
|
|
208
236
|
**Install dependencies:**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mystilleef/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Pi subagent for the SPAE Framework",
|
|
5
5
|
"author": "Lateef Alabi-Oki <mystilleef@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,14 +64,14 @@
|
|
|
64
64
|
"typebox": "*"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@biomejs/biome": "^2.
|
|
68
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
69
|
-
"@earendil-works/pi-ai": "^0.
|
|
70
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
71
|
-
"@earendil-works/pi-tui": "^0.
|
|
67
|
+
"@biomejs/biome": "^2.5.0",
|
|
68
|
+
"@earendil-works/pi-agent-core": "^0.79.3",
|
|
69
|
+
"@earendil-works/pi-ai": "^0.79.3",
|
|
70
|
+
"@earendil-works/pi-coding-agent": "^0.79.3",
|
|
71
|
+
"@earendil-works/pi-tui": "^0.79.3",
|
|
72
72
|
"@types/bun": "^1.3.14",
|
|
73
|
-
"@types/node": "^25.9.
|
|
74
|
-
"typebox": "^1.2.
|
|
73
|
+
"@types/node": "^25.9.3",
|
|
74
|
+
"typebox": "^1.2.9",
|
|
75
75
|
"typescript": "^6.0.3"
|
|
76
76
|
}
|
|
77
77
|
}
|
package/src/agent/agent-cache.ts
CHANGED
package/src/agent/agents.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface AgentConfig {
|
|
|
23
23
|
tools?: string[] | undefined;
|
|
24
24
|
skills?: string[] | undefined;
|
|
25
25
|
thinking?: ThinkingLevel | undefined;
|
|
26
|
+
model?: string | undefined;
|
|
27
|
+
provider?: string | undefined;
|
|
26
28
|
systemPrompt: string;
|
|
27
29
|
source: AgentSource;
|
|
28
30
|
filePath: string;
|
|
@@ -74,6 +76,12 @@ function parseThinkingLevel(raw: unknown): ThinkingLevel | undefined {
|
|
|
74
76
|
: undefined;
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
function parseOptionalString(raw: unknown): string | undefined {
|
|
80
|
+
if (typeof raw !== "string") return undefined;
|
|
81
|
+
const normalized = raw.trim();
|
|
82
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
function parseAgentConfig(
|
|
78
86
|
content: string,
|
|
79
87
|
source: AgentSource,
|
|
@@ -83,6 +91,7 @@ function parseAgentConfig(
|
|
|
83
91
|
try {
|
|
84
92
|
parsed = parseFrontmatter<Record<string, unknown>>(content);
|
|
85
93
|
} catch {
|
|
94
|
+
/* malformed frontmatter returns null to skip invalid agent files */
|
|
86
95
|
return null;
|
|
87
96
|
}
|
|
88
97
|
const { frontmatter, body } = parsed;
|
|
@@ -98,22 +107,31 @@ function parseAgentConfig(
|
|
|
98
107
|
tools: rawTools,
|
|
99
108
|
skills: rawSkills,
|
|
100
109
|
thinking: rawThinking,
|
|
110
|
+
model: rawModel,
|
|
111
|
+
provider: rawProvider,
|
|
101
112
|
} = frontmatter;
|
|
102
113
|
if (typeof name !== "string" || typeof description !== "string") return null;
|
|
103
114
|
if (rawTools != null && typeof rawTools !== "string") return null;
|
|
104
115
|
if (rawSkills != null && typeof rawSkills !== "string") return null;
|
|
105
116
|
if (rawThinking != null && typeof rawThinking !== "string") return null;
|
|
117
|
+
if (rawModel != null && typeof rawModel !== "string") return null;
|
|
118
|
+
if (rawProvider != null && typeof rawProvider !== "string") return null;
|
|
106
119
|
const tools = parseCommaList(rawTools);
|
|
107
120
|
const skills = Object.hasOwn(frontmatter, "skills")
|
|
108
121
|
? (parseCommaList(rawSkills) ?? [])
|
|
109
122
|
: undefined;
|
|
110
123
|
const thinking = parseThinkingLevel(rawThinking);
|
|
124
|
+
const model = parseOptionalString(rawModel);
|
|
125
|
+
const provider = parseOptionalString(rawProvider);
|
|
126
|
+
if (provider !== undefined && model === undefined) return null;
|
|
111
127
|
return {
|
|
112
128
|
name,
|
|
113
129
|
description,
|
|
114
130
|
tools,
|
|
115
131
|
skills,
|
|
116
132
|
thinking,
|
|
133
|
+
model,
|
|
134
|
+
provider,
|
|
117
135
|
systemPrompt: body,
|
|
118
136
|
source,
|
|
119
137
|
filePath,
|
|
@@ -130,6 +148,7 @@ async function loadAgentEntryAsync(
|
|
|
130
148
|
try {
|
|
131
149
|
content = await fsPromises.readFile(filePath, "utf-8");
|
|
132
150
|
} catch {
|
|
151
|
+
/* unreadable files are skipped silently during agent discovery */
|
|
133
152
|
return null;
|
|
134
153
|
}
|
|
135
154
|
return parseAgentConfig(content, source, filePath);
|
|
@@ -156,7 +175,7 @@ async function loadAgentsFromDirAsync(
|
|
|
156
175
|
return { agents, markdownFiles };
|
|
157
176
|
}
|
|
158
177
|
|
|
159
|
-
|
|
178
|
+
function isMarkdownDirent(entry: Dirent): boolean {
|
|
160
179
|
return (
|
|
161
180
|
entry.name.endsWith(".md") && (entry.isFile() || entry.isSymbolicLink())
|
|
162
181
|
);
|
|
@@ -175,6 +194,7 @@ export async function readMarkdownDirWithStatusAsync(
|
|
|
175
194
|
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
176
195
|
return { entries: entries.filter(isMarkdownDirent), ok: true };
|
|
177
196
|
} catch {
|
|
197
|
+
/* missing or inaccessible directories return empty listing */
|
|
178
198
|
return { entries: [], ok: false };
|
|
179
199
|
}
|
|
180
200
|
}
|
|
@@ -187,6 +207,7 @@ export async function isDirectoryAsync(p: string): Promise<boolean> {
|
|
|
187
207
|
try {
|
|
188
208
|
return (await fsPromises.stat(p)).isDirectory();
|
|
189
209
|
} catch {
|
|
210
|
+
/* stat failures indicate non-existent or inaccessible paths */
|
|
190
211
|
return false;
|
|
191
212
|
}
|
|
192
213
|
}
|
|
@@ -10,6 +10,7 @@ function tryFirstResult(details: unknown): Record<string, unknown> | null {
|
|
|
10
10
|
if (typeof nested !== "object" || nested === null) return null;
|
|
11
11
|
return nested as Record<string, unknown>;
|
|
12
12
|
} catch {
|
|
13
|
+
/* malformed details structure returns null to use fallback preview */
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
@@ -90,6 +91,7 @@ export function parseChildEventLine(line: string): ChildEventParseResult {
|
|
|
90
91
|
try {
|
|
91
92
|
event = JSON.parse(line);
|
|
92
93
|
} catch {
|
|
94
|
+
/* malformed JSON lines are marked invalid for diagnostic reporting */
|
|
93
95
|
return { kind: "invalid", line };
|
|
94
96
|
}
|
|
95
97
|
if (
|