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.
- package/CHANGELOG.md +263 -2
- package/CONTRIBUTING.md +4 -17
- package/README.md +29 -22
- package/agents/worker.md +1 -1
- package/docs/contracts/display-liveness-v1.md +373 -0
- package/docs/contracts/drop-non-pi-kinds-v4.md +198 -0
- package/docs/contracts/esc-abort-v1.md +4 -4
- package/docs/contracts/esc-no-retire-v3.md +2 -2
- package/docs/contracts/launch-admission-queue-v1.md +839 -0
- package/docs/contracts/status-panel-v1.md +174 -0
- package/docs/inprocess-supervisor.md +37 -4
- package/docs/legion-v2-contract.md +18 -2
- package/docs/performance-2026-10-19.md +199 -0
- package/docs/spawn-mode-visibility-v1.md +206 -0
- package/docs/vexillatio-contract.md +3 -1
- package/index.ts +633 -84
- package/package.json +4 -2
- package/skills/pi-legion/SKILL.md +2 -3
- package/src/agents/agents.ts +17 -10
- package/src/agents/overrides.ts +0 -1
- package/src/agents/presets.ts +14 -40
- package/src/agents/settings.ts +26 -77
- package/src/agents/step-model.ts +7 -22
- package/src/api.ts +1 -35
- package/src/extension/event-bus.ts +80 -8
- package/src/extension/legion-commands.ts +43 -17
- package/src/extension/legion-doctor.ts +1 -1
- package/src/extension/legion-stop.ts +39 -13
- package/src/extension/legion-tools.ts +21 -4
- package/src/extension/liveness.ts +200 -0
- package/src/extension/notify.ts +6 -6
- package/src/extension/playbook.ts +6 -6
- package/src/extension/recycle.ts +6 -2
- package/src/extension/runtime.ts +422 -18
- package/src/extension/slash.ts +0 -21
- package/src/extension/spawn-render.ts +183 -0
- package/src/extension/summary.ts +1 -1
- package/src/legion/db.ts +165 -1
- package/src/legion/events.ts +81 -16
- package/src/legion/nodes.ts +13 -2
- package/src/legion/rebuild.ts +7 -1
- package/src/legion/usage.ts +58 -4
- package/src/runs/orchestrator.ts +1165 -62
- package/src/runs/worktree.ts +190 -21
- package/src/shared/ignored-kind.ts +57 -0
- package/src/shared/progress.ts +11 -0
- package/src/shared/session.ts +199 -19
- package/src/shared/types.ts +50 -66
- package/src/supervisor/admission-queue.ts +516 -0
- package/src/supervisor/inprocess-supervisor.ts +108 -33
- package/src/supervisor/proc-memory.ts +67 -0
- package/src/supervisor/routing-supervisor.ts +58 -1
- package/src/tui/live-overlay.ts +14 -0
- package/src/tui/session-messages-file.ts +100 -0
- package/src/tui/status.ts +103 -21
- package/src/tui/tree-view.ts +36 -3
- package/src/tui/viz/layout.ts +54 -3
- package/src/tui/viz/model.ts +67 -14
- package/src/tui/viz/palette.ts +11 -0
- package/src/tui/viz/panels.ts +222 -8
- package/src/tui/viz/railway.ts +28 -25
- package/src/tui/viz/view.ts +110 -23
- package/src/backends/legacy/client.ts +0 -638
- package/src/backends/legacy/cursor-chat.ts +0 -348
- package/src/backends/legacy/runner.ts +0 -101
- package/src/backends/legacy/types.ts +0 -138
- 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
|
-
|
|
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 级断言 |
|