@kushalkhemka/opensec-pi-subagents 0.20.2 → 0.20.3
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 +69 -0
- package/README.md +125 -7
- package/dist/agent-manager.d.ts +119 -9
- package/dist/agent-manager.js +263 -56
- package/dist/agent-runner.d.ts +11 -0
- package/dist/agent-runner.js +96 -22
- package/dist/completion-outbox.d.ts +65 -0
- package/dist/completion-outbox.js +108 -0
- package/dist/cross-extension-rpc.js +8 -0
- package/dist/env.js +3 -2
- package/dist/exec-tree.js +11 -2
- package/dist/index.d.ts +7 -1
- package/dist/index.js +827 -252
- package/dist/invocation-config.d.ts +17 -4
- package/dist/invocation-config.js +29 -2
- package/dist/nested-tools.js +10 -4
- package/dist/schedule.d.ts +1 -1
- package/dist/schedule.js +10 -4
- package/dist/session-handoff.d.ts +102 -0
- package/dist/session-handoff.js +162 -0
- package/dist/settings.d.ts +26 -0
- package/dist/settings.js +10 -0
- package/dist/types.d.ts +24 -2
- package/dist/ui/agent-widget.d.ts +4 -0
- package/dist/ui/agent-widget.js +5 -0
- package/dist/ui/conversation-viewer.d.ts +104 -3
- package/dist/ui/conversation-viewer.js +425 -52
- package/dist/ui/fleet-list.d.ts +30 -9
- package/dist/ui/fleet-list.js +37 -12
- package/dist/workflow/collisions.d.ts +3 -1
- package/dist/workflow/collisions.js +4 -1
- package/dist/workflow/host.js +30 -7
- package/dist/workflow/journal.d.ts +16 -8
- package/dist/workflow/journal.js +12 -9
- package/dist/workflow/runtime.d.ts +7 -1
- package/dist/workflow/runtime.js +38 -8
- package/dist/workflow/task.d.ts +11 -1
- package/dist/workflow/task.js +13 -1
- package/dist/workflow/tool-description.d.ts +9 -1
- package/dist/workflow/tool-description.js +21 -1
- package/docs/rpc.md +10 -10
- package/docs/workflows.md +4 -4
- package/examples/agent-tool-description.md +1 -3
- package/package.json +16 -11
- package/src/agent-manager.ts +288 -55
- package/src/agent-runner.ts +100 -21
- package/src/completion-outbox.ts +121 -0
- package/src/cross-extension-rpc.ts +8 -0
- package/src/env.ts +3 -2
- package/src/exec-tree.ts +11 -2
- package/src/index.ts +810 -237
- package/src/invocation-config.ts +44 -4
- package/src/nested-tools.ts +10 -4
- package/src/schedule.ts +10 -4
- package/src/session-handoff.ts +188 -0
- package/src/settings.ts +34 -0
- package/src/types.ts +24 -2
- package/src/ui/agent-widget.ts +8 -0
- package/src/ui/conversation-viewer.ts +419 -35
- package/src/ui/fleet-list.ts +34 -11
- package/src/workflow/collisions.ts +4 -1
- package/src/workflow/host.ts +30 -7
- package/src/workflow/journal.ts +18 -9
- package/src/workflow/runtime.ts +50 -8
- package/src/workflow/task.ts +14 -0
- package/src/workflow/tool-description.ts +27 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,75 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.20.3] - 2026-10-16
|
|
11
|
+
|
|
12
|
+
Background agents survive a session switch, subagents run on the parent's model by default, and nested and workflow
|
|
13
|
+
agents stay resumable after a restart. Also ports upstream `@tintinweb/pi-subagents` 0.20.0, 0.21.0 and 0.22.0
|
|
14
|
+
(package-shipped agents and MCP for subagents are not ported).
|
|
15
|
+
|
|
16
|
+
> **Possibly breaking:**
|
|
17
|
+
> - **Subagents run on the parent's model and thinking level by default**; the `Agent` and nested tools no longer offer
|
|
18
|
+
> `model`/`thinking` unless `allowModelOverride` is on.
|
|
19
|
+
> - **Background agents keep running across `/new`, `/resume`, `/fork` and `/reload`** when their session is saved.
|
|
20
|
+
> - **Pi 1.1.0 or newer is required** (peer range `>=1.1.0`).
|
|
21
|
+
> - **Background completions are delivered at the parent's next boundary.** `smart`, the default join mode, no longer
|
|
22
|
+
> combines a turn's agents into one notification after waiting for all of them; `group` does that, per parent turn.
|
|
23
|
+
> A completion pending when you press `Esc` now starts a turn once the interrupted turn stops.
|
|
24
|
+
> - **An agent file's `isolated:` and `isolation:` win on every spawn path**, including RPC callers that pass their own
|
|
25
|
+
> value, and an `isolation: worktree` agent fails to start outside a git repository instead of running in place.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **Background agents keep running across a session switch.** `/new`, `/resume`, `/fork` and `/reload` no longer stop them when their session is saved: the next session takes over the running agents. An agent belongs to the session that started it: other sessions do not list or address it, and a result that lands while another session is open is written into its own session and delivered as the usual notification when you return there. Quitting still stops every agent and saves worktrees.
|
|
30
|
+
- **`ctrl+b` sends running foreground `Agent` calls to the background**, as in Claude Code; `b` in the conversation
|
|
31
|
+
viewer sends one ([tintinweb/pi-subagents#261](https://github.com/tintinweb/pi-subagents/pull/261)). The run keeps
|
|
32
|
+
going and its result arrives as a background notification. The key is configurable as `backgroundKey` in
|
|
33
|
+
`subagents.json` (`"off"` disables it).
|
|
34
|
+
- **Fullscreen conversation observer** ([tintinweb/pi-subagents#357](https://github.com/tintinweb/pi-subagents/pull/357)):
|
|
35
|
+
in Pi's fullscreen mode an agent opened from FleetView, `/agents` or the workflow inspector fills the terminal, with
|
|
36
|
+
mouse-wheel scrolling, a draggable scrollbar following Pi's `fullscreenScrollbar`, clickable footer controls and a
|
|
37
|
+
`Ctrl+End` jump-to-latest marker that counts new messages. Built on this fork's incremental viewer: without a JIT, a
|
|
38
|
+
frame takes about 20 µs and a scroll step about 50 µs at 500 messages, whatever the transcript's length.
|
|
39
|
+
- **Subagents load Pi's built-in codemode and tool search** (`builtin:codemode`, `builtin:tool-search`) as the main
|
|
40
|
+
session does ([tintinweb/pi-subagents#386](https://github.com/tintinweb/pi-subagents/pull/386)). Pi's built-in MCP
|
|
41
|
+
stays off for subagents, so no child starts its own `mcp.json` servers, unless an agent file names `builtin:mcp` in
|
|
42
|
+
`extensions:`.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **Subagents run on your model and thinking level.** By default every subagent runs on the parent's current model at the parent's current thinking level, on every spawn path: the `Agent` tool, nested delegation, workflow `agent()`, `@mentions`, scheduled jobs and cross-extension RPC. Per-call choices are ignored, and the `Agent` and nested tools no longer offer `model` or `thinking`, so the main model cannot pick cheaper models or lower effort on its own. An agent file's `model:` or `thinking:` still applies, and a resumed agent keeps what it ran with. The new `allowModelOverride` setting (`subagents.json` or `/agents → Settings → Model override`) restores the per-call overrides and the tool parameters.
|
|
47
|
+
- **Background completions reach a busy parent before its next model call**, after its current tool batch, through
|
|
48
|
+
Pi's steering queue instead of a follow-up held until the parent stopped
|
|
49
|
+
([tintinweb/pi-subagents#387](https://github.com/tintinweb/pi-subagents/pull/387)). Completions that finish together
|
|
50
|
+
arrive as one message and none waits for its siblings (`async` now means `smart`); an idle parent gets a turn about
|
|
51
|
+
200 ms after a completion; `group` batches by parent turn; `get_subagent_result` suppresses a notification any time
|
|
52
|
+
before it is sent ([tintinweb/pi-subagents#388](https://github.com/tintinweb/pi-subagents/pull/388)). Results that
|
|
53
|
+
land while another session is open, or that are still waiting when you switch away, are delivered the same way when
|
|
54
|
+
you return to their session.
|
|
55
|
+
- **An agent file's `isolated:` and `isolation:` hold on every spawn path**: direct and fallback `@handle` mentions,
|
|
56
|
+
cross-extension RPC and registry spawns. A caller's value applies only when the file leaves the field unset; the
|
|
57
|
+
widget's `worktree` tag shows for every worktree agent. Workflow `agent()` calls keep Claude Code's rule (only the
|
|
58
|
+
script's `isolation` decides), and an agent reopened from its session file keeps its placement (upstream 0.21.0).
|
|
59
|
+
- **Subagent tool scoping follows Pi's tool exposure**: only `direct`/`model-only` tools are activated, and
|
|
60
|
+
`defaultActive: false` tools only when named; `codemode`/`deferred` tools stay undeclared until codemode or
|
|
61
|
+
`tool_search` reaches them (upstream 0.21.0).
|
|
62
|
+
- Pi peer floor `>=1.1.0`; `typebox` is a `"*"` peer and `@sinclair/typebox` is no longer declared
|
|
63
|
+
([tintinweb/pi-subagents#384](https://github.com/tintinweb/pi-subagents/pull/384)).
|
|
64
|
+
- The conversation viewer's regular mode reuses cached widths and chrome rows: without a JIT a frame costs about
|
|
65
|
+
20 µs instead of about 2 ms.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **Nested agents and workflow children stay resumable after a restart**, `/resume`, `/fork` or `/reload`: their resume state is recorded in the parent session (`subagents:resume` entries), so a nested agent can be resumed by its owner and a workflow child with `agent({ resume })`. `resumeFromRunId` finds a run of the same session after a restart, and a child replayed from the journal can still be continued: the journal records which agent produced each answer.
|
|
70
|
+
- `SubagentWorkflow` honours an agent file's `isolated: true`
|
|
71
|
+
([tintinweb/pi-subagents#339](https://github.com/tintinweb/pi-subagents/pull/339)).
|
|
72
|
+
- FleetView ends with a blank line above Pi's footer
|
|
73
|
+
([tintinweb/pi-subagents#351](https://github.com/tintinweb/pi-subagents/issues/351)), and its arrow keys work under
|
|
74
|
+
any prompt editor that implements `getText`/`setText`
|
|
75
|
+
([tintinweb/pi-subagents#374](https://github.com/tintinweb/pi-subagents/issues/374)).
|
|
76
|
+
- The workflow stand-down recognises a lowercase `workflow` tool
|
|
77
|
+
([tintinweb/pi-subagents#283](https://github.com/tintinweb/pi-subagents/issues/283)).
|
|
78
|
+
|
|
10
79
|
## [0.20.2] - 2026-10-16
|
|
11
80
|
|
|
12
81
|
### Changed
|
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ Restart Pi or run `/reload` after installing. Update with `pi update --extension
|
|
|
32
32
|
|
|
33
33
|
### Requirements
|
|
34
34
|
|
|
35
|
-
- Pi
|
|
36
|
-
|
|
35
|
+
- Pi 1.1.0 or newer (peer range `>=1.1.0`, the test suite runs against Pi 1.1.0). `typebox` is a `"*"` peer that Pi
|
|
36
|
+
supplies; `@sinclair/typebox` is no longer used.
|
|
37
37
|
- Install only one Subagents package at a time: the public and private packages expose the same tools.
|
|
38
38
|
|
|
39
39
|
## Features
|
|
@@ -72,6 +72,61 @@ Agent({ subagent_type: "Explore", prompt: "Find authentication code", descriptio
|
|
|
72
72
|
| `/agents` | Inspect and manage agents: live conversations, settings, workflows, and scheduled jobs (`/agents → Scheduled jobs`). |
|
|
73
73
|
| `@<agent> <message>` | With agent mentions enabled, sends a message to that agent, e.g. `@explore check the RPC path`. A finished agent is resumed (see [Resuming agents](#resuming-agents)). |
|
|
74
74
|
|
|
75
|
+
### Background completions
|
|
76
|
+
|
|
77
|
+
When a background agent (or workflow) finishes, the main session is notified, as in Claude Code:
|
|
78
|
+
|
|
79
|
+
- A busy parent gets the notification after its current batch of tool calls, before its next model call; running tools
|
|
80
|
+
are not interrupted. Everything that finished during that batch arrives as one message.
|
|
81
|
+
- An idle parent gets a turn about 200 ms after a completion, together with anything else that finished in that window.
|
|
82
|
+
- If you interrupt the parent (`Esc`), what was waiting is kept and delivered once the interrupted turn has stopped,
|
|
83
|
+
starting a turn.
|
|
84
|
+
- Reading a result with `get_subagent_result` (or the RPC `consume`) before the notification is sent suppresses it.
|
|
85
|
+
- A result that lands while another session is open, or that is still waiting for its parent's boundary when you switch
|
|
86
|
+
away, is recorded in its own session and delivered the same way when you return there (see
|
|
87
|
+
[Switching sessions](#switching-sessions)).
|
|
88
|
+
|
|
89
|
+
The join mode (`/agents → Settings → Join mode`, `defaultJoinMode`) decides whether a result waits for its siblings:
|
|
90
|
+
|
|
91
|
+
| Mode | Behaviour |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `smart` (default) | No waiting: each result is delivered at the parent's next boundary. `async` is accepted as an older name for it. |
|
|
94
|
+
| `group` | The background agents spawned in one parent turn report together, in one notification once all of them have finished (a lone spawn reports alone). A 30 s timeout after the first completion sends a partial notification; stragglers follow within 15 s. |
|
|
95
|
+
|
|
96
|
+
### Sending a foreground agent to the background
|
|
97
|
+
|
|
98
|
+
While a foreground `Agent` call runs (`run_in_background: false`), press `ctrl+b` at the prompt to hand it to the
|
|
99
|
+
background, as in Claude Code. The same run keeps going (session, in-flight turn, transcript and worktree); the blocked
|
|
100
|
+
call returns what a `run_in_background` spawn returns, and the result arrives later as the usual background
|
|
101
|
+
notification. `ctrl+b` hands off every running foreground call at once; a dim `(ctrl+b to run in background)` hint
|
|
102
|
+
appears under one of them after 2 s. In the conversation viewer (FleetView, `/agents`), `b` hands off just that agent.
|
|
103
|
+
|
|
104
|
+
- The key is claimed only while a foreground call runs and the prompt editor has focus; otherwise `ctrl+b` keeps its
|
|
105
|
+
editor meaning (cursor left). Rebind it with `"backgroundKey"` in `subagents.json` (a Pi key id such as `alt+b`), or
|
|
106
|
+
turn it off with `"backgroundKey": "off"`.
|
|
107
|
+
- After the handoff, `Esc` on the parent turn no longer stops the agent; `x` in the viewer and `/agents` still do. The
|
|
108
|
+
run moves from the foreground to the background concurrency pool, over the limit if the pool is full.
|
|
109
|
+
- Nested agents, workflow agents, queued spawns and resumed runs cannot be handed off.
|
|
110
|
+
|
|
111
|
+
### Fullscreen conversation observer
|
|
112
|
+
|
|
113
|
+
In Pi's fullscreen mode (`/settings → TUI mode → fullscreen`, Pi's default), an agent opened from FleetView, `/agents`
|
|
114
|
+
or the workflow inspector fills the terminal; regular mode keeps the floating viewer. Neither replaces the main session
|
|
115
|
+
or pauses an agent, and `Esc` returns to the previous view (while composing, the first `Esc` cancels the draft).
|
|
116
|
+
|
|
117
|
+
- Mouse-wheel scrolling. Scrolling up pauses following; reaching the bottom resumes it.
|
|
118
|
+
- A one-column scrollbar in Pi's style that follows Pi's `/settings → Fullscreen scrollbar`: `auto` appears while
|
|
119
|
+
scrolling or hovering and hides after a second, `always` stays, `hidden` removes it and its mouse target. Drag the
|
|
120
|
+
thumb, or click the track to page. The column is kept free in `auto` mode too, so the bar never re-wraps the text.
|
|
121
|
+
- Clickable footer controls (steer, send to background, stop with two clicks, Markdown mode, close).
|
|
122
|
+
- While scrolled away, the divider above the footer shows `[ ↓ N new messages · Ctrl+End ]` (conversation messages,
|
|
123
|
+
not streaming updates); click it or press `Ctrl+End` to jump back and follow, without losing a steering draft.
|
|
124
|
+
- Mouse events are consumed, so they never scroll, select or click the main view underneath. Text selection, copying
|
|
125
|
+
and switching agents without leaving the observer are not implemented.
|
|
126
|
+
- Cost: built on the viewer's incremental transcript, so scrolling, wheel and drags reuse cached lines, a frame checks
|
|
127
|
+
only the transcript's last message and draws only the visible rows, and nothing ticks while it is idle. Without a JIT
|
|
128
|
+
(Pi-Bolt), a frame or a scroll step costs well under a millisecond whatever the transcript's length.
|
|
129
|
+
|
|
75
130
|
### Resuming agents
|
|
76
131
|
|
|
77
132
|
Every agent result names the agent: `Agent ID: <id>` (foreground and background results alike). Pass it back to continue
|
|
@@ -92,28 +147,89 @@ Agent({ resume: "<id>", prompt: "Now check the tests too", description: "Continu
|
|
|
92
147
|
file, under the same id, for as long as that file exists. Sessions are saved by default (`rememberAgents`; turn it
|
|
93
148
|
off and agents are resumable only for those ten minutes).
|
|
94
149
|
- What survives a restart, `/resume`, `/fork` or `/reload`: finished agents of that session (on its branch, for a fork)
|
|
95
|
-
stay resumable by the model and by `@handle`.
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
stay resumable by the model and by `@handle`. `/new` starts with none. Nested agents and workflow children are
|
|
151
|
+
rebuilt too (their resume state is written to the parent session as `subagents:resume` entries, one small entry per
|
|
152
|
+
settled child): a nested agent stays resumable by the agent that owns it, once that agent is resumed under its id.
|
|
98
153
|
- In workflows, `agent(prompt, { resume: label })` continues a child of the same run the same way, also after its record
|
|
99
|
-
was evicted.
|
|
154
|
+
was evicted. `resumeFromRunId` finds a run of the same session after a restart or `/resume` by its files, and a child
|
|
155
|
+
it replays from the journal can still be continued with `agent({ resume })`: the journal records which agent
|
|
156
|
+
produced each answer.
|
|
157
|
+
|
|
158
|
+
### Switching sessions
|
|
159
|
+
|
|
160
|
+
Background agents keep running when you switch sessions with `/new`, `/resume`, `/fork` or `/reload`, as long as the
|
|
161
|
+
session that started them is saved (it has a session file).
|
|
162
|
+
|
|
163
|
+
- An agent belongs to the session that started it. While another session is open it does not show in that session's
|
|
164
|
+
widget, FleetView, `/agents` lists or `@` suggestions, and `get_subagent_result`, `steer_subagent` and `resume` there
|
|
165
|
+
do not find it. It still counts toward the background concurrency limit.
|
|
166
|
+
- If it finishes while another session is open, its result is written into its own session (a `subagents:record`
|
|
167
|
+
entry marked `pending`) and nothing is sent to the open session. When you return to its session the result arrives as
|
|
168
|
+
the usual completion notification, and the main agent takes a turn on it. The same happens after a restart if the
|
|
169
|
+
result was recorded but not yet delivered.
|
|
170
|
+
- Its nested agents keep running with it. Foreground agents (Pi interrupts their tool call; send one to the background
|
|
171
|
+
with `ctrl+b` first to keep it), workflows, `@mention` prompts in flight and agents of a session kept only in memory
|
|
172
|
+
stop at a switch, as before, and are recorded as stopped.
|
|
173
|
+
- Quitting (`/quit`, Ctrl+D, SIGTERM, SIGHUP) stops every agent and saves worktrees, as before. If Pi exits while a
|
|
174
|
+
switch is still in progress, the agents are recorded as stopped; their worktrees are recovered at the next start.
|
|
175
|
+
- After updating this package, the first `/reload` (or switch) loads the new version, which cannot take over the old
|
|
176
|
+
version's running agents: they are stopped and recorded as stopped and resumable.
|
|
177
|
+
|
|
178
|
+
### Model and thinking level
|
|
179
|
+
|
|
180
|
+
A subagent runs on the parent's current model at the parent's current thinking level, on every spawn path: the `Agent`
|
|
181
|
+
tool, nested delegation, workflow `agent()`, `@mentions`, scheduled jobs and cross-extension RPC. For a nested agent the
|
|
182
|
+
parent is the agent that spawned it.
|
|
183
|
+
|
|
184
|
+
- An agent file's own `model:` or `thinking:` is your configuration and always applies.
|
|
185
|
+
- Per-call choices are ignored by default: the `Agent` tool and the nested tool do not offer `model` or `thinking`,
|
|
186
|
+
workflow `agent({ model, effort })` is ignored, and so are a scheduled job's and an RPC spawn's model and thinking
|
|
187
|
+
level. This keeps the main model from picking cheaper models or lower effort on its own.
|
|
188
|
+
- A resumed agent keeps the model and thinking level it ran with.
|
|
189
|
+
- `allowModelOverride: true` (project or global `subagents.json`, or `/agents → Settings → Model override`) restores the
|
|
190
|
+
per-call overrides and the tool parameters. The resolution changes at once; the tool parameters at the next session.
|
|
100
191
|
|
|
101
192
|
### Custom agents
|
|
102
193
|
|
|
103
194
|
Put custom definitions in `.pi/agents/<name>.md` or the global Pi agents directory. Agents inherit the current parent's
|
|
104
|
-
model and thinking level unless their definition
|
|
195
|
+
model and thinking level unless their definition sets one (see [Model and thinking level](#model-and-thinking-level)).
|
|
105
196
|
|
|
106
197
|
`isolated: true` (on a call or in an agent file) removes extension and MCP tools, skills, `ext:` selectors and nested
|
|
107
198
|
delegation. It is not a sandbox: the agent still inherits Pi's environment variables (including provider keys its
|
|
108
199
|
`bash` can read), the parent's model registry and auth, global and trusted project settings (shell, command prefix,
|
|
109
200
|
retry, compaction), and, for append-mode agents such as `general-purpose`, the parent's whole system prompt.
|
|
110
201
|
|
|
202
|
+
The agent file wins over the caller for `isolated:` and `isolation:`; a call's value applies only when the file leaves
|
|
203
|
+
the field unset. This holds on every spawn path: the `Agent` tool, `@handle` mentions (direct mode and the fallback
|
|
204
|
+
when a mention clone fails), cross-extension RPC and the manager registry. So
|
|
205
|
+
`isolation: off` in a file refuses a caller's `isolation: "worktree"`, and an `isolation: worktree` file gets its
|
|
206
|
+
worktree even when the caller asked for none; outside a git repository such an agent fails to start instead of running
|
|
207
|
+
in the main checkout. Two exceptions: in a workflow, only the script's `agent()` option decides `isolation` (as in Claude
|
|
208
|
+
Code; `isolated:` from the file still applies), and an agent reopened from its session file continues where it ran
|
|
209
|
+
(its `isolated:` is read from the file again).
|
|
210
|
+
|
|
111
211
|
`tools:` limits built-in tools only. Tools registered by extensions stay available unless the file sets
|
|
112
212
|
`extensions: false` (or names the extensions to load), or lists `ext:` selectors in `tools:`, which turns extension
|
|
113
213
|
tools into an allowlist. The built-in `Explore` and `Plan` agents get `read`, `grep`, `find` and `ls` and no extension
|
|
114
214
|
tools, so they cannot write files or run commands; a file named `Explore.md` or `Plan.md` replaces them if you want
|
|
115
215
|
them to have more.
|
|
116
216
|
|
|
217
|
+
Pi's built-in codemode and tool search (`builtin:codemode`, `builtin:tool-search`) load in subagents as in the main
|
|
218
|
+
session: on with `extensions: true`, kept or dropped by `extensions:` and `exclude_extensions:` like any other
|
|
219
|
+
extension (address them by the full `builtin:` name), and off for everyone with `-builtin:<name>` in Pi's `extensions`
|
|
220
|
+
setting or `--no-extensions`. Pi's built-in MCP (`builtin:mcp`) is the exception: subagents do not load it, so no
|
|
221
|
+
child starts its own set of `mcp.json` servers, unless the agent file names it, e.g. `extensions: ["*", builtin:mcp]`
|
|
222
|
+
(`--no-mcp` still keeps it out). MCP tools are reached through codemode or `tool_search`, so an `ext:` allowlist must
|
|
223
|
+
name that bridge too: `tools: "read, ext:builtin:codemode, ext:builtin:mcp/mcp__srv__search"`. A bare
|
|
224
|
+
`mcp__<server>__<tool>` name in `tools:` is reported as an unknown built-in (`tools-error:`); name MCP tools with
|
|
225
|
+
`ext:builtin:mcp/<tool>`.
|
|
226
|
+
|
|
227
|
+
A subagent activates only the tools Pi itself would declare to the model: `direct` and `model-only` tools, minus those
|
|
228
|
+
registered with `defaultActive: false` unless the agent names them (`ext:foo/bar`). `codemode` and `deferred` tools
|
|
229
|
+
(Pi's MCP tools) stay undeclared until codemode or `tool_search` reaches them. Calls a tool makes through
|
|
230
|
+
`ctx.executeTool()`, such as a codemode script calling another tool, go through the same scope check as the model's
|
|
231
|
+
own calls; out-of-scope tools are blocked but can still appear in codemode's `searchTools()` and `tool_search` results.
|
|
232
|
+
|
|
117
233
|
### Project trust
|
|
118
234
|
|
|
119
235
|
By default (`projectTrust: "full"`) subagents and this extension trust every project: a child session loads the
|
|
@@ -142,6 +258,8 @@ To have them follow Pi's project trust decision instead, set `"projectTrust": "p
|
|
|
142
258
|
| Setting | Default | Meaning |
|
|
143
259
|
|---|---|---|
|
|
144
260
|
| Background concurrency (`/agents → Settings`) | `10` | How many background agents run at once. |
|
|
261
|
+
| `backgroundKey` (`subagents.json`) | `ctrl+b` | Key that sends running foreground `Agent` calls to the background (see [Sending a foreground agent to the background](#sending-a-foreground-agent-to-the-background)); `"off"` disables it. |
|
|
262
|
+
| Model override (`/agents → Settings`, `allowModelOverride`) | `false` | Whether a spawn's own `model` / `thinking` (Agent tool, nested tool, workflow `agent()`, scheduled jobs, RPC) may choose the subagent's model and thinking level. Off: subagents run on the parent's current model and level unless their agent file sets one, and the tools do not offer the parameters. |
|
|
145
263
|
| Tool timeout (`/agents → Settings`, `toolTimeoutSeconds`) | `1200` | Seconds one subagent tool call may run (`0` = no limit). A `bash` call is killed at the limit and the agent sees the timeout; any other tool still running a minute later is stopped and the agent is told to continue another way (up to 3 times per run). The main agent's `bash` calls get the same timeout when they set none; a timeout the main agent asks for is kept. |
|
|
146
264
|
|
|
147
265
|
## Notes
|
package/dist/agent-manager.d.ts
CHANGED
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { Model } from "@earendil-works/pi-ai";
|
|
17
17
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import { type ToolActivity } from "./agent-runner.js";
|
|
18
|
+
import { resumeAgent, runAgent, type ToolActivity } from "./agent-runner.js";
|
|
19
|
+
import { getAgentConfig } from "./agent-types.js";
|
|
20
|
+
import { sessionFileProblem } from "./session-file.js";
|
|
19
21
|
import type { AgentInvocation, AgentRecord, AgentResumeState, AgentTombstone, IsolationMode, MentionResolution, SubagentType, ThinkingLevel } from "./types.js";
|
|
20
22
|
import { type LifetimeUsage } from "./usage.js";
|
|
21
23
|
import type { CompiledSchema } from "./workflow/json-schema.js";
|
|
24
|
+
import { cleanupWorktree, createWorktree, isWorktreeIsolationEnabled, pruneWorktrees, restoreWorktree } from "./worktree.js";
|
|
22
25
|
export type OnAgentComplete = (record: AgentRecord) => void;
|
|
23
26
|
export type OnAgentStart = (record: AgentRecord) => void;
|
|
24
27
|
export type OnAgentCompact = (record: AgentRecord, info: CompactionInfo) => void;
|
|
@@ -36,6 +39,38 @@ export type CompactionInfo = {
|
|
|
36
39
|
reason: "manual" | "threshold" | "overflow";
|
|
37
40
|
tokensBefore: number;
|
|
38
41
|
};
|
|
42
|
+
/** What the extension instance observes of the manager's agents. */
|
|
43
|
+
export interface ManagerObservers {
|
|
44
|
+
onComplete?: OnAgentComplete;
|
|
45
|
+
onStart?: OnAgentStart;
|
|
46
|
+
onCompact?: OnAgentCompact;
|
|
47
|
+
onUsage?: OnAgentUsage;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The module functions the manager runs agents and worktrees with. Pi loads a
|
|
51
|
+
* fresh copy of this extension's modules for every session, and a manager kept
|
|
52
|
+
* across a session switch (session-handoff.ts) was built by an older copy: the
|
|
53
|
+
* adopting instance hands it its own, so new spawns see the current session's
|
|
54
|
+
* agent registry and settings. Runs already going keep the ones they started
|
|
55
|
+
* with. Unset, the manager uses its own module's imports.
|
|
56
|
+
*/
|
|
57
|
+
export interface ManagerServices {
|
|
58
|
+
runAgent: typeof runAgent;
|
|
59
|
+
resumeAgent: typeof resumeAgent;
|
|
60
|
+
createWorktree: typeof createWorktree;
|
|
61
|
+
cleanupWorktree: typeof cleanupWorktree;
|
|
62
|
+
restoreWorktree: typeof restoreWorktree;
|
|
63
|
+
isWorktreeIsolationEnabled: typeof isWorktreeIsolationEnabled;
|
|
64
|
+
pruneWorktrees: typeof pruneWorktrees;
|
|
65
|
+
sessionFileProblem: typeof sessionFileProblem;
|
|
66
|
+
/** The agent registry the current session loaded (an agent file's `isolation:`). */
|
|
67
|
+
getAgentConfig: typeof getAgentConfig;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* This module's {@link ManagerServices}, for an instance adopting a manager
|
|
71
|
+
* built by another copy. Getters, so each import is read only when used.
|
|
72
|
+
*/
|
|
73
|
+
export declare function managerServices(): ManagerServices;
|
|
39
74
|
/**
|
|
40
75
|
* Whether a record is one of the session's own agents, rather than something
|
|
41
76
|
* another agent or a workflow owns.
|
|
@@ -280,10 +315,19 @@ interface ResumeOptions {
|
|
|
280
315
|
* or undefined when there is no session file to reopen.
|
|
281
316
|
*/
|
|
282
317
|
export declare function resumeStateOf(record: AgentRecord): AgentResumeState | undefined;
|
|
318
|
+
/** Session entry a top-level agent's final record goes into (with its resume state). */
|
|
319
|
+
export declare const RECORD_ENTRY_TYPE = "subagents:record";
|
|
320
|
+
/**
|
|
321
|
+
* Session entry an owned child's resume state goes into: a nested agent's or a
|
|
322
|
+
* workflow's. Kept apart from {@link RECORD_ENTRY_TYPE}, which other extensions
|
|
323
|
+
* read as the session's own agents, while an owned child reports only through
|
|
324
|
+
* its owner. Carries the resume state alone, so it stays small.
|
|
325
|
+
*/
|
|
326
|
+
export declare const RESUME_ENTRY_TYPE = "subagents:resume";
|
|
283
327
|
/**
|
|
284
|
-
* The resume states a session's `subagents:record`
|
|
285
|
-
* first. Entries written before they carried one, or
|
|
286
|
-
* skipped: there is nothing to reopen them from.
|
|
328
|
+
* The resume states a session's `subagents:record` and `subagents:resume`
|
|
329
|
+
* entries carry, oldest first. Entries written before they carried one, or
|
|
330
|
+
* malformed ones, are skipped: there is nothing to reopen them from.
|
|
287
331
|
*/
|
|
288
332
|
export declare function resumeStatesFromEntries(entries: readonly unknown[]): AgentResumeState[];
|
|
289
333
|
export declare class AgentManager {
|
|
@@ -345,7 +389,41 @@ export declare class AgentManager {
|
|
|
345
389
|
/** Pending releases, by record. */
|
|
346
390
|
private releaseTimers;
|
|
347
391
|
private sessionReleaseMs;
|
|
392
|
+
/** Set by an instance that adopted this manager; see {@link ManagerServices}. */
|
|
393
|
+
private services?;
|
|
394
|
+
/**
|
|
395
|
+
* The session whose agents the top-level surfaces show (`listAgents`,
|
|
396
|
+
* `resolveMention`, `hasRunning`, `waitForAll`). `null`: every agent — the
|
|
397
|
+
* default, and the only mode before a session has started.
|
|
398
|
+
*/
|
|
399
|
+
private visibleOwner;
|
|
400
|
+
/**
|
|
401
|
+
* Arguments of spawns that have not reached `runAgent` yet (queued, or still
|
|
402
|
+
* creating their worktree), so a session switch can swap their context for a
|
|
403
|
+
* frozen copy before Pi invalidates it.
|
|
404
|
+
*/
|
|
405
|
+
private pendingArgs;
|
|
348
406
|
constructor(onComplete?: OnAgentComplete, maxConcurrent?: number, onStart?: OnAgentStart, onCompact?: OnAgentCompact, onUsage?: OnAgentUsage);
|
|
407
|
+
/** Replace what the extension observes of this manager's agents (an adopting instance, or parking). */
|
|
408
|
+
setObservers(observers: ManagerObservers): void;
|
|
409
|
+
/** Run new work with these module functions; see {@link ManagerServices}. */
|
|
410
|
+
useServices(services: ManagerServices): void;
|
|
411
|
+
private svc;
|
|
412
|
+
/**
|
|
413
|
+
* Show only the agents of this session (its file; undefined for one in
|
|
414
|
+
* memory) on the top-level surfaces, or every agent with `null`. Agents of
|
|
415
|
+
* other sessions keep running and are reachable by id internally — their
|
|
416
|
+
* nested tools, the workflow host — just not listed or addressable here.
|
|
417
|
+
*/
|
|
418
|
+
setVisibleOwner(owner: string | undefined | null): void;
|
|
419
|
+
/** Whether the record belongs to the session the surfaces show. */
|
|
420
|
+
isVisible(record: Pick<AgentRecord, "ownerSession">): boolean;
|
|
421
|
+
/**
|
|
422
|
+
* Before a session switch: give every spawn that has not started its run yet
|
|
423
|
+
* a frozen copy of its context, since Pi invalidates the live one (every read
|
|
424
|
+
* throws) and the spawn reads it when it starts.
|
|
425
|
+
*/
|
|
426
|
+
detachPendingContexts(): void;
|
|
349
427
|
/** Update the max concurrent background agents limit. */
|
|
350
428
|
setMaxConcurrent(n: number): void;
|
|
351
429
|
getMaxConcurrent(): number;
|
|
@@ -438,12 +516,18 @@ export declare class AgentManager {
|
|
|
438
516
|
* an aborted signal, so a `spawnAndWait` on it would wait forever — pi has no
|
|
439
517
|
* tool-execution timeout to bail it out.
|
|
440
518
|
*
|
|
441
|
-
* The listener is
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* `abortAll`/`dispose` paths could not offer anyway.
|
|
519
|
+
* The listener is removable (`record.detachParentAbort`): `startAgent`
|
|
520
|
+
* replaces it with its own, and `sendToBackground` must be able to take it
|
|
521
|
+
* off, or the caller that armed it could stop a run it no longer owns.
|
|
445
522
|
*/
|
|
446
523
|
private armQueuedAbort;
|
|
524
|
+
/**
|
|
525
|
+
* Stop `record` when its caller's `signal` aborts, replacing any listener an
|
|
526
|
+
* earlier stage armed — at most one is live per record, so one call to
|
|
527
|
+
* `detachParentAbort` cuts the run loose from its caller.
|
|
528
|
+
*/
|
|
529
|
+
private armParentAbort;
|
|
530
|
+
private detachParentAbort;
|
|
447
531
|
/**
|
|
448
532
|
* Kick off an agent's startup and register it under `startups`. The returned
|
|
449
533
|
* promise never rejects — the failure is delivered through `awaitStartup`,
|
|
@@ -458,6 +542,7 @@ export declare class AgentManager {
|
|
|
458
542
|
* handle goes back.
|
|
459
543
|
*/
|
|
460
544
|
private launch;
|
|
545
|
+
private dropPendingArgs;
|
|
461
546
|
/**
|
|
462
547
|
* Resolves once the agent is actually running, and rejects with the startup
|
|
463
548
|
* failure (strict worktree isolation) that `spawn()` used to throw before the
|
|
@@ -506,6 +591,28 @@ export declare class AgentManager {
|
|
|
506
591
|
id: string;
|
|
507
592
|
record: AgentRecord;
|
|
508
593
|
}>;
|
|
594
|
+
/**
|
|
595
|
+
* Hand a live run to the background without restarting it. The session,
|
|
596
|
+
* promise, in-flight turn, transcript and worktree all continue under the
|
|
597
|
+
* same id; what changes is who answers for it. The caller's parent-abort
|
|
598
|
+
* listener comes off (its Esc no longer reaches the run — `abort()` and
|
|
599
|
+
* `/agents` still do), the slot moves to the background pool, and the run
|
|
600
|
+
* then settles through the ordinary background path: `endRun` releases the
|
|
601
|
+
* run's live pool, and the settle reads the live `isBackground`, so the
|
|
602
|
+
* result is delivered as a background completion.
|
|
603
|
+
*
|
|
604
|
+
* Releasing the caller is the caller's job — the `Agent` tool races its own
|
|
605
|
+
* wake-up against `spawnAndWait`. This only refuses what it can see: a run
|
|
606
|
+
* nobody is blocking on (a background or detached spawn, one already handed
|
|
607
|
+
* off, or a foreground resume — `blocking` is cleared when the spawned run
|
|
608
|
+
* settles), one that reports through something other than the session (a
|
|
609
|
+
* nested child, a workflow's agent), and one that is stopped or queued.
|
|
610
|
+
*
|
|
611
|
+
* The background slot is taken even when the pool is full: suspending a
|
|
612
|
+
* live run is not an option, so it runs over the limit and queued
|
|
613
|
+
* background work waits for the count to drop, as with `bypassQueue`.
|
|
614
|
+
*/
|
|
615
|
+
sendToBackground(id: string): boolean;
|
|
509
616
|
/**
|
|
510
617
|
* Resume an existing agent session with a new prompt.
|
|
511
618
|
*/
|
|
@@ -584,7 +691,10 @@ export declare class AgentManager {
|
|
|
584
691
|
dropTombstone(idOrHandle: string): void;
|
|
585
692
|
/** Evicted top-level agents whose conversation can still be reopened, newest first. */
|
|
586
693
|
listTombstones(): AgentTombstone[];
|
|
694
|
+
/** The agents of the session the surfaces show (see {@link setVisibleOwner}), newest first. */
|
|
587
695
|
listAgents(): AgentRecord[];
|
|
696
|
+
/** Every agent this manager holds, whichever session it belongs to, newest first. */
|
|
697
|
+
listAllAgents(): AgentRecord[];
|
|
588
698
|
abort(id: string): boolean;
|
|
589
699
|
/**
|
|
590
700
|
* Settle a record that never left the queue as stopped. It still reaches
|
|
@@ -630,7 +740,7 @@ export declare class AgentManager {
|
|
|
630
740
|
* commit and branch that preserve the agent's changes — finishes before the
|
|
631
741
|
* process exits instead of racing it.
|
|
632
742
|
*/
|
|
633
|
-
awaitWorktreeCleanup(timeoutMs: number): Promise<void>;
|
|
743
|
+
awaitWorktreeCleanup(timeoutMs: number, ids?: ReadonlySet<string>): Promise<void>;
|
|
634
744
|
/** Wait for all running and queued agents to complete (including queued ones). */
|
|
635
745
|
waitForAll(): Promise<void>;
|
|
636
746
|
/**
|