@nklisch/pi-enhanced 0.2.7 → 0.3.1

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 +27 -0
  15. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +76 -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 +118 -104
  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 -239
  37. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -700
  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 -122
  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 +488 -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 -94
  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 +412 -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
@@ -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
  *
@@ -34,11 +34,12 @@ only, not lifecycle semantics or consumer contracts.
34
34
 
35
35
  ## Scope guard
36
36
 
37
- The published package keeps the narrow built-in background widget, session
37
+ The published package keeps the narrow built-in detached widget, session
38
38
  navigator, and settings command accepted by ADR 0004. These status surfaces
39
- show each subagent's exact effective model, exact effective thinking level, and elapsed or final runtime. The
40
- package excludes the retired agent editors, wizards, and bespoke conversation
41
- viewer from the import branch. Reviving those surfaces is a new decision, not
42
- a restoration. Children inherit extension tools through a registration-open
43
- denylist policy; scheduling, memory, nested delegation, worktrees, and RPC stay
44
- outside the core.
39
+ show each subagent's exact effective model, exact effective thinking level,
40
+ active or final runtime, run id, mode, and terminal reason. The package
41
+ excludes the retired agent editors, wizards, and bespoke conversation viewer
42
+ from the import branch. Reviving those surfaces is a new decision, not a
43
+ restoration. Children inherit extension tools through a registration-open
44
+ denylist policy; batch identity, hard process termination, and RPC stay outside
45
+ the core.
@@ -9,8 +9,9 @@ order — capability the observational lifecycle events cannot provide.
9
9
  ## Product shape
10
10
 
11
11
  - A minimal core that owns configuration, model-runtime inheritance, sessions,
12
- tools, queueing, concurrency, consumption-aware retention, workspaces, turn
13
- limits, and disposal.
12
+ tools, joined/detached delivery, queueing, concurrency,
13
+ consumption-aware retention, workspaces, turn limits, runtime deadlines,
14
+ cooperative stop/resume, and disposal.
14
15
  - Extensions — including the fork's own interceptor seam — compose on that
15
16
  core rather than forking it (ADR 0002).
16
17
  - Two public entrypoints only: the root service contract and `./settings`.
@@ -20,15 +21,16 @@ order — capability the observational lifecycle events cannot provide.
20
21
  ## Consumers and compatibility
21
22
 
22
23
  The root export is loaded by pi-plugins through a verified packaged loader;
23
- its shape is a hard compatibility surface. The published package includes a narrow built-in operator UI: the background
24
- status widget, session navigator, and settings command. Every subagent status
25
- surface identifies the exact effective model, exact effective thinking level, and
26
- elapsed or final runtime. The
27
- package excludes the retired experimental editors, wizards, and bespoke
24
+ its shape is a hard compatibility surface. The published package includes a
25
+ narrow built-in operator UI: the detached status widget, session navigator, and
26
+ settings command. Every subagent status surface identifies the exact effective
27
+ model, exact effective thinking level, active or final runtime, run id, delivery
28
+ mode, and terminal reason when one exists. Parent-only lifecycle tools are
29
+ centrally registered and removed from child tool definitions, preventing
30
+ recursive orchestration while preserving ordinary inherited extension tools.
31
+ The package excludes the retired experimental editors, wizards, and bespoke
28
32
  conversation viewer from the import branch (see ADR 0004 for the UI direction
29
- history). Child sessions inherit parent extension tools through a denylist
30
- policy so synchronous and lifecycle-time registrations remain available without
31
- exposing recursive orchestration tools.
33
+ history).
32
34
 
33
35
  ## Fork posture
34
36
 
@@ -9,11 +9,11 @@ This document describes the architecture of the pi-subagents fork: a focused, co
9
9
  2. **Composable by default** — other extensions can spawn agents, observe their lifecycle, and display their state without importing this package directly.
10
10
  3. **Typed API boundary** — this package exports a `SubagentsService` interface and `Symbol.for()` accessors (`publishSubagentsService` / `getSubagentsService`).
11
11
  Consumers declare this package as an optional peer dependency and use dynamic import for compile-time types.
12
- The runtime bridge is `Symbol.for("@gotgenes/pi-subagents:service")` on `globalThis` — no separate API package.
12
+ The runtime bridge is `Symbol.for("@nklisch/pi-subagents:service")` on `globalThis` — no separate API package.
13
13
  4. **No time-based scheduling** — cron-style timed dispatch (upstream's `schedule.ts` subsystem) is removed from the core (#52).
14
- Timed dispatch is a separate concern that any extension can implement by calling `spawn()` on the published API.
14
+ Timed dispatch is a separate concern that any extension can implement by calling `launch()` on the published API.
15
15
  The max-concurrent admission gate is not scheduling in this sense — concurrency management stays in core.
16
- 5. **UI is an in-core, substitutable consumer** — [ADR-0004](../decisions/0004-reconsider-ui-direction.md) records the per-component decision: the widget shrinks to background agents only, the bespoke conversation viewer is replaced by native session navigation, the `/agents` command is dissolved into focused surfaces, and the surviving UI stays in the core as a reactive consumer (not extracted to a separate package).
16
+ 5. **UI is an in-core, substitutable consumer** — [ADR-0004](../decisions/0004-reconsider-ui-direction.md) records the per-component decision: the widget shows detached agents only, the bespoke conversation viewer is replaced by native session navigation, the `/agents` command is dissolved into focused surfaces, and the surviving UI stays in the core as a reactive consumer (not extracted to a separate package).
17
17
  Extraction remains an available future option because the composition invariant holds — the core is byte-for-byte identical with or without a given UI consumer.
18
18
  6. **Snapshot identity; share the canonical runtime** — mutable parent identity and prompt state are frozen into `ParentSnapshot` at spawn. The parent `ModelRuntime` is deliberately shared so child sessions preserve runtime provider and authentication registrations; no live session context is captured.
19
19
  7. **Subscribe, don't thread** — observation of agent progress uses direct session-event subscription, not callback parameters threaded through multiple layers.
@@ -51,11 +51,11 @@ flowchart TB
51
51
 
52
52
  subgraph lifecycle["Lifecycle domain"]
53
53
  direction TB
54
- SubagentManager["SubagentManager<br/>(spawn, abort, collection)"]
55
- ConcurrencyLimiter["ConcurrencyLimiter<br/>(thunk admission gate)"]
54
+ SubagentManager["SubagentManager<br/>(launch, resume, stop, collection)"]
55
+ ConcurrencyLimiter["ConcurrencyLimiter<br/>(cancellable FIFO admission)"]
56
56
  CreateSubagentSession["createSubagentSession<br/>(assembly factory)"]
57
57
  SubagentSession["SubagentSession<br/>(turn loop, steer, dispose)"]
58
- Subagent["Subagent<br/>(status, behavior: abort/steer/run lifecycle)"]
58
+ Subagent["Subagent<br/>(status, run lease, stop/steer lifecycle)"]
59
59
  ParentSnapshot["ParentSnapshot<br/>(frozen parent state)"]
60
60
  Workspace["workspace<br/>(provider seam: child cwd + teardown)"]
61
61
  end
@@ -69,12 +69,15 @@ flowchart TB
69
69
  subgraph tools["Tools domain"]
70
70
  direction TB
71
71
  AgentTool["subagent tool<br/>(dispatch)"]
72
+ QueryTool["query_subagent_session<br/>(bounded read)"]
72
73
  ResultRenderer["result-renderer<br/>(pure rendering)"]
73
74
  SpawnConfig["spawn-config<br/>(resolve params)"]
74
- FgRunner["foreground-runner"]
75
- BgSpawner["background-spawner"]
75
+ Resume["resume_subagent"]
76
+ Stop["stop_subagent"]
77
+ List["list_subagents"]
76
78
  GetResult["get_subagent_result"]
77
79
  Steer["steer_subagent"]
80
+ Query["query_subagent_session"]
78
81
  end
79
82
 
80
83
  subgraph ui["UI domain"]
@@ -115,11 +118,11 @@ classDiagram
115
118
  +subagentSession?: SubagentSession
116
119
  +toolCallId?: string
117
120
  +markRunning() delegates
118
- +markCompleted() delegates
119
- +run()
120
- +resume(prompt, signal)
121
- +abort(): boolean
121
+ +launch()
122
+ +resume(prompt, mode, signal)
123
+ +requestStop(reason): boolean
122
124
  +steer(message): Promise~SteerOutcome~
125
+ +settlement: Promise~void~
123
126
  +isSessionReady(): boolean
124
127
  +getConversation(): string | undefined
125
128
  +getContextPercent(): number | null
@@ -163,11 +166,11 @@ classDiagram
163
166
 
164
167
  class SubagentManager {
165
168
  +spawn(snapshot, type, prompt, config)
166
- +spawnAndWait(snapshot, type, prompt, config)
167
- +resume(id, prompt, signal)
169
+ +launch(snapshot, type, prompt, config)
170
+ +resume(id, prompt, mode, signal)
171
+ +stop(id, timeout): StopOutcome
168
172
  +getRecord(id): Subagent
169
173
  +listAgents(): Subagent[]
170
- +abort(id)
171
174
  }
172
175
 
173
176
  class AgentTypeRegistry {
@@ -187,11 +190,13 @@ classDiagram
187
190
  }
188
191
 
189
192
  class SubagentsService {
190
- +spawn(type, prompt, options?)
193
+ +launch(type, prompt, options?): Promise~LaunchDelivery~
194
+ +resume(id, prompt, options?): Promise~ResumeDelivery~
195
+ +stop(id, timeout?): Promise~StopDelivery~
196
+ +steer(id, message): Promise~SteerDelivery~
197
+ +list(options?): SubagentRecord[]
198
+ +getResult(id): ResultDelivery
191
199
  +getRecord(id): SubagentRecord
192
- +listAgents(): SubagentRecord[]
193
- +abort(id)
194
- +steer(id, message)
195
200
  +waitForAll()
196
201
  +hasRunning(): boolean
197
202
  }
@@ -208,32 +213,20 @@ classDiagram
208
213
 
209
214
  ```mermaid
210
215
  stateDiagram-v2
211
- [*] --> queued : spawn (background, at capacity)
212
- [*] --> running : spawn (foreground or under limit)
216
+ [*] --> queued : launch / resume through FIFO admission
213
217
  queued --> running : capacity available
214
- queued --> stopped : stopped before admission
215
- running --> completed : all turns finished
216
- running --> error : unhandled exception
217
- running --> aborted : abort() called
218
- running --> stopped : max turns reached
219
- running --> steered : steer message injected
220
- steered --> running : continues with message
218
+ queued --> stopped : queued stop or joined cancellation
219
+ running --> completed : success or graceful turn limit
220
+ running --> stopped : explicit stop, parent cancellation, timeout, hard limit, lifecycle abort
221
+ running --> error : provider, execution, or workspace teardown failure
221
222
  completed --> resume_wait : reserve resume
222
- stopped --> resume_wait : reserve after abort
223
+ stopped --> resume_wait : reserve resume
223
224
  error --> resume_wait : reserve resume
224
- aborted --> resume_wait : reserve resume
225
225
  resume_wait --> running : prior execution settled + Pi idle
226
- resume_wait --> stopped : abort resume wait
226
+ resume_wait --> stopped : stop during wind-down
227
227
  completed --> [*]
228
- error --> [*]
229
- aborted --> [*]
230
228
  stopped --> [*]
231
-
232
- note right of running
233
- markCompleted, markAborted,
234
- markSteered, and markError
235
- are no-ops when status is stopped
236
- end note
229
+ error --> [*]
237
230
  ```
238
231
 
239
232
  Note: `markStopped` always succeeds regardless of current status.
@@ -241,7 +234,7 @@ Other terminal transitions guard against overwriting `stopped` — once an agent
241
234
 
242
235
  Resume admission is a record-owned lease. A genuinely running or already-reserved record rejects a second resume before reaching Pi. A stopped record may still own a winding-down prompt, and Pi may still be finishing post-run continuation after the domain result looks terminal; the lease therefore spans the prior execution promise and Pi's authoritative `agent_settled`/`isIdle` boundary. Retention, abort, result waiting, and manager shutdown all treat the lease as active.
243
236
 
244
- Terminal outcomes also carry orthogonal consumption state. Foreground delivery, `get_subagent_result`, and a queued completion notification mark the outcome consumed. Records remain available for the parent session; only their heavy live child sessions are released after the configured consumed or unconsumed retention window. Released records keep their result and persisted transcript pointer but cannot resume. Release detaches the session synchronously before awaiting teardown, so it cannot race a new resume.
237
+ Terminal outcomes also carry orthogonal consumption state. Joined delivery, `get_subagent_result`, and a queued detached completion notification mark the outcome consumed. Records remain available for the parent session; only their heavy live child sessions are released after the configured consumed or unconsumed retention window. Released records keep their result and persisted transcript pointer but cannot resume. Release detaches the session synchronously before awaiting teardown, so it cannot race a new resume.
245
238
 
246
239
  Completion notifications are held while the parent agent run is active and flushed on `agent_settled`, rechecking consumption before enqueueing a follow-up. A blocking `get_subagent_result` request claims direct delivery before awaiting, so the same terminal outcome cannot also enqueue a completion follow-up. Child session creation shares the parent model runtime and leaves extension-tool registration open; a denylist removes disallowed built-ins and recursive orchestration tools across registry refreshes. Child teardown mirrors Pi's managed root lifecycle: it emits and awaits `session_shutdown` before `AgentSession.dispose()` revokes extension contexts, then publishes the child `disposed` event.
247
240
 
@@ -259,11 +252,11 @@ sequenceDiagram
259
252
  participant Sub as SubagentSession
260
253
  participant Child as Child session
261
254
 
262
- LLM->>Tool: subagent(type, prompt, ...)
255
+ LLM->>Tool: subagent(type, prompt, mode, ...)
263
256
  Tool->>Spawn: resolveSpawnConfig(params)
264
257
  Spawn-->>Tool: ResolvedSpawnConfig
265
- Tool->>Mgr: spawn(snapshot, type, prompt, config)
266
- Mgr->>Ag: run()
258
+ Tool->>Mgr: launch(snapshot, type, prompt, config)
259
+ Mgr->>Ag: schedule lease through FIFO admission
267
260
  Ag->>Factory: createSubagentSession(params, deps)
268
261
  Factory->>Asm: assembleSessionConfig(type, ctx, opts, env, registry, io)
269
262
  Asm-->>Factory: SessionConfig
@@ -275,14 +268,14 @@ sequenceDiagram
275
268
  Child-->>Sub: result text
276
269
  Sub-->>Ag: TurnLoopResult
277
270
  Ag-->>Mgr: update Subagent
278
- Mgr-->>Tool: Subagent
279
- Tool-->>LLM: formatted result
271
+ Mgr-->>Tool: detached identity or joined settled record
272
+ Tool-->>LLM: bounded result or delivery identity
280
273
  Note over Mgr: retention releases heavy sessions but keeps terminal records and transcript pointers
281
274
  ```
282
275
 
283
276
  ## Module organization
284
277
 
285
- The extension has 62 source files organized into six domains plus entry-point wiring.
278
+ The extension has 67 source files organized into six domains plus entry-point wiring.
286
279
  All eight domains have directories: `config/`, `session/`, `lifecycle/`, `observation/`, `service/`, `tools/`, `ui/`, and `handlers/`.
287
280
  Issue #164 moved the 26 previously flat root-level files into five new domain directories, reducing the root to 5 files + 8 directories.
288
281
 
@@ -295,7 +288,7 @@ src/
295
288
  ├── types.ts shared type definitions
296
289
  ├── settings.ts SettingsManager (persistent operational settings)
297
290
  ├── debug.ts debug logging utility
298
- ├── layered-settings.ts loadLayeredSettings helper (published as @gotgenes/pi-subagents/settings)
291
+ ├── layered-settings.ts loadLayeredSettings helper (published as @nklisch/pi-subagents/settings)
299
292
 
300
293
  ├── config/ agent type definitions and resolution
301
294
  │ ├── agent-types.ts AgentTypeRegistry class
@@ -308,6 +301,8 @@ src/
308
301
  │ ├── prompts.ts system prompt building
309
302
  │ ├── content-items.ts shared message content parsing (tool-call names, assistant content)
310
303
  │ ├── context.ts parent conversation extraction
304
+ │ ├── query.ts bounded live/file transcript projection and search
305
+ │ ├── query-source.ts shared JSONL adapter for query and native navigation
311
306
  │ ├── conversation.ts render a session's messages as formatted text
312
307
  │ ├── env.ts git/platform detection
313
308
  │ ├── model-resolver.ts fuzzy model name resolution
@@ -318,11 +313,11 @@ src/
318
313
  │ ├── create-subagent-session.ts assembly factory: session creation, binding, tool filtering
319
314
  │ ├── subagent-session.ts born-complete child session: turn loop, steer, dispose
320
315
  │ ├── turn-limits.ts normalizeMaxTurns (turn-count policy)
321
- │ ├── subagent.ts owns full execution lifecycle (run, abort, steer)
322
- │ ├── subagent-state.ts lifecycle status + metrics value object (transitions, accumulators)
323
- │ ├── run-listeners.ts per-run observer-unsub and signal-detach handles
316
+ │ ├── subagent.ts authoritative run lease and stop/resume lifecycle
317
+ │ ├── subagent-state.ts coarse status, terminal reasons, and metrics
318
+ │ ├── run-listeners.ts per-run observer-unsubscribe handle
324
319
  │ ├── workspace-bracket.ts child workspace prepare/dispose lifecycle
325
- │ ├── concurrency-limiter.ts background admission gate: schedules run thunks FIFO against the limit
320
+ │ ├── concurrency-limiter.ts cancellable FIFO admission gate for all run modes
326
321
  │ ├── parent-snapshot.ts immutable spawn-time parent state
327
322
  │ ├── child-lifecycle.ts child-execution lifecycle event publisher
328
323
  │ ├── workspace.ts workspace provider seam (generative extension surface)
@@ -343,10 +338,12 @@ src/
343
338
  │ ├── agent-tool.ts subagent tool definition, validation, dispatch
344
339
  │ ├── result-renderer.ts pure per-status result rendering
345
340
  │ ├── spawn-config.ts pure config resolution
346
- │ ├── foreground-runner.ts foreground execution loop
347
- │ ├── background-spawner.ts background spawn setup
341
+ │ ├── resume-tool.ts resume_subagent tool
342
+ │ ├── stop-tool.ts stop_subagent tool
343
+ │ ├── list-tool.ts list_subagents tool
348
344
  │ ├── get-result-tool.ts get_subagent_result tool
349
- │ ├── get-result-report.ts pure get_subagent_result report formatter
345
+ │ ├── query-session-tool.ts query_subagent_session tool
346
+ │ ├── parent-tool-registry.ts authoritative parent-only tool names
350
347
  │ ├── steer-tool.ts steer_subagent tool
351
348
  │ └── helpers.ts shared tool utilities
352
349
 
@@ -356,7 +353,7 @@ src/
356
353
  │ ├── display.ts pure formatters and shared types
357
354
  │ ├── subagents-settings.ts /subagents:settings command handler
358
355
  │ ├── session-navigation.ts pure session-selection and transcript-source logic
359
- │ └── session-navigator.ts /subagents:sessions command handler
356
+ │ └── session-navigator.ts /subagents:sessions command handler and search overlay
360
357
 
361
358
  └── handlers/ event handlers
362
359
  ├── index.ts barrel re-export
@@ -365,21 +362,43 @@ src/
365
362
  └── tool-start.ts tool_execution_start handler
366
363
  ```
367
364
 
365
+ ### Transcript query boundary
366
+
367
+ `query_subagent_session` is a parent-only, read-only pull surface. Its pure
368
+ `session/query.ts` projection correlates tool results to calls, searches every
369
+ complete visible field with literal case-insensitive matching, and returns
370
+ match-centered bounded excerpts with source-range metadata. Tool names and call
371
+ IDs remain complete only in the projection-local search/correlation data;
372
+ returned display values are bounded while stable entry IDs remain intact for the
373
+ overlay. Scope, ordering, 1–50 result limits, and stateless numeric offsets page
374
+ the ordered matching set; byte and line output bounds may shorten a page without
375
+ advancing beyond entries actually returned, but a non-empty page always emits a
376
+ bounded first entry. It reads a live child session when retained and otherwise
377
+ uses the same Pi JSONL/context adapter as native session navigation. No index,
378
+ cursor, or query state is persisted, and successful reads report complete
379
+ search rather than a partial-search state.
380
+
381
+ `/subagents:sessions` keeps Pi's native message, markdown, and tool-execution
382
+ components for the transcript body. Its overlay owns only ephemeral search
383
+ state and block-level match chrome; a release notification swaps the source to
384
+ the file snapshot, while an unavailable snapshot preserves the last readable
385
+ content and labels the degraded state.
386
+
368
387
  ### Observation model
369
388
 
370
389
  Record statistics (tool uses, token usage, compaction counts) and live activity (active tools, response text, turn counts) are updated by `record-observer.ts`, which subscribes directly to session events.
371
390
  This is the single per-child session subscription — all run state lives on the `Subagent` record.
372
391
 
373
- The widget maintains a bounded reactive read model of active background records and the small terminal linger set, populated by `SubagentManagerObserver` lifecycle callbacks. While active it refreshes that model at 500 ms; each refresh reads only those retained records and requests the normal Pi widget render, rather than cloning or sorting the manager's full terminal history. When only terminal linger records remain, it renders the completion state once and leaves the static widget registered without an interval until turn aging or clear removes it. The `/subagents:sessions` navigator reads messages via `Subagent.agentMessages` and subscribes to updates via `Subagent.subscribeToUpdates()` — no direct `AgentSession` reference (#277).
392
+ The widget maintains a bounded reactive read model of active detached records and the small terminal linger set, populated by `SubagentManagerObserver` lifecycle callbacks. While active it refreshes that model at 500 ms; each refresh reads only those retained records and requests the normal Pi widget render, rather than cloning or sorting the manager's full terminal history. When only terminal linger records remain, it renders the completion state once and leaves the static widget registered without an interval until turn aging or clear removes it. The `/subagents:sessions` navigator reads messages via `Subagent.agentMessages`, falls back to the retained JSONL source after release, and subscribes to both session and record-release updates — no direct `AgentSession` reference (#277).
374
393
 
375
394
  ## Cross-extension architecture
376
395
 
377
396
  ```mermaid
378
397
  flowchart TD
379
- subgraph core["@gotgenes/pi-subagents"]
398
+ subgraph core["@nklisch/pi-subagents"]
380
399
  direction TB
381
400
  exports["SubagentsService API<br/>publish / getSubagentsService<br/>SubagentRecord, SubagentStatus"]
382
- engine["Tools: subagent, get_subagent_result,<br/>steer_subagent<br/>SubagentManager, createSubagentSession, SubagentSession"]
401
+ engine["Tools: subagent, resume_subagent, stop_subagent,<br/>list_subagents, get_subagent_result, steer_subagent,<br/>query_subagent_session<br/>SubagentManager, createSubagentSession, SubagentSession"]
383
402
  ui_int["Internal UI: widget, session-navigator,<br/>subagents-settings"]
384
403
  end
385
404
 
@@ -388,14 +407,14 @@ flowchart TD
388
407
  core -- "Symbol.for on globalThis" --> future["any future extension"]
389
408
  ```
390
409
 
391
- Consumers call `getSubagentsService()?.spawn(...)` at runtime.
410
+ Consumers call `getSubagentsService()?.launch(...)` at runtime.
392
411
  They declare this package as an optional peer dependency and use dynamic import for compile-time types.
393
412
 
394
413
  ### What the core owns
395
414
 
396
- - The three tools: `subagent` (née `Agent`), `get_subagent_result`, `steer_subagent`.
397
- - `SubagentManager` — spawn, abort, resume, collection management, observer wiring.
398
- - `ConcurrencyLimiter` — background admission gate: schedules run thunks FIFO against a configurable concurrency limit.
415
+ - The parent-only tools: `subagent`, `resume_subagent`, `stop_subagent`, `list_subagents`, `get_subagent_result`, `steer_subagent`, and `query_subagent_session`.
416
+ - `SubagentManager` — launch, resume, cooperative stop, collection management, and observer wiring.
417
+ - `ConcurrencyLimiter` — cancellable FIFO admission gate shared by joined and detached runs.
399
418
  - `createSubagentSession` — assembly factory: session creation and extension binding; returns a born-complete `SubagentSession`.
400
419
  - `SubagentSession` — the born-complete child session: drives turn loops, exposes Pi's idle boundary, steers, and performs idempotent asynchronous teardown (`session_shutdown` → `AgentSession.dispose()` → child `disposed`).
401
420
  - `child-lifecycle` — publishes the child-execution lifecycle (`spawning`, `session-created` before `bindExtensions()`, `completed`, `disposed`) on `pi.events`.
@@ -426,7 +445,7 @@ They declare this package as an optional peer dependency and use dynamic import
426
445
 
427
446
  ## SubagentsService
428
447
 
429
- The `SubagentsService` interface, accessor functions, and serializable types are exported from `@gotgenes/pi-subagents` via the `./service` export map entry.
448
+ The `SubagentsService` interface, accessor functions, and serializable types are exported from `@nklisch/pi-subagents` via the root export.
430
449
  No separate API package is needed.
431
450
 
432
451
  Consumers declare this package as an optional peer dependency:
@@ -434,10 +453,10 @@ Consumers declare this package as an optional peer dependency:
434
453
  ```json
435
454
  {
436
455
  "peerDependencies": {
437
- "@gotgenes/pi-subagents": ">=5.0.0"
456
+ "@nklisch/pi-subagents": ">=18.0.0"
438
457
  },
439
458
  "peerDependenciesMeta": {
440
- "@gotgenes/pi-subagents": { "optional": true }
459
+ "@nklisch/pi-subagents": { "optional": true }
441
460
  }
442
461
  }
443
462
  ```
@@ -445,31 +464,31 @@ Consumers declare this package as an optional peer dependency:
445
464
  At runtime, consumers use dynamic import for type-safe access to the accessor functions:
446
465
 
447
466
  ```typescript
448
- const { getSubagentsService } = await import("@gotgenes/pi-subagents");
467
+ const { getSubagentsService } = await import("@nklisch/pi-subagents");
449
468
  const svc = getSubagentsService();
450
469
  if (svc) {
451
- svc.spawn("Explore", "Check for stale TODOs");
470
+ void svc.launch("Explore", "Check for stale TODOs", { mode: "detached" });
452
471
  }
453
472
  ```
454
473
 
455
474
  Pi's extension loader creates a fresh `jiti` instance per extension with `moduleCache: false`, so module-scoped singletons don't survive across extensions.
456
- The accessor functions use `Symbol.for("@gotgenes/pi-subagents:service")` on `globalThis`, which is process-global by spec, to bridge this gap.
475
+ The accessor functions use `Symbol.for("@nklisch/pi-subagents:service")` on `globalThis`, which is process-global by spec, to bridge this gap.
457
476
  The dynamic import provides compile-time types; the `Symbol.for()` key is the actual runtime channel.
458
477
 
459
478
  ### Interface
460
479
 
461
- See `src/service.ts` for the canonical definition.
480
+ See `src/service/service.ts` for the canonical definition.
462
481
  Key types:
463
482
 
464
- - `SubagentsService` — `spawn`, `getRecord`, `listAgents`, `abort`, `steer`, `waitForAll`, `hasRunning`.
465
- - `SubagentRecord` — serializable agent snapshot (no live session objects).
466
- - `SpawnOptions` — `description`, `model`, `maxTurns`, `thinkingLevel`, `inheritContext`, `foreground`, `bypassQueue`.
483
+ - `SubagentsService` — `launch`, `resume`, `stop`, `steer`, `list`, `getResult`, `getRecord`, `waitForAll`, and `hasRunning`.
484
+ - `SubagentRecord` — serializable agent snapshot with run id, mode, status, terminal reason, active runtime, model, and thinking level.
485
+ - `LaunchOptions` — `description`, `model`, `maxTurns`, `thinkingLevel`, `inheritContext`, `mode`, `timeoutSeconds`, and `signal`.
467
486
  - `SUBAGENT_EVENTS` — channel constants for `pi.events` subscriptions.
468
487
 
469
488
  ### Accessor pattern
470
489
 
471
490
  ```typescript
472
- const SERVICE_KEY = Symbol.for("@gotgenes/pi-subagents:service");
491
+ const SERVICE_KEY = Symbol.for("@nklisch/pi-subagents:service");
473
492
 
474
493
  export function publishSubagentsService(service: SubagentsService): void {
475
494
  (globalThis as Record<symbol, unknown>)[SERVICE_KEY] = service;
@@ -490,12 +509,12 @@ The core emits events on `pi.events` that any extension can observe:
490
509
 
491
510
  | Channel | Payload | When |
492
511
  | --------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------- |
493
- | `subagents:started` | `{ id, type, description }` | Agent begins running |
494
- | `subagents:completed` | `{ id, type, description, status, result?, error?, toolUses, durationMs, tokens? }` | Agent finishes successfully |
512
+ | `subagents:started` | `{ id, type, description, runId, mode }` | Agent begins running |
513
+ | `subagents:completed` | `{ id, type, runId, mode, status, terminalReason, result?, error?, toolUses, activeRuntimeMs, tokens? }` | Agent reaches a terminal state |
495
514
  | `subagents:resumed` | same as `completed` (`buildEventData` shape) | A retained session's resumed turn finishes |
496
- | `subagents:failed` | same as `completed` (`buildEventData` shape) | Agent ends in `error`/`stopped`/`aborted` |
515
+ | `subagents:failed` | same as `completed` (`buildEventData` shape) | Agent ends in `error` or `stopped` |
497
516
  | `subagents:compacted` | `{ id, type, description, reason, tokensBefore, compactionCount }` | Child session compacts |
498
- | `subagents:created` | `{ id, type, description, isBackground }` | Background agent created (pre-admission) |
517
+ | `subagents:created` | `{ id, type, description, runId, mode }` | Agent record created (pre-admission) |
499
518
  | `subagents:steered` | `{ id, message }` | Steering message delivered to a running agent |
500
519
 
501
520
  These are fire-and-forget broadcast events — no request IDs, no reply channels.
@@ -624,7 +643,7 @@ The axis that decides push versus pull is whether a need is reactive or discrete
624
643
  - **Reactive** (ambient state that changes underneath you) → subscribe to the broadcast; be told.
625
644
  The state-owner announces; the consumer maintains its own read-model; nobody pulls.
626
645
  - **Discrete** (a one-shot question: current value, full transcript) → pull a query.
627
- `get_subagent_result`, opening a transcript, and the external `SubagentsService.getRecord` are queries by nature and stay pull, in-package or not.
646
+ `get_subagent_result`, `query_subagent_session`, opening a transcript, and the external `SubagentsService.getRecord` are queries by nature and stay pull, in-package or not.
628
647
 
629
648
  Behavior is a third interface: **tell by id, with outcomes**.
630
649
  `steer` and `abort` own their own rules — a non-running agent rejects a steer from inside `steer`, not via a caller's status pre-check — so coordinators never ask-then-tell.
@@ -676,8 +695,8 @@ Bags with 10+ fields are the highest priority for decomposition.
676
695
 
677
696
  | Interface | Fields | Consumers | Severity |
678
697
  | ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------- | --------- |
679
- | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
680
- | `AgentSpawnConfig` | 13 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
698
+ | `ResolvedSpawnConfig` | 3 nested | spawn-config, agent-tool | ✓ done |
699
+ | `AgentSpawnConfig` | execution options + parent identity | subagent-manager (internal) | ✓ done |
681
700
  | `CreateSubagentSessionParams` | 6 (snapshot, type, cwd, parentSession, model, thinkingLevel) | create-subagent-session | ✓ done |
682
701
  | `TurnLoopOptions` | 4 (maxTurns, defaultMaxTurns, graceTurns, signal) | subagent-session | ✓ done |
683
702
  | `SessionConfig` | 6 (flat fields; extensions/noSkills/extras removed in #264) | session-config (output of assembler) | ✓ done |
@@ -704,7 +723,7 @@ Files with highest commit frequency × complexity:
704
723
  | 27.1 | `index.ts` | 109 | ▼ cooling |
705
724
  | 9.6 | `tools/agent-tool.ts` | 56 | ▼ cooling |
706
725
  | 8.8 | `ui/agent-widget.ts` | 22 | ▼ cooling |
707
- | 7.3 | `tools/foreground-runner.ts` | 22 | ▼ cooling |
726
+ | 7.3 | `lifecycle/subagent.ts` | 22 | ▼ cooling |
708
727
  | 6.6 | `service/service-adapter.ts` | 15 | ▲ accelerating |
709
728
  | 6.3 | `config/custom-agents.ts` | 13 | ▼ cooling |
710
729
 
@@ -712,9 +731,8 @@ Files with highest commit frequency × complexity:
712
731
 
713
732
  ### Production duplication
714
733
 
715
- The prior clone group between `agent-runner.ts` and `message-formatters.ts` was resolved in #172.
716
- The 20-line clone group between `agent-config-editor.ts` and `agent-creation-wizard.ts` was resolved in #217 extracted into `ui/agent-file-writer.ts` (`writeAgentFile`).
717
- The final 11-line internal clone group within `agent-config-editor.ts` was eliminated in Phase 19 Step 6 ([#441]) when the file itself was deleted; production duplication is 0 lines.
734
+ The prior clone group between the execution and formatting paths was resolved in #172.
735
+ The definition-management clone groups were removed with the `/agents` terminal cut in Phase 19 ([#441]); the supporting file-writer module was removed with them. Production duplication remains 0 lines.
718
736
 
719
737
  ### Session encapsulation debt (Law of Demeter) — resolved by [#277] ✔️
720
738
 
@@ -725,11 +743,10 @@ The intent-revealing replacements added by [#277]:
725
743
  | Reach-through | Sites | Replacement |
726
744
  | ---------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------- |
727
745
  | Steer buffer-or-deliver (was duplicated) | `service-adapter.ts`, `steer-tool.ts` | `Subagent.steer(message)` |
728
- | Conversation viewing | `get-result-tool.ts`, `agent-menu.ts`, `conversation-viewer.ts` | `Subagent.getConversation()` / `Subagent.messages` |
746
+ | Conversation viewing | `session-navigation.ts`, `session-navigator.ts` | `Subagent.agentMessages` / transcript sources |
729
747
  | Session-readiness guard | `agent-tool.ts`, `subagent-manager.ts` | `Subagent.isSessionReady()` |
730
- | Context-window stats | `steer-tool.ts`, `get-result-tool.ts`, `notification.ts`, `conversation-viewer.ts` | `Subagent.getContextPercent()` |
731
- | Live updates (subscription) | `conversation-viewer.ts` | `Subagent.subscribeToUpdates(fn)` |
732
- | Observer callback session param | `background-spawner.ts`, `foreground-runner.ts` | `subagent.subagentSession` (narrowed callback) |
748
+ | Context-window stats | `steer-tool.ts`, `get-result-tool.ts`, `notification.ts` | `Subagent.getContextPercent()` |
749
+ | Live updates (subscription) | `session-navigator.ts` | `Subagent.subscribeToUpdates(fn)` |
733
750
  | Session disposal | `subagent-manager.ts` | `SubagentSession.dispose()` — resolved by [#265] |
734
751
 
735
752
  ### Proposed bag decompositions
@@ -756,7 +773,8 @@ interface SpawnExecution {
756
773
  effectiveMaxTurns: number | undefined;
757
774
  thinking: ThinkingLevel | undefined;
758
775
  inheritContext: boolean;
759
- runInBackground: boolean;
776
+ mode: SubagentMode;
777
+ timeoutSeconds?: number;
760
778
  agentInvocation: AgentInvocation;
761
779
  }
762
780
 
@@ -768,8 +786,8 @@ interface SpawnPresentation {
768
786
  }
769
787
  ```
770
788
 
771
- `foreground-runner` and `background-spawner` primarily consume `SpawnExecution` + `SpawnIdentity`.
772
- `agent-tool` uses all three to build the `AgentSpawnConfig` and the result text.
789
+ `agent-tool` consumes all three to build the internal `AgentSpawnConfig` and the result text.
790
+ The joined/detached mode lives in `SpawnExecution`; no separate foreground/background runner exists.
773
791
  After decomposition, each consumer declares its real dependencies explicitly.
774
792
 
775
793
  #### AgentSpawnConfig — ParentSessionInfo extracted (done, [#166][166])
@@ -800,8 +818,7 @@ export interface RunContext {
800
818
  }
801
819
  ```
802
820
 
803
- The remaining `RunOptions` fields (`model`, `maxTurns`, `signal`, `thinkingLevel`, `defaultMaxTurns`, `graceTurns`, `onSessionCreated`) are genuine execution parameters.
804
- `RunOptions` now has 9 fields: 1 nested `context: RunContext` (2 per-call fields) plus 8 flat execution fields.
821
+ The current execution seam is `SubagentExecution` in `lifecycle/subagent.ts`; it carries the immutable parent snapshot, joined/detached mode, runtime deadline, session factory, and lifecycle collaborators directly. The old runner-specific `RunOptions` bag is no longer part of the source tree.
805
822
 
806
823
  #### SessionConfig (11 fields → extract ToolFilterConfig) — done ([#168][168])
807
824
 
@@ -809,7 +826,7 @@ The tool-filtering cluster (`toolNames`, `disallowedSet`, `extensions`) was extr
809
826
  `filterActiveTools` now accepts a single `ToolFilterConfig` argument instead of three positional parameters.
810
827
  `SessionConfig` reduced from 10 to 8 top-level fields.
811
828
 
812
- #### RunnerIO (9 methods → 2 focused interfaces) — done ([#167][167])
829
+ #### Session factory IO (9 methods → 2 focused interfaces) — done ([#167][167])
813
830
 
814
831
  The IO boundary was split into two focused interfaces:
815
832
 
@@ -834,13 +851,9 @@ export interface SessionFactoryIO {
834
851
  ) => Promise<{ session: AgentSession }>;
835
852
  assemblerIO: AssemblerIO;
836
853
  }
837
-
838
- /** Backward-compatible intersection of the two focused interfaces. */
839
- export type RunnerIO = EnvironmentIO & SessionFactoryIO;
840
854
  ```
841
855
 
842
- `RunnerIO` is kept as a type alias for the intersection.
843
- All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
856
+ The factory consumes the `EnvironmentIO & SessionFactoryIO` boundary directly; no runner implementation or compatibility alias remains.
844
857
 
845
858
  ## Phase 11 (complete)
846
859
 
@@ -924,6 +937,10 @@ See [phase-19-implement-ui-decisions.md](history/phase-19-implement-ui-decisions
924
937
 
925
938
  ## Phase 20 improvement roadmap
926
939
 
940
+ > Planning note: this roadmap records the pre-execution-control baseline. The
941
+ > joined/detached execution-control work below supersedes any obsolete runner,
942
+ > unbounded-result, and foreground/background vocabulary in these notes.
943
+
927
944
  Phase 20 realizes the last un-extracted domain from the [first-principles refinement](#first-principles-refinement-and-the-deeper-target) — **result delivery** — and clears the residual boundary and complexity debt discovery surfaced around it.
928
945
 
929
946
  Discovery findings (fallow + entry-point trace + test-constructibility audit, 2026-07-03):
@@ -969,9 +986,8 @@ Target files:
969
986
 
970
987
  Outcome: `execute` ≤ 30 lines with cyclomatic < 10; off the fallow high-complexity list.
971
988
 
972
- Landed: `src/tools/get-result-report.ts` added `AgentReport` value object plus `renderStatsParts` / `renderReportBody` / `formatAgentReport` pure functions, unit-tested directly in `test/tools/get-result-report.test.ts`.
973
- `GetResultTool.execute` now owns only record lookup and the wait/consume policy (13 lines), delegating report assembly to a private `buildReport` + `formatAgentReport`; output is byte-identical.
974
- `get-result-tool.execute` is off the HIGH-CRAP list (3 → 2 remaining: `service-adapter.spawn`, the notification renderer arrow).
989
+ Landed: bounded result formatting was extracted while the earlier result-delivery design was active. The later execution-control clean break removed the standalone report module and its unbounded wait path; `GetResultTool` now owns a nonblocking, bounded status/result projection.
990
+ `get-result-tool.execute` remains a focused lookup-and-render boundary.
975
991
 
976
992
  `Release: batch "result-delivery"`
977
993
 
@@ -1016,8 +1032,6 @@ Target files:
1016
1032
 
1017
1033
  - `src/ui/agent-widget.ts` — replace `tui: any` with a lean local interface (`terminal.columns`, `requestRender()`); shrink the 4-rule file-level disable.
1018
1034
  - `src/tools/agent-tool.ts` — type `renderCall`/`renderResult` params (`theme`, `result`) with lean local interfaces; shrink the 6-rule file-level disable to the genuinely SDK-gapped lines.
1019
- - `src/tools/foreground-runner.ts` — retire the line-level `details as any` cast if the SDK surface allows.
1020
-
1021
1035
  Some disables are irreducible SDK export gaps; the goal is line-level precision, not zero.
1022
1036
 
1023
1037
  Outcome: file-level eslint-disable headers 5 → ≤ 2; remaining suppressions are line-level with named rules.
@@ -6,7 +6,7 @@ The source tree was reviewed against `@gotgenes/pi-subagents` 19.2.1 and `@tinti
6
6
 
7
7
  ## Shared core
8
8
 
9
- All three implementations provide in-process foreground and background agents, custom agent definitions, concurrency control, steering, result retrieval, session resume, model selection, thinking control, and completion notifications.
9
+ All three implementations provide in-process subagents, custom agent definitions, concurrency control, steering, result retrieval, session resume, model selection, thinking control, and completion notifications. This fork exposes joined and detached delivery rather than separate foreground/background APIs.
10
10
 
11
11
  The direct `gotgenes` lineage and this fork also share a focused-core architecture:
12
12
 
@@ -34,8 +34,8 @@ A mechanical rebase is unsafe because direct upstream may change or remove these
34
34
 
35
35
  This fork selectively carries the useful 18.1–19.2 reliability work:
36
36
 
37
- - queued and resumed runs are awaitable through the current record promise;
38
- - interrupting `get_subagent_result(wait: true)` ends only the wait;
37
+ - queued and resumed runs settle through the current run lease;
38
+ - `get_subagent_result` is bounded and nonblocking, so status inspection never waits on child execution;
39
39
  - queued stops follow the terminal lifecycle and state that no work started;
40
40
  - consumption-aware retention keeps terminal records for the parent session while releasing heavy live sessions on separate consumed and unconsumed windows;
41
41
  - completion notifications wait for the parent `agent_settled` boundary and recheck consumption;