@gotgenes/pi-subagents 6.18.3 → 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 +23 -0
- package/docs/architecture/architecture.md +75 -136
- package/docs/plans/0167-narrow-runner-io.md +150 -0
- package/docs/plans/0168-extract-tool-filter-config.md +173 -0
- package/docs/retro/0167-narrow-runner-io.md +63 -0
- package/docs/retro/0168-extract-tool-filter-config.md +39 -0
- package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +29 -0
- package/package.json +1 -1
- package/src/lifecycle/agent-runner.ts +35 -30
- package/src/session/session-config.ts +18 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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
|
+
|
|
20
|
+
## [6.18.4](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.3...pi-subagents-v6.18.4) (2026-05-24)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
|
|
25
|
+
* mark RunnerIO split done in architecture ([#167](https://github.com/gotgenes/pi-packages/issues/167)) ([824fd72](https://github.com/gotgenes/pi-packages/commit/824fd726361f62b6696dcc62de3b0bbb9cf45711))
|
|
26
|
+
* plan narrow RunnerIO into EnvironmentIO + SessionFactoryIO ([#167](https://github.com/gotgenes/pi-packages/issues/167)) ([8110fec](https://github.com/gotgenes/pi-packages/commit/8110fec44dfaf08bd93d9cbc59ad04c6cba62a84))
|
|
27
|
+
* **retro:** add planning stage notes for issue [#167](https://github.com/gotgenes/pi-packages/issues/167) ([1aceff7](https://github.com/gotgenes/pi-packages/commit/1aceff77c8177093c60b90b87a3f991cb0186602))
|
|
28
|
+
* **retro:** add retro notes for issue [#180](https://github.com/gotgenes/pi-packages/issues/180) ([1fcd0ac](https://github.com/gotgenes/pi-packages/commit/1fcd0ace6fd7f5ec90a8d44423b276eb351875af))
|
|
29
|
+
* **retro:** add TDD stage notes for issue [#167](https://github.com/gotgenes/pi-packages/issues/167) ([870c767](https://github.com/gotgenes/pi-packages/commit/870c7670fdab831d408232c126312d0b5010d6f4))
|
|
30
|
+
|
|
8
31
|
## [6.18.3](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.2...pi-subagents-v6.18.3) (2026-05-24)
|
|
9
32
|
|
|
10
33
|
|
|
@@ -220,9 +220,9 @@ sequenceDiagram
|
|
|
220
220
|
|
|
221
221
|
## Module organization
|
|
222
222
|
|
|
223
|
-
The extension has
|
|
224
|
-
All eight domains
|
|
225
|
-
Issue #164 moved the 26 previously flat root-level files into
|
|
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
|
-
|
|
238
|
-
├── agent-types.ts
|
|
239
|
-
├── default-agents.ts
|
|
240
|
-
├── custom-agents.ts
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
├── session-config.ts
|
|
245
|
-
├── prompts.ts
|
|
246
|
-
├── context.ts
|
|
247
|
-
├── memory.ts
|
|
248
|
-
├── skill-loader.ts
|
|
249
|
-
├── env.ts
|
|
250
|
-
├── model-resolver.ts
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
├── agent-manager.ts
|
|
255
|
-
├── agent-runner.ts
|
|
256
|
-
├── agent-record.ts
|
|
257
|
-
├── parent-snapshot.ts
|
|
258
|
-
├── execution-state.ts
|
|
259
|
-
├── worktree.ts
|
|
260
|
-
├── worktree-state.ts
|
|
261
|
-
|
|
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
|
-
|
|
264
|
-
├── record-observer.ts
|
|
265
|
-
├── notification.ts
|
|
266
|
-
├── notification-state.ts
|
|
267
|
-
|
|
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
|
-
|
|
270
|
-
├── service.ts
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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.
|
|
@@ -500,20 +442,20 @@ These are fire-and-forget broadcast events — no request IDs, no reply channels
|
|
|
500
442
|
These interfaces carry hidden dependencies that obscure true coupling.
|
|
501
443
|
Bags with 10+ fields are the highest priority for decomposition.
|
|
502
444
|
|
|
503
|
-
| Interface | Fields
|
|
504
|
-
| --------------------------- |
|
|
505
|
-
| `ResolvedSpawnConfig` | 3 nested
|
|
506
|
-
| `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested)
|
|
507
|
-
| `RunOptions` | 12
|
|
508
|
-
| `SessionConfig` |
|
|
509
|
-
| `NotificationDetails` | 10
|
|
510
|
-
| `ResourceLoaderOptions` | 10
|
|
511
|
-
| `RunnerIO` |
|
|
512
|
-
| `CreateSessionOptions` | 9
|
|
513
|
-
| `AgentToolDeps` | 8
|
|
514
|
-
| `AgentMenuDeps` | 8
|
|
515
|
-
| `ConversationViewerOptions` | 8
|
|
516
|
-
| `AgentRecordInit` | 8
|
|
445
|
+
| Interface | Fields | Consumers | Severity |
|
|
446
|
+
| --------------------------- | ------------------------------------------------------ | ------------------------------------------------- | -------- |
|
|
447
|
+
| `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
|
|
448
|
+
| `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
|
|
449
|
+
| `RunOptions` | 12 | agent-runner | High |
|
|
450
|
+
| `SessionConfig` | 8 (ToolFilterConfig nested) | agent-runner (output of assembler) | ✓ done |
|
|
451
|
+
| `NotificationDetails` | 10 | notification | Medium |
|
|
452
|
+
| `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
|
|
453
|
+
| `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
|
|
454
|
+
| `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
|
|
455
|
+
| `AgentToolDeps` | 8 | agent-tool | Low |
|
|
456
|
+
| `AgentMenuDeps` | 8 | agent-menu | Low |
|
|
457
|
+
| `ConversationViewerOptions` | 8 | conversation-viewer | Low |
|
|
458
|
+
| `AgentRecordInit` | 8 | agent-record | Low |
|
|
517
459
|
|
|
518
460
|
### Complexity hotspots
|
|
519
461
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
626
|
-
/** Tool filtering configuration — used by filterActiveTools. */
|
|
627
|
-
interface ToolFilterConfig {
|
|
628
|
-
toolNames: string[];
|
|
629
|
-
disallowedSet: Set<string> | undefined;
|
|
630
|
-
extensions: boolean | string[];
|
|
631
|
-
}
|
|
632
|
-
```
|
|
569
|
+
#### RunnerIO (9 methods → 2 focused interfaces) — done ([#167][167])
|
|
633
570
|
|
|
634
|
-
|
|
635
|
-
|
|
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 —
|
|
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 —
|
|
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
|
-
|
|
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
|
|
|
@@ -681,15 +618,17 @@ Enables Step 3 (narrowing AgentSpawnConfig, [#166][166]).
|
|
|
681
618
|
Extracted `parentSessionFile`, `parentSessionId`, `toolCallId` into `ParentSessionInfo`.
|
|
682
619
|
`AgentSpawnConfig`, `BackgroundParams`, `ForegroundParams`, and `RunOptions` all carry the nested group.
|
|
683
620
|
|
|
684
|
-
### Step 4: Narrow RunnerIO ([#167][167])
|
|
621
|
+
### Step 4: Narrow RunnerIO ([#167][167]) ✓ Done
|
|
685
622
|
|
|
686
|
-
|
|
687
|
-
|
|
623
|
+
`RunnerIO` split into `EnvironmentIO` (3 methods: environment discovery) and `SessionFactoryIO` (5 methods + `assemblerIO`: SDK object creation).
|
|
624
|
+
`RunnerIO` kept as a backward-compatible type alias for the intersection.
|
|
625
|
+
All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
|
|
688
626
|
|
|
689
|
-
### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168])
|
|
627
|
+
### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168]) ✓ Done
|
|
690
628
|
|
|
691
|
-
|
|
692
|
-
|
|
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.
|
|
693
632
|
|
|
694
633
|
### Step 6: Extract RunContext from RunOptions ([#169][169])
|
|
695
634
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 167
|
|
3
|
+
issue_title: "refactor(pi-subagents): narrow RunnerIO (9 methods → 2 focused interfaces)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Narrow RunnerIO into EnvironmentIO + SessionFactoryIO
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`RunnerIO` in `agent-runner.ts` bundles 8 members (7 methods + 1 sub-interface) into a single IO boundary.
|
|
11
|
+
The methods split naturally into two concerns — environment discovery vs. SDK object creation — but the current monolithic interface forces every consumer (and every test mock) to provide all members regardless of which subset they actually use.
|
|
12
|
+
This violates Interface Segregation (ISP) and inflates test factory helpers.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Split `RunnerIO` into two focused interfaces: `EnvironmentIO` (3 methods) and `SessionFactoryIO` (5 methods + `assemblerIO`).
|
|
17
|
+
- Keep `RunnerIO` as a type alias (`EnvironmentIO & SessionFactoryIO`) so the change is fully backward-compatible at the type level.
|
|
18
|
+
- Update both test `createRunnerIO()` factories to use the new sub-interfaces in their comments/structure (no behavioral test changes needed — factories already return plain objects).
|
|
19
|
+
- Zero runtime behavior change.
|
|
20
|
+
|
|
21
|
+
## Non-Goals
|
|
22
|
+
|
|
23
|
+
- Splitting the `runAgent()` function itself — that's a separate concern.
|
|
24
|
+
- Changing how `createAgentRunner()` accepts its IO parameter — it keeps taking `RunnerIO` (the intersection).
|
|
25
|
+
- Refactoring `index.ts` wiring — the construction site already builds a plain object; it will continue to satisfy `RunnerIO`.
|
|
26
|
+
- Extracting `AssemblerIO` further — it already has its own interface in `session-config.ts`.
|
|
27
|
+
|
|
28
|
+
## Background
|
|
29
|
+
|
|
30
|
+
`RunnerIO` was introduced in issue #133 to decouple `agent-runner.ts` from direct Pi SDK imports.
|
|
31
|
+
It succeeded at making the runner testable via plain stubs, but bundled all IO into one wide interface.
|
|
32
|
+
Issue #164 (closed) reorganized source into domain directories; the current file path is `src/lifecycle/agent-runner.ts`.
|
|
33
|
+
|
|
34
|
+
The 8 members group into two cohesive clusters:
|
|
35
|
+
|
|
36
|
+
| Cluster | Members | Responsibility |
|
|
37
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
|
|
38
|
+
| Environment discovery | `detectEnv`, `getAgentDir`, `deriveSessionDir` | Discover runtime environment, resolve directories |
|
|
39
|
+
| Session factory | `createResourceLoader`, `createSessionManager`, `createSettingsManager`, `createSession`, `assemblerIO` | Create SDK objects for a child session |
|
|
40
|
+
|
|
41
|
+
In `runAgent()`, the environment methods are called first (lines ~265–275), then the factory methods build SDK objects (lines ~280–320).
|
|
42
|
+
The two groups have no cross-dependencies within `runAgent()`.
|
|
43
|
+
|
|
44
|
+
## Design Overview
|
|
45
|
+
|
|
46
|
+
### New interfaces
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
/** Environment discovery — detect runtime context and resolve directories. */
|
|
50
|
+
export interface EnvironmentIO {
|
|
51
|
+
detectEnv: (exec: ShellExec, cwd: string) => Promise<EnvInfo>;
|
|
52
|
+
getAgentDir: () => string;
|
|
53
|
+
deriveSessionDir: (parentSessionFile: string | undefined, effectiveCwd: string) => string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Session factory — create SDK objects for a child agent session. */
|
|
57
|
+
export interface SessionFactoryIO {
|
|
58
|
+
createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
|
|
59
|
+
createSessionManager: (cwd: string, sessionDir: string) => SessionManagerLike;
|
|
60
|
+
createSettingsManager: (cwd: string, agentDir: string) => SettingsManager;
|
|
61
|
+
createSession: (opts: CreateSessionOptions) => Promise<{ session: AgentSession }>;
|
|
62
|
+
assemblerIO: AssemblerIO;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* IO boundary injected into runAgent().
|
|
67
|
+
* Backward-compatible intersection of the two focused interfaces.
|
|
68
|
+
*/
|
|
69
|
+
export type RunnerIO = EnvironmentIO & SessionFactoryIO;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Backward compatibility
|
|
73
|
+
|
|
74
|
+
- `RunnerIO` becomes a type alias for the intersection.
|
|
75
|
+
Any code that imports `RunnerIO` continues to compile unchanged.
|
|
76
|
+
- `index.ts` builds a plain object literal that satisfies `RunnerIO` — no change needed.
|
|
77
|
+
- Test factories return unannotated objects that are structurally compatible — no change needed for compilation, but comments can be updated to reference the sub-interfaces.
|
|
78
|
+
|
|
79
|
+
### Consumer call-site verification
|
|
80
|
+
|
|
81
|
+
The call site in `createAgentRunner()` (3 lines):
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
export function createAgentRunner(io: RunnerIO): AgentRunner {
|
|
85
|
+
return {
|
|
86
|
+
run: (snapshot, type, prompt, options) => runAgent(snapshot, type, prompt, options, io),
|
|
87
|
+
resume: resumeAgent,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This passes the full `io` to `runAgent()`, which continues to accept `RunnerIO`.
|
|
93
|
+
No Tell-Don't-Ask or LoD violations introduced.
|
|
94
|
+
|
|
95
|
+
## Module-Level Changes
|
|
96
|
+
|
|
97
|
+
### `src/lifecycle/agent-runner.ts`
|
|
98
|
+
|
|
99
|
+
1. Add `EnvironmentIO` interface (3 members) before the current `RunnerIO` definition.
|
|
100
|
+
2. Add `SessionFactoryIO` interface (5 members) after `EnvironmentIO`.
|
|
101
|
+
3. Change `RunnerIO` from an `interface` to a `type` alias: `type RunnerIO = EnvironmentIO & SessionFactoryIO`.
|
|
102
|
+
4. Export the two new interfaces alongside `RunnerIO`.
|
|
103
|
+
5. Move existing JSDoc from `RunnerIO` members to the sub-interfaces.
|
|
104
|
+
6. No changes to function signatures — `runAgent()` and `createAgentRunner()` keep accepting `RunnerIO`.
|
|
105
|
+
|
|
106
|
+
### `src/index.ts`
|
|
107
|
+
|
|
108
|
+
No changes.
|
|
109
|
+
The construction site builds a plain object satisfying all 8 members — TypeScript's structural typing ensures it satisfies `EnvironmentIO & SessionFactoryIO` without annotation changes.
|
|
110
|
+
|
|
111
|
+
### Test files
|
|
112
|
+
|
|
113
|
+
No behavioral changes.
|
|
114
|
+
The `createRunnerIO()` factories in both test files return unannotated plain objects that structurally satisfy the intersection.
|
|
115
|
+
Comments referencing `RunnerIO` can be updated to mention the sub-interfaces for documentation clarity.
|
|
116
|
+
|
|
117
|
+
Files affected:
|
|
118
|
+
|
|
119
|
+
- `test/lifecycle/agent-runner.test.ts` — update comment (line ~23–27).
|
|
120
|
+
- `test/lifecycle/agent-runner-extension-tools.test.ts` — update comment (line ~46).
|
|
121
|
+
|
|
122
|
+
## Test Impact Analysis
|
|
123
|
+
|
|
124
|
+
1. **New unit tests enabled:** The split enables future tests that inject only `EnvironmentIO` or only `SessionFactoryIO` — useful when testing environment-only or factory-only code paths in future extractions.
|
|
125
|
+
No new tests are needed in this issue because `runAgent()` still consumes the full intersection.
|
|
126
|
+
2. **Redundant tests:** None — existing tests already test through `runAgent()` which uses all members.
|
|
127
|
+
3. **Tests that stay as-is:** All existing tests in both `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts` remain valid; the factories produce objects compatible with the new type alias.
|
|
128
|
+
|
|
129
|
+
## TDD Order
|
|
130
|
+
|
|
131
|
+
1. **Red → Green: export `EnvironmentIO` and `SessionFactoryIO`, convert `RunnerIO` to type alias.**
|
|
132
|
+
Test surface: existing `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts` suites (must still pass).
|
|
133
|
+
Run `pnpm run check` to verify type compatibility.
|
|
134
|
+
Commit: `refactor: split RunnerIO into EnvironmentIO and SessionFactoryIO (#167)`
|
|
135
|
+
|
|
136
|
+
2. **Update test comments to reference sub-interfaces.**
|
|
137
|
+
Test surface: no behavioral test changes — comment-only updates.
|
|
138
|
+
Commit: `refactor: update test comments for RunnerIO sub-interfaces (#167)`
|
|
139
|
+
|
|
140
|
+
## Risks and Mitigations
|
|
141
|
+
|
|
142
|
+
| Risk | Mitigation |
|
|
143
|
+
| --------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| External consumers importing `RunnerIO` break | `RunnerIO` remains exported as a type alias for the intersection — fully backward-compatible |
|
|
145
|
+
| Test factories need updating | Factories return unannotated objects — structural typing handles the new type alias without changes |
|
|
146
|
+
| Future extractions assume wrong interface | Each sub-interface has a clear JSDoc explaining its scope |
|
|
147
|
+
|
|
148
|
+
## Open Questions
|
|
149
|
+
|
|
150
|
+
None — the split follows the natural cohesion boundary identified in the issue body.
|
|
@@ -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,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 167
|
|
3
|
+
issue_title: "refactor(pi-subagents): narrow RunnerIO (9 methods → 2 focused interfaces)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #167 — narrow RunnerIO (9 methods → 2 focused interfaces)
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-24T20:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a plan to split the `RunnerIO` interface in `agent-runner.ts` into two focused sub-interfaces (`EnvironmentIO` and `SessionFactoryIO`) while keeping `RunnerIO` as a backward-compatible type alias for their intersection.
|
|
13
|
+
The change is a pure refactoring with zero runtime behavior change.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- The split is mechanical and low-risk: `RunnerIO` becomes `type RunnerIO = EnvironmentIO & SessionFactoryIO`, and all existing consumers (production code and test factories) continue to compile via structural typing.
|
|
18
|
+
- Dependency #164 (reorganize into domain directories) is already closed, so file paths are current.
|
|
19
|
+
- The two test `createRunnerIO()` factories are unannotated (intentionally, per testing skill guidelines), so they don't need type-level updates — only comment updates for documentation.
|
|
20
|
+
- This is a two-commit TDD plan, suitable for `/build-plan` rather than full TDD cycles since no new tests are required.
|
|
21
|
+
|
|
22
|
+
## Stage: Implementation — TDD (2026-05-24T20:45:00Z)
|
|
23
|
+
|
|
24
|
+
### Session summary
|
|
25
|
+
|
|
26
|
+
Completed both TDD steps in full.
|
|
27
|
+
Step 1 added `EnvironmentIO` and `SessionFactoryIO` interfaces and converted `RunnerIO` to a type alias in `agent-runner.ts`; step 2 updated comments in both test factories.
|
|
28
|
+
Test count held steady at 805/805 (50 files) — no behavioral changes.
|
|
29
|
+
|
|
30
|
+
### Observations
|
|
31
|
+
|
|
32
|
+
- The type check (`pnpm run check`) passed immediately after the interface split — structural typing meant zero call-site changes in `index.ts` or test factories.
|
|
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
|
+
- Architecture doc updated: wide-interface table row and Step 4 roadmap entry both marked done.
|
|
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).
|
|
@@ -31,3 +31,32 @@ Test count unchanged at 805 across 50 files.
|
|
|
31
31
|
- The JSDoc bullet for append mode also described the old ordering ("env header + parent system prompt + ...") and was corrected as part of the green step.
|
|
32
32
|
- The `<active_agent>` tag is followed by a `\n\n`, so when it moves after `<sub_agent_context>`, a `\n\n` separator between the bridge and the tag was needed to maintain clean section boundaries.
|
|
33
33
|
- No deviations from the plan; both steps were exactly as described.
|
|
34
|
+
|
|
35
|
+
## Stage: Final Retrospective (2026-05-24T21:00:00Z)
|
|
36
|
+
|
|
37
|
+
### Session summary
|
|
38
|
+
|
|
39
|
+
Issue #180 went from external community observation through release (`pi-subagents-v6.18.3`) in a single continuous session.
|
|
40
|
+
The plan predicted exactly two TDD steps; both executed without deviation or rework.
|
|
41
|
+
|
|
42
|
+
### Observations
|
|
43
|
+
|
|
44
|
+
#### What went well
|
|
45
|
+
|
|
46
|
+
- End-to-end lifecycle in one session: external comment → issue → plan → TDD → ship → release.
|
|
47
|
+
No corrections, no scope drift, no rework across any stage.
|
|
48
|
+
- The plan's test impact analysis was accurate — only two positional assertions needed updating; all `toContain()` tests passed untouched.
|
|
49
|
+
- Confirming pi-permission-system's `ACTIVE_AGENT_TAG_REGEX.exec()` is position-independent during planning eliminated the second `pkg:*` label's scope entirely, keeping the change to a single file.
|
|
50
|
+
|
|
51
|
+
#### What caused friction (agent side)
|
|
52
|
+
|
|
53
|
+
- `wrong-abstraction` — Launched an Explore agent (75.9s, 18 tool uses) to map the prompt assembly flow when the `package-pi-subagents` skill already listed the file layout and `prompts.ts` is 107 lines.
|
|
54
|
+
Direct `read` + `grep` achieved the same confirmation in ~3 seconds during the planning phase.
|
|
55
|
+
Impact: added ~75 seconds of latency but no rework.
|
|
56
|
+
- `missing-context` — The plan listed "Update the JSDoc comment" but missed that the mode-description bullet ("env header + parent system prompt + ...") also encoded the old ordering.
|
|
57
|
+
Caught during the green step and fixed in the same commit.
|
|
58
|
+
Impact: added friction but no rework.
|
|
59
|
+
|
|
60
|
+
#### What caused friction (user side)
|
|
61
|
+
|
|
62
|
+
- Nothing notable — the user's prompts were well-scoped and the issue description was unambiguous.
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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;
|
|
@@ -91,7 +88,7 @@ export interface SessionManagerLike {
|
|
|
91
88
|
getSessionFile(): string | undefined;
|
|
92
89
|
}
|
|
93
90
|
|
|
94
|
-
/** Options passed to
|
|
91
|
+
/** Options passed to EnvironmentIO/SessionFactoryIO methods. */
|
|
95
92
|
export interface ResourceLoaderOptions {
|
|
96
93
|
cwd: string;
|
|
97
94
|
agentDir: string;
|
|
@@ -105,7 +102,7 @@ export interface ResourceLoaderOptions {
|
|
|
105
102
|
appendSystemPromptOverride?: (base: string[]) => string[];
|
|
106
103
|
}
|
|
107
104
|
|
|
108
|
-
/** Options passed to
|
|
105
|
+
/** Options passed to SessionFactoryIO.createSession. */
|
|
109
106
|
export interface CreateSessionOptions {
|
|
110
107
|
cwd: string;
|
|
111
108
|
agentDir: string;
|
|
@@ -119,22 +116,40 @@ export interface CreateSessionOptions {
|
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
/**
|
|
122
|
-
*
|
|
119
|
+
* Environment discovery — detect runtime context and resolve directories.
|
|
123
120
|
*
|
|
124
|
-
* Decouples the runner from direct
|
|
125
|
-
*
|
|
121
|
+
* Decouples the runner from direct process/SDK reads so each can be stubbed
|
|
122
|
+
* independently in tests.
|
|
126
123
|
*/
|
|
127
|
-
export interface
|
|
124
|
+
export interface EnvironmentIO {
|
|
128
125
|
detectEnv: (exec: ShellExec, cwd: string) => Promise<EnvInfo>;
|
|
129
126
|
getAgentDir: () => string;
|
|
130
|
-
createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
|
|
131
127
|
deriveSessionDir: (parentSessionFile: string | undefined, effectiveCwd: string) => string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Session factory — create SDK objects for a child agent session.
|
|
132
|
+
*
|
|
133
|
+
* Decouples the runner from direct Pi SDK imports and sibling-module IO,
|
|
134
|
+
* making it testable via plain stub objects without vi.mock().
|
|
135
|
+
*/
|
|
136
|
+
export interface SessionFactoryIO {
|
|
137
|
+
createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
|
|
132
138
|
createSessionManager: (cwd: string, sessionDir: string) => SessionManagerLike;
|
|
133
139
|
createSettingsManager: (cwd: string, agentDir: string) => SettingsManager;
|
|
134
140
|
createSession: (opts: CreateSessionOptions) => Promise<{ session: AgentSession }>;
|
|
135
141
|
assemblerIO: AssemblerIO;
|
|
136
142
|
}
|
|
137
143
|
|
|
144
|
+
/**
|
|
145
|
+
* IO boundary injected into runAgent().
|
|
146
|
+
*
|
|
147
|
+
* Backward-compatible intersection of EnvironmentIO and SessionFactoryIO.
|
|
148
|
+
* Callers that previously constructed a RunnerIO object continue to satisfy
|
|
149
|
+
* both sub-interfaces via TypeScript's structural typing.
|
|
150
|
+
*/
|
|
151
|
+
export type RunnerIO = EnvironmentIO & SessionFactoryIO;
|
|
152
|
+
|
|
138
153
|
// ── Public interfaces ─────────────────────────────────────────────────────────
|
|
139
154
|
|
|
140
155
|
export interface RunOptions {
|
|
@@ -294,7 +309,7 @@ export async function runAgent(
|
|
|
294
309
|
const loader = io.createResourceLoader({
|
|
295
310
|
cwd: cfg.effectiveCwd,
|
|
296
311
|
agentDir,
|
|
297
|
-
noExtensions: cfg.extensions === false,
|
|
312
|
+
noExtensions: cfg.toolFilter.extensions === false,
|
|
298
313
|
noSkills: cfg.noSkills,
|
|
299
314
|
noPromptTemplates: true,
|
|
300
315
|
noThemes: true,
|
|
@@ -318,7 +333,7 @@ export async function runAgent(
|
|
|
318
333
|
settingsManager: io.createSettingsManager(cfg.effectiveCwd, agentDir),
|
|
319
334
|
modelRegistry: snapshot.modelRegistry,
|
|
320
335
|
model: cfg.model,
|
|
321
|
-
tools: cfg.toolNames,
|
|
336
|
+
tools: cfg.toolFilter.toolNames,
|
|
322
337
|
resourceLoader: loader,
|
|
323
338
|
thinkingLevel: cfg.thinkingLevel,
|
|
324
339
|
});
|
|
@@ -326,13 +341,8 @@ export async function runAgent(
|
|
|
326
341
|
// Filter active tools: remove our own tools to prevent nesting,
|
|
327
342
|
// apply extension allowlist if specified, and apply disallowedTools denylist.
|
|
328
343
|
// First pass — over built-in tools, before bindExtensions registers extension tools.
|
|
329
|
-
if (cfg.extensions !== false || cfg.disallowedSet) {
|
|
330
|
-
const filtered = filterActiveTools(
|
|
331
|
-
session.getActiveToolNames(),
|
|
332
|
-
cfg.toolNames,
|
|
333
|
-
cfg.extensions,
|
|
334
|
-
cfg.disallowedSet,
|
|
335
|
-
);
|
|
344
|
+
if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
|
|
345
|
+
const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
336
346
|
session.setActiveToolsByName(filtered);
|
|
337
347
|
}
|
|
338
348
|
|
|
@@ -348,13 +358,8 @@ export async function runAgent(
|
|
|
348
358
|
// re-filter, the `extensions: string[]` allowlist branch never matches any
|
|
349
359
|
// extension tools and `extensions: true` lets non-allowlisted denylist
|
|
350
360
|
// entries slip in. Run the same filter against the post-bind active set.
|
|
351
|
-
if (cfg.extensions !== false || cfg.disallowedSet) {
|
|
352
|
-
const refiltered = filterActiveTools(
|
|
353
|
-
session.getActiveToolNames(),
|
|
354
|
-
cfg.toolNames,
|
|
355
|
-
cfg.extensions,
|
|
356
|
-
cfg.disallowedSet,
|
|
357
|
-
);
|
|
361
|
+
if (cfg.toolFilter.extensions !== false || cfg.toolFilter.disallowedSet) {
|
|
362
|
+
const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
|
|
358
363
|
session.setActiveToolsByName(refiltered);
|
|
359
364
|
}
|
|
360
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
|
-
/**
|
|
104
|
-
|
|
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,
|