@ferris1225/pi-subagents 4.3.8 → 4.3.10
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/CHANGELOG.md +41 -0
- package/README.md +165 -117
- package/index.ts +2 -0
- package/package.json +12 -10
- package/src/configuration/setup.ts +16 -14
- package/src/delegation/agents.ts +2 -1
- package/src/delegation/dispatch.ts +189 -38
- package/src/delegation/phase-scope.ts +178 -0
- package/src/delegation/prompt.ts +46 -36
- package/src/delegation/risk.ts +168 -0
- package/src/execution/rpc-control.ts +2 -29
- package/src/execution/rpc-run.ts +29 -30
- package/src/execution/spawn.ts +21 -20
- package/src/isolation/temp-hygiene.ts +7 -9
- package/src/isolation/worktree.ts +13 -88
- package/src/lifecycle/durable.ts +24 -8
- package/src/lifecycle/runtime.ts +27 -63
- package/src/lifecycle/thread-lifecycle.ts +67 -414
- package/src/lifecycle/thread-restore.ts +16 -35
- package/src/lifecycle/thread-shared.ts +9 -63
- package/src/lifecycle/tools.ts +86 -241
- package/src/presentation/announcements.ts +1 -1
- package/src/presentation/format.ts +6 -16
- package/src/presentation/monitor.ts +0 -91
- package/src/presentation/widget.ts +7 -17
- package/src/execution/session-fork.ts +0 -86
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,47 @@ Published versions of `@ferris1225/pi-subagents`. Unpublished numbers
|
|
|
4
4
|
(`4.2.3`, `4.2.6`, `4.2.9`–`4.2.11`) never shipped on npm; their changes
|
|
5
5
|
landed in the next published release.
|
|
6
6
|
|
|
7
|
+
## 4.3.10
|
|
8
|
+
|
|
9
|
+
- Add read-only `subagent_status`: list current-session runs or inspect an exact id,
|
|
10
|
+
including progress, elapsed time, terminal diagnostics, and retained artifact paths.
|
|
11
|
+
Runtime facts use Pi's existing `details.runs`; children do not need strict JSON reports.
|
|
12
|
+
- Remove `subagent_control` and all steer/park/resume entry points, continuation
|
|
13
|
+
admission, session forking, and resume widget markers. Main handles failed or
|
|
14
|
+
incomplete phases; new deliverables get new briefs. Dispatch, automatic completion
|
|
15
|
+
delivery, destructive stop, RPC cancellation, and manual worktree recovery remain.
|
|
16
|
+
- Keep interrupted worktree edits even when the retained Pi session file is missing.
|
|
17
|
+
Manual recovery no longer depends on model context; managed-path validation and
|
|
18
|
+
index-preserving Git integration stay intact.
|
|
19
|
+
- Record child exits before RPC settlement with their exit code or signal instead
|
|
20
|
+
of returning only partial output. Clear stale provider errors after successful Pi
|
|
21
|
+
retries, expose missing diagnostic evidence explicitly, and keep individual failed
|
|
22
|
+
tool calls separate from a terminal run failure. Preserve safe pre-prompt startup
|
|
23
|
+
retries and keep the last recorded cause when that retry budget is exhausted.
|
|
24
|
+
- Require Pi 0.85.0 and reuse its exported RPC command/response types. Include its
|
|
25
|
+
official server package as a peer and development dependency: the unbundled SDK
|
|
26
|
+
and CLI require it at runtime, not only in tests.
|
|
27
|
+
- Discover actual built-in/custom role definitions in setup, respecting project
|
|
28
|
+
trust. Saving selection removes unavailable names and overrides without retired-role
|
|
29
|
+
aliases or config migration; real disabled and never-configured custom roles remain selectable.
|
|
30
|
+
|
|
31
|
+
## 4.3.9
|
|
32
|
+
|
|
33
|
+
- Add optional bounded stable `phaseId` and exact declarative write `scope` claims to single
|
|
34
|
+
and parallel dispatches. Phase identity is immutable across task rewrites and resume; scope
|
|
35
|
+
is monotonic across retained generations and survives durable v1 restore. Exact task+cwd
|
|
36
|
+
remains the compatibility fallback.
|
|
37
|
+
- Reject deterministic duplicates and declared writer-scope conflicts before parallel batch
|
|
38
|
+
allocation. Fresh single and resumed writers also reject normalized absolute scope overlap
|
|
39
|
+
with active leases, without requiring equal caller cwd. Parallel calls that omit scope remain
|
|
40
|
+
compatible and explicitly report `independence not verified`; declared claims do not prove
|
|
41
|
+
natural-language task independence. Scope is conflict metadata, not permissions or a sandbox.
|
|
42
|
+
- Add the advisory-only `subagent_risk` tool. Without a model call it resolves the repository
|
|
43
|
+
root, reads root-relative tracked and untracked changes from `HEAD`, and applies fixed
|
|
44
|
+
explainable rules for concurrency, trust-boundary, persistence-compatibility, and
|
|
45
|
+
failure-cancellation risk. It propagates cancellation, and suggests but never dispatches or
|
|
46
|
+
requires a Sentinel review.
|
|
47
|
+
|
|
7
48
|
## 4.3.8
|
|
8
49
|
|
|
9
50
|
- Restore `sentinel` as an optional fresh-context reviewer instead of the mandatory
|
package/README.md
CHANGED
|
@@ -12,12 +12,12 @@ once and your main agent delegates on its own.
|
|
|
12
12
|
|
|
13
13
|
## What's new
|
|
14
14
|
|
|
15
|
-
**4.3.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
**4.3.10** — adds read-only `subagent_status` and removes `subagent_control`
|
|
16
|
+
(`steer`, `park`, and `resume`). Runs are one-shot; main handles unfinished work.
|
|
17
|
+
Failure reporting preserves real RPC exit/provider diagnostics and distinguishes
|
|
18
|
+
a failed run from an intentionally failing tool call. Requires Pi **0.85.0** and
|
|
19
|
+
reuses its official RPC types. Setup discovers actual role definitions instead
|
|
20
|
+
of inventing roles from saved names; there are no retired-role aliases or migrations.
|
|
21
21
|
|
|
22
22
|
See [CHANGELOG.md](./CHANGELOG.md).
|
|
23
23
|
|
|
@@ -28,7 +28,7 @@ See [CHANGELOG.md](./CHANGELOG.md).
|
|
|
28
28
|
- [The team](#the-team)
|
|
29
29
|
- [Dispatching work](#dispatching-work)
|
|
30
30
|
- [Parallel edits](#parallel-edits)
|
|
31
|
-
- [
|
|
31
|
+
- [Runs: status and stop](#runs-status-and-stop)
|
|
32
32
|
- [Live status and results](#live-status-and-results)
|
|
33
33
|
- [Models, thinking, and tools](#models-thinking-and-tools)
|
|
34
34
|
- [Configuration](#configuration)
|
|
@@ -50,24 +50,24 @@ back — with you. This extension owns them:
|
|
|
50
50
|
handoff costs. Every brief carries the objective and done condition, exact paths,
|
|
51
51
|
facts already established with citations, boundaries, and the expected output, so a
|
|
52
52
|
child starts from evidence instead of re-deriving it.
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
- A stable `phaseId` owns a logical phase in one resolved working directory even if
|
|
54
|
+
its task wording changes. IDs are 1–80 ASCII letters, numbers, or `._:-`, starting
|
|
55
|
+
with a letter or number, so lease output stays single-line. Exact normalized task+cwd
|
|
56
|
+
remains the backward-compatible fallback for old calls.
|
|
57
|
+
- Runs are one-shot. Use `subagent_status` to inspect them and `subagent_stop` to
|
|
58
|
+
cancel them; main handles unfinished work instead of continuing a failed child.
|
|
58
59
|
- Background completions and stop results arrive at the next parent model boundary;
|
|
59
60
|
`wait: true` returns the same result in-turn instead. A run uses exactly one route.
|
|
60
61
|
- Parallel writers use detached Git worktrees without touching your index.
|
|
61
|
-
Worktree setup obeys the bounded queue; final integration releases its process
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
instead of becoming silent hangs.
|
|
62
|
+
Worktree setup obeys the bounded queue; final integration releases its process slot.
|
|
63
|
+
- Interrupted work retains artifacts for manual recovery after reload or crash.
|
|
64
|
+
Within a run, a configured child-model failure can still hand off to the main model.
|
|
65
|
+
- Failure notifications include available reasons, and status keeps terminal facts
|
|
66
|
+
and retained recovery paths queryable for the current parent session.
|
|
67
67
|
|
|
68
68
|
## Install
|
|
69
69
|
|
|
70
|
-
Requires **pi >= 0.
|
|
70
|
+
Requires **pi >= 0.85.0** and **Node.js >= 22.19.0**.
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
73
|
pi install npm:@ferris1225/pi-subagents
|
|
@@ -117,20 +117,41 @@ the objective and its done condition, exact paths and symbols, facts already
|
|
|
117
117
|
established (with citations), boundaries, and the expected output shape — which is
|
|
118
118
|
what the injected delegation guidance produces when the main agent dispatches for you.
|
|
119
119
|
|
|
120
|
+
Children run the official `pi --mode rpc` server, using Pi's exported command/response
|
|
121
|
+
types and its own session persistence. There is no separate subagent protocol. The
|
|
122
|
+
host transport remains local because Pi 0.85.0's `RpcClient` cannot attach to our
|
|
123
|
+
child process or provide process-tree shutdown, bounded abort coordination, and
|
|
124
|
+
cancellation of child extension dialogs.
|
|
125
|
+
|
|
120
126
|
## Dispatching work
|
|
121
127
|
|
|
122
128
|
```ts
|
|
123
129
|
// One task
|
|
124
130
|
subagent({
|
|
131
|
+
phaseId: "cache-invalidation-fix",
|
|
125
132
|
agent: "artisan",
|
|
126
133
|
task: "Fix the cache invalidation bug in src/cache, add regression tests, run the checks.",
|
|
134
|
+
scope: {
|
|
135
|
+
paths: ["src/cache"],
|
|
136
|
+
symbols: [{ path: "test/cache.test.ts", name: "invalidates stale entries" }],
|
|
137
|
+
},
|
|
127
138
|
});
|
|
128
139
|
|
|
129
140
|
// Parallel only when each scope independently justifies a child
|
|
130
141
|
subagent({
|
|
131
142
|
tasks: [
|
|
132
|
-
{
|
|
133
|
-
|
|
143
|
+
{
|
|
144
|
+
agent: "artisan",
|
|
145
|
+
phaseId: "provider-docs",
|
|
146
|
+
task: "Update provider limits documentation from the established API citations.",
|
|
147
|
+
scope: { paths: ["docs/provider-limits.md"] },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
agent: "artisan",
|
|
151
|
+
phaseId: "config-validation",
|
|
152
|
+
task: "Fix config validation in src/config.ts and its tests.",
|
|
153
|
+
scope: { paths: ["src/config.ts", "test/config.test.ts"] },
|
|
154
|
+
},
|
|
134
155
|
],
|
|
135
156
|
});
|
|
136
157
|
```
|
|
@@ -140,13 +161,14 @@ independent unit in one `tasks` array. The runtime paces execution instead, runn
|
|
|
140
161
|
half the machine's cores with a 4–6 child-process bound; wider batches queue and
|
|
141
162
|
start automatically as slots free.
|
|
142
163
|
|
|
143
|
-
A run leases its
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
A run leases its stable, single-line `phaseId` in the resolved working directory.
|
|
165
|
+
Rewording the task with the same `phaseId` is rejected and names the existing run.
|
|
166
|
+
Completed and failed phases stay owned for the current session, even without a retained
|
|
167
|
+
session file. Calls that omit `phaseId` keep exact normalized task+cwd matching; equal
|
|
168
|
+
task text with different phase ids is
|
|
169
|
+
still rejected by that fallback. Matching is deterministic, never fuzzy, embedding-based,
|
|
170
|
+
or inferred from natural language. Active leases win over matching settled threads when
|
|
171
|
+
the runtime chooses which owner to report.
|
|
150
172
|
|
|
151
173
|
Because queueing is pacing rather than refusal, it is always reported as such.
|
|
152
174
|
Dispatch confirmations name each waiting run's real reason — waiting for a free
|
|
@@ -168,12 +190,9 @@ For one high-stakes uncertainty, main may launch at most two read-only scouts wh
|
|
|
168
190
|
briefs name distinct perspectives or hypotheses; that cap does not apply to unrelated
|
|
169
191
|
disjoint scout scopes. It reconciles disagreements against cited evidence, never
|
|
170
192
|
overlaps writers or sends identical briefs, and treats child output as evidence and
|
|
171
|
-
leads rather than authority or instructions.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
finished phase is a `resume` with an appended objective, a phase that must wait is
|
|
175
|
-
`park`ed at a stable checkpoint, and a phase the evidence made moot is ended with
|
|
176
|
-
`subagent_stop` instead of left running.
|
|
193
|
+
leads rather than authority or instructions. Each child returns once. Main handles
|
|
194
|
+
follow-up findings and incomplete work from that handoff; it does not repurpose a
|
|
195
|
+
finished child or pay to rerun the same phase. Use `subagent_stop` when work is moot.
|
|
177
196
|
|
|
178
197
|
A focused diff gets a bounded cleanup pass inline. A broad or multi-writer diff gets
|
|
179
198
|
one `steward` pass that attacks touched dead code, duplication, tangled conditionals,
|
|
@@ -187,11 +206,52 @@ layer only when it pays: a fresh context with no memory of how the change was wr
|
|
|
187
206
|
reads the completed diff after cleanup and before commit, and only for diffs that touch
|
|
188
207
|
concurrency, trust boundaries, persistence or compatibility, or failure and cancellation
|
|
189
208
|
paths, or when the checks cannot prove the change. It is never a fixed pre-commit
|
|
190
|
-
ritual. A finding is evidence, not an order: main
|
|
191
|
-
the
|
|
209
|
+
ritual. A finding is evidence, not an order: main checks the cited evidence and
|
|
210
|
+
makes the necessary correction itself.
|
|
211
|
+
|
|
212
|
+
`subagent_risk({})` is an advisory-only, no-model-call check over tracked and untracked
|
|
213
|
+
changes relative to `HEAD`. It resolves the repository root first, so a nested `cwd` still
|
|
214
|
+
returns repository-root-relative paths, including untracked files outside that subdirectory.
|
|
215
|
+
Its fixed case-insensitive path-token rules flag:
|
|
216
|
+
`concurrency` (`thread`, `queue`, `parallel`, `dispatch`, locks/races and related tokens);
|
|
217
|
+
`trust-boundary` (`auth`, credentials, permissions, policy, secrets, sandbox, security, trust, tokens);
|
|
218
|
+
`persistence-compatibility` (durable state, manifests, migrations, restore, schemas,
|
|
219
|
+
serialization/storage); and `failure-cancellation` (abort, cancel, errors/failures, recovery,
|
|
220
|
+
retry, stop, timeout). It returns the changed paths, matched categories, and whether those
|
|
221
|
+
rules suggest Sentinel. If Git or `HEAD` is unavailable, it reports advisory unavailable; an
|
|
222
|
+
aborted tool call propagates cancellation instead of converting it to an advisory result. It
|
|
223
|
+
never blocks, starts a child, or automatically dispatches Sentinel.
|
|
224
|
+
|
|
225
|
+
This classifier is intentionally conservative and explainable: it only sees path names, so
|
|
226
|
+
it can produce false positives and miss risky behavior hidden behind neutral names. Main
|
|
227
|
+
still decides whether review pays from the actual diff, test evidence, handoff cost, and the
|
|
228
|
+
complete conversation. The runtime can enforce explicit phase/scope admission, but cannot
|
|
229
|
+
safely force the natural-language judgment of whether work is worth delegating.
|
|
192
230
|
|
|
193
231
|
## Parallel edits
|
|
194
232
|
|
|
233
|
+
`scope` is declarative admission metadata for expected writes, not access control. `paths`
|
|
234
|
+
contains exact file or directory paths; `symbols` contains exact `{ path, name }` claims.
|
|
235
|
+
Paths resolve from each task's caller-facing cwd and use case-insensitive comparison on
|
|
236
|
+
Windows. Wildcard `*` and `?` inputs are rejected; other punctuation is treated literally,
|
|
237
|
+
so paths such as `app/[id]/page.tsx` are valid exact claims. A path claim overlaps the same
|
|
238
|
+
path, an ancestor/descendant path, or a symbol under that path; identical path+symbol
|
|
239
|
+
claims overlap, while two different symbols in the same file may run together.
|
|
240
|
+
|
|
241
|
+
Fresh dispatches check declared writer scope against active, interrupted, or settling
|
|
242
|
+
writer leases before allocating a run. Scope
|
|
243
|
+
comparison uses normalized absolute claims rather than requiring equal caller cwd, so a
|
|
244
|
+
repo-root claim still conflicts with the same path claimed from a nested cwd. Settled
|
|
245
|
+
threads do not block a later phase solely because it edits the same scope.
|
|
246
|
+
|
|
247
|
+
Before allocating any run in a parallel call, the runtime also rejects deterministic phase
|
|
248
|
+
duplicates within the batch or against existing active/retained threads, then compares
|
|
249
|
+
declared writer scopes across the whole batch. A definite conflict rejects the whole batch
|
|
250
|
+
with zero starts. Parallel calls without `scope` remain valid, but their tool result and
|
|
251
|
+
launch receipt say `independence not verified`; that means the contract lacked enough
|
|
252
|
+
metadata, not that overlap was proved safe. Single calls never make a batch-independence
|
|
253
|
+
claim. The existing shared-checkout writer lane remains the final serialization boundary.
|
|
254
|
+
|
|
195
255
|
- Single tasks use your checkout. Every parallel write-capable agent (`artisan`,
|
|
196
256
|
`steward`, and custom writers) defaults to a detached Git worktree, so
|
|
197
257
|
parallel writers run at the same time. Worktree mode needs a committed `HEAD`;
|
|
@@ -224,81 +284,60 @@ Third-party Pi packages execute as trusted code and must be reviewed accordingly
|
|
|
224
284
|
already been applied and only the cleanup failed, the next session start
|
|
225
285
|
removes the retained copy itself and clears the notice.
|
|
226
286
|
|
|
227
|
-
##
|
|
287
|
+
## Runs: status and stop
|
|
228
288
|
|
|
229
|
-
Every dispatch returns a stable `#id
|
|
289
|
+
Every dispatch returns a stable `#id`. Runs are one-shot: there is no
|
|
290
|
+
`subagent_control`, `steer`, `park`, or `resume` interface. Main takes over failed
|
|
291
|
+
or incomplete work using the child's partial edits and artifacts. A different
|
|
292
|
+
deliverable needs a new phase and brief, not a recycled thread.
|
|
230
293
|
|
|
231
|
-
| Tool
|
|
232
|
-
|
|
|
233
|
-
| `
|
|
234
|
-
| `subagent_stop`
|
|
294
|
+
| Tool | What it does |
|
|
295
|
+
| ---- | ------------ |
|
|
296
|
+
| `subagent_status` | Read-only inspection. Omit `id` to list this parent session's runs, or pass an exact numeric `id` to inspect one. |
|
|
297
|
+
| `subagent_stop` | Destructively cancel/retire a run by id/prefix, or all active runs with `all: true`. Delivers partial output and finalizes isolated changes. |
|
|
235
298
|
|
|
236
299
|
```ts
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
300
|
+
subagent_status({});
|
|
301
|
+
subagent_status({ id: 7 });
|
|
302
|
+
subagent_stop({ id: "7" });
|
|
240
303
|
```
|
|
241
304
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
`
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
The wait has no timer chosen by the model: it resolves when its run settles, and
|
|
280
|
-
a parked run returns its resume handle. Control operations are bounded so they do
|
|
281
|
-
not hang on a generation that is still settling.
|
|
282
|
-
|
|
283
|
-
A thread stays durable while its work is unfinished. Parked sessions, worktree
|
|
284
|
-
checkpoints, and result excerpts are recorded under the per-project storage root,
|
|
285
|
-
so reload, restart, or crash produces a resumable checkpoint. An isolated thread
|
|
286
|
-
continues in its original worktree.
|
|
287
|
-
|
|
288
|
-
Restore runs at session start. `subagent_control`, `subagent_stop`, prompt
|
|
289
|
-
injection, and new dispatches wait for it, so a parked id cannot be reported
|
|
290
|
-
missing or reused. If a recorded worktree is gone, the run is surfaced as failed
|
|
291
|
-
and non-resumable while its retained session and recovery record remain available
|
|
292
|
-
for inspection or destructive stop.
|
|
293
|
-
|
|
294
|
-
Persisted sessions and worktrees are resumed or removed only when their canonical paths
|
|
295
|
-
match the current project's managed storage layout and repository. Invalid records are
|
|
296
|
-
dropped without following or deleting their targets. Recovery-owned worktrees and patches
|
|
297
|
-
remain protected from startup sweeps and project-root retention until recovery is announced.
|
|
298
|
-
|
|
299
|
-
Only interrupted work needs a record, so a thread that completes or fails cleanly
|
|
300
|
-
drops its own. That also means a reload keeps interrupted threads resumable, while
|
|
301
|
-
threads that had already finished keep only their delivered result.
|
|
305
|
+
Status reads runtime state without starting, stopping, continuing, or waiting for a
|
|
306
|
+
child to finish. It includes the phase/task summary, activity, elapsed time, model,
|
|
307
|
+
usage, terminal diagnostics, and available result/session/recovery paths. States
|
|
308
|
+
distinguish `queued`, `running`, `interrupting`, `settling` (Git finalization),
|
|
309
|
+
`completed`, `failed`, `stopped`, and `interrupted` (recovered unfinished work).
|
|
310
|
+
Queued runs report their actual wait reason. Settled runs remain queryable in the
|
|
311
|
+
current parent session even after their transient widget rows disappear.
|
|
312
|
+
|
|
313
|
+
The tool returns these facts in Pi's existing structured `details.runs` field.
|
|
314
|
+
An individual run's failure does not make a successful status lookup a tool error;
|
|
315
|
+
an unknown `id` does. This is runtime-authored data, not a requirement for children
|
|
316
|
+
to generate strict JSON. Agent-written reports remain evidence to verify.
|
|
317
|
+
|
|
318
|
+
A background dispatch returns a launch receipt, then its completion arrives at
|
|
319
|
+
the next safe parent boundary—after current tool calls and before the next model
|
|
320
|
+
call. `wait: true` instead holds the dispatch until its new runs settle, which is
|
|
321
|
+
useful for one-shot `pi -p` sessions or an immediate dependency. Each run has one
|
|
322
|
+
delivery route; aborting the waiting parent turn transfers delivery to the
|
|
323
|
+
background path. Use status for on-demand inspection, not a polling or sleep loop.
|
|
324
|
+
|
|
325
|
+
Stop drops messages still queued inside Pi, performs a bounded RPC abort, and
|
|
326
|
+
terminates the child process tree. It retires the session; it never starts another
|
|
327
|
+
attempt. Worktree integration failures keep their recovery artifacts.
|
|
328
|
+
|
|
329
|
+
Interrupted work retains a durable record and any session/worktree artifacts for
|
|
330
|
+
manual recovery after reload or crash. Missing session files no longer discard
|
|
331
|
+
isolated edits. Restore runs at session start; lookup tools, prompt injection, and
|
|
332
|
+
fresh dispatch wait for that pass so an existing id cannot be reported missing or
|
|
333
|
+
reused. Missing recorded worktrees surface as failures without discarding the
|
|
334
|
+
remaining recovery evidence.
|
|
335
|
+
|
|
336
|
+
Canonical managed-path and repository validation remains in place. Invalid records
|
|
337
|
+
are dropped without following or deleting their targets. Recovery-owned worktrees
|
|
338
|
+
and patches stay protected from startup sweeps and project-root retention.
|
|
339
|
+
Completed/failed runs drop their durable thread record; after reload, inspect their
|
|
340
|
+
delivered result instead of expecting them in the current-session status list.
|
|
302
341
|
|
|
303
342
|
## Live status and results
|
|
304
343
|
|
|
@@ -318,14 +357,13 @@ and, dim under the label column, what it is doing right now:
|
|
|
318
357
|
● #15 scout src/models.ts · ↑1.2k ↓8.4k R31.0k W1.1k $0.0900 · openai/gpt-5-mini · think:low · 3m07s
|
|
319
358
|
↳ grep fallback
|
|
320
359
|
○ #23 artisan src/config.ts · repo lane
|
|
321
|
-
○ #24 artisan
|
|
360
|
+
○ #24 artisan tests/config.test.ts · queued · 5m02s
|
|
322
361
|
```
|
|
323
362
|
|
|
324
363
|
Telemetry drops leftmost-first when a row runs out of width (badge, wait state,
|
|
325
364
|
usage, model, thinking) while elapsed survives every width. Queued rows state
|
|
326
365
|
what they actually wait for — `queued` for a free process slot, `repo lane`
|
|
327
|
-
for shared-checkout write serialization, or `starting
|
|
328
|
-
carries a dim `↻` in its agent column with its cumulative time. The widget is
|
|
366
|
+
for shared-checkout write serialization, or `starting`. The widget is
|
|
329
367
|
capped at ten lines: when many runs are live, extra runs collapse into a
|
|
330
368
|
`… +N more` marker so the editor keeps its space.
|
|
331
369
|
|
|
@@ -379,7 +417,7 @@ no `thinking` field in agent Markdown. Precedence: your setup override > the
|
|
|
379
417
|
role default, then the model clamp. There is no separate vision mode — assign
|
|
380
418
|
a multimodal model and name the image paths in the task.
|
|
381
419
|
|
|
382
|
-
Every dispatch,
|
|
420
|
+
Every dispatch, startup retry, and model fallback snapshots the parent's active tools,
|
|
383
421
|
and all `subagent*` tools are removed so children remain leaves. A role without
|
|
384
422
|
an explicit list inherits that snapshot; an explicit list is a strict
|
|
385
423
|
intersection, so active extension tools are available only when named. A declared
|
|
@@ -406,8 +444,12 @@ search snippets, records material dates/versions, and marks uncertainty.
|
|
|
406
444
|
`/subagents-setup` opens the original settings menu: enable or disable roles,
|
|
407
445
|
configure one enabled role's model and thinking level, or walk through a full
|
|
408
446
|
re-setup. `Esc` moves back through the menu stack, and model lists support fuzzy
|
|
409
|
-
search.
|
|
410
|
-
|
|
447
|
+
search. The enable menu discovers built-ins and actual custom role files in the configured
|
|
448
|
+
scope, including roles never configured before. Project files require Pi's project trust.
|
|
449
|
+
Config-only names are not role definitions and never appear in the enable or configure
|
|
450
|
+
picker. Saving an enable selection or full setup discards unavailable role names and their
|
|
451
|
+
model/thinking settings; no retired-name aliases or configuration migration are applied.
|
|
452
|
+
To start over, remove `pi-subagents.json` and run `/subagents-setup` again. Other settings live in
|
|
411
453
|
`~/.pi/agent/pi-subagents.json` (following `PI_CODING_AGENT_DIR`):
|
|
412
454
|
|
|
413
455
|
```json
|
|
@@ -425,7 +467,7 @@ enable menu. Other settings live in
|
|
|
425
467
|
| Field | Meaning |
|
|
426
468
|
| --------------------- | ------- |
|
|
427
469
|
| `enabledAgents` | Agents available for discovery and delegation. `[]` disables all. |
|
|
428
|
-
| `knownAgents` |
|
|
470
|
+
| `knownAgents` | Catalog shown by setup; tracks built-in adoption, but cannot define a custom role without a file. |
|
|
429
471
|
| `agentModels` | Optional model per agent; missing means the current main model. |
|
|
430
472
|
| `agentThinkingLevels` | Optional setup override per agent; missing means the role default. |
|
|
431
473
|
| `maxResultLines` | Lines kept in a completion message before the artifact takes over. Default `40`. |
|
|
@@ -437,9 +479,9 @@ automatically. `enabledAgents` is authoritative after catalog adoption: a newly
|
|
|
437
479
|
shipped built-in is appended once, then `knownAgents` records that it was surfaced
|
|
438
480
|
so a deliberate later disable remains disabled. `sentinel` returns through that
|
|
439
481
|
rule: a config written by 4.3.5–4.3.7, which removed it, enables it once on the next
|
|
440
|
-
load; turn it off in `/subagents-setup` and it stays off.
|
|
441
|
-
|
|
442
|
-
|
|
482
|
+
load; turn it off in `/subagents-setup` and it stays off. Available custom roles remain
|
|
483
|
+
selectable even when disabled. Invalid known fields fall back safely, and unknown fields
|
|
484
|
+
are dropped when canonical config is persisted.
|
|
443
485
|
|
|
444
486
|
At session start, model overrides that pi no longer reports are removed with a
|
|
445
487
|
one-time notice. If pi's own session compaction fails mid-thread, a notice surfaces
|
|
@@ -481,7 +523,7 @@ that removes it, so this directory does not grow without bound:
|
|
|
481
523
|
| `pi-subagents.json` | Your configuration | Never — it is yours |
|
|
482
524
|
| `ferris-pi-subagents/pi-subagents-recovery.json` | Worktree integration and cleanup failures | When the retained patch or worktree it points at is gone |
|
|
483
525
|
| `ferris-pi-subagents/<project>/pi-subagents-threads.json` | One record per interrupted thread | When the thread settles, or after 30 days |
|
|
484
|
-
| `ferris-pi-subagents/<project>/sessions/` |
|
|
526
|
+
| `ferris-pi-subagents/<project>/sessions/` | Child sessions for in-run fallback and manual recovery | When the owning session ends and no recovery record claims them |
|
|
485
527
|
| `ferris-pi-subagents/<project>/worktrees/` | Isolated checkouts for parallel writers | On integration, or when no thread/recovery record claims them |
|
|
486
528
|
| `ferris-pi-subagents/<project>/results/` | Full text of truncated results | After 7 days, or beyond 50 per project |
|
|
487
529
|
| `ferris-pi-subagents/<project>/tmp/` | Child prompt copies and the no-retry policy shim | When its owning process exits |
|
|
@@ -489,7 +531,7 @@ that removes it, so this directory does not grow without bound:
|
|
|
489
531
|
|
|
490
532
|
Cleanup runs at session start and is deliberately conservative. A directory goes
|
|
491
533
|
away only when the process that created it is gone and no valid manifest record still
|
|
492
|
-
claims it, so a live sibling pi instance never loses state and
|
|
534
|
+
claims it, so a live sibling pi instance never loses state and interrupted or recovery-owned
|
|
493
535
|
work outlives its own process by design. Thread and recovery references always beat an
|
|
494
536
|
age rule.
|
|
495
537
|
|
|
@@ -506,6 +548,12 @@ grouped by responsibility under `src/`: configuration, delegation, execution, is
|
|
|
506
548
|
lifecycle, and presentation. Thread restoration, shared lifecycle coordination, RPC control,
|
|
507
549
|
and Git command execution live in focused modules rather than oversized catch-all files.
|
|
508
550
|
|
|
551
|
+
The test runner uses Node 22 or 24; Node 26 removed `--experimental-transform-types`.
|
|
552
|
+
Pi 0.85.0's unbundled SDK and CLI import `@earendil-works/pi-server` without declaring
|
|
553
|
+
it. This project declares the official server package as a peer (and a development
|
|
554
|
+
dependency), so npm can resolve it alongside the SDK in consumer installations.
|
|
555
|
+
It is not bundled into the extension, and no replacement RPC server is introduced.
|
|
556
|
+
|
|
509
557
|
## Changelog
|
|
510
558
|
|
|
511
559
|
See [CHANGELOG.md](./CHANGELOG.md) for published release notes.
|
package/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { getConfigPath, loadConfig } from "./src/configuration/config.ts";
|
|
|
24
24
|
import { runSetup } from "./src/configuration/setup.ts";
|
|
25
25
|
import { discoverAgents } from "./src/delegation/agents.ts";
|
|
26
26
|
import { registerSubagentTool } from "./src/delegation/dispatch.ts";
|
|
27
|
+
import { registerSubagentRiskTool } from "./src/delegation/risk.ts";
|
|
27
28
|
import { buildDelegationDirective } from "./src/delegation/prompt.ts";
|
|
28
29
|
import { currentSubagentDepth } from "./src/execution/spawn.ts";
|
|
29
30
|
import { createRuntime } from "./src/lifecycle/runtime.ts";
|
|
@@ -69,6 +70,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
69
70
|
});
|
|
70
71
|
|
|
71
72
|
registerSubagentTool(pi, runtime);
|
|
73
|
+
registerSubagentRiskTool(pi);
|
|
72
74
|
registerLookupTools(pi, runtime);
|
|
73
75
|
|
|
74
76
|
pi.registerCommand("subagents-setup", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "4.3.
|
|
4
|
-
"description": "A managed sub-agent team for pi: scout, artisan, steward, and sentinel roles,
|
|
3
|
+
"version": "4.3.10",
|
|
4
|
+
"description": "A managed sub-agent team for pi: scout, artisan, steward, and sentinel roles, one-shot runs, read-only status, and Git worktree isolation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -43,17 +43,19 @@
|
|
|
43
43
|
"prepack": "npm run check"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@earendil-works/pi-agent-core": ">=0.
|
|
47
|
-
"@earendil-works/pi-ai": ">=0.
|
|
48
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
49
|
-
"@earendil-works/pi-
|
|
46
|
+
"@earendil-works/pi-agent-core": ">=0.85.0",
|
|
47
|
+
"@earendil-works/pi-ai": ">=0.85.0",
|
|
48
|
+
"@earendil-works/pi-coding-agent": ">=0.85.0",
|
|
49
|
+
"@earendil-works/pi-server": ">=0.85.0",
|
|
50
|
+
"@earendil-works/pi-tui": ">=0.85.0",
|
|
50
51
|
"typebox": "*"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
54
|
-
"@earendil-works/pi-ai": "^0.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
56
|
-
"@earendil-works/pi-
|
|
54
|
+
"@earendil-works/pi-agent-core": "^0.85.0",
|
|
55
|
+
"@earendil-works/pi-ai": "^0.85.0",
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.85.0",
|
|
57
|
+
"@earendil-works/pi-server": "^0.85.0",
|
|
58
|
+
"@earendil-works/pi-tui": "^0.85.0",
|
|
57
59
|
"@types/node": "^22.10.0",
|
|
58
60
|
"typebox": "^1.3.9",
|
|
59
61
|
"typescript": "^5.9.0"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { stat } from "node:fs/promises";
|
|
10
10
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
11
11
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { discoverAgents } from "../delegation/agents.ts";
|
|
12
13
|
import {
|
|
13
14
|
AGENT_PROFILES,
|
|
14
15
|
BUILTIN_AGENT_NAMES,
|
|
@@ -46,16 +47,13 @@ const THINKING_LEVEL_HINTS: Record<ThinkingLevel, string> = {
|
|
|
46
47
|
max: "strongest reasoning",
|
|
47
48
|
};
|
|
48
49
|
|
|
49
|
-
function setupAgentNames(config: SubagentsConfig): string[] {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
...Object.keys(config.agentThinkingLevels),
|
|
57
|
-
]),
|
|
58
|
-
];
|
|
50
|
+
function setupAgentNames(ctx: ExtensionCommandContext, config: SubagentsConfig): string[] {
|
|
51
|
+
const { agents } = discoverAgents(ctx.cwd, {
|
|
52
|
+
scope: config.agentScope,
|
|
53
|
+
projectTrusted: ctx.isProjectTrusted?.() === true,
|
|
54
|
+
});
|
|
55
|
+
const available = new Set(agents.map((agent) => agent.name));
|
|
56
|
+
return [...new Set([...BUILTIN_AGENT_NAMES, ...available])].filter((name) => available.has(name));
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
function agentPickerItems(names: readonly string[]): Array<{ value: string; label: string; description: string }> {
|
|
@@ -87,12 +85,13 @@ async function pickEnabledAgents(
|
|
|
87
85
|
ctx: ExtensionCommandContext,
|
|
88
86
|
config: SubagentsConfig,
|
|
89
87
|
): Promise<string[] | undefined> {
|
|
88
|
+
const names = setupAgentNames(ctx, config);
|
|
90
89
|
return promptSelectMany(
|
|
91
90
|
ctx,
|
|
92
91
|
"Which agents should run?",
|
|
93
92
|
"Each line is a role and its job. Space toggles • Enter confirms • Esc back",
|
|
94
|
-
agentPickerItems(
|
|
95
|
-
config.enabledAgents,
|
|
93
|
+
agentPickerItems(names),
|
|
94
|
+
config.enabledAgents.filter((name) => names.includes(name)),
|
|
96
95
|
);
|
|
97
96
|
}
|
|
98
97
|
|
|
@@ -210,8 +209,10 @@ async function configureOneAgent(
|
|
|
210
209
|
ctx: ExtensionCommandContext,
|
|
211
210
|
config: SubagentsConfig,
|
|
212
211
|
): Promise<ConfiguredAgentChoice | undefined> {
|
|
212
|
+
const available = setupAgentNames(ctx, config);
|
|
213
|
+
const enabled = config.enabledAgents.filter((name) => available.includes(name));
|
|
213
214
|
while (true) {
|
|
214
|
-
const name = await pickAgentToConfigure(ctx,
|
|
215
|
+
const name = await pickAgentToConfigure(ctx, enabled);
|
|
215
216
|
if (name === undefined) return undefined;
|
|
216
217
|
const profile = agentProfile(name);
|
|
217
218
|
if (profile) ctx.ui.notify(`${name}: ${profile.remark}`, "info");
|
|
@@ -289,7 +290,7 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
289
290
|
|
|
290
291
|
const next: SubagentsConfig = {
|
|
291
292
|
enabledAgents: enabled,
|
|
292
|
-
knownAgents: setupAgentNames(base),
|
|
293
|
+
knownAgents: setupAgentNames(ctx, base),
|
|
293
294
|
agentModels,
|
|
294
295
|
agentThinkingLevels: keepAgentEntries(base.agentThinkingLevels, enabled),
|
|
295
296
|
maxResultLines: base.maxResultLines,
|
|
@@ -326,6 +327,7 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
326
327
|
const enabled = await pickEnabledAgents(ctx, config);
|
|
327
328
|
if (enabled === undefined) continue;
|
|
328
329
|
next.enabledAgents = enabled;
|
|
330
|
+
next.knownAgents = setupAgentNames(ctx, config);
|
|
329
331
|
next.agentModels = keepAgentEntries(next.agentModels, enabled);
|
|
330
332
|
next.agentThinkingLevels = keepAgentEntries(next.agentThinkingLevels, enabled);
|
|
331
333
|
} else {
|