pi-legion 3.0.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +263 -2
  2. package/CONTRIBUTING.md +4 -17
  3. package/README.md +29 -22
  4. package/agents/worker.md +1 -1
  5. package/docs/contracts/display-liveness-v1.md +373 -0
  6. package/docs/contracts/drop-non-pi-kinds-v4.md +198 -0
  7. package/docs/contracts/esc-abort-v1.md +4 -4
  8. package/docs/contracts/esc-no-retire-v3.md +2 -2
  9. package/docs/contracts/launch-admission-queue-v1.md +839 -0
  10. package/docs/contracts/status-panel-v1.md +174 -0
  11. package/docs/inprocess-supervisor.md +37 -4
  12. package/docs/legion-v2-contract.md +18 -2
  13. package/docs/performance-2026-10-19.md +199 -0
  14. package/docs/spawn-mode-visibility-v1.md +206 -0
  15. package/docs/vexillatio-contract.md +3 -1
  16. package/index.ts +633 -84
  17. package/package.json +4 -2
  18. package/skills/pi-legion/SKILL.md +2 -3
  19. package/src/agents/agents.ts +17 -10
  20. package/src/agents/overrides.ts +0 -1
  21. package/src/agents/presets.ts +14 -40
  22. package/src/agents/settings.ts +26 -77
  23. package/src/agents/step-model.ts +7 -22
  24. package/src/api.ts +1 -35
  25. package/src/extension/event-bus.ts +80 -8
  26. package/src/extension/legion-commands.ts +43 -17
  27. package/src/extension/legion-doctor.ts +1 -1
  28. package/src/extension/legion-stop.ts +39 -13
  29. package/src/extension/legion-tools.ts +21 -4
  30. package/src/extension/liveness.ts +200 -0
  31. package/src/extension/notify.ts +6 -6
  32. package/src/extension/playbook.ts +6 -6
  33. package/src/extension/recycle.ts +6 -2
  34. package/src/extension/runtime.ts +422 -18
  35. package/src/extension/slash.ts +0 -21
  36. package/src/extension/spawn-render.ts +183 -0
  37. package/src/extension/summary.ts +1 -1
  38. package/src/legion/db.ts +165 -1
  39. package/src/legion/events.ts +81 -16
  40. package/src/legion/nodes.ts +13 -2
  41. package/src/legion/rebuild.ts +7 -1
  42. package/src/legion/usage.ts +58 -4
  43. package/src/runs/orchestrator.ts +1165 -62
  44. package/src/runs/worktree.ts +190 -21
  45. package/src/shared/ignored-kind.ts +57 -0
  46. package/src/shared/progress.ts +11 -0
  47. package/src/shared/session.ts +199 -19
  48. package/src/shared/types.ts +50 -66
  49. package/src/supervisor/admission-queue.ts +516 -0
  50. package/src/supervisor/inprocess-supervisor.ts +108 -33
  51. package/src/supervisor/proc-memory.ts +67 -0
  52. package/src/supervisor/routing-supervisor.ts +58 -1
  53. package/src/tui/live-overlay.ts +14 -0
  54. package/src/tui/session-messages-file.ts +100 -0
  55. package/src/tui/status.ts +103 -21
  56. package/src/tui/tree-view.ts +36 -3
  57. package/src/tui/viz/layout.ts +54 -3
  58. package/src/tui/viz/model.ts +67 -14
  59. package/src/tui/viz/palette.ts +11 -0
  60. package/src/tui/viz/panels.ts +222 -8
  61. package/src/tui/viz/railway.ts +28 -25
  62. package/src/tui/viz/view.ts +110 -23
  63. package/src/backends/legacy/client.ts +0 -638
  64. package/src/backends/legacy/cursor-chat.ts +0 -348
  65. package/src/backends/legacy/runner.ts +0 -101
  66. package/src/backends/legacy/types.ts +0 -138
  67. package/src/runs/kind.ts +0 -260
