@gotgenes/pi-subagents 6.3.0 → 6.4.0
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 +234 -263
- package/docs/plans/0108-extract-agent-type-registry.md +322 -0
- package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +36 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +5 -0
- package/src/agent-runner.ts +4 -0
- package/src/agent-types.ts +108 -91
- package/src/index.ts +16 -21
- package/src/session-config.ts +5 -4
- package/src/tools/agent-tool.ts +8 -8
- package/src/tools/get-result-tool.ts +3 -1
- package/src/types.ts +0 -3
- package/src/ui/agent-menu.ts +23 -25
- package/src/ui/agent-widget.ts +10 -9
- package/src/ui/conversation-viewer.ts +4 -2
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 108
|
|
3
|
+
issue_title: "refactor(pi-subagents): extract AgentTypeRegistry class from module-scoped state"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extract AgentTypeRegistry class
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`agent-types.ts` manages a module-scoped `Map<string, AgentConfig>` mutated by `registerAgents()` and read by 12+ call sites across 6 files.
|
|
11
|
+
This is global mutable state hidden behind free functions — tests must call `registerAgents(new Map())` in `beforeEach` to reset it, and `reloadCustomAgents` is threaded as a callback through `AgentToolDeps` and `AgentMenuDeps` because there is no object to own the reload.
|
|
12
|
+
|
|
13
|
+
## Goals
|
|
14
|
+
|
|
15
|
+
- Wrap the module-scoped `agents` Map and its free functions into an injectable `AgentTypeRegistry` class.
|
|
16
|
+
- Replace the `reloadCustomAgents` callback (threaded through 2 dependency bags) with `registry.reload()`.
|
|
17
|
+
- Move `DEFAULT_AGENT_NAMES` from `types.ts` to the registry (it is a constant, not a type).
|
|
18
|
+
- Enable test isolation without module resets — each test creates its own registry instance.
|
|
19
|
+
- Use lift-and-shift: introduce the class alongside the free functions, migrate consumers incrementally, then remove the free functions.
|
|
20
|
+
|
|
21
|
+
## Non-Goals
|
|
22
|
+
|
|
23
|
+
- `SettingsManager` extraction (#109) — separate Phase 7 step.
|
|
24
|
+
- `AgentActivityTracker` extraction (#110) — separate Phase 7 step.
|
|
25
|
+
- Splitting `AgentRecord` lifecycle state (#111) — separate Phase 7 step.
|
|
26
|
+
- Narrowing `AgentConfig` (21 fields) — tracked in the architecture doc but out of scope.
|
|
27
|
+
- Moving `BUILTIN_TOOL_NAMES` — it is a constant with no Map dependency, stays as a module export.
|
|
28
|
+
|
|
29
|
+
## Background
|
|
30
|
+
|
|
31
|
+
### Architecture reference
|
|
32
|
+
|
|
33
|
+
Phase 7, Step A1 in `docs/architecture/architecture.md`.
|
|
34
|
+
Steps A1–A3 are independent and can proceed in any order.
|
|
35
|
+
This plan addresses A1 only.
|
|
36
|
+
|
|
37
|
+
### Relevant modules
|
|
38
|
+
|
|
39
|
+
| Module | Role | agent-types dependency |
|
|
40
|
+
| --------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
41
|
+
| `agent-types.ts` | Module-scoped `agents` Map + 11 free functions | Owns the state |
|
|
42
|
+
| `default-agents.ts` | `DEFAULT_AGENTS` Map constant | Read by `registerAgents` |
|
|
43
|
+
| `custom-agents.ts` | `loadCustomAgents()` → disk scan | Imports `BUILTIN_TOOL_NAMES` |
|
|
44
|
+
| `session-config.ts` | `assembleSessionConfig()` | Imports `resolveAgentConfig`, `getToolNamesForType`, `getMemoryToolNames`, `getReadOnlyMemoryToolNames` |
|
|
45
|
+
| `agent-runner.ts` | `runAgent()` → calls `assembleSessionConfig` | No direct import (transitive via session-config) |
|
|
46
|
+
| `agent-manager.ts` | `AgentManager` → calls runner | No direct import |
|
|
47
|
+
| `tools/agent-tool.ts` | Agent tool definition | Imports `resolveAgentConfig`, `resolveType`; receives `reloadCustomAgents` via `AgentToolDeps` |
|
|
48
|
+
| `ui/agent-menu.ts` | `/agents` command handler | Imports `BUILTIN_TOOL_NAMES`, `getAllTypes`, `resolveAgentConfig`, `resolveType`; receives `reloadCustomAgents` via `AgentMenuDeps` |
|
|
49
|
+
| `ui/agent-widget.ts` | Widget + `getDisplayName` / `getPromptModeLabel` helpers | Imports `resolveAgentConfig` |
|
|
50
|
+
| `ui/conversation-viewer.ts` | Live conversation overlay | Imports `getDisplayName`, `getPromptModeLabel` from `agent-widget.ts` |
|
|
51
|
+
| `tools/get-result-tool.ts` | `get_subagent_result` tool | Imports `getDisplayName` from `agent-widget.ts` |
|
|
52
|
+
| `index.ts` | Extension entry point, wiring | Imports `registerAgents`, `getAvailableTypes`, `getDefaultAgentNames`, `getUserAgentNames`, `resolveAgentConfig`; defines `reloadCustomAgents` closure |
|
|
53
|
+
|
|
54
|
+
### Test files affected
|
|
55
|
+
|
|
56
|
+
| Test file | Current agent-types coupling | Change needed |
|
|
57
|
+
| -------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
58
|
+
| `agent-types.test.ts` (333 lines) | Tests free functions directly, calls `registerAgents()` in `beforeEach` | Add class tests; eventually migrate free-function tests to class tests |
|
|
59
|
+
| `session-config.test.ts` (601 lines) | `vi.mock("../src/agent-types.js")` | Pass mock registry param, remove module mock |
|
|
60
|
+
| `agent-runner.test.ts` (302 lines) | `vi.mock("../src/agent-types.js")` | Provide mock registry in `RunOptions`, remove module mock |
|
|
61
|
+
| `agent-runner-extension-tools.test.ts` (307 lines) | `vi.mock("../src/agent-types.js")` | Same as `agent-runner.test.ts` |
|
|
62
|
+
| `tools/agent-tool.test.ts` (240 lines) | Mocks `reloadCustomAgents` in deps | Replace with `registry` in deps |
|
|
63
|
+
| `ui/agent-menu.test.ts` (184 lines) | `vi.mock("../../src/agent-types.js")`, mocks `reloadCustomAgents` in deps | Replace with `registry` in deps, remove module mock |
|
|
64
|
+
| `agent-widget.test.ts` (26 lines) | Minimal | Pass registry to widget constructor |
|
|
65
|
+
| `agent-manager.test.ts` (712 lines) | No `agent-types.js` mock | Add registry to constructor options |
|
|
66
|
+
|
|
67
|
+
### Constraints from AGENTS.md / code-design skill
|
|
68
|
+
|
|
69
|
+
- **ISP:** `session-config.ts` should accept a narrow interface (only `resolveAgentConfig` + `getToolNamesForType`), not the full registry class.
|
|
70
|
+
- **DIP:** Accept collaborators as parameters; keep IO at the edges.
|
|
71
|
+
- **Pi SDK boundaries:** Pure helpers must not import Pi SDK types.
|
|
72
|
+
- **Lift-and-shift:** Never plan a single step that rewrites an entire large test file at once.
|
|
73
|
+
|
|
74
|
+
## Design Overview
|
|
75
|
+
|
|
76
|
+
### `AgentTypeRegistry` class
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
export class AgentTypeRegistry {
|
|
80
|
+
private agents = new Map<string, AgentConfig>();
|
|
81
|
+
|
|
82
|
+
constructor(private loadUserAgents: () => Map<string, AgentConfig>) {
|
|
83
|
+
this.reload();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Re-scan custom agents from disk and merge with defaults. */
|
|
87
|
+
reload(): void { /* clear + merge DEFAULT_AGENTS + loadUserAgents() */ }
|
|
88
|
+
|
|
89
|
+
resolveAgentConfig(type: string): AgentConfig { /* ... */ }
|
|
90
|
+
resolveType(name: string): string | undefined { /* ... */ }
|
|
91
|
+
getAvailableTypes(): string[] { /* ... */ }
|
|
92
|
+
getAllTypes(): string[] { /* ... */ }
|
|
93
|
+
getDefaultAgentNames(): string[] { /* ... */ }
|
|
94
|
+
getUserAgentNames(): string[] { /* ... */ }
|
|
95
|
+
isValidType(type: string): boolean { /* ... */ }
|
|
96
|
+
getToolNamesForType(type: string): string[] { /* ... */ }
|
|
97
|
+
|
|
98
|
+
static readonly DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"] as const;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The constructor accepts a `loadUserAgents` callback (typically `() => loadCustomAgents(process.cwd())`).
|
|
103
|
+
This keeps `process.cwd()` at the edge (in `index.ts`) and makes tests trivial — they pass `() => new Map()` or a fixture.
|
|
104
|
+
|
|
105
|
+
### What stays as free functions
|
|
106
|
+
|
|
107
|
+
- **`BUILTIN_TOOL_NAMES`** — constant array, no Map dependency.
|
|
108
|
+
Stays as a module-level export in `agent-types.ts`.
|
|
109
|
+
- **`getMemoryToolNames` / `getReadOnlyMemoryToolNames`** — pure functions over constant arrays, no Map access.
|
|
110
|
+
Stay as module-level exports.
|
|
111
|
+
|
|
112
|
+
### Narrow interface for `session-config.ts`
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
/** Narrow registry interface for session-config (ISP). */
|
|
116
|
+
export interface AgentConfigLookup {
|
|
117
|
+
resolveAgentConfig(type: string): AgentConfig;
|
|
118
|
+
getToolNamesForType(type: string): string[];
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`assembleSessionConfig` gains a `registry: AgentConfigLookup` parameter.
|
|
123
|
+
Tests construct a plain object satisfying this interface — no class instantiation needed.
|
|
124
|
+
|
|
125
|
+
### Threading through the call chain
|
|
126
|
+
|
|
127
|
+
`session-config.ts` ← called by `agent-runner.ts` ← called by `AgentManager`:
|
|
128
|
+
|
|
129
|
+
1. `assembleSessionConfig(type, ctx, options, env, registry)` — new param.
|
|
130
|
+
2. `RunOptions` gains `registry: AgentConfigLookup`.
|
|
131
|
+
3. `AgentManagerOptions` gains `registry: AgentTypeRegistry`.
|
|
132
|
+
4. `AgentManager` stores the registry and passes it to the runner via `RunOptions`.
|
|
133
|
+
|
|
134
|
+
### Replacing `reloadCustomAgents` callback
|
|
135
|
+
|
|
136
|
+
Before:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// index.ts
|
|
140
|
+
const reloadCustomAgents = () => {
|
|
141
|
+
const userAgents = loadCustomAgents(process.cwd());
|
|
142
|
+
registerAgents(userAgents);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// AgentToolDeps / AgentMenuDeps
|
|
146
|
+
reloadCustomAgents: () => void;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
After:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// index.ts
|
|
153
|
+
const registry = new AgentTypeRegistry(() => loadCustomAgents(process.cwd()));
|
|
154
|
+
|
|
155
|
+
// AgentToolDeps / AgentMenuDeps
|
|
156
|
+
registry: AgentTypeRegistry;
|
|
157
|
+
|
|
158
|
+
// Callers use:
|
|
159
|
+
deps.registry.reload();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Display helpers (`getDisplayName`, `getPromptModeLabel`)
|
|
163
|
+
|
|
164
|
+
These functions in `agent-widget.ts` call `resolveAgentConfig(type)`.
|
|
165
|
+
After the extraction, they accept the registry (or a `resolveAgentConfig` callback) as a parameter.
|
|
166
|
+
The `AgentWidget` constructor gains a `registry` parameter and passes it to these helpers internally.
|
|
167
|
+
External callers (`conversation-viewer.ts`, `get-result-tool.ts`, etc.) pass the registry they already hold via their deps.
|
|
168
|
+
|
|
169
|
+
## Module-Level Changes
|
|
170
|
+
|
|
171
|
+
### New
|
|
172
|
+
|
|
173
|
+
No new files.
|
|
174
|
+
|
|
175
|
+
### Modified
|
|
176
|
+
|
|
177
|
+
1. **`src/agent-types.ts`**
|
|
178
|
+
- Add `AgentTypeRegistry` class with all instance methods.
|
|
179
|
+
- Add `AgentConfigLookup` interface.
|
|
180
|
+
- Keep free functions temporarily (delegation shim during migration).
|
|
181
|
+
- Final step: remove free functions, module-scoped `agents` Map, and `registerAgents`.
|
|
182
|
+
|
|
183
|
+
2. **`src/types.ts`**
|
|
184
|
+
- Remove `DEFAULT_AGENT_NAMES` constant (moved to `AgentTypeRegistry.DEFAULT_AGENT_NAMES`).
|
|
185
|
+
|
|
186
|
+
3. **`src/session-config.ts`**
|
|
187
|
+
- `assembleSessionConfig` gains a `registry: AgentConfigLookup` parameter.
|
|
188
|
+
- Remove imports of `resolveAgentConfig`, `getToolNamesForType` from `agent-types.ts`.
|
|
189
|
+
|
|
190
|
+
4. **`src/agent-runner.ts`**
|
|
191
|
+
- `RunOptions` gains `registry: AgentConfigLookup`.
|
|
192
|
+
- `runAgent` passes `options.registry` to `assembleSessionConfig`.
|
|
193
|
+
|
|
194
|
+
5. **`src/agent-manager.ts`**
|
|
195
|
+
- `AgentManagerOptions` gains `registry: AgentTypeRegistry`.
|
|
196
|
+
- `AgentManager` stores `this.registry` and passes it into `RunOptions` when calling `runner.run`.
|
|
197
|
+
|
|
198
|
+
6. **`src/tools/agent-tool.ts`**
|
|
199
|
+
- `AgentToolDeps`: add `registry: AgentTypeRegistry`, remove `reloadCustomAgents`.
|
|
200
|
+
- Replace `resolveType(...)` / `resolveAgentConfig(...)` imports with `deps.registry.resolveType(...)` / `deps.registry.resolveAgentConfig(...)`.
|
|
201
|
+
- Replace `deps.reloadCustomAgents()` with `deps.registry.reload()`.
|
|
202
|
+
|
|
203
|
+
7. **`src/ui/agent-menu.ts`**
|
|
204
|
+
- `AgentMenuDeps`: add `registry: AgentTypeRegistry`, remove `reloadCustomAgents`.
|
|
205
|
+
- Replace `getAllTypes()`/`resolveAgentConfig()`/`resolveType()` imports with `deps.registry.*` calls.
|
|
206
|
+
- `BUILTIN_TOOL_NAMES` import stays (it is a constant, not a method).
|
|
207
|
+
- Replace `deps.reloadCustomAgents()` with `deps.registry.reload()`.
|
|
208
|
+
|
|
209
|
+
8. **`src/ui/agent-widget.ts`**
|
|
210
|
+
- `AgentWidget` constructor gains a `registry: AgentTypeRegistry` (or narrow interface) parameter.
|
|
211
|
+
- `getDisplayName(type, registry)` / `getPromptModeLabel(type, registry)` gain a registry parameter.
|
|
212
|
+
- Internal render methods use `this.registry`.
|
|
213
|
+
|
|
214
|
+
9. **`src/ui/conversation-viewer.ts`**
|
|
215
|
+
- `ConversationViewer` constructor gains a registry parameter.
|
|
216
|
+
- Passes it to `getDisplayName` / `getPromptModeLabel` calls.
|
|
217
|
+
|
|
218
|
+
10. **`src/tools/get-result-tool.ts`**
|
|
219
|
+
- `GetResultToolDeps` (or equivalent) gains registry.
|
|
220
|
+
- Passes it to `getDisplayName` calls.
|
|
221
|
+
|
|
222
|
+
11. **`src/index.ts`**
|
|
223
|
+
- Construct `AgentTypeRegistry` with `() => loadCustomAgents(process.cwd())`.
|
|
224
|
+
- Pass registry to `AgentManager`, agent-tool deps, menu deps, widget, get-result-tool deps.
|
|
225
|
+
- Remove `reloadCustomAgents` closure.
|
|
226
|
+
- Remove free-function imports (`registerAgents`, `getDefaultAgentNames`, `getUserAgentNames`, `getAvailableTypes`, `resolveAgentConfig`).
|
|
227
|
+
- Use `registry.*` methods directly for `buildTypeListText`.
|
|
228
|
+
|
|
229
|
+
### Removed
|
|
230
|
+
|
|
231
|
+
- Free functions from `agent-types.ts`: `registerAgents`, `resolveType`, `resolveAgentConfig`, `getAvailableTypes`, `getAllTypes`, `getDefaultAgentNames`, `getUserAgentNames`, `isValidType`, `getToolNamesForType` (final cleanup step).
|
|
232
|
+
- Module-scoped `agents` Map and `resolveKey` helper.
|
|
233
|
+
- `DEFAULT_AGENT_NAMES` from `types.ts`.
|
|
234
|
+
- `reloadCustomAgents` field from `AgentToolDeps` and `AgentMenuDeps`.
|
|
235
|
+
|
|
236
|
+
## Test Impact Analysis
|
|
237
|
+
|
|
238
|
+
### New tests enabled
|
|
239
|
+
|
|
240
|
+
- **Isolation without module resets:** Each test creates its own `AgentTypeRegistry` with a fixture callback, eliminating cross-test state leakage and the `registerAgents(new Map())` ceremony.
|
|
241
|
+
- **Reload behavior:** Tests can verify `registry.reload()` picks up new agents without touching module state.
|
|
242
|
+
|
|
243
|
+
### Existing tests that become redundant
|
|
244
|
+
|
|
245
|
+
- The existing free-function tests in `agent-types.test.ts` become redundant once the class tests cover the same behavior.
|
|
246
|
+
They can be removed in the final cleanup step.
|
|
247
|
+
|
|
248
|
+
### Existing tests that must stay
|
|
249
|
+
|
|
250
|
+
- `session-config.test.ts` — tests `assembleSessionConfig` behavior; mock setup changes from `vi.mock("agent-types.js")` to passing a mock `AgentConfigLookup` object.
|
|
251
|
+
- `agent-runner.test.ts`, `agent-runner-extension-tools.test.ts` — test runner behavior; mock setup changes from `vi.mock("agent-types.js")` to providing mock registry in `RunOptions`.
|
|
252
|
+
- `tools/agent-tool.test.ts` — tests tool handler; deps mock changes from `reloadCustomAgents: vi.fn()` to `registry: mockRegistry`.
|
|
253
|
+
- `ui/agent-menu.test.ts` — tests menu handler; deps mock changes similarly.
|
|
254
|
+
- `agent-manager.test.ts` — must add a mock registry to constructor options.
|
|
255
|
+
|
|
256
|
+
## TDD Order
|
|
257
|
+
|
|
258
|
+
1. **Create `AgentTypeRegistry` class** — Add class to `agent-types.ts` alongside existing free functions.
|
|
259
|
+
Add `AgentConfigLookup` interface.
|
|
260
|
+
Test all methods in a new `describe('AgentTypeRegistry')` block in `agent-types.test.ts`: construction, `reload()`, `resolveAgentConfig`, `resolveType`, `getAvailableTypes`, `getAllTypes`, `getDefaultAgentNames`, `getUserAgentNames`, `isValidType`, `getToolNamesForType`.
|
|
261
|
+
- Test surface: `agent-types.test.ts` — new describe block
|
|
262
|
+
- Commit: `feat(pi-subagents): add AgentTypeRegistry class (#108)`
|
|
263
|
+
|
|
264
|
+
2. **Inject through the config-assembly chain** — `assembleSessionConfig` gains `registry: AgentConfigLookup` param.
|
|
265
|
+
`RunOptions` gains `registry: AgentConfigLookup`.
|
|
266
|
+
`AgentManagerOptions` gains `registry: AgentTypeRegistry`.
|
|
267
|
+
Construct registry in `index.ts` and pass through `AgentManager` → `runAgent` → `assembleSessionConfig`.
|
|
268
|
+
Update `session-config.test.ts` (replace `vi.mock("agent-types.js")` with mock `AgentConfigLookup` object), `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts` (provide mock registry in `RunOptions`, remove `vi.mock`), `agent-manager.test.ts` (add registry to constructor options).
|
|
269
|
+
- Test surface: `session-config.test.ts`, `agent-runner.test.ts`, `agent-runner-extension-tools.test.ts`, `agent-manager.test.ts`
|
|
270
|
+
- Commit: `refactor(pi-subagents): inject registry through config-assembly chain (#108)`
|
|
271
|
+
|
|
272
|
+
3. **Inject into agent tool** — Add `registry: AgentTypeRegistry` to `AgentToolDeps`, remove `reloadCustomAgents`.
|
|
273
|
+
Replace `resolveType` / `resolveAgentConfig` module imports with `deps.registry.*` calls.
|
|
274
|
+
Replace `deps.reloadCustomAgents()` with `deps.registry.reload()`.
|
|
275
|
+
Update `index.ts` agent-tool deps and `tools/agent-tool.test.ts`.
|
|
276
|
+
- Test surface: `tools/agent-tool.test.ts`
|
|
277
|
+
- Commit: `refactor(pi-subagents): inject registry into agent tool (#108)`
|
|
278
|
+
|
|
279
|
+
4. **Inject into agent menu** — Add `registry: AgentTypeRegistry` to `AgentMenuDeps`, remove `reloadCustomAgents`.
|
|
280
|
+
Replace `getAllTypes` / `resolveAgentConfig` / `resolveType` module imports with `deps.registry.*` calls.
|
|
281
|
+
Replace `deps.reloadCustomAgents()` with `deps.registry.reload()`.
|
|
282
|
+
Update `index.ts` menu deps and `ui/agent-menu.test.ts`.
|
|
283
|
+
- Test surface: `ui/agent-menu.test.ts`
|
|
284
|
+
- Commit: `refactor(pi-subagents): inject registry into agent menu (#108)`
|
|
285
|
+
|
|
286
|
+
5. **Inject into agent widget and display helpers** — `AgentWidget` constructor gains `registry`.
|
|
287
|
+
`getDisplayName(type, registry)` and `getPromptModeLabel(type, registry)` gain registry parameters.
|
|
288
|
+
`ConversationViewer` constructor gains registry.
|
|
289
|
+
`GetResultToolDeps` gains registry for `getDisplayName` calls.
|
|
290
|
+
Update `index.ts` wiring, `agent-widget.test.ts`, and any test files that call these helpers.
|
|
291
|
+
- Test surface: `agent-widget.test.ts`, related callers
|
|
292
|
+
- Commit: `refactor(pi-subagents): inject registry into agent widget (#108)`
|
|
293
|
+
|
|
294
|
+
6. **Move `DEFAULT_AGENT_NAMES` to registry** — Add static `DEFAULT_AGENT_NAMES` property on `AgentTypeRegistry`.
|
|
295
|
+
Remove the constant from `types.ts`.
|
|
296
|
+
Grep confirms no import consumers exist — the constant is defined but unused.
|
|
297
|
+
- Test surface: `agent-types.test.ts` — add assertion for static property
|
|
298
|
+
- Commit: `refactor(pi-subagents): move DEFAULT_AGENT_NAMES to registry (#108)`
|
|
299
|
+
|
|
300
|
+
7. **Remove free-function exports** — Delete `registerAgents`, `resolveType`, `resolveAgentConfig`, `getAvailableTypes`, `getAllTypes`, `getDefaultAgentNames`, `getUserAgentNames`, `isValidType`, `getToolNamesForType`, the module-scoped `agents` Map, and `resolveKey` helper from `agent-types.ts`.
|
|
301
|
+
Remove the free-function tests from `agent-types.test.ts` (now covered by class tests).
|
|
302
|
+
Remove any remaining free-function imports from `index.ts`.
|
|
303
|
+
Verify with `pnpm run check` that no dangling references remain.
|
|
304
|
+
- Test surface: `agent-types.test.ts` — remove old describe block
|
|
305
|
+
- Commit: `refactor(pi-subagents): remove free-function exports from agent-types (#108)`
|
|
306
|
+
|
|
307
|
+
## Risks and Mitigations
|
|
308
|
+
|
|
309
|
+
| Risk | Impact | Mitigation |
|
|
310
|
+
| --------------------------------------------------------------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
311
|
+
| Large blast radius — 11 source files, 8 test files | Merge conflicts if other PRs land concurrently | Lift-and-shift: free functions keep working until final removal; each step is a valid commit |
|
|
312
|
+
| `vi.mock("agent-types.js")` removal in runner tests changes behavior | Tests may expose latent bugs in session-config | Mock `AgentConfigLookup` with the same values the current `vi.mock` provides |
|
|
313
|
+
| Display helpers (`getDisplayName`, `getPromptModeLabel`) thread registry through many callers | Signature churn in UI layer | These callers already have a deps bag or constructor params — registry fits naturally |
|
|
314
|
+
| `agent-types.test.ts` (333 lines) needs migration from free-function tests to class tests | Large test rewrite in step 7 | Step 1 creates class tests first; step 7 only deletes the now-redundant free-function tests |
|
|
315
|
+
| `AgentRunner` interface change (`RunOptions` gains `registry`) | Breaks callers that construct `RunOptions` | Only `agent-manager.ts` constructs `RunOptions`; single-site change |
|
|
316
|
+
|
|
317
|
+
## Open Questions
|
|
318
|
+
|
|
319
|
+
- **`getDisplayName` / `getPromptModeLabel` placement:** These are thin display helpers that wrap `resolveAgentConfig`.
|
|
320
|
+
The plan proposes adding a registry parameter.
|
|
321
|
+
An alternative is to make them methods on the registry itself (e.g., `registry.getDisplayName(type)`), trading purity for convenience.
|
|
322
|
+
Decide during implementation based on how natural the call sites feel.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 100
|
|
3
|
+
issue_title: "Replace callback threading with direct session-event subscription"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #100 — Replace callback threading with direct session-event subscription
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-20T22:30:00-04:00)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Implemented Step 3 of the AgentManager internal decomposition — replacing the 3-layer callback-threading pattern with two independent session observers (`subscribeRecordObserver` and `subscribeUIObserver`).
|
|
13
|
+
Five TDD cycles landed with only one unanticipated test breakage, releasing as `pi-subagents-v6.3.0`.
|
|
14
|
+
`SpawnOptions` dropped 5 `on*` fields, `RunOptions` dropped 5, and `ResumeOptions` dropped 3, with net −220 lines of source code removed.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
#### What went well
|
|
19
|
+
|
|
20
|
+
- The phased plan structure (extract observers → wire AgentManager → wire agent-tool → simplify runner) created clean isolation between cycles.
|
|
21
|
+
Each cycle touched 1–2 source files and 1 test file, making changes easy to review.
|
|
22
|
+
- Upgrading `mockSession()` once in cycle 3 to support `subscribe()` and `emit()` was a one-time investment that made the stat-verification tests more realistic — events now drive record state instead of manually calling callbacks on `RunOptions`.
|
|
23
|
+
- The `resume()` simplification was particularly clean: 10 lines of callback wiring reduced to 2 lines (`subscribeRecordObserver` + `runner.resume` with only `{ signal }`).
|
|
24
|
+
- All 5 TDD cycles except one landed first-try with no rework.
|
|
25
|
+
|
|
26
|
+
#### What caused friction (agent side)
|
|
27
|
+
|
|
28
|
+
- `missing-context` — The plan listed `bindExtensions({ onError })` as part of the unchanged runner code, but the `onError` handler called `options.onToolActivity` which was removed in cycle 5.
|
|
29
|
+
The plan said "grep for `ToolActivity`" but the reference was inside a function literal passed to `bindExtensions`, not a type import.
|
|
30
|
+
Impact: one unexpected test failure in cycle 5 (`bindExtensions` test expected `onError`), caught immediately and fixed in the same commit.
|
|
31
|
+
Noted in commit body as a plan deviation.
|
|
32
|
+
|
|
33
|
+
#### What caused friction (user side)
|
|
34
|
+
|
|
35
|
+
- No friction observed.
|
|
36
|
+
The issue description was thorough and unambiguous, and the user's involvement was limited to triggering each phase (`/plan-issue`, `/tdd-plan`, `/ship-issue`).
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Model } from "@earendil-works/pi-ai";
|
|
|
11
11
|
import type { AgentSession, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { AgentRecord } from "./agent-record.js";
|
|
13
13
|
import type { AgentRunner } from "./agent-runner.js";
|
|
14
|
+
import { AgentTypeRegistry } from "./agent-types.js";
|
|
14
15
|
import { debugLog } from "./debug.js";
|
|
15
16
|
import { buildParentSnapshot } from "./parent-snapshot.js";
|
|
16
17
|
import { subscribeRecordObserver } from "./record-observer.js";
|
|
@@ -30,6 +31,7 @@ export interface AgentManagerOptions {
|
|
|
30
31
|
runner: AgentRunner;
|
|
31
32
|
worktrees: WorktreeManager;
|
|
32
33
|
exec: ShellExec;
|
|
34
|
+
registry: AgentTypeRegistry;
|
|
33
35
|
maxConcurrent?: number;
|
|
34
36
|
getRunConfig?: () => RunConfig;
|
|
35
37
|
onStart?: OnAgentStart;
|
|
@@ -81,6 +83,7 @@ export class AgentManager {
|
|
|
81
83
|
private readonly runner: AgentRunner;
|
|
82
84
|
private readonly worktrees: WorktreeManager;
|
|
83
85
|
private readonly exec: ShellExec;
|
|
86
|
+
private readonly registry: AgentTypeRegistry;
|
|
84
87
|
private maxConcurrent: number;
|
|
85
88
|
private getRunConfig?: () => RunConfig;
|
|
86
89
|
|
|
@@ -93,6 +96,7 @@ export class AgentManager {
|
|
|
93
96
|
this.runner = options.runner;
|
|
94
97
|
this.worktrees = options.worktrees;
|
|
95
98
|
this.exec = options.exec;
|
|
99
|
+
this.registry = options.registry;
|
|
96
100
|
this.onComplete = options.onComplete;
|
|
97
101
|
this.onStart = options.onStart;
|
|
98
102
|
this.onCompact = options.onCompact;
|
|
@@ -203,6 +207,7 @@ export class AgentManager {
|
|
|
203
207
|
parentSessionFile: options.parentSessionFile,
|
|
204
208
|
parentSessionId: options.parentSessionId,
|
|
205
209
|
signal: record.abortController!.signal,
|
|
210
|
+
registry: this.registry,
|
|
206
211
|
onSessionCreated: (session) => {
|
|
207
212
|
record.session = session;
|
|
208
213
|
// Capture the session file path early so it's available for display
|
package/src/agent-runner.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
SessionManager,
|
|
13
13
|
SettingsManager,
|
|
14
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import type { AgentConfigLookup } from "./agent-types.js";
|
|
15
16
|
import { extractText } from "./context.js";
|
|
16
17
|
import { detectEnv } from "./env.js";
|
|
17
18
|
import { assembleSessionConfig } from "./session-config.js";
|
|
@@ -91,6 +92,8 @@ export interface RunOptions {
|
|
|
91
92
|
* module-scope `graceTurns` during migration.
|
|
92
93
|
*/
|
|
93
94
|
graceTurns?: number;
|
|
95
|
+
/** Agent config lookup — provides resolveAgentConfig and getToolNamesForType. */
|
|
96
|
+
registry: AgentConfigLookup;
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
export interface RunResult {
|
|
@@ -189,6 +192,7 @@ export async function runAgent(
|
|
|
189
192
|
thinkingLevel: options.thinkingLevel,
|
|
190
193
|
},
|
|
191
194
|
env,
|
|
195
|
+
options.registry,
|
|
192
196
|
);
|
|
193
197
|
|
|
194
198
|
const agentDir = getAgentDir();
|