@kushalkhemka/opensec-pi-subagents 0.20.1 → 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.
Files changed (69) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +131 -8
  3. package/dist/agent-manager.d.ts +119 -9
  4. package/dist/agent-manager.js +263 -56
  5. package/dist/agent-runner.d.ts +11 -0
  6. package/dist/agent-runner.js +96 -22
  7. package/dist/completion-outbox.d.ts +65 -0
  8. package/dist/completion-outbox.js +108 -0
  9. package/dist/cross-extension-rpc.js +8 -0
  10. package/dist/env.js +3 -2
  11. package/dist/exec-tree.js +11 -2
  12. package/dist/index.d.ts +7 -1
  13. package/dist/index.js +827 -252
  14. package/dist/invocation-config.d.ts +17 -4
  15. package/dist/invocation-config.js +29 -2
  16. package/dist/nested-tools.js +10 -4
  17. package/dist/project-trust.d.ts +3 -2
  18. package/dist/project-trust.js +19 -2
  19. package/dist/schedule.d.ts +1 -1
  20. package/dist/schedule.js +10 -4
  21. package/dist/session-handoff.d.ts +102 -0
  22. package/dist/session-handoff.js +162 -0
  23. package/dist/settings.d.ts +40 -0
  24. package/dist/settings.js +17 -0
  25. package/dist/types.d.ts +24 -2
  26. package/dist/ui/agent-widget.d.ts +4 -0
  27. package/dist/ui/agent-widget.js +5 -0
  28. package/dist/ui/conversation-viewer.d.ts +104 -3
  29. package/dist/ui/conversation-viewer.js +425 -52
  30. package/dist/ui/fleet-list.d.ts +30 -9
  31. package/dist/ui/fleet-list.js +37 -12
  32. package/dist/workflow/collisions.d.ts +3 -1
  33. package/dist/workflow/collisions.js +4 -1
  34. package/dist/workflow/host.js +30 -7
  35. package/dist/workflow/journal.d.ts +16 -8
  36. package/dist/workflow/journal.js +12 -9
  37. package/dist/workflow/runtime.d.ts +7 -1
  38. package/dist/workflow/runtime.js +38 -8
  39. package/dist/workflow/task.d.ts +11 -1
  40. package/dist/workflow/task.js +13 -1
  41. package/dist/workflow/tool-description.d.ts +9 -1
  42. package/dist/workflow/tool-description.js +21 -1
  43. package/docs/rpc.md +10 -10
  44. package/docs/workflows.md +6 -6
  45. package/examples/agent-tool-description.md +1 -3
  46. package/package.json +16 -11
  47. package/src/agent-manager.ts +288 -55
  48. package/src/agent-runner.ts +100 -21
  49. package/src/completion-outbox.ts +121 -0
  50. package/src/cross-extension-rpc.ts +8 -0
  51. package/src/env.ts +3 -2
  52. package/src/exec-tree.ts +11 -2
  53. package/src/index.ts +813 -239
  54. package/src/invocation-config.ts +44 -4
  55. package/src/nested-tools.ts +10 -4
  56. package/src/project-trust.ts +16 -2
  57. package/src/schedule.ts +10 -4
  58. package/src/session-handoff.ts +188 -0
  59. package/src/settings.ts +55 -0
  60. package/src/types.ts +24 -2
  61. package/src/ui/agent-widget.ts +8 -0
  62. package/src/ui/conversation-viewer.ts +419 -35
  63. package/src/ui/fleet-list.ts +34 -11
  64. package/src/workflow/collisions.ts +4 -1
  65. package/src/workflow/host.ts +30 -7
  66. package/src/workflow/journal.ts +18 -9
  67. package/src/workflow/runtime.ts +50 -8
  68. package/src/workflow/task.ts +14 -0
  69. package/src/workflow/tool-description.ts +27 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,81 @@ 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
+
79
+ ## [0.20.2] - 2026-10-16
80
+
81
+ ### Changed
82
+
83
+ - **Every project is trusted again by default**, as before 0.20.1: subagents load a project's `.pi` extensions and settings, and the extension uses its agents, skills, workflows and `subagents.json`, whatever Pi decided for the folder. To follow Pi's project trust decision instead (0.20.1's behaviour), set `"projectTrust": "pi"` in the global `~/.pi/agent/subagents.json`.
84
+
10
85
  ## [0.20.1] - 2026-10-16
11
86
 
12
87
  ### Fixed
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: this Node build loads and runs a real turn on Pi 0.87.1 and Pi 1.0.0 (both on Node). The peer-dependency
36
- range is `>=0.84.0`; earlier versions were not re-tested.
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,31 +147,97 @@ 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`. Agents still running are stopped, as before, and recorded as stopped
96
- and resumable. `/new` starts with none. Nested agents can be resumed by their parent while their parent's manager
97
- lives; across a restart only top-level agents are rebuilt.
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 or call overrides them.
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
- Subagents follow Pi's project trust decision for the parent session.
235
+ By default (`projectTrust: "full"`) subagents and this extension trust every project: a child session loads the
236
+ project's `.pi/extensions` and `.pi/settings.json`, and the extension uses the project's agents, skills, workflows and
237
+ `subagents.json`, whatever Pi decided for the parent session.
238
+
239
+ To have them follow Pi's project trust decision instead, set `"projectTrust": "pi"` in the global
240
+ `~/.pi/agent/subagents.json` (a project's own file cannot change it). Then:
120
241
 
121
242
  - In a folder Pi does not trust, subagents load none of the project's `.pi/extensions`, `.pi/settings.json` (for
122
243
  example `shellCommandPrefix`), skills or prompts, exactly like the parent. This extension also ignores the project's
@@ -137,6 +258,8 @@ Subagents follow Pi's project trust decision for the parent session.
137
258
  | Setting | Default | Meaning |
138
259
  |---|---|---|
139
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. |
140
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. |
141
264
 
142
265
  ## Notes
@@ -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` entries carry, oldest
285
- * first. Entries written before they carried one, or malformed ones, are
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 left in place when the agent starts. `startAgent` adds its
442
- * own, so both fire on a later abort, but `abort()` on an already-stopped
443
- * record is a no-op — so detaching would only be tidiness, and tidiness the
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
  /**