@@ -0,0 +1,206 @@
1
+ # Spawn-mode visibility + RPC copy cleanup — frozen contract v1
2
+
3
+ Status: frozen for implementation. Owner decisions are D1–D7; do not re-litigate.
4
+
5
+ ## Why
6
+
7
+ `DEFAULT_SPAWN_MODE = "inprocess"` (`src/supervisor/spawn-mode.ts:18`) has shipped,
8
+ but the parent-facing guidance still says every child is an "RPC child", and the
9
+ actual backend (`handle.spawnMode`) is only visible as a substring of a result
10
+ text line. Users cannot tell inprocess from rpc from the UI.
11
+
12
+ Facts established by recon (do not re-verify):
13
+
14
+ - pi never renders tool-result `details` on its own. The only consumer is
15
+ `renderResult` (`tool-execution.js:243`); with no renderer the host falls back
16
+ to `content` text only. Adding a `details` field alone is invisible.
17
+ - `subagent` / `vexillatio` register **no** renderer (`index.ts:708`, `index.ts:973`).
18
+ - `Handle.spawnMode: SpawnMode` is non-optional and is the arbitrated backend
19
+ (`src/shared/types.ts:482-503`, produced at `src/runs/orchestrator.ts:809-818`).
20
+ - `LaunchSession.handles` is `string[]` (`index.ts:1831`) — the mode is dropped there.
21
+ - The `/legion` node modal footer is `metaPlain()` (`src/tui/live-overlay.ts:737-756`)
22
+ rendering `role · model · turn N (Xs) · elapsed · status`.
23
+ - `legion.nodes` has **no** spawn_mode column (`src/legion/db.ts:47-67`).
24
+
25
+ ## D1 — Wording (backend-neutral) for parent guidance
26
+
27
+ Children are **in-process by default**; a separate `pi` process is used only when
28
+ the launch needs a worktree (centurion) or the role pins `spawnMode: rpc`.
29
+
30
+ Replace "RPC child / headless RPC children / started via RPC / RPC launch" with
31
+ backend-neutral phrasing in these exact places:
32
+
33
+ | File:line | Current |
34
+ | --- | --- |
35
+ | `src/extension/playbook.ts:12` | `Child processes are headless RPC children; no pane or tab is created.` |
36
+ | `src/extension/playbook.ts:13` | `when each RPC child finishes` |
37
+ | `src/extension/playbook.ts:15` | `A collect timeout with a live RPC child` |
38
+ | `src/extension/playbook.ts:25` | `children use --no-extensions` (keep only if still true for the rpc path; phrase as `rpc children use --no-extensions`) |
39
+ | `index.ts:1669` | `playbook: … → RPC launch → watch` |
40
+ | `index.ts:1966` | `playbook: … started via RPC` |
41
+ | `index.ts:1801` | `when each RPC child finishes` |
42
+ | `index.ts:1616` | `(live RPC child steered)` |
43
+ | `index.ts:1574` | `RPC process stopped; session kept for resume:` |
44
+ | `src/extension/recycle.ts:10` | `the RPC child is already stopped` |
45
+ | `src/extension/notify.ts:200` | `RPC child retired.` |
46
+ | `src/extension/notify.ts:237` | `(RPC child is not alive)` |
47
+ | `src/extension/notify.ts:315` | `(RPC child retired)` |
48
+
49
+ Use `child` as the noun. Suggested replacement vocabulary (one word list, keep it
50
+ consistent): `child`, `in-process child`, `rpc child` (lowercase, only when the
51
+ rpc backend is meant), `session`, `watch`.
52
+
53
+ ## D2 — Out of scope for D1 (do NOT touch)
54
+
55
+ These are either internal identifiers or the genuine rpc backend; changing them
56
+ breaks failure classification or contracts:
57
+
58
+ - `src/supervisor/rpc-supervisor.ts` reasons (`Pi RPC process exited …`, etc.)
59
+ - `src/extension/auto-doctor.ts:50` regex (matches `Pi RPC process exited`)
60
+ - `src/runs/orchestrator.ts:146` `TRANSIENT_CONNECTION_REASON` value
61
+ - `src/runs/orchestrator.ts:579/782`, `src/runs/hygiene.ts`, `--no-extensions`
62
+ plumbing, `rpc-child-*` temp dir prefixes, `subagents.rpc.*` keys
63
+ - the frozen head line `interrupted (parent turn ended; children still running)`
64
+ - `docs/contracts/**`
65
+
66
+ ## D3 — `details` carries the mode (machine facts)
67
+
68
+ Additive only; never replace an existing field, never emit `isError`.
69
+
70
+ `subagent` launch-family success + interrupted results gain:
71
+
72
+ ```ts
73
+ spawns?: Array<{ name: string; agent: string; spawnMode: "rpc" | "inprocess" }>
74
+ ```
75
+
76
+ - Emitted only when at least one handle resolved (omitted otherwise, matching the
77
+ existing `stillRunning?` "present only when non-empty" convention).
78
+ - Values come from `handle.spawnMode` (`Handle`, never re-derived, never guessed).
79
+ - Sites: `index.ts:1754` (interrupted) and `index.ts:1807` (normal).
80
+ The interrupted path runs after `handles` is cleared, so keep the resolved
81
+ entries in a parallel field on `LaunchSession` (precedent: `timeoutByName`).
82
+
83
+ `vexillatio` gains `spawnMode` on each handle object:
84
+
85
+ ```ts
86
+ handles: Array<{ name: string; agent: string; sessionFile: string; spawnMode?: SpawnMode }>
87
+ ```
88
+
89
+ `spawnMode` is optional so `ChildRecord.spawnMode?: SpawnMode` legacy runs
90
+ (`src/shared/types.ts:365-373`) render as absent, never as `"rpc"`.
91
+ `docs/vexillatio-contract.md:57` freezes top-level details keys only, so this is
92
+ allowed; do not change that doc.
93
+
94
+ ## D4 — TUI renderer for both tools (this is what makes D3 visible)
95
+
96
+ New file `src/extension/spawn-render.ts`:
97
+
98
+ ```ts
99
+ export function spawnModeSummary(details: unknown): string | undefined
100
+ export function renderSpawnResult(
101
+ result: AgentToolResult<unknown>,
102
+ opts: { expanded: boolean; isPartial: boolean },
103
+ theme: Theme,
104
+ ): Component
105
+ ```
106
+
107
+ Behaviour:
108
+
109
+ 1. `spawnModeSummary` reads `details.spawns` and/or `details.handles[].spawnMode`
110
+ defensively (unknown → undefined). Output: `spawn: scout-0=inprocess`
111
+ or `spawn: worker-0=rpc, scout-1=inprocess`. Entries with an unknown mode are
112
+ skipped; if none survive, return `undefined`.
113
+ 2. `renderSpawnResult` builds the text from `content` text blocks only
114
+ (`stripTerminalSequences`), prepends the summary line when defined, then
115
+ collapsed → first 10 lines + `... (N more lines, ctrl+o to expand)`,
116
+ expanded → all lines. Returns `new Text(text, 0, 0)`.
117
+ 3. It must never throw: wrap the details access in optional chaining; a throw is
118
+ silently swallowed by the host and would blank the card.
119
+
120
+ Attach `renderResult: (result, opts, theme) => renderSpawnResult(result, opts, theme)`
121
+ to both `subagent` (`index.ts:973`) and `vexillatio` (`index.ts:708`).
122
+ No `renderCall`, no `renderShell: "self"`.
123
+
124
+ Rationale: `content` is model-facing, `details` is TUI-facing (`docs/design.md:258`).
125
+ The summary line goes through `details` so it does not enter the LLM context.
126
+
127
+ ## D5 — `/legion` node modal footer shows the same value
128
+
129
+ Thread the arbitrated mode to the overlay without a DB migration:
130
+
131
+ 1. `TrackedJobInput` (`src/extension/runtime.ts:68`) gains `spawnMode?: SpawnMode`.
132
+ 2. Launch sites pass `handle.spawnMode` (`index.ts` ~1914 and the follow sites
133
+ ~2709-2790 that already carry the handle/record).
134
+ 3. `TreeNodeSnapshot` (`src/tui/tree-view.ts:22`) gains `spawnMode?: SpawnMode`;
135
+ `snapshotNodes()` (`src/extension/runtime.ts:608`) merges the job's value onto
136
+ the node snapshot (`job.spawnMode`, matched by nodeId/name, no guessing).
137
+ 4. `liveNode()` (`src/extension/legion-commands.ts:298`) maps it through
138
+ `LiveOverlayNode` (`src/tui/live-overlay.ts:25`).
139
+ 5. `metaPlain()` appends `spawn <mode>` **only when known**; legacy/unknown →
140
+ omit the segment entirely (never print `rpc` as a default).
141
+
142
+ Do **not** add a `legion.db` column and do **not** bump `LEGION_DB_USER_VERSION`.
143
+
144
+ ## D6 — Tests (must be updated/added by the same change)
145
+
146
+ Update (currently lock the old wording):
147
+
148
+ - `test/unit/playbook.test.ts:17` `/headless RPC children/`, `:76` `/RPC child is a progress signal/`
149
+ - `test/unit/notify.test.ts:52,163,169,398,410-411`
150
+ - `test/unit/recycle.test.ts:11`
151
+ - `test/unit/runtime.test.ts:1252`
152
+
153
+ Add:
154
+
155
+ - `spawnModeSummary` returns `undefined` for `{}`/unknown/legacy, and the
156
+ documented string for both launch-family and vexillatio details shapes.
157
+ - `renderSpawnResult` collapsed keeps the summary line and caps at 10 lines +
158
+ hint; expanded prints everything.
159
+ - launch details expose `spawns` with the arbitrated value (extend an existing
160
+ harness that already inspects `details`, e.g. `test/unit/teams.test.ts:1320`).
161
+ - `metaPlain` includes `spawn rpc` / `spawn inprocess` when set and omits it when
162
+ unknown (`test/unit/legion-live-overlay.test.ts`).
163
+
164
+ ## Errata (review round 1, accepted)
165
+
166
+ - **D3/D6 (line numbers)**: the D1 table merged two `index.ts` copy sites into
167
+ `playbook.ts` rows. The real sites are `playbook.ts:12/14/15` plus
168
+ `index.ts:1669/1801/1966/1616/1574`. Behaviour unchanged.
169
+ - **D1 (`--no-extensions`)**: the suggested wording `rpc children use
170
+ --no-extensions` was wrong — in-process children run with
171
+ `noExtensions = !childInheritsExtensions(...)` too
172
+ (`src/supervisor/inprocess-supervisor.ts:145`). The sentence must stay
173
+ backend-neutral; do not name `rpc` there.
174
+ - **D4 (never-throw)**: shape checks are not enough. A `details` object with a
175
+ throwing getter still throws, and the host silently swallows it
176
+ (`tool-execution.js:150-160`). `spawnModeSummary` and `renderSpawnResult` must
177
+ both be wrapped in `try/catch`. Covered by a getter/Proxy test.
178
+ - **D4 (line cap)**: the collapsed cap must count rendered lines, so `\r` and
179
+ binary control bytes are stripped **before** splitting — parity with the host
180
+ fallback (`render-utils.js:40`: `sanitizeBinaryOutput` + `.replace(/\r/g,"")`).
181
+ Without it a `\r` progress stream renders 21 lines and never shows the hint.
182
+ - **D4 (dedupe)**: one `name=mode` entry per child, first occurrence wins.
183
+ - **D5 (`treeNodes()`)**: `snapshotNodes()` alone is not enough. `treeNodes()`
184
+ synthesizes a `TreeNodeSnapshot` for a job with identity but no DB row
185
+ (`runtime.ts:1311-1331`), and that literal dropped `spawnMode`, so the modal
186
+ footer lost the segment in the plain-session / pre-DB-row window. Merge in
187
+ `treeNodes()` (shared helper), not only in `snapshotNodes()`.
188
+ - **D5 (width)**: `spawn <mode>` appended last is truncated to `spawn inpr...`
189
+ at the default 80-column layout (`resolveModalLayout(24,80)` → `innerWidth=78`).
190
+ Put the segment before `model` so the role+status survive, and test with a
191
+ realistic model id.
192
+
193
+ ## D8 — Commit ownership
194
+
195
+ Implementation lands as a single commit **created by the parent agent**.
196
+ Sub-agents must not `git commit`, `git push`, or merge. Round 1 violated this
197
+ (`80890ba` was authored by the worker); it is amended by the parent in round 2.
198
+
199
+ ## D7 — Acceptance
200
+
201
+ - `npm run typecheck`
202
+ - `npm test`
203
+ - `npm run test:integration`
204
+ - No new `RPC child` occurrences in parent-visible strings: the D1 table is empty
205
+ afterwards, and `rg -n 'RPC child|headless RPC|via RPC' src index.ts` returns
206
+ only the files listed in D2.
@@ -41,7 +41,8 @@ utility: true # 布尔;缺省 false。utility 与 allowNestedSubagents
41
41
 
