@cr1ms0n/pi-subagent 0.8.8 → 0.9.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/docs/RELEASING.md CHANGED
@@ -1,32 +1,32 @@
1
- # Releasing @cr1ms0n/pi-subagent
2
-
3
- This is an independent community fork of `@parke.dev/pi-subagent` by Luke Parke. Preserve the MIT license and copyright, and do not publish this fork under the upstream scope. The upstream monorepo's tag automation does not publish this package.
4
-
5
- ## Prepare
6
-
7
- 1. Update `package.json` and `CHANGELOG.md`; use an unpublished version.
8
- 2. Run strict type checking against the installed Pi version and offline fake-runner/extension tests. Do not run real provider calls without permission.
9
- 3. Run `npm pack --dry-run --json` and review the file list. Do not include authentication, user settings, machine-specific paths, local backups, session transcripts or generated test bundles.
10
- 4. Run `npm pack --ignore-scripts`, install the resulting tarball into an isolated prefix with `--ignore-scripts --legacy-peer-deps`, then repeat the offline checks against the packed source.
11
-
12
- ## Publish
13
-
14
- Use an existing npm login or a securely supplied environment-based credential. Never put tokens in command text, source files, or a committed `.npmrc`. Enable TLS verification.
15
-
16
- ```bash
17
- npm whoami --strict-ssl=true --registry=https://registry.npmjs.org/
18
- npm publish ./cr1ms0n-pi-subagent-<version>.tgz --access public --ignore-scripts --strict-ssl=true --registry=https://registry.npmjs.org/
19
- npm view @cr1ms0n/pi-subagent@<version> name version dist.integrity --strict-ssl=true --registry=https://registry.npmjs.org/
20
- ```
21
-
22
- A successful public release makes every file in the tarball public. Verify the package metadata and registry integrity before replacing an existing installation.
23
-
24
- ## Install and replace
25
-
26
- ```bash
27
- pi install npm:@cr1ms0n/pi-subagent@<version>
28
- ```
29
-
30
- Do not enable this package and `@parke.dev/pi-subagent` at the same time: both register the same tool names. Back up the old package selection, activate only the new package after verification, and keep the old source for rollback. Reload or restart Pi after changing packages. Configure `~/.pi/subagent.json` modelPolicy before spawning tasks.
31
-
32
- This fork uses the same config and persisted state paths as upstream. Switching packages is not a data migration. Upgrading must not overwrite user model choices or credentials.
1
+ # Releasing @cr1ms0n/pi-subagent
2
+
3
+ This is an independent community fork of `@parke.dev/pi-subagent` by Luke Parke. Preserve the MIT license and copyright, and do not publish this fork under the upstream scope. The upstream monorepo's tag automation does not publish this package.
4
+
5
+ ## Prepare
6
+
7
+ 1. Update `package.json` and `CHANGELOG.md`; use an unpublished version.
8
+ 2. Run strict type checking against the installed Pi version and offline fake-runner/extension tests. Do not run real provider calls without permission.
9
+ 3. Run `npm pack --dry-run --json` and review the file list. Do not include authentication, user settings, machine-specific paths, local backups, session transcripts or generated test bundles.
10
+ 4. Run `npm pack --ignore-scripts`, install the resulting tarball into an isolated prefix with `--ignore-scripts --legacy-peer-deps`, then repeat the offline checks against the packed source.
11
+
12
+ ## Publish
13
+
14
+ Use an existing npm login or a securely supplied environment-based credential. Never put tokens in command text, source files, or a committed `.npmrc`. Enable TLS verification.
15
+
16
+ ```bash
17
+ npm whoami --strict-ssl=true --registry=https://registry.npmjs.org/
18
+ npm publish ./cr1ms0n-pi-subagent-<version>.tgz --access public --ignore-scripts --strict-ssl=true --registry=https://registry.npmjs.org/
19
+ npm view @cr1ms0n/pi-subagent@<version> name version dist.integrity --strict-ssl=true --registry=https://registry.npmjs.org/
20
+ ```
21
+
22
+ A successful public release makes every file in the tarball public. Verify the package metadata and registry integrity before replacing an existing installation.
23
+
24
+ ## Install and replace
25
+
26
+ ```bash
27
+ pi install npm:@cr1ms0n/pi-subagent@<version>
28
+ ```
29
+
30
+ Do not enable this package and `@parke.dev/pi-subagent` at the same time: both register the same tool names. Back up the old package selection, activate only the new package after verification, and keep the old source for rollback. Reload or restart Pi after changing packages. Configure `~/.pi/subagent.json` `jevRouting` (candidate models plus the credential environment variable) before spawning tasks.
31
+
32
+ This fork uses the same config and persisted state paths as upstream. Switching packages is not a data migration. Upgrading must not overwrite user model choices or credentials.
package/docs/SECURITY.md CHANGED
@@ -1,97 +1,125 @@
1
- # Security model
2
-
3
- Pi packages run with full system access. This extension spawns child `pi`
4
- processes that inherit the parent environment (including provider credentials)
5
- and can use tools according to their capability profile.
6
-
7
- ## What subagents can do
8
-
9
- | Profile | Default tools | Writes? |
10
- |---------|---------------|---------|
11
- | `explore` | `read`, `grep`, `find`, `ls` (+ safe extras and allowlisted Pi context tools) | No project-file writes |
12
- | `review` | same as explore | No project-file writes |
13
- | `general` | inherited active tools (+ allowlisted Pi context tools) | Yes if `bash`/`edit`/`write` are active |
14
-
15
- Pi context tools are allowlisted by **target model**, not by parent session: a
16
- child receives `new_context`, `get_context_remaining`, `history`, or `notes`
17
- only when the toolkit config has `compaction.contextManagement: "remote"`, the
18
- child's target model exactly equals a `compaction.gatewayContextModels` entry,
19
- and the parent exposes the tool. Missing/unreadable/invalid/disabled toolkit
20
- configuration yields no context tools. Retry fallback attempts are re-filtered
21
- per attempt, so a disallowed fallback cannot inherit them.
22
-
23
- Parallel mode defaults to `explore` to avoid concurrent shared writes.
24
-
25
- ## Hard rules
26
-
27
- 1. **Read-only means no project-file mutation.** `bash` can rewrite the disk and is never part of
28
- an explore/review profile. Pi context-management tools (`new_context`,
29
- `get_context_remaining`, `history`, `notes`) are an explicit control-plane
30
- exception, but only for the exact target models allowlisted above: they may
31
- update continuity notes/window state but cannot access the project write
32
- tools, and they are rejected as unavailable for every other target.
33
- 2. **Parallel writers** require `isolation: "worktree"`, distinct `cwd` values,
34
- or an explicit `allow_shared_writes: true` opt-in.
35
- 3. **Depth is capped** (`maxDepth`, default 2). Nested children at the ceiling do
36
- not re-register the subagent tool. Depth is scheduling metadata — `bash` or an
37
- env-scrubbing wrapper can still invoke `pi` directly, so treat it as an
38
- accidental-recursion guard, not a security boundary.
39
- **Spawn allowlists** (`spawns:` in agent frontmatter, env `PI_SUBAGENT_SPAWNS`)
40
- refine that same guard: a child may be limited to named personas, or to none
41
- (tool not registered). Like depth, this is not a sandbox — children can still
42
- shell out to `pi`.
43
- 4. **Process caps** limit concurrency both per parent session (`maxActiveProcesses`)
44
- and machine-wide (`maxGlobalActive`, default 16).
45
- 5. **Transcripts** under `~/.pi/subagent-sessions` may contain task content, tool
46
- output, and secrets that appeared in context. Protect that directory. Task text
47
- is delivered via stdin (not argv) so it stays out of `ps` listings, but it is
48
- still written into the child session log.
49
- 6. **Background permission prompts** are limited because children run headless
50
- (RPC mode). Extension UI dialogs raised inside a child are auto-cancelled so
51
- they can never hang a run — which also means a child can never obtain
52
- interactive consent. Prefer restricted tools for async/background runs.
53
- 11. **Steering messages** (`action: "steer"` and the overlay `s` key) inject text
54
- into a running child's conversation with user-level authority. Anything that
55
- can call the subagent tool can steer any live run in the same session.
56
- 7. **Process cleanup.** On POSIX, children run in their own process group so tree
57
- kills work for ordinary descendants. Parent (re)start reaps orphans recorded
58
- under `~/.pi/subagent-locks/runs/` so resume cannot race a still-alive writer.
59
- Grandchildren that call `setsid()` can still escape a simple process-group kill.
60
- 8. **Resume exclusivity.** Direct resume takes a durable per-session file lock;
61
- concurrent parents cannot append to the same child session.
62
- 9. **Profiles are tool-selection policy, not a sandbox.** Children inherit
63
- `$HOME`, SSH/cloud credentials, network access, and the parent filesystem.
64
- Git worktrees only isolate the checkout. For untrusted tasks, use an outer
65
- container/cgroup/network policy.
66
- 10. **`max_cost` is accounting, not a hard provider gate.** Usage arrives after a
67
- turn; orphans may spend money the ledger never sees. Combine with provider
68
- account budgets for hard spend limits.
69
-
70
- ## Trust and project cwd
71
-
72
- If `cwd` points outside the parent project, the child inherits whatever local
73
- project config/trust applies to that path. Treat external `cwd` as elevated risk
74
- and prefer read-only profiles when exploring third-party trees.
75
-
76
- ## Output artifacts
77
-
78
- `output` files are written by the child. Resolve paths carefully and reject
79
- duplicate output paths across parallel workers.
80
-
81
- ## Named agent files
82
-
83
- Agent files (`.pi/agents/`, `.agents/agents/`, global agent dir) inject their
84
- body into the child's system prompt and set its model/tools/budgets. A
85
- project-level agent file shapes subagent behavior the same way project
86
- extensions and skills do review them like code when working in untrusted
87
- repositories. Mitigations: capability profiles still fail closed (an agent
88
- cannot grant write tools under `explore`/`review`), symlinked agent files are
89
- skipped, names are validated against traversal characters, and files over
90
- 64KB are ignored.
91
-
92
- ## Machine-wide state
93
-
94
- `~/.pi/subagent-locks/` holds session locks, global concurrency slots, and run
95
- process identity records. It is per-user (under `$HOME`) and must not be shared
96
- across untrusted users/containers without care — a compromised client could
97
- interfere with lock reclaim on the same account.
1
+ # Security model
2
+
3
+ Pi packages run with full system access. This extension spawns child `pi`
4
+ processes that inherit the parent environment (including provider credentials)
5
+ and can use tools according to their capability profile.
6
+
7
+ ## What subagents can do
8
+
9
+ | Profile | Default tools | Writes? |
10
+ |---------|---------------|---------|
11
+ | `explore` | `read`, `grep`, `find`, `ls` (+ safe extras and Pi context tools) | No project-file writes |
12
+ | `review` | same as explore | No project-file writes |
13
+ | `general` | Jev-chosen subset of the full available locally permitted catalog (+ Pi context tools) | Yes if the selected tools include `bash`/`edit`/`write` |
14
+
15
+ Parallel mode defaults to `explore` to avoid concurrent shared writes.
16
+
17
+ ## Hard rules
18
+
19
+ 1. **Read-only means no project-file mutation.** `bash` can rewrite the disk and is never part of
20
+ an explore/review profile. Pi context-management tools (`new_context`,
21
+ `get_context_remaining`, `history`, `notes`) are an explicit control-plane
22
+ exception: they may update continuity notes/window state but cannot access
23
+ the project write tools. The finalized tools reach the child as Pi's `--tools`
24
+ allowlist (`--no-tools` when empty), and the selector's answer is re-validated
25
+ locally: unknown, unavailable or unsafe choices cannot launch broader
26
+ capability, and an empty selection never becomes "all tools". Pi 0.86.0 is the
27
+ verified baseline for built-in, extension and late-registered tool enforcement;
28
+ a host that cannot honor the allowlist is refused rather than silently weakened.
29
+ Before the real task prompt, a package-local startup check verifies the routing
30
+ bootstrap command source and has the child acknowledge the exact selected model
31
+ and tool names; a mismatch aborts as a capability diagnostic and is never fixed
32
+ by widening tools, switching models or approving project trust.
33
+ 2. **Parallel writers** require `isolation: "worktree"`, distinct `cwd` values,
34
+ or an explicit `allow_shared_writes: true` opt-in.
35
+ 3. **Depth is capped** (`maxDepth`, default 2). Nested children at the ceiling do
36
+ not re-register the subagent tool. Depth is scheduling metadata — `bash` or an
37
+ env-scrubbing wrapper can still invoke `pi` directly, so treat it as an
38
+ accidental-recursion guard, not a security boundary.
39
+ **Spawn allowlists** (`spawns:` in agent frontmatter, env `PI_SUBAGENT_SPAWNS`)
40
+ refine that same guard: a child may be limited to named personas, or to none
41
+ (tool not registered). Like depth, this is not a sandbox — children can still
42
+ shell out to `pi`.
43
+ 4. **Process caps** limit concurrency both per parent session (`maxActiveProcesses`)
44
+ and machine-wide (`maxGlobalActive`, default 16).
45
+ 5. **Transcripts** under `~/.pi/subagent-sessions` may contain task content, tool
46
+ output, and secrets that appeared in context. Protect that directory. Task text
47
+ is delivered via stdin (not argv) so it stays out of `ps` listings, but it is
48
+ still written into the child session log.
49
+ 6. **Background permission prompts** are limited because children run headless
50
+ (RPC mode). Extension UI dialogs raised inside a child are auto-cancelled so
51
+ they can never hang a run — which also means a child can never obtain
52
+ interactive consent. Prefer restricted tools for async/background runs.
53
+ 11. **Steering messages** (`action: "steer"` and the overlay `s` key) inject text
54
+ into a running child's conversation with user-level authority. Anything that
55
+ can call the subagent tool can steer any live run in the same session.
56
+ 7. **Process cleanup.** On POSIX, children run in their own process group so tree
57
+ kills work for ordinary descendants. Parent (re)start reaps orphans recorded
58
+ under `~/.pi/subagent-locks/runs/` so resume cannot race a still-alive writer.
59
+ Grandchildren that call `setsid()` can still escape a simple process-group kill.
60
+ 8. **Resume exclusivity.** Direct resume takes a durable per-session file lock;
61
+ concurrent parents cannot append to the same child session.
62
+ 9. **Profiles are tool-selection policy, not a sandbox.** Children inherit
63
+ `$HOME`, SSH/cloud credentials, network access, and the parent filesystem.
64
+ Git worktrees only isolate the checkout. For untrusted tasks, use an outer
65
+ container/cgroup/network policy.
66
+ 10. **`max_cost` is accounting, not a hard provider gate.** Usage arrives after a
67
+ turn; orphans may spend money the ledger never sees. It caps provider-reported
68
+ execution cost only: TypeSafe reports routing tokens, not currency, so selector
69
+ cost is unreported and outside `max_cost`. Combine with provider account
70
+ budgets for hard spend limits.
71
+
72
+ ## Routing disclosure and credentials
73
+
74
+ Jev routing sends a minimal projection to TypeSafe: the current delegated task
75
+ text, the configured candidate model IDs and your per-model descriptions,
76
+ eligible candidate tool names and descriptions, and necessary constraints
77
+ (profile, requested thinking, whether structured output is needed). It does not
78
+ upload repository files, conversation history, full system prompts, persona text
79
+ or tool parameter schemas, and does not read them in the background. Resume, fork
80
+ and synthesis select from the new task instruction rather than the assembled
81
+ transcript. Task text and model descriptions are user content and can themselves
82
+ contain secrets; there is no guaranteed redaction.
83
+
84
+ The TypeSafe credential is read from the environment variable named by
85
+ `jevRouting.apiKeyEnv` (default `TYPESAFE_API_KEY`) and sent only as an
86
+ `Authorization` header to the fixed official HTTPS endpoint, with redirects
87
+ disabled. It is never stored in the config file, prompts, argv, logs or results.
88
+ Rotate any credential that has been pasted into a transcript or shared in
89
+ conversation.
90
+
91
+ New extension-managed dispatch is Pi-only. A `backend: "codex"` or
92
+ `backend: "claude"` new task is rejected before any selector or provider work,
93
+ including a backend inherited from agent frontmatter, rather than silently
94
+ switched to Pi. Existing native-backend runs stay manageable.
95
+
96
+ ## Trust and project cwd
97
+
98
+ If `cwd` points outside the parent project, the child inherits whatever local
99
+ project config/trust applies to that path. Treat external `cwd` as elevated risk
100
+ and prefer read-only profiles when exploring third-party trees.
101
+
102
+ ## Output artifacts
103
+
104
+ `output` files are written by the child. Resolve paths carefully and reject
105
+ duplicate output paths across parallel workers.
106
+
107
+ ## Named agent files
108
+
109
+ Agent files (`.pi/agents/`, `.agents/agents/`, global agent dir) inject their
110
+ body into the child's system prompt and set persona, thinking and budget
111
+ defaults. Model and tool selection come from Jev routing; a legacy
112
+ `model`/`fallback_models` in frontmatter is ignored. A
113
+ project-level agent file shapes subagent behavior the same way project
114
+ extensions and skills do — review them like code when working in untrusted
115
+ repositories. Mitigations: capability profiles still fail closed (an agent
116
+ cannot grant write tools under `explore`/`review`), symlinked agent files are
117
+ skipped, names are validated against traversal characters, and files over
118
+ 64KB are ignored.
119
+
120
+ ## Machine-wide state
121
+
122
+ `~/.pi/subagent-locks/` holds session locks, global concurrency slots, and run
123
+ process identity records. It is per-user (under `$HOME`) and must not be shared
124
+ across untrusted users/containers without care — a compromised client could
125
+ interfere with lock reclaim on the same account.
package/docs/UX.md CHANGED
@@ -1,141 +1,158 @@
1
- # pi-subagent UX
2
-
3
- ## Overview
4
- The standalone pi-subagent provides rich TUI support for monitoring, inspecting, and interacting with isolated subagent runs (single and parallel modes): inline streaming blocks, a terse footer, an ambient widget for background runs, batched completion notifications, mid-run steering, a worktree apply loop, and the `/subagents` inspector. UI logic is kept independent from `runner`/`registry` via small structural adapters (`SubagentAdapter`).
5
-
6
- ## Design principles
7
-
8
- 1. **Pi's tool shell owns state signaling.** The Box wrapper paints
9
- `toolPendingBg` / `toolSuccessBg` / `toolErrorBg`, so inline blocks do not
10
- repeat state words or draw their own success/error framing.
11
- 2. **Cost is a per-run attribute, not a competing ledger.** Dollar cost appears
12
- inside the run's own result block; the parent/children/combined ledger is
13
- available on demand via `/subagent-cost`, in `status` tool output, and in the
14
- `/subagents` overlay header. The footer never shows cost.
15
- 3. **Fixed-height, mutate-in-place progress.** Streaming blocks keep a stable
16
- shape (stats line + one `⎿ activity` line; parallel adds one line per task)
17
- and the same component identity is reused across partial renders.
18
- 4. **Trailing-edge streaming flush.** Structural updates (state transition, new
19
- session id, billed turn) emit immediately; live-text bursts coalesce with a
20
- deferred flush so the last update of a burst always lands.
21
-
22
- ## Surfaces
23
-
24
- ### Inline tool block (foreground runs)
25
- - `renderCall` is exactly one line: `subagent <task preview>` (or
26
- `N parallel tasks — first task…`, `wait a1b2c3d4`, `… · background`).
27
- - `renderResult` while streaming (fixed shape, spinner animates via wall-clock
28
- frame; Pi's working indicator drives repaints):
29
- ```
30
- ⠹ ↻3 · 12.4k tok · 8s
31
- ⎿ reading src/auth/middleware.ts…
32
- ```
33
- - Terminal single run:
34
- ```
35
- ↻8 · 33.8k tok · $0.012 · 12s
36
- ⎿ Found 5 middleware call sites…
37
- → /tmp/report.md
38
- ```
39
- - Parallel: one line per task with a themed state glyph
40
- (`◌ queued · ⠹ running · ✓ done · ✗ failed · ◐ partial · − cancelled · ◷ timeout`),
41
- per-task stats, and a one-line tail (live activity or first output line).
42
- - Expanded (Ctrl+O / `app.tools.expand`): full task output capped with a dim
43
- `… +N lines` trailer pointing at the artifact/child session.
44
- - Durations freeze at `endedAt`; running durations tick at render time.
45
- - Reliability annotations render inline: `[attempt 2]` during a retry,
46
- `[stalled 2m]` while the stall watchdog is flagging silence, and
47
- `◐ wrapped up` on budget-stopped runs that concluded gracefully.
48
-
49
- ### Footer status
50
- Terse and actionable only: `⚙ 2 running · 1 ready · /subagents`. Cleared when
51
- nothing is running or ready. No cost Pi's footer already shows session cost.
52
-
53
- ### Ambient widget (background runs only)
54
- An above-editor widget renders while `async: true` runs are live foreground
55
- runs already render inline as the tool result, so they never appear here
56
- (avoids double-render):
57
-
58
- ```
59
- Subagents
60
- ├─ ⠼ Audit deps · ↻4 · 18k tok · 41s
61
- │ ⎿ checking license headers…
62
- └─ ◌ License scan · 12s
63
- ```
64
-
65
- Cleared when the last background run settles. Spinner and elapsed animate on
66
- a 250ms interval that exists only while background runs are live.
67
-
68
- ### Completion notifications (background runs only)
69
- When an async run reaches a terminal state, a `steer` message (custom type
70
- `subagent-completion`) is queued for the parent LLM before its next LLM call,
71
- so it can react without polling. The human sees a themed compact box (state
72
- glyph, label, stats, one-line preview, artifact pointers); the LLM sees plain
73
- text with run ids and a `wait { id }` pointer.
74
-
75
- - Successes within a short window batch into one message (no fanout spam);
76
- failures bypass batching and flush immediately, carrying held successes.
77
- - A `wait` that already delivered the run suppresses the redundant
78
- notification (delivered-state is re-checked at flush time).
79
-
80
- ### `/subagents` overlay
81
- - Header: title + running/ready counters + full usage ledger + rule.
82
- - List: two lines per run — glyph/id/state/stats, then the task preview.
83
- Selection cursor `▶`, animated spinner for live runs.
84
- - Detail: run stats, summary, then per-task sections (glyph, label,
85
- model/profile/thinking, usage, pointers, transcript/final output/errors),
86
- scrollable with ↑↓/j/k and PageUp/PageDown.
87
- - Actions: `c` cancel, `s` steer (prompts for a message, injects it into the
88
- running child), `d` dismiss, `r` resume, `o` output pointers, `a` apply a
89
- finished run's changed worktree into the main checkout (confirm dialog),
90
- `x` discard worktree + branch (confirm dialog), Enter drill-down,
91
- Esc/b back, Esc/q close.
92
- - Live transcript (`t` on a **running** run's detail): tails the child's
93
- session file (`sessionDir/<…sessionId…>.jsonl`) on a 500ms poll while the
94
- pane is visible compact role/tool lines, auto-follow unless you scroll
95
- up (which pauses follow). No RPC reads; hidden/finished runs never poll.
96
- Missing file shows “waiting for child session…”. `s` steering still works
97
- from the same pane so observe steer stays on one surface.
98
-
99
- ### `/subagent-cost`
100
- Prints the root/subagents/combined ledger once, on demand.
101
-
102
- ### Mid-run steering
103
- Children run in Pi RPC mode, so their stdin stays open as a command channel.
104
- `action: "steer"` (or `s` in the overlay) queues a message that is delivered
105
- after the child's current assistant turn, before its next LLM call course
106
- correction without cancel + retry. Parallel runs steer one task via `index`.
107
-
108
- ### Worktree loop
109
- Finished runs with changed worktrees support `diff` / `apply` / `discard`
110
- actions (tool) and `a` / `x` keys (overlay). `apply` lands the worktree's
111
- combined patch (committed + uncommitted + untracked vs base) onto the main
112
- checkout as **uncommitted working-tree changes** via `git apply --3way`; it
113
- never commits and never deletes the worktree. `discard` is the explicit
114
- cleanup step and always confirms first.
115
-
116
- ### Parallel fan-in
117
- `synthesis: "<instruction>"` on a parallel run spawns one read-only child
118
- after all tasks settle that folds their outputs into a single brief, delivered
119
- first in the result. Synthesis failures degrade silently to raw results.
120
-
121
- ## States
122
- - **Queued/Running**: spinner + live stats + activity tail from live text.
123
- - **Completed/Partial/Failed/Cancelled/Timeout/Lost**: state glyph, frozen
124
- duration, usage summary, output pointers; failures show the error message.
125
- - **Delivered vs Undelivered**: footer/overlay track pending delivery.
126
- - **Notification**: one per terminal transition to avoid spam.
127
-
128
- ## Integration Notes
129
- - Extension wires via `ctx.ui.custom((tui, theme, kb, done) => createSubagentsOverlay(tui, theme, adapter, done), {overlay: true})`.
130
- - Adapter provides getActiveRuns/getCompletedRuns/cancelRun etc. without tight coupling.
131
- - Inline renderers reuse `context.lastComponent` (a `LineBlock`) so the row keeps
132
- a stable component identity across partial renders.
133
- - Streamed tool updates separate LLM-facing `content` (compact status string)
134
- from render-facing `details` (state, usage, live-text tail, run timing).
135
- - Tests combine pure UI models with a headless Pi extension harness: format
136
- helpers, block layouts (collapsed/streaming/terminal/parallel), navigation,
137
- truncation (ANSI-safe via `visibleWidth`), ready state, lifecycle/disposal.
138
- - Follows Pi TUI guidelines (render(width), handleInput, invalidate,
139
- requestRender, dispose). The overlay owns a single animation interval.
140
-
141
- See ARCHITECTURE.md for ownership boundaries. All rendering respects terminal width and ANSI safety.
1
+ # pi-subagent UX
2
+
3
+ ## Overview
4
+ The standalone pi-subagent provides rich TUI support for monitoring, inspecting, and interacting with isolated subagent runs (single and parallel modes): inline streaming blocks, a terse footer, an ambient widget for background runs, batched completion notifications, mid-run steering, a worktree apply loop, and the `/subagents` inspector. UI logic is kept independent from `runner`/`registry` via small structural adapters (`SubagentAdapter`).
5
+
6
+ ## Design principles
7
+
8
+ 1. **Pi's tool shell owns state signaling.** The Box wrapper paints
9
+ `toolPendingBg` / `toolSuccessBg` / `toolErrorBg`, so inline blocks do not
10
+ repeat state words or draw their own success/error framing.
11
+ 2. **Cost is a per-run attribute, not a competing ledger.** Dollar cost appears
12
+ inside the run's own result block; the parent/children/combined ledger is
13
+ available on demand via `/subagent-cost`, in `status` tool output, and in the
14
+ `/subagents` overlay header. The footer never shows cost.
15
+ 3. **Fixed-height, mutate-in-place progress.** Streaming blocks keep a stable
16
+ shape (stats line + one `⎿ activity` line; parallel adds one line per task)
17
+ and the same component identity is reused across partial renders.
18
+ 4. **Trailing-edge streaming flush.** Structural updates (state transition, new
19
+ session id, billed turn) emit immediately; live-text bursts coalesce with a
20
+ deferred flush so the last update of a burst always lands.
21
+
22
+ ## Surfaces
23
+
24
+ ### Inline tool block (foreground runs)
25
+ - `renderCall` is exactly one line: `subagent <task preview>` (or
26
+ `N parallel tasks — first task…`, `wait a1b2c3d4`, `… · background`).
27
+ - `renderResult` while streaming (fixed shape, spinner animates via wall-clock
28
+ frame; Pi's working indicator drives repaints):
29
+ ```
30
+ ⠹ ↻3 · 12.4k tok · 8s
31
+ ⎿ reading src/auth/middleware.ts…
32
+ ```
33
+ - Terminal single run:
34
+ ```
35
+ ↻8 · 33.8k tok · $0.012 · 12s
36
+ ⎿ Found 5 middleware call sites…
37
+ → /tmp/report.md
38
+ ```
39
+ - Parallel: one line per task with a themed state glyph
40
+ (`◌ queued · ⠹ running · ✓ done · ✗ failed · ◐ partial · − cancelled · ◷ timeout`),
41
+ per-task stats, and a one-line tail (live activity or first output line).
42
+ - Expanded (Ctrl+O / `app.tools.expand`): full task output capped with a dim
43
+ `… +N lines` trailer pointing at the artifact/child session.
44
+ - Expanded detail adds one bounded route line for Jev-routed runs: selected
45
+ execution model, selected tools (plus locally added control-plane tools),
46
+ selector version, confidence, outcome and selection latency. Legacy runs
47
+ simply have no route line.
48
+ - Durations freeze at `endedAt`; running durations tick at render time.
49
+ - Reliability annotations render inline: `[attempt 2]` during a same-model
50
+ retry, `[stalled 2m]` while the stall watchdog is flagging silence, and
51
+ `◐ wrapped up` on budget-stopped runs that concluded gracefully.
52
+
53
+ ### Footer status
54
+ Terse and actionable only: `⚙ 2 running · 1 ready · /subagents`. Cleared when
55
+ nothing is running or ready. No cost Pi's footer already shows session cost.
56
+
57
+ ### Ambient widget (background runs only)
58
+ An above-editor widget renders while `async: true` runs are live — foreground
59
+ runs already render inline as the tool result, so they never appear here
60
+ (avoids double-render):
61
+
62
+ ```
63
+ ● Subagents
64
+ ├─ ⠼ Audit deps · ↻4 · 18k tok · 41s
65
+ │ ⎿ checking license headers…
66
+ └─ License scan · 12s
67
+ ```
68
+
69
+ Cleared when the last background run settles. Spinner and elapsed animate on
70
+ a 250ms interval that exists only while background runs are live.
71
+
72
+ ### Completion notifications (background runs only)
73
+ When an async run reaches a terminal state, a `steer` message (custom type
74
+ `subagent-completion`) is queued for the parent LLM before its next LLM call,
75
+ so it can react without polling. The human sees a themed compact box (state
76
+ glyph, label, stats, one-line preview, artifact pointers); the LLM sees plain
77
+ text with run ids and a `wait { id }` pointer.
78
+
79
+ - Successes within a short window batch into one message (no fanout spam);
80
+ failures bypass batching and flush immediately, carrying held successes.
81
+ - A `wait` that already delivered the run suppresses the redundant
82
+ notification (delivered-state is re-checked at flush time).
83
+
84
+ ### `/subagents` overlay
85
+ - Header: title + running/ready counters + full usage ledger + rule.
86
+ - List: two lines per run — glyph/id/state/stats, then the task preview.
87
+ Selection cursor `▶`, animated spinner for live runs.
88
+ - Detail: run stats, summary, then per-task sections (glyph, label,
89
+ model/selector route/profile/thinking, usage, pointers, transcript/final
90
+ output/errors), scrollable with ↑↓/j/k and PageUp/PageDown.
91
+ - Actions: `c` cancel, `s` steer (prompts for a message, injects it into the
92
+ running child), `d` dismiss, `r` resume, `o` output pointers, `a` apply a
93
+ finished run's changed worktree into the main checkout (confirm dialog),
94
+ `x` discard worktree + branch (confirm dialog), Enter drill-down,
95
+ Esc/b back, Esc/q close.
96
+ - Live transcript (`t` on a **running** run's detail): tails the child's
97
+ session file (`sessionDir/<…sessionId…>.jsonl`) on a 500ms poll while the
98
+ pane is visible — compact role/tool lines, auto-follow unless you scroll
99
+ up (which pauses follow). No RPC reads; hidden/finished runs never poll.
100
+ Missing file shows “waiting for child session…”. `s` steering still works
101
+ from the same pane so observe → steer stays on one surface.
102
+
103
+ ### `/subagent-cost`
104
+ Prints the root/subagents/routing/combined ledger once, on demand. Routing
105
+ tokens appear as their own category and their currency as unreported; the known
106
+ dollar totals exclude that unreported selector spend.
107
+
108
+ ### Mid-run steering
109
+ Children run in Pi RPC mode, so their stdin stays open as a command channel.
110
+ `action: "steer"` (or `s` in the overlay) queues a message that is delivered
111
+ after the child's current assistant turn, before its next LLM call — course
112
+ correction without cancel + retry. Parallel runs steer one task via `index`.
113
+
114
+ ### Worktree loop
115
+ Finished runs with changed worktrees support `diff` / `apply` / `discard`
116
+ actions (tool) and `a` / `x` keys (overlay). `apply` lands the worktree's
117
+ combined patch (committed + uncommitted + untracked vs base) onto the main
118
+ checkout as **uncommitted working-tree changes** via `git apply --3way`; it
119
+ never commits and never deletes the worktree. `discard` is the explicit
120
+ cleanup step and always confirms first.
121
+
122
+ ### Parallel fan-in
123
+ `synthesis: "<instruction>"` on a parallel run asks for one read-only child
124
+ after all tasks settle that folds their outputs into a single brief, delivered
125
+ first in the result. It is best effort and deferred: its route is selected only
126
+ when aggregation is actually needed after the workers finish, so it never blocks
127
+ worker launch. A selector or child failure keeps the raw worker outputs, their
128
+ usage and a bounded `Optional synthesis blocked: …` diagnostic instead of
129
+ discarding or re-routing them.
130
+
131
+ ### Plan results (tool output, not TUI)
132
+ `action:"plan"` returns the resolved model/tools and the selector usage it
133
+ incurred, and states that a later dispatch selects again. It starts no child and
134
+ creates no run entry, so plan never adds an overlay row or ambient widget. A
135
+ plan whose optional synthesis selection fails still returns the valid worker plan
136
+ and labels only that synthetic stage blocked with its diagnostic.
137
+
138
+ ## States
139
+ - **Queued/Running**: spinner + live stats + activity tail from live text.
140
+ - **Completed/Partial/Failed/Cancelled/Timeout/Lost**: state glyph, frozen
141
+ duration, usage summary, output pointers; failures show the error message.
142
+ - **Delivered vs Undelivered**: footer/overlay track pending delivery.
143
+ - **Notification**: one per terminal transition to avoid spam.
144
+
145
+ ## Integration Notes
146
+ - Extension wires via `ctx.ui.custom((tui, theme, kb, done) => createSubagentsOverlay(tui, theme, adapter, done), {overlay: true})`.
147
+ - Adapter provides getActiveRuns/getCompletedRuns/cancelRun etc. without tight coupling.
148
+ - Inline renderers reuse `context.lastComponent` (a `LineBlock`) so the row keeps
149
+ a stable component identity across partial renders.
150
+ - Streamed tool updates separate LLM-facing `content` (compact status string)
151
+ from render-facing `details` (state, usage, live-text tail, run timing).
152
+ - Tests combine pure UI models with a headless Pi extension harness: format
153
+ helpers, block layouts (collapsed/streaming/terminal/parallel), navigation,
154
+ truncation (ANSI-safe via `visibleWidth`), ready state, lifecycle/disposal.
155
+ - Follows Pi TUI guidelines (render(width), handleInput, invalidate,
156
+ requestRender, dispose). The overlay owns a single animation interval.
157
+
158
+ See ARCHITECTURE.md for ownership boundaries. All rendering respects terminal width and ANSI safety.