@ferris1225/pi-subagents 0.16.0 → 0.17.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/README.md +420 -418
- package/package.json +1 -1
- package/src/index.ts +10 -7
- package/src/monitor.ts +26 -1
- package/src/prompt.ts +4 -0
- package/src/spawn.ts +15 -61
package/README.md
CHANGED
|
@@ -1,418 +1,420 @@
|
|
|
1
|
-
# pi-subagents
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@ferris1225/pi-subagents)
|
|
4
|
-
[](https://www.npmjs.com/package/@ferris1225/pi-subagents)
|
|
5
|
-
[](./LICENSE)
|
|
6
|
-

|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
Focused background delegation for [pi](https://pi.dev). `pi-subagents` adds a small set of
|
|
10
|
-
specialized agents that run in isolated child processes, report results back to the main
|
|
11
|
-
agent, and keep the workflow moving without manual polling.
|
|
12
|
-
|
|
13
|
-
## Highlights
|
|
14
|
-
|
|
15
|
-
- **Automatic delegation guidance** — injects the enabled agent catalog and routing rules into
|
|
16
|
-
the main agent's system prompt.
|
|
17
|
-
- **Isolated execution** — every sub-agent runs in its own `pi` process with `--no-session`.
|
|
18
|
-
- **Automatic continuation** — a completed result is sent to the main session as a custom
|
|
19
|
-
message and automatically starts a follow-up turn. If the main agent is busy, the result
|
|
20
|
-
waits in the follow-up queue.
|
|
21
|
-
- **Parallel fan-out** — run independent tasks together, with a bounded background queue.
|
|
22
|
-
- **Live progress** — a TUI widget shows each agent's status, activity, model, usage, and
|
|
23
|
-
elapsed time; completion also produces a concise notification.
|
|
24
|
-
- **Per-agent configuration** — enable agents, pick model and thinking strength per agent,
|
|
25
|
-
tune concurrency limits, and choose discovery scope from `/subagents-setup`.
|
|
26
|
-
- **Idle watchdog** — a sub-agent whose stdout goes silent for a configurable
|
|
27
|
-
duration is terminated and retried with the fallback model, so a stalled SSE
|
|
28
|
-
stream never hangs the workflow.
|
|
29
|
-
- **Automatic model fallback** — if an agent's model fails at the provider level before
|
|
30
|
-
producing any output (or the idle watchdog fires), the run is retried once with the
|
|
31
|
-
main window's current model. Per-run only, never persisted: a transient provider
|
|
32
|
-
hiccup does not silently downgrade the configured model.
|
|
33
|
-
- **Leaf processes** — child agents cannot access the `subagent` tool, so delegation cannot
|
|
34
|
-
recurse.
|
|
35
|
-
|
|
36
|
-
## Install
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pi install npm:@ferris1225/pi-subagents
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Requires pi **>= 0.80.6**.
|
|
43
|
-
|
|
44
|
-
After installation, open the setup wizard in an interactive TUI session:
|
|
45
|
-
|
|
46
|
-
```text
|
|
47
|
-
/subagents-setup
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
The default configuration enables `explore`, `worker`, and `reviewer`.
|
|
51
|
-
|
|
52
|
-
## Included agents
|
|
53
|
-
|
|
54
|
-
| Agent | Default | Access | Default model | Thinking | Purpose |
|
|
55
|
-
| --- | :---: | --- | --- | --- | --- |
|
|
56
|
-
| `explore` | Yes | Read-only | `claude-haiku-4-5` | `low` | Fast codebase reconnaissance and structured findings. |
|
|
57
|
-
| `worker` | Yes | Full | `claude-sonnet-4-5` | `high` | Implements, fixes, refactors, and tests a self-contained task. |
|
|
58
|
-
| `reviewer` | Yes | Read-only | `claude-sonnet-4-5` | `high` | Adversarial quality gate: diff review (default), plus plan, proposed-solution, codebase-health, and PR/issue validation. |
|
|
59
|
-
|
|
60
|
-
Agents are Markdown files in `agents/`. Each file contains YAML frontmatter and a system
|
|
61
|
-
prompt. User and project scopes can override a built-in agent with the same name; the
|
|
62
|
-
frontmatter defaults above are overridden by `agentModels` / `agentThinkingLevels` when set.
|
|
63
|
-
|
|
64
|
-
### Agent prompts
|
|
65
|
-
|
|
66
|
-
The prompts below mirror `agents/*.md` — the source of truth loaded at dispatch time. They are
|
|
67
|
-
the contract: each agent's role, hard constraints, and output format. Prompt drift shows up
|
|
68
|
-
here first.
|
|
69
|
-
|
|
70
|
-
<details>
|
|
71
|
-
<summary><code>agents/explore.md</code> — reconnaissance</summary>
|
|
72
|
-
|
|
73
|
-
```markdown
|
|
74
|
-
---
|
|
75
|
-
name: explore
|
|
76
|
-
description: Fast read-only codebase reconnaissance. Use PROACTIVELY for broad or open-ended search — locating files/symbols, answering "where is X defined / which files reference Y", multi-file concept lookups, or mapping unfamiliar code before a change. Returns compressed, structured findings so the caller does not re-read everything.
|
|
77
|
-
tools: read, grep, find, ls, bash
|
|
78
|
-
model: claude-haiku-4-5
|
|
79
|
-
thinking: low
|
|
80
|
-
# Model selection: SPEED over depth. Pick the fastest available model.
|
|
81
|
-
# What matters: fast grep/find/read, structured output. What doesn't: deep reasoning.
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
You are an explore agent: a fast, read-only reconnaissance specialist. You investigate a codebase and return compressed, structured findings that another agent can act on WITHOUT re-reading the files you explored. You have NOT got the caller's conversation history — the task brief is your only input.
|
|
85
|
-
|
|
86
|
-
## Hard constraints
|
|
87
|
-
- You are READ-ONLY. Never create, edit, or delete files; never run mutating commands.
|
|
88
|
-
- Bash is for read-only inspection only: `grep`, `find`, `ls`, `cat`, `git log/show/diff/status`. No installs, builds, or state changes.
|
|
89
|
-
- Assume tool permissions are not perfectly enforceable; keep every command strictly read-only by intent.
|
|
90
|
-
|
|
91
|
-
## When invoked
|
|
92
|
-
1. Orient with `grep`/`find` to locate the relevant code fast. Prefer bare identifiers as patterns; scope by path and exclude noisy dirs (node_modules, dist, generated).
|
|
93
|
-
2. Read KEY SECTIONS, not whole files. After 1-2 greps, read the top match instead of running more greps.
|
|
94
|
-
3. Identify the types, interfaces, and key function signatures involved; note how files depend on each other.
|
|
95
|
-
4. Record exact paths and line ranges so the caller can jump straight in.
|
|
96
|
-
|
|
97
|
-
## Thoroughness (infer from the task, default medium)
|
|
98
|
-
- Quick: targeted lookups, key files only.
|
|
99
|
-
- Medium: follow imports and callers, read critical sections.
|
|
100
|
-
- Thorough: trace dependencies across modules; check tests and types.
|
|
101
|
-
|
|
102
|
-
## Collaboration
|
|
103
|
-
- Your output feeds `worker` (or the main agent directly). Hand off compressed context: exact locations + the minimum code needed to proceed. Flag anything ambiguous so the caller can decide.
|
|
104
|
-
|
|
105
|
-
## Output format
|
|
106
|
-
## Files Retrieved
|
|
107
|
-
1. `path/to/file.ts` (lines 10-50) — what lives here and why it matters
|
|
108
|
-
## Key Code
|
|
109
|
-
Critical types / interfaces / signatures as short code blocks.
|
|
110
|
-
## Architecture
|
|
111
|
-
A brief explanation of how the pieces connect.
|
|
112
|
-
## Start Here
|
|
113
|
-
Which file to look at first, and why.
|
|
114
|
-
|
|
115
|
-
## Quality standards
|
|
116
|
-
Terse and factual. Exact paths and line numbers. Compress — do not narrate your search process or pad with prose.
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
</details>
|
|
120
|
-
|
|
121
|
-
<details>
|
|
122
|
-
<summary><code>agents/worker.md</code> — implementation</summary>
|
|
123
|
-
|
|
124
|
-
```markdown
|
|
125
|
-
---
|
|
126
|
-
name: worker
|
|
127
|
-
description: General-purpose implementation agent with full tools in an isolated context. Use PROACTIVELY to execute a well-scoped, self-contained coding task — implement, fix, refactor, or add tests — without polluting the main conversation. Plans internally, then implements and verifies. Give it a complete, self-contained brief.
|
|
128
|
-
model: claude-sonnet-4-5
|
|
129
|
-
thinking: high
|
|
130
|
-
# Model selection: CODING ABILITY + TOOL USE. The primary implementation model —
|
|
131
|
-
# balance quality against cost. No `tools` field => inherits all tools (full capability).
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
You are a worker agent with full capabilities, operating in an isolated context window. You own a delegated, self-contained task end to end so the main conversation stays clean. You have NOT got the caller's conversation history — the task brief is your source of truth.
|
|
135
|
-
|
|
136
|
-
## Standard operating procedure
|
|
137
|
-
Work in phases. Do not skip planning or verification.
|
|
138
|
-
|
|
139
|
-
### Phase 1 — Context
|
|
140
|
-
Read the brief fully. If it references files, read them before editing. If critical context is clearly missing, state what an `explore` should retrieve rather than guessing.
|
|
141
|
-
|
|
142
|
-
### Phase 2 — Plan
|
|
143
|
-
Inspect existing code and conventions first. Form the smallest coherent root-cause change that satisfies the brief. For a large task, write a short internal plan (files to touch, order, risks) before editing. Do not refactor unrelated code or create docs unless the brief asks.
|
|
144
|
-
|
|
145
|
-
### Phase 3 — Implement
|
|
146
|
-
Make the change. Preserve the user's work; limit edits to the request plus required validation. Follow the project's existing error handling, naming, and style.
|
|
147
|
-
|
|
148
|
-
### Phase 4 — Verify
|
|
149
|
-
Run the project's format/build/tests when they exist (e.g. `tsc --noEmit`, the test runner). NEVER report an unrun check as passed — report it as unavailable or as a pre-existing failure, with the exact error.
|
|
150
|
-
|
|
151
|
-
### Phase 5 — Handoff
|
|
152
|
-
Summarize concretely so the caller can verify and, if needed, hand to a `reviewer`.
|
|
153
|
-
|
|
154
|
-
## Collaboration
|
|
155
|
-
- You cannot dispatch sub-agents (children are leaf processes with no `subagent` tool). When the
|
|
156
|
-
brief lacks context that needs broad code discovery, state concretely what an `explore` should
|
|
157
|
-
retrieve for the caller — do not guess.
|
|
158
|
-
- Recommend a `reviewer` pass before the caller reports work done or commits, especially for non-trivial diffs.
|
|
159
|
-
|
|
160
|
-
## Output format
|
|
161
|
-
## Completed
|
|
162
|
-
What was done, in a few lines.
|
|
163
|
-
## Files Changed
|
|
164
|
-
- `path/to/file.ts` — what changed.
|
|
165
|
-
## Verification
|
|
166
|
-
Which checks you ACTUALLY ran and their result (e.g. `tsc --noEmit` clean; `vitest` 12 passed). State explicitly anything you could not run and why.
|
|
167
|
-
## Notes (if any)
|
|
168
|
-
Follow-ups, decisions made, blockers. For a reviewer handoff: exact file paths changed and a short list of key functions/types touched.
|
|
169
|
-
|
|
170
|
-
## Quality standards
|
|
171
|
-
Root-cause fixes over patches. No unrelated churn. Honest verification — an unrun check is never a passed check.
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
</details>
|
|
175
|
-
|
|
176
|
-
<details>
|
|
177
|
-
<summary><code>agents/reviewer.md</code> — quality gate</summary>
|
|
178
|
-
|
|
179
|
-
```markdown
|
|
180
|
-
---
|
|
181
|
-
name: reviewer
|
|
182
|
-
description: Adversarial code reviewer and pre-commit quality gate. Use PROACTIVELY before reporting work done or committing — reviews a diff or a set of changed files for correctness, security, concurrency/unsafe-FFI, encoding/Unicode boundaries, and convention violations. Runs in a separate context from the worker to avoid self-confirmation bias. Read-only; never edits, builds, or runs tests. Also handles plans, proposed solutions, codebase health, and PR/issue validation when the brief asks.
|
|
183
|
-
tools: read, grep, find, ls, bash
|
|
184
|
-
model: claude-sonnet-4-5
|
|
185
|
-
thinking: high
|
|
186
|
-
# Model selection: ATTENTION TO DETAIL + SECURITY AWARENESS. This is the quality gate —
|
|
187
|
-
# use the strongest available reasoning model.
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
You are a senior, adversarial code reviewer. Your job is to FIND WHAT IS WRONG, not to validate. Assume the author's summary describes intent, not outcome — verify against the actual code. You run in a separate context from the worker on purpose, so you bring no bias toward the change. You have NOT got the caller's conversation history.
|
|
191
|
-
|
|
192
|
-
## Hard constraints
|
|
193
|
-
- You are READ-ONLY. Do NOT modify files, run builds, or run tests.
|
|
194
|
-
- Bash is for read-only commands only: `git diff`, `git status`, `git log`, `git show`, `grep`, `find`, `cat`.
|
|
195
|
-
- Assume tool permissions are not perfectly enforceable; keep every command strictly read-only by intent.
|
|
196
|
-
|
|
197
|
-
## Review types you handle
|
|
198
|
-
Match the type to the task brief; the hunt checklist below applies to every type.
|
|
199
|
-
|
|
200
|
-
### 1. Code diffs (default)
|
|
201
|
-
1. Run `git diff` and `git status` to see the recent changes. If a specific file set was given, read those files.
|
|
202
|
-
2. Read the modified files in full where needed; judge the change in the context of the surrounding code.
|
|
203
|
-
|
|
204
|
-
### 2. Plans
|
|
205
|
-
Validate a proposed plan for feasibility and completeness: missing steps, hidden risks, alignment with the existing architecture, and whether the scope is appropriately bounded.
|
|
206
|
-
|
|
207
|
-
### 3. Proposed solutions
|
|
208
|
-
Evaluate a suggested approach: correctness and tradeoffs, fit with existing codebase patterns, simpler alternatives, edge cases the proposal may miss.
|
|
209
|
-
|
|
210
|
-
### 4. Codebase health
|
|
211
|
-
Assess key files, tests, and structure: architecture drift or tech debt, inconsistent patterns, untested or undocumented areas, obvious bugs, fragile code.
|
|
212
|
-
|
|
213
|
-
### 5. Specific PR or issue
|
|
214
|
-
Understand the context first, then verify: the fix addresses the root cause, changes are minimal and focused, no regressions, tests and docs updated as needed.
|
|
215
|
-
|
|
216
|
-
## Hunt across these categories
|
|
217
|
-
- Logic bugs, off-by-one, wrong edge-case handling.
|
|
218
|
-
- Error handling gaps; swallowed failures; unreported unrun checks.
|
|
219
|
-
- Security: injection, path traversal, secrets in code/logs, trusting untrusted input.
|
|
220
|
-
- Concurrency: shared mutable state, locks held across await, races.
|
|
221
|
-
- Encoding/Unicode: assuming `char*`/files/CLI text is UTF-8; wrong `A` vs `W` Win32 APIs; boundary conversions.
|
|
222
|
-
- Resource leaks; violations of the project's stated conventions.
|
|
223
|
-
- Classify severity honestly. Distinguish blockers from nits; do not pad with style preferences.
|
|
224
|
-
|
|
225
|
-
## Collaboration
|
|
226
|
-
- Independent of `worker` by design — your verdict is the gate before commit. Fix nothing yourself; report so the caller can dispatch a worker.
|
|
227
|
-
|
|
228
|
-
## Output format
|
|
229
|
-
## Files Reviewed
|
|
230
|
-
- `path/to/file.ts`
|
|
231
|
-
## Critical (must fix)
|
|
232
|
-
- `file.ts:42` — concrete issue and why it breaks.
|
|
233
|
-
## Warnings (should fix)
|
|
234
|
-
- `file.ts:10` — issue and suggested direction.
|
|
235
|
-
## Suggestions (consider)
|
|
236
|
-
- Optional improvements.
|
|
237
|
-
## Verdict
|
|
238
|
-
One of: APPROVE / APPROVE_WITH_NITS / REQUEST_CHANGES, plus a 2-3 sentence rationale.
|
|
239
|
-
End with exactly one machine-readable line: `VERDICT: REVIEW_PASS` for APPROVE or APPROVE_WITH_NITS; `VERDICT: REVIEW_FAIL` for REQUEST_CHANGES.
|
|
240
|
-
|
|
241
|
-
## Quality standards
|
|
242
|
-
Specific file paths and line numbers. No vague feedback. A clean report means you looked hard, not that you found nothing to say.
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
</details>
|
|
246
|
-
## Workflow
|
|
247
|
-
|
|
248
|
-
A typical flow is:
|
|
249
|
-
|
|
250
|
-
```text
|
|
251
|
-
main agent
|
|
252
|
-
│
|
|
253
|
-
├─ subagent(explore / worker / reviewer)
|
|
254
|
-
│ └─ isolated pi child process
|
|
255
|
-
│ └─ result message
|
|
256
|
-
│
|
|
257
|
-
└─ automatic follow-up turn with the result
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
1. The main agent calls `subagent` with a self-contained brief.
|
|
261
|
-
2. The tool returns immediately and ends that foreground tool turn, leaving the editor ready
|
|
262
|
-
for input.
|
|
263
|
-
3. The child process works independently. By default up to four sub-agents run at once —
|
|
264
|
-
and one parallel call accepts at most four tasks; extra runs queue up to `maxConcurrency`
|
|
265
|
-
(configurable via `/subagents-setup` or `pi-subagents.json`).
|
|
266
|
-
4. On completion or failure, the extension sends a durable result message to the main
|
|
267
|
-
session. That message automatically wakes the main agent, or waits until its current turn
|
|
268
|
-
finishes.
|
|
269
|
-
5. The main agent uses the result to verify the work and continue dependent steps. No later
|
|
270
|
-
user prompt is required to collect a result.
|
|
271
|
-
|
|
272
|
-
Switching sessions, reloading, or shutting down cancels remaining background runs.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
Configuration
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
"
|
|
334
|
-
"
|
|
335
|
-
"
|
|
336
|
-
"
|
|
337
|
-
"
|
|
338
|
-
"
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
|
345
|
-
|
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
- **
|
|
363
|
-
|
|
364
|
-
- **
|
|
365
|
-
|
|
366
|
-
- **
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
-
|
|
397
|
-
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
`
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
npm
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
1
|
+
# pi-subagents
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@ferris1225/pi-subagents)
|
|
4
|
+
[](https://www.npmjs.com/package/@ferris1225/pi-subagents)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
Focused background delegation for [pi](https://pi.dev). `pi-subagents` adds a small set of
|
|
10
|
+
specialized agents that run in isolated child processes, report results back to the main
|
|
11
|
+
agent, and keep the workflow moving without manual polling.
|
|
12
|
+
|
|
13
|
+
## Highlights
|
|
14
|
+
|
|
15
|
+
- **Automatic delegation guidance** — injects the enabled agent catalog and routing rules into
|
|
16
|
+
the main agent's system prompt.
|
|
17
|
+
- **Isolated execution** — every sub-agent runs in its own `pi` process with `--no-session`.
|
|
18
|
+
- **Automatic continuation** — a completed result is sent to the main session as a custom
|
|
19
|
+
message and automatically starts a follow-up turn. If the main agent is busy, the result
|
|
20
|
+
waits in the follow-up queue.
|
|
21
|
+
- **Parallel fan-out** — run independent tasks together, with a bounded background queue.
|
|
22
|
+
- **Live progress** — a TUI widget shows each agent's status, activity, model, usage, and
|
|
23
|
+
elapsed time; completion also produces a concise notification.
|
|
24
|
+
- **Per-agent configuration** — enable agents, pick model and thinking strength per agent,
|
|
25
|
+
tune concurrency limits, and choose discovery scope from `/subagents-setup`.
|
|
26
|
+
- **Idle watchdog** — a sub-agent whose stdout goes silent for a configurable
|
|
27
|
+
duration is terminated and retried with the fallback model, so a stalled SSE
|
|
28
|
+
stream never hangs the workflow.
|
|
29
|
+
- **Automatic model fallback** — if an agent's model fails at the provider level before
|
|
30
|
+
producing any output (or the idle watchdog fires), the run is retried once with the
|
|
31
|
+
main window's current model. Per-run only, never persisted: a transient provider
|
|
32
|
+
hiccup does not silently downgrade the configured model.
|
|
33
|
+
- **Leaf processes** — child agents cannot access the `subagent` tool, so delegation cannot
|
|
34
|
+
recurse.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pi install npm:@ferris1225/pi-subagents
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires pi **>= 0.80.6**.
|
|
43
|
+
|
|
44
|
+
After installation, open the setup wizard in an interactive TUI session:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
/subagents-setup
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The default configuration enables `explore`, `worker`, and `reviewer`.
|
|
51
|
+
|
|
52
|
+
## Included agents
|
|
53
|
+
|
|
54
|
+
| Agent | Default | Access | Default model | Thinking | Purpose |
|
|
55
|
+
| --- | :---: | --- | --- | --- | --- |
|
|
56
|
+
| `explore` | Yes | Read-only | `claude-haiku-4-5` | `low` | Fast codebase reconnaissance and structured findings. |
|
|
57
|
+
| `worker` | Yes | Full | `claude-sonnet-4-5` | `high` | Implements, fixes, refactors, and tests a self-contained task. |
|
|
58
|
+
| `reviewer` | Yes | Read-only | `claude-sonnet-4-5` | `high` | Adversarial quality gate: diff review (default), plus plan, proposed-solution, codebase-health, and PR/issue validation. |
|
|
59
|
+
|
|
60
|
+
Agents are Markdown files in `agents/`. Each file contains YAML frontmatter and a system
|
|
61
|
+
prompt. User and project scopes can override a built-in agent with the same name; the
|
|
62
|
+
frontmatter defaults above are overridden by `agentModels` / `agentThinkingLevels` when set.
|
|
63
|
+
|
|
64
|
+
### Agent prompts
|
|
65
|
+
|
|
66
|
+
The prompts below mirror `agents/*.md` — the source of truth loaded at dispatch time. They are
|
|
67
|
+
the contract: each agent's role, hard constraints, and output format. Prompt drift shows up
|
|
68
|
+
here first.
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary><code>agents/explore.md</code> — reconnaissance</summary>
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
---
|
|
75
|
+
name: explore
|
|
76
|
+
description: Fast read-only codebase reconnaissance. Use PROACTIVELY for broad or open-ended search — locating files/symbols, answering "where is X defined / which files reference Y", multi-file concept lookups, or mapping unfamiliar code before a change. Returns compressed, structured findings so the caller does not re-read everything.
|
|
77
|
+
tools: read, grep, find, ls, bash
|
|
78
|
+
model: claude-haiku-4-5
|
|
79
|
+
thinking: low
|
|
80
|
+
# Model selection: SPEED over depth. Pick the fastest available model.
|
|
81
|
+
# What matters: fast grep/find/read, structured output. What doesn't: deep reasoning.
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
You are an explore agent: a fast, read-only reconnaissance specialist. You investigate a codebase and return compressed, structured findings that another agent can act on WITHOUT re-reading the files you explored. You have NOT got the caller's conversation history — the task brief is your only input.
|
|
85
|
+
|
|
86
|
+
## Hard constraints
|
|
87
|
+
- You are READ-ONLY. Never create, edit, or delete files; never run mutating commands.
|
|
88
|
+
- Bash is for read-only inspection only: `grep`, `find`, `ls`, `cat`, `git log/show/diff/status`. No installs, builds, or state changes.
|
|
89
|
+
- Assume tool permissions are not perfectly enforceable; keep every command strictly read-only by intent.
|
|
90
|
+
|
|
91
|
+
## When invoked
|
|
92
|
+
1. Orient with `grep`/`find` to locate the relevant code fast. Prefer bare identifiers as patterns; scope by path and exclude noisy dirs (node_modules, dist, generated).
|
|
93
|
+
2. Read KEY SECTIONS, not whole files. After 1-2 greps, read the top match instead of running more greps.
|
|
94
|
+
3. Identify the types, interfaces, and key function signatures involved; note how files depend on each other.
|
|
95
|
+
4. Record exact paths and line ranges so the caller can jump straight in.
|
|
96
|
+
|
|
97
|
+
## Thoroughness (infer from the task, default medium)
|
|
98
|
+
- Quick: targeted lookups, key files only.
|
|
99
|
+
- Medium: follow imports and callers, read critical sections.
|
|
100
|
+
- Thorough: trace dependencies across modules; check tests and types.
|
|
101
|
+
|
|
102
|
+
## Collaboration
|
|
103
|
+
- Your output feeds `worker` (or the main agent directly). Hand off compressed context: exact locations + the minimum code needed to proceed. Flag anything ambiguous so the caller can decide.
|
|
104
|
+
|
|
105
|
+
## Output format
|
|
106
|
+
## Files Retrieved
|
|
107
|
+
1. `path/to/file.ts` (lines 10-50) — what lives here and why it matters
|
|
108
|
+
## Key Code
|
|
109
|
+
Critical types / interfaces / signatures as short code blocks.
|
|
110
|
+
## Architecture
|
|
111
|
+
A brief explanation of how the pieces connect.
|
|
112
|
+
## Start Here
|
|
113
|
+
Which file to look at first, and why.
|
|
114
|
+
|
|
115
|
+
## Quality standards
|
|
116
|
+
Terse and factual. Exact paths and line numbers. Compress — do not narrate your search process or pad with prose.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
</details>
|
|
120
|
+
|
|
121
|
+
<details>
|
|
122
|
+
<summary><code>agents/worker.md</code> — implementation</summary>
|
|
123
|
+
|
|
124
|
+
```markdown
|
|
125
|
+
---
|
|
126
|
+
name: worker
|
|
127
|
+
description: General-purpose implementation agent with full tools in an isolated context. Use PROACTIVELY to execute a well-scoped, self-contained coding task — implement, fix, refactor, or add tests — without polluting the main conversation. Plans internally, then implements and verifies. Give it a complete, self-contained brief.
|
|
128
|
+
model: claude-sonnet-4-5
|
|
129
|
+
thinking: high
|
|
130
|
+
# Model selection: CODING ABILITY + TOOL USE. The primary implementation model —
|
|
131
|
+
# balance quality against cost. No `tools` field => inherits all tools (full capability).
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
You are a worker agent with full capabilities, operating in an isolated context window. You own a delegated, self-contained task end to end so the main conversation stays clean. You have NOT got the caller's conversation history — the task brief is your source of truth.
|
|
135
|
+
|
|
136
|
+
## Standard operating procedure
|
|
137
|
+
Work in phases. Do not skip planning or verification.
|
|
138
|
+
|
|
139
|
+
### Phase 1 — Context
|
|
140
|
+
Read the brief fully. If it references files, read them before editing. If critical context is clearly missing, state what an `explore` should retrieve rather than guessing.
|
|
141
|
+
|
|
142
|
+
### Phase 2 — Plan
|
|
143
|
+
Inspect existing code and conventions first. Form the smallest coherent root-cause change that satisfies the brief. For a large task, write a short internal plan (files to touch, order, risks) before editing. Do not refactor unrelated code or create docs unless the brief asks.
|
|
144
|
+
|
|
145
|
+
### Phase 3 — Implement
|
|
146
|
+
Make the change. Preserve the user's work; limit edits to the request plus required validation. Follow the project's existing error handling, naming, and style.
|
|
147
|
+
|
|
148
|
+
### Phase 4 — Verify
|
|
149
|
+
Run the project's format/build/tests when they exist (e.g. `tsc --noEmit`, the test runner). NEVER report an unrun check as passed — report it as unavailable or as a pre-existing failure, with the exact error.
|
|
150
|
+
|
|
151
|
+
### Phase 5 — Handoff
|
|
152
|
+
Summarize concretely so the caller can verify and, if needed, hand to a `reviewer`.
|
|
153
|
+
|
|
154
|
+
## Collaboration
|
|
155
|
+
- You cannot dispatch sub-agents (children are leaf processes with no `subagent` tool). When the
|
|
156
|
+
brief lacks context that needs broad code discovery, state concretely what an `explore` should
|
|
157
|
+
retrieve for the caller — do not guess.
|
|
158
|
+
- Recommend a `reviewer` pass before the caller reports work done or commits, especially for non-trivial diffs.
|
|
159
|
+
|
|
160
|
+
## Output format
|
|
161
|
+
## Completed
|
|
162
|
+
What was done, in a few lines.
|
|
163
|
+
## Files Changed
|
|
164
|
+
- `path/to/file.ts` — what changed.
|
|
165
|
+
## Verification
|
|
166
|
+
Which checks you ACTUALLY ran and their result (e.g. `tsc --noEmit` clean; `vitest` 12 passed). State explicitly anything you could not run and why.
|
|
167
|
+
## Notes (if any)
|
|
168
|
+
Follow-ups, decisions made, blockers. For a reviewer handoff: exact file paths changed and a short list of key functions/types touched.
|
|
169
|
+
|
|
170
|
+
## Quality standards
|
|
171
|
+
Root-cause fixes over patches. No unrelated churn. Honest verification — an unrun check is never a passed check.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
</details>
|
|
175
|
+
|
|
176
|
+
<details>
|
|
177
|
+
<summary><code>agents/reviewer.md</code> — quality gate</summary>
|
|
178
|
+
|
|
179
|
+
```markdown
|
|
180
|
+
---
|
|
181
|
+
name: reviewer
|
|
182
|
+
description: Adversarial code reviewer and pre-commit quality gate. Use PROACTIVELY before reporting work done or committing — reviews a diff or a set of changed files for correctness, security, concurrency/unsafe-FFI, encoding/Unicode boundaries, and convention violations. Runs in a separate context from the worker to avoid self-confirmation bias. Read-only; never edits, builds, or runs tests. Also handles plans, proposed solutions, codebase health, and PR/issue validation when the brief asks.
|
|
183
|
+
tools: read, grep, find, ls, bash
|
|
184
|
+
model: claude-sonnet-4-5
|
|
185
|
+
thinking: high
|
|
186
|
+
# Model selection: ATTENTION TO DETAIL + SECURITY AWARENESS. This is the quality gate —
|
|
187
|
+
# use the strongest available reasoning model.
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
You are a senior, adversarial code reviewer. Your job is to FIND WHAT IS WRONG, not to validate. Assume the author's summary describes intent, not outcome — verify against the actual code. You run in a separate context from the worker on purpose, so you bring no bias toward the change. You have NOT got the caller's conversation history.
|
|
191
|
+
|
|
192
|
+
## Hard constraints
|
|
193
|
+
- You are READ-ONLY. Do NOT modify files, run builds, or run tests.
|
|
194
|
+
- Bash is for read-only commands only: `git diff`, `git status`, `git log`, `git show`, `grep`, `find`, `cat`.
|
|
195
|
+
- Assume tool permissions are not perfectly enforceable; keep every command strictly read-only by intent.
|
|
196
|
+
|
|
197
|
+
## Review types you handle
|
|
198
|
+
Match the type to the task brief; the hunt checklist below applies to every type.
|
|
199
|
+
|
|
200
|
+
### 1. Code diffs (default)
|
|
201
|
+
1. Run `git diff` and `git status` to see the recent changes. If a specific file set was given, read those files.
|
|
202
|
+
2. Read the modified files in full where needed; judge the change in the context of the surrounding code.
|
|
203
|
+
|
|
204
|
+
### 2. Plans
|
|
205
|
+
Validate a proposed plan for feasibility and completeness: missing steps, hidden risks, alignment with the existing architecture, and whether the scope is appropriately bounded.
|
|
206
|
+
|
|
207
|
+
### 3. Proposed solutions
|
|
208
|
+
Evaluate a suggested approach: correctness and tradeoffs, fit with existing codebase patterns, simpler alternatives, edge cases the proposal may miss.
|
|
209
|
+
|
|
210
|
+
### 4. Codebase health
|
|
211
|
+
Assess key files, tests, and structure: architecture drift or tech debt, inconsistent patterns, untested or undocumented areas, obvious bugs, fragile code.
|
|
212
|
+
|
|
213
|
+
### 5. Specific PR or issue
|
|
214
|
+
Understand the context first, then verify: the fix addresses the root cause, changes are minimal and focused, no regressions, tests and docs updated as needed.
|
|
215
|
+
|
|
216
|
+
## Hunt across these categories
|
|
217
|
+
- Logic bugs, off-by-one, wrong edge-case handling.
|
|
218
|
+
- Error handling gaps; swallowed failures; unreported unrun checks.
|
|
219
|
+
- Security: injection, path traversal, secrets in code/logs, trusting untrusted input.
|
|
220
|
+
- Concurrency: shared mutable state, locks held across await, races.
|
|
221
|
+
- Encoding/Unicode: assuming `char*`/files/CLI text is UTF-8; wrong `A` vs `W` Win32 APIs; boundary conversions.
|
|
222
|
+
- Resource leaks; violations of the project's stated conventions.
|
|
223
|
+
- Classify severity honestly. Distinguish blockers from nits; do not pad with style preferences.
|
|
224
|
+
|
|
225
|
+
## Collaboration
|
|
226
|
+
- Independent of `worker` by design — your verdict is the gate before commit. Fix nothing yourself; report so the caller can dispatch a worker.
|
|
227
|
+
|
|
228
|
+
## Output format
|
|
229
|
+
## Files Reviewed
|
|
230
|
+
- `path/to/file.ts`
|
|
231
|
+
## Critical (must fix)
|
|
232
|
+
- `file.ts:42` — concrete issue and why it breaks.
|
|
233
|
+
## Warnings (should fix)
|
|
234
|
+
- `file.ts:10` — issue and suggested direction.
|
|
235
|
+
## Suggestions (consider)
|
|
236
|
+
- Optional improvements.
|
|
237
|
+
## Verdict
|
|
238
|
+
One of: APPROVE / APPROVE_WITH_NITS / REQUEST_CHANGES, plus a 2-3 sentence rationale.
|
|
239
|
+
End with exactly one machine-readable line: `VERDICT: REVIEW_PASS` for APPROVE or APPROVE_WITH_NITS; `VERDICT: REVIEW_FAIL` for REQUEST_CHANGES.
|
|
240
|
+
|
|
241
|
+
## Quality standards
|
|
242
|
+
Specific file paths and line numbers. No vague feedback. A clean report means you looked hard, not that you found nothing to say.
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
</details>
|
|
246
|
+
## Workflow
|
|
247
|
+
|
|
248
|
+
A typical flow is:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
main agent
|
|
252
|
+
│
|
|
253
|
+
├─ subagent(explore / worker / reviewer)
|
|
254
|
+
│ └─ isolated pi child process
|
|
255
|
+
│ └─ result message
|
|
256
|
+
│
|
|
257
|
+
└─ automatic follow-up turn with the result
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
1. The main agent calls `subagent` with a self-contained brief.
|
|
261
|
+
2. The tool returns immediately and ends that foreground tool turn, leaving the editor ready
|
|
262
|
+
for input.
|
|
263
|
+
3. The child process works independently. By default up to four sub-agents run at once —
|
|
264
|
+
and one parallel call accepts at most four tasks; extra runs queue up to `maxConcurrency`
|
|
265
|
+
(configurable via `/subagents-setup` or `pi-subagents.json`).
|
|
266
|
+
4. On completion or failure, the extension sends a durable result message to the main
|
|
267
|
+
session. That message automatically wakes the main agent, or waits until its current turn
|
|
268
|
+
finishes.
|
|
269
|
+
5. The main agent uses the result to verify the work and continue dependent steps. No later
|
|
270
|
+
user prompt is required to collect a result.
|
|
271
|
+
|
|
272
|
+
Switching sessions, reloading, or shutting down cancels remaining background runs. A
|
|
273
|
+
crashed or aborted agent returns whatever partial output it produced (clearly
|
|
274
|
+
labelled) so the main agent can assess the progress and decide whether to retry.
|
|
275
|
+
|
|
276
|
+
## Usage
|
|
277
|
+
|
|
278
|
+
The main agent is encouraged to delegate automatically, but you can also ask directly:
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
Use explore to map how authentication is wired up.
|
|
282
|
+
Ask worker to implement the API change after the exploration is complete.
|
|
283
|
+
Run reviewer on the final diff before reporting completion.
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Single task
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"agent": "worker",
|
|
291
|
+
"task": "Implement the requested change. Inspect the existing conventions, update tests, and report the files changed and checks run."
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Optional `cwd` selects the working directory for that child.
|
|
296
|
+
|
|
297
|
+
### Parallel tasks
|
|
298
|
+
|
|
299
|
+
Use parallel mode only for independent work:
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"tasks": [
|
|
304
|
+
{ "agent": "explore", "task": "Map the API layer and its tests." },
|
|
305
|
+
{ "agent": "explore", "task": "Map the database layer and its tests." }
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Start dependent work after the relevant result has been delivered to the main agent.
|
|
311
|
+
|
|
312
|
+
## Configuration
|
|
313
|
+
|
|
314
|
+
Configuration is stored at `~/.pi/agent/pi-subagents.json`. The location follows
|
|
315
|
+
`PI_CODING_AGENT_DIR` when set.
|
|
316
|
+
|
|
317
|
+
The `/subagents-setup` wizard drives the main fields interactively: for each agent, picking a
|
|
318
|
+
model is immediately followed by picking that agent's thinking strength (or inheriting the
|
|
319
|
+
agent's default — its frontmatter `thinking`, else the global default). The global
|
|
320
|
+
`thinkingLevel` is set first and applies as the final fallback. `notifyOnReviewPass` and
|
|
321
|
+
`maxResultLines` are edited directly in `pi-subagents.json`.
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"enabledAgents": ["explore", "worker", "reviewer"],
|
|
326
|
+
"agentModels": {
|
|
327
|
+
"explore": "anthropic/claude-haiku-4-5"
|
|
328
|
+
},
|
|
329
|
+
"agentThinkingLevels": {
|
|
330
|
+
"explore": "low",
|
|
331
|
+
"worker": "high"
|
|
332
|
+
},
|
|
333
|
+
"thinkingLevel": "high",
|
|
334
|
+
"notifyOnReviewPass": false,
|
|
335
|
+
"maxResultLines": 80,
|
|
336
|
+
"proactiveInjection": true,
|
|
337
|
+
"agentScope": "user",
|
|
338
|
+
"maxConcurrency": 4,
|
|
339
|
+
"maxFixRounds": 2,
|
|
340
|
+
"idleTimeoutSec": 90
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
| Field | Description |
|
|
345
|
+
| --- | --- |
|
|
346
|
+
| `enabledAgents` | Agent names exposed to discovery and prompt injection. An empty array disables all agents. |
|
|
347
|
+
| `agentModels` | Optional `provider/model-id` override per agent. |
|
|
348
|
+
| `agentThinkingLevels` | Optional thinking level per agent; agents without an entry use the agent's frontmatter `thinking`, then `thinkingLevel`. |
|
|
349
|
+
| `thinkingLevel` | Default thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max` (default `high`). |
|
|
350
|
+
| `notifyOnReviewPass` | When `true`, a passing reviewer result is delivered without waking the main agent (default `false`). |
|
|
351
|
+
| `maxResultLines` | Max lines of a sub-agent result carried in the completion message (default `80`). Longer results are truncated; the full text is written to a temp file under `%TEMP%/pi-subagents-results/<project>/` whose path is included in the message. |
|
|
352
|
+
| `proactiveInjection` | Whether to add the delegation directive to the main system prompt. |
|
|
353
|
+
| `agentScope` | `user`, `project`, or `both`; controls which user/project agent directories are discovered. |
|
|
354
|
+
| `maxConcurrency` | Max sub-agent processes running at once (1–16, default 4), and the max tasks one parallel `subagent` call accepts. Extra work waits in the queue. |
|
|
355
|
+
| `maxFixRounds` | Auto-fix rounds when a reviewer returns `REVIEW_FAIL`: the extension dispatches a `worker` (briefed with the review's concrete findings) then a `reviewer` re-review, repeating up to this many times before waking the main agent with the full chain. `0` disables it (the main agent handles fixes itself). Default 2. The reviewer stays read-only and in its own context; the loop is orchestrated by the extension, not by the reviewer. |
|
|
356
|
+
| `idleTimeoutSec` | Idle timeout in seconds: a sub-agent whose stdout (JSON event stream) goes silent for this long is terminated and retried with the fallback model (if one is available). `0` disables the idle watchdog. Default 90. This only fires when the child produces no output at all — a long but active run is never interrupted. |
|
|
357
|
+
|
|
358
|
+
### Configuration migration
|
|
359
|
+
|
|
360
|
+
The config file migrates itself on load — no manual steps after an upgrade:
|
|
361
|
+
|
|
362
|
+
- **Schema upgrades** — a config written by an older version (missing newer keys or
|
|
363
|
+
holding invalid values) is normalized and saved back with the new fields filled in.
|
|
364
|
+
- **Removed agents** — agents no longer shipped (e.g. the old `plan` agent) are stripped
|
|
365
|
+
from `enabledAgents`, `agentModels`, and `agentThinkingLevels` automatically.
|
|
366
|
+
- **Merged limits** — the pre-0.13 `maxParallelTasks` key is folded into `maxConcurrency`
|
|
367
|
+
(the larger of the two wins) and dropped on the next save.
|
|
368
|
+
- **Removed keys** — `maxSubagentDepth` (0.14) is dropped on load: sub-agent children are
|
|
369
|
+
always leaf processes (the `subagent` tool is excluded from their toolset, with a depth
|
|
370
|
+
marker as defense in depth). To disable delegation entirely, use `"enabledAgents": []`.
|
|
371
|
+
- **New fields** — `idleTimeoutSec` (0.16) is filled in on load with its default (90)
|
|
372
|
+
when missing from an older config.
|
|
373
|
+
|
|
374
|
+
Model selection uses this precedence:
|
|
375
|
+
|
|
376
|
+
```text
|
|
377
|
+
configured agent model → current main-session model → agent frontmatter model
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Unavailable configured models are replaced with a usable current-session model when possible
|
|
381
|
+
and the repaired configuration is saved.
|
|
382
|
+
|
|
383
|
+
At runtime, if an agent's model fails at the provider level before producing any output (bad
|
|
384
|
+
model id, auth, thinking level, quota, ...), the run is retried **once** with the main window's
|
|
385
|
+
current model. This per-run degradation is never persisted — a transient provider hiccup must
|
|
386
|
+
not silently downgrade the configured model — and it does not apply to task-level failures
|
|
387
|
+
(the model worked, the task failed) or aborts. Idle timeouts (the child's stdout goes silent
|
|
388
|
+
for `idleTimeoutSec` seconds) are treated as model-level failures and do trigger the fallback,
|
|
389
|
+
since a stalled SSE stream is usually a provider-side issue. Results carry a `model fell back
|
|
390
|
+
from …` note when it happened.
|
|
391
|
+
|
|
392
|
+
Thinking strength uses this precedence: `agentThinkingLevels` entry → agent frontmatter `thinking` → `thinkingLevel` default.
|
|
393
|
+
|
|
394
|
+
## Agent discovery and overrides
|
|
395
|
+
|
|
396
|
+
- Built-in agents are shipped with the package.
|
|
397
|
+
- User agents live in `~/.pi/agent/agents/`.
|
|
398
|
+
- Project agents live in the nearest `.pi/agents/` directory.
|
|
399
|
+
- For duplicate names, project overrides user and user overrides built-in.
|
|
400
|
+
|
|
401
|
+
Use a matching Markdown filename and `name` field to replace a built-in agent. Keep the task
|
|
402
|
+
brief explicit: include the goal, relevant paths, constraints, and expected handoff.
|
|
403
|
+
|
|
404
|
+
Optional frontmatter fields: `model` (default model reference) and `thinking` (default
|
|
405
|
+
thinking strength). Both are overridden by `agentModels` / `agentThinkingLevels` in
|
|
406
|
+
`pi-subagents.json` when set.
|
|
407
|
+
|
|
408
|
+
## Development
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
npm install
|
|
412
|
+
npm run check
|
|
413
|
+
npm test
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
The package has no runtime dependencies beyond pi peer dependencies.
|
|
417
|
+
|
|
418
|
+
## License
|
|
419
|
+
|
|
420
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Focused sub-agent delegation for pi: explore / worker / reviewer agents in isolated context, with proactive dispatch injection and per-agent model selection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* runaway recursion and keeps child context windows clean.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
16
15
|
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
16
|
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
18
17
|
import { Type } from "typebox";
|
|
@@ -31,7 +30,6 @@ import { buildDelegationDirective } from "./prompt.ts";
|
|
|
31
30
|
import { runSetup } from "./setup.ts";
|
|
32
31
|
import {
|
|
33
32
|
currentSubagentDepth,
|
|
34
|
-
getFinalOutput,
|
|
35
33
|
getResultOutput,
|
|
36
34
|
isFailedResult,
|
|
37
35
|
reviewVerdict,
|
|
@@ -126,7 +124,7 @@ function formatUsage(usage: UsageStats): string {
|
|
|
126
124
|
return parts.join(" ");
|
|
127
125
|
}
|
|
128
126
|
|
|
129
|
-
function formatCompletionBlock(result: SingleResult, maxResultLines: number): string {
|
|
127
|
+
function formatCompletionBlock(result: SingleResult, maxResultLines: number, cwd?: string): string {
|
|
130
128
|
const status = isFailedResult(result) ? "failed" : "completed";
|
|
131
129
|
const usage = formatUsage(result.usage);
|
|
132
130
|
const output = getResultOutput(result);
|
|
@@ -137,7 +135,7 @@ function formatCompletionBlock(result: SingleResult, maxResultLines: number): st
|
|
|
137
135
|
const lines = [`### [${result.agent}] ${status}${usage ? ` (${usage})` : ""}${fallbackNote}`, "", `Task: ${formatTaskSummary(result.task, 80, false)}`, "", text];
|
|
138
136
|
if (truncated) {
|
|
139
137
|
// The full text lives on disk so the main agent can read it on demand.
|
|
140
|
-
lines.push("", `(output truncated to ${maxResultLines} lines; full result: ${writeResultArtifact(output, result.agent)})`);
|
|
138
|
+
lines.push("", `(output truncated to ${maxResultLines} lines; full result: ${writeResultArtifact(output, result.agent, cwd)})`);
|
|
141
139
|
}
|
|
142
140
|
return lines.join("\n");
|
|
143
141
|
}
|
|
@@ -193,6 +191,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
193
191
|
sessionActive = false;
|
|
194
192
|
completionBatcher.dispose();
|
|
195
193
|
backgroundQueue.cancelAll();
|
|
194
|
+
// Clear the monitor so stale runs from this session never leak into the
|
|
195
|
+
// next one (the module-level singleton survives across sessions).
|
|
196
|
+
monitor.clear();
|
|
196
197
|
});
|
|
197
198
|
|
|
198
199
|
pi.registerTool({
|
|
@@ -214,10 +215,11 @@ export default function (pi: ExtensionAPI): void {
|
|
|
214
215
|
"Use subagent with agent 'reviewer' for a fresh read-only review before reporting work done or committing.",
|
|
215
216
|
"subagent launches work in the background and ends the current turn; when a result arrives, the main agent is automatically resumed with it.",
|
|
216
217
|
"Run independent tasks in parallel by passing a tasks array to subagent; let the automatically resumed main agent start dependent work after results arrive.",
|
|
218
|
+
"NEVER sleep, wait, poll, or call other tools alongside subagent — it ends the turn immediately. The main agent is auto-resumed when results arrive; manual waiting only blocks the turn and delays delivery.",
|
|
217
219
|
],
|
|
218
220
|
parameters: SubagentParams,
|
|
219
221
|
|
|
220
|
-
async execute(_toolCallId, params, signal,
|
|
222
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
221
223
|
monitor.beginTurn();
|
|
222
224
|
let config = await loadConfig(configPath);
|
|
223
225
|
// Pick up concurrency changes from /subagents-setup without a restart.
|
|
@@ -251,6 +253,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
251
253
|
monitor.setStatus(runId, status); // stamps endedAt for the elapsed time
|
|
252
254
|
const run = opts?.retain ? monitor.findRun(runId) : monitor.removeRun(runId);
|
|
253
255
|
if (!run) return; // already finished — stay idempotent
|
|
256
|
+
if (opts?.retain) monitor.setRetained(runId, true);
|
|
254
257
|
if (opts?.silent || !sessionActive) return;
|
|
255
258
|
const icon = status === "done" ? "✓" : "✗";
|
|
256
259
|
ctx.ui.notify(`${icon} ${monitor.summarize(run)}`, status === "done" ? "info" : "error");
|
|
@@ -437,7 +440,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
437
440
|
if (!sessionActive) return;
|
|
438
441
|
const items: CompletionMessageItem[] = chain.map((r) => ({
|
|
439
442
|
agent: r.agent,
|
|
440
|
-
block: formatCompletionBlock(r, config.maxResultLines),
|
|
443
|
+
block: formatCompletionBlock(r, config.maxResultLines, ctx.cwd),
|
|
441
444
|
triggerTurn: true,
|
|
442
445
|
}));
|
|
443
446
|
sendCompletionGroup(items);
|
|
@@ -517,7 +520,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
517
520
|
if (!sessionActive) return;
|
|
518
521
|
const completion: CompletionMessageItem = {
|
|
519
522
|
agent: result.agent,
|
|
520
|
-
block: formatCompletionBlock(result, config.maxResultLines),
|
|
523
|
+
block: formatCompletionBlock(result, config.maxResultLines, ctx.cwd),
|
|
521
524
|
triggerTurn: completionTriggersTurn(result, config.notifyOnReviewPass),
|
|
522
525
|
};
|
|
523
526
|
if (failed) {
|
package/src/monitor.ts
CHANGED
|
@@ -42,6 +42,10 @@ export interface RunView {
|
|
|
42
42
|
relationLabel?: string;
|
|
43
43
|
/** Free-form note shown in the widget next to the status label (e.g. "auto-fix chain running"). */
|
|
44
44
|
annotation?: string;
|
|
45
|
+
/** True when a finished run is intentionally kept in the widget (e.g. an
|
|
46
|
+
* auto-fix chain parent whose chain is still running). beginTurn preserves
|
|
47
|
+
* retained runs so they are not swept between turns. */
|
|
48
|
+
retained?: boolean;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/** Optional chain metadata for runs spawned by an auto-fix loop. */
|
|
@@ -298,7 +302,12 @@ export class MonitorStore {
|
|
|
298
302
|
beginTurn(): void {
|
|
299
303
|
// Clear finished runs from a previous turn, but keep any still-active
|
|
300
304
|
// (queued/running) ones so a concurrent sub-agent call is not wiped.
|
|
301
|
-
|
|
305
|
+
// Retained runs (e.g. an auto-fix chain parent whose chain is still
|
|
306
|
+
// running) are also preserved — their status is "done" but they must
|
|
307
|
+
// stay visible until the chain resolves.
|
|
308
|
+
this.runs = this.runs.filter(
|
|
309
|
+
(r) => r.status === "queued" || r.status === "running" || r.retained,
|
|
310
|
+
);
|
|
302
311
|
this.notify();
|
|
303
312
|
}
|
|
304
313
|
|
|
@@ -357,11 +366,27 @@ export class MonitorStore {
|
|
|
357
366
|
this.notify();
|
|
358
367
|
}
|
|
359
368
|
|
|
369
|
+
/** Mark a run as retained (kept in the widget despite being finished). */
|
|
370
|
+
setRetained(id: number, retained: boolean): void {
|
|
371
|
+
const run = this.find(id);
|
|
372
|
+
if (!run) return;
|
|
373
|
+
run.retained = retained;
|
|
374
|
+
this.notify();
|
|
375
|
+
}
|
|
376
|
+
|
|
360
377
|
/** Look up a run by id without removing it. */
|
|
361
378
|
findRun(id: number): RunView | undefined {
|
|
362
379
|
return this.find(id);
|
|
363
380
|
}
|
|
364
381
|
|
|
382
|
+
/** Remove all runs (used on session shutdown so stale state never leaks
|
|
383
|
+
* into the next session). Does not reset the id counter so in-flight
|
|
384
|
+
* finishRun calls from the old session remain safe no-ops. */
|
|
385
|
+
clear(): void {
|
|
386
|
+
this.runs = [];
|
|
387
|
+
this.notify();
|
|
388
|
+
}
|
|
389
|
+
|
|
365
390
|
/** Remove a run (finished runs leave the widget). Returns the removed run. */
|
|
366
391
|
removeRun(id: number): RunView | undefined {
|
|
367
392
|
const index = this.runs.findIndex((r) => r.id === id);
|
package/src/prompt.ts
CHANGED
|
@@ -41,6 +41,10 @@ It immediately ends the current main-agent turn so the user can keep working. Wh
|
|
|
41
41
|
finishes, its result is sent back as a message that automatically resumes the main agent;
|
|
42
42
|
if the main agent is busy, the result waits as a follow-up.
|
|
43
43
|
|
|
44
|
+
NEVER run sleep, wait, or polling commands (e.g. Start-Sleep, sleep, timeout) to wait for
|
|
45
|
+
a sub-agent — the turn already ended and the main agent is auto-resumed when results arrive.
|
|
46
|
+
Manual waiting blocks the turn, delays result delivery, and wastes the user's time.
|
|
47
|
+
|
|
44
48
|
Available agents:
|
|
45
49
|
${catalog}
|
|
46
50
|
|
package/src/spawn.ts
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* written to a temp file and passed via `--append-system-prompt` (which accepts a
|
|
5
5
|
* file path). The task itself is sent through the child's stdin pipe, not another
|
|
6
6
|
* temp file or command-line argument. Child stdout is a JSON-lines event stream;
|
|
7
|
-
* we accumulate assistant messages from `message_end` events
|
|
8
|
-
* output back via onUpdate.
|
|
7
|
+
* we accumulate assistant messages from `message_end` events.
|
|
9
8
|
*
|
|
10
9
|
* Adapted from the official pi example `examples/extensions/subagent`.
|
|
11
10
|
*/
|
|
@@ -16,7 +15,6 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
16
15
|
import { tmpdir } from "node:os";
|
|
17
16
|
import { basename, join } from "node:path";
|
|
18
17
|
import { StringDecoder } from "node:string_decoder";
|
|
19
|
-
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
20
18
|
import type { Message } from "@earendil-works/pi-ai";
|
|
21
19
|
import type { AgentConfig, AgentSource } from "./agents.ts";
|
|
22
20
|
import { DEFAULT_THINKING_LEVEL, type ThinkingLevel } from "./config.ts";
|
|
@@ -28,8 +26,6 @@ import { DEFAULT_THINKING_LEVEL, type ThinkingLevel } from "./config.ts";
|
|
|
28
26
|
/** Default thinking level for sub-agents. pi clamps it to the resolved model's support. */
|
|
29
27
|
export const SUBAGENT_THINKING_LEVEL: ThinkingLevel = DEFAULT_THINKING_LEVEL;
|
|
30
28
|
export const DEPTH_ENV_VAR = "PI_SUBAGENT_DEPTH";
|
|
31
|
-
/** No default deadline: sub-agents may run until completion or explicit cancellation. */
|
|
32
|
-
export const SUBAGENT_TIMEOUT_MS = 0;
|
|
33
29
|
export const SUBAGENT_KILL_GRACE_MS = 5_000;
|
|
34
30
|
/** Default idle watchdog: terminate a child whose stdout goes silent for this
|
|
35
31
|
* many milliseconds. 0 disables it. The actual value comes from config
|
|
@@ -70,8 +66,6 @@ export interface SubagentDetails {
|
|
|
70
66
|
background?: boolean;
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
export type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
|
|
74
|
-
|
|
75
69
|
export type SubagentLiveEvent =
|
|
76
70
|
| { kind: "status"; status: "queued" | "running" | "done" | "failed" }
|
|
77
71
|
| { kind: "usage"; usage: UsageStats; model?: string }
|
|
@@ -134,9 +128,14 @@ export function truncateResultOutput(output: string, maxLines: number): Truncate
|
|
|
134
128
|
return { text: kept.join("\n"), truncated: true };
|
|
135
129
|
}
|
|
136
130
|
|
|
137
|
-
/** Persist the full result where the main agent can read it on demand. Returns the file path.
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
/** Persist the full result where the main agent can read it on demand. Returns the file path.
|
|
132
|
+
* Results are grouped under a per-project subdirectory so concurrent projects don't
|
|
133
|
+
* litter a single flat folder. */
|
|
134
|
+
export function writeResultArtifact(output: string, agentName: string, cwd?: string): string {
|
|
135
|
+
const projectSlug = cwd
|
|
136
|
+
? basename(cwd).replace(/[^\w.-]+/g, "_") || "default"
|
|
137
|
+
: "default";
|
|
138
|
+
const dir = join(tmpdir(), "pi-subagents-results", projectSlug);
|
|
140
139
|
mkdirSync(dir, { recursive: true });
|
|
141
140
|
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
142
141
|
// A random suffix keeps same-millisecond writes from clobbering each other.
|
|
@@ -165,7 +164,6 @@ export function isModelLevelFailure(result: SingleResult): boolean {
|
|
|
165
164
|
if (result.errorMessage?.includes("idle timeout")) return true;
|
|
166
165
|
// The model produced text: the failure belongs to the task, not the model.
|
|
167
166
|
if (getFinalOutput(result.messages)) return false;
|
|
168
|
-
if (result.errorMessage?.includes("timed out")) return false;
|
|
169
167
|
// Require evidence the failure came from the model/provider (an error
|
|
170
168
|
// message or stderr), not from the child process failing to start.
|
|
171
169
|
return result.messages.length > 0 || result.stderr.trim().length > 0;
|
|
@@ -173,31 +171,14 @@ export function isModelLevelFailure(result: SingleResult): boolean {
|
|
|
173
171
|
|
|
174
172
|
export function getResultOutput(result: SingleResult): string {
|
|
175
173
|
if (isFailedResult(result)) {
|
|
176
|
-
|
|
174
|
+
const error = result.errorMessage || result.stderr;
|
|
175
|
+
const partial = getFinalOutput(result.messages);
|
|
176
|
+
if (error && partial) return `${error}\n\n--- Partial output ---\n${partial}`;
|
|
177
|
+
return error || partial || "(no output)";
|
|
177
178
|
}
|
|
178
179
|
return getFinalOutput(result.messages) || "(no output)";
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
export async function mapWithConcurrencyLimit<TIn, TOut>(
|
|
182
|
-
items: TIn[],
|
|
183
|
-
concurrency: number,
|
|
184
|
-
fn: (item: TIn, index: number) => Promise<TOut>,
|
|
185
|
-
): Promise<TOut[]> {
|
|
186
|
-
if (items.length === 0) return [];
|
|
187
|
-
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
188
|
-
const results: TOut[] = new Array(items.length);
|
|
189
|
-
let nextIndex = 0;
|
|
190
|
-
const workers = new Array(limit).fill(null).map(async () => {
|
|
191
|
-
while (true) {
|
|
192
|
-
const current = nextIndex++;
|
|
193
|
-
if (current >= items.length) return;
|
|
194
|
-
results[current] = await fn(items[current], current);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
await Promise.all(workers);
|
|
198
|
-
return results;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
182
|
async function writePromptToTempFile(agentName: string, prompt: string): Promise<{ dir: string; filePath: string }> {
|
|
202
183
|
const dir = await mkdtemp(join(tmpdir(), "pi-subagents-"));
|
|
203
184
|
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
@@ -261,13 +242,10 @@ export interface RunSingleOptions {
|
|
|
261
242
|
cwd?: string;
|
|
262
243
|
/** Thinking level passed to the child pi process. */
|
|
263
244
|
thinkingLevel?: ThinkingLevel;
|
|
264
|
-
/** Optional total timeout; zero (the default) disables it. Intended for tests and controlled callers. */
|
|
265
|
-
timeoutMs?: number;
|
|
266
245
|
/** Idle timeout in ms: terminate the child if its stdout produces no activity
|
|
267
246
|
* for this duration. 0 (the default) disables the idle watchdog. */
|
|
268
247
|
idleTimeoutMs?: number;
|
|
269
248
|
signal?: AbortSignal;
|
|
270
|
-
onUpdate?: OnUpdateCallback;
|
|
271
249
|
onLive?: (e: SubagentLiveEvent) => void;
|
|
272
250
|
makeDetails: (results: SingleResult[]) => SubagentDetails;
|
|
273
251
|
env?: NodeJS.ProcessEnv;
|
|
@@ -281,10 +259,8 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
281
259
|
task,
|
|
282
260
|
cwd,
|
|
283
261
|
thinkingLevel = SUBAGENT_THINKING_LEVEL,
|
|
284
|
-
timeoutMs = SUBAGENT_TIMEOUT_MS,
|
|
285
262
|
idleTimeoutMs = SUBAGENT_DEFAULT_IDLE_TIMEOUT_MS,
|
|
286
263
|
signal,
|
|
287
|
-
onUpdate,
|
|
288
264
|
onLive,
|
|
289
265
|
makeDetails,
|
|
290
266
|
} = options;
|
|
@@ -324,13 +300,6 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
324
300
|
thinking: thinkingLevel,
|
|
325
301
|
};
|
|
326
302
|
|
|
327
|
-
const emitUpdate = (): void => {
|
|
328
|
-
onUpdate?.({
|
|
329
|
-
content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
|
|
330
|
-
details: makeDetails([currentResult]),
|
|
331
|
-
});
|
|
332
|
-
};
|
|
333
|
-
|
|
334
303
|
try {
|
|
335
304
|
if (agent.systemPrompt.trim()) {
|
|
336
305
|
const tmp = await writePromptToTempFile(agent.name, agent.systemPrompt);
|
|
@@ -340,7 +309,6 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
340
309
|
}
|
|
341
310
|
|
|
342
311
|
let wasAborted = false;
|
|
343
|
-
let timedOut = false;
|
|
344
312
|
|
|
345
313
|
// Increment depth so nested sub-agents can be guarded against runaway recursion.
|
|
346
314
|
const childDepth = currentSubagentDepth(options.env) + 1;
|
|
@@ -361,7 +329,6 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
361
329
|
let closed = false;
|
|
362
330
|
let termSent = false;
|
|
363
331
|
let forceKillTimer: ReturnType<typeof setTimeout> | undefined;
|
|
364
|
-
let timeoutTimer: ReturnType<typeof setTimeout> | undefined;
|
|
365
332
|
let abortHandler: (() => void) | undefined;
|
|
366
333
|
let lastActivityAt = Date.now();
|
|
367
334
|
let idleTimer: ReturnType<typeof setInterval> | undefined;
|
|
@@ -370,7 +337,6 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
370
337
|
if (closed) return;
|
|
371
338
|
closed = true;
|
|
372
339
|
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
373
|
-
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
374
340
|
if (idleTimer) clearInterval(idleTimer);
|
|
375
341
|
if (signal && abortHandler) signal.removeEventListener("abort", abortHandler);
|
|
376
342
|
resolve(code ?? 1);
|
|
@@ -461,12 +427,10 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
461
427
|
onLive({ kind: "usage", usage: { ...currentResult.usage }, model: currentResult.model });
|
|
462
428
|
} catch { /* never throw from event handling */ }
|
|
463
429
|
}
|
|
464
|
-
emitUpdate();
|
|
465
430
|
}
|
|
466
431
|
|
|
467
432
|
if (event.type === "tool_result_end" && event.message) {
|
|
468
433
|
currentResult.messages.push(event.message as Message);
|
|
469
|
-
emitUpdate();
|
|
470
434
|
}
|
|
471
435
|
};
|
|
472
436
|
// Send the task through the child stdin pipe instead of the process
|
|
@@ -502,7 +466,6 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
502
466
|
const failed =
|
|
503
467
|
code !== 0 ||
|
|
504
468
|
wasAborted ||
|
|
505
|
-
timedOut ||
|
|
506
469
|
(signal?.aborted ?? false) ||
|
|
507
470
|
currentResult.stopReason === "error" ||
|
|
508
471
|
currentResult.stopReason === "aborted";
|
|
@@ -526,22 +489,12 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
526
489
|
finish(1);
|
|
527
490
|
});
|
|
528
491
|
|
|
529
|
-
if (timeoutMs > 0) {
|
|
530
|
-
timeoutTimer = setTimeout(() => {
|
|
531
|
-
timedOut = true;
|
|
532
|
-
currentResult.stopReason = "error";
|
|
533
|
-
currentResult.errorMessage = `Subagent timed out after ${Math.ceil(timeoutMs / 1000)} seconds.`;
|
|
534
|
-
terminate();
|
|
535
|
-
}, timeoutMs);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
492
|
if (idleTimeoutMs > 0) {
|
|
539
493
|
const checkInterval = Math.min(10_000, Math.floor(idleTimeoutMs / 3));
|
|
540
494
|
idleTimer = setInterval(() => {
|
|
541
495
|
if (closed) return;
|
|
542
496
|
if (Date.now() - lastActivityAt >= idleTimeoutMs) {
|
|
543
497
|
if (idleTimer) clearInterval(idleTimer);
|
|
544
|
-
timedOut = true;
|
|
545
498
|
currentResult.stopReason = "error";
|
|
546
499
|
currentResult.errorMessage = `Subagent idle timeout: no activity for ${Math.ceil(idleTimeoutMs / 1000)} seconds.`;
|
|
547
500
|
terminate();
|
|
@@ -561,12 +514,13 @@ export async function runSingleAgent(options: RunSingleOptions): Promise<SingleR
|
|
|
561
514
|
|
|
562
515
|
currentResult.exitCode = exitCode;
|
|
563
516
|
if (wasAborted) {
|
|
517
|
+
currentResult.stopReason = "aborted";
|
|
518
|
+
currentResult.errorMessage ??= "Subagent was aborted";
|
|
564
519
|
if (onLive) {
|
|
565
520
|
try {
|
|
566
521
|
onLive({ kind: "status", status: "failed" });
|
|
567
522
|
} catch { /* never throw from event handling */ }
|
|
568
523
|
}
|
|
569
|
-
throw new Error("Subagent was aborted");
|
|
570
524
|
}
|
|
571
525
|
return currentResult;
|
|
572
526
|
} finally {
|