pi-legion 2.0.4 → 3.0.1

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +96 -1
  2. package/CONTRIBUTING.md +27 -24
  3. package/README.md +103 -19
  4. package/docs/contracts/osc-busy-plugin.md +72 -0
  5. package/docs/contracts/spawn-default-inprocess-v3.md +322 -0
  6. package/docs/contracts/wait-not-stop.md +30 -0
  7. package/docs/design.md +2 -2
  8. package/docs/headless-child-contract.md +83 -0
  9. package/docs/inprocess-supervisor.md +140 -0
  10. package/docs/qa-hunting-round.md +150 -0
  11. package/docs/subagents-teams.md +55 -18
  12. package/docs/tui-qa-playbook.md +284 -0
  13. package/index.ts +315 -49
  14. package/package.json +24 -13
  15. package/skills/pi-legion/SKILL.md +5 -0
  16. package/src/agents/agents.ts +34 -6
  17. package/src/agents/context.ts +83 -1
  18. package/src/agents/overrides.ts +1 -0
  19. package/src/agents/settings.ts +122 -1
  20. package/src/api.ts +23 -0
  21. package/src/extension/child-guard.ts +42 -3
  22. package/src/extension/legion-commands.ts +9 -2
  23. package/src/extension/legion-doctor.ts +54 -0
  24. package/src/extension/legion-stop.ts +6 -2
  25. package/src/extension/playbook.ts +3 -3
  26. package/src/extension/runtime.ts +57 -8
  27. package/src/extension/scout-guard.ts +39 -1
  28. package/src/extension/session-errors.ts +191 -0
  29. package/src/runs/args.ts +8 -0
  30. package/src/runs/orchestrator.ts +125 -16
  31. package/src/shared/output-cap.ts +2 -2
  32. package/src/shared/session.ts +2 -1
  33. package/src/shared/types.ts +74 -0
  34. package/src/supervisor/child-context.ts +90 -0
  35. package/src/supervisor/child-memory.ts +27 -3
  36. package/src/supervisor/inprocess-depth.ts +71 -0
  37. package/src/supervisor/inprocess-supervisor.ts +1011 -0
  38. package/src/supervisor/routing-supervisor.ts +179 -0
  39. package/src/supervisor/rpc-supervisor.ts +14 -0
  40. package/src/supervisor/spawn-mode.ts +35 -0
  41. package/src/supervisor/types.ts +13 -0
  42. package/src/supervisor/ui-proxy.ts +78 -0
  43. package/src/tui/live-overlay.ts +147 -55
  44. package/src/tui/status.ts +71 -35
  45. package/src/tui/viz/model.ts +6 -0
  46. package/src/tui/viz/panels.ts +8 -9
  47. package/src/tui/viz/railway.ts +154 -21
  48. package/src/tui/viz/rate.ts +15 -14
package/CHANGELOG.md CHANGED
@@ -11,6 +11,100 @@ summary so you can see **2.x vs 0.16** without leaving the tree.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [3.0.1] — 2026-10-18
15
+
16
+ ### Fixed
17
+
18
+ - **Published manifest no longer ships a `preinstall` hook.** 3.0.0 added a
19
+ pnpm-only `preinstall` guard meant for repository contributors, and npm always
20
+ packs `package.json`, so the hook shipped to every consumer. Pi installs
21
+ extensions with `npm install <spec> --prefix <agentDir>/npm --legacy-peer-deps`,
22
+ so `pi update --extensions` failed with
23
+ `Use pnpm to install dependencies (see CONTRIBUTING.md).` — npm exited 1 and
24
+ rolled the previously installed version back, which left the
25
+ "Package Updates Available" notice pointing at the same outdated version on
26
+ every start. The pnpm-only intent now lives in `devEngines.packageManager`,
27
+ which npm enforces for the repository root (`EBADDEVENGINES`) and ignores when
28
+ the package is installed as a dependency. The repository keeps all of its
29
+ contributor tooling declarations: `packageManager`, `engines.pnpm` and
30
+ CONTRIBUTING.md are unchanged. (Note: `pnpm pack` omits `packageManager` from
31
+ the tarball, which only affects that dev-only field in the published manifest —
32
+ npm consumers install and load the extension normally.)
33
+ - `test/unit/publish-manifest.test.ts` guards the published manifest against
34
+ install-phase scripts (`preinstall` / `install` / `postinstall`) and asserts the
35
+ pnpm requirement stays declared, so this cannot ship again silently.
36
+ - `release.yml` now fails the tag build when a published install-phase script was
37
+ reintroduced, before anything is staged.
38
+
39
+ ## [3.0.0] — 2026-10-18
40
+
41
+ ### Added
42
+
43
+ - In-process `AgentSession` spawn mode (#197): `spawnMode: "rpc" | "inprocess"`,
44
+ `InProcessSupervisor` + `RoutingSupervisor`, and separate
45
+ `subagents.inprocess.*` limit keys. See `docs/inprocess-supervisor.md`.
46
+
47
+ ### Changed
48
+
49
+ - **BREAKING (#197):** `spawnMode` now defaults to `inprocess`, not `rpc`.
50
+ When the key is omitted and the resolved spawn needs an OS worktree
51
+ (`worktree: true` — the writer/centurion default), Legion falls back to
52
+ `rpc` automatically. Non-`pi` roles are unaffected by this fallback: they
53
+ are refused with `INVALID_PARAMS` before the backend is chosen, because
54
+ only `kind: pi` children are supported at all. An explicit
55
+ `spawnMode: "inprocess"` combined with `worktree: true` is still refused
56
+ with `INVALID_PARAMS` — the fallback only covers the implicit default.
57
+ - **BREAKING (#197):** omitting the whole `subagents.inprocess` block no
58
+ longer means "unlimited". The in-process supervisor now falls back to the
59
+ `legion.*` defaults (`maxDepth: 4`, `maxChildrenPerNode: 8`,
60
+ `maxActive: 30`); an explicit block that omits `maxActive` still gets 8.
61
+ `maxSpawnsPerSession` / `waitTimeoutMs` keep no default.
62
+ - #197 in-process children read their identity (node id, db path, acceptance
63
+ role, nesting budget) from an AsyncLocalStorage child context instead of
64
+ `PI_SUBAGENT_*` / `PI_LEGION_*` env. In-process sessions share the parent's
65
+ `process.env`, so the env channel delivered the parent's values — or none at
66
+ all in a root parent — and mis-scoped read-only role enforcement. ALS does
67
+ not cross `spawn()`, so `bash` subprocesses can no longer inherit a tree
68
+ identity.
69
+ - TUI: drop the footer `N agents running` segment (#192); the aboveEditor
70
+ widget already shows the same count. `STATUS_FOOTER_KEY` / `formatFooterStatus`
71
+ remain exported but `@deprecated` (soft-land).
72
+ - Railway lane clock: the leading clock is now "time since the last task
73
+ started" — it keeps ticking through the quiet gap after a turn settles
74
+ (previously it froze at that turn's end, i.e. the *previous* task's elapsed)
75
+ and freezes only when the agent itself ends.
76
+ - Railway lane clocks show from round 1. #159 gated them behind `turns >= 2`,
77
+ but a child is usually dispatched with a single user message, so that gate
78
+ hid the clocks almost always and starved the detail column.
79
+ - Round 1 prints one clock, not two: that turn starts with the agent, so
80
+ `16s / 17s` was one fact plus the spawn gap.
81
+ - Railway column is content-driven: the right-hand name/detail block takes what
82
+ the labels need and the time axis absorbs the rest (floor
83
+ `RAIL_AXIS_MIN_COLS`), so a row fills the line instead of trailing blanks.
84
+
85
+ ### Fixed
86
+
87
+ - Dropped the `now` / `live` wording from every tok/s surface. "now" meant two
88
+ different things at once (the live-only header rate vs. the chart's x-axis
89
+ right edge), which read as a bug; the stat row key is now `rate`.
90
+ - Rail lane detail drops a field for *every* lane when it fits none, instead of
91
+ stripping the role from one long-named row only.
92
+ - `make rebuild` rebuilds the sandbox image and swaps the container.
93
+ - Real-model sandbox: children get `PI_LEGION_CHILD_EXTENSIONS` so they load
94
+ the provider package (#43) — without it every subagent failed with
95
+ `Unknown provider "any-endpoint"`.
96
+ - Subagent detail modal: the area around the frame is no longer black. It was
97
+ mounted as a full-screen overlay, and pi-tui composites an overlay by
98
+ discarding every base cell in its rectangle (there is no transparency option),
99
+ so the viewer's own dim-mask spaces were erasing the host chat / `/legion`
100
+ view and leaving the terminal's default background. The overlay now covers the
101
+ frame rect only, so the host paints everything around it (#193).
102
+ - Geometry is a percentage width with no `maxHeight`, so pi-tui re-derives the
103
+ rect every frame; absolute cells froze at the open-time terminal size and
104
+ clipped the frame's bottom border and Esc hint after a resize.
105
+ - "Click the mask to dismiss" is gone — clicks outside the frame now reach the
106
+ host, as they should. Esc and the title-bar Esc hint still close the modal.
107
+
14
108
  ## [2.0.4] — 2026-10-15
15
109
 
16
110
  ### Added
@@ -164,7 +258,8 @@ branch — pin `pi-legion@0.16.x` if you still need that model. See
164
258
  Earlier 0.11–0.15 history (including the `pi-herdr-swarm` /
165
259
  `@zzjcool/pi-herdr-subagents` renames) lives in git tags and older Releases.
166
260
 
167
- [Unreleased]: https://github.com/zzjcool/pi-legion/compare/v2.0.4...HEAD
261
+ [Unreleased]: https://github.com/zzjcool/pi-legion/compare/v3.0.0...HEAD
262
+ [3.0.0]: https://github.com/zzjcool/pi-legion/compare/v2.0.4...v3.0.0
168
263
  [2.0.4]: https://github.com/zzjcool/pi-legion/compare/v2.0.3...v2.0.4
169
264
  [2.0.3]: https://github.com/zzjcool/pi-legion/compare/v2.0.2...v2.0.3
170
265
  [2.0.2]: https://github.com/zzjcool/pi-legion/compare/v2.0.1...v2.0.2
package/CONTRIBUTING.md CHANGED
@@ -21,13 +21,13 @@ directly to `master`. Implementers do not merge their own pull requests.
21
21
  **Before you push.** Run:
22
22
 
23
23
  ```bash
24
- npm run typecheck
25
- npm test
26
- npm run test:integration # or npm run test:all
24
+ pnpm run typecheck
25
+ pnpm test
26
+ pnpm run test:integration # or pnpm run test:all
27
27
  ```
28
28
 
29
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
30
+ mock-llm. Run `pnpm run test:integration` (or `pnpm run test:all`) locally
31
31
  before you push, especially when the change touches `src/runs/`, `src/legion/`,
32
32
  or `src/extension/runtime.ts`.
33
33
 
@@ -45,9 +45,10 @@ timeout the caller is awaiting stays referenced, as with the wait timer in
45
45
  `src/extension/runtime.ts`.
46
46
 
47
47
  **npm release.** A `v*.*.*` tag runs the release workflow
48
- (`.github/workflows/release.yml`). Publish from that workflow with
49
- `npm stage publish` and a stage-only token. A maintainer promotes the staged
50
- version on npm with 2FA. Do not use plain `npm publish` in GitHub Actions.
48
+ (`.github/workflows/release.yml`). Dependencies are installed with **pnpm**;
49
+ staging still uses the npm CLI (`npm stage publish`) and a stage-only token.
50
+ A maintainer promotes the staged version on npm with 2FA. Do not use plain
51
+ `npm publish` in GitHub Actions.
51
52
 
52
53
  **Communication.** Coordinate in the team channel, or with Alice. Do not ping
53
54
  the maintainer user for a routine review or merge ask.
@@ -57,34 +58,36 @@ the maintainer user for a routine review or merge ask.
57
58
  ```bash
58
59
  git clone https://github.com/zzjcool/pi-legion.git
59
60
  cd pi-legion
60
- npm install
61
+ pnpm install
61
62
  ```
62
63
 
63
- Requires Node.js 22+ (`node --experimental-strip-types` is how tests run;
64
- there is no compile step to ship).
64
+ Requires Node.js 22+ and **pnpm** 9+ (`packageManager` in `package.json`;
65
+ `node --experimental-strip-types` is how tests run; there is no compile step
66
+ to ship). Prefer Corepack (`corepack enable`) or install pnpm 9.x. Do not use
67
+ `npm install` in this repo.
65
68
 
66
69
  ## Checks
67
70
 
68
71
  The required GitHub Actions check `ci` (`.github/workflows/ci.yml`, on pull
69
72
  requests and pushes to `master`) runs, in order:
70
73
 
71
- 1. `npm run typecheck`
72
- 2. `npm test` (unit)
73
- 3. `npm run test:integration`
74
- 4. `npm run test:mock-llm`
74
+ 1. `pnpm run typecheck`
75
+ 2. `pnpm test` (unit)
76
+ 3. `pnpm run test:integration`
77
+ 4. `pnpm run test:mock-llm`
75
78
 
76
79
  A failing integration step fails `ci`, so nesting and orchestrator regressions
77
80
  are blocked before merge. `test:live` and `test:docker` stay out of that job.
78
81
 
79
82
  ```bash
80
- npm run typecheck
81
- npm test # test/unit/*.test.ts
82
- npm run test:integration # FakeSupervisor / nested fakes; no live `pi` binary
83
- npm run test:all # typecheck + unit + integration
84
- npm run test:mock-llm # also part of the required ci check
83
+ pnpm run typecheck
84
+ pnpm test # test/unit/*.test.ts
85
+ pnpm run test:integration # FakeSupervisor / nested fakes; no live `pi` binary
86
+ pnpm run test:all # typecheck + unit + integration
87
+ pnpm run test:mock-llm # also part of the required ci check
85
88
  ```
86
89
 
87
- `npm run test:live` is a **pi RPC live** smoke (`test/live/rpc-live.test.ts`).
90
+ `pnpm run test:live` is a **pi RPC live** smoke (`test/live/rpc-live.test.ts`).
88
91
  It skips itself when the `pi` binary is missing from `PATH` (see the probe at
89
92
  the top of that file). Do not fail CI on it.
90
93
 
@@ -102,8 +105,8 @@ For offline Pi / legion smoke without provider quota, start the scriptable
102
105
  OpenAI-compatible mock (see [`test/mock-llm/README.md`](test/mock-llm/README.md)):
103
106
 
104
107
  ```bash
105
- npm run mock-llm # default scenario=echo on :8765
106
- npm run test:mock-llm # engine + HTTP + optional pi --print smoke
108
+ pnpm run mock-llm # default scenario=echo on :8765
109
+ pnpm run test:mock-llm # engine + HTTP + optional pi --print smoke
107
110
  ```
108
111
 
109
112
  `test/docker/mock-llm.mjs` launches the same CLI (default scenario `tool-once`
@@ -142,12 +145,12 @@ bash test/docker/verify-11-nested-alias.sh # nested + alias + completionGuard
142
145
  Or all at once (RPC mainline **1–11** only):
143
146
 
144
147
  ```bash
145
- npm run test:docker
148
+ pnpm run test:docker
146
149
  ```
147
150
 
148
151
  **Legacy Herdr / Space / kind checks** live under `test/legacy/docker/` (need
149
152
  pi-legion **0.16.x** Herdr-era setup). They are **not** part of
150
- `npm run test:docker`:
153
+ `pnpm run test:docker`:
151
154
 
152
155
  ```bash
153
156
  bash test/legacy/docker/verify-12-workspace.sh # Herdr Space pin (legacy)
package/README.md CHANGED
@@ -20,6 +20,8 @@ shared SQLite ledger records the whole tree.
20
20
  ## Contents
21
21
 
22
22
  - [Architecture](#architecture)
23
+ - [Spawn mode](#spawn-mode)
24
+ - [Headless child contract](#headless-child-contract)
23
25
  - [Quick start](#quick-start)
24
26
  - [Legion tools and phases](#legion-tools-and-phases)
25
27
  - [Three-layer visualization](#three-layer-visualization)
@@ -63,6 +65,50 @@ Transport, state, decision-making, and display are separate layers:
63
65
  - **Display:** the event bus feeds the resident tree, the `/legion` full view,
64
66
  and an on-demand live overlay.
65
67
 
68
+ ## Spawn mode
69
+
70
+ `spawnMode` picks the backend that runs one child. Per role it comes from agent
71
+ frontmatter → `subagents.agentOverrides.<role>` → team member override:
72
+
73
+ ```yaml
74
+ # agents/scout.md
75
+ ---
76
+ spawnMode: inprocess # or: rpc
77
+ ---
78
+ ```
79
+
80
+ **Default:** omitting `spawnMode` means `inprocess` — the child runs as an
81
+ `AgentSession` inside the parent Node process (`createAgentSession`), which
82
+ trades process isolation for spawn latency.
83
+
84
+ **Automatic fallback to `rpc`:** when `spawnMode` is omitted *and* the resolved
85
+ spawn cannot be in-process, Legion launches it over `pi --mode rpc` instead.
86
+ That happens when the role needs an OS worktree (`worktree: true` — the writer
87
+ and centurion default). A `kind !== "pi"` role is rejected earlier with
88
+ `INVALID_PARAMS` (only `kind: pi` children are supported at all), so it never
89
+ reaches this fallback.
90
+
91
+ **Explicit is strict:** `spawnMode: "inprocess"` with `worktree: true` is
92
+ refused with `INVALID_PARAMS` rather than silently downgraded, so a role that
93
+ asks for in-process never gets a different transport behind its back.
94
+
95
+ Limits for the in-process backend are separate (`subagents.inprocess.*`) and are
96
+ not shared with the `legion.*` tree budgets — see
97
+ [docs/inprocess-supervisor.md](docs/inprocess-supervisor.md).
98
+
99
+ ## Headless child contract
100
+
101
+ Legion children on the **`rpc` backend** are rpc only (`pi --mode rpc`).
102
+ Headless modes have no
103
+ interactive tool-approval UI. Project `.pi/settings.json` / project extensions
104
+ load under headless only after `/trust` (or `defaultProjectTrust=always`), or
105
+ when you opt in with `subagents.rpc.approve: true` (appends `--approve`). Legion
106
+ `onBlocked` is a parent subagent block strategy — **not** a pi tool-approval
107
+ replacement; keep the layers separate.
108
+
109
+ Full mapping (exit → `SettleResult` → node state) and the three upstream headless
110
+ modes vs legion: [docs/headless-child-contract.md](docs/headless-child-contract.md).
111
+
66
112
  ## Quick start
67
113
 
68
114
  Install and load the extension in Pi:
@@ -109,7 +155,8 @@ child's result in the same print turn, call `wait` or `collect` before the
109
155
  turn ends, or launch with `async: false`. To stop children explicitly, use
110
156
  `subagent({ action: "stop", name: "worker-0" })` or
111
157
  `subagent({ action: "stop", all: true })`; this runs the normal graceful-retire
112
- sequence for each selected child.
158
+ sequence for each selected child. Pass `force: true` to escalate to hard retire
159
+ when a child ignores soft steer / graceful close.
113
160
 
114
161
  ### Stopping async subagents yourself
115
162
 
@@ -123,6 +170,16 @@ shutdown); process exit / session shutdown also retires:
123
170
  parent (stop the parent to retire them).
124
171
  - **`x`** on the selected live agent in the `/legion` view or its live
125
172
  overlay asks `y` to confirm in place, then stops it.
173
+ - **Tool**: `subagent({ action: "stop", name })` is graceful; pass
174
+ `force: true` for hard retire (SIGTERM → SIGKILL + descendant sweep per
175
+ `docs/retire-hardening-contract.md`). Example:
176
+ `subagent({ action: "stop", name: "worker-0", force: true })`.
177
+ TUI `/legion stop --force` is an optional follow-up (same `force: true`).
178
+
179
+ **`wait` ≠ stop.** A `wait` timeout (or still-running result) does **not**
180
+ retire children. The tool result includes live child names, elapsed ms, and
181
+ suggested decisions (`extend` / `steer_again` / `stop` / `force_stop`); the
182
+ parent chooses the next tool call. See `docs/contracts/wait-not-stop.md`.
126
183
 
127
184
  While subagents are live, the railway header can show a dim
128
185
  `/legion stop [name|all]` hint (shortened on narrow terminals; idle gating /
@@ -182,10 +239,15 @@ commands without an LLM. Passing verification advances the squad to `done`.
182
239
  1. **Railway strip above the input:** one git-graph lane per live subagent
183
240
  (max 4, 3 below 80 columns; oldest/shallowest first, `+N` for the rest).
184
241
  When an agent ends its lane merges back and lower lanes shift up. The
185
- header shows the live count and tok/s (trailing 60s average). Hidden when
186
- no subagent is live. Set `PI_LEGION_STRIP=tree` for the classic roster.
242
+ header shows the live count and tok/s (trailing 60s average). The lane
243
+ label reads `role · <time since the last task started> / <lifetime> · <age
244
+ of last activity>`; the leading clock keeps ticking through the quiet gap
245
+ after a turn settles and freezes only when the agent itself ends. The lane
246
+ column is measured from what the labels need and the time axis takes the
247
+ rest (down to a floor), so a row always fills the line. Hidden when no
248
+ subagent is live. Set `PI_LEGION_STRIP=tree` for the classic roster.
187
249
  2. **`/legion` full view:** galaxy graph of the agent tree, stacked tok/s
188
- chart by agent type (peak marker, now/avg/peak/total, per-role share), and
250
+ chart by agent type (peak marker, rate/avg/peak/total, per-role share), and
189
251
  a scrollable `log --graph` history. Tab switches panes, arrows scroll or
190
252
  move the selection, Enter selects an agent (its own rate line, tok/s,
191
253
  tokens and share are overlaid), `o` opens its live overlay, `x` stops it
@@ -285,9 +347,20 @@ per-member overrides. Object member overrides (for example `model` or
285
347
  omitted override fields continue to use their normal role/configuration
286
348
  values.
287
349
 
288
- See [docs/subagents-teams.md](docs/subagents-teams.md) for four editable
289
- frontend/backend × premium/value team templates. The model IDs in that template
290
- are placeholders for your provider, not required defaults.
350
+ Teams may also set optional `parentContext` / `childContext` (same value forms
351
+ as the global keys, including `@path` and `false`). When the active team
352
+ defines a side, that value **replaces** the global context for that side — it
353
+ does not merge. Omit the field to fall back to global (or convention files);
354
+ set `false` to disable that side with no fallthrough. Unknown team names keep
355
+ today's warn + full roster behaviour and still use global context.
356
+
357
+ See [docs/subagents-teams.md](docs/subagents-teams.md) for editable
358
+ frontend/backend team templates with **distinct workflow text** (not only
359
+ different models). The model IDs in that template are placeholders for your
360
+ provider, not required defaults. For running a multi-agent QA hunting round in
361
+ this repo, see [docs/qa-hunting-round.md](docs/qa-hunting-round.md) (round
362
+ orchestration) and [docs/tui-qa-playbook.md](docs/tui-qa-playbook.md)
363
+ (evidence rules).
291
364
 
292
365
  ### Parent/child context conventions
293
366
 
@@ -296,8 +369,11 @@ inline or through `@path` references. If either setting is omitted, Legion
296
369
  looks for `subagents-parent-context.md` or `subagents-child-context.md`
297
370
  respectively in the directory containing that settings file. These are
298
371
  convention-based fallbacks: an explicit setting takes precedence, and setting
299
- either field to `false` disables its context. The convention filenames are
300
- `PARENT_CONTEXT_CONVENTION` and `CHILD_CONTEXT_CONVENTION` in
372
+ either field to `false` disables its context. Named teams can override each
373
+ side via `subagents.teams.<name>.parentContext` /
374
+ `childContext` (replace, never merge; see [Team 配置](#team-配置)). The
375
+ convention filenames are `PARENT_CONTEXT_CONVENTION` and
376
+ `CHILD_CONTEXT_CONVENTION` in
301
377
  [`src/agents/settings.ts`](src/agents/settings.ts#L30-L31); missing convention
302
378
  files simply contribute no context. The repository examples are
303
379
  [`examples/subagents-parent-context.md`](examples/subagents-parent-context.md)
@@ -311,7 +387,7 @@ same idea as `pi list` / `pi auth check`.
311
387
 
312
388
  - `/legion doctor` — full report in the chat (works outside TUI too).
313
389
  - `/legion doctor [section]` — one of: `summary`, `extensions`, `models`,
314
- `spawn`, `memory`, `tools`, `storage`, `runtime`, `next`.
390
+ `spawn`, `memory`, `tools`, `storage`, `runtime`, `errors`, `next`.
315
391
  - `subagent({ action: "diagnose" })` — same full report as tool output so the
316
392
  main agent can call it without waiting for you. The tool has no section
317
393
  field; filter with `/legion doctor [section]`.
@@ -326,6 +402,12 @@ snippet (relevant sections + Next steps) so the main agent can self-fix
326
402
  without anyone typing `/legion doctor`. Full `/legion doctor` remains for
327
403
  manual deep dives.
328
404
 
405
+ **Session errors (#160):** tool validation / call failures from `subagent`
406
+ (and similar) append to `.pi-subagents/session-errors.jsonl` (JSONL, rotated
407
+ to ~500 lines). `/legion doctor` / `diagnose` show counts, by-code tallies,
408
+ and the last few one-line titles under the `errors` section; when the log is
409
+ large they print only that summary plus the log path.
410
+
329
411
  Coverage:
330
412
 
331
413
  | Section | What it checks |
@@ -337,6 +419,7 @@ Coverage:
337
419
  | tools | `/legion` slash list (status, team, agents, summary, on/off, scout-guard, doctor, stop), idle-Esc / strip hints, tool names |
338
420
  | 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 |
339
421
  | runtime | Live children, tree statuses, acceptance-ish / active nodes |
422
+ | errors | Recent session/tool errors (validation failures, etc.) as a short summary; full journal at `.pi-subagents/session-errors.jsonl` (rotated ~500 lines). Large logs: summary + path only — doctor never dumps the whole file |
340
423
 
341
424
  ### Child extensions
342
425
 
@@ -439,11 +522,11 @@ another CLI, keep it on the 0.16.x line or replace it with a Pi role.
439
522
  ## Development
440
523
 
441
524
  ```bash
442
- npm install
443
- npm run typecheck
444
- npm test # unit tests
445
- npm run test:integration # integration tests
446
- npm run test:all # typecheck + unit + integration tests
525
+ pnpm install
526
+ pnpm run typecheck
527
+ pnpm test # unit tests
528
+ pnpm run test:integration # integration tests
529
+ pnpm run test:all # typecheck + unit + integration tests
447
530
  ```
448
531
 
449
532
  ### Mock LLM (offline pi / legion smoke)
@@ -453,9 +536,9 @@ It registers with Pi like any local gateway (`models.json` → `api: "openai-com
453
536
  so spawn / stop / doctor / verify paths can run without real API keys:
454
537
 
455
538
  ```bash
456
- npm run mock-llm # http://127.0.0.1:8765/v1 scenario=echo
457
- npm run test:mock-llm # unit + integration smoke for the mock
458
- MOCK_LLM_SCENARIO=spawn-scout npm run mock-llm
539
+ pnpm run mock-llm # http://127.0.0.1:8765/v1 scenario=echo
540
+ pnpm run test:mock-llm # unit + integration smoke for the mock
541
+ MOCK_LLM_SCENARIO=spawn-scout pnpm run mock-llm
459
542
  ```
460
543
 
461
544
  Use `writeMockPiHome()` from `test/mock-llm` to generate an isolated
@@ -463,7 +546,8 @@ Use `writeMockPiHome()` from `test/mock-llm` to generate an isolated
463
546
  `auth.json` into fixtures.
464
547
 
465
548
  See [CONTRIBUTING.md](CONTRIBUTING.md#project-workflow) for the project
466
- workflow: issues, pull requests, the merge gate, and npm releases.
549
+ workflow: issues, pull requests, the merge gate, and releases
550
+ (deps via pnpm; npm stage publish for staging).
467
551
 
468
552
  Version history for consumers: [CHANGELOG.md](CHANGELOG.md). Tag-by-tag
469
553
  release bodies: [GitHub Releases](https://github.com/zzjcool/pi-legion/releases).
@@ -0,0 +1,72 @@
1
+ # OSC 7501 busy / plugin contract (pi-legion #156)
2
+
3
+ Legion keeps the **root** program-status record `working` while any live child
4
+ or registered extension activity remains. OSC truth is owned by Pi's
5
+ `ProgramStatusReporter` — **plugins and legion must not `printf` OSC 7501**.
6
+
7
+ ## Handshake
8
+
9
+ - Terminals may answer `OSC 7501;?` / `PI_PROGRAM_STATUS=1`.
10
+ - Prefer Pi APIs (`ui.setActivity`, `ui.setProgramRecord`). Do not emit raw OSC.
11
+
12
+ ## Plugin / child-side rules
13
+
14
+ 1. **Raise**: while async work is unfinished →
15
+ `ui.setActivity(source, { message })` and/or
16
+ `ui.setProgramRecord({ id, state: "working", message })`.
17
+ 2. **Clear**: on settle / cancel / error →
18
+ `ui.setActivity(source, undefined)` and
19
+ `ui.setProgramRecord({ id, state: "clear" })`.
20
+ 3. **Ids**: use a stable hierarchical id (`plugin/<name>/…` or
21
+ `legion/<slug>`). Do **not** write an id-less root `working` that steals
22
+ Pi's root semantics — register an activity source or a child id.
23
+ 4. **RPC children**: in RPC mode, Pi forwards `ui.setActivity` /
24
+ `ui.setProgramRecord` as `extension_activity` to the parent. Legion folds
25
+ those into the parent reporter (`legion/<child>/<source>`). **Do not**
26
+ parse child PTY OSC bytes.
27
+
28
+ ## Legion aggregation
29
+
30
+ | Source | Mechanism |
31
+ |--------|-----------|
32
+ | Live RPC child | `emitBusy` → `ui.setActivity("legion", …)` + per-child `id=legion/<slug>` |
33
+ | Child-side plugin busy | RPC `extension_activity` → parent `setActivity` / `setProgramRecord` |
34
+ | Same-process plugin | Direct `ui.setActivity` / `setProgramRecord` |
35
+
36
+ Root stays `working` while any activity source or uncleared `legion/*` child
37
+ id remains. `/legion stop` and runtime dispose clear the legion source and
38
+ child ids. **Esc** (wait interrupt) does **not** clear activity.
39
+
40
+ Priority inside Pi (unchanged): `blocked` > compacting > (`runActive` OR
41
+ `activities.size > 0` → root `working`) > resting (`done`/`idle`/`error`).
42
+
43
+ ## Mock example (copy-paste)
44
+
45
+ ```ts
46
+ // Same-process plugin / test double
47
+ ui.setActivity?.("plugin/bg-tasks", { message: "2 background tasks" });
48
+ ui.setProgramRecord?.({
49
+ id: "plugin/bg-tasks/job-1",
50
+ state: "working",
51
+ message: "job-1",
52
+ });
53
+
54
+ // …work finishes…
55
+ ui.setProgramRecord?.({ id: "plugin/bg-tasks/job-1", state: "clear" });
56
+ ui.setActivity?.("plugin/bg-tasks", undefined);
57
+ ```
58
+
59
+ Parent-side mock of a child fan-in (what legion does after RPC):
60
+
61
+ ```ts
62
+ // Child "scout" raised setActivity("bg", { message: "compiling" })
63
+ ui.setActivity("legion/scout/bg", { message: "compiling" });
64
+ // Child cleared
65
+ ui.setActivity("legion/scout/bg", undefined);
66
+ ```
67
+
68
+ ## Non-goals
69
+
70
+ - Herdr / inbox OSC consumers
71
+ - Parsing child stdout for OSC 7501
72
+ - Big TUI railway/footer changes in this wave