42
42
  ```ts
43
43
  const VexillatioParams = Type.Object({
44
- task: Type.String({ description: "Recon task; one clear question list" }),
44
+ // REV-17.10: Optional —— `tasks[]` 是完整请求(与 `subagent({ tasks })` 同形)。
45
+ task: Type.Optional(Type.String({ description: "Recon task; one clear question list" })),
45
46
  agent: Type.Optional(Type.String({ description: "Utility agent name (default: scout)" })),
46
47
  tasks: Type.Optional(Type.Array(TaskItem, { description: "Parallel recon launches" })),
47
48
  cwd: Type.Optional(Type.String()),
@@ -161,3 +162,4 @@ const VexillatioParams = Type.Object({
161
162
  | 2026-10-12 | REV-17.7 | 新增 §2.7 工具白名单注入规则(`pushToolArgs` 对非 utility 角色追加 vexillatio);§3 新增 `src/runs/args.ts` 所有权 + 两个 `read,bash` 断言授权(orchestrator.test:99 / supervisor.test:265)。分支已合入 master 最新(PR #22) | worker-6 完成全部实现后指出:受限角色(显式 tools: 白名单)调不到未列名的 vexillatio,需在 args 层追加且两个既有断言未授权——拒绝正确;从原会话恢复继续,勿重写已完成的 90% |
162
163
  | 2026-10-13 | REV-17.8 | 三路 review 门禁退回后的修复授权:M1=非 legion 派发者子记录持久化(persistChild 改 findChild 守卫式 addChild+updateChild,补集成测试);M3=package.json 四脚本加 `-u PI_SUBAGENT_UTILITY_CHILD`;测试缺口补:跨通道同名续接(subagent 先派 scout-0 后 vexillatio 应派 scout-1)与超时行为(不 autoSettle 场景);契约自身修正:§2.2 步骤 4 措辞(child 走 addChild 惯例/root 走 createRootLegion)、rpc-supervisor/types.ts 追认、§3 新增 package.json。四个 minor 采纳:abort signal 贯通、strip 列表常量化、verify 字段报错、child-mode 越授权改动追认;其余 minor(F5 重构收敛等)记档不阻塞 | 四路 reviewer 独立印证 3 major(M1 探针实证 children:[]、M2 部署惰性、M3 32 假失败实测);采纳 worker-0/2/3 交叉发现 |
163
164
  | 2026-10-19 | REV-17.9 | §2.2 步骤 3 补中断返回语义:父轮结束时返回已收集结果 + `details.interrupted`,冻结头行保持逐字(#227 D4) | issue #227 缺陷 2(中断丢弃已收集结果,`index.ts:2416` / `:743-745` 两层丢弃);`docs/contracts/vexillatio-nonblocking-launch-v1.md` 冻结 |
165
+ | 2026-10-21 | REV-17.10 | §2.2 参数形状修订:`task` 改为可选,`tasks[]` 成为完整请求(两者同传时 `tasks[]` 生效、`task` 仅作父 run 标签),空请求由 `buildUtilityPlan` 拒绝。请求形状与 `subagent` 对齐并写进 playbook | 实测复现:模型照 `subagent({ tasks })` 形状调 vexillatio,pi 的 typebox 校验在 `execute` 之前就拒绝(`task: must have required properties task`),会话里 5 次调用中 2 次因此重试;集成测试直连 `tool.execute` 绕过 schema 校验,故补 schema 级断言 |