@nklisch/pi-enhanced 0.2.6 → 0.3.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.node +0 -0
  3. package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
  4. package/node_modules/@nklisch/pi-plugins/README.md +4 -2
  5. package/node_modules/@nklisch/pi-plugins/dist/hooks.js +81 -8
  6. package/node_modules/@nklisch/pi-plugins/dist/hooks.js.map +1 -1
  7. package/node_modules/@nklisch/pi-plugins/dist/mcp.js +32 -1
  8. package/node_modules/@nklisch/pi-plugins/dist/mcp.js.map +1 -1
  9. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.d.ts +10 -4
  10. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js +144 -10
  11. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js.map +1 -1
  12. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js +40 -14
  13. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js.map +1 -1
  14. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +25 -0
  15. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +75 -47
  16. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +171 -140
  17. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/settings.d.ts +2 -2
  18. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +8 -7
  19. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +12 -10
  20. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +112 -105
  21. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +3 -3
  22. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/decisions/0004-reconsider-ui-direction.md +5 -0
  23. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
  24. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +31 -4
  25. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +1 -1
  26. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/invocation-config.ts +7 -4
  27. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +18 -9
  28. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/layered-settings.ts +2 -2
  29. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/child-lifecycle.ts +10 -63
  30. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/concurrency-limiter.ts +122 -76
  31. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +3 -5
  32. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/lifecycle-interceptor.ts +4 -3
  33. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/run-listeners.ts +2 -23
  34. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +177 -252
  35. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +90 -280
  36. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +153 -223
  37. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -683
  38. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/workspace.ts +1 -1
  39. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +78 -119
  40. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +15 -42
  41. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +44 -95
  42. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +157 -90
  43. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +60 -68
  44. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/conversation.ts +3 -3
  45. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query-source.ts +28 -0
  46. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts +327 -0
  47. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +1 -1
  48. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +171 -249
  49. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +51 -92
  50. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts +9 -6
  51. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/list-tool.ts +46 -0
  52. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/parent-tool-registry.ts +17 -0
  53. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/query-session-tool.ts +330 -0
  54. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +27 -111
  55. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/resume-tool.ts +105 -0
  56. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +26 -8
  57. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +27 -111
  58. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/stop-tool.ts +53 -0
  59. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +11 -5
  60. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +33 -27
  61. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +6 -3
  62. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +121 -10
  63. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +430 -112
  64. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +1 -1
  65. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +8 -9
  66. package/node_modules/@nklisch/pi-plugins/package.json +2 -2
  67. package/package.json +1 -1
  68. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +0 -81
  69. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +0 -149
  70. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +0 -77
@@ -4,7 +4,7 @@
4
4
 
5
5
  A [pi](https://pi.dev) extension that gives pi **a focused, in-process sub-agent core** — autonomous agents that run inside the same pi runtime (no spawned subprocesses), plus a typed API and lifecycle events other extensions build on.
6
6
  Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level.
7
- Run them in foreground or background, steer them mid-run, resume completed sessions, and define your own custom agent types.
7
+ Run them in joined or detached mode, steer them while active, resume retained sessions, and define your own custom agent types.
8
8
 
9
9
  > A maintained MIT fork of [`@gotgenes/pi-subagents`](https://www.npmjs.com/package/@gotgenes/pi-subagents), preserving its upstream execution core while adding a generic ordered lifecycle-interceptor provider seam.
10
10
  > See [Fork maintenance](./docs/FORK-MAINTENANCE.md) for provenance, qualification, and upstream-return policy.
@@ -15,10 +15,10 @@ Run them in foreground or background, steer them mid-run, resume completed sessi
15
15
 
16
16
  ## Features
17
17
 
18
- - **In-process & native** — agents run inside the same pi runtime (no spawned subprocesses), sharing tool names, calling conventions, and UI patterns (`subagent`, `get_subagent_result`, `steer_subagent`) — feels native
19
- - **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 4) and individual completion notifications
18
+ - **In-process & native** — agents run inside the same pi runtime (no spawned subprocesses), sharing tool names, calling conventions, and UI patterns (`subagent`, `resume_subagent`, `stop_subagent`, `list_subagents`, `get_subagent_result`, `steer_subagent`) — feels native
19
+ - **Joined and detached delivery** — join a child result into the current parent turn or detach it for a completion notification; every run shares FIFO admission (configurable concurrency limit, default 4)
20
20
  - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons
21
- - **Session transcripts** — open any subagent's full session transcript, including records whose heavy live session has been released, in pi's native read-only viewer via `/subagents:sessions`
21
+ - **Session transcripts** — open any subagent's full session transcript, including records whose heavy live session has been released, in pi's native read-only viewer via `/subagents:sessions`; search literal text and cycle matches without steering the child
22
22
  - **Custom agent types** — define project agents in `.pi/agents/<name>.md` or the shared `.agents/agents/<name>.md` convention, with YAML frontmatter for prompts, models, thinking, and built-in tools
23
23
  - **Mid-run steering** — inject messages into running agents to redirect their work without restarting
24
24
  - **Session resume** — pick up where an agent left off, preserving full conversation context
@@ -26,8 +26,8 @@ Run them in foreground or background, steer them mid-run, resume completed sessi
26
26
  - **Policy-aware agent types** — unambiguous names resolve case-insensitively. Unknown names default to `general-purpose`, can target another enabled fallback, or can fail closed via `fallbackSubagent`
27
27
  - **Fuzzy model selection** — specify models by name (`"haiku"`, `"sonnet"`) instead of full IDs, with automatic filtering to only available/configured models
28
28
  - **Context inheritance** — optionally fork the parent conversation into a sub-agent so it knows what's been discussed
29
- - **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML.
30
- Expandable to show full output
29
+ - **Styled completion notifications** — detached agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML.
30
+ Expandable to show the bounded preview; use the transcript path for the full session
31
31
  - **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `resumed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
32
32
 
33
33
  ## Install
@@ -50,16 +50,16 @@ subagent({
50
50
  subagent_type: "Explore",
51
51
  prompt: "Find all files that handle authentication",
52
52
  description: "Find auth files",
53
- run_in_background: true,
53
+ mode: "joined",
54
54
  })
55
55
  ```
56
56
 
57
- Foreground agents block until complete and return results inline.
58
- Background agents return an ID immediately and notify you on completion.
57
+ Joined agents return their settled result inline. Detached agents return an ID
58
+ immediately and notify you once on completion (the default).
59
59
 
60
60
  ## UI
61
61
 
62
- The extension renders a persistent widget above the editor showing active background agents (foreground runs are rendered inline by the `subagent` tool's progress stream):
62
+ The extension renders a persistent widget above the editor showing active detached agents (joined runs are rendered inline by the `subagent` tool's progress stream):
63
63
 
64
64
  ```text
65
65
  ● Agents
@@ -86,13 +86,12 @@ Individual agent results render inline in the conversation:
86
86
  | **Running** | `⠹ ⟳3≤30 · 3 tool uses · 12.4k token (8%)` / `⎿ searching, reading 3 files…` |
87
87
  | **Completed** | `✓ ⟳8 · 5 tool uses · 33.8k token (62%) · 12.3s` / `⎿ Done` |
88
88
  | **Wrapped up** | `✓ ⟳50≤50 · 50 tool uses · 89.1k token (84% · ↻2) · 45.2s` / `⎿ Wrapped up (turn limit)` |
89
- | **Stopped** | `■ ⟳3 · 3 tool uses · 12.4k token (8%)` / `⎿ Stopped` |
89
+ | **Stopped** | `■ ⟳3 · 3 tool uses · 12.4k token (8%)` / `⎿ Stopped (explicit stop)` |
90
90
  | **Error** | `✗ ⟳3 · 3 tool uses · 12.4k token (8%)` / `⎿ Error: timeout` |
91
- | **Aborted** | `✗ ⟳55≤50 · 55 tool uses · 102.3k token (95% · ↻3)` / `⎿ Aborted (max turns exceeded)` |
92
91
 
93
92
  Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
94
93
 
95
- Background agent completion notifications render as styled boxes:
94
+ Detached agent completion notifications render as styled boxes:
96
95
 
97
96
  ```text
98
97
  ✓ Find auth files completed
@@ -101,7 +100,7 @@ Background agent completion notifications render as styled boxes:
101
100
  transcript: .pi/output/agent-abc123.jsonl
102
101
  ```
103
102
 
104
- The LLM receives structured `<task-notification>` XML for parsing, while the user sees the themed visual.
103
+ The LLM receives structured `<task-notification>` XML for parsing, while the user sees the themed visual. The notification contains a bounded preview; use `get_subagent_result` or the transcript path for longer output.
105
104
 
106
105
  ## Default Agent Types
107
106
 
@@ -174,12 +173,21 @@ All fields are optional — sensible defaults for everything.
174
173
  | `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
175
174
  | `prompt_mode` | `append` | `replace`: parent prompt is the cacheable base; body is appended last with full control (no `<sub_agent_context>` bridge, no `<agent_instructions>` wrapper). `append`: parent prompt is the base; body is wrapped in `<agent_instructions>` and a sub-agent context bridge is injected (agent acts as a "parent twin") |
176
175
  | `inherit_context` | `false` | Fork parent conversation into agent |
177
- | `run_in_background` | `false` | Run in background by default |
176
+ | `mode` | `detached` | Default result delivery: `detached` or `joined` |
177
+ | `timeout_seconds` | — | Positive active-runtime deadline; queued time is excluded |
178
178
  | `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
179
179
 
180
180
  Frontmatter is authoritative.
181
- If an agent file sets `model`, `thinking`, `max_turns`, `inherit_context`, or `run_in_background`, those values are locked for that agent.
182
- `subagent` tool parameters only fill fields the agent config leaves unspecified.
181
+ If an agent file sets `model`, `thinking`, `max_turns`, `inherit_context`, or `mode`, those values are used as defaults for that agent.
182
+ `subagent` tool parameters only fill fields the agent config leaves unspecified. Legacy `run_in_background` and `foreground` fields are rejected with a diagnostic; they are never silently remapped.
183
+
184
+ Migration is direct:
185
+
186
+ ```yaml
187
+ # Before # After
188
+ run_in_background: true mode: detached
189
+ run_in_background: false mode: joined
190
+ ```
183
191
 
184
192
  ## Tools
185
193
 
@@ -195,27 +203,41 @@ Launch a sub-agent.
195
203
  | `model` | string | no | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
196
204
  | `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh, max |
197
205
  | `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
198
- | `run_in_background` | boolean | no | Run without blocking |
199
- | `resume` | string | no | Retained, finished agent ID to continue with the same history |
206
+ | `mode` | `joined` or `detached` | no | Result delivery mode; defaults to `detached` |
207
+ | `timeout_seconds` | integer | no | Positive active-runtime deadline; queued time is excluded |
200
208
  | `inherit_context` | boolean | no | Fork parent conversation into agent |
201
209
 
202
210
  ### Choosing the next action
203
211
 
204
- - Let background agents finish normally. Completion automatically wakes the parent with a result preview; do not poll.
205
- - Use `steer_subagent` to redirect an agent that is still running.
206
- - Use `resume` after an agent finishes when it should continue with the same retained conversation history.
207
- - Launch a new subagent without `resume` when prior conversation history is unnecessary.
208
- - Use `get_subagent_result` only for full output, verbose conversation, an explicit status check or synchronization point, or recovery after a missed notification.
212
+ - Use `mode: "joined"` when this parent turn needs the settled result; use the default `mode: "detached"` for independent work.
213
+ - Let detached agents finish normally. Completion automatically wakes the parent with one bounded result preview; do not poll.
214
+ - Use `steer_subagent` to redirect an active agent and `stop_subagent` for cooperative cancellation.
215
+ - Use `resume_subagent` after an agent finishes when it should continue with the same retained conversation history.
216
+ - Use `get_subagent_result` for a bounded status/result check or recovery after a missed notification; it never waits or dumps the full conversation.
209
217
 
210
218
  ### `get_subagent_result`
211
219
 
212
- Inspect status or retrieve full results from a background agent. It is not the normal completion path because completion notifications wake the parent automatically.
220
+ Inspect a bounded status or final result from a subagent. It is not the normal completion path because detached completion notifications wake the parent automatically.
221
+
222
+ | Parameter | Type | Required | Description |
223
+ | ---------- | ------ | -------- | ------------------------------------------------ |
224
+ | `agent_id` | string | yes | Agent ID to check |
213
225
 
214
- | Parameter | Type | Required | Description |
215
- | ---------- | ------- | -------- | ----------------------------- |
216
- | `agent_id` | string | yes | Agent ID to check |
217
- | `wait` | boolean | no | Wait for completion |
218
- | `verbose` | boolean | no | Include full conversation log |
226
+ The result is nonblocking and bounded. Live records report current status and activity; terminal records include a transcript pointer when output is truncated. Use `/subagents:sessions` for the full retained transcript.
227
+
228
+ ### `query_subagent_session`
229
+
230
+ Search a child transcript without steering or changing the child. The tool reads the live in-memory session when available and falls back to the retained JSONL transcript after release.
231
+
232
+ | Parameter | Type | Required | Description |
233
+ | --- | --- | --- | --- |
234
+ | `agent_id` | string | yes | Agent ID to inspect |
235
+ | `query` | string | no | Case-insensitive literal text; omit for recent entries |
236
+ | `kind` | `all`, `messages`, `tool_calls`, or `tool_results` | no | Search scope; defaults to `all` |
237
+ | `order` | `newest` or `oldest` | no | Result order; defaults to `newest` |
238
+ | `limit` | integer 1–50 | no | Maximum entries; defaults to 20 |
239
+
240
+ Arguments and results are capped before matching and output is bounded. Results identify the source and retained transcript path when one exists; use `/subagents:sessions` for the complete native transcript and `get_subagent_result` for status or final output.
219
241
 
220
242
  ### `steer_subagent`
221
243
 
@@ -242,13 +264,20 @@ Changes persist across pi restarts (see [Persistent Settings](#persistent-settin
242
264
  ### `/subagents:sessions`
243
265
 
244
266
  Pick any subagent — running, completed, or retained after its live session was released — and read its full session transcript in pi's native per-entry viewer.
245
- Read-only: no steering, no session takeover (steering lives in the `steer_subagent` tool and the background widget).
267
+ Read-only: no steering, no session takeover (steering lives in the `steer_subagent` tool and the detached widget).
268
+
269
+ In the viewer, press `/` to enter literal search, type a query, and press `Enter`
270
+ (or `n`) for the next match. Press `N` or `Shift+Enter` for the previous match.
271
+ Press `Tab` to toggle the tool-only filter without discarding the query. While
272
+ search is active, the first `Esc` clears the search and returns to the transcript;
273
+ a second `Esc` closes the viewer. Outside search, `q` or `Esc` closes it directly.
274
+ Ordinary `↑`/`↓` (or `j`/`k`) and Page Up/Page Down scrolling remain available.
246
275
 
247
276
  Creating and editing agent definitions is not a command — write an agent `.md` file in your editor, or ask a pi session to generate one (see [Custom Agents](#custom-agents)).
248
277
 
249
278
  ## Graceful Max Turns
250
279
 
251
- Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
280
+ Instead of immediately hard-aborting at the turn limit, agents get a graceful shutdown:
252
281
 
253
282
  1. At `max_turns` — steering message: *"Wrap up immediately — provide your final answer now."*
254
283
  2. Up to 5 grace turns to finish cleanly
@@ -256,23 +285,22 @@ Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
256
285
 
257
286
  | Status | Meaning | Icon |
258
287
  | ----------- | ----------------------------- | ---------- |
259
- | `completed` | Finished naturally | `✓` green |
260
- | `steered` | Hit limit, wrapped up in time | `✓` yellow |
261
- | `aborted` | Grace period exceeded | `✗` red |
262
- | `stopped` | User-initiated abort | `■` dim |
288
+ | `completed` | Finished naturally or within the grace window | `✓` green |
289
+ | `stopped` | Explicit stop, parent cancellation, timeout, hard limit, or lifecycle abort | `■` dim |
290
+ | `error` | Provider, execution, or workspace teardown failure | `✗` red |
263
291
 
264
292
  ## Concurrency
265
293
 
266
- Background agents are subject to a configurable concurrency limit (default: 4).
267
- Excess agents are automatically queued and start as running agents complete. The widget shows each queued agent with its effective model and queued state. Stopping a queued agent follows the normal terminal lifecycle and reports that no work started.
294
+ Joined and detached agents share a configurable FIFO concurrency limit (default: 4).
295
+ Excess agents are automatically queued and start as running agents complete. The widget shows each queued detached agent with its effective model and queued state. Stopping queued work settles it immediately and reports that no work started.
268
296
 
269
- Foreground agents bypass the queue they block the parent anyway. Completion nudges are held while the parent is running and flushed at the parent run boundary, preventing a pulled result from also arriving as a duplicate notification.
297
+ Joined agents do not bypass the queue: sibling joined tool calls use Pi's native parallel execution and share the same admission limit. Detached completion nudges are held while the parent is running and flushed at the parent run boundary, suppressing stale or consumed notifications.
270
298
 
271
299
  Resume requests are serialized per retained session. A second request while a turn is genuinely active gets a deterministic busy result; after an abort or extension-driven continuation, resume waits for both the prior invocation and Pi's authoritative idle boundary instead of racing `AgentSession.prompt()`. Session teardown likewise emits and awaits `session_shutdown` before Pi revokes child extension contexts, allowing child-owned processes and timers to stop cleanly.
272
300
 
273
301
  ## Persistent Settings
274
302
 
275
- Runtime tuning values set via `/subagents:settings` persist across pi restarts. Terminal records remain available for the whole parent session. Their heavy live sessions are released after the consumed or unconsumed retention window; the result and persisted transcript pointer remain available, but a released session cannot resume without starting a new subagent. A completion notification, foreground result, or `get_subagent_result` collection marks a result consumed, so the shorter consumed window normally governs after delivery.
303
+ Runtime tuning values set via `/subagents:settings` persist across pi restarts. Terminal records remain available for the whole parent session. Their heavy live sessions are released after the consumed or unconsumed retention window; the result and persisted transcript pointer remain available, but a released session cannot resume without starting a new subagent. A completion notification, joined result, or `get_subagent_result` collection marks a result consumed, so the shorter consumed window normally governs after delivery.
276
304
  Two files, merged on load:
277
305
 
278
306
  - **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults.
@@ -298,7 +326,7 @@ cat > ~/.pi/agent/subagents.json <<'EOF'
298
326
  EOF
299
327
  ```
300
328
 
301
- Every project now starts with concurrency 16, grace 10, a one-day unconsumed retention cap, background agents surviving parent ESC, and unknown agent types failing closed.
329
+ Every project now starts with concurrency 16, grace 10, a one-day unconsumed retention cap, detached agents surviving parent ESC, and unknown agent types failing closed.
302
330
  Individual projects can still override via `/subagents:settings`.
303
331
 
304
332
  **Failure behavior:** missing file is silent; malformed JSON logs a `[pi-subagents] Ignoring malformed settings at …` warning to stderr; invalid/out-of-range field values are dropped per-field; write failures downgrade the `/subagents:settings` toast to a warning with `(session only; failed to persist)`.
@@ -309,11 +337,11 @@ Agent lifecycle events are emitted via `pi.events.emit()` so other extensions ca
309
337
 
310
338
  | Event | When | Key fields |
311
339
  | ---------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
312
- | `subagents:created` | Background agent registered | `id`, `type`, `description`, `isBackground` |
313
- | `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description` |
314
- | `subagents:completed` | Agent finished successfully | `id`, `type`, `durationMs`, `tokens` (lifetime `{ input, output, total }`), `toolUses`, `result` |
315
- | `subagents:resumed` | A resumed turn reached a terminal state | completed-event shape plus `status` and `error` |
316
- | `subagents:failed` | Agent errored, stopped, or aborted | same as completed + `error`, `status` |
340
+ | `subagents:created` | Agent record registered | `id`, `type`, `description`, `runId`, `mode` |
341
+ | `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description`, `runId`, `mode` |
342
+ | `subagents:completed` | Agent reaches a terminal state | `id`, `type`, `runId`, `mode`, `status`, `terminalReason`, `activeRuntimeMs`, `tokens`, `toolUses`, `result` |
343
+ | `subagents:resumed` | A resumed turn reaches a terminal state | completed-event shape plus `runId`, `status`, `terminalReason`, and `error` |
344
+ | `subagents:failed` | Agent ends in an error or stopped state | same as completed + `error`, `status`, `terminalReason` |
317
345
  | `subagents:steered` | Steering message sent | `id`, `message` |
318
346
  | `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount` |
319
347
  | `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
@@ -373,7 +401,7 @@ Access the subagent service from another extension at runtime:
373
401
  ```typescript
374
402
  const { getSubagentsService } = await import("@nklisch/pi-subagents");
375
403
  const svc = getSubagentsService();
376
- svc?.spawn("Explore", "Check for stale TODOs");
404
+ void svc?.launch("Explore", "Check for stale TODOs", { mode: "detached" });
377
405
  ```
378
406
 
379
407
  Declare this package as an optional peer dependency.
@@ -1,5 +1,86 @@
1
1
  import { ModelThinkingLevel } from '@earendil-works/pi-ai';
2
2
 
3
+ /** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
4
+ /**
5
+ * Lifetime usage components, accumulated via `message_end` events. Survives
6
+ * compaction (which replaces session.state.messages and would reset any
7
+ * stats-derived sum). cacheRead is excluded because each turn's cacheRead is
8
+ * the cumulative cached prefix re-read on that one call — summing across
9
+ * turns counts the prefix N times. See issue #38.
10
+ */
11
+ type LifetimeUsage = {
12
+ input: number;
13
+ output: number;
14
+ cacheWrite: number;
15
+ };
16
+
17
+ /** Authoritative coarse lifecycle state and metrics for one subagent record. */
18
+
19
+ type SubagentStatus = "queued" | "running" | "completed" | "stopped" | "error";
20
+ type SubagentTerminalReason = "completed" | "explicit_stop" | "parent_cancelled" | "runtime_timeout" | "turn_limit_graceful" | "turn_limit_hard" | "lifecycle_abort" | "provider_failure" | "execution_failure" | "workspace_teardown_failure";
21
+ type SubagentStopReason = "explicit_stop" | "parent_cancelled" | "runtime_timeout";
22
+
23
+ /**
24
+ * workspace.ts — The single generative extension seam (ADR 0002, Phase 16 Step 2).
25
+ *
26
+ * "Where does a child run, and what brackets the run?" is a strategy (git
27
+ * worktree, container, tmpdir, remote sandbox), not core behavior. The core
28
+ * needs only a working directory plus a disposal hook; the default — the
29
+ * parent's cwd, with no setup/teardown — is always correct.
30
+ *
31
+ * Unlike the observational lifecycle events in child-lifecycle.ts, this is a
32
+ * *generative* seam: a registered provider returns a value the core consumes
33
+ * synchronously at run-start. The core has no knowledge of git or worktrees.
34
+ */
35
+
36
+ /** Context the core hands a provider when a child run starts. */
37
+ interface WorkspacePrepareContext {
38
+ agentId: string;
39
+ agentType: SubagentType;
40
+ baseCwd: string;
41
+ invocation?: AgentInvocation;
42
+ }
43
+ /** Outcome the core reports to a workspace when the run ends. */
44
+ interface WorkspaceDisposeOutcome {
45
+ status: SubagentStatus;
46
+ description: string;
47
+ }
48
+ /** What dispose may hand back for the core to fold into the child result. */
49
+ interface WorkspaceDisposeResult {
50
+ /** Appended verbatim to the child's result text — the provider owns the wording. */
51
+ resultAddendum?: string;
52
+ }
53
+ /** A prepared working directory plus its bracketed teardown. Born complete. */
54
+ interface Workspace {
55
+ /** The working directory — already exists when the workspace is handed back. */
56
+ readonly cwd: string;
57
+ dispose(outcome: WorkspaceDisposeOutcome): WorkspaceDisposeResult | undefined;
58
+ }
59
+ /** The single generative seam: supplies a child's workspace. */
60
+ interface WorkspaceProvider {
61
+ prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined>;
62
+ }
63
+
64
+ /**
65
+ * types.ts — Type definitions for the subagent system.
66
+ */
67
+
68
+ /** Pi's model-capability thinking levels, including the explicit `off` value. */
69
+ type ThinkingLevel = ModelThinkingLevel;
70
+ /** Agent type: any string name (built-in defaults or user-defined). */
71
+ type SubagentType = string;
72
+ /** Delivery mode for one initial or resumed run. */
73
+ type SubagentMode = "joined" | "detached";
74
+ interface AgentInvocation {
75
+ /** Exact effective model label in `provider/id` form. */
76
+ modelName?: string;
77
+ thinking?: ThinkingLevel;
78
+ maxTurns?: number;
79
+ inheritContext?: boolean;
80
+ mode?: SubagentMode;
81
+ timeoutSeconds?: number;
82
+ }
83
+
3
84
  /**
4
85
  * lifecycle-interceptor.ts — Ordered, generative lifecycle interception.
5
86
  *
@@ -8,18 +89,19 @@ import { ModelThinkingLevel } from '@earendil-works/pi-ai';
8
89
  * It owns ordering, cancellation, registration lifetime, and the finite
9
90
  * continuation bound; it does not expose sessions, records, models, or queues.
10
91
  */
92
+
11
93
  /** The fixed bound prevents a provider from turning completion into an unbounded loop. */
12
94
  declare const MAX_LIFECYCLE_CONTINUATION_ROUNDS = 3;
13
95
  type SubagentExecutionPhase = "initial" | "resume";
14
96
  type SubagentExecutionOrigin = "tool" | "service";
15
- type SubagentExecutionMode = "foreground" | "background";
97
+ type SubagentExecutionMode = SubagentMode;
16
98
  type SubagentExecutionAdmission = "immediate" | "queued";
17
- type SubagentLifecycleOutcome = "completed" | "steered" | "aborted";
99
+ type SubagentLifecycleOutcome = "completed" | "turn_limit_graceful" | "turn_limit_hard";
18
100
  /** Immutable identifiers for one initial or resumed execution attempt. */
19
101
  interface SubagentLifecycleIdentity {
20
102
  readonly agentId: string;
21
103
  readonly sessionId: string;
22
- readonly runId: string;
104
+ readonly runId: number;
23
105
  readonly agentType: string;
24
106
  readonly parentSessionId?: string;
25
107
  }
@@ -80,164 +162,116 @@ interface SubagentLifecycleRegistration {
80
162
  dispose(): Promise<void>;
81
163
  }
82
164
 
83
- /**
84
- * types.ts — Type definitions for the subagent system.
85
- */
165
+ /** Public cross-extension contract for subagent execution control. */
86
166
 
87
- /** Pi's model-capability thinking levels, including the explicit `off` value. */
88
- type ThinkingLevel = ModelThinkingLevel;
89
- /** Agent type: any string name (built-in defaults or user-defined). */
90
- type SubagentType = string;
91
- interface AgentInvocation {
92
- /** Exact effective model label in `provider/id` form. */
93
- modelName?: string;
94
- thinking?: ThinkingLevel;
95
- maxTurns?: number;
96
- inheritContext?: boolean;
97
- runInBackground?: boolean;
98
- }
99
-
100
- /** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
101
- /**
102
- * Lifetime usage components, accumulated via `message_end` events. Survives
103
- * compaction (which replaces session.state.messages and would reset any
104
- * stats-derived sum). cacheRead is excluded because each turn's cacheRead is
105
- * the cumulative cached prefix re-read on that one call — summing across
106
- * turns counts the prefix N times. See issue #38.
107
- */
108
- type LifetimeUsage = {
109
- input: number;
110
- output: number;
111
- cacheWrite: number;
112
- };
113
-
114
- /**
115
- * subagent-state.ts — SubagentState value object: lifecycle status, metrics, and live activity.
116
- *
117
- * Owns the passive, readable state of a subagent — status, result, error,
118
- * timestamps, stats (toolUses, lifetimeUsage, compactionCount), and live-activity
119
- * fields (turnCount, activeTools, responseText) — together with the transition
120
- * methods (markRunning, markCompleted, …), accumulation methods
121
- * (incrementToolUses, addUsage, incrementCompactions), and live-activity
122
- * transition methods (incrementTurnCount, addActiveTool, removeActiveTool,
123
- * resetResponseText, appendResponseText) that mutate them.
124
- *
125
- * State is encapsulated behind getters; external code reads through them but
126
- * mutates only via the transition/accumulation methods. The value object owns
127
- * all of its own mutations — no field is written from outside.
128
- *
129
- * Subagent holds one of these privately and delegates its getters and mutation
130
- * methods to it. Extracting it lets the lifecycle state machine and the
131
- * session-event observer be unit-tested without constructing an executor.
132
- */
133
-
134
- type SubagentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
135
-
136
- /**
137
- * workspace.ts — The single generative extension seam (ADR 0002, Phase 16 Step 2).
138
- *
139
- * "Where does a child run, and what brackets the run?" is a strategy (git
140
- * worktree, container, tmpdir, remote sandbox), not core behavior. The core
141
- * needs only a working directory plus a disposal hook; the default — the
142
- * parent's cwd, with no setup/teardown — is always correct.
143
- *
144
- * Unlike the observational lifecycle events in child-lifecycle.ts, this is a
145
- * *generative* seam: a registered provider returns a value the core consumes
146
- * synchronously at run-start. The core has no knowledge of git or worktrees.
147
- */
148
-
149
- /** Context the core hands a provider when a child run starts. */
150
- interface WorkspacePrepareContext {
151
- agentId: string;
152
- agentType: SubagentType;
153
- baseCwd: string;
154
- invocation?: AgentInvocation;
155
- }
156
- /** Outcome the core reports to a workspace when the run ends. */
157
- interface WorkspaceDisposeOutcome {
158
- status: SubagentStatus;
159
- description: string;
160
- }
161
- /** What dispose may hand back for the core to fold into the child result. */
162
- interface WorkspaceDisposeResult {
163
- /** Appended verbatim to the child's result text — the provider owns the wording. */
164
- resultAddendum?: string;
165
- }
166
- /** A prepared working directory plus its bracketed teardown. Born complete. */
167
- interface Workspace {
168
- /** The working directory — already exists when the workspace is handed back. */
169
- readonly cwd: string;
170
- dispose(outcome: WorkspaceDisposeOutcome): WorkspaceDisposeResult | undefined;
171
- }
172
- /** The single generative seam: supplies a child's workspace. */
173
- interface WorkspaceProvider {
174
- prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined>;
175
- }
176
-
177
- /**
178
- * service.ts — Public API surface for cross-extension access to subagents.
179
- *
180
- * Consumers declare this package as an optional peer dependency and use
181
- * dynamic import to access the accessor functions:
182
- *
183
- * const { getSubagentsService } = await import("@nklisch/pi-subagents");
184
- * const svc = getSubagentsService();
185
- * svc?.spawn("Explore", "Check for stale TODOs");
186
- */
187
-
188
- /** Serializable snapshot of an agent's state — no live session objects. */
189
167
  interface SubagentRecord {
190
168
  id: string;
191
169
  type: string;
192
170
  description: string;
171
+ runId: number;
172
+ mode: SubagentExecutionMode;
193
173
  status: SubagentStatus;
174
+ stopRequested: boolean;
175
+ terminalReason?: SubagentTerminalReason;
194
176
  result?: string;
195
177
  error?: string;
196
178
  toolUses: number;
197
179
  startedAt: number;
198
180
  completedAt?: number;
181
+ activeRuntimeMs: number;
182
+ modelLabel: string;
183
+ thinkingLevel: ThinkingLevel;
184
+ activeTools: string[];
185
+ currentActivity: string;
199
186
  lifetimeUsage: LifetimeUsage;
200
187
  compactionCount: number;
188
+ outputFile?: string;
201
189
  }
202
- /** Options for spawning an agent via the service. */
203
- interface SpawnOptions {
190
+ interface LaunchOptions {
204
191
  description?: string;
205
192
  model?: string;
206
193
  maxTurns?: number;
207
- thinkingLevel?: string;
194
+ thinkingLevel?: ThinkingLevel;
208
195
  inheritContext?: boolean;
209
- foreground?: boolean;
210
- bypassQueue?: boolean;
196
+ mode?: SubagentExecutionMode;
197
+ timeoutSeconds?: number;
198
+ signal?: AbortSignal;
199
+ }
200
+ type LaunchDelivery = {
201
+ kind: "detached";
202
+ agentId: string;
203
+ runId: number;
204
+ } | {
205
+ kind: "joined";
206
+ record: SubagentRecord;
207
+ };
208
+ type ResumeDelivery = LaunchDelivery | {
209
+ kind: "not_found";
210
+ agentId: string;
211
+ } | {
212
+ kind: "wrong_state";
213
+ agentId: string;
214
+ status: SubagentStatus;
215
+ };
216
+ type StopDelivery = {
217
+ kind: "stopped";
218
+ agentId: string;
219
+ runId: number;
220
+ reason: SubagentStopReason;
221
+ record: SubagentRecord;
222
+ } | {
223
+ kind: "stop_pending";
224
+ agentId: string;
225
+ runId: number;
226
+ reason: SubagentStopReason;
227
+ record: SubagentRecord;
228
+ } | {
229
+ kind: "already_terminal";
230
+ agentId: string;
231
+ runId: number;
232
+ record: SubagentRecord;
233
+ } | {
234
+ kind: "not_found";
235
+ agentId: string;
236
+ };
237
+ type SteerDelivery = {
238
+ kind: "delivered" | "buffered";
239
+ agentId: string;
240
+ runId: number;
241
+ } | {
242
+ kind: "rejected";
243
+ agentId: string;
244
+ runId: number;
245
+ status: SubagentStatus;
246
+ } | {
247
+ kind: "not_found";
248
+ agentId: string;
249
+ };
250
+ type ResultDelivery = {
251
+ kind: "result";
252
+ record: SubagentRecord;
253
+ } | {
254
+ kind: "not_found";
255
+ agentId: string;
256
+ };
257
+ type SubagentListState = "active" | "terminal" | "all";
258
+ interface ListOptions {
259
+ state?: SubagentListState;
260
+ limit?: number;
211
261
  }
212
- /** The public service contract for cross-extension subagent access. */
213
262
  interface SubagentsService {
214
- /** Spawn an agent. Returns the agent ID immediately. */
215
- spawn(type: string, prompt: string, options?: SpawnOptions): string;
216
- /** Get a snapshot of an agent's current state. */
217
- getRecord(id: string): SubagentRecord | undefined;
218
- /** List all tracked agents, most recent first. */
219
- listAgents(): SubagentRecord[];
220
- /** Abort a running or queued agent. Returns false if not found. */
221
- abort(id: string): boolean;
222
- /** Send a steering message to a running agent. */
223
- steer(id: string, message: string): Promise<boolean>;
224
- /** Wait for all running and queued agents to complete. */
263
+ launch(type: string, prompt: string, options?: LaunchOptions): Promise<LaunchDelivery>;
264
+ resume(agentId: string, prompt: string, options?: Pick<LaunchOptions, "mode" | "timeoutSeconds" | "signal">): Promise<ResumeDelivery>;
265
+ stop(agentId: string, settlementTimeoutSeconds?: number): Promise<StopDelivery>;
266
+ steer(agentId: string, message: string): Promise<SteerDelivery>;
267
+ list(options?: ListOptions): SubagentRecord[];
268
+ getResult(agentId: string): ResultDelivery;
269
+ getRecord(agentId: string): SubagentRecord | undefined;
225
270
  waitForAll(): Promise<void>;
226
- /** Whether any agents are running or queued. */
227
271
  hasRunning(): boolean;
228
- /**
229
- * Register the single workspace provider that supplies a child's working
230
- * directory plus bracketed setup/teardown. Throws if one is already
231
- * registered. Returns a disposer that unregisters the provider.
232
- */
233
272
  registerWorkspaceProvider(provider: WorkspaceProvider): () => void;
234
- /**
235
- * Register an ordered async lifecycle provider. It receives only immutable
236
- * execution facts and prompt/result decisions, never a manager or session.
237
- */
238
273
  registerLifecycleInterceptor(interceptor: SubagentLifecycleInterceptor): SubagentLifecycleRegistration;
239
274
  }
240
- /** Event channel constants for pi.events subscriptions. */
241
275
  declare const SUBAGENT_EVENTS: {
242
276
  readonly STARTED: "subagents:started";
243
277
  readonly COMPLETED: "subagents:completed";
@@ -247,12 +281,9 @@ declare const SUBAGENT_EVENTS: {
247
281
  readonly CREATED: "subagents:created";
248
282
  readonly STEERED: "subagents:steered";
249
283
  };
250
- /** Publish the SubagentsService on globalThis for cross-extension access. */
251
284
  declare function publishSubagentsService(service: SubagentsService): void;
252
- /** Retrieve the published SubagentsService, or undefined if not yet published. */
253
285
  declare function getSubagentsService(): SubagentsService | undefined;
254
- /** Remove the SubagentsService from globalThis (call on shutdown/reload). */
255
286
  declare function unpublishSubagentsService(): void;
256
287
 
257
288
  export { MAX_LIFECYCLE_CONTINUATION_ROUNDS, SUBAGENT_EVENTS, getSubagentsService, publishSubagentsService, unpublishSubagentsService };
258
- export type { LifetimeUsage, SpawnOptions, SubagentExecutionAdmission, SubagentExecutionMode, SubagentExecutionOrigin, SubagentExecutionPhase, SubagentLifecycleCompletionContext, SubagentLifecycleCompletionDecision, SubagentLifecycleExecutionPath, SubagentLifecycleIdentity, SubagentLifecycleInterceptor, SubagentLifecycleOutcome, SubagentLifecycleRegistration, SubagentLifecycleStartContext, SubagentLifecycleStartDecision, SubagentRecord, SubagentStatus, SubagentsService, Workspace, WorkspaceDisposeOutcome, WorkspaceDisposeResult, WorkspacePrepareContext, WorkspaceProvider };
289
+ export type { LaunchDelivery, LaunchOptions, LifetimeUsage, ListOptions, ResultDelivery, ResumeDelivery, SteerDelivery, StopDelivery, SubagentExecutionAdmission, SubagentExecutionMode, SubagentExecutionOrigin, SubagentExecutionPhase, SubagentLifecycleCompletionContext, SubagentLifecycleCompletionDecision, SubagentLifecycleExecutionPath, SubagentLifecycleIdentity, SubagentLifecycleInterceptor, SubagentLifecycleOutcome, SubagentLifecycleRegistration, SubagentLifecycleStartContext, SubagentLifecycleStartDecision, SubagentListState, SubagentExecutionMode as SubagentMode, SubagentRecord, SubagentStatus, SubagentStopReason, SubagentTerminalReason, SubagentsService, Workspace, WorkspaceDisposeOutcome, WorkspaceDisposeResult, WorkspacePrepareContext, WorkspaceProvider };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Generic layered settings loader for `@gotgenes/pi-*` extensions.
2
+ * Generic layered settings loader for `@nklisch/pi-*` extensions.
3
3
  *
4
4
  * Extensions that store configuration in JSON files under a global agent
5
5
  * directory and a per-project `.pi/` folder share the same three-step idiom:
@@ -15,7 +15,7 @@
15
15
  * ## Usage
16
16
  *
17
17
  * ```typescript
18
- * import { loadLayeredSettings, type LayeredSettingsSource } from "@gotgenes/pi-subagents/settings";
18
+ * import { loadLayeredSettings, type LayeredSettingsSource } from "@nklisch/pi-subagents/settings";
19
19
  *
20
20
  * interface MyConfig { enabled?: boolean; limit?: number }
21
21
  *