@ferris1225/pi-subagents 4.1.23 → 4.2.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 +57 -114
- package/agents/executor.md +53 -0
- package/package.json +1 -1
- package/src/agents.ts +2 -2
- package/src/announcements.ts +0 -26
- package/src/completion.ts +0 -11
- package/src/config.ts +51 -13
- package/src/dispatch.ts +16 -310
- package/src/durable.ts +0 -5
- package/src/format.ts +0 -8
- package/src/monitor.ts +18 -143
- package/src/prompt.ts +7 -30
- package/src/recovery.ts +145 -145
- package/src/runtime.ts +3 -10
- package/src/setup.ts +13 -21
- package/src/spawn.ts +0 -10
- package/src/thread-lifecycle.ts +51 -219
- package/src/widget.ts +63 -274
- package/agents/cleaner.md +0 -50
- package/agents/documenter.md +0 -40
- package/agents/reviewer.md +0 -82
- package/agents/synthesizer.md +0 -39
- package/agents/worker.md +0 -43
- package/src/workflow.ts +0 -215
package/README.md
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
A managed engineering team for [pi](https://github.com/earendil-works/pi):
|
|
10
|
-
|
|
9
|
+
A managed engineering team for [pi](https://github.com/earendil-works/pi): two
|
|
10
|
+
focused sub-agents, durable threads, and Git worktree
|
|
11
11
|
isolation. You install it once and your main agent delegates on its own.
|
|
12
12
|
|
|
13
13
|
## Why
|
|
14
14
|
|
|
15
15
|
Delegation is supposed to remove coordination work. Most sub-agent launchers stop
|
|
16
16
|
at "spawn a child with a prompt" and leave the hard parts — when to delegate, how
|
|
17
|
-
wide to fan out,
|
|
17
|
+
wide to fan out, what happens when a model dies, how results come
|
|
18
18
|
back — with you. This extension owns them:
|
|
19
19
|
|
|
20
20
|
- The main model delegates without being asked, because a delegation directive is
|
|
@@ -22,8 +22,6 @@ back — with you. This extension owns them:
|
|
|
22
22
|
- Dispatching never blocks or ends the main turn, so it can start several runs and
|
|
23
23
|
keep working while they execute.
|
|
24
24
|
- Results deliver themselves. There is no status tool to poll and no lookup step.
|
|
25
|
-
- Successful implementation work goes through an independent reviewer gate, and a
|
|
26
|
-
failing gate fixes itself before it reaches you.
|
|
27
25
|
- Parallel writers get their own Git worktrees, so concurrent edits do not collide
|
|
28
26
|
and your index is never touched.
|
|
29
27
|
- Threads keep their context across resume, stop, reload, and crash; a dead model
|
|
@@ -53,14 +51,12 @@ directly when you want exact control.
|
|
|
53
51
|
|
|
54
52
|
## The team
|
|
55
53
|
|
|
56
|
-
| Agent | Access
|
|
57
|
-
| ------------- |
|
|
58
|
-
| `explorer` | Read-only
|
|
59
|
-
| `
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| `synthesizer` | Read-only | Merging a fan-out's result artifacts or other long sources into one deduplicated, attributed brief. Conflicts and gaps stay explicit, and your main context never re-reads the inputs. |
|
|
63
|
-
| `reviewer` | Read-only (review) / full (fix stage) | Audits, code-health checks, plans, PR and issue validation, and independent gates. A failing managed gate continues into the reviewer's own write-enabled fix stage. |
|
|
54
|
+
| Agent | Access | Best for |
|
|
55
|
+
| ------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `explorer` | Read-only | Broad search, unfamiliar-area mapping, symbol and dependency tracing. Returns a retrieval index — never proof. |
|
|
57
|
+
| `executor` | Full | The default route for any non-trivial, self-contained task: implementation, fixes, refactors, tests, evidence-first cleanup, docs/comment sync, or merging a fan-out's results into one brief — carried through verification and a result-only handoff. |
|
|
58
|
+
|
|
59
|
+
Custom roles join them with a Markdown file (see [Custom agents](#custom-agents)).
|
|
64
60
|
|
|
65
61
|
Every child is an isolated leaf pi process with its own context window and no
|
|
66
62
|
memory of your conversation, so the brief is its only input. A good brief carries
|
|
@@ -70,13 +66,9 @@ injected delegation guidance produces when the main agent dispatches for you.
|
|
|
70
66
|
```text
|
|
71
67
|
You
|
|
72
68
|
└─ pi main agent
|
|
73
|
-
├─ explorer ─── retrieval
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
├─ documenter ─ explicit docs/comments task → deliver │
|
|
77
|
-
├─ synthesizer ─ merges fan-out results into one brief │
|
|
78
|
-
└─ reviewer ─── advisory report (no VERDICT), or managed gate ◀──────┘
|
|
79
|
-
└─ direct REVIEW_FAIL → findings + fix instructions → main agent fixes
|
|
69
|
+
├─ explorer ─── parallel recon, retrieval leads only
|
|
70
|
+
└─ executor ─── one deliverable per child: implement, fix, clean up,
|
|
71
|
+
sync docs, or merge fan-out results → verify → deliver
|
|
80
72
|
```
|
|
81
73
|
|
|
82
74
|
## Dispatching work
|
|
@@ -84,7 +76,7 @@ You
|
|
|
84
76
|
```ts
|
|
85
77
|
// One task
|
|
86
78
|
subagent({
|
|
87
|
-
agent: "
|
|
79
|
+
agent: "executor",
|
|
88
80
|
task: "Fix the cache invalidation bug in src/cache, add regression tests, run the checks.",
|
|
89
81
|
});
|
|
90
82
|
|
|
@@ -92,7 +84,7 @@ subagent({
|
|
|
92
84
|
subagent({
|
|
93
85
|
tasks: [
|
|
94
86
|
{ agent: "explorer", task: "Trace model fallback from dispatch to completion." },
|
|
95
|
-
{ agent: "
|
|
87
|
+
{ agent: "executor", task: "Add edge-case tests for config migration." },
|
|
96
88
|
],
|
|
97
89
|
});
|
|
98
90
|
```
|
|
@@ -106,78 +98,31 @@ as slots free.
|
|
|
106
98
|
Because queueing is pacing rather than refusal, it is always reported as such.
|
|
107
99
|
Dispatch confirmations name each waiting run's real reason — waiting for a free
|
|
108
100
|
process slot, serialized behind the shared-checkout write lane, or already
|
|
109
|
-
starting its child — alongside the slot capacity. A run that
|
|
110
|
-
|
|
111
|
-
work and serialized writers never starve new dispatches.
|
|
101
|
+
starting its child — alongside the slot capacity. A run that waits for the write
|
|
102
|
+
lane releases its slot first, so serialized writers never starve new dispatches.
|
|
112
103
|
|
|
113
104
|
One child owns one coherent deliverable and its files. Dependent work starts only
|
|
114
|
-
after its prerequisite delivers.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
subagent({
|
|
120
|
-
agent: "reviewer",
|
|
121
|
-
task: "Gate the current diff for correctness, regressions, and missing tests.",
|
|
122
|
-
});
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
A gate ends with exactly one verdict line, `VERDICT: REVIEW_PASS` or
|
|
126
|
-
`VERDICT: REVIEW_FAIL`. Every finding carries a concrete fix instruction, and the
|
|
127
|
-
complete finding set must arrive in one pass — findings are never rationed across
|
|
128
|
-
later rounds.
|
|
129
|
-
|
|
130
|
-
Gates are proportional to the change. A small, contained diff gets a fast review
|
|
131
|
-
of its correctness, regressions, and blast radius rather than a whole-surface
|
|
132
|
-
audit, and `review: "none"` on a `worker` or `cleaner` task skips the gate
|
|
133
|
-
outright for mechanical, low-risk edits you verify yourself: typos, comments, doc
|
|
134
|
-
strings, config value tweaks. The default remains one fresh gate whenever behavior
|
|
135
|
-
can change, and a resumed thread keeps the choice its dispatch made.
|
|
136
|
-
|
|
137
|
-
A run that changed nothing is not gated either — there is no diff to review, and
|
|
138
|
-
making zero edits is a valid outcome for a cleaner that found no safe cut. That
|
|
139
|
-
one is decided afterwards rather than at dispatch, and only on proof: an isolated
|
|
140
|
-
worktree starts at its integration base, so an empty diff against that base is
|
|
141
|
-
proof. A shared checkout is shared with you and your editor, so nothing in it can
|
|
142
|
-
be attributed to one run and the gate always runs.
|
|
143
|
-
|
|
144
|
-
A failing **managed** gate — the automatic one after a top-level `worker` or
|
|
145
|
-
`cleaner` — converges inside the workflow. The same retained reviewer session
|
|
146
|
-
gains write access and applies its own fix instructions, then a fresh gate
|
|
147
|
-
verifies those fixes and hunts regressions they introduced. Re-reviews converge on
|
|
148
|
-
the fixes instead of rescanning everything, and the loop is capped at two fix
|
|
149
|
-
rounds, after which the still-failing gate returns to the main agent with every
|
|
150
|
-
finding.
|
|
151
|
-
|
|
152
|
-
A failing gate **you dispatched directly** returns its full findings to the main
|
|
153
|
-
agent, which resolves them itself, inline or through a worker it briefs, without
|
|
154
|
-
waiting for you. Only a genuinely destructive or scope-changing fix is worth
|
|
155
|
-
asking about. It re-verifies once, then reports what remains and moves on: gate
|
|
156
|
-
dispatches never loop.
|
|
157
|
-
|
|
158
|
-
Generic audits and read-only reviews are advisory by default — no verdict, no
|
|
159
|
-
edits. Role authority stays honest in both directions: asking for an audit never
|
|
160
|
-
silently authorizes code changes, and asking for cleanup never rewards
|
|
161
|
-
speculative deletion. A top-level `documenter` is an explicit docs-writing task
|
|
162
|
-
that delivers without another gate.
|
|
105
|
+
after its prerequisite delivers. Verification belongs to whoever did the work:
|
|
106
|
+
every child runs the checks it can and reports exactly which ones ran, and the
|
|
107
|
+
main agent inspects the actual changes before calling anything done.
|
|
163
108
|
|
|
164
109
|
## Parallel edits
|
|
165
110
|
|
|
166
|
-
- Single tasks use your checkout. Every parallel write-capable agent (`
|
|
167
|
-
|
|
111
|
+
- Single tasks use your checkout. Every parallel write-capable agent (`executor`
|
|
112
|
+
and custom writers) defaults to a detached Git worktree, so
|
|
168
113
|
parallel writers run at the same time. Worktree mode needs a committed `HEAD`,
|
|
169
114
|
and read-only agents reject it.
|
|
170
115
|
- A role file can pin its own default with `isolation: worktree` or
|
|
171
116
|
`isolation: shared` in the frontmatter. Precedence is an explicit per-dispatch
|
|
172
117
|
`isolation`, then the role's declaration, then the parallel write default.
|
|
173
|
-
- An isolated
|
|
174
|
-
|
|
175
|
-
|
|
118
|
+
- An isolated run's tracked, deleted, untracked, and binary changes integrate
|
|
119
|
+
back exactly once, after the child settles. Nothing is staged and your index is
|
|
120
|
+
untouched.
|
|
176
121
|
- Integration is a three-way merge, so parallel workers that touched disjoint
|
|
177
122
|
files or regions land cleanly even when earlier patches moved the checkout
|
|
178
123
|
underneath them. A genuine overlap leaves conflict markers in the checkout and
|
|
179
124
|
keeps the worktree and patch for you to resolve.
|
|
180
|
-
- Shared-checkout writers
|
|
125
|
+
- Shared-checkout writers serialize through
|
|
181
126
|
one repository lane, so two of them never race. A run waiting there is reported
|
|
182
127
|
as a lane wait, not as slot queueing, and its process slot is already released.
|
|
183
128
|
- Setup and integration failures keep the useful patch and worktree, and record
|
|
@@ -223,31 +168,23 @@ threads that had already finished keep only their delivered result.
|
|
|
223
168
|
|
|
224
169
|
## Live status and results
|
|
225
170
|
|
|
226
|
-
The TUI widget renders one line per
|
|
171
|
+
The TUI widget renders one line per active run in fixed identity columns —
|
|
227
172
|
status icon, right-aligned `#id`, padded agent name, then the task label — so
|
|
228
173
|
every label starts at the same column, with the live activity dimmed after
|
|
229
|
-
|
|
230
|
-
badge, the token flow in the footer vocabulary (`↑` input, `↓` output,
|
|
231
|
-
`R`/`W` cache read/write), cost, the full `provider/model
|
|
232
|
-
wait state, and an elapsed time that always carries seconds.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
chain) renders as a tree: the parent line carries the workflow-wide token/cost
|
|
236
|
-
totals and total elapsed, and every stage gets its own `├`/`└`-connected row
|
|
237
|
-
with its own model, token flow, and elapsed — settled stages keep the
|
|
238
|
-
telemetry frozen at settlement, the live stage shows its child's model and
|
|
239
|
-
current activity:
|
|
174
|
+
`↳` on its own line and the rest of the telemetry flowing inline after ` · `: the
|
|
175
|
+
worktree badge, the token flow in the footer vocabulary (`↑` input, `↓` output,
|
|
176
|
+
`R`/`W` cache read/write), cost, the full `provider/model` ref, the
|
|
177
|
+
wait state, and an elapsed time that always carries seconds. A live run renders
|
|
178
|
+
two lines: what it is — agent, task, token flow, cost, provider/model, elapsed —
|
|
179
|
+
and, dim under the label column, what it is doing right now:
|
|
240
180
|
|
|
241
181
|
```text
|
|
242
|
-
●
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
● #15 explorer src/models.ts — grep fallback · ↑1.2k ↓8.4k R31.0k W1.1k $0.0900 · openai/gpt-5-mini/low · 3m07s
|
|
249
|
-
○ #23 worker src/config.ts · repo lane
|
|
250
|
-
○ #24 worker ↻ tests/config.test.ts · queued · 5m02s
|
|
182
|
+
● #12 executor src/cache.ts · wt:a91f3c · ↑5.2k ↓41.0k R210.0k W6.1k $1.9400 · 12m06s
|
|
183
|
+
↳ edit src/auth.ts
|
|
184
|
+
● #15 explorer src/models.ts · ↑1.2k ↓8.4k R31.0k W1.1k $0.0900 · openai/gpt-5-mini · 3m07s
|
|
185
|
+
↳ grep fallback
|
|
186
|
+
○ #23 executor src/config.ts · repo lane
|
|
187
|
+
○ #24 executor ↻ tests/config.test.ts · queued · 5m02s
|
|
251
188
|
```
|
|
252
189
|
|
|
253
190
|
Telemetry drops leftmost-first when a row runs out of width (badge, wait
|
|
@@ -255,17 +192,15 @@ state, usage, model) while the elapsed survives every width. Queued rows state
|
|
|
255
192
|
what they actually wait for — `queued` for a free process slot, `repo lane`
|
|
256
193
|
for shared-checkout write serialization, or `starting` — and a resumed thread
|
|
257
194
|
carries a dim `↻` in its agent column with its cumulative time. The widget is
|
|
258
|
-
capped at ten lines: when many runs are live, extra
|
|
259
|
-
`… +N more` marker
|
|
260
|
-
the live stage so the editor keeps its space.
|
|
195
|
+
capped at ten lines: when many runs are live, extra runs collapse into a
|
|
196
|
+
`… +N more` marker so the editor keeps its space.
|
|
261
197
|
|
|
262
198
|
Completions resume the main agent on their own, with a compact block of at most 40
|
|
263
199
|
lines by default; longer output lands unchanged in a Markdown artifact whose path
|
|
264
200
|
comes with the message. Roles write result-only handoffs — outcome, paths,
|
|
265
201
|
verification, unresolved blockers — and the main agent is told to add its
|
|
266
|
-
conclusion rather than restate what you already read. A
|
|
267
|
-
|
|
268
|
-
run adds its failed-tool diagnostics.
|
|
202
|
+
conclusion rather than restate what you already read. A failed run adds its
|
|
203
|
+
failed-tool diagnostics.
|
|
269
204
|
|
|
270
205
|
## Models, thinking, and tools
|
|
271
206
|
|
|
@@ -280,7 +215,7 @@ effective model supports. `/subagents-setup` → _Configure an agent_ also offer
|
|
|
280
215
|
manual strength, listing only the levels that model supports. There is no separate
|
|
281
216
|
vision mode — assign a multimodal model and name the image paths in the task.
|
|
282
217
|
|
|
283
|
-
Every dispatch,
|
|
218
|
+
Every dispatch, resume, retry, and fallback snapshots the parent's
|
|
284
219
|
currently active tools. A role with no explicit list inherits the full set. An
|
|
285
220
|
explicit list keeps its pi built-in boundary and gains active extension tools,
|
|
286
221
|
while its shell slot follows the parent: a role file naming `bash` runs
|
|
@@ -304,10 +239,10 @@ strength per agent. Everything else is config-file only, stored at
|
|
|
304
239
|
|
|
305
240
|
```json
|
|
306
241
|
{
|
|
307
|
-
"enabledAgents": ["explorer", "
|
|
242
|
+
"enabledAgents": ["explorer", "executor"],
|
|
243
|
+
"knownAgents": ["explorer", "executor"],
|
|
308
244
|
"agentModels": { "explorer": "anthropic/claude-haiku-4-5" },
|
|
309
|
-
"agentThinkingLevels": { "
|
|
310
|
-
"notifyOnReviewPass": false,
|
|
245
|
+
"agentThinkingLevels": { "executor": "high" },
|
|
311
246
|
"maxResultLines": 40,
|
|
312
247
|
"agentScope": "user",
|
|
313
248
|
"idleTimeoutSec": 90
|
|
@@ -317,20 +252,28 @@ strength per agent. Everything else is config-file only, stored at
|
|
|
317
252
|
| Field | Meaning |
|
|
318
253
|
| --------------------- | --------------------------------------------------------------------------------- |
|
|
319
254
|
| `enabledAgents` | Agents available for discovery and delegation. `[]` disables all. |
|
|
255
|
+
| `knownAgents` | Built-ins this config has seen; automatic bookkeeping — never edit it. |
|
|
320
256
|
| `agentModels` | Optional `provider/model-id` per agent; missing = current main model. |
|
|
321
257
|
| `agentThinkingLevels` | Optional manual level per agent; missing = Auto. |
|
|
322
|
-
| `notifyOnReviewPass` | Deliver a standalone passing gate without waking the main agent. Default `false`. |
|
|
323
258
|
| `maxResultLines` | Lines kept in a completion message before the artifact takes over. Default `40`. |
|
|
324
259
|
| `agentScope` | Discover `user`, `project`, or `both` agent directories. Default `user`. |
|
|
325
260
|
| `idleTimeoutSec` | Seconds without child RPC output before termination; `0` disables. Default `90`. |
|
|
326
261
|
|
|
327
262
|
The delegation directive is always injected; there is no toggle. Invalid values
|
|
328
263
|
fall back safely, and stale keys — including the former `proactiveInjection`,
|
|
329
|
-
`maxConcurrency`, and `
|
|
264
|
+
`maxConcurrency`, `maxFixRounds`, and `notifyOnReviewPass` knobs — are dropped
|
|
265
|
+
automatically. At session
|
|
330
266
|
start, model overrides pi no longer reports are removed with a one-time notice. If
|
|
331
267
|
pi's own session compaction fails mid-thread, a notice surfaces the error and the
|
|
332
268
|
automatic retry instead of failing quietly.
|
|
333
269
|
|
|
270
|
+
Agents shipped by a newer package version turn themselves on at the next
|
|
271
|
+
session: a built-in the config has never seen is adopted into `enabledAgents`
|
|
272
|
+
and follows explorer's configured model and thinking level — the fast lane
|
|
273
|
+
these light roles need — while an agent you disabled stays disabled
|
|
274
|
+
(`knownAgents` is what tells the two cases apart). Enabling a role in
|
|
275
|
+
`/subagents-setup` adopts the same explorer route.
|
|
276
|
+
|
|
334
277
|
## Custom agents
|
|
335
278
|
|
|
336
279
|
Built-ins ship with the package. Add or replace them with Markdown files:
|
|
@@ -389,7 +332,7 @@ npm test
|
|
|
389
332
|
```
|
|
390
333
|
|
|
391
334
|
There are no bundled runtime dependencies; pi and TypeBox are peers. The source is
|
|
392
|
-
split by responsibility: dispatch
|
|
335
|
+
split by responsibility: dispatch policy, thread lifecycle, RPC
|
|
393
336
|
transport, worktree integration, completion delivery, tools, and TUI status.
|
|
394
337
|
|
|
395
338
|
## License
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: executor
|
|
3
|
+
description: Default route for any delegated, self-contained task — implement, fix, refactor, test, clean up, sync docs, or merge fan-out results — then verify and hand off.
|
|
4
|
+
thinking: high
|
|
5
|
+
# No `tools` field => inherits all tools (full capability).
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are an executor agent with full capabilities in an isolated context window. You own one 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.
|
|
9
|
+
|
|
10
|
+
Repository instructions (AGENTS.md) and any skills available in this session apply to you as to any agent: follow their process for the domains they own (language style, tests, debugging, cleanup discipline, verification). Where a skill covers the same ground as this brief, the skill's discipline wins — except for the release boundary below, which always wins.
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
1. **Context.** Read the brief fully, plus referenced files and images, before acting. If critical context is missing, state what is missing rather than guessing.
|
|
15
|
+
2. **Plan.** Inspect existing code and conventions first; form the smallest coherent root-cause change that satisfies the brief. Prefer the design that deletes complexity over one that rearranges it. No unrelated refactors or standalone docs work unless the brief asks.
|
|
16
|
+
3. **Implement.** Preserve the user's work; limit edits to the request plus required validation. Follow the project's error handling, naming, and style. Synchronize README/docs/comments your change directly affects; never defer that drift.
|
|
17
|
+
4. **Verify.** Run the project's format/build/tests when they exist. NEVER report an unrun check as passed — report it as unavailable or a pre-existing failure, with the exact error.
|
|
18
|
+
|
|
19
|
+
## Cleanup work
|
|
20
|
+
|
|
21
|
+
When the brief authorizes cleanup (dead code, duplication, simplification), a candidate is not a deletion: re-read the load-bearing files and repeat the decisive searches yourself — never inherit proof from another agent's report. Search the whole repository for consumers before removing anything, and keep a candidate when a real consumer exists, dynamic reachability is unresolved, or the cut removes a user capability, public API, persisted format, or compatibility path unless the brief explicitly approves it. Consolidate semantically equivalent duplicates by extracting the smallest stable shared helper and migrating every in-scope caller. Finding no safe cut and making zero edits is valid.
|
|
22
|
+
|
|
23
|
+
## Merging inputs
|
|
24
|
+
|
|
25
|
+
When the brief names several inputs (result artifacts, reports, logs), read every input fully before writing. Deduplicate restatements into one attributed entry, verify disagreements with a short read when a cited file settles them, and report surviving conflicts side by side instead of averaging them away. Stay within the named inputs; report what they cannot answer as a gap.
|
|
26
|
+
|
|
27
|
+
## Boundaries
|
|
28
|
+
|
|
29
|
+
- Never commit, push, publish, tag, release, or bump a package version — the caller owns every release action, even when repository instructions normally automate release after green checks.
|
|
30
|
+
- Children are leaf processes: you cannot dispatch sub-agents.
|
|
31
|
+
- Never change runtime behavior to make documentation true; report the defect instead.
|
|
32
|
+
|
|
33
|
+
## Output format
|
|
34
|
+
|
|
35
|
+
Return only the concrete outcome. Do not repeat the task brief, the plan, the root-cause investigation, or the tool chronology.
|
|
36
|
+
|
|
37
|
+
## Completed
|
|
38
|
+
|
|
39
|
+
What was done, in a few lines.
|
|
40
|
+
|
|
41
|
+
## Files Changed
|
|
42
|
+
|
|
43
|
+
- `path/to/file.ts` — what changed.
|
|
44
|
+
|
|
45
|
+
## Verification
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
## Notes (only when material)
|
|
50
|
+
|
|
51
|
+
Unresolved blockers, rejected requirements, or decisions the caller must know. Omit when nothing actionable.
|
|
52
|
+
|
|
53
|
+
Keep the final response comfortably below the 40-line delivery cap unless the result genuinely requires more.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "A managed sub-agent team for pi: specialized roles, pre-commit documentation sync, retained threads, auto-fix chains, model fallback, and Git worktree isolation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/agents.ts
CHANGED
|
@@ -94,8 +94,8 @@ export function resolveAgentTools(
|
|
|
94
94
|
export function isWriteCapableAgent(
|
|
95
95
|
agent: Pick<AgentConfig, "name" | "tools">,
|
|
96
96
|
): boolean {
|
|
97
|
-
if (agent.name === "explorer"
|
|
98
|
-
if (agent.name === "
|
|
97
|
+
if (agent.name === "explorer") return false;
|
|
98
|
+
if (agent.name === "executor") return true;
|
|
99
99
|
if (!agent.tools) return true;
|
|
100
100
|
return agent.tools.includes("edit") || agent.tools.includes("write");
|
|
101
101
|
}
|
package/src/announcements.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { loadConfig, saveConfig } from "./config.ts";
|
|
6
6
|
import { availableModelsInScope, filterUnavailableModelOverrides } from "./models.ts";
|
|
7
|
-
import { formatToolActivity, monitor } from "./monitor.ts";
|
|
8
7
|
import { announceRecoveryRecords } from "./recovery.ts";
|
|
9
8
|
import type { SubagentRuntime } from "./runtime.ts";
|
|
10
9
|
import { installActiveRunsWidget } from "./widget.ts";
|
|
@@ -36,32 +35,7 @@ async function migrateUnavailableAgentModels(
|
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
/** Track the parent pi session itself as the widget's first row: what the
|
|
40
|
-
* current model is doing while its agent loop runs. Same activity vocabulary
|
|
41
|
-
* as subagent rows (thinking / responding / tool + target), fed by the
|
|
42
|
-
* session's own extension events; the row disappears when the loop settles. */
|
|
43
|
-
function trackMainActivity(pi: ExtensionAPI): void {
|
|
44
|
-
pi.on("agent_start", () => monitor.setMainAgentActive(true));
|
|
45
|
-
pi.on("agent_end", () => monitor.setMainAgentActive(false));
|
|
46
|
-
pi.on("agent_settled", () => monitor.setMainAgentActive(false));
|
|
47
|
-
pi.on("model_select", (event) => monitor.setMainModel(event.model?.id));
|
|
48
|
-
pi.on("thinking_level_select", (event) => monitor.setMainThinking(event.level));
|
|
49
|
-
pi.on("message_update", (event) => {
|
|
50
|
-
if (event.message.role !== "assistant") return;
|
|
51
|
-
const kind = event.assistantMessageEvent.type;
|
|
52
|
-
if (kind === "text_start" || kind === "text_delta") monitor.setMainActivity("responding");
|
|
53
|
-
else if (kind === "thinking_start" || kind === "thinking_delta") monitor.setMainActivity("thinking");
|
|
54
|
-
});
|
|
55
|
-
pi.on("tool_execution_start", (event) =>
|
|
56
|
-
monitor.recordMainToolStart(event.toolName, formatToolActivity(event.toolName, event.args)));
|
|
57
|
-
pi.on("tool_execution_end", (event) => monitor.recordMainToolEnd(event.toolName, event.isError));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
38
|
export function registerAnnouncements(pi: ExtensionAPI, runtime: SubagentRuntime): void {
|
|
61
|
-
// Registered at extension load (not session_start) so a model selection
|
|
62
|
-
// made during restore is already captured when the widget appears.
|
|
63
|
-
trackMainActivity(pi);
|
|
64
|
-
|
|
65
39
|
pi.on("session_start", async (_event, ctx) => {
|
|
66
40
|
if (!existsSync(runtime.configPath)) {
|
|
67
41
|
ctx.ui.notify(
|
package/src/completion.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* failure directly so it is never delayed.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } from "./spawn.ts";
|
|
11
10
|
import { formatUsageCompact, sumUsage, type RunWaitReason } from "./monitor.ts";
|
|
12
11
|
import type { UsageStats } from "./rpc-run.ts";
|
|
13
12
|
|
|
@@ -121,16 +120,6 @@ export function completionGroupTriggersTurn(items: readonly CompletionMessageIte
|
|
|
121
120
|
return items.some((item) => item.triggerTurn);
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
/** Passing reviewer notifications may opt out of waking; every other result wakes. */
|
|
125
|
-
export function completionTriggersTurn(result: SingleResult, notifyOnReviewPass: boolean): boolean {
|
|
126
|
-
if (isFailedResult(result)) return true;
|
|
127
|
-
return !(
|
|
128
|
-
notifyOnReviewPass &&
|
|
129
|
-
result.agent === "reviewer" &&
|
|
130
|
-
reviewVerdict(getResultOutput(result)) === "pass"
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
123
|
/** Minimal shape of an active run, for the "others still running" footer. Kept
|
|
135
124
|
* decoupled from the monitor's RunView so this stays a pure, easily tested
|
|
136
125
|
* formatter; the caller maps its live runs into this shape. */
|
package/src/config.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { dirname, join } from "node:path";
|
|
|
13
13
|
import { getAgentDir, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
|
|
15
15
|
/** Full catalog of agents shipped with the package (selectable in /subagents-setup). */
|
|
16
|
-
export const BUILTIN_AGENT_NAMES = ["explorer", "
|
|
16
|
+
export const BUILTIN_AGENT_NAMES = ["explorer", "executor"] as const;
|
|
17
17
|
|
|
18
18
|
/** Agents enabled out of the box on a fresh install. */
|
|
19
19
|
export const DEFAULT_ENABLED_AGENTS: readonly string[] = [...BUILTIN_AGENT_NAMES];
|
|
@@ -47,15 +47,15 @@ export const IDLE_TIMEOUT_SEC_LIMIT = 600;
|
|
|
47
47
|
export interface SubagentsConfig {
|
|
48
48
|
/** Agent names that are discoverable and injected. Fresh-install default: every built-in agent. */
|
|
49
49
|
enabledAgents: string[];
|
|
50
|
+
/** Built-in names this config has already surfaced. A shipped agent outside
|
|
51
|
+
* this set is new in an upgrade: loadConfig enables it instead of leaving it
|
|
52
|
+
* dark behind a stale allow-list. Bookkeeping only — maintained automatically,
|
|
53
|
+
* and it is what keeps an explicit disable from being undone. */
|
|
54
|
+
knownAgents: string[];
|
|
50
55
|
/** Per-agent model override, keyed by agent name, as "provider/model-id". */
|
|
51
56
|
agentModels: Record<string, string>;
|
|
52
57
|
/** Optional per-agent thinking preference. Runtime clamps it to the effective model's supported levels. */
|
|
53
58
|
agentThinkingLevels: Record<string, ThinkingLevel>;
|
|
54
|
-
/**
|
|
55
|
-
* When a standalone review passes (REVIEW_PASS verdict), deliver it without
|
|
56
|
-
* waking the main agent. Managed workflows always wake once at final delivery.
|
|
57
|
-
*/
|
|
58
|
-
notifyOnReviewPass: boolean;
|
|
59
59
|
/**
|
|
60
60
|
* Max lines of a sub-agent result carried in the completion message. Longer
|
|
61
61
|
* results are truncated; the full text is written to a temp file whose path
|
|
@@ -74,9 +74,9 @@ export interface SubagentsConfig {
|
|
|
74
74
|
|
|
75
75
|
export const DEFAULT_CONFIG: SubagentsConfig = {
|
|
76
76
|
enabledAgents: [...DEFAULT_ENABLED_AGENTS],
|
|
77
|
+
knownAgents: [...BUILTIN_AGENT_NAMES],
|
|
77
78
|
agentModels: {},
|
|
78
79
|
agentThinkingLevels: {},
|
|
79
|
-
notifyOnReviewPass: false,
|
|
80
80
|
maxResultLines: DEFAULT_MAX_RESULT_LINES,
|
|
81
81
|
agentScope: "user",
|
|
82
82
|
idleTimeoutSec: DEFAULT_IDLE_TIMEOUT_SEC,
|
|
@@ -123,6 +123,20 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
123
123
|
config.enabledAgents = [...new Set(names.map((name) => name.trim()))];
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// Known-agent bookkeeping starts empty for a parsed record (not the fresh
|
|
127
|
+
// default) so loadConfig can still tell which shipped agents this config
|
|
128
|
+
// has never seen. Every enabled name was necessarily surfaced.
|
|
129
|
+
config.knownAgents = [];
|
|
130
|
+
if (Array.isArray(raw.knownAgents)) {
|
|
131
|
+
const names = raw.knownAgents.filter(
|
|
132
|
+
(name): name is string => typeof name === "string" && name.trim().length > 0,
|
|
133
|
+
);
|
|
134
|
+
config.knownAgents = [...new Set(names.map((name) => name.trim()))];
|
|
135
|
+
}
|
|
136
|
+
for (const name of config.enabledAgents) {
|
|
137
|
+
if (!config.knownAgents.includes(name)) config.knownAgents.push(name);
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
if (isRecord(raw.agentModels)) {
|
|
127
141
|
for (const [rawKey, value] of Object.entries(raw.agentModels)) {
|
|
128
142
|
const key = rawKey.trim();
|
|
@@ -145,10 +159,6 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
145
159
|
}
|
|
146
160
|
}
|
|
147
161
|
|
|
148
|
-
if (typeof raw.notifyOnReviewPass === "boolean") {
|
|
149
|
-
config.notifyOnReviewPass = raw.notifyOnReviewPass;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
162
|
const maxResultLines = clampCount(raw.maxResultLines, MAX_RESULT_LINES_LIMIT);
|
|
153
163
|
if (maxResultLines !== undefined) config.maxResultLines = maxResultLines;
|
|
154
164
|
|
|
@@ -173,11 +183,39 @@ function defaultConfig(): SubagentsConfig {
|
|
|
173
183
|
};
|
|
174
184
|
}
|
|
175
185
|
|
|
186
|
+
/**
|
|
187
|
+
* A shipped agent the config has never recorded is new in this release; the
|
|
188
|
+
* stale allow-list must not keep it dark. Enable it and adopt explorer's
|
|
189
|
+
* configured model and thinking level, so an upgrade surfaces the new role on
|
|
190
|
+
* the fast light-task lane instead of silently spending the main model.
|
|
191
|
+
*/
|
|
192
|
+
function adoptNewBuiltins(config: SubagentsConfig): SubagentsConfig {
|
|
193
|
+
const known = new Set(config.knownAgents);
|
|
194
|
+
const fresh = BUILTIN_AGENT_NAMES.filter((name) => !known.has(name));
|
|
195
|
+
if (fresh.length === 0) return config;
|
|
196
|
+
const agentModels = { ...config.agentModels };
|
|
197
|
+
const agentThinkingLevels = { ...config.agentThinkingLevels };
|
|
198
|
+
for (const name of fresh) {
|
|
199
|
+
if (!agentModels[name] && config.agentModels.explorer) agentModels[name] = config.agentModels.explorer;
|
|
200
|
+
if (!agentThinkingLevels[name] && config.agentThinkingLevels.explorer) {
|
|
201
|
+
agentThinkingLevels[name] = config.agentThinkingLevels.explorer;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
...config,
|
|
206
|
+
enabledAgents: [...config.enabledAgents, ...fresh],
|
|
207
|
+
knownAgents: [...known, ...fresh],
|
|
208
|
+
agentModels,
|
|
209
|
+
agentThinkingLevels,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
176
213
|
/**
|
|
177
214
|
* Load config. A missing file is a normal state and yields the defaults (not an error).
|
|
178
215
|
* A corrupt file also falls back to defaults rather than throwing, so startup never breaks.
|
|
179
216
|
* A file from an older version (missing newer keys or holding extra keys) is
|
|
180
|
-
* normalized and persisted back, so the on-disk config stays current.
|
|
217
|
+
* normalized and persisted back, so the on-disk config stays current. Built-in
|
|
218
|
+
* agents the file has never seen are adopted: enabled with explorer's route.
|
|
181
219
|
*/
|
|
182
220
|
export async function loadConfig(configPath: string = getConfigPath()): Promise<SubagentsConfig> {
|
|
183
221
|
let text: string;
|
|
@@ -195,7 +233,7 @@ export async function loadConfig(configPath: string = getConfigPath()): Promise<
|
|
|
195
233
|
return defaultConfig();
|
|
196
234
|
}
|
|
197
235
|
|
|
198
|
-
const config = normalizeConfig(parsed);
|
|
236
|
+
const config = adoptNewBuiltins(normalizeConfig(parsed));
|
|
199
237
|
|
|
200
238
|
// Schema upgrade: persist the normalized shape when the file gained fields
|
|
201
239
|
// (new version) or dropped invalid ones.
|