@gotgenes/pi-subagents 6.18.4 → 6.18.5

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,18 @@ 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.5](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.4...pi-subagents-v6.18.5) (2026-05-24)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * 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))
14
+ * plan extract ToolFilterConfig from SessionConfig ([#168](https://github.com/gotgenes/pi-packages/issues/168)) ([beaaeb4](https://github.com/gotgenes/pi-packages/commit/beaaeb41588bb93739b5ba6959c12202efbe7862))
15
+ * **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))
16
+ * **retro:** add retro notes for issue [#167](https://github.com/gotgenes/pi-packages/issues/167) ([cc96edd](https://github.com/gotgenes/pi-packages/commit/cc96edd20994a48ee2f824ea9136fa0da83e4c23))
17
+ * **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))
18
+ * 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))
19
+
8
20
  ## [6.18.4](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.3...pi-subagents-v6.18.4) (2026-05-24)
9
21
 
10
22
 
@@ -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.
@@ -505,7 +447,7 @@ Bags with 10+ fields are the highest priority for decomposition.
505
447
  | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
506
448
  | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
507
449
  | `RunOptions` | 12 | agent-runner | High |
508
- | `SessionConfig` | 11 | agent-runner (output of assembler) | High |
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 |
@@ -618,44 +560,39 @@ interface RunContext {
618
560
 
619
561
  The remaining `RunOptions` fields (`model`, `maxTurns`, `signal`, `isolated`, `thinkingLevel`, `defaultMaxTurns`, `graceTurns`, `onSessionCreated`) are genuine execution parameters.
620
562
 
621
- #### SessionConfig (11 fields → extract ToolFilterConfig)
563
+ #### SessionConfig (11 fields → extract ToolFilterConfig) — done ([#168][168])
622
564
 
623
- Three fields form a cohesive tool-filtering cluster:
565
+ The tool-filtering cluster (`toolNames`, `disallowedSet`, `extensions`) was extracted into `ToolFilterConfig` and nested as `SessionConfig.toolFilter`.
566
+ `filterActiveTools` now accepts a single `ToolFilterConfig` argument instead of three positional parameters.
567
+ `SessionConfig` reduced from 10 to 8 top-level fields.
624
568
 
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
- ```
633
-
634
- Extracting this reduces `SessionConfig` from 11 to 8 fields and gives `filterActiveTools` a named input type instead of three positional parameters.
569
+ #### RunnerIO (9 methods → 2 focused interfaces) — done ([#167][167])
635
570
 
636
- #### RunnerIO (9 methods 2 focused interfaces)
637
-
638
- The IO boundary mixes environment discovery with session factory operations:
571
+ The IO boundary was split into two focused interfaces:
639
572
 
640
573
  ```typescript
641
- /** Environment discovery — detecting paths and platform info. */
642
- interface EnvironmentIO {
574
+ /** Environment discovery — detect runtime context and resolve directories. */
575
+ export interface EnvironmentIO {
643
576
  detectEnv: (exec: ShellExec, cwd: string) => Promise<EnvInfo>;
644
577
  getAgentDir: () => string;
645
578
  deriveSessionDir: (parentSessionFile: string | undefined, effectiveCwd: string) => string;
646
579
  }
647
580
 
648
- /** Session factory — creating SDK objects. */
649
- interface SessionFactoryIO {
581
+ /** Session factory — create SDK objects for a child agent session. */
582
+ export interface SessionFactoryIO {
650
583
  createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
651
584
  createSessionManager: (cwd: string, sessionDir: string) => SessionManagerLike;
652
585
  createSettingsManager: (cwd: string, agentDir: string) => SettingsManager;
653
586
  createSession: (opts: CreateSessionOptions) => Promise<{ session: AgentSession }>;
654
587
  assemblerIO: AssemblerIO;
655
588
  }
589
+
590
+ /** Backward-compatible intersection of the two focused interfaces. */
591
+ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
656
592
  ```
657
593
 
658
- The runner would accept `EnvironmentIO & SessionFactoryIO` (keeping backward compatibility) while each piece can be tested independently.
594
+ `RunnerIO` is kept as a type alias for the intersection.
595
+ All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
659
596
 
660
597
  ## Improvement roadmap (Phase 10)
661
598
 
@@ -687,10 +624,11 @@ Extracted `parentSessionFile`, `parentSessionId`, `toolCallId` into `ParentSessi
687
624
  `RunnerIO` kept as a backward-compatible type alias for the intersection.
688
625
  All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
689
626
 
690
- ### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168])
627
+ ### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168]) ✓ Done
691
628
 
692
- Extract the tool-filtering cluster into `ToolFilterConfig`.
693
- Give `filterActiveTools` a named input type.
629
+ `ToolFilterConfig` extracted from `SessionConfig`, grouping `toolNames`, `disallowedSet`, and `extensions`.
630
+ `filterActiveTools` now accepts a single `ToolFilterConfig` argument.
631
+ `SessionConfig` reduced from 10 to 8 top-level fields.
694
632
 
695
633
  ### Step 6: Extract RunContext from RunOptions ([#169][169])
696
634
 
@@ -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.
@@ -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,39 @@
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).
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.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/service.ts"
@@ -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;
@@ -312,7 +309,7 @@ export async function runAgent(
312
309
  const loader = io.createResourceLoader({
313
310
  cwd: cfg.effectiveCwd,
314
311
  agentDir,
315
- noExtensions: cfg.extensions === false,
312
+ noExtensions: cfg.toolFilter.extensions === false,
316
313
  noSkills: cfg.noSkills,
317
314
  noPromptTemplates: true,
318
315
  noThemes: true,
@@ -336,7 +333,7 @@ export async function runAgent(
336
333
  settingsManager: io.createSettingsManager(cfg.effectiveCwd, agentDir),
337
334
  modelRegistry: snapshot.modelRegistry,
338
335
  model: cfg.model,
339
- tools: cfg.toolNames,
336
+ tools: cfg.toolFilter.toolNames,
340
337
  resourceLoader: loader,
341
338
  thinkingLevel: cfg.thinkingLevel,
342
339
  });
@@ -344,13 +341,8 @@ export async function runAgent(
344
341
  // Filter active tools: remove our own tools to prevent nesting,
345
342
  // apply extension allowlist if specified, and apply disallowedTools denylist.
346
343
  // 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
- );
344
+ if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
345
+ const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
354
346
  session.setActiveToolsByName(filtered);
355
347
  }
356
348
 
@@ -366,13 +358,8 @@ export async function runAgent(
366
358
  // re-filter, the `extensions: string[]` allowlist branch never matches any
367
359
  // extension tools and `extensions: true` lets non-allowlisted denylist
368
360
  // 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
- );
361
+ if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
362
+ const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
376
363
  session.setActiveToolsByName(refiltered);
377
364
  }
378
365
 
@@ -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
  *
@@ -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,