@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 CHANGED
@@ -6,15 +6,15 @@
6
6
  ![platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
7
7
  ![pi](https://img.shields.io/badge/pi-extension-orange)
8
8
 
9
- A managed engineering team for [pi](https://github.com/earendil-works/pi): six
10
- specialized sub-agents, durable threads, automatic review gates, and Git worktree
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, who reviews, what happens when a model dies, how results come
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 | Best for |
57
- | ------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
- | `explorer` | Read-only | Broad search, unfamiliar-area mapping, symbol and dependency tracing. Returns a retrieval index — never proof. |
59
- | `worker` | Full | The default route for any non-trivial, self-contained implementation, fix, refactor, or test task, carried through verification. |
60
- | `cleaner` | Full | Cleanup, removal, simplification, deduplication — requested by you or dispatched proactively when finished work leaves dead code. The brief is its edit authorization and every safe proven cut applies. It cleans the uncommitted diff by default; a brief can scope it to a Git range or a directory instead, and scope bounds its edits without ever narrowing the search that proves a cut safe. |
61
- | `documenter` | Docs/comments | Standalone docs and comment work, including syncing real drift a change left behind. May make zero edits; never changes runtime behavior. |
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 index only (never an automatic gate)
74
- ├─ worker ───── implements ─┬─▶ reviewer PASS deliver
75
- ├─ cleaner ──── cleans up ──┘ └─ FAIL reviewer fixes itself
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 resultsverify 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: "worker",
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: "worker", task: "Add edge-case tests for config migration." },
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 moves into its
110
- managed stages or waits for the write lane releases its slot first, so managed
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
- ## Review gates
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 (`worker`,
167
- `cleaner`, `documenter`, custom writers) defaults to a detached Git worktree, so
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 workflow's reviewer and documenter run inside the same worktree.
174
- Tracked, deleted, untracked, and binary changes integrate back exactly once,
175
- after the workflow settles. Nothing is staged and your index is untouched.
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 — and reviewers snapshotting a diff — serialize through
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 participant in fixed identity columns —
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
- ` ` and the rest of the telemetry flowing inline after ` · `: the worktree
230
- badge, the token flow in the footer vocabulary (`↑` input, `↓` output,
231
- `R`/`W` cache read/write), cost, the full `provider/model/thinking` ref, the
232
- wait state, and an elapsed time that always carries seconds. The first line is
233
- the parent session itselfwhat the current model is doing right now while
234
- its agent loop runs. A managed workflow (the automatic review / fix / re-review
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
- pi subagent Implement the login redirect fix · openai/gpt-5/max · 12m06s
243
- #12 worker src/cache.ts · wt:a91f3c · ↑5.2k ↓41.0k R210.0k W6.1k $1.9400 · 12m06s
244
- implement · ↑1.0k12.0k R40.0k W1.2k $0.5100 · xai/grok-4/xhigh · 2m41s
245
- ! review · ↑0.9k ↓6.0k R38.0k W0.9k $0.3300 · openai/gpt-5 · 1m12s
246
- review fix — edit src/auth.ts · ↑0.2k ↓3.0k R12.0k $0.1200 · openai/gpt-5/medium · 41s
247
- re-review
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.2k8.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 roots collapse into a
259
- `… +N more` marker, and an oversized stage chain keeps a window anchored on
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 successful managed
267
- workflow delivers the writer's handoff plus the integration outcome, and a failed
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, managed stage, resume, retry, and fallback snapshots the parent's
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", "worker", "cleaner", "documenter", "synthesizer", "reviewer"],
242
+ "enabledAgents": ["explorer", "executor"],
243
+ "knownAgents": ["explorer", "executor"],
308
244
  "agentModels": { "explorer": "anthropic/claude-haiku-4-5" },
309
- "agentThinkingLevels": { "reviewer": "high" },
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 `maxFixRounds` knobs — are dropped automatically. At session
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 and workflow policy, thread lifecycle, RPC
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.1.23",
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" || agent.name === "reviewer") return false;
98
- if (agent.name === "worker") return true;
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
  }
@@ -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", "worker", "cleaner", "documenter", "synthesizer", "reviewer"] as const;
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.