@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/CHANGELOG.md +14 -6
- package/README.md +218 -128
- package/docs/ARCHITECTURE.md +168 -132
- package/docs/COST-ACCOUNTING.md +116 -66
- package/docs/RELEASING.md +32 -32
- package/docs/SECURITY.md +125 -97
- package/docs/UX.md +158 -141
- package/package.json +2 -2
- package/skills/subagent/SKILL.md +142 -121
- package/src/agents.ts +282 -288
- package/src/backends/pi.ts +164 -94
- package/src/child-preflight.ts +166 -0
- package/src/config.ts +254 -252
- package/src/dispatch-preflight.ts +87 -0
- package/src/dispatch-routing.ts +56 -0
- package/src/extension.ts +368 -187
- package/src/format.ts +436 -365
- package/src/jev-router.ts +1036 -0
- package/src/orchestrator.ts +303 -312
- package/src/persistence.ts +643 -335
- package/src/policy.ts +562 -561
- package/src/process-lock.ts +730 -687
- package/src/protocol.ts +320 -290
- package/src/registry.ts +730 -632
- package/src/routing-policy.ts +268 -0
- package/src/routing-types.ts +217 -0
- package/src/runner.ts +1299 -850
- package/src/schema.ts +189 -189
- package/src/startup-check.ts +481 -0
- package/src/types.ts +208 -198
- package/src/usage.ts +316 -274
- package/src/context-policy.ts +0 -169
- package/src/model-policy.ts +0 -169
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`
|
|
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
|
|
12
|
-
| `review` | same as explore | No project-file writes |
|
|
13
|
-
| `general` |
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
tools,
|
|
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.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
###
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
###
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|