@gotgenes/pi-subagents 6.18.4 → 6.18.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [6.18.6](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.5...pi-subagents-v6.18.6) (2026-05-24)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * plan extract RunContext from RunOptions ([#169](https://github.com/gotgenes/pi-packages/issues/169)) ([ca12b2e](https://github.com/gotgenes/pi-packages/commit/ca12b2ebd116cb50c6e12d2d3fe3a87ff997d6d6))
14
+ * **retro:** add planning stage notes for issue [#169](https://github.com/gotgenes/pi-packages/issues/169) ([05b0176](https://github.com/gotgenes/pi-packages/commit/05b01764a4aaa885efca9d061abf6bacb384057c))
15
+ * **retro:** add retro notes for issue [#168](https://github.com/gotgenes/pi-packages/issues/168) ([dfe46ed](https://github.com/gotgenes/pi-packages/commit/dfe46ed5b5ee62371912dbbc6227443adee8e67b))
16
+ * **retro:** add TDD stage notes for issue [#169](https://github.com/gotgenes/pi-packages/issues/169) ([84c0d8d](https://github.com/gotgenes/pi-packages/commit/84c0d8d5ef0a94750c470e3f10b3ab589caac794))
17
+ * update architecture doc for RunContext extraction ([#169](https://github.com/gotgenes/pi-packages/issues/169)) ([ea49fe1](https://github.com/gotgenes/pi-packages/commit/ea49fe1b9c316e6541814de821738d6d121c4d13))
18
+ * update RunOptions field references in comments ([#169](https://github.com/gotgenes/pi-packages/issues/169)) ([fd9c3ed](https://github.com/gotgenes/pi-packages/commit/fd9c3ed0e0b01c45136c8f34d8f31a89564e8061))
19
+
20
+ ## [6.18.5](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.4...pi-subagents-v6.18.5) (2026-05-24)
21
+
22
+
23
+ ### Documentation
24
+
25
+ * mark ToolFilterConfig extraction done in architecture doc ([#168](https://github.com/gotgenes/pi-packages/issues/168)) ([686c33e](https://github.com/gotgenes/pi-packages/commit/686c33ed107e1ec1ec56c73441b1551bf59bff3f))
26
+ * plan extract ToolFilterConfig from SessionConfig ([#168](https://github.com/gotgenes/pi-packages/issues/168)) ([beaaeb4](https://github.com/gotgenes/pi-packages/commit/beaaeb41588bb93739b5ba6959c12202efbe7862))
27
+ * **retro:** add planning stage notes for issue [#168](https://github.com/gotgenes/pi-packages/issues/168) ([7086139](https://github.com/gotgenes/pi-packages/commit/70861390a6190653d499c720fc298972e03967aa))
28
+ * **retro:** add retro notes for issue [#167](https://github.com/gotgenes/pi-packages/issues/167) ([cc96edd](https://github.com/gotgenes/pi-packages/commit/cc96edd20994a48ee2f824ea9136fa0da83e4c23))
29
+ * **retro:** add TDD stage notes for issue [#168](https://github.com/gotgenes/pi-packages/issues/168) ([8a14bf7](https://github.com/gotgenes/pi-packages/commit/8a14bf766e9c38c89f31468293fdafa8c79d32ea))
30
+ * update architecture to reflect current layout and RunnerIO split ([#167](https://github.com/gotgenes/pi-packages/issues/167)) ([d4a98aa](https://github.com/gotgenes/pi-packages/commit/d4a98aaa1600c24c28dd1005e381588990ab74fd))
31
+
8
32
  ## [6.18.4](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.3...pi-subagents-v6.18.4) (2026-05-24)
9
33
 
10
34
 
@@ -220,9 +220,9 @@ sequenceDiagram
220
220
 
221
221
  ## Module organization
222
222
 
223
- The extension has 53 source files (7,288 LOC) organized into six domains plus entry-point wiring.
224
- All eight domains now have directories: `config/`, `session/`, `lifecycle/`, `observation/`, `service/`, `tools/`, `ui/`, and `handlers/`.
225
- Issue #164 moved the 26 previously flat root-level files into the five new domain directories.
223
+ The extension has 51 source files (7,338 LOC) organized into six domains plus entry-point wiring.
224
+ All eight domains have directories: `config/`, `session/`, `lifecycle/`, `observation/`, `service/`, `tools/`, `ui/`, and `handlers/`.
225
+ Issue #164 moved the 26 previously flat root-level files into five new domain directories, reducing the root to 5 files + 8 directories.
226
226
 
227
227
  ### Current layout
228
228
 
@@ -234,44 +234,43 @@ src/
234
234
  ├── settings.ts SettingsManager (persistent operational settings)
235
235
  ├── debug.ts debug logging utility
236
236
 
237
- │ ── Config domain (agent type definitions and resolution) ──
238
- ├── agent-types.ts AgentTypeRegistry class
239
- ├── default-agents.ts built-in agent configs (general-purpose, Explore, Plan)
240
- ├── custom-agents.ts user-defined agent .md file loader
241
- ├── invocation-config.ts per-call config merge
237
+ ├── config/ agent type definitions and resolution
238
+ ├── agent-types.ts AgentTypeRegistry class
239
+ ├── default-agents.ts built-in agent configs (general-purpose, Explore, Plan)
240
+ ├── custom-agents.ts user-defined agent .md file loader
241
+ │ └── invocation-config.ts per-call config merge
242
242
 
243
- │ ── Session domain (session assembly and preparation) ──
244
- ├── session-config.ts pure assembler (main entry)
245
- ├── prompts.ts system prompt building
246
- ├── context.ts parent conversation extraction
247
- ├── memory.ts persistent MEMORY.md per agent
248
- ├── skill-loader.ts skill preloading
249
- ├── env.ts git/platform detection
250
- ├── model-resolver.ts fuzzy model name resolution
251
- ├── session-dir.ts session directory derivation
243
+ ├── session/ session assembly and preparation
244
+ ├── session-config.ts pure assembler (main entry)
245
+ ├── prompts.ts system prompt building
246
+ ├── context.ts parent conversation extraction
247
+ ├── memory.ts persistent MEMORY.md per agent
248
+ ├── skill-loader.ts skill preloading
249
+ ├── env.ts git/platform detection
250
+ ├── model-resolver.ts fuzzy model name resolution
251
+ │ └── session-dir.ts session directory derivation
252
252
 
253
- │ ── Lifecycle domain (agent execution and state) ──
254
- ├── agent-manager.ts spawn, queue, abort, resume, concurrency
255
- ├── agent-runner.ts session creation, turn loop, tool filtering
256
- ├── agent-record.ts status state machine
257
- ├── parent-snapshot.ts immutable spawn-time parent state
258
- ├── execution-state.ts session/output phase state
259
- ├── worktree.ts git worktree isolation
260
- ├── worktree-state.ts worktree phase state
261
- ├── usage.ts token usage tracking
253
+ ├── lifecycle/ agent execution and state tracking
254
+ ├── agent-manager.ts spawn, queue, abort, resume, concurrency
255
+ ├── agent-runner.ts session creation, turn loop, tool filtering
256
+ ├── agent-record.ts status state machine
257
+ ├── parent-snapshot.ts immutable spawn-time parent state
258
+ ├── execution-state.ts session/output phase state
259
+ ├── worktree.ts git worktree isolation
260
+ ├── worktree-state.ts worktree phase state
261
+ │ └── usage.ts token usage tracking
262
262
 
263
- │ ── Observation domain (progress tracking and notification) ──
264
- ├── record-observer.ts session-event stats observer
265
- ├── notification.ts completion nudges
266
- ├── notification-state.ts per-agent notification tracking
267
- ├── renderer.ts notification TUI component
263
+ ├── observation/ progress tracking and notification
264
+ ├── record-observer.ts session-event stats observer
265
+ ├── notification.ts completion nudges
266
+ ├── notification-state.ts per-agent notification tracking
267
+ │ └── renderer.ts notification TUI component
268
268
 
269
- │ ── Service domain (cross-extension API) ──
270
- ├── service.ts SubagentsService interface + Symbol.for() accessors
271
- ├── service-adapter.ts SubagentsService wrapper around AgentManager
269
+ ├── service/ cross-extension API boundary
270
+ ├── service.ts SubagentsService interface + Symbol.for() accessors
271
+ │ └── service-adapter.ts SubagentsService wrapper around AgentManager
272
272
 
273
- │ ── Tools domain (LLM-facing tool implementations) ──
274
- ├── tools/
273
+ ├── tools/ LLM-facing tool implementations
275
274
  │ ├── agent-tool.ts Agent tool definition, validation, dispatch
276
275
  │ ├── spawn-config.ts pure config resolution
277
276
  │ ├── foreground-runner.ts foreground execution loop
@@ -280,8 +279,7 @@ src/
280
279
  │ ├── steer-tool.ts steer_subagent tool
281
280
  │ └── helpers.ts shared tool utilities
282
281
 
283
- │ ── UI domain (user-facing presentation) ──
284
- ├── ui/
282
+ ├── ui/ user-facing presentation
285
283
  │ ├── agent-widget.ts above-editor live status widget
286
284
  │ ├── widget-renderer.ts pure rendering for widget
287
285
  │ ├── agent-menu.ts /agents slash command menu
@@ -293,68 +291,12 @@ src/
293
291
  │ ├── ui-observer.ts session-event observer for streaming
294
292
  │ └── display.ts pure formatters and shared types
295
293
 
296
- │ ── Event handlers ──
297
- └── handlers/
294
+ └── handlers/ event handlers
295
+ ├── index.ts barrel re-export
298
296
  ├── lifecycle.ts session_start, session_before_switch, session_shutdown
299
297
  └── tool-start.ts tool_execution_start handler
300
298
  ```
301
299
 
302
- ### Proposed directory restructuring
303
-
304
- Move the four ungrouped domains into subdirectories so the filesystem mirrors the domain model.
305
- Root-level files stay: `index.ts` (entry point), `runtime.ts` (wiring), `types.ts` (shared), `settings.ts`, `debug.ts`.
306
-
307
- ```text
308
- src/
309
- ├── index.ts
310
- ├── runtime.ts
311
- ├── types.ts
312
- ├── settings.ts
313
- ├── debug.ts
314
-
315
- ├── config/ agent type definitions and resolution
316
- │ ├── agent-types.ts
317
- │ ├── default-agents.ts
318
- │ ├── custom-agents.ts
319
- │ └── invocation-config.ts
320
-
321
- ├── session/ session assembly and preparation
322
- │ ├── session-config.ts
323
- │ ├── prompts.ts
324
- │ ├── context.ts
325
- │ ├── memory.ts
326
- │ ├── skill-loader.ts
327
- │ ├── env.ts
328
- │ ├── model-resolver.ts
329
- │ └── session-dir.ts
330
-
331
- ├── lifecycle/ agent execution and state tracking
332
- │ ├── agent-manager.ts
333
- │ ├── agent-runner.ts
334
- │ ├── agent-record.ts
335
- │ ├── parent-snapshot.ts
336
- │ ├── execution-state.ts
337
- │ ├── worktree.ts
338
- │ ├── worktree-state.ts
339
- │ └── usage.ts
340
-
341
- ├── observation/ progress tracking and notification
342
- │ ├── record-observer.ts
343
- │ ├── notification.ts
344
- │ ├── notification-state.ts
345
- │ └── renderer.ts
346
-
347
- ├── service/ cross-extension API boundary
348
- │ ├── service.ts
349
- │ └── service-adapter.ts
350
-
351
- ├── tools/ (existing)
352
- ├── ui/ (existing)
353
- └── handlers/ (existing)
354
- ```
355
-
356
- Root goes from 31 files to 5 files + 8 directories — each directory name tells you what domain it belongs to.
357
-
358
300
  ### Observation model
359
301
 
360
302
  Record statistics (tool uses, token usage, compaction counts) are updated by `record-observer.ts`, which subscribes directly to session events.
@@ -504,8 +446,8 @@ Bags with 10+ fields are the highest priority for decomposition.
504
446
  | --------------------------- | ------------------------------------------------------ | ------------------------------------------------- | -------- |
505
447
  | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
506
448
  | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
507
- | `RunOptions` | 12 | agent-runner | High |
508
- | `SessionConfig` | 11 | agent-runner (output of assembler) | High |
449
+ | `RunOptions` | 9 (`RunContext` nested) | agent-runner | ✓ done |
450
+ | `SessionConfig` | 8 (ToolFilterConfig nested) | agent-runner (output of assembler) | ✓ done |
509
451
  | `NotificationDetails` | 10 | notification | Medium |
510
452
  | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
511
453
  | `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
@@ -601,61 +543,57 @@ export interface ParentSessionInfo {
601
543
 
602
544
  `AgentSpawnConfig` now carries `parentSession?: ParentSessionInfo` instead of three flat optional fields.
603
545
 
604
- #### RunOptions (12 fields → extract RunContext)
546
+ #### RunOptions (12 fields → extract RunContext) — done ([#169][169])
605
547
 
606
- The `RunOptions` bag mixes execution parameters with context information:
548
+ The `RunOptions` bag mixes execution parameters with context information.
549
+ `RunContext` was extracted and nested as `RunOptions.context`:
607
550
 
608
551
  ```typescript
609
- /** Parent context needed to configure the child session. */
610
- interface RunContext {
611
- cwd?: string;
612
- parentSessionFile?: string;
613
- parentSessionId?: string;
552
+ /** Parent execution context where/who is running. */
553
+ export interface RunContext {
614
554
  exec: ShellExec;
615
555
  registry: AgentConfigLookup;
556
+ cwd?: string;
557
+ parentSession?: ParentSessionInfo;
616
558
  }
617
559
  ```
618
560
 
619
561
  The remaining `RunOptions` fields (`model`, `maxTurns`, `signal`, `isolated`, `thinkingLevel`, `defaultMaxTurns`, `graceTurns`, `onSessionCreated`) are genuine execution parameters.
562
+ `RunOptions` now has 9 fields: 1 nested `context: RunContext` plus 8 flat execution fields.
620
563
 
621
- #### SessionConfig (11 fields → extract ToolFilterConfig)
622
-
623
- Three fields form a cohesive tool-filtering cluster:
624
-
625
- ```typescript
626
- /** Tool filtering configuration — used by filterActiveTools. */
627
- interface ToolFilterConfig {
628
- toolNames: string[];
629
- disallowedSet: Set<string> | undefined;
630
- extensions: boolean | string[];
631
- }
632
- ```
564
+ #### SessionConfig (11 fields → extract ToolFilterConfig) — done ([#168][168])
633
565
 
634
- Extracting this reduces `SessionConfig` from 11 to 8 fields and gives `filterActiveTools` a named input type instead of three positional parameters.
566
+ The tool-filtering cluster (`toolNames`, `disallowedSet`, `extensions`) was extracted into `ToolFilterConfig` and nested as `SessionConfig.toolFilter`.
567
+ `filterActiveTools` now accepts a single `ToolFilterConfig` argument instead of three positional parameters.
568
+ `SessionConfig` reduced from 10 to 8 top-level fields.
635
569
 
636
- #### RunnerIO (9 methods → 2 focused interfaces)
570
+ #### RunnerIO (9 methods → 2 focused interfaces) — done ([#167][167])
637
571
 
638
- The IO boundary mixes environment discovery with session factory operations:
572
+ The IO boundary was split into two focused interfaces:
639
573
 
640
574
  ```typescript
641
- /** Environment discovery — detecting paths and platform info. */
642
- interface EnvironmentIO {
575
+ /** Environment discovery — detect runtime context and resolve directories. */
576
+ export interface EnvironmentIO {
643
577
  detectEnv: (exec: ShellExec, cwd: string) => Promise<EnvInfo>;
644
578
  getAgentDir: () => string;
645
579
  deriveSessionDir: (parentSessionFile: string | undefined, effectiveCwd: string) => string;
646
580
  }
647
581
 
648
- /** Session factory — creating SDK objects. */
649
- interface SessionFactoryIO {
582
+ /** Session factory — create SDK objects for a child agent session. */
583
+ export interface SessionFactoryIO {
650
584
  createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
651
585
  createSessionManager: (cwd: string, sessionDir: string) => SessionManagerLike;
652
586
  createSettingsManager: (cwd: string, agentDir: string) => SettingsManager;
653
587
  createSession: (opts: CreateSessionOptions) => Promise<{ session: AgentSession }>;
654
588
  assemblerIO: AssemblerIO;
655
589
  }
590
+
591
+ /** Backward-compatible intersection of the two focused interfaces. */
592
+ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
656
593
  ```
657
594
 
658
- The runner would accept `EnvironmentIO & SessionFactoryIO` (keeping backward compatibility) while each piece can be tested independently.
595
+ `RunnerIO` is kept as a type alias for the intersection.
596
+ All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
659
597
 
660
598
  ## Improvement roadmap (Phase 10)
661
599
 
@@ -687,15 +625,16 @@ Extracted `parentSessionFile`, `parentSessionId`, `toolCallId` into `ParentSessi
687
625
  `RunnerIO` kept as a backward-compatible type alias for the intersection.
688
626
  All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
689
627
 
690
- ### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168])
628
+ ### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168]) ✓ Done
691
629
 
692
- Extract the tool-filtering cluster into `ToolFilterConfig`.
693
- Give `filterActiveTools` a named input type.
630
+ `ToolFilterConfig` extracted from `SessionConfig`, grouping `toolNames`, `disallowedSet`, and `extensions`.
631
+ `filterActiveTools` now accepts a single `ToolFilterConfig` argument.
632
+ `SessionConfig` reduced from 10 to 8 top-level fields.
694
633
 
695
- ### Step 6: Extract RunContext from RunOptions ([#169][169])
634
+ ### Step 6: Extract RunContext from RunOptions ([#169][169]) ✓ Done
696
635
 
697
- Extract context fields into `RunContext`.
698
- Reduces RunOptions from 12 to 7 fields.
636
+ Extracted `exec`, `registry`, `cwd`, and `parentSession` into `RunContext`, nested as `RunOptions.context`.
637
+ `RunOptions` reduced from 12 to 9 fields (1 nested `context` + 8 flat execution fields).
699
638
 
700
639
  ### Step 7: Reduce buildContentLines complexity ([#170][170])
701
640
 
@@ -0,0 +1,173 @@
1
+ ---
2
+ issue: 168
3
+ issue_title: "refactor(pi-subagents): extract ToolFilterConfig from SessionConfig (11 fields)"
4
+ ---
5
+
6
+ # Extract ToolFilterConfig from SessionConfig
7
+
8
+ ## Problem Statement
9
+
10
+ `SessionConfig` in `session-config.ts` has 10 top-level fields.
11
+ Three of those fields — `toolNames`, `disallowedSet`, and `extensions` — form a cohesive tool-filtering cluster consumed together by `filterActiveTools` in `agent-runner.ts`.
12
+ Today `filterActiveTools` accepts these as three separate positional parameters, and the guard condition `cfg.extensions !== false || cfg.disallowedSet` is duplicated at both call sites.
13
+ Extracting the cluster into a named `ToolFilterConfig` type makes the relationship explicit and gives `filterActiveTools` a single named input.
14
+
15
+ ## Goals
16
+
17
+ - Define a `ToolFilterConfig` interface grouping `toolNames`, `disallowedSet`, and `extensions`.
18
+ - Nest `ToolFilterConfig` inside `SessionConfig`, replacing the three flat fields.
19
+ - Change `filterActiveTools` to accept `ToolFilterConfig` instead of three positional parameters.
20
+ - Update `runAgent` to destructure `cfg.toolFilter` at both filter call sites.
21
+ - Update all test assertions that read the three flat fields to use the nested path.
22
+
23
+ ## Non-Goals
24
+
25
+ - Extracting `ToolFilterConfig` into its own file — the type is small (3 fields) and co-located with its producer (`assembleSessionConfig`).
26
+ - Adding a `needsFiltering()` predicate or encapsulating the guard condition — follow-up if the duplication grows.
27
+ - Changing the `extensions` field on `AgentConfig` — that lives in the config domain and is unrelated.
28
+
29
+ ## Background
30
+
31
+ ### Affected modules
32
+
33
+ | Module | Role |
34
+ | ------------------------------- | -------------------------------------------------------------------- |
35
+ | `src/session/session-config.ts` | Defines `SessionConfig`, produces it in `assembleSessionConfig` |
36
+ | `src/lifecycle/agent-runner.ts` | Consumes `SessionConfig` in `runAgent`, contains `filterActiveTools` |
37
+
38
+ ### Consumer analysis
39
+
40
+ `runAgent` accesses the three tool-filter fields in four places:
41
+
42
+ 1. `tools: cfg.toolNames` — passed to `io.createSession` (session-creation tool list).
43
+ 2. `noExtensions: cfg.extensions === false` — resource-loader option.
44
+ 3. Two identical guard-plus-filter blocks passing all three fields to `filterActiveTools`.
45
+
46
+ After extraction, sites 1 and 2 will read from `cfg.toolFilter.toolNames` and `cfg.toolFilter.extensions`.
47
+ Site 3 (both occurrences) will pass `cfg.toolFilter` as a single argument.
48
+
49
+ ### Test files affected
50
+
51
+ | Test file | What changes |
52
+ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
53
+ | `test/session/session-config.test.ts` | Assertions on `result.toolNames`, `result.extensions`, `result.disallowedSet` move to `result.toolFilter.*` |
54
+ | `test/lifecycle/agent-runner-extension-tools.test.ts` | No direct `SessionConfig` assertions — exercises tool filtering via `runAgent` end-to-end; no changes needed |
55
+
56
+ ### Dependency
57
+
58
+ Issue #164 (reorganize into domain directories) — closed ✓.
59
+
60
+ ## Design Overview
61
+
62
+ ### ToolFilterConfig shape
63
+
64
+ ```typescript
65
+ /** Tool filtering configuration — consumed by filterActiveTools. */
66
+ export interface ToolFilterConfig {
67
+ /** Built-in tool name allowlist for this agent type. */
68
+ toolNames: string[];
69
+ /** Disallowed tool set from agentConfig. undefined when empty. */
70
+ disallowedSet: Set<string> | undefined;
71
+ /** Resolved extensions setting: false | true | string[] allowlist. */
72
+ extensions: boolean | string[];
73
+ }
74
+ ```
75
+
76
+ ### SessionConfig after extraction
77
+
78
+ ```typescript
79
+ export interface SessionConfig {
80
+ effectiveCwd: string;
81
+ systemPrompt: string;
82
+ toolFilter: ToolFilterConfig;
83
+ model: unknown;
84
+ thinkingLevel: ThinkingLevel | undefined;
85
+ noSkills: boolean;
86
+ extras: PromptExtras;
87
+ agentMaxTurns: number | undefined;
88
+ }
89
+ ```
90
+
91
+ Field count drops from 10 top-level fields to 8 (7 remaining + 1 `toolFilter`).
92
+
93
+ ### filterActiveTools after extraction
94
+
95
+ ```typescript
96
+ function filterActiveTools(
97
+ activeTools: string[],
98
+ config: ToolFilterConfig,
99
+ ): string[] {
100
+ const { toolNames, extensions, disallowedSet } = config;
101
+ // ... body unchanged
102
+ }
103
+ ```
104
+
105
+ ### Consumer call site (runAgent)
106
+
107
+ ```typescript
108
+ // Resource loader
109
+ noExtensions: cfg.toolFilter.extensions === false,
110
+
111
+ // Session creation
112
+ tools: cfg.toolFilter.toolNames,
113
+
114
+ // Guard + filter (two sites)
115
+ if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
116
+ const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
117
+ session.setActiveToolsByName(filtered);
118
+ }
119
+ ```
120
+
121
+ ## Module-Level Changes
122
+
123
+ ### `src/session/session-config.ts`
124
+
125
+ 1. Add `ToolFilterConfig` interface (exported — consumed by `agent-runner.ts`).
126
+ 2. Replace `toolNames`, `disallowedSet`, and `extensions` fields on `SessionConfig` with a single `toolFilter: ToolFilterConfig` field.
127
+ 3. Update `assembleSessionConfig` return statement to nest the three values under `toolFilter`.
128
+
129
+ ### `src/lifecycle/agent-runner.ts`
130
+
131
+ 1. Import `ToolFilterConfig` from `session-config.ts`.
132
+ 2. Change `filterActiveTools` signature from `(activeTools, toolNames, extensions, disallowedSet)` to `(activeTools, config: ToolFilterConfig)`.
133
+ 3. Destructure `config` inside `filterActiveTools` body.
134
+ 4. Update both filter call sites to pass `cfg.toolFilter` instead of three separate arguments.
135
+ 5. Update `noExtensions:` and `tools:` references to use `cfg.toolFilter.*`.
136
+
137
+ ### `test/session/session-config.test.ts`
138
+
139
+ 1. Update all assertions reading `result.toolNames` → `result.toolFilter.toolNames`.
140
+ 2. Update all assertions reading `result.extensions` → `result.toolFilter.extensions`.
141
+ 3. Update all assertions reading `result.disallowedSet` → `result.toolFilter.disallowedSet`.
142
+
143
+ ## Test Impact Analysis
144
+
145
+ 1. No new unit tests are needed — the extraction does not introduce new behavior or branching.
146
+ 2. No existing tests become redundant — the assembler tests verify field population, and the extension-tools integration tests verify end-to-end filtering.
147
+ Both remain valuable at their respective layers.
148
+ 3. The `agent-runner-extension-tools.test.ts` tests exercise tool filtering via `runAgent` and do not reference `SessionConfig` fields directly, so they require no changes and serve as regression canaries for the refactoring.
149
+
150
+ ## TDD Order
151
+
152
+ This is a pure refactoring with no new behavior, so the steps are refactor→verify rather than red→green.
153
+
154
+ 1. **Add `ToolFilterConfig` interface and nest it in `SessionConfig`; update assembler return.**
155
+ Update `session-config.test.ts` assertions to read from `result.toolFilter.*`.
156
+ Run `pnpm run check` + tests.
157
+ Commit: `refactor(pi-subagents): extract ToolFilterConfig from SessionConfig`
158
+
159
+ 2. **Change `filterActiveTools` signature to accept `ToolFilterConfig`; update `runAgent` call sites.**
160
+ Import `ToolFilterConfig`, destructure inside `filterActiveTools`, update all `cfg.*` references in `runAgent`.
161
+ Run `pnpm run check` + full test suite.
162
+ Commit: `refactor(pi-subagents): pass ToolFilterConfig to filterActiveTools`
163
+
164
+ ## Risks and Mitigations
165
+
166
+ | Risk | Mitigation |
167
+ | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
168
+ | `cfg.toolNames` is also used for `tools:` in `createSession` — nesting it might confuse readers about intent | The field name `toolFilter.toolNames` is still descriptive; add a brief inline comment at the `tools:` site if needed |
169
+ | Test assertions reference flat fields — missing one causes a silent pass on `undefined` | grep for all three field names across `test/` before committing step 1 |
170
+
171
+ ## Open Questions
172
+
173
+ None — the issue's proposed shape matches the architecture doc exactly and the change is internal to the package.
@@ -0,0 +1,194 @@
1
+ ---
2
+ issue: 169
3
+ issue_title: "refactor(pi-subagents): extract RunContext from RunOptions (12 fields)"
4
+ ---
5
+
6
+ # Extract RunContext from RunOptions
7
+
8
+ ## Problem Statement
9
+
10
+ `RunOptions` in `agent-runner.ts` has 12 fields mixing two distinct concerns: parent execution context ("where/who is running") and per-call execution parameters ("how to run").
11
+ Extracting the context cluster into a named `RunContext` interface makes the separation explicit and reduces the flat field count from 12 to 9 (8 execution fields + 1 nested context).
12
+
13
+ ## Goals
14
+
15
+ - Define a `RunContext` interface grouping the 4 parent-context fields: `exec`, `registry`, `cwd`, and `parentSession`.
16
+ - Nest `RunContext` inside `RunOptions` as `context: RunContext`, replacing the 4 flat fields.
17
+ - Update `runAgent()` to read context fields from `options.context.*`.
18
+ - Update `AgentManager.startAgent()` to construct the nested `context` object when building `RunOptions`.
19
+ - Update all test files that construct or assert on `RunOptions` fields.
20
+ - Non-breaking refactor — `RunOptions` is not part of the public API (`service.ts` export boundary).
21
+
22
+ ## Non-Goals
23
+
24
+ - Changing the `AgentRunner` interface signature — `run()` keeps its 4 positional parameters; `RunContext` is nested inside `RunOptions`, not a separate parameter.
25
+ - Extracting `RunContext` into its own file — the interface is small (4 fields) and co-located with its consumer (`runAgent`).
26
+ - Further splitting the remaining 8 execution fields — they form a coherent "how to run" cluster.
27
+ - Hoisting `RunContext` construction to `AgentManager` instance level — two of the four fields (`cwd`, `parentSession`) vary per spawn, so a per-spawn construction is appropriate.
28
+
29
+ ## Background
30
+
31
+ Issue #164 (closed) reorganized source into domain directories; the runner now lives at `src/lifecycle/agent-runner.ts`.
32
+ Issue #166 (closed) extracted `ParentSessionInfo` and nested it inside `RunOptions.parentSession`.
33
+ Issue #167 (closed) split `RunnerIO` into `EnvironmentIO` and `SessionFactoryIO`.
34
+ Issue #168 (closed) extracted `ToolFilterConfig` from `SessionConfig`.
35
+
36
+ This issue continues the structural improvement by separating the two concerns mixed in `RunOptions`.
37
+
38
+ ### Field analysis
39
+
40
+ | Field | Concern | Usage in `runAgent()` |
41
+ | ------------------ | --------- | ------------------------------------------ |
42
+ | `exec` | Context | `io.detectEnv(options.exec, effectiveCwd)` |
43
+ | `registry` | Context | Passed to `assembleSessionConfig` |
44
+ | `cwd` | Context | Override working directory (worktree) |
45
+ | `parentSession` | Context | Session dir derivation + session linking |
46
+ | `model` | Execution | Per-call model override |
47
+ | `maxTurns` | Execution | Turn limit |
48
+ | `signal` | Execution | Abort forwarding |
49
+ | `isolated` | Execution | Extension isolation flag |
50
+ | `thinkingLevel` | Execution | Thinking level override |
51
+ | `onSessionCreated` | Execution | Session delivery callback |
52
+ | `defaultMaxTurns` | Execution | Fallback turn limit from runtime config |
53
+ | `graceTurns` | Execution | Grace window after soft limit |
54
+
55
+ ### Consumer analysis
56
+
57
+ `AgentManager.startAgent()` is the sole constructor of `RunOptions`.
58
+ The context fields come from two sources:
59
+
60
+ - Manager instance fields: `this.exec`, `this.registry`
61
+ - Per-spawn values: `worktreeCwd` (computed locally), `options.parentSession` (from `AgentSpawnConfig`)
62
+
63
+ ## Design Overview
64
+
65
+ ### `RunContext` interface
66
+
67
+ ```typescript
68
+ export interface RunContext {
69
+ /** Shell-exec callback for detectEnv — injected from pi.exec(). */
70
+ exec: ShellExec;
71
+ /** Agent config lookup — provides resolveAgentConfig and getToolNamesForType. */
72
+ registry: AgentConfigLookup;
73
+ /** Override working directory (e.g. for worktree isolation). */
74
+ cwd?: string;
75
+ /** Parent session identity (file path + session ID). */
76
+ parentSession?: ParentSessionInfo;
77
+ }
78
+ ```
79
+
80
+ ### Updated `RunOptions`
81
+
82
+ ```typescript
83
+ export interface RunOptions {
84
+ /** Parent execution context — where/who is running. */
85
+ context: RunContext;
86
+ model?: Model<any>;
87
+ maxTurns?: number;
88
+ signal?: AbortSignal;
89
+ isolated?: boolean;
90
+ thinkingLevel?: ThinkingLevel;
91
+ onSessionCreated?: (session: AgentSession) => void;
92
+ defaultMaxTurns?: number;
93
+ graceTurns?: number;
94
+ }
95
+ ```
96
+
97
+ ### Call-site sketch — `AgentManager.startAgent`
98
+
99
+ ```typescript
100
+ const promise = this.runner.run(snapshot, type, prompt, {
101
+ context: {
102
+ exec: this.exec,
103
+ registry: this.registry,
104
+ cwd: worktreeCwd,
105
+ parentSession: options.parentSession,
106
+ },
107
+ model: options.model,
108
+ maxTurns: options.maxTurns,
109
+ // ... remaining execution fields
110
+ });
111
+ ```
112
+
113
+ ### Access pattern in `runAgent`
114
+
115
+ ```typescript
116
+ const effectiveCwd = options.context.cwd ?? snapshot.cwd;
117
+ const env = await io.detectEnv(options.context.exec, effectiveCwd);
118
+ // ...
119
+ const sessionDir = io.deriveSessionDir(
120
+ options.context.parentSession?.parentSessionFile,
121
+ cfg.effectiveCwd,
122
+ );
123
+ ```
124
+
125
+ ## Module-Level Changes
126
+
127
+ ### `src/lifecycle/agent-runner.ts`
128
+
129
+ 1. Add `RunContext` interface (4 fields, exported) before `RunOptions`.
130
+ 2. Replace the 4 flat context fields on `RunOptions` with `context: RunContext`.
131
+ 3. Update all `options.*` reads in `runAgent()`:
132
+ - `options.exec` → `options.context.exec`
133
+ - `options.cwd` → `options.context.cwd`
134
+ - `options.parentSession` → `options.context.parentSession`
135
+ - `options.registry` → `options.context.registry`
136
+ 4. Move JSDoc from the removed flat fields to `RunContext` interface members.
137
+ 5. Export `RunContext`.
138
+
139
+ ### `src/lifecycle/agent-manager.ts`
140
+
141
+ 1. Update the `RunOptions` object literal in `startAgent()` to nest the four context fields under `context: { ... }`.
142
+ 2. No import changes needed — `RunOptions` is consumed via the `AgentRunner` interface, not imported directly.
143
+
144
+ ### No changes needed
145
+
146
+ - `src/lifecycle/agent-runner.ts` — `AgentRunner` interface signature unchanged (`options: RunOptions`).
147
+ - `src/lifecycle/agent-runner.ts` — `createAgentRunner()` unchanged.
148
+ - `src/index.ts` — no changes (doesn't import `RunOptions`).
149
+ - `src/runtime.ts` — comment-only reference to `RunOptions`; update comment if desired.
150
+ - `src/session/session-config.ts` — comment-only reference; update comment if desired.
151
+
152
+ ## Test Impact Analysis
153
+
154
+ ### New unit tests enabled
155
+
156
+ The extraction does not enable new test surfaces — `RunContext` is a plain data carrier with no behavior.
157
+ A type-check verification (`pnpm run check`) confirms the structural compatibility.
158
+
159
+ ### Existing tests that need updates
160
+
161
+ 1. `test/lifecycle/agent-runner.test.ts` — 9 `runAgent()` call sites: wrap `exec`, `registry`, `cwd`, and `parentSession` fields in `context: { ... }`.
162
+ 2. `test/lifecycle/agent-runner-extension-tools.test.ts` — 7 `runAgent()` call sites: same wrapping.
163
+ 3. `test/lifecycle/agent-manager.test.ts` — 3 assertion sites: update `runOpts.parentSession` → `runOpts.context.parentSession`, `runOpts.defaultMaxTurns` stays flat (execution field).
164
+
165
+ ### Tests that stay as-is
166
+
167
+ - `test/lifecycle/agent-runner-settings.test.ts` — tests `normalizeMaxTurns` (pure function, no `RunOptions` involvement).
168
+ - All other test files — no `RunOptions` construction or assertion.
169
+
170
+ ## TDD Order
171
+
172
+ 1. **Define `RunContext` and update `RunOptions`** — add `RunContext` interface, replace 4 flat fields with `context: RunContext` on `RunOptions`.
173
+ Update all `options.*` reads in `runAgent()` to `options.context.*`.
174
+ Update `agent-manager.ts` `startAgent()` to construct nested context.
175
+ Update `agent-runner.test.ts` (9 call sites) and `agent-runner-extension-tools.test.ts` (7 call sites) to nest context fields.
176
+ Update `agent-manager.test.ts` assertions (3 sites) to read from `runOpts.context.*`.
177
+ Run `pnpm run check` and `pnpm vitest run` to verify.
178
+ Commit: `refactor: extract RunContext from RunOptions (#169)`
179
+
180
+ 2. **Update comments** — update comment references in `runtime.ts` and `session-config.ts` that mention `RunOptions` field names.
181
+ Commit: `docs: update RunOptions field references in comments (#169)`
182
+
183
+ ## Risks and Mitigations
184
+
185
+ | Risk | Mitigation |
186
+ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
187
+ | Test factories using spread patterns lose context fields silently | No test factory returns `Partial<RunOptions>` — all call sites construct the options inline, so TypeScript will reject missing `context` |
188
+ | `agent-manager.test.ts` assertions on execution fields break | Only context-field assertions change; execution-field assertions (`defaultMaxTurns`, `graceTurns`) remain on `runOpts.*` |
189
+ | Nested access adds verbosity to `runAgent()` | Only 6 access sites gain the `.context` prefix; readability trade-off is minimal for the structural clarity gained |
190
+
191
+ ## Open Questions
192
+
193
+ None — the extraction follows the natural "where/who vs. how" seam identified in the issue body.
194
+ The issue's proposed flat `parentSessionFile`/`parentSessionId` fields have been superseded by the already-implemented `parentSession?: ParentSessionInfo` grouping from #166.
@@ -33,3 +33,31 @@ Test count held steady at 805/805 (50 files) — no behavioral changes.
33
33
  - `RunnerIO` JSDoc was split: `EnvironmentIO` got the environment-discovery description, `SessionFactoryIO` got the original "decouples from Pi SDK imports" description, and `RunnerIO` itself got a short backward-compatibility note.
34
34
  - Architecture doc updated: wide-interface table row and Step 4 roadmap entry both marked done.
35
35
  - No deviations from the plan.
36
+
37
+ ## Stage: Final Retrospective (2026-05-24T21:30:00Z)
38
+
39
+ ### Session summary
40
+
41
+ Issue #167 completed across three sessions: planning, TDD implementation, and ship.
42
+ The `RunnerIO` interface was split into `EnvironmentIO` and `SessionFactoryIO` with a backward-compatible type alias.
43
+ Released as `pi-subagents-v6.18.4`.
44
+
45
+ ### Observations
46
+
47
+ #### What went well
48
+
49
+ - The plan correctly identified that structural typing would make this zero-friction — no call-site changes were needed in `index.ts` or either test factory.
50
+ - The two-step TDD plan was right-sized for a pure refactoring: step 1 for the interface split, step 2 for comment updates.
51
+ No new tests were needed, and the existing 805 tests validated the change without modification.
52
+ - Ship session was clean: CI passed first try, release-please PR merged, issue closed.
53
+
54
+ #### What caused friction (agent side)
55
+
56
+ - `missing-context` — The TDD session's architecture doc update (`824fd72`) only touched the `RunnerIO`-specific table row and Step 4 roadmap entry.
57
+ It missed that the same file's "Current layout" section still showed the pre-#164 flat file structure and retained a redundant "Proposed directory restructuring" section.
58
+ The user had to request a follow-up update (`d4a98aa`) in the ship session.
59
+ Impact: one extra commit and user prompt; no rework of prior commits.
60
+
61
+ #### What caused friction (user side)
62
+
63
+ - No friction observed.
@@ -0,0 +1,76 @@
1
+ ---
2
+ issue: 168
3
+ issue_title: "refactor(pi-subagents): extract ToolFilterConfig from SessionConfig (11 fields)"
4
+ ---
5
+
6
+ # Retro: #168 — extract ToolFilterConfig from SessionConfig
7
+
8
+ ## Stage: Planning (2026-05-24T19:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a 2-step plan to extract `ToolFilterConfig` (grouping `toolNames`, `disallowedSet`, `extensions`) from `SessionConfig` and update `filterActiveTools` to accept the named type.
13
+ The change is a pure internal refactoring — `SessionConfig` is not exported from the package.
14
+
15
+ ### Observations
16
+
17
+ - The issue says "11 fields" but `SessionConfig` currently has 10 — likely a minor count discrepancy from when the issue was filed.
18
+ The extraction still reduces top-level fields from 10 to 8.
19
+ - `toolNames` serves dual duty: it's both the session-creation tool list and the `filterActiveTools` allowlist reference.
20
+ Nesting it under `toolFilter` is still correct since both uses originate from the same assembled config.
21
+ - `agent-runner-extension-tools.test.ts` exercises tool filtering end-to-end via `runAgent` and never references `SessionConfig` fields directly — it serves as a zero-change regression canary for this refactoring.
22
+ - The plan has only 2 TDD steps because the refactoring is mechanical and behavior-preserving.
23
+ Step 1 handles the interface change + assembler + tests; step 2 handles the consumer (`filterActiveTools` + `runAgent`).
24
+
25
+ ## Stage: Implementation — TDD (2026-05-24T19:30:00Z)
26
+
27
+ ### Session summary
28
+
29
+ Completed both refactoring steps cleanly.
30
+ `ToolFilterConfig` is now exported from `session-config.ts`, nested as `SessionConfig.toolFilter`, and consumed by `filterActiveTools` as a single named argument.
31
+ All 805 tests continue to pass; no new tests were added (pure structural refactoring with no behavior change).
32
+
33
+ ### Observations
34
+
35
+ - Step 1 left intentional type errors in `agent-runner.ts` (expected: the consumer hadn't been updated yet); committing mid-step-1 was correct because the session-config tests were green in isolation.
36
+ - The autoformatter ran on `agent-runner.ts` after the Step 2 edits (Biome reformatted the two condensed filter-call lines); the committed diff was already formatted.
37
+ - All 9 flat-field assertions in `session-config.test.ts` (`result.toolNames`, `result.extensions`, `result.disallowedSet`) were correctly migrated to `result.toolFilter.*` — grep confirmed no stragglers.
38
+ - `agent-runner-extension-tools.test.ts` required zero changes, confirming its role as a regression canary.
39
+ - Architecture doc updated: `SessionConfig` row in the wide-interface table marked `✓ done`; Step 5 narrative updated to reflect actual field count (10 → 8, not 11 → 8 as the issue stated).
40
+
41
+ ## Stage: Final Retrospective (2026-05-24T20:00:00Z)
42
+
43
+ ### Session summary
44
+
45
+ Issue #168 completed across three sessions (Planning → TDD → Ship) with zero friction, rework, or plan deviations.
46
+ Total diff: 3 files changed, 37 insertions, 41 deletions (net reduction).
47
+ Released as `pi-subagents-v6.18.5`.
48
+
49
+ ### Observations
50
+
51
+ #### What went well
52
+
53
+ - **Grep-before-commit safety net.**
54
+ The planning session identified 9 flat-field assertions in `session-config.test.ts` that would silently pass as `undefined` if missed during migration.
55
+ The TDD session grepped for all three field names before committing step 1, catching all 9 in one pass.
56
+ This is the testing skill’s "grep for all test files" rule applied to assertion migration.
57
+ - **Regression canary identification during planning.**
58
+ The planning session called out `agent-runner-extension-tools.test.ts` as a zero-change regression canary.
59
+ The TDD session confirmed this prediction — no changes needed, all existing tests green.
60
+ Identifying canary tests during planning gave confidence that the two refactoring steps were correctly scoped.
61
+ - **2-step granularity was right.**
62
+ Step 1 (interface + assembler + tests) left intentional type errors in the consumer.
63
+ Step 2 (consumer update) resolved them.
64
+ This kept each commit reviewable and type-check-green at the session-config boundary.
65
+
66
+ #### What caused friction (agent side)
67
+
68
+ None.
69
+
70
+ #### What caused friction (user side)
71
+
72
+ None.
73
+
74
+ ### Changes made
75
+
76
+ No process changes — clean execution with no proposals warranted.
@@ -0,0 +1,37 @@
1
+ ---
2
+ issue: 169
3
+ issue_title: "refactor(pi-subagents): extract RunContext from RunOptions (12 fields)"
4
+ ---
5
+
6
+ # Retro: #169 — extract RunContext from RunOptions
7
+
8
+ ## Stage: Planning (2026-05-24T17:07:10Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a plan to extract 4 parent-context fields (`exec`, `registry`, `cwd`, `parentSession`) from `RunOptions` into a nested `RunContext` interface.
13
+ The plan is a single-step refactor (all changes in one commit) plus a comment-update commit, affecting 3 source files and 3 test files.
14
+
15
+ ### Observations
16
+
17
+ - The issue body proposed flat `parentSessionFile`/`parentSessionId` fields on `RunContext`, but #166 already grouped these into `ParentSessionInfo`.
18
+ The plan uses `parentSession?: ParentSessionInfo` instead, preserving the existing grouping.
19
+ - `RunOptions` is purely internal — not exported via `service.ts` — so the refactor is non-breaking.
20
+ - All test call sites construct `RunOptions` inline (no `Partial<RunOptions>` spread patterns), so TypeScript will catch any missing `context` field at compile time.
21
+ - The change is small enough to land in a single TDD step — no lift-and-shift needed.
22
+ - Prerequisite #164 (directory reorganization) is already implemented.
23
+
24
+ ## Stage: Implementation — TDD (2026-05-24T17:14:32Z)
25
+
26
+ ### Session summary
27
+
28
+ Completed both TDD steps in one session.
29
+ Step 1 defined `RunContext`, updated `RunOptions`, migrated `runAgent()` reads to `options.context.*`, restructured `AgentManager.startAgent()`, and updated all 16 test call sites across 3 test files.
30
+ Step 2 updated comment references in `runtime.ts` and `session-config.ts`.
31
+ Test count unchanged (50 files, 805 tests — pure refactor with no behavior change).
32
+
33
+ ### Observations
34
+
35
+ - The `agent-manager.test.ts` update also added two new assertions (`context.exec` and `context.registry` are defined) to each existing `getRunConfig` threading test, confirming the context object is wired correctly; these were not in the plan but add useful coverage.
36
+ - All 16 `runAgent()` call sites in tests used inline option literals (no spread patterns), so TypeScript caught any missed site at compile time — the plan's risk mitigation held.
37
+ - No deviations from the plan otherwise; the comment-only step was trivial.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "6.18.4",
3
+ "version": "6.18.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/service.ts"
@@ -225,17 +225,19 @@ export class AgentManager {
225
225
 
226
226
  const runConfig = this.getRunConfig?.();
227
227
  const promise = this.runner.run(snapshot, type, prompt, {
228
- exec: this.exec,
228
+ context: {
229
+ exec: this.exec,
230
+ registry: this.registry,
231
+ cwd: worktreeCwd,
232
+ parentSession: options.parentSession,
233
+ },
229
234
  model: options.model,
230
235
  maxTurns: options.maxTurns,
231
236
  defaultMaxTurns: runConfig?.defaultMaxTurns,
232
237
  graceTurns: runConfig?.graceTurns,
233
238
  isolated: options.isolated,
234
239
  thinkingLevel: options.thinkingLevel,
235
- cwd: worktreeCwd,
236
- parentSession: options.parentSession,
237
240
  signal: record.abortController!.signal,
238
- registry: this.registry,
239
241
  onSessionCreated: (session) => {
240
242
  // Capture the session file path early so it's available for display
241
243
  // before the run completes (e.g. in background agent status messages).
@@ -13,7 +13,7 @@ import type { ParentSessionInfo } from "#src/lifecycle/agent-manager";
13
13
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
14
14
  import { extractText } from "#src/session/context";
15
15
  import type { EnvInfo } from "#src/session/env";
16
- import { type AssemblerIO, assembleSessionConfig } from "#src/session/session-config";
16
+ import { type AssemblerIO, assembleSessionConfig, type ToolFilterConfig } from "#src/session/session-config";
17
17
  import type { ShellExec, SubagentType, ThinkingLevel } from "#src/types";
18
18
 
19
19
  /** Names of tools registered by this extension that subagents must NOT inherit. */
@@ -45,16 +45,13 @@ function getToolCallName(c: { type: string }): string {
45
45
  * the post-bind re-filter trivial.
46
46
  *
47
47
  * @param activeTools Names currently active on the session.
48
- * @param toolNames The built-in tool name allowlist for this agent type.
49
- * @param extensions Agent config `extensions` field: false | true | string[] (allowlist).
50
- * @param disallowedSet Optional denylist from agent config.
48
+ * @param config Tool filtering configuration from the assembled session config.
51
49
  */
52
50
  function filterActiveTools(
53
51
  activeTools: string[],
54
- toolNames: string[],
55
- extensions: boolean | string[],
56
- disallowedSet: Set<string> | undefined,
52
+ config: ToolFilterConfig,
57
53
  ): string[] {
54
+ const { toolNames, extensions, disallowedSet } = config;
58
55
  if (extensions === false) {
59
56
  // Extensions disabled: only apply the denylist to built-in tools.
60
57
  if (!disallowedSet) return activeTools;
@@ -155,18 +152,31 @@ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
155
152
 
156
153
  // ── Public interfaces ─────────────────────────────────────────────────────────
157
154
 
158
- export interface RunOptions {
155
+ /**
156
+ * Parent execution context — where/who is running.
157
+ *
158
+ * Groups the four fields that describe the parent environment and identity,
159
+ * separating them from the per-call execution parameters in RunOptions.
160
+ */
161
+ export interface RunContext {
159
162
  /** Shell-exec callback for detectEnv — injected from pi.exec(). */
160
163
  exec: ShellExec;
164
+ /** Agent config lookup — provides resolveAgentConfig and getToolNamesForType. */
165
+ registry: AgentConfigLookup;
166
+ /** Override working directory (e.g. for worktree isolation). */
167
+ cwd?: string;
168
+ /** Parent session identity (file path + session ID). */
169
+ parentSession?: ParentSessionInfo;
170
+ }
171
+
172
+ export interface RunOptions {
173
+ /** Parent execution context — where/who is running. */
174
+ context: RunContext;
161
175
  model?: Model<any>;
162
176
  maxTurns?: number;
163
177
  signal?: AbortSignal;
164
178
  isolated?: boolean;
165
179
  thinkingLevel?: ThinkingLevel;
166
- /** Override working directory (e.g. for worktree isolation). */
167
- cwd?: string;
168
- /** Parent session identity (file path + session ID). */
169
- parentSession?: ParentSessionInfo;
170
180
  /** Called once after session creation — session delivery mechanism. */
171
181
  onSessionCreated?: (session: AgentSession) => void;
172
182
  /**
@@ -180,8 +190,6 @@ export interface RunOptions {
180
190
  * module-scope `graceTurns` during migration.
181
191
  */
182
192
  graceTurns?: number;
183
- /** Agent config lookup — provides resolveAgentConfig and getToolNamesForType. */
184
- registry: AgentConfigLookup;
185
193
  }
186
194
 
187
195
  export interface RunResult {
@@ -278,8 +286,8 @@ export async function runAgent(
278
286
  io: RunnerIO,
279
287
  ): Promise<RunResult> {
280
288
  // Resolve working directory upfront — needed for detectEnv before assembly.
281
- const effectiveCwd = options.cwd ?? snapshot.cwd;
282
- const env = await io.detectEnv(options.exec, effectiveCwd);
289
+ const effectiveCwd = options.context.cwd ?? snapshot.cwd;
290
+ const env = await io.detectEnv(options.context.exec, effectiveCwd);
283
291
 
284
292
  // Assemble session configuration (synchronous, no SDK objects).
285
293
  const cfg = assembleSessionConfig(
@@ -291,13 +299,13 @@ export async function runAgent(
291
299
  modelRegistry: snapshot.modelRegistry,
292
300
  },
293
301
  {
294
- cwd: options.cwd,
302
+ cwd: options.context.cwd,
295
303
  isolated: options.isolated,
296
304
  model: options.model,
297
305
  thinkingLevel: options.thinkingLevel,
298
306
  },
299
307
  env,
300
- options.registry,
308
+ options.context.registry,
301
309
  io.assemblerIO,
302
310
  );
303
311
 
@@ -312,7 +320,7 @@ export async function runAgent(
312
320
  const loader = io.createResourceLoader({
313
321
  cwd: cfg.effectiveCwd,
314
322
  agentDir,
315
- noExtensions: cfg.extensions === false,
323
+ noExtensions: cfg.toolFilter.extensions === false,
316
324
  noSkills: cfg.noSkills,
317
325
  noPromptTemplates: true,
318
326
  noThemes: true,
@@ -325,9 +333,9 @@ export async function runAgent(
325
333
  // Create a persisted SessionManager so transcripts are written in Pi's
326
334
  // official JSONL format. Falls back to a temp directory when the parent
327
335
  // session is not persisted (e.g. headless/API mode).
328
- const sessionDir = io.deriveSessionDir(options.parentSession?.parentSessionFile, cfg.effectiveCwd);
336
+ const sessionDir = io.deriveSessionDir(options.context.parentSession?.parentSessionFile, cfg.effectiveCwd);
329
337
  const sessionManager = io.createSessionManager(cfg.effectiveCwd, sessionDir);
330
- sessionManager.newSession({ parentSession: options.parentSession?.parentSessionId });
338
+ sessionManager.newSession({ parentSession: options.context.parentSession?.parentSessionId });
331
339
 
332
340
  const { session } = await io.createSession({
333
341
  cwd: cfg.effectiveCwd,
@@ -336,7 +344,7 @@ export async function runAgent(
336
344
  settingsManager: io.createSettingsManager(cfg.effectiveCwd, agentDir),
337
345
  modelRegistry: snapshot.modelRegistry,
338
346
  model: cfg.model,
339
- tools: cfg.toolNames,
347
+ tools: cfg.toolFilter.toolNames,
340
348
  resourceLoader: loader,
341
349
  thinkingLevel: cfg.thinkingLevel,
342
350
  });
@@ -344,13 +352,8 @@ export async function runAgent(
344
352
  // Filter active tools: remove our own tools to prevent nesting,
345
353
  // apply extension allowlist if specified, and apply disallowedTools denylist.
346
354
  // First pass — over built-in tools, before bindExtensions registers extension tools.
347
- if (cfg.extensions !== false || cfg.disallowedSet) {
348
- const filtered = filterActiveTools(
349
- session.getActiveToolNames(),
350
- cfg.toolNames,
351
- cfg.extensions,
352
- cfg.disallowedSet,
353
- );
355
+ if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
356
+ const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
354
357
  session.setActiveToolsByName(filtered);
355
358
  }
356
359
 
@@ -366,13 +369,8 @@ export async function runAgent(
366
369
  // re-filter, the `extensions: string[]` allowlist branch never matches any
367
370
  // extension tools and `extensions: true` lets non-allowlisted denylist
368
371
  // entries slip in. Run the same filter against the post-bind active set.
369
- if (cfg.extensions !== false || cfg.disallowedSet) {
370
- const refiltered = filterActiveTools(
371
- session.getActiveToolNames(),
372
- cfg.toolNames,
373
- cfg.extensions,
374
- cfg.disallowedSet,
375
- );
372
+ if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
373
+ const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
376
374
  session.setActiveToolsByName(refiltered);
377
375
  }
378
376
 
package/src/runtime.ts CHANGED
@@ -22,7 +22,7 @@ export interface WidgetLike {
22
22
  }
23
23
 
24
24
  /**
25
- * Narrow config subset read by AgentManager when constructing RunOptions.
25
+ * Narrow config subset read by AgentManager when constructing RunOptions execution fields.
26
26
  * Kept separate so callers can satisfy it without depending on the full runtime.
27
27
  */
28
28
  export interface RunConfig {
@@ -27,6 +27,21 @@ import type {
27
27
 
28
28
  // ── Public interfaces ────────────────────────────────────────────────────────
29
29
 
30
+ /**
31
+ * Tool filtering configuration — consumed by `filterActiveTools` in `agent-runner.ts`.
32
+ *
33
+ * Groups the three fields that travel together through the assembly→runner boundary:
34
+ * the built-in tool allowlist, the optional denylist, and the extensions setting.
35
+ */
36
+ export interface ToolFilterConfig {
37
+ /** Built-in tool name allowlist for this agent type. */
38
+ toolNames: string[];
39
+ /** Disallowed tool set from agentConfig. undefined when empty. */
40
+ disallowedSet: Set<string> | undefined;
41
+ /** Resolved extensions setting: false | true | string[] allowlist. */
42
+ extensions: boolean | string[];
43
+ }
44
+
30
45
  /**
31
46
  * IO collaborators injected into `assembleSessionConfig`.
32
47
  *
@@ -76,7 +91,7 @@ export interface AssemblerContext {
76
91
  }
77
92
 
78
93
  /**
79
- * Narrow slice of RunOptions consumed by the assembler.
94
+ * Narrow slice of RunOptions execution fields consumed by the assembler.
80
95
  * All fields are optional — callers pass only what they have.
81
96
  */
82
97
  export interface AssemblerOptions {
@@ -100,12 +115,8 @@ export interface SessionConfig {
100
115
  effectiveCwd: string;
101
116
  /** Fully-assembled system prompt string (ready for `systemPromptOverride`). */
102
117
  systemPrompt: string;
103
- /** Built-in tool names for session creation, filtering, and memory augmentation. */
104
- toolNames: string[];
105
- /** Disallowed tool set from agentConfig (for `filterActiveTools`). undefined when empty. */
106
- disallowedSet: Set<string> | undefined;
107
- /** Resolved extensions setting for resource loader and tool filtering. */
108
- extensions: boolean | string[];
118
+ /** Tool filtering cluster tool allowlist, denylist, and extensions setting. */
119
+ toolFilter: ToolFilterConfig;
109
120
  /**
110
121
  * Resolved model instance (undefined → use parent model as passed to SDK).
111
122
  * Opaque handle — the assembler passes it through without inspection.
@@ -264,9 +275,7 @@ export function assembleSessionConfig(
264
275
  return {
265
276
  effectiveCwd,
266
277
  systemPrompt,
267
- toolNames,
268
- disallowedSet,
269
- extensions,
278
+ toolFilter: { toolNames, disallowedSet, extensions },
270
279
  model,
271
280
  thinkingLevel,
272
281
  noSkills,