pi-legion 2.0.0 → 2.0.2

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 ADDED
@@ -0,0 +1,136 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Published GitHub Release notes remain available at
9
+ https://github.com/zzjcool/pi-legion/releases — this file is the in-repo
10
+ summary so you can see **2.x vs 0.16** without leaving the tree.
11
+
12
+ ## [Unreleased]
13
+
14
+ ## [2.0.2] — 2026-10-16
15
+
16
+ ### Fixed
17
+
18
+ - Child worktree launches are suppressed when the parent checkout is itself a
19
+ linked git worktree (`git-dir` ≠ `git-common-dir`): role defaults and unset
20
+ launch flags collapse to `worktree: false`, matching the centurion-child
21
+ precedent. Explicit `launch: true` / per-child `step` flags still win.
22
+ - `worktreeBootstrap: "npm-install"` no longer fires `npm install` on a stray
23
+ `package.json` without a lockfile (observed: Java/Maven repos carrying
24
+ package.json for docs/tooling); without a lockfile the mode records the
25
+ marker and skips, so non-npm worktrees launch instead of failing after the
26
+ 10-minute bootstrap budget.
27
+
28
+ ### Fixed in 2.0.1 (inherited from prior entries)
29
+
30
+ - Railway / `/legion` header "now" tok/s is 0 when no agent is running; only live nodes count toward now (chart buckets still use the full trailing window) (#136).
31
+ - Narrow/stacked `/legion` header truncates long run + selection badges without overwriting `⚔ /legion`; galaxy legend reserves room for failed/retired glyphs before roles (#125).
32
+ - Galaxy legend `statusBudget` counts grapheme cells (not `String.length`); selection badge min width unified for portrait + landscape (#128).
33
+ - Dispatched children now receive `--provider` pinned from the parent's model
34
+ registry, so a bare model id (`workbuddy/…`) that also exists under a static
35
+ auth-less provider in `models.json` can no longer hijack the child start
36
+ (`No API key found for …`) (#123).
37
+ - Scout-guard now counts first-class `grep` / `find` / `ls` tool calls as reads (#87).
38
+ - Doctor diagnose help no longer repeats itself; storage section shows hygiene sweep state (#86).
39
+ - Bundled roles no longer advertise unloaded `lens_*` tools (#75).
40
+ - Non-interactive `pi --print` waits for async children before cascade retire (#77).
41
+ - Roles load `builtin:codemode`; doctor diagnose shows a dedicated section (#89 / #90 → #93).
42
+ - Doctor role-tool→extension gates are data-driven / extensible (#97 / #99).
43
+ - Launch honors `name=` so `/legion status <name>` matches the railway label (#101 / #104).
44
+ - Galaxy legend shows failed/retired glyphs only when those states are present (#102 / #103).
45
+ - Live child status leads with `alive · quiet Ns`; long quiet emphasizes living over a stale execution snapshot (#81 / #92 / #105).
46
+ - npm `package.json` description/keywords aligned with Legion 2.x RPC (no Herdr panes) (#106 / #111).
47
+
48
+ ### Added
49
+
50
+ - `/legion status` human-readable summary; doctor slash list covers team / agents / on|off / summary (#76).
51
+ - README documents scout-guard settings and `/scout-guard` / `/no-scout-guard` (#74 / #71).
52
+ - Scout-guard mock-llm e2e: notify ×1, cooldown, and dispatch clear (#108 / #113).
53
+
54
+ ### Changed
55
+
56
+ - Esc no longer retires children; only `/legion stop` or session exit retires (esc-no-retire-v3, #134).
57
+ - Status strip shows a persistent `/legion stop` hint while children are live (railway + tree; width may truncate) (#134).
58
+ - Prefer `PI_LEGION_EXTRA_AGENT_DIRS` over legacy `PI_HERDR_SUBAGENTS_*` (alias; new name wins if both set) (#109 / #114).
59
+ - CONTRIBUTING/CI docs aligned with 2.x RPC; verify-12/13 paths point at `test/legacy/docker/` (#95 / #100, #107 / #112).
60
+ - README: remove duplicate Child extensions heading (#94 / #98).
61
+ - `design.md` historical banner; landed F38–F46 marked ✅ / Historical (#80 / #83 → #91).
62
+ - CI PR job runs `test:integration` (#79 / #88).
63
+
64
+ ## [2.0.0] - 2026-10-14
65
+
66
+ First **Pi Legion** line on headless Pi RPC. Relative to the final Herdr-era
67
+ **0.16.x** packages, this is a breaking transport and packaging change.
68
+
69
+ ### Breaking
70
+
71
+ - **RPC-only transport:** children run as headless `pi --mode rpc` processes
72
+ under `LegionSupervisor`. Herdr pane execution is archived under
73
+ `src/backends/legacy/` and is **not** a v2 runtime option.
74
+ - **Kinds:** only `kind: pi` is supported. Migrate non-Pi roles or stay on
75
+ `pi-legion@0.16.x`.
76
+ - **Shared tree ledger:** squad state lives in SQLite WAL `legion.db` (phases,
77
+ mail, resume), not pane-centric session bookkeeping alone.
78
+ - Bundled agent playbooks aligned with local RPC practice (#66).
79
+
80
+ ### Added
81
+
82
+ - Recursive centurion trees, four-phase
83
+ planning / implementing / reviewing / verifying machine, `legion_mail`,
84
+ three-layer `/legion` UI (resident tree, full view, live overlay), railway
85
+ strip, tok/s / galaxy / log panels.
86
+ - Scout-guard: remind the main session after a streak of read-class tools (#64).
87
+ - Storage hygiene sweep on startup: prune idle runs, orphan temp dirs, abandoned
88
+ nodes, event retention, WAL checkpoint (#37 / #38 / #40 → #47).
89
+ - `/legion doctor` (+ diagnose) self-check; auto-doctor snippets on gated spawn
90
+ failures (#52).
91
+ - Child memory gate (`NODE_OPTIONS` V8 flags, pre-spawn gate) and tree-wide
92
+ verify concurrency limit (#50).
93
+ - Children default to `--no-extensions` with explicit/role escape hatches (#43 / #48).
94
+ - User stop controls for async children (`/legion stop`, idle Esc, `x` in view) (#41).
95
+ - Mock LLM server for offline pi / legion smoke (`test/mock-llm`, #54 / #56).
96
+ - GitHub Actions CI (typecheck + unit + integration) and stage-only npm publish
97
+ on tag (#65 / #68).
98
+ - Project workflow docs in CONTRIBUTING (#69).
99
+
100
+ ### Changed
101
+
102
+ - Slash commands consolidated under `/legion` (#57).
103
+ - Status bar shows current-turn elapsed beside lifetime total (#62).
104
+ - tok/s uses output tokens / generationMs (decode throughput) (#61).
105
+
106
+ ### Fixed
107
+
108
+ - Railway: pin live spawn verticals when the strip scrolls left (#67); retire /
109
+ downward spawn connector regressions (#46).
110
+ - Live worker no longer marked retired on a transient Connection blip (#63).
111
+ - Session JSONL streamed + incrementally parsed (no full-file `readFileSync`) (#36 / #45).
112
+ - Per-stream child output byte cap; kill on overflow (#35 / #44).
113
+ - Bun SQLite `.get()` null treated as miss on root insert (#53).
114
+ - Vexillatio sibling naming scoped to the current parent (#42 / #51).
115
+
116
+ ### Security / robustness (design.md F38–F46, already on master)
117
+
118
+ - Model-scope globs use non-RegExp matching (ReDoS).
119
+ - Session create uses `writeFileSync` with `wx` (no fd leak / TOCTOU).
120
+ - Session decode swallows scalar JSON lines as torn.
121
+ - `tasks[]` / blank task validated before launch.
122
+ - Per-agent `timeoutMs`, pending criteria, nested YAML acceptance parsing;
123
+ `UNENFORCED_FIELDS` empty.
124
+
125
+ ## [0.16.x] - Herdr-era (final)
126
+
127
+ Last line that runs children in Herdr panes. Not developed further on this
128
+ branch — pin `pi-legion@0.16.x` if you still need that model. See
129
+ [Herdr transition](README.md#herdr-transition-and-role-migration) in the README.
130
+
131
+ Earlier 0.11–0.15 history (including the `pi-herdr-swarm` /
132
+ `@zzjcool/pi-herdr-subagents` renames) lives in git tags and older Releases.
133
+
134
+ [Unreleased]: https://github.com/zzjcool/pi-legion/compare/v2.0.0...HEAD
135
+ [2.0.0]: https://github.com/zzjcool/pi-legion/releases/tag/v2.0.0
136
+ [0.16.x]: https://github.com/zzjcool/pi-legion/releases
package/CONTRIBUTING.md CHANGED
@@ -23,8 +23,14 @@ directly to `master`. Implementers do not merge their own pull requests.
23
23
  ```bash
24
24
  npm run typecheck
25
25
  npm test
26
+ npm run test:integration # or npm run test:all
26
27
  ```
27
28
 
29
+ The required GitHub Actions check `ci` runs typecheck, unit, integration, and
30
+ mock-llm. Run `npm run test:integration` (or `npm run test:all`) locally
31
+ before you push, especially when the change touches `src/runs/`, `src/legion/`,
32
+ or `src/extension/runtime.ts`.
33
+
28
34
  Add the unit tests the change needs. Prefer **TDD** (see [Checks](#checks)):
29
35
  a failing test in `test/unit` or `test/integration` first, then the production
30
36
  code.
@@ -59,20 +65,36 @@ there is no compile step to ship).
59
65
 
60
66
  ## Checks
61
67
 
68
+ The required GitHub Actions check `ci` (`.github/workflows/ci.yml`, on pull
69
+ requests and pushes to `master`) runs, in order:
70
+
71
+ 1. `npm run typecheck`
72
+ 2. `npm test` (unit)
73
+ 3. `npm run test:integration`
74
+ 4. `npm run test:mock-llm`
75
+
76
+ A failing integration step fails `ci`, so nesting and orchestrator regressions
77
+ are blocked before merge. `test:live` and `test:docker` stay out of that job.
78
+
62
79
  ```bash
63
80
  npm run typecheck
64
81
  npm test # test/unit/*.test.ts
65
- npm run test:integration # fake herdr, no live binary
82
+ npm run test:integration # FakeSupervisor / nested fakes; no live `pi` binary
66
83
  npm run test:all # typecheck + unit + integration
84
+ npm run test:mock-llm # also part of the required ci check
67
85
  ```
68
86
 
69
- `npm run test:live` talks to a real herdr. It skips itself unless `HERDR_ENV=1`
70
- and `herdr` is on `PATH`. Do not fail CI on it.
87
+ `npm run test:live` is a **pi RPC live** smoke (`test/live/rpc-live.test.ts`).
88
+ It skips itself when the `pi` binary is missing from `PATH` (see the probe at
89
+ the top of that file). Do not fail CI on it.
71
90
 
72
91
  Prefer **TDD** for behaviour changes: a failing test in `test/unit` or
73
92
  `test/integration` first, then the production code. Integration tests should
74
- go through `createHerdrClient(createFakeRunner(fake))` so argv construction
75
- and JSON parsing stay covered.
93
+ drive the orchestrator through `FakeSupervisor` (`test/helpers/fake-supervisor.ts`)
94
+ so nesting / launch / collect stay covered without a live `pi`. The herdr-era
95
+ helpers `createHerdrClient` / `createFakeRunner` remain as **deprecated legacy**
96
+ exports (see `src/api.ts` and `test/legacy/`); use them only when touching the
97
+ archived Herdr backend.
76
98
 
77
99
  ## Mock LLM
78
100
 
@@ -91,16 +113,20 @@ dummy key is always the literal `mock`.
91
113
 
92
114
  ## Docker verification
93
115
 
94
- `test/docker/` runs the package inside `pi-herdr-sandbox:latest` with a copy
95
- of the **host** Pi config (`~/.pi/agent/settings.json`, `models.json`,
96
- `auth.json` if present). The copy is disposable (`/tmp/pi-herdr-docker-home`
97
- by default); the container cannot write back to your real `~/.pi`.
116
+ `test/docker/` runs the package inside the RPC / pi-legion sandbox image.
117
+ Default image and staging path names are **historical** (`pi-herdr-sandbox:latest`,
118
+ `/tmp/pi-herdr-docker-home`; override with `PI_DOCKER_IMAGE` / `DOCKER_PI_HOME`).
119
+ Content is the 2.x RPC sandbox, not a live herdr pane host. The scripts copy
120
+ the **host** Pi config (`~/.pi/agent/settings.json`, `models.json`, `auth.json`
121
+ if present) into that disposable home; the container cannot write back to your
122
+ real `~/.pi`.
98
123
 
99
124
  ```bash
100
- # image must already exist
125
+ # image must already exist (historical default name; do not rename a local tag
126
+ # just to match docs — override with PI_DOCKER_IMAGE if yours differs)
101
127
  docker image inspect pi-herdr-sandbox:latest >/dev/null
102
128
 
103
- bash test/docker/verify-1-child-guard.sh # child herdr interceptor
129
+ bash test/docker/verify-1-child-guard.sh # RPC child-guard / ledger block
104
130
  bash test/docker/verify-2-task-appendix.sh # frozen task appendix
105
131
  bash test/docker/verify-3-readonly.sh # read-only bash denylist
106
132
  bash test/docker/verify-4-acceptance.sh # collect → verified
@@ -111,19 +137,26 @@ bash test/docker/verify-8-verify-command.sh # criterion.command + timeout
111
137
  bash test/docker/verify-9-worktree.sh # git worktree cwd
112
138
  bash test/docker/verify-10-fallback.sh # fallbackModels
113
139
  bash test/docker/verify-11-nested-alias.sh # nested + alias + completionGuard
114
- bash test/docker/verify-12-workspace.sh # pi install + child pinned to parent Space
115
- bash test/docker/verify-13-kinds.sh # kind/model matrix; live CodeBuddy when mounted
116
140
  ```
117
141
 
118
- Or all at once:
142
+ Or all at once (RPC mainline **1–11** only):
119
143
 
120
144
  ```bash
121
145
  npm run test:docker
122
146
  ```
123
147
 
124
- `verify-13-kinds.sh` is **not** in `npm run test:docker`: it bind-mounts host
125
- `codebuddy` / `cursor-agent` and (unless `LIVE_KIND_PROBES=0`) calls live
126
- CodeBuddy. The numbered 1–12 scripts stay offline/mock.
148
+ **Legacy Herdr / Space / kind checks** live under `test/legacy/docker/` (need
149
+ pi-legion **0.16.x** Herdr-era setup). They are **not** part of
150
+ `npm run test:docker`:
151
+
152
+ ```bash
153
+ bash test/legacy/docker/verify-12-workspace.sh # Herdr Space pin (legacy)
154
+ bash test/legacy/docker/verify-13-kinds.sh # kind/model matrix; live CodeBuddy when mounted
155
+ ```
156
+
157
+ `verify-13-kinds.sh` also bind-mounts host `codebuddy` / `cursor-agent` and
158
+ (unless `LIVE_KIND_PROBES=0`) calls live CodeBuddy. The numbered 1–11 scripts
159
+ under `test/docker/` stay offline/mock.
127
160
 
128
161
  Override paths with `HOST_PI_AGENT`, `DOCKER_PI_HOME`, `PI_DOCKER_IMAGE`.
129
162
 
@@ -145,7 +178,7 @@ them by hand.
145
178
  | `src/runs/acceptance.ts` | `verification-output` command |
146
179
  | `test/mock-llm/` | Scriptable OpenAI mock for offline pi / legion |
147
180
  | `agents/*.md` | bundled roles |
148
- | `docs/design.md` | measured herdr findings (F1–F46) |
181
+ | `docs/design.md` | design notes / F1–F46 (incl. historical herdr-era measurements; see file banner) |
149
182
  | `README.md` | what is actually wired today |
150
183
 
151
184
  Do not import from `src/` in downstream packages — use
package/README.md CHANGED
@@ -14,6 +14,8 @@ shared SQLite ledger records the whole tree.
14
14
  > `@zzjcool/pi-herdr-subagents` (through 0.14.3; renamed after an npm name
15
15
  > collision), then as `pi-herdr-swarm` (0.15.0), before becoming `pi-legion`
16
16
  > in 0.16.0. **2.0.0: the legion era — recursive centurion tree on pi RPC transport; herdr pane execution archived to legacy**
17
+ >
18
+ > **What changed in 2.x?** See [CHANGELOG.md](CHANGELOG.md) (Keep a Changelog). Published notes also land on [GitHub Releases](https://github.com/zzjcool/pi-legion/releases).
17
19
 
18
20
  ## Contents
19
21
 
@@ -22,10 +24,12 @@ shared SQLite ledger records the whole tree.
22
24
  - [Legion tools and phases](#legion-tools-and-phases)
23
25
  - [Three-layer visualization](#three-layer-visualization)
24
26
  - [Settings and budget gates](#settings-and-budget-gates)
27
+ - [Scout guard](#scout-guard)
25
28
  - [Team 配置](#team-配置)
26
29
  - [Parent/child context conventions](#parentchild-context-conventions)
27
30
  - [Delivery and resume](#delivery-and-resume)
28
31
  - [Herdr transition and role migration](#herdr-transition-and-role-migration)
32
+ - [Changelog](CHANGELOG.md)
29
33
  - [Development](#development)
30
34
 
31
35
  ## Architecture
@@ -87,35 +91,64 @@ subagent({ agent: "scout", task: "Map the export code path and report relevant f
87
91
 
88
92
  No manual child-process launch is needed. Use `/legion` to inspect the tree;
89
93
  see [Three-layer visualization](#three-layer-visualization) for navigation.
94
+ `/legion status` prints that tree in the chat, and `/legion status <name>`
95
+ prints one node — see [Status](#status-legion-status).
90
96
 
91
97
  The `subagent` control actions include event-driven `wait` and graceful `stop`:
92
98
  `subagent({ action: "wait", name: "worker-0" })` returns as soon as the child
93
- settles (or the parent turn is interrupted with Esc). Aborting a parent turn
94
- with Esc gracefully retires its tracked children, including those being waited
95
- on or collected; the cascade is silent. To stop children explicitly, use
99
+ settles (or the parent turn is interrupted with Esc). Esc only ends the parent
100
+ wait — children keep running and settle still notifies; retire them with
101
+ `/legion stop` or by exiting the session. Launch stays async by default. Under
102
+ `pi --print` or JSON mode the process would otherwise exit at the end of the
103
+ parent turn and retire those children before they write an assistant message.
104
+ That shutdown waits for each live async child to settle, bounded by the
105
+ child's turn timeout, and only then retires whatever is still running. A
106
+ child killed by that shutdown is recorded as aborted because the parent
107
+ session shut down, not because the model produced no reply. To include the
108
+ child's result in the same print turn, call `wait` or `collect` before the
109
+ turn ends, or launch with `async: false`. To stop children explicitly, use
96
110
  `subagent({ action: "stop", name: "worker-0" })` or
97
111
  `subagent({ action: "stop", all: true })`; this runs the normal graceful-retire
98
112
  sequence for each selected child.
99
113
 
100
114
  ### Stopping async subagents yourself
101
115
 
102
- Async children keep running after the main turn ends, so Esc-abort no longer
103
- reaches them. Three user controls run the same graceful `stop` (a stopped
104
- parent retires its own subtree on shutdown):
116
+ Esc never retires children (see `docs/contracts/esc-no-retire-v3.md`). Two user
117
+ controls run graceful `stop` (a stopped parent retires its own subtree on
118
+ shutdown); process exit / session shutdown also retires:
105
119
 
106
120
  - **`/legion stop`** stops every live subagent; **`/legion stop <name>`** stops
107
121
  one (`all` is an alias for no name). Names complete from the live children;
108
122
  nested agents complete as `parent/name` and are reported as owned by their
109
123
  parent (stop the parent to retire them).
110
- - **Esc** while the main agent is idle, the editor is empty, no dialog or
111
- overlay is open and subagents are live asks "Stop N running subagents?".
112
- In every other state Esc keeps pi's normal behaviour.
113
124
  - **`x`** on the selected live agent in the `/legion` view or its live
114
125
  overlay asks `y` to confirm in place, then stops it.
115
126
 
116
- While the main agent is idle and subagents are live, the railway header shows
117
- a dim `esc stop all · /legion stop [name]` hint (shortened on narrow terminals,
118
- hidden while the main agent is working).
127
+ While subagents are live, the railway header can show a dim
128
+ `/legion stop [name|all]` hint (shortened on narrow terminals; idle gating /
129
+ tree-strip coverage is a TUI follow-up).
130
+
131
+ ### Status (`/legion status`)
132
+
133
+ The railway and `/legion` view are TUI surfaces. `/legion status` is the chat
134
+ snapshot of the same tree:
135
+
136
+ - `/legion status` prints every node with the same lines as `legion_tree`
137
+ (`id role=… status=… phase=… model=… depth=…`), plus a count of each status.
138
+ Live jobs that are not in the tree yet are listed under that summary.
139
+ - `/legion status <name>` prints one node with the same text as
140
+ `subagent({ action: "status", name })`. Living children lead with
141
+ `alive · quiet Ns` (session JSONL mtime); after ~30s of silence the line
142
+ becomes `alive · quiet Ns (living)` so 「活着但安静」is obvious. Run-store
143
+ execution/acceptance fields are labeled snapshots, and collect-deadline /
144
+ transient-connection reasons are omitted from the snapshot line while the
145
+ child is alive so they do not steal attention from quiet age. A ledger-only
146
+ node still uses the same renderer with the fields the tree has.
147
+ - Quiet age is not a heartbeat: there is no child-side progress ping. Minutes
148
+ of silence can still mean the model is reasoning (especially high-thinking
149
+ turns). Quiet alone does not mean the child is stuck; pair it with `alive`.
150
+ - Both forms work outside the TUI. A short name that matches more than one
151
+ node needs the full node id (`/legion status <node-id>`).
119
152
 
120
153
  ## Legion tools and phases
121
154
 
@@ -208,6 +241,38 @@ subtree, including itself; when launched by a parent at depth `d`, it inherits
208
241
  `C_child = min(C_parent, d + R, 4)` (`R = 1` makes it a leaf), so it can tighten
209
242
  the inherited cap but cannot widen it.
210
243
 
244
+ ### Scout guard
245
+
246
+ `subagents.scoutGuard` watches the main session. After `threshold` consecutive
247
+ read-like tool calls with no `subagent` launch or `vexillatio` call in
248
+ between, it injects one follow-up reminder and then waits out `cooldownMs`.
249
+ It is on by default. Set it in `~/.pi/agent/settings.json` or the project
250
+ `.pi/settings.json`; a project file overrides the same fields from the user
251
+ file.
252
+
253
+ ```json
254
+ {
255
+ "subagents": {
256
+ "scoutGuard": {
257
+ "enabled": true,
258
+ "threshold": 8,
259
+ "cooldownMs": 120000
260
+ }
261
+ }
262
+ }
263
+ ```
264
+
265
+ | Field | Default | Meaning |
266
+ | --- | ---: | --- |
267
+ | `enabled` | `true` | Count and remind. `false` skips the guard until it is turned back on. |
268
+ | `threshold` | `8` | Consecutive read-like calls before one reminder. Positive integer. |
269
+ | `cooldownMs` | `120000` | Quiet period after a reminder, in milliseconds. Integer ≥ 0. |
270
+
271
+ `/scout-guard` turns the guard on for this session and `/no-scout-guard`
272
+ turns it off. Both override `enabled` in memory only; settings files stay
273
+ unchanged. Child sessions do not register these commands. Behavior in full
274
+ is in [docs/design.md §6.4.1](docs/design.md#641-scout-guard-subagentsscoutguard).
275
+
211
276
  ### Team 配置
212
277
 
213
278
  Use `subagents.team` to select the default named team from `subagents.teams`.
@@ -245,10 +310,11 @@ gate, missing lens tools, orphan temps, …), ask Legion rather than guessing
245
310
  same idea as `pi list` / `pi auth check`.
246
311
 
247
312
  - `/legion doctor` — full report in the chat (works outside TUI too).
248
- - `/legion doctor <section>` — one of: `summary`, `extensions`, `models`,
313
+ - `/legion doctor [section]` — one of: `summary`, `extensions`, `models`,
249
314
  `spawn`, `memory`, `tools`, `storage`, `runtime`, `next`.
250
315
  - `subagent({ action: "diagnose" })` — same full report as tool output so the
251
- main agent can call it without waiting for you.
316
+ main agent can call it without waiting for you. The tool has no section
317
+ field; filter with `/legion doctor [section]`.
252
318
 
253
319
  The report is structured for the model: each section ends in **Issues** /
254
320
  **Next steps** with copy-pasteable fixes (env exports, role frontmatter,
@@ -268,11 +334,10 @@ Coverage:
268
334
  | models | Host vs child model lists, which package restores providers, team/roster gaps |
269
335
  | spawn | Sample child argv, recent spawn failures + stderr, nested-spawn flags |
270
336
  | memory | Free RAM, memory gate, child V8 flags, verify concurrency slots in use |
271
- | tools | `/legion` commands, stop / idle-Esc / strip hints, tool names |
272
- | storage | `legion.db` sizes, orphan `rpc-child-*` temps, last hygiene sweep marker |
337
+ | tools | `/legion` slash list (status, team, agents, summary, on/off, scout-guard, doctor, stop), idle-Esc / strip hints, tool names |
338
+ | storage | `legion.db` sizes, orphan `rpc-child-*` temps, effective hygiene limits (default vs env), last sweep marker plus a hint when it is missing or stale |
273
339
  | runtime | Live children, tree statuses, acceptance-ish / active nodes |
274
340
 
275
- ### Child extensions
276
341
  ### Child extensions
277
342
 
278
343
  Children start with `--no-extensions`, so they do not load the host's
@@ -288,6 +353,20 @@ itself (guard and nested spawning) and every path listed in the role's
288
353
  | `PI_LEGION_CHILD_INHERIT_EXTENSIONS=1` | Every child loads all host extensions, as before. |
289
354
  | `PI_LEGION_CHILD_EXTENSIONS=/a.ts,/b.ts` | Extra extension paths every child loads explicitly, e.g. a provider extension the child's model needs. |
290
355
 
356
+ Bundled planner, reviewer, and worker do not list `lens_*` tools. pi-lens starts an LSP stack (~1 GB per child, #43) and is not part of the default `--no-extensions` child. To give a role those tools, add the tool names to its `tools:` list and point `extensions:` / `subagentOnlyExtensions` (or `PI_LEGION_CHILD_EXTENSIONS`) at the pi-lens extension file. `/legion doctor` reports `ROLE_MISSING_LENS` when a role lists the tools without that path.
357
+
358
+ ### Extra agent directories
359
+
360
+ PATH-style directories scanned for agent `*.md` definitions. They load as
361
+ user-level, **below** the real user agents dir in precedence (a same-name file
362
+ in `~/.pi/agent/agents` still wins). Useful for hermetic installs (Nix store,
363
+ container, read-only mount).
364
+
365
+ | Env | Effect |
366
+ | --- | --- |
367
+ | `PI_LEGION_EXTRA_AGENT_DIRS` | Canonical name. Colon-/semicolon-separated absolute dirs (`path.delimiter`). |
368
+ | `PI_HERDR_SUBAGENTS_EXTRA_AGENT_DIRS` | Deprecated compatibility alias (kept for at least one minor version). If **both** are set, `PI_LEGION_EXTRA_AGENT_DIRS` wins and the legacy value is ignored (one stderr warning). |
369
+
291
370
  ### Child memory controls
292
371
 
293
372
  | Env | Default | Effect |
@@ -341,7 +420,8 @@ Version **0.16.x is the final Herdr-era line**. Pi Legion 2.x is RPC-only:
341
420
  child execution uses headless `pi --mode rpc` processes, and the legacy backend
342
421
  is archived under `src/backends/legacy/`, not offered as a v2 runtime option.
343
422
  Pin to `pi-legion` 0.16.x if a workflow must stay on the former Herdr-based
344
- execution model.
423
+ execution model. A short breaking-change summary lives in
424
+ [CHANGELOG.md](CHANGELOG.md#200---2026-10-14).
345
425
 
346
426
  Pi Legion 2.x supports `kind: pi` only. Migrate a custom role that uses another
347
427
  CLI kind by changing its frontmatter, for example:
@@ -385,4 +465,7 @@ Use `writeMockPiHome()` from `test/mock-llm` to generate an isolated
385
465
  See [CONTRIBUTING.md](CONTRIBUTING.md#project-workflow) for the project
386
466
  workflow: issues, pull requests, the merge gate, and npm releases.
387
467
 
468
+ Version history for consumers: [CHANGELOG.md](CHANGELOG.md). Tag-by-tag
469
+ release bodies: [GitHub Releases](https://github.com/zzjcool/pi-legion/releases).
470
+
388
471
  MIT. See [LICENSE](LICENSE).
package/agents/planner.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: planner
3
3
  description: 规划员:把任务拆成可并行/可验收的实施计划,冻结接口,指定文件归属
4
- tools: read, grep, find, ls, bash, codemode, lens_diagnostics, symbol_search, project_report, module_report, read_symbol, read_enclosing, pi_lens_activate_tools
4
+ tools: read, grep, find, ls, bash, codemode
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: reviewer
3
3
  description: 审查员:只读对抗式审查一个指定范围的改动——对照需求查漏、查正确性与测试覆盖,输出带路径与严重级别的发现
4
- tools: read, grep, find, ls, bash, codemode, lens_diagnostics, symbol_search, project_report, module_report, read_symbol, read_enclosing, pi_lens_activate_tools
4
+ tools: read, grep, find, ls, bash, codemode
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
@@ -31,9 +31,8 @@ maxSubagentDepth: 1
31
31
 
32
32
  ## 工具纪律(读代码的正确姿势)
33
33
 
34
- - 你有 pi-lens 工具:看模块结构用 `module_report`,读单个函数/类的完整源码用 `read_symbol`(比 grep 片段读快且省 token),找符号位置用 `symbol_search`,改完的类型/错误检查用 `lens_diagnostics`。
35
34
  - `grep`/`find` 工具底层就是 ripgrep/fd,优先于 bash 管道。只在需要管道(如 `git show X | grep`)时才用 bash。
36
- - **禁止对同一文件碎片化 grep 超过 3 次**——超过说明该用 `module_report`/`read_symbol` 整体看了。
35
+ - **禁止对同一文件碎片化 grep 超过 3 次**——超过就用 `read` 把相关段落整段读完。
37
36
  - 多个独立的读操作(批量读文件、批量检查)用 `codemode` 的 `Promise.allSettled` 并行发,不要串行。
38
37
 
39
38
  ## 先确定范围
package/agents/worker.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: worker
3
3
  description: 实现者:按冻结计划写代码、跑测试,完成后自报 verdict
4
- tools: read, edit, write, grep, find, ls, bash, subagent, codemode, lens_diagnostics, symbol_search, project_report, module_report, read_symbol, read_enclosing, pi_lens_activate_tools
4
+ tools: read, edit, write, grep, find, ls, bash, subagent, codemode
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
@@ -27,9 +27,8 @@ maxSubagentDepth: 3
27
27
 
28
28
  ## 工具纪律(读代码的正确姿势)
29
29
 
30
- - 你有 pi-lens 工具:看模块结构用 `module_report`,读单个函数/类的完整源码用 `read_symbol`(比 grep 片段读快且省 token),找符号位置用 `symbol_search`,改完后对涉及文件跑 `lens_diagnostics` 查类型/错误。
31
30
  - `grep`/`find` 工具底层就是 ripgrep/fd,优先于 bash 管道。只在需要管道(如 `git diff | grep`)时才用 bash。
32
- - **禁止对同一文件碎片化 grep 超过 3 次**——超过说明该用 `module_report`/`read_symbol` 整体看了。
31
+ - **禁止对同一文件碎片化 grep 超过 3 次**——超过就用 `read` 把相关段落整段读完。
33
32
  - 多个独立的读操作(批量读文件、批量检查)用 `codemode` 的 `Promise.allSettled` 并行发,不要串行。
34
33
 
35
34
  ## 职责边界
@@ -2,6 +2,8 @@
2
2
 
3
3
  > **v2 修订(2026-10-13)**:ESC / 父 turn abort 的语义已改为级联优雅关闭由该 turn 拥有的 children,推翻 v1 下文的「ESC ≠ 杀 children」决定。下文保留 v1 冻结原文,仅供历史考古;当前语义以 `docs/contracts/esc-cascade-v2.md` 为准。
4
4
 
5
+ > **v3 说明(2026-10-15)**:v3(`esc-no-retire-v3.md`)再次回到 v1 的「ESC ≠ 杀 children」原则;与 v1 不同的是保留 session_shutdown drain/归因与显式 `/legion stop`。v2 级联语义已推翻。
6
+
5
7
  ## 目标
6
8
 
7
9
  1. 主 pi 按 ESC(或 RPC abort)打断当前 turn 时,正在执行的 `subagent` 工具调用必须尽快感知并优雅收场,不能钉住事件循环。
@@ -1,5 +1,8 @@
1
1
  # ESC 级联关闭契约(v2,2026-10-13 冻结)
2
2
 
3
+ > **v3 推翻(2026-10-15)**:ESC / 父 turn abort **不再**级联 retire children。当前语义以 `docs/contracts/esc-no-retire-v3.md` 为准。下文保留 v2 冻结原文,仅供历史考古。
4
+
5
+
3
6
  ## 背景
4
7
 
5
8
  v1(esc-abort-v1.md,已随 e66c85f 合入)冻结了「ESC ≠ 杀 children」语义。用户裁决推翻:turn 是 children 的 owner,turn 被 abort 时 children 应级联关闭,否则是资源泄漏。
@@ -57,7 +60,8 @@ vexillatio 工具 execute 也要把 signal 传进来(找到它的 registerTool
57
60
  ## 语义边界(不变)
58
61
 
59
62
  - **stop action 不变**:显式停止仍走全链。
60
- - **主 pi 正常退出(quit)不变**:session_shutdown 已级联。
63
+ - **主 pi 正常退出(quit)不变**:交互 TUI 的 session_shutdown 仍立即级联 retire。
64
+ - **非交互 print/JSON(#72)**:`ctx.mode` 为 `print` 或 `json` 时,session_shutdown 先等仍活着的 async 子 agent 结算(上限为各子的 turn timeout),再 retire 尚未结束的。避免 `--print` 在首包前把刚 spawn 的子进程记成 `no assistant message for the last prompt`。父会话关机导致的无 assistant abort 原因改为 parent session shutdown,与模型自己没写出 assistant 区分。
61
65
  - **SIGKILL/崩溃不覆盖**:孤儿扫描 + resume 语义不变(这是既定接受的风险)。
62
66
  - **children 的 children**:retire 链本身递归(孙进程 sweep 在 rpc-supervisor 的 retireChild 里),无需额外处理。
63
67
  - **blocked children**:一并 retire(stop 语义一致)。
@@ -0,0 +1,57 @@
1
+ # ESC 不再 retire children(v3,2026-10-15 冻结)
2
+
3
+ ## 背景
4
+
5
+ v2(`esc-cascade-v2.md`)把「父 turn abort → 级联 retire children」冻结为产品语义。2026-10-15 实测事故:主会话异步派了多个 child 后用 `wait` 阻塞,用户按 Esc 打断父 turn,级联 retire 把整棵仍在生长的 legion 树抹掉(含尚未派生子 agent 的 centurion)。Esc 是高频误触键,后果不可恢复。
6
+
7
+ 用户裁决推翻 v2:**Esc 只打断父 turn 的等待,不杀 children;停止 subagent 只有两条路——显式 `/legion stop`,或主进程退出。**
8
+
9
+ ## 冻结决定
10
+
11
+ ### A. Esc ≠ retire
12
+
13
+ 1. **Idle Esc stop 删除**:不再有 `createIdleEscStop` / `onTerminalInput` 接线。裸 Esc 在 idle 时也不再 stop all。
14
+ 2. **父 turn abort 不再级联**:工具 execute 各 `signal?.aborted` 分支只返回 interrupted 文案,**不**调用 `cascadeRetireAll`。`cascadeRetireAll` / `cascadePromise` / `agent_settled{aborted}` 级联钩子全部删除。
15
+ 3. **`abandonLaunchedChild`**:spawn 后 abort 时 **不 retire**;`followLaunched(..., { watch: true })`,child 继续跑,settle 仍通知父会话。
16
+ 4. **`runtime.waitOne` WAIT_INTERRUPTED**:**必须 re-arm watch**(`consumedByTool = false` + `watch`),否则 child 完成后无人收割、无完成通知。
17
+
18
+ ### B. 中断文案
19
+
20
+ 统一为:
21
+
22
+ ```text
23
+ interrupted (parent turn ended; children still running)
24
+ ```
25
+
26
+ 不再使用 `interrupted (parent turn aborted)`(易误导为已 abort children)。
27
+
28
+ ### C. 保留的 retire 路径(不变)
29
+
30
+ - 显式 `stop` action / `/legion stop` / `/legion` 视图 `x`
31
+ - `session_shutdown` → `shutdownRetireAll`(交互立刻 retire;`--print` / JSON 先 `drainBeforeShutdown` 再 retire)
32
+ - SIGINT / 进程退出 = 主进程退出 = retire
33
+
34
+ ### D. TUI 提示
35
+
36
+ 有 live child 时,aboveEditor 状态栏常驻 `/legion stop [name|all]`(短版 `/legion stop`):railway header 在宽度挤压下可截断摘要以保短 hint;`PI_LEGION_STRIP=tree` 在 header 下另起一行短提示。无 idle 门控。
37
+
38
+ ## 语义边界
39
+
40
+ | 动作 | children |
41
+ |---|---|
42
+ | Esc 打断 wait / 父 turn | 继续跑;re-arm watch;settle 通知 |
43
+ | `/legion stop` / `stop` action | graceful retire |
44
+ | 主进程退出 / session_shutdown | drain(print/json)+ retire |
45
+ | SIGINT | 进程退出 → retire |
46
+
47
+ ## 与 v1 / v2 的关系
48
+
49
+ - **v1**(`esc-abort-v1.md`)主张「ESC ≠ 杀 children」——v3 回到这一原则,但保留 v1 没有的 drain / shutdown 归因与显式 stop。
50
+ - **v2**(`esc-cascade-v2.md`)级联 retire —— **v3 推翻**;原文保留供考古。
51
+
52
+ ## 验收锚点
53
+
54
+ - 无 `cascadeRetireAll` / idle Esc 接线
55
+ - wait abort → children still alive + watching;完成通知仍达
56
+ - launch abort mid-spawn → child watched, not retired
57
+ - print-shutdown / session_shutdown / stop 回归绿