@gotgenes/pi-subagents 7.2.4 → 7.2.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 +21 -0
- package/docs/architecture/architecture.md +16 -150
- package/docs/architecture/history/phase-11-closure-to-class.md +100 -0
- package/docs/plans/0196-convert-runner-menu-to-classes.md +268 -0
- package/docs/plans/0205-decompose-render-widget-lines.md +140 -0
- package/docs/retro/0196-convert-runner-menu-to-classes.md +73 -0
- package/docs/retro/0205-decompose-render-widget-lines.md +36 -0
- package/package.json +1 -1
- package/src/index.ts +14 -28
- package/src/lifecycle/agent-runner.ts +12 -6
- package/src/ui/agent-menu.ts +96 -94
- package/src/ui/widget-renderer.ts +141 -77
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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
|
+
## [7.2.6](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.2.5...pi-subagents-v7.2.6) (2026-05-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* archive Phase 11 to history, add Phase 11 to refactoring table ([2c617f2](https://github.com/gotgenes/pi-packages/commit/2c617f2c752ea935d55878ba58fce997985086b5))
|
|
14
|
+
* plan decompose renderWidgetLines ([#205](https://github.com/gotgenes/pi-packages/issues/205)) ([88d09cd](https://github.com/gotgenes/pi-packages/commit/88d09cdad53bc3f215c069b8d6da6a44e10b5af7))
|
|
15
|
+
* **retro:** add planning stage notes for issue [#205](https://github.com/gotgenes/pi-packages/issues/205) ([14afc1f](https://github.com/gotgenes/pi-packages/commit/14afc1ff82d61828fdac9373f31cb68ebfc1a2e7))
|
|
16
|
+
* **retro:** add retro notes for issue [#196](https://github.com/gotgenes/pi-packages/issues/196) ([cfc7d94](https://github.com/gotgenes/pi-packages/commit/cfc7d94f72b120a4550f73e2d1cf00822db759c2))
|
|
17
|
+
* **retro:** add TDD stage notes for issue [#205](https://github.com/gotgenes/pi-packages/issues/205) ([a676078](https://github.com/gotgenes/pi-packages/commit/a6760789898435e5b552941124de6f32be21407e))
|
|
18
|
+
|
|
19
|
+
## [7.2.5](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.2.4...pi-subagents-v7.2.5) (2026-05-25)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
* mark Phase 11 Layer 3 and Layer 4 complete ([bf71795](https://github.com/gotgenes/pi-packages/commit/bf71795649a5b3c14a2b3ff16b2109d131a0ed32))
|
|
25
|
+
* plan convert AgentRunner and AgentsMenuHandler to classes ([#196](https://github.com/gotgenes/pi-packages/issues/196)) ([cd0bd1f](https://github.com/gotgenes/pi-packages/commit/cd0bd1fdec0c87655bdb38f8243084df807b676a))
|
|
26
|
+
* **retro:** add planning stage notes for issue [#196](https://github.com/gotgenes/pi-packages/issues/196) ([677d4bf](https://github.com/gotgenes/pi-packages/commit/677d4bf6619f13eba8d17181efab04cc67e47bbd))
|
|
27
|
+
* **retro:** add TDD stage notes for issue [#196](https://github.com/gotgenes/pi-packages/issues/196) ([72d24ba](https://github.com/gotgenes/pi-packages/commit/72d24ba56b8dc7668ff350ad3f0ba027b996d26e))
|
|
28
|
+
|
|
8
29
|
## [7.2.4](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.2.3...pi-subagents-v7.2.4) (2026-05-25)
|
|
9
30
|
|
|
10
31
|
|
|
@@ -599,154 +599,18 @@ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
|
|
|
599
599
|
`RunnerIO` is kept as a type alias for the intersection.
|
|
600
600
|
All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
|
|
601
601
|
|
|
602
|
-
##
|
|
602
|
+
## Phase 11 (complete)
|
|
603
603
|
|
|
604
|
-
Phase 11
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
> "Make the change that makes the change easy." —Kent Beck
|
|
608
|
-
|
|
609
|
-
### Findings
|
|
610
|
-
|
|
611
|
-
| Metric | Value |
|
|
612
|
-
| ------------------------- | ------------------------------------------ |
|
|
613
|
-
| Health score | 75/100 (B) |
|
|
614
|
-
| #1 hotspot | `index.ts` (128 commits, accelerating) |
|
|
615
|
-
| Dead exports | 0 (down from 1; Layer 2 removed re-export) |
|
|
616
|
-
| Production duplication | 0 |
|
|
617
|
-
| Test duplication | 1,396 lines (69 clone groups, 22 files) |
|
|
618
|
-
| `as any` casts in index | 1 (down from 5; Layer 1 resolved 4) |
|
|
619
|
-
| Adapter closures in index | 40 (down from 44; Layers 1–2 resolved 4) |
|
|
620
|
-
| Index fan-out | 25 imports |
|
|
621
|
-
|
|
622
|
-
### Root cause
|
|
623
|
-
|
|
624
|
-
The 44 adapter closures in `index.ts` exist because the tool factories accept narrow interfaces that don't structurally match the real objects.
|
|
625
|
-
The real objects can't satisfy the interfaces because:
|
|
626
|
-
|
|
627
|
-
1. ~~`SubagentRuntime.currentCtx` is typed `{ pi: unknown; ctx: unknown }` — so every consumer must `as any` cast to read fields.~~
|
|
628
|
-
Resolved by Layer 0 (#192) + Layer 1 (#193).
|
|
629
|
-
2. ~~Context queries (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) live as closures in index.ts instead of methods on the state holder.~~
|
|
630
|
-
Resolved by Layer 1 (#193).
|
|
631
|
-
3. ~~`AgentToolManager` mixes fields from `AgentManager` and `SettingsManager` (source mismatch).~~
|
|
632
|
-
Resolved by Layer 2 (#194).
|
|
633
|
-
4. ~~`AgentToolWidget` uses different method names than `SubagentRuntime` (name mismatch).~~
|
|
634
|
-
Resolved by Layer 2 (#194).
|
|
635
|
-
|
|
636
|
-
Fix these structural misalignments and the class conversions become mechanical.
|
|
637
|
-
|
|
638
|
-
### Layer 0: Define `SessionContext` narrow interface ([#192][192])
|
|
639
|
-
|
|
640
|
-
`SubagentRuntime` currently types its context as `unknown` to avoid SDK coupling.
|
|
641
|
-
But `ExtensionContext` is exported by the SDK — the `unknown` is a historical choice, not a constraint.
|
|
642
|
-
Define a narrow `SessionContext` interface capturing the 5 fields runtime actually needs:
|
|
643
|
-
|
|
644
|
-
```typescript
|
|
645
|
-
export interface SessionContext {
|
|
646
|
-
readonly cwd: string;
|
|
647
|
-
readonly model: unknown;
|
|
648
|
-
readonly modelRegistry: ModelRegistry | undefined;
|
|
649
|
-
getSystemPrompt(): string;
|
|
650
|
-
readonly sessionManager: {
|
|
651
|
-
getSessionFile(): string | undefined;
|
|
652
|
-
getSessionId(): string;
|
|
653
|
-
getBranch(): unknown[];
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
- Target: `src/types.ts`
|
|
659
|
-
- Smell: Category C (platform type threading)
|
|
660
|
-
- Outcome: typed foundation for Layers 1–4; no `as any` needed by consumers of `SubagentRuntime`
|
|
661
|
-
|
|
662
|
-
### Layer 1: `SubagentRuntime` stores typed context, owns its queries ([#193][193]) ✓ done
|
|
663
|
-
|
|
664
|
-
Change `currentCtx` from `{ pi: unknown; ctx: unknown }` to `SessionContext | undefined`.
|
|
665
|
-
The single `as SessionContext` cast moves into `handleSessionStart` — the boundary where the SDK hands us the value.
|
|
666
|
-
Add typed methods: `buildSnapshot(inheritContext)`, `getModelInfo()`, `getSessionInfo()`.
|
|
667
|
-
|
|
668
|
-
- Target: `src/runtime.ts`, `src/handlers/lifecycle.ts`, `src/service/service-adapter.ts`, `src/index.ts`
|
|
669
|
-
- Smell: Category C (closure queries on mutable field → methods on state owner)
|
|
670
|
-
- Outcome: 3 closure queries in index.ts → 0; `SubagentRuntime` is self-sufficient for tool deps
|
|
671
|
-
- Enables: Layer 3 (tools accept `SubagentRuntime` directly)
|
|
672
|
-
|
|
673
|
-
### Layer 2: Align interfaces so real objects satisfy tool deps structurally ([#194][194]) ✓ done
|
|
674
|
-
|
|
675
|
-
Three alignment changes:
|
|
676
|
-
|
|
677
|
-
1. **Move `getMaxConcurrent` off `AgentToolManager`** — it reads from `SettingsManager`, not `AgentManager`.
|
|
678
|
-
The tool already receives `settings`; read it from there.
|
|
679
|
-
2. **Rename widget methods** — align `SubagentRuntime` method names with `AgentToolWidget` (either rename `updateWidget()` → `update()` on runtime, or rename the interface to match).
|
|
680
|
-
3. **Remove dead re-export** — `getToolCallName` in `ui/message-formatters.ts` (fallow finding).
|
|
681
|
-
|
|
682
|
-
After this step, `AgentManager` structurally satisfies `AgentToolManager` and `SubagentRuntime` structurally satisfies `AgentToolWidget`.
|
|
683
|
-
|
|
684
|
-
- Target: `src/tools/agent-tool.ts` (interface), `src/runtime.ts` (method names), `src/ui/message-formatters.ts`
|
|
685
|
-
- Smell: Category C (source mismatch, name mismatch) + Category A (dead export)
|
|
686
|
-
- Outcome: structural typing connects real objects to tool interfaces without adapters; 0 dead exports (fallow clean)
|
|
687
|
-
- Enables: Layer 3 (class constructors accept real objects directly)
|
|
688
|
-
|
|
689
|
-
### Layer 3: Convert closure factories to classes ([#195][195], [#196][196])
|
|
690
|
-
|
|
691
|
-
With Layers 0–2 complete, each factory is a mechanical conversion. ✓ Tool factories converted in [#195][195]:
|
|
692
|
-
|
|
693
|
-
| Factory | Class | Constructor params |
|
|
694
|
-
| -------------------------------- | ------------------------------ | --------------------------------------------------- |
|
|
695
|
-
| `createAgentTool({...})` | `AgentTool` ✓ | `manager`, `runtime`, `settings`, `registry` |
|
|
696
|
-
| `createGetResultTool(...)` | `GetResultTool` ✓ | `manager`, `notifications`, `registry` |
|
|
697
|
-
| `createSteerTool(...)` | `SteerTool` ✓ | `manager`, `events` |
|
|
698
|
-
| `createAgentRunner(runnerIO)` | `AgentRunner` (concrete class) | `io: RunnerIO` |
|
|
699
|
-
| `createAgentsMenuHandler({...})` | `AgentsMenuHandler` | `manager`, `registry`, `settings`, `fileOps`, paths |
|
|
700
|
-
|
|
701
|
-
Each class satisfies the existing interface via structural typing.
|
|
702
|
-
The `defineTool()` wrapper moves into a `toToolDefinition()` method on each tool class.
|
|
703
|
-
|
|
704
|
-
- Target: `src/tools/*.ts`, `src/lifecycle/agent-runner.ts`, `src/ui/agent-menu.ts`
|
|
705
|
-
- Smell: Category C (closure factories masquerading as classes)
|
|
706
|
-
- Outcome: deps are constructor params (inspectable, testable); no captured closures
|
|
707
|
-
- Enables: Layer 4 (index.ts simplification)
|
|
708
|
-
|
|
709
|
-
### Layer 4: Simplify index.ts (included in [#196][196])
|
|
710
|
-
|
|
711
|
-
With real objects satisfying tool interfaces and queries living on `SubagentRuntime`, the composition root becomes pure construction:
|
|
712
|
-
|
|
713
|
-
```typescript
|
|
714
|
-
const runtime = new SubagentRuntime();
|
|
715
|
-
const settings = new SettingsManager(...);
|
|
716
|
-
const manager = new AgentManager(...);
|
|
717
|
-
const agentTool = new AgentTool(manager, runtime, settings, registry);
|
|
718
|
-
pi.registerTool(agentTool.toToolDefinition());
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
No adapter closures.
|
|
722
|
-
No `as any`.
|
|
723
|
-
Fan-out drops from 25 to ~15 (internal factories eliminated).
|
|
724
|
-
|
|
725
|
-
- Target: `src/index.ts`
|
|
726
|
-
- Smell: Category B (god file) + Category C (adapter closure density)
|
|
727
|
-
- Outcome: index.ts shrinks from 280 to ~150 lines; churn hotspot stabilizes
|
|
728
|
-
|
|
729
|
-
### Step dependencies
|
|
730
|
-
|
|
731
|
-
```mermaid
|
|
732
|
-
flowchart LR
|
|
733
|
-
L0["Layer 0: SessionContext interface"] --> L1["Layer 1: Runtime owns queries"]
|
|
734
|
-
L1 --> L3["Layer 3: Classes replace factories"]
|
|
735
|
-
L2["Layer 2: Align interfaces"] --> L3
|
|
736
|
-
L3 --> L4["Layer 4: Simplify index.ts"]
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
Layers 0 and 2 are independent of each other.
|
|
740
|
-
Layer 1 depends on Layer 0.
|
|
741
|
-
Layer 3 depends on both Layer 1 and Layer 2.
|
|
742
|
-
Layer 4 depends on Layer 3.
|
|
604
|
+
Phase 11 converted all closure factories to classes, eliminating adapter closure density in `index.ts`.
|
|
605
|
+
Four layers: SessionContext typing → runtime query methods → interface alignment → class conversions → index.ts simplification.
|
|
606
|
+
See [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) for details.
|
|
743
607
|
|
|
744
608
|
## Improvement roadmap (Phase 12)
|
|
745
609
|
|
|
746
610
|
Phase 12 addresses the remaining fallow refactoring targets and test duplication.
|
|
747
611
|
These are independent of Phase 11 and can proceed in parallel if desired.
|
|
748
612
|
|
|
749
|
-
### Step 1: Decompose `renderWidgetLines` (cognitive 44)
|
|
613
|
+
### Step 1: Decompose `renderWidgetLines` (cognitive 44) — [#205]
|
|
750
614
|
|
|
751
615
|
`renderWidgetLines` in `ui/widget-renderer.ts` handles agent-status formatting, tree connectors, overflow, and empty states.
|
|
752
616
|
Extract per-status renderers and a tree-connector utility.
|
|
@@ -754,7 +618,7 @@ Extract per-status renderers and a tree-connector utility.
|
|
|
754
618
|
- Target: `src/ui/widget-renderer.ts`
|
|
755
619
|
- Outcome: cognitive complexity < 10
|
|
756
620
|
|
|
757
|
-
### Step 2: Decompose `showAgentDetail` (cognitive 33)
|
|
621
|
+
### Step 2: Decompose `showAgentDetail` (cognitive 33) — [#206]
|
|
758
622
|
|
|
759
623
|
`showAgentDetail` in `ui/agent-config-editor.ts` handles display, edit, eject, and delete flows.
|
|
760
624
|
Extract sub-functions per menu action.
|
|
@@ -762,7 +626,7 @@ Extract sub-functions per menu action.
|
|
|
762
626
|
- Target: `src/ui/agent-config-editor.ts`
|
|
763
627
|
- Outcome: cognitive complexity < 10
|
|
764
628
|
|
|
765
|
-
### Step 3: Decompose `update` in `agent-widget.ts` (cognitive 31)
|
|
629
|
+
### Step 3: Decompose `update` in `agent-widget.ts` (cognitive 31) — [#207]
|
|
766
630
|
|
|
767
631
|
`update` mixes timer lifecycle, agent list assembly, render delegation, and visibility state.
|
|
768
632
|
Extract `assembleWidgetState` (pure) and timer management.
|
|
@@ -770,7 +634,7 @@ Extract `assembleWidgetState` (pure) and timer management.
|
|
|
770
634
|
- Target: `src/ui/agent-widget.ts`
|
|
771
635
|
- Outcome: cognitive complexity < 10
|
|
772
636
|
|
|
773
|
-
### Step 4: Extract shared test fixtures
|
|
637
|
+
### Step 4: Extract shared test fixtures — [#208]
|
|
774
638
|
|
|
775
639
|
The 3 heaviest clone families:
|
|
776
640
|
|
|
@@ -785,7 +649,7 @@ Extract shared factories into `test/fixtures/` modules.
|
|
|
785
649
|
|
|
786
650
|
## Refactoring history
|
|
787
651
|
|
|
788
|
-
Phases 1–5 and 7–
|
|
652
|
+
Phases 1–5 and 7–11 are complete.
|
|
789
653
|
Phase 6 (UI extraction to a separate package) is deferred.
|
|
790
654
|
Detailed records are preserved in per-phase history files:
|
|
791
655
|
|
|
@@ -801,6 +665,7 @@ Detailed records are preserved in per-phase history files:
|
|
|
801
665
|
| 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
|
|
802
666
|
| 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
|
|
803
667
|
| 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
|
|
668
|
+
| 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
|
|
804
669
|
|
|
805
670
|
### Structural refactoring issues
|
|
806
671
|
|
|
@@ -815,6 +680,8 @@ Detailed records are preserved in per-phase history files:
|
|
|
815
680
|
| Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
|
|
816
681
|
| Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
|
|
817
682
|
| Phase 10 | #164, #165, #166, #167, #168, #169, #170, #171, #172 | Domain directories, ResolvedSpawnConfig, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult, content-items |
|
|
683
|
+
| Phase 11 | #192, #193, #194, #195, #196 | SessionContext, runtime queries, interface alignment, tool classes, runner/menu classes, index.ts simplification |
|
|
684
|
+
| Phase 12 | #205, #206, #207, #208 | renderWidgetLines, showAgentDetail, widget update, shared test fixtures |
|
|
818
685
|
|
|
819
686
|
The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
|
|
820
687
|
|
|
@@ -838,8 +705,7 @@ The upstream test suite is run periodically as a regression canary for the agent
|
|
|
838
705
|
[167]: https://github.com/gotgenes/pi-packages/issues/167
|
|
839
706
|
[168]: https://github.com/gotgenes/pi-packages/issues/168
|
|
840
707
|
[169]: https://github.com/gotgenes/pi-packages/issues/169
|
|
841
|
-
[
|
|
842
|
-
[
|
|
843
|
-
[
|
|
844
|
-
[
|
|
845
|
-
[196]: https://github.com/gotgenes/pi-packages/issues/196
|
|
708
|
+
[#205]: https://github.com/gotgenes/pi-packages/issues/205
|
|
709
|
+
[#206]: https://github.com/gotgenes/pi-packages/issues/206
|
|
710
|
+
[#207]: https://github.com/gotgenes/pi-packages/issues/207
|
|
711
|
+
[#208]: https://github.com/gotgenes/pi-packages/issues/208
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Phase 11: Closure factories to classes
|
|
2
|
+
|
|
3
|
+
Target: convert all closure factories to classes, eliminating adapter closure density in `index.ts` (the package's #1 churn hotspot) and making the composition root a pure construction site.
|
|
4
|
+
|
|
5
|
+
## Root cause
|
|
6
|
+
|
|
7
|
+
The 44 adapter closures in `index.ts` existed because tool factories accepted narrow interfaces that didn't structurally match the real objects.
|
|
8
|
+
Four structural misalignments prevented direct wiring:
|
|
9
|
+
|
|
10
|
+
1. `SubagentRuntime.currentCtx` was typed `{ pi: unknown; ctx: unknown }` — every consumer had to `as any` cast.
|
|
11
|
+
2. Context queries (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) lived as closures in `index.ts` instead of methods on the state holder.
|
|
12
|
+
3. `AgentToolManager` mixed fields from `AgentManager` and `SettingsManager` (source mismatch).
|
|
13
|
+
4. `AgentToolWidget` used different method names than `SubagentRuntime` (name mismatch).
|
|
14
|
+
|
|
15
|
+
## Layer 0: Define SessionContext narrow interface (#192)
|
|
16
|
+
|
|
17
|
+
Defined a `SessionContext` interface capturing the 5 fields `SubagentRuntime` actually needs.
|
|
18
|
+
Replaced the `{ pi: unknown; ctx: unknown }` typing with `SessionContext | undefined`.
|
|
19
|
+
|
|
20
|
+
Impact: typed foundation for all subsequent layers; no `as any` needed by consumers.
|
|
21
|
+
|
|
22
|
+
## Layer 1: SubagentRuntime stores typed context, owns its queries (#193)
|
|
23
|
+
|
|
24
|
+
Changed `currentCtx` to `SessionContext | undefined`.
|
|
25
|
+
The single `as SessionContext` cast moved into `handleSessionStart` — the SDK boundary.
|
|
26
|
+
Added typed methods: `buildSnapshot(inheritContext)`, `getModelInfo()`, `getSessionInfo()`.
|
|
27
|
+
|
|
28
|
+
Impact: 3 closure queries in `index.ts` → 0; 4 `as any` casts eliminated; `SubagentRuntime` is self-sufficient for tool deps.
|
|
29
|
+
|
|
30
|
+
## Layer 2: Align interfaces for structural typing (#194)
|
|
31
|
+
|
|
32
|
+
Three alignment changes:
|
|
33
|
+
|
|
34
|
+
1. Moved `getMaxConcurrent` off `AgentToolManager` — it reads from `SettingsManager`, not `AgentManager`.
|
|
35
|
+
2. Renamed widget methods — aligned `SubagentRuntime` method names with `AgentToolWidget`.
|
|
36
|
+
3. Removed dead re-export `getToolCallName` in `ui/message-formatters.ts`.
|
|
37
|
+
|
|
38
|
+
Impact: `AgentManager` structurally satisfies `AgentToolManager`; `SubagentRuntime` structurally satisfies `AgentToolWidget`; 0 dead exports.
|
|
39
|
+
|
|
40
|
+
## Layer 3: Convert closure factories to classes (#195, #196)
|
|
41
|
+
|
|
42
|
+
All five closure factories converted to classes:
|
|
43
|
+
|
|
44
|
+
| Factory | Class | Issue |
|
|
45
|
+
| -------------------------------- | --------------------- | ----- |
|
|
46
|
+
| `createAgentTool({...})` | `AgentTool` | #195 |
|
|
47
|
+
| `createGetResultTool(...)` | `GetResultTool` | #195 |
|
|
48
|
+
| `createSteerTool(...)` | `SteerTool` | #195 |
|
|
49
|
+
| `createAgentRunner(runnerIO)` | `ConcreteAgentRunner` | #196 |
|
|
50
|
+
| `createAgentsMenuHandler({...})` | `AgentsMenuHandler` | #196 |
|
|
51
|
+
|
|
52
|
+
Each class satisfies the existing interface via structural typing.
|
|
53
|
+
Tool classes expose `toToolDefinition()` for Pi tool registration.
|
|
54
|
+
`getModelLabel` internalized into `AgentsMenuHandler` (was a 7-line closure in `index.ts`).
|
|
55
|
+
|
|
56
|
+
Impact: deps are constructor params (inspectable, testable); no captured closures.
|
|
57
|
+
|
|
58
|
+
## Layer 4: Simplify index.ts (#196)
|
|
59
|
+
|
|
60
|
+
With all factories converted and `AgentManager` satisfying `AgentMenuManager` structurally:
|
|
61
|
+
|
|
62
|
+
- 4 adapter closures eliminated (3 manager method adapters + `getModelLabel`).
|
|
63
|
+
- 4 unused imports removed.
|
|
64
|
+
- Remaining ~15 closures are structural (event registrations, SDK factory callbacks).
|
|
65
|
+
|
|
66
|
+
Impact: adapter closure count halved; `AgentManager` passed directly without wrappers; churn hotspot stabilized.
|
|
67
|
+
|
|
68
|
+
## Step dependencies
|
|
69
|
+
|
|
70
|
+
```mermaid
|
|
71
|
+
flowchart LR
|
|
72
|
+
L0["Layer 0: SessionContext #192"] --> L1["Layer 1: Runtime queries #193"]
|
|
73
|
+
L1 --> L3["Layer 3: Classes #195 #196"]
|
|
74
|
+
L2["Layer 2: Align interfaces #194"] --> L3
|
|
75
|
+
L3 --> L4["Layer 4: Simplify index.ts #196"]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Layers 0 and 2 were independent.
|
|
79
|
+
Layer 1 depended on Layer 0.
|
|
80
|
+
Layer 3 depended on both Layer 1 and Layer 2.
|
|
81
|
+
Layer 4 depended on Layer 3.
|
|
82
|
+
|
|
83
|
+
## Impact
|
|
84
|
+
|
|
85
|
+
| Metric | Before | After |
|
|
86
|
+
| ------------------------------ | ---------- | --------------------- |
|
|
87
|
+
| Health score | 75/100 (B) | 76/100 (B) |
|
|
88
|
+
| Adapter closures in `index.ts` | 44 | ~15 (structural only) |
|
|
89
|
+
| `as any` casts in `index.ts` | 5 | 1 (SDK boundary) |
|
|
90
|
+
| Dead exports | 1 | 0 |
|
|
91
|
+
| Closure factories | 5 | 0 |
|
|
92
|
+
| `index.ts` fan-out | 25 imports | 21 imports |
|
|
93
|
+
|
|
94
|
+
## Related issues
|
|
95
|
+
|
|
96
|
+
- #192 — Define SessionContext narrow interface
|
|
97
|
+
- #193 — Runtime owns context queries
|
|
98
|
+
- #194 — Align tool interfaces for structural typing
|
|
99
|
+
- #195 — Convert tool factories to classes
|
|
100
|
+
- #196 — Convert AgentRunner and AgentsMenuHandler to classes, simplify index.ts
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 196
|
|
3
|
+
issue_title: "Convert AgentRunner and AgentsMenuHandler to classes, simplify index.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Convert AgentRunner and AgentsMenuHandler to classes, simplify index.ts
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
Two remaining closure factories in pi-subagents mask class-shaped code:
|
|
11
|
+
|
|
12
|
+
1. `createAgentRunner(runnerIO)` captures `RunnerIO` and returns `{ run, resume }` — the `AgentRunner` interface already exists but lacks a concrete class implementation.
|
|
13
|
+
2. `createAgentsMenuHandler({...})` captures an 8-field deps object and returns a handler function.
|
|
14
|
+
|
|
15
|
+
With #195 (tool factory → class conversions) complete, these are the last two closure factories.
|
|
16
|
+
After converting them, `index.ts` can be simplified: adapter closures drop, fan-out decreases, and the composition root becomes pure object construction.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Convert `createAgentRunner` factory to a concrete `ConcreteAgentRunner` class implementing `AgentRunner`.
|
|
21
|
+
- Convert `createAgentsMenuHandler` factory to an `AgentsMenuHandler` class.
|
|
22
|
+
- Internalize the `getModelLabel` closure from `index.ts` into `AgentsMenuHandler` (it uses `resolveModel` and `getModelLabelFromConfig`, both pure functions the class can import directly).
|
|
23
|
+
- Pass `AgentManager` directly as the `manager` param (it structurally satisfies `AgentMenuManager`), eliminating 3 adapter closures.
|
|
24
|
+
- Simplify `index.ts` by removing eliminated adapter closures and unused imports.
|
|
25
|
+
- Update architecture doc to mark Layer 3 and Layer 4 as done.
|
|
26
|
+
|
|
27
|
+
## Non-Goals
|
|
28
|
+
|
|
29
|
+
- Changing `runAgent()` or `resumeAgent()` function signatures — they remain as free functions called by the class.
|
|
30
|
+
- Removing the `AgentRunner` interface — it stays as the contract for `AgentManager`.
|
|
31
|
+
- Removing `RunnerIO` type — it stays as the IO boundary for `runAgent()`.
|
|
32
|
+
- Changing the `AgentMenuManager` interface — it stays as the narrow contract; `AgentManager` satisfies it structurally.
|
|
33
|
+
- Removing `AgentMenuDeps` — it is replaced by the class constructor; the type itself is removed.
|
|
34
|
+
- Refactoring `NotificationManager`, `SettingsManager`, or `SessionLifecycleHandler` — those are already class-shaped.
|
|
35
|
+
- Phase 12 work (decompose `renderWidgetLines`, consolidate test duplication).
|
|
36
|
+
|
|
37
|
+
## Background
|
|
38
|
+
|
|
39
|
+
### Phase 11 layer structure
|
|
40
|
+
|
|
41
|
+
Phase 11 in `docs/architecture/architecture.md` converts closure factories to classes in four layers:
|
|
42
|
+
|
|
43
|
+
- Layer 0: `SessionContext` interface (#192) ✓
|
|
44
|
+
- Layer 1: Runtime owns context queries (#193) ✓
|
|
45
|
+
- Layer 2: Align interfaces for structural typing (#194) ✓
|
|
46
|
+
- Layer 3: Convert closure factories to classes (#195 tools ✓, #196 runner + menu)
|
|
47
|
+
- Layer 4: Simplify `index.ts` (#196)
|
|
48
|
+
|
|
49
|
+
Issue #196 completes Layer 3 (runner + menu) and Layer 4 (index.ts simplification).
|
|
50
|
+
|
|
51
|
+
### Current state
|
|
52
|
+
|
|
53
|
+
`createAgentRunner` (3 lines) wraps `runAgent`/`resumeAgent` in an object literal:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
export function createAgentRunner(io: RunnerIO): AgentRunner {
|
|
57
|
+
return {
|
|
58
|
+
run: (snapshot, type, prompt, options) => runAgent(snapshot, type, prompt, options, io),
|
|
59
|
+
resume: resumeAgent,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`createAgentsMenuHandler` (200+ lines) captures 8 deps and returns a handler function.
|
|
65
|
+
The deps bag includes `getModelLabel` (a closure built in `index.ts`) and `agentActivity` (a `Map` from runtime).
|
|
66
|
+
|
|
67
|
+
`index.ts` currently has ~23 arrow closures and 27 imports at 229 lines.
|
|
68
|
+
|
|
69
|
+
### Structural typing confirmation
|
|
70
|
+
|
|
71
|
+
`AgentManager` already has `listAgents()`, `getRecord()`, and `spawnAndWait()` methods that structurally satisfy `AgentMenuManager`.
|
|
72
|
+
The `spawnAndWait` parameter type (`Omit<AgentSpawnConfig, "isBackground">`) is a superset of the menu's `{ description, maxTurns }`, so structural typing matches.
|
|
73
|
+
|
|
74
|
+
## Design Overview
|
|
75
|
+
|
|
76
|
+
### ConcreteAgentRunner
|
|
77
|
+
|
|
78
|
+
A minimal class that implements `AgentRunner` by delegating to the free functions:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export class ConcreteAgentRunner implements AgentRunner {
|
|
82
|
+
constructor(private readonly io: RunnerIO) {}
|
|
83
|
+
|
|
84
|
+
async run(snapshot: ParentSnapshot, type: SubagentType, prompt: string, options: RunOptions): Promise<RunResult> {
|
|
85
|
+
return runAgent(snapshot, type, prompt, options, this.io);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async resume(session: AgentSession, prompt: string, options?: ResumeOptions): Promise<string> {
|
|
89
|
+
return resumeAgent(session, prompt, options);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The factory function `createAgentRunner` is removed.
|
|
95
|
+
The free functions `runAgent`, `resumeAgent`, `getAgentConversation`, and `normalizeMaxTurns` remain exported — they are used directly by tests and other modules.
|
|
96
|
+
|
|
97
|
+
### AgentsMenuHandler
|
|
98
|
+
|
|
99
|
+
The class replaces `createAgentsMenuHandler` and `AgentMenuDeps`.
|
|
100
|
+
Constructor params are the subset of deps that are true collaborators:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
export class AgentsMenuHandler {
|
|
104
|
+
constructor(
|
|
105
|
+
private readonly manager: AgentMenuManager,
|
|
106
|
+
private readonly registry: AgentTypeRegistry,
|
|
107
|
+
private readonly agentActivity: AgentActivityReader,
|
|
108
|
+
private readonly settings: AgentMenuSettings,
|
|
109
|
+
private readonly fileOps: AgentFileOps,
|
|
110
|
+
private readonly personalAgentsDir: string,
|
|
111
|
+
private readonly projectAgentsDir: string,
|
|
112
|
+
) {}
|
|
113
|
+
|
|
114
|
+
async handle(ctx: { ui: MenuUI; modelRegistry: ModelRegistry; parentSnapshot: ParentSnapshot }): Promise<void> { ... }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Key design decisions:
|
|
119
|
+
|
|
120
|
+
1. **`agentActivity` stays as a constructor param** — it is a collaborator used in `viewAgentConversation`.
|
|
121
|
+
The issue's proposed signature omits it, but the class needs it at runtime.
|
|
122
|
+
2. **`getModelLabel` is internalized** — the class imports `resolveModel` and `getModelLabelFromConfig` directly and computes the label in a private method.
|
|
123
|
+
This eliminates the closure from `index.ts` and removes the `getModelLabel` field from the deps interface.
|
|
124
|
+
3. **`AgentMenuDeps` is removed** — the class constructor replaces it.
|
|
125
|
+
4. **The `handle` method** replaces the returned function.
|
|
126
|
+
The inner helpers (`showAgentsMenu`, `showAllAgentsList`, etc.) become private methods.
|
|
127
|
+
|
|
128
|
+
### index.ts simplification
|
|
129
|
+
|
|
130
|
+
After both conversions:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Before (adapter closures):
|
|
134
|
+
const agentsMenuHandler = createAgentsMenuHandler({
|
|
135
|
+
manager: {
|
|
136
|
+
listAgents: () => manager.listAgents(),
|
|
137
|
+
getRecord: (id) => manager.getRecord(id),
|
|
138
|
+
spawnAndWait: (...) => manager.spawnAndWait(...),
|
|
139
|
+
},
|
|
140
|
+
registry,
|
|
141
|
+
agentActivity: runtime.agentActivity,
|
|
142
|
+
getModelLabel: (type, modelRegistry) => { ... }, // 7-line closure
|
|
143
|
+
settings,
|
|
144
|
+
fileOps: new FsAgentFileOps(),
|
|
145
|
+
personalAgentsDir: join(getAgentDir(), 'agents'),
|
|
146
|
+
projectAgentsDir: join(process.cwd(), '.pi', 'agents'),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// After:
|
|
150
|
+
const agentsMenu = new AgentsMenuHandler(
|
|
151
|
+
manager, registry, runtime.agentActivity,
|
|
152
|
+
settings, new FsAgentFileOps(),
|
|
153
|
+
join(getAgentDir(), 'agents'),
|
|
154
|
+
join(process.cwd(), '.pi', 'agents'),
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Eliminated closures: 4 (3 manager method adapters + 1 getModelLabel closure).
|
|
159
|
+
Eliminated imports: `getModelLabelFromConfig`, `resolveModel` (from index.ts), `createAgentRunner`, `type RunnerIO`, `createAgentsMenuHandler`.
|
|
160
|
+
|
|
161
|
+
Remaining adapter closures in `index.ts` (~15) are necessary: event handler registrations, SDK factory callbacks, `pi.sendMessage`/`pi.exec` adapters.
|
|
162
|
+
These are structural — they bridge the Pi SDK's callback-based API to the extension's object-oriented internals.
|
|
163
|
+
|
|
164
|
+
## Module-Level Changes
|
|
165
|
+
|
|
166
|
+
### `src/lifecycle/agent-runner.ts`
|
|
167
|
+
|
|
168
|
+
- Add `ConcreteAgentRunner` class implementing `AgentRunner`.
|
|
169
|
+
- Remove `createAgentRunner` factory function.
|
|
170
|
+
- Keep all free functions (`runAgent`, `resumeAgent`, `getAgentConversation`, `normalizeMaxTurns`) and all types exported.
|
|
171
|
+
|
|
172
|
+
### `src/ui/agent-menu.ts`
|
|
173
|
+
|
|
174
|
+
- Replace `createAgentsMenuHandler` factory with `AgentsMenuHandler` class.
|
|
175
|
+
- Remove `AgentMenuDeps` interface.
|
|
176
|
+
- Add private `getModelLabel` method (internalizes the closure from `index.ts`).
|
|
177
|
+
- Convert inner functions (`showAgentsMenu`, `showAllAgentsList`, `showRunningAgents`, `viewAgentConversation`, `showSettings`) to private methods.
|
|
178
|
+
- Add imports: `resolveModel` from `#src/session/model-resolver`, `getModelLabelFromConfig` from `#src/tools/helpers`.
|
|
179
|
+
- Keep exported interfaces: `AgentMenuManager`, `AgentMenuSettings`, `AgentActivityReader`, `MenuUI`.
|
|
180
|
+
|
|
181
|
+
### `src/index.ts`
|
|
182
|
+
|
|
183
|
+
- Replace `createAgentRunner(runnerIO)` with `new ConcreteAgentRunner(runnerIO)`.
|
|
184
|
+
- Replace `createAgentsMenuHandler({...})` with `new AgentsMenuHandler(...)`.
|
|
185
|
+
- Replace `agentsMenuHandler({...})` with `agentsMenu.handle({...})`.
|
|
186
|
+
- Remove adapter closures for `manager.listAgents`, `manager.getRecord`, `manager.spawnAndWait`, and `getModelLabel`.
|
|
187
|
+
- Remove unused imports: `createAgentRunner`, `type RunnerIO` → `ConcreteAgentRunner`; `createAgentsMenuHandler` → `AgentsMenuHandler`; `getModelLabelFromConfig`, `resolveModel`.
|
|
188
|
+
- Net effect: ~15 lines removed, 5 imports removed.
|
|
189
|
+
|
|
190
|
+
### `docs/architecture/architecture.md`
|
|
191
|
+
|
|
192
|
+
- Mark Layer 3 remaining items (runner, menu) as done.
|
|
193
|
+
- Mark Layer 4 as done.
|
|
194
|
+
- Update the factory→class table entries for `createAgentRunner` and `createAgentsMenuHandler` with ✓.
|
|
195
|
+
|
|
196
|
+
## Test Impact Analysis
|
|
197
|
+
|
|
198
|
+
### `test/lifecycle/agent-runner.test.ts` (and siblings)
|
|
199
|
+
|
|
200
|
+
No changes needed.
|
|
201
|
+
Tests call `runAgent()` and `resumeAgent()` directly — they never use `createAgentRunner`.
|
|
202
|
+
The `ConcreteAgentRunner` class is a trivial two-method delegation wrapper tested implicitly through `index.ts` integration and explicitly through one new unit test.
|
|
203
|
+
|
|
204
|
+
### `test/ui/agent-menu.test.ts`
|
|
205
|
+
|
|
206
|
+
Tests need updating:
|
|
207
|
+
|
|
208
|
+
1. Replace `createAgentsMenuHandler(makeDeps())` with `new AgentsMenuHandler(...)`.
|
|
209
|
+
2. Replace `handler(params)` with `handler.handle(params)`.
|
|
210
|
+
3. Remove `getModelLabel` from `makeDeps()` — it is now an internal method.
|
|
211
|
+
4. Remove `AgentMenuDeps` import; update `makeDeps` to construct positional args or a helper that returns a handler directly.
|
|
212
|
+
|
|
213
|
+
No test logic changes — only call-site updates for the new API shape.
|
|
214
|
+
|
|
215
|
+
### New tests
|
|
216
|
+
|
|
217
|
+
One new unit test for `ConcreteAgentRunner`: verify it delegates `run` and `resume` to the underlying functions.
|
|
218
|
+
|
|
219
|
+
## TDD Order
|
|
220
|
+
|
|
221
|
+
1. **Add `ConcreteAgentRunner` class alongside factory.**
|
|
222
|
+
Add the class to `agent-runner.ts`, keep `createAgentRunner` temporarily.
|
|
223
|
+
Add a unit test verifying delegation.
|
|
224
|
+
`test: add ConcreteAgentRunner delegation test`
|
|
225
|
+
|
|
226
|
+
2. **Switch `index.ts` to `ConcreteAgentRunner`, remove factory.**
|
|
227
|
+
Replace `createAgentRunner(runnerIO)` with `new ConcreteAgentRunner(runnerIO)`.
|
|
228
|
+
Remove the `createAgentRunner` factory function.
|
|
229
|
+
Update imports.
|
|
230
|
+
`refactor: replace createAgentRunner with ConcreteAgentRunner class`
|
|
231
|
+
|
|
232
|
+
3. **Convert `createAgentsMenuHandler` to `AgentsMenuHandler` class.**
|
|
233
|
+
Replace factory function with class.
|
|
234
|
+
Move inner functions to private methods.
|
|
235
|
+
Internalize `getModelLabel` as a private method.
|
|
236
|
+
Remove `AgentMenuDeps` interface.
|
|
237
|
+
`refactor: convert createAgentsMenuHandler to AgentsMenuHandler class`
|
|
238
|
+
|
|
239
|
+
4. **Update `agent-menu.test.ts` for class API.**
|
|
240
|
+
Replace `createAgentsMenuHandler(makeDeps())` with class construction.
|
|
241
|
+
Replace `handler(params)` with `handler.handle(params)`.
|
|
242
|
+
Remove `getModelLabel` from test deps.
|
|
243
|
+
All existing tests pass with updated call sites.
|
|
244
|
+
`test: update agent-menu tests for AgentsMenuHandler class`
|
|
245
|
+
|
|
246
|
+
5. **Simplify `index.ts` wiring.**
|
|
247
|
+
Replace `createAgentsMenuHandler({...})` with `new AgentsMenuHandler(...)`.
|
|
248
|
+
Pass `manager` directly (structural typing).
|
|
249
|
+
Remove adapter closures and unused imports.
|
|
250
|
+
`refactor: simplify index.ts wiring for AgentsMenuHandler`
|
|
251
|
+
|
|
252
|
+
6. **Update architecture doc.**
|
|
253
|
+
Mark Layer 3 remaining items and Layer 4 as done in `docs/architecture/architecture.md`.
|
|
254
|
+
`docs: mark Phase 11 Layer 3 and Layer 4 complete`
|
|
255
|
+
|
|
256
|
+
## Risks and Mitigations
|
|
257
|
+
|
|
258
|
+
| Risk | Mitigation |
|
|
259
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
260
|
+
| `AgentManager` might not structurally satisfy `AgentMenuManager` | Confirmed: `listAgents()`, `getRecord()`, `spawnAndWait()` signatures are compatible. `pnpm run check` in step 5 verifies. |
|
|
261
|
+
| Internalized `getModelLabel` might diverge from the closure | The private method uses the same `resolveModel` and `getModelLabelFromConfig` imports — identical logic, just moved. |
|
|
262
|
+
| Tests that use `AgentMenuDeps` type break when removed | Step 4 updates all test call sites before step 5 changes production code. The test file is 215 lines — manageable in one step. |
|
|
263
|
+
| `agentActivity` missing from constructor | Included in the class constructor (diverging from the issue's proposed signature which omits it). |
|
|
264
|
+
|
|
265
|
+
## Open Questions
|
|
266
|
+
|
|
267
|
+
None — the issue's proposed design is clear and the implementation is mechanical.
|
|
268
|
+
The one deviation (keeping `agentActivity` as a constructor param) is necessary and minimal.
|