@gotgenes/pi-subagents 7.2.0 → 7.2.2
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 +27 -0
- package/docs/architecture/architecture.md +21 -19
- package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +139 -0
- package/docs/plans/0195-convert-tool-factories-to-classes.md +264 -0
- package/docs/retro/0193-runtime-owns-context-queries.md +32 -0
- package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +62 -0
- package/docs/retro/0195-convert-tool-factories-to-classes.md +42 -0
- package/package.json +1 -1
- package/src/index.ts +9 -44
- package/src/runtime.ts +1 -1
- package/src/tools/agent-tool.ts +227 -217
- package/src/tools/background-spawner.ts +2 -2
- package/src/tools/get-result-tool.ts +112 -87
- package/src/tools/steer-tool.ts +99 -77
- package/src/ui/message-formatters.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.2.1...pi-subagents-v7.2.2) (2026-05-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* remove unused AgentSession import and over-annotated mock return type in get-result-tool ([c3bc590](https://github.com/gotgenes/pi-packages/commit/c3bc59015e766d961422f4a7ecd23643c4cabefd))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* mark [#195](https://github.com/gotgenes/pi-packages/issues/195) tool factory conversions complete in architecture roadmap ([cb5562b](https://github.com/gotgenes/pi-packages/commit/cb5562b57ef613ab0c1e136c9d9712c71e831bcd))
|
|
19
|
+
* plan convert tool factories to classes ([#195](https://github.com/gotgenes/pi-packages/issues/195)) ([ec916c2](https://github.com/gotgenes/pi-packages/commit/ec916c22a9d5f3453fba5784e3d2f5ecdc68740d))
|
|
20
|
+
* **retro:** add planning stage notes for issue [#195](https://github.com/gotgenes/pi-packages/issues/195) ([2ca0c96](https://github.com/gotgenes/pi-packages/commit/2ca0c964f8e73b0b0247daf9834986a9344be060))
|
|
21
|
+
* **retro:** add retro notes for issue [#194](https://github.com/gotgenes/pi-packages/issues/194) ([d7d973f](https://github.com/gotgenes/pi-packages/commit/d7d973f88ca48cf1a19b24bd396ef15a606a98bc))
|
|
22
|
+
* **retro:** add TDD stage notes for issue [#195](https://github.com/gotgenes/pi-packages/issues/195) ([921b1f8](https://github.com/gotgenes/pi-packages/commit/921b1f8e500baf9b2fffeb140336d4561bdaebf1))
|
|
23
|
+
|
|
24
|
+
## [7.2.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.2.0...pi-subagents-v7.2.1) (2026-05-25)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
* mark Layer 2 done and update health metrics in architecture doc ([ad00426](https://github.com/gotgenes/pi-packages/commit/ad00426f0f29ceff0915033419fdc2f1b53755b0))
|
|
30
|
+
* plan align tool interfaces for structural typing ([#194](https://github.com/gotgenes/pi-packages/issues/194)) ([36e56b0](https://github.com/gotgenes/pi-packages/commit/36e56b08351893e3c2d63569e7cfa140c172e20b))
|
|
31
|
+
* **retro:** add planning stage notes for issue [#194](https://github.com/gotgenes/pi-packages/issues/194) ([63a5763](https://github.com/gotgenes/pi-packages/commit/63a5763ed2bf4c2a6e2e9a19fdcdce71a2a9905a))
|
|
32
|
+
* **retro:** add retro notes for issue [#193](https://github.com/gotgenes/pi-packages/issues/193) ([8987f90](https://github.com/gotgenes/pi-packages/commit/8987f907e00bb70429782c947a2afbdb1db5faa9))
|
|
33
|
+
* **retro:** add TDD stage notes for issue [#194](https://github.com/gotgenes/pi-packages/issues/194) ([f692323](https://github.com/gotgenes/pi-packages/commit/f69232395882c07d6d95273e08021b94800f0e43))
|
|
34
|
+
|
|
8
35
|
## [7.2.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.1.0...pi-subagents-v7.2.0) (2026-05-25)
|
|
9
36
|
|
|
10
37
|
|
|
@@ -457,7 +457,7 @@ Bags with 10+ fields are the highest priority for decomposition.
|
|
|
457
457
|
| `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
|
|
458
458
|
| `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
|
|
459
459
|
| `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
|
|
460
|
-
| `AgentToolDeps` | 8 | agent-tool |
|
|
460
|
+
| `AgentToolDeps` | 8 | agent-tool | ✓ done |
|
|
461
461
|
| `AgentMenuDeps` | 8 | agent-menu | Low |
|
|
462
462
|
| `ConversationViewerOptions` | 8 | conversation-viewer | Low |
|
|
463
463
|
| `AgentRecordInit` | 8 | agent-record | Low |
|
|
@@ -608,16 +608,16 @@ The approach is layered: each step makes the next step trivial.
|
|
|
608
608
|
|
|
609
609
|
### Findings
|
|
610
610
|
|
|
611
|
-
| Metric | Value
|
|
612
|
-
| ------------------------- |
|
|
613
|
-
| Health score | 75/100 (B)
|
|
614
|
-
| #1 hotspot | `index.ts` (128 commits, accelerating)
|
|
615
|
-
| Dead exports |
|
|
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 |
|
|
620
|
-
| Index fan-out | 25 imports
|
|
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
621
|
|
|
622
622
|
### Root cause
|
|
623
623
|
|
|
@@ -628,8 +628,10 @@ The real objects can't satisfy the interfaces because:
|
|
|
628
628
|
Resolved by Layer 0 (#192) + Layer 1 (#193).
|
|
629
629
|
2. ~~Context queries (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) live as closures in index.ts instead of methods on the state holder.~~
|
|
630
630
|
Resolved by Layer 1 (#193).
|
|
631
|
-
3.
|
|
632
|
-
|
|
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).
|
|
633
635
|
|
|
634
636
|
Fix these structural misalignments and the class conversions become mechanical.
|
|
635
637
|
|
|
@@ -668,7 +670,7 @@ Add typed methods: `buildSnapshot(inheritContext)`, `getModelInfo()`, `getSessio
|
|
|
668
670
|
- Outcome: 3 closure queries in index.ts → 0; `SubagentRuntime` is self-sufficient for tool deps
|
|
669
671
|
- Enables: Layer 3 (tools accept `SubagentRuntime` directly)
|
|
670
672
|
|
|
671
|
-
### Layer 2: Align interfaces so real objects satisfy tool deps structurally ([#194][194])
|
|
673
|
+
### Layer 2: Align interfaces so real objects satisfy tool deps structurally ([#194][194]) ✓ done
|
|
672
674
|
|
|
673
675
|
Three alignment changes:
|
|
674
676
|
|
|
@@ -681,18 +683,18 @@ After this step, `AgentManager` structurally satisfies `AgentToolManager` and `S
|
|
|
681
683
|
|
|
682
684
|
- Target: `src/tools/agent-tool.ts` (interface), `src/runtime.ts` (method names), `src/ui/message-formatters.ts`
|
|
683
685
|
- Smell: Category C (source mismatch, name mismatch) + Category A (dead export)
|
|
684
|
-
- Outcome: structural typing connects real objects to tool interfaces without adapters
|
|
686
|
+
- Outcome: structural typing connects real objects to tool interfaces without adapters; 0 dead exports (fallow clean)
|
|
685
687
|
- Enables: Layer 3 (class constructors accept real objects directly)
|
|
686
688
|
|
|
687
689
|
### Layer 3: Convert closure factories to classes ([#195][195], [#196][196])
|
|
688
690
|
|
|
689
|
-
With Layers 0–2 complete, each factory is a mechanical conversion:
|
|
691
|
+
With Layers 0–2 complete, each factory is a mechanical conversion. ✓ Tool factories converted in [#195][195]:
|
|
690
692
|
|
|
691
693
|
| Factory | Class | Constructor params |
|
|
692
694
|
| -------------------------------- | ------------------------------ | --------------------------------------------------- |
|
|
693
|
-
| `createAgentTool({...})` | `AgentTool`
|
|
694
|
-
| `createGetResultTool(...)` | `GetResultTool`
|
|
695
|
-
| `createSteerTool(...)` | `SteerTool`
|
|
695
|
+
| `createAgentTool({...})` | `AgentTool` ✓ | `manager`, `runtime`, `settings`, `registry` |
|
|
696
|
+
| `createGetResultTool(...)` | `GetResultTool` ✓ | `manager`, `notifications`, `registry` |
|
|
697
|
+
| `createSteerTool(...)` | `SteerTool` ✓ | `manager`, `events` |
|
|
696
698
|
| `createAgentRunner(runnerIO)` | `AgentRunner` (concrete class) | `io: RunnerIO` |
|
|
697
699
|
| `createAgentsMenuHandler({...})` | `AgentsMenuHandler` | `manager`, `registry`, `settings`, `fileOps`, paths |
|
|
698
700
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 194
|
|
3
|
+
issue_title: "Align tool interfaces for structural typing"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Align tool interfaces for structural typing
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
The narrow interfaces that tool factories accept don't structurally match the real objects (`AgentManager`, `SubagentRuntime`, `SettingsManager`).
|
|
11
|
+
This forces `index.ts` to build adapter closures bridging the gap — each a one-liner that exists only because names or ownership don't align.
|
|
12
|
+
Three specific mismatches prevent structural typing from connecting real objects to tool interfaces directly.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Remove `getMaxConcurrent()` from `AgentToolManager` — it belongs on the settings accessor.
|
|
17
|
+
- Rename `SubagentRuntime.updateWidget()` → `update()` so `SubagentRuntime` structurally satisfies `AgentToolWidget`.
|
|
18
|
+
- Remove the dead `getToolCallName` re-export from `ui/message-formatters.ts`.
|
|
19
|
+
- After these changes, `AgentManager` structurally satisfies `AgentToolManager` and `SubagentRuntime` structurally satisfies `AgentToolWidget` — no adapter closures needed in `index.ts`.
|
|
20
|
+
|
|
21
|
+
## Non-Goals
|
|
22
|
+
|
|
23
|
+
- Converting tool factories to classes (that's #195).
|
|
24
|
+
- Simplifying `index.ts` wiring (that's #195/#196, after this layer).
|
|
25
|
+
- Changing `NotificationManager`'s constructor parameter name (`updateWidget` callback) — it's a positional callback, not a structural interface member.
|
|
26
|
+
|
|
27
|
+
## Background
|
|
28
|
+
|
|
29
|
+
This is Phase 11, Layer 2 in `docs/architecture/architecture.md`.
|
|
30
|
+
Layer 0 (#192, done) and Layer 1 (#193, done) established the typed `SessionContext` and moved context queries onto `SubagentRuntime`.
|
|
31
|
+
Layer 2 (this issue) aligns the remaining structural mismatches.
|
|
32
|
+
Layer 3 (#195) depends on this layer.
|
|
33
|
+
|
|
34
|
+
Relevant modules:
|
|
35
|
+
|
|
36
|
+
- `src/tools/agent-tool.ts` — defines `AgentToolManager` and `AgentToolWidget` interfaces.
|
|
37
|
+
- `src/tools/background-spawner.ts` — defines `BackgroundManagerDeps` with `getMaxConcurrent()`.
|
|
38
|
+
- `src/runtime.ts` — defines `SubagentRuntime` class with `updateWidget()` delegation method.
|
|
39
|
+
- `src/ui/message-formatters.ts` — has the dead `getToolCallName` re-export.
|
|
40
|
+
- `src/index.ts` — composition root that builds adapter closures.
|
|
41
|
+
- `src/settings.ts` — `SettingsManager` owns `maxConcurrent`.
|
|
42
|
+
|
|
43
|
+
## Design Overview
|
|
44
|
+
|
|
45
|
+
### 1. Move `getMaxConcurrent` off manager interfaces → settings
|
|
46
|
+
|
|
47
|
+
The `BackgroundManagerDeps` and `AgentToolManager` interfaces both declare `getMaxConcurrent(): number`.
|
|
48
|
+
In reality, the value comes from `SettingsManager.maxConcurrent`.
|
|
49
|
+
The fix:
|
|
50
|
+
|
|
51
|
+
- Remove `getMaxConcurrent` from `AgentToolManager`.
|
|
52
|
+
- Remove `getMaxConcurrent` from `BackgroundManagerDeps`.
|
|
53
|
+
- Widen `AgentToolDeps.settings` from `{ readonly defaultMaxTurns: number | undefined }` to also include `readonly maxConcurrent: number`.
|
|
54
|
+
- Pass `settings` (or a narrow settings interface) to `spawnBackground` so it can read `maxConcurrent` directly.
|
|
55
|
+
- `SettingsManager` already exposes a `get maxConcurrent(): number` property, so it structurally satisfies the widened interface.
|
|
56
|
+
|
|
57
|
+
After this, `AgentManager` (which has `spawn`, `spawnAndWait`, `resume`, `getRecord` but NOT `getMaxConcurrent`) structurally satisfies `AgentToolManager`.
|
|
58
|
+
|
|
59
|
+
### 2. Rename `SubagentRuntime.updateWidget()` → `update()`
|
|
60
|
+
|
|
61
|
+
The `AgentToolWidget` interface declares `update(): void`.
|
|
62
|
+
`SubagentRuntime` has `updateWidget(): void` which delegates to `this.widget?.update()`.
|
|
63
|
+
Renaming the delegation method to `update()` makes `SubagentRuntime` structurally satisfy `AgentToolWidget` (it already has `setUICtx`, `ensureTimer`, and `markFinished`).
|
|
64
|
+
|
|
65
|
+
Callers of `runtime.updateWidget()`:
|
|
66
|
+
|
|
67
|
+
- `src/index.ts` line 70: `() => runtime.updateWidget()` → `() => runtime.update()`
|
|
68
|
+
- `src/index.ts` line 199: `update: () => runtime.updateWidget()` → can now pass `runtime` directly (but that's a #195 concern — for now just rename the call).
|
|
69
|
+
|
|
70
|
+
The `WidgetLike` interface in `runtime.ts` already uses `update()` — no conflict.
|
|
71
|
+
|
|
72
|
+
### 3. Remove dead re-export
|
|
73
|
+
|
|
74
|
+
`src/ui/message-formatters.ts` line 24 exports `getToolCallName` from `#src/session/content-items`.
|
|
75
|
+
No consumer imports `getToolCallName` from `message-formatters` — all uses go directly to `content-items.ts`.
|
|
76
|
+
Delete the re-export line.
|
|
77
|
+
|
|
78
|
+
### After all three changes
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// AgentToolManager (after removing getMaxConcurrent):
|
|
82
|
+
interface AgentToolManager {
|
|
83
|
+
spawn(...): string;
|
|
84
|
+
spawnAndWait(...): Promise<AgentRecord>;
|
|
85
|
+
resume(...): Promise<AgentRecord | undefined>;
|
|
86
|
+
getRecord(id: string): AgentRecord | undefined;
|
|
87
|
+
}
|
|
88
|
+
// AgentManager has all four methods → structural match ✓
|
|
89
|
+
|
|
90
|
+
// AgentToolWidget (unchanged):
|
|
91
|
+
interface AgentToolWidget {
|
|
92
|
+
setUICtx(ctx: unknown): void;
|
|
93
|
+
ensureTimer(): void;
|
|
94
|
+
update(): void;
|
|
95
|
+
markFinished(id: string): void;
|
|
96
|
+
}
|
|
97
|
+
// SubagentRuntime has all four methods (after rename) → structural match ✓
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Module-Level Changes
|
|
101
|
+
|
|
102
|
+
| File | Change |
|
|
103
|
+
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `src/tools/agent-tool.ts` | Remove `getMaxConcurrent` from `AgentToolManager`. Widen `settings` type in `AgentToolDeps` to include `readonly maxConcurrent: number`. |
|
|
105
|
+
| `src/tools/background-spawner.ts` | Remove `getMaxConcurrent` from `BackgroundManagerDeps`. Add a `settings: { readonly maxConcurrent: number }` parameter (or add to `BackgroundParams`). Read `settings.maxConcurrent` instead of `manager.getMaxConcurrent()`. |
|
|
106
|
+
| `src/runtime.ts` | Rename `updateWidget()` → `update()`. |
|
|
107
|
+
| `src/ui/message-formatters.ts` | Remove the `export { getToolCallName } from ...` line. |
|
|
108
|
+
| `src/index.ts` | Update `runtime.updateWidget()` → `runtime.update()` at both call sites. Remove `getMaxConcurrent` from the `manager` adapter object passed to `createAgentTool`. Pass `settings` through to `spawnBackground` via the tool deps. |
|
|
109
|
+
| `test/tools/background-spawner.test.ts` | Remove `getMaxConcurrent` from mock manager objects. Add `settings` mock with `maxConcurrent`. |
|
|
110
|
+
| `test/runtime.test.ts` | Rename `updateWidget` → `update` in test descriptions and call sites. |
|
|
111
|
+
| `docs/architecture/architecture.md` | Update Layer 2 status and health metrics (adapter closures count, dead exports count). |
|
|
112
|
+
|
|
113
|
+
## Test Impact Analysis
|
|
114
|
+
|
|
115
|
+
1. No new unit tests are strictly needed — this is interface alignment, not new behavior.
|
|
116
|
+
2. `test/tools/background-spawner.test.ts` needs mock shape updates (remove `getMaxConcurrent` from manager mock, add settings mock).
|
|
117
|
+
3. `test/runtime.test.ts` needs the method name updated from `updateWidget` to `update`.
|
|
118
|
+
4. Existing tests for `agent-tool`, `notification`, and `message-formatters` remain as-is (no behavior change).
|
|
119
|
+
|
|
120
|
+
## TDD Order
|
|
121
|
+
|
|
122
|
+
1. `refactor:` Rename `SubagentRuntime.updateWidget()` → `update()` — update `runtime.ts`, `test/runtime.test.ts`, and both call sites in `index.ts`.
|
|
123
|
+
Run `pnpm run check` to verify no type errors remain.
|
|
124
|
+
2. `refactor:` Move `getMaxConcurrent` off manager interfaces — remove from `AgentToolManager` and `BackgroundManagerDeps`, widen `AgentToolDeps.settings`, add settings parameter to `spawnBackground`, update `index.ts` call site and `test/tools/background-spawner.test.ts`.
|
|
125
|
+
Run `pnpm run check`.
|
|
126
|
+
3. `refactor:` Remove dead `getToolCallName` re-export from `ui/message-formatters.ts`.
|
|
127
|
+
4. `docs:` Update architecture doc — mark Layer 2 as done, update health metrics (dead exports: 0, adapter closures reduced).
|
|
128
|
+
|
|
129
|
+
## Risks and Mitigations
|
|
130
|
+
|
|
131
|
+
| Risk | Mitigation |
|
|
132
|
+
| ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
133
|
+
| `update()` is a generic name on `SubagentRuntime` — could confuse readers about what's being updated | The method is a documented widget delegation like its siblings (`markFinished`, `ensureTimer`); the JSDoc comment clarifies it delegates to `widget.update()`. |
|
|
134
|
+
| `background-spawner` signature change could break other callers | Grep confirms only `agent-tool.ts` calls `spawnBackground` — no other consumers. |
|
|
135
|
+
| Renaming method in runtime could miss a call site | Grep and `pnpm run check` after each step catch all references. |
|
|
136
|
+
|
|
137
|
+
## Open Questions
|
|
138
|
+
|
|
139
|
+
None — the issue's proposed direction is unambiguous and the architecture doc confirms the design.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 195
|
|
3
|
+
issue_title: "Convert tool factories to classes"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Convert tool factories to classes
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`createAgentTool`, `createGetResultTool`, and `createSteerTool` are closure factories that capture dependencies and return tool definitions.
|
|
11
|
+
This pattern hides dependencies in closures, forces `index.ts` to build narrow adapter objects at each call site, and prevents direct structural typing between real objects and tool interfaces.
|
|
12
|
+
With #193 (runtime owns queries) and #194 (interfaces aligned) both complete, the conversions are mechanical.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Convert all three tool factories to classes with constructor-injected dependencies.
|
|
17
|
+
- Each class exposes a `toToolDefinition()` method that wraps the tool with `defineTool()`.
|
|
18
|
+
- `index.ts` passes real objects directly — no adapter closures for tool wiring.
|
|
19
|
+
- Eliminate 16+ adapter closures from `index.ts`.
|
|
20
|
+
|
|
21
|
+
## Non-Goals
|
|
22
|
+
|
|
23
|
+
- Converting `createAgentRunner` or `createAgentsMenuHandler` — those belong to #196.
|
|
24
|
+
- Simplifying `index.ts` beyond tool wiring — that's #196 Layer 4.
|
|
25
|
+
- Changing tool behavior or parameters — this is a purely structural refactoring.
|
|
26
|
+
|
|
27
|
+
## Background
|
|
28
|
+
|
|
29
|
+
### Dependencies (both closed)
|
|
30
|
+
|
|
31
|
+
- Issue #193 moved context queries (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) onto `SubagentRuntime`.
|
|
32
|
+
- Issue #194 aligned tool interfaces so `AgentManager` and `SubagentRuntime` structurally satisfy the narrow tool interfaces without adapters.
|
|
33
|
+
|
|
34
|
+
### Existing module layout
|
|
35
|
+
|
|
36
|
+
The three factory functions live in:
|
|
37
|
+
|
|
38
|
+
- `src/tools/agent-tool.ts` — `createAgentTool(deps: AgentToolDeps)`
|
|
39
|
+
- `src/tools/get-result-tool.ts` — `createGetResultTool(getRecord, cancelNudge, getConversation, registry)`
|
|
40
|
+
- `src/tools/steer-tool.ts` — `createSteerTool(getRecord, emitEvent, steerAgent, queueSteer)`
|
|
41
|
+
|
|
42
|
+
Each returns a plain object with `name`, `label`, `description`, `parameters`, `execute`, and optional render methods.
|
|
43
|
+
`index.ts` wraps each in `defineTool()` at registration time.
|
|
44
|
+
|
|
45
|
+
### Architecture doc reference
|
|
46
|
+
|
|
47
|
+
Phase 11, Layer 3 in `docs/architecture/architecture.md` (lines 689–709).
|
|
48
|
+
|
|
49
|
+
## Design Overview
|
|
50
|
+
|
|
51
|
+
### Class structure
|
|
52
|
+
|
|
53
|
+
Each class stores constructor-injected dependencies as instance fields and exposes:
|
|
54
|
+
|
|
55
|
+
1. A `toToolDefinition()` method that calls `defineTool(this.buildToolSpec())` (or similar) — returning the Pi SDK tool registration object.
|
|
56
|
+
2. Private methods that correspond to the existing function body sections.
|
|
57
|
+
|
|
58
|
+
### Constructor signatures
|
|
59
|
+
|
|
60
|
+
| Class | Constructor params |
|
|
61
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
62
|
+
| `AgentTool` | `manager: AgentToolManager`, `runtime: AgentToolRuntime`, `settings: AgentToolSettings`, `registry: AgentTypeRegistry`, `agentDir: string` |
|
|
63
|
+
| `GetResultTool` | `manager: GetResultToolManager`, `notifications: GetResultToolNotifications`, `registry: AgentConfigLookup` |
|
|
64
|
+
| `SteerTool` | `manager: SteerToolManager`, `events: SteerToolEvents` |
|
|
65
|
+
|
|
66
|
+
### Narrow interfaces
|
|
67
|
+
|
|
68
|
+
Each class defines its own narrow interface for the collaborators it uses.
|
|
69
|
+
These already exist (e.g., `AgentToolManager`, `BackgroundManagerDeps`) — they stay as-is; the class uses them directly.
|
|
70
|
+
|
|
71
|
+
#### `AgentTool`
|
|
72
|
+
|
|
73
|
+
The existing `AgentToolDeps` interface (8 fields) collapses into 5 constructor params.
|
|
74
|
+
The `widget`, `agentActivity`, `buildSnapshot`, `getModelInfo`, and `getSessionInfo` fields merge into a single `runtime` param because `SubagentRuntime` structurally satisfies all of them after #193/#194.
|
|
75
|
+
|
|
76
|
+
New narrow interface for the runtime slice:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
export interface AgentToolRuntime {
|
|
80
|
+
readonly agentActivity: AgentActivityAccess;
|
|
81
|
+
setUICtx(ctx: UICtx): void;
|
|
82
|
+
ensureTimer(): void;
|
|
83
|
+
update(): void;
|
|
84
|
+
markFinished(id: string): void;
|
|
85
|
+
buildSnapshot(inheritContext: boolean): ParentSnapshot;
|
|
86
|
+
getModelInfo(): ModelInfo;
|
|
87
|
+
getSessionInfo(): { parentSessionFile: string; parentSessionId: string };
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`SubagentRuntime` satisfies this structurally — no adapter needed in `index.ts`.
|
|
92
|
+
|
|
93
|
+
#### `GetResultTool`
|
|
94
|
+
|
|
95
|
+
New narrow interfaces:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
export interface GetResultToolManager {
|
|
99
|
+
getRecord(id: string): AgentRecord | undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface GetResultToolNotifications {
|
|
103
|
+
cancelNudge(key: string): void;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`getAgentConversation` is a pure function — imported directly by the class, not injected.
|
|
108
|
+
|
|
109
|
+
#### `SteerTool`
|
|
110
|
+
|
|
111
|
+
New narrow interfaces:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
export interface SteerToolManager {
|
|
115
|
+
getRecord(id: string): AgentRecord | undefined;
|
|
116
|
+
queueSteer(id: string, message: string): boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface SteerToolEvents {
|
|
120
|
+
emit(name: string, data: unknown): void;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`steerAgent` is a pure function — imported directly by the class, not injected.
|
|
125
|
+
|
|
126
|
+
### Call site after conversion
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const agentTool = new AgentTool(manager, runtime, settings, registry, getAgentDir());
|
|
130
|
+
const getResultTool = new GetResultTool(manager, notifications, registry);
|
|
131
|
+
const steerTool = new SteerTool(manager, pi.events);
|
|
132
|
+
pi.registerTool(agentTool.toToolDefinition());
|
|
133
|
+
pi.registerTool(getResultTool.toToolDefinition());
|
|
134
|
+
pi.registerTool(steerTool.toToolDefinition());
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Design decisions
|
|
138
|
+
|
|
139
|
+
1. **`toToolDefinition()` on each class** — encapsulates the `defineTool()` call so `index.ts` doesn't import `defineTool` for each tool.
|
|
140
|
+
2. **Pure functions imported directly** — `steerAgent` and `getAgentConversation` are pure utilities with no state; injecting them adds indirection without testability benefit.
|
|
141
|
+
3. **`agentDir` stays a constructor param** — it's a static string computed once at startup; putting it on the registry would add scope responsibility the registry doesn't need.
|
|
142
|
+
4. **Keep existing `AgentToolDeps` temporarily** — remove it in the same PR once the class replaces all usage.
|
|
143
|
+
|
|
144
|
+
## Module-Level Changes
|
|
145
|
+
|
|
146
|
+
### `src/tools/agent-tool.ts`
|
|
147
|
+
|
|
148
|
+
- Add `AgentToolRuntime` interface (narrow runtime slice).
|
|
149
|
+
- Add `AgentToolSettings` type alias (existing inline type, extracted for clarity).
|
|
150
|
+
- Add `AgentTool` class with constructor storing `manager`, `runtime`, `settings`, `registry`, `agentDir`.
|
|
151
|
+
- Move the existing factory body into class methods: `toToolDefinition()`, private `execute()`.
|
|
152
|
+
- Remove `createAgentTool` function and `AgentToolDeps` interface.
|
|
153
|
+
- Keep `AgentToolManager`, `AgentToolWidget`, `AgentActivityAccess` interfaces (still used by `background-spawner` and `foreground-runner` — though `AgentToolWidget` may be replaceable by `AgentToolRuntime`).
|
|
154
|
+
|
|
155
|
+
### `src/tools/get-result-tool.ts`
|
|
156
|
+
|
|
157
|
+
- Add `GetResultToolManager` and `GetResultToolNotifications` interfaces.
|
|
158
|
+
- Add `GetResultTool` class with constructor.
|
|
159
|
+
- Import `getAgentConversation` directly.
|
|
160
|
+
- Move factory body into class method.
|
|
161
|
+
- Remove `createGetResultTool` function.
|
|
162
|
+
|
|
163
|
+
### `src/tools/steer-tool.ts`
|
|
164
|
+
|
|
165
|
+
- Add `SteerToolManager` and `SteerToolEvents` interfaces.
|
|
166
|
+
- Add `SteerTool` class with constructor.
|
|
167
|
+
- Import `steerAgent` directly.
|
|
168
|
+
- Move factory body into class method.
|
|
169
|
+
- Remove `createSteerTool` function.
|
|
170
|
+
|
|
171
|
+
### `src/tools/background-spawner.ts`
|
|
172
|
+
|
|
173
|
+
- Update `BackgroundWidgetDeps` to reference `AgentToolRuntime` instead (or keep as-is since `AgentToolRuntime` is a superset).
|
|
174
|
+
Actually no change needed — `spawnBackground` already accepts narrow interfaces; the `AgentTool` class passes `this.runtime` which satisfies `BackgroundWidgetDeps` structurally.
|
|
175
|
+
|
|
176
|
+
### `src/tools/foreground-runner.ts`
|
|
177
|
+
|
|
178
|
+
- Same as above — no change needed.
|
|
179
|
+
The `AgentTool` class passes `this.runtime` which satisfies `ForegroundWidgetDeps` structurally.
|
|
180
|
+
|
|
181
|
+
### `src/index.ts`
|
|
182
|
+
|
|
183
|
+
- Remove adapter closures for all three tool registrations.
|
|
184
|
+
- Replace with class construction + `toToolDefinition()` calls.
|
|
185
|
+
- Remove `defineTool` import (moves into tool classes).
|
|
186
|
+
- Remove unused imports that were only needed for adapter closures.
|
|
187
|
+
|
|
188
|
+
### `test/tools/agent-tool.test.ts`
|
|
189
|
+
|
|
190
|
+
- Replace `createToolDeps()` with class construction in test helpers.
|
|
191
|
+
- Update `execute()` helper to instantiate `AgentTool` and call its execute.
|
|
192
|
+
- Keep all existing test cases — behavior is unchanged.
|
|
193
|
+
|
|
194
|
+
### `test/tools/get-result-tool.test.ts`
|
|
195
|
+
|
|
196
|
+
- Replace positional args with class construction.
|
|
197
|
+
- Update `makeDeps` and `execute` helpers.
|
|
198
|
+
- Keep all existing test cases.
|
|
199
|
+
|
|
200
|
+
### `test/tools/steer-tool.test.ts`
|
|
201
|
+
|
|
202
|
+
- Replace positional args with class construction.
|
|
203
|
+
- Update `makeDeps` and `execute` helpers.
|
|
204
|
+
- Keep all existing test cases.
|
|
205
|
+
|
|
206
|
+
### `test/helpers/make-deps.ts`
|
|
207
|
+
|
|
208
|
+
- Update `createToolDeps` to construct `AgentTool`-compatible deps (or remove if no longer needed).
|
|
209
|
+
Likely transforms into a factory that builds mock `AgentToolRuntime`, `AgentToolManager`, etc.
|
|
210
|
+
|
|
211
|
+
### `docs/architecture/architecture.md`
|
|
212
|
+
|
|
213
|
+
- Update the Layer 3 table to mark #195 as done.
|
|
214
|
+
- Update the file listing for `src/tools/` to reflect class names (if the listing is that detailed).
|
|
215
|
+
|
|
216
|
+
## Test Impact Analysis
|
|
217
|
+
|
|
218
|
+
1. **No new test surfaces** — this is a structural refactoring with identical behavior.
|
|
219
|
+
Existing tests already cover all tool paths.
|
|
220
|
+
2. **Test helper changes** — `createToolDeps()` and per-tool `makeDeps()` functions need updating to use class construction instead of function calls, but the mock shapes remain the same.
|
|
221
|
+
3. **All existing tests stay** — they exercise the tool execution logic which doesn't change.
|
|
222
|
+
|
|
223
|
+
## TDD Order
|
|
224
|
+
|
|
225
|
+
1. **Convert `SteerTool` to class** — smallest tool (fewest deps, no render methods).
|
|
226
|
+
- Update `steer-tool.ts`: add interfaces, add class, remove factory.
|
|
227
|
+
- Update `test/tools/steer-tool.test.ts`: use class construction.
|
|
228
|
+
- Verify: `pnpm vitest run test/tools/steer-tool.test.ts`
|
|
229
|
+
- Commit: `refactor: convert createSteerTool to SteerTool class (#195)`
|
|
230
|
+
|
|
231
|
+
2. **Convert `GetResultTool` to class** — medium complexity (3 deps, no render methods).
|
|
232
|
+
- Update `get-result-tool.ts`: add interfaces, add class, import `getAgentConversation`, remove factory.
|
|
233
|
+
- Update `test/tools/get-result-tool.test.ts`: use class construction.
|
|
234
|
+
- Verify: `pnpm vitest run test/tools/get-result-tool.test.ts`
|
|
235
|
+
- Commit: `refactor: convert createGetResultTool to GetResultTool class (#195)`
|
|
236
|
+
|
|
237
|
+
3. **Convert `AgentTool` to class** — largest (5 deps, render methods, delegates to spawner/runner).
|
|
238
|
+
- Add `AgentToolRuntime` and `AgentToolSettings` interfaces.
|
|
239
|
+
- Add `AgentTool` class.
|
|
240
|
+
- Remove `createAgentTool` and `AgentToolDeps`.
|
|
241
|
+
- Update `test/tools/agent-tool.test.ts` and `test/helpers/make-deps.ts`.
|
|
242
|
+
- Verify: `pnpm vitest run test/tools/agent-tool.test.ts`
|
|
243
|
+
- Commit: `refactor: convert createAgentTool to AgentTool class (#195)`
|
|
244
|
+
|
|
245
|
+
4. **Update `index.ts`** — wire class constructors, remove adapter closures and `defineTool` import.
|
|
246
|
+
- Verify: `pnpm vitest run` (full suite) + `pnpm run check` (type-check).
|
|
247
|
+
- Commit: `refactor: wire tool classes in index.ts, remove adapter closures (#195)`
|
|
248
|
+
|
|
249
|
+
5. **Update architecture doc** — mark Layer 3 (partial) as complete.
|
|
250
|
+
- Commit: `docs: mark #195 complete in architecture roadmap`
|
|
251
|
+
|
|
252
|
+
## Risks and Mitigations
|
|
253
|
+
|
|
254
|
+
| Risk | Mitigation |
|
|
255
|
+
| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
256
|
+
| `background-spawner` and `foreground-runner` accept narrow interfaces that differ from `AgentToolRuntime` | `AgentToolRuntime` is a superset — pass `this.runtime` and structural typing handles it. |
|
|
257
|
+
| Test factories import `AgentToolDeps` which will be removed | Step 3 updates all test imports in the same commit. |
|
|
258
|
+
| `defineTool` import removed from `index.ts` — must live somewhere | Each class's `toToolDefinition()` imports it internally. |
|
|
259
|
+
| Spreading class instances in tests breaks method access | Tests will construct the class, not spread it. Step 3 uses lift-and-shift for `make-deps.ts`. |
|
|
260
|
+
|
|
261
|
+
## Open Questions
|
|
262
|
+
|
|
263
|
+
- Should `AgentToolWidget` interface be removed entirely (replaced by `AgentToolRuntime`)?
|
|
264
|
+
Defer to implementation — if `background-spawner` and `foreground-runner` can accept `AgentToolRuntime` directly, remove it; otherwise keep the narrow subset interfaces.
|
|
@@ -41,3 +41,35 @@ This is cleaner than the plan's `getModelInfo(): { modelRegistry: unknown }` app
|
|
|
41
41
|
- Biome's `noUnusedPrivateClassMembers` warning caught the leftover `private readonly pi: unknown` in `SessionLifecycleHandler`.
|
|
42
42
|
Removed `pi` from the constructor entirely (rather than adding `_` prefix), which also cleaned up `index.ts`.
|
|
43
43
|
- The `eslint-disable` directive at the top of `index.ts` had two now-unused entries (`no-unsafe-member-access`, `no-unsafe-call`) removed by `eslint --fix`.
|
|
44
|
+
|
|
45
|
+
## Stage: Final Retrospective (2026-05-24T20:45:00Z)
|
|
46
|
+
|
|
47
|
+
### Session summary
|
|
48
|
+
|
|
49
|
+
All three stages (plan, TDD, ship) completed in a single session.
|
|
50
|
+
Released as `pi-subagents-v7.2.0`.
|
|
51
|
+
One plan contradiction required a judgment call during implementation; otherwise clean mechanical execution.
|
|
52
|
+
|
|
53
|
+
### Observations
|
|
54
|
+
|
|
55
|
+
#### What went well
|
|
56
|
+
|
|
57
|
+
- The architecture doc's Phase 11 Layer 1 spec was precise enough that no `ask_user` was needed at any stage — the issue body, architecture doc, and #192 retro were fully aligned.
|
|
58
|
+
- `ServiceRuntimeLike` ended up simpler than planned (only `currentCtx` + `buildSnapshot` instead of also requiring `getModelInfo()`) — the implementation found a cleaner design than the plan specified.
|
|
59
|
+
- Test count increase (+6) validates the design: methods that were previously untestable as anonymous closures now have dedicated unit tests.
|
|
60
|
+
|
|
61
|
+
#### What caused friction (agent side)
|
|
62
|
+
|
|
63
|
+
- `missing-context` — The plan's Non-Goals section claimed `buildParentContext` would not change, contradicting Module-Level Changes item #4 which explicitly listed the file.
|
|
64
|
+
The planning stage didn't cross-check these sections before committing.
|
|
65
|
+
Impact: brief confusion during step 3 about which section to trust (resolved by following Module-Level Changes); no rework, added ~30s of deliberation.
|
|
66
|
+
- `missing-context` — TypeScript's discriminated union narrowing limitation with a `{ type: string }` catch-all arm was not anticipated.
|
|
67
|
+
Impact: required adding a local `BranchEntry` union type and explicit casts in `context.ts`; no rework but ~2 min of debugging the type error.
|
|
68
|
+
|
|
69
|
+
#### What caused friction (user side)
|
|
70
|
+
|
|
71
|
+
- None — no user intervention was needed at any point across all three stages.
|
|
72
|
+
|
|
73
|
+
### Changes made
|
|
74
|
+
|
|
75
|
+
1. `.pi/prompts/plan-issue.md` — added a Non-Goals vs Module-Level Changes cross-check instruction under the Module-Level Changes bullet.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 194
|
|
3
|
+
issue_title: "Align tool interfaces for structural typing"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #194 — Align tool interfaces for structural typing
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-24T12:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced an implementation plan for three targeted alignment changes: moving `getMaxConcurrent` off manager interfaces to the settings accessor, renaming `SubagentRuntime.updateWidget()` → `update()`, and removing the dead `getToolCallName` re-export.
|
|
13
|
+
The plan includes a 4-step TDD order with type-check gates after each refactoring step.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- Issue #193 (Layer 1) is already closed/implemented, confirming this layer can proceed immediately.
|
|
18
|
+
|
|
19
|
+
## Stage: Implementation — TDD (2026-05-24T21:00:00Z)
|
|
20
|
+
|
|
21
|
+
### Session summary
|
|
22
|
+
|
|
23
|
+
Completed all 4 TDD steps: renamed `SubagentRuntime.updateWidget()` → `update()`, moved `getMaxConcurrent` from manager interfaces to the `settings` narrow type, removed the dead `getToolCallName` re-export, and updated the architecture doc.
|
|
24
|
+
Test count stayed flat at 854 (53 files) — all green.
|
|
25
|
+
The `pnpm run check` type-gate caught a previously-unnoticed `test/helpers/make-deps.test.ts` that also validated `getMaxConcurrent` and the old `settings` shape; this file was updated as part of step 2.
|
|
26
|
+
|
|
27
|
+
### Observations
|
|
28
|
+
|
|
29
|
+
- An unexpected file `test/helpers/make-deps.test.ts` had three type errors after removing `getMaxConcurrent` (one test asserting `manager.getMaxConcurrent()`, one structural compatibility check referencing it, and one settings override that only passed `defaultMaxTurns`).
|
|
30
|
+
All three were fixed in the same commit as step 2 — no deviation from the plan.
|
|
31
|
+
- Adding `settings` to `BackgroundParams` (instead of as a 5th function parameter) was the right call: it keeps `spawnBackground` at 4 arguments and groups all spawn-context values together.
|
|
32
|
+
- The health metric update: dead exports 1 → 0, adapter closures 41 → 40 (only `getMaxConcurrent` was removed in this layer; the remaining 8 widget/manager adapter closures need #195 class conversion to collapse).
|
|
33
|
+
- The `background-spawner.ts` module is the only consumer of `getMaxConcurrent` — grep confirms no other call sites beyond `agent-tool.ts`'s interface definition.
|
|
34
|
+
- The `NotificationManager` constructor takes `updateWidget` as a positional callback parameter name — this does NOT need renaming (it's not a structural interface member).
|
|
35
|
+
- The rename from `updateWidget` → `update` is safe because the `WidgetLike` interface in `runtime.ts` already uses `update()` — no naming conflict within the class.
|
|
36
|
+
- All three changes are independent of each other and could be committed in any order, but the plan sequences them for clean `pnpm run check` passes at each step.
|
|
37
|
+
|
|
38
|
+
## Stage: Final Retrospective (2026-05-24T21:15:00Z)
|
|
39
|
+
|
|
40
|
+
### Session summary
|
|
41
|
+
|
|
42
|
+
Planning, TDD implementation, and shipping completed in one continuous session.
|
|
43
|
+
Released as `pi-subagents-v7.2.1` with zero rework or deviations from the plan.
|
|
44
|
+
|
|
45
|
+
### Observations
|
|
46
|
+
|
|
47
|
+
#### What went well
|
|
48
|
+
|
|
49
|
+
- Clean execution end-to-end: 3 refactor commits + 1 docs commit, all planned in advance.
|
|
50
|
+
- The `pnpm run check` gate after step 2 caught `test/helpers/make-deps.test.ts` — a file the plan didn't list — preventing a broken intermediate state.
|
|
51
|
+
This validates the "run type-check after each step" pattern for interface-alignment work.
|
|
52
|
+
- Phased architecture approach paid off: Layers 0 and 1 being done made Layer 2 entirely mechanical.
|
|
53
|
+
|
|
54
|
+
#### What caused friction (agent side)
|
|
55
|
+
|
|
56
|
+
- None identified.
|
|
57
|
+
The issue scope was tight, the plan was unambiguous, and no rabbit-holes arose.
|
|
58
|
+
|
|
59
|
+
#### What caused friction (user side)
|
|
60
|
+
|
|
61
|
+
- None identified.
|
|
62
|
+
The issue body and architecture doc provided complete context with no ambiguity.
|