@gotgenes/pi-subagents 6.9.0 → 6.9.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 +18 -0
- package/docs/architecture/architecture.md +36 -31
- package/docs/plans/0115-decompose-agent-tool.md +337 -0
- package/docs/plans/0116-type-housekeeping.md +351 -0
- package/docs/retro/0114-narrow-agent-tool-menu-deps.md +38 -0
- package/docs/retro/0115-decompose-agent-tool.md +51 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +12 -4
- package/src/agent-runner.ts +2 -1
- package/src/env.ts +7 -1
- package/src/index.ts +2 -4
- package/src/notification.ts +48 -33
- package/src/parent-snapshot.ts +20 -1
- package/src/prompts.ts +3 -2
- package/src/renderer.ts +1 -1
- package/src/session-config.ts +2 -1
- package/src/tools/agent-tool.ts +33 -201
- package/src/tools/background-spawner.ts +116 -0
- package/src/tools/foreground-runner.ts +175 -0
- package/src/tools/helpers.ts +45 -1
- package/src/types.ts +14 -46
- package/src/ui/agent-menu.ts +1 -1
- package/src/ui/conversation-viewer.ts +27 -10
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 116
|
|
3
|
+
issue_title: "refactor(pi-subagents): type housekeeping and small structural cleanups"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Type housekeeping and small structural cleanups
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`types.ts` is a type dumping ground — it collects types that don't have a natural home because the module that should own them didn't exist yet.
|
|
11
|
+
With foundation extractions (#108 registry, `parent-snapshot.ts`, `env.ts`, `notification.ts`) now in place, most of these types have a natural home.
|
|
12
|
+
Several other small housekeeping items share the same "polish" character: a closure-bag factory that is a class in disguise, a positional constructor that creates test friction, and a 22-field `AgentConfig` interface consumed by modules that touch 2–4 of its fields.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Move `NotificationDetails` to `notification.ts`, `ParentSnapshot` to `parent-snapshot.ts`, and `EnvInfo` to `env.ts`.
|
|
17
|
+
- Convert `createNotificationSystem` closure to a `NotificationManager` class.
|
|
18
|
+
- Convert `ConversationViewer` constructor from 7 positional parameters to an options bag.
|
|
19
|
+
- Define narrow `AgentConfig` subset interfaces for consumers that use only a few fields.
|
|
20
|
+
- Leave `types.ts` containing only genuinely cross-cutting types: `SubagentType`, `MemoryScope`, `IsolationMode`, `AgentConfig`, `AgentInvocation`, `ShellExec`, and re-exports (`AgentRecord`, `ThinkingLevel`).
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- Refactoring `AgentConfig` itself (field additions, removals, renames).
|
|
25
|
+
- Changing the `NotificationDeps` interface shape.
|
|
26
|
+
- Modifying `agent-menu.ts` to use narrow subsets — it legitimately reads most `AgentConfig` fields as a config editor/viewer.
|
|
27
|
+
- Touching `invocation-config.ts` — it already receives `AgentConfig | undefined` and only reads invocation-default fields; narrowing its parameter is a separate concern.
|
|
28
|
+
|
|
29
|
+
## Background
|
|
30
|
+
|
|
31
|
+
### Prerequisite status
|
|
32
|
+
|
|
33
|
+
| Issue | Title | Status |
|
|
34
|
+
| ----- | --------------------------------- | ------- |
|
|
35
|
+
| #108 | Extract `AgentTypeRegistry` class | ✅ Done |
|
|
36
|
+
|
|
37
|
+
`DEFAULT_AGENT_NAMES` was already moved to `AgentTypeRegistry.DEFAULT_AGENT_NAMES` as part of #108.
|
|
38
|
+
The remaining work from the issue's checklist is type relocations, two structural conversions, and narrow subset interfaces.
|
|
39
|
+
|
|
40
|
+
### Current `types.ts` exports
|
|
41
|
+
|
|
42
|
+
| Export | Kind | Should stay? |
|
|
43
|
+
| -------------------------------------------------- | --------- | ------------------------------- |
|
|
44
|
+
| `AgentRecord` (re-export) | class | ✅ Cross-cutting |
|
|
45
|
+
| `AgentRecordInit`, `AgentRecordStatus` (re-export) | type | ✅ Cross-cutting |
|
|
46
|
+
| `ThinkingLevel` (re-export) | type | ✅ Cross-cutting |
|
|
47
|
+
| `SubagentType` | type | ✅ Cross-cutting |
|
|
48
|
+
| `MemoryScope` | type | ✅ Cross-cutting |
|
|
49
|
+
| `IsolationMode` | type | ✅ Cross-cutting |
|
|
50
|
+
| `AgentConfig` | interface | ✅ Cross-cutting |
|
|
51
|
+
| `AgentInvocation` | interface | ✅ Cross-cutting |
|
|
52
|
+
| `ShellExec` | type | ✅ Cross-cutting |
|
|
53
|
+
| `NotificationDetails` | interface | ❌ Move to `notification.ts` |
|
|
54
|
+
| `ParentSnapshot` | interface | ❌ Move to `parent-snapshot.ts` |
|
|
55
|
+
| `EnvInfo` | interface | ❌ Move to `env.ts` |
|
|
56
|
+
|
|
57
|
+
### `createNotificationSystem` closure analysis
|
|
58
|
+
|
|
59
|
+
The factory in `notification.ts` shares `pendingNudges` (a `Map`) and timer state across 4 inner functions (`cancelNudge`, `scheduleNudge`, `sendCompletion`, `cleanupCompleted`, `dispose`).
|
|
60
|
+
This is a class in disguise — mutable state + methods that read/write it.
|
|
61
|
+
Converting to a `NotificationManager` class makes the state explicit and lets tests use instance methods directly.
|
|
62
|
+
|
|
63
|
+
### `ConversationViewer` constructor
|
|
64
|
+
|
|
65
|
+
The constructor takes 7 positional parameters:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
constructor(tui, session, record, activity, theme, done, registry)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every test must reconstruct all 7 in order.
|
|
72
|
+
An options bag reduces friction and is resilient to new parameters.
|
|
73
|
+
|
|
74
|
+
### `AgentConfig` field usage by consumer
|
|
75
|
+
|
|
76
|
+
| Consumer | Fields used |
|
|
77
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
78
|
+
| `agent-widget.ts` (`getDisplayName`, `getPromptModeLabel`) | `name`, `displayName`, `promptMode` |
|
|
79
|
+
| `tools/helpers.ts` (`formatAgentList`) | `name`, `description`, `model` |
|
|
80
|
+
| `prompts.ts` (`buildAgentPrompt`) | `name`, `promptMode`, `systemPrompt` |
|
|
81
|
+
| `session-config.ts` (`assembleSessionConfig`) | `name`, `model`, `thinking`, `maxTurns`, `extensions`, `skills`, `memory`, `disallowedTools` |
|
|
82
|
+
| `agent-menu.ts` | Nearly all fields (config viewer/editor) |
|
|
83
|
+
|
|
84
|
+
Natural clusters for narrow interfaces:
|
|
85
|
+
|
|
86
|
+
- **`AgentIdentity`**: `name`, `displayName`, `description`, `promptMode` — UI display and agent listing.
|
|
87
|
+
- **`AgentPromptConfig`**: `name`, `promptMode`, `systemPrompt` — prompt assembly.
|
|
88
|
+
|
|
89
|
+
`session-config.ts` uses 8 fields; narrowing it yields limited value vs. complexity.
|
|
90
|
+
`invocation-config.ts` receives `AgentConfig | undefined` and reads invocation-default fields; this is a separate concern.
|
|
91
|
+
|
|
92
|
+
## Design Overview
|
|
93
|
+
|
|
94
|
+
### Type relocations
|
|
95
|
+
|
|
96
|
+
Move each type to its natural home module and re-export from `types.ts` during an interim step.
|
|
97
|
+
After updating all importers to point at the new home, remove the re-export.
|
|
98
|
+
This two-phase approach (move + re-export, then update importers + remove re-export) avoids a big-bang commit.
|
|
99
|
+
|
|
100
|
+
However, since importers are few (2–4 each) and the types are only used internally, a single step per type is simpler: move the type, update all importers in the same commit.
|
|
101
|
+
|
|
102
|
+
### `NotificationManager` class
|
|
103
|
+
|
|
104
|
+
Replace the `createNotificationSystem` factory with a class that owns its state:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
export class NotificationManager {
|
|
108
|
+
private pendingNudges = new Map<string, ReturnType<typeof setTimeout>>();
|
|
109
|
+
|
|
110
|
+
constructor(private deps: NotificationDeps) {}
|
|
111
|
+
|
|
112
|
+
cancelNudge(key: string): void { /* ... */ }
|
|
113
|
+
sendCompletion(record: AgentRecord): void { /* ... */ }
|
|
114
|
+
cleanupCompleted(id: string): void { /* ... */ }
|
|
115
|
+
dispose(): void { /* ... */ }
|
|
116
|
+
|
|
117
|
+
// private helpers
|
|
118
|
+
private scheduleNudge(key: string, send: () => void, delay?: number): void { /* ... */ }
|
|
119
|
+
private emitIndividualNudge(record: AgentRecord): void { /* ... */ }
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The `NotificationSystem` interface stays as-is — `NotificationManager implements NotificationSystem`.
|
|
124
|
+
The `NotificationDeps` interface is unchanged.
|
|
125
|
+
Callers in `index.ts` switch from `createNotificationSystem(deps)` to `new NotificationManager(deps)`.
|
|
126
|
+
|
|
127
|
+
Consumer call site (index.ts):
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const notifications = new NotificationManager({
|
|
131
|
+
sendMessage: (msg, opts) => pi.sendMessage(msg, opts),
|
|
132
|
+
agentActivity: runtime.agentActivity,
|
|
133
|
+
markFinished: (id) => runtime.markFinished(id),
|
|
134
|
+
updateWidget: () => runtime.updateWidget(),
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `ConversationViewer` options bag
|
|
139
|
+
|
|
140
|
+
Replace positional parameters with a named options interface:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
export interface ConversationViewerOptions {
|
|
144
|
+
tui: TUI;
|
|
145
|
+
session: AgentSession;
|
|
146
|
+
record: AgentRecord;
|
|
147
|
+
activity: AgentActivityTracker | undefined;
|
|
148
|
+
theme: Theme;
|
|
149
|
+
done: (result: undefined) => void;
|
|
150
|
+
registry: AgentConfigLookup;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class ConversationViewer {
|
|
154
|
+
constructor(private options: ConversationViewerOptions) {
|
|
155
|
+
// destructure into private fields or use options.field access
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The existing private fields (`tui`, `session`, `record`, etc.) become reads from `this.options.*` or are destructured in the constructor to named private fields.
|
|
161
|
+
The constructor body (session subscription) is unchanged.
|
|
162
|
+
|
|
163
|
+
### Narrow `AgentConfig` subset interfaces
|
|
164
|
+
|
|
165
|
+
Define two narrow interfaces in `types.ts` and make `AgentConfig` extend them:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
/** UI display and agent listing — name, display name, description, prompt mode. */
|
|
169
|
+
export interface AgentIdentity {
|
|
170
|
+
name: string;
|
|
171
|
+
displayName?: string;
|
|
172
|
+
description: string;
|
|
173
|
+
promptMode: "replace" | "append";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Prompt assembly — name, prompt mode, system prompt. */
|
|
177
|
+
export interface AgentPromptConfig {
|
|
178
|
+
name: string;
|
|
179
|
+
promptMode: "replace" | "append";
|
|
180
|
+
systemPrompt: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
184
|
+
// remaining fields unchanged
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Then update consumers to accept the narrow interface:
|
|
189
|
+
|
|
190
|
+
- `agent-widget.ts`: `getDisplayName(type, registry)` and `getPromptModeLabel(type, registry)` — these go through `AgentConfigLookup.resolveAgentConfig()`, which returns `AgentConfig`.
|
|
191
|
+
The narrowing applies to the *return type usage*, not the function signature.
|
|
192
|
+
No change needed here — the function already destructures only `displayName`, `name`, `promptMode`.
|
|
193
|
+
- `tools/helpers.ts`: `formatAgentList` reads `description`, `model` — these are not in `AgentIdentity`.
|
|
194
|
+
`model` is session-config territory.
|
|
195
|
+
Leave as-is.
|
|
196
|
+
- `prompts.ts`: `buildAgentPrompt(config: AgentPromptConfig, ...)` — narrow the parameter type.
|
|
197
|
+
- `AgentConfigLookup.resolveAgentConfig()` return type stays `AgentConfig` — the registry always has the full config.
|
|
198
|
+
|
|
199
|
+
Since `AgentConfig extends AgentIdentity, AgentPromptConfig`, any code passing an `AgentConfig` to a function expecting the narrow type works without casts.
|
|
200
|
+
|
|
201
|
+
## Module-Level Changes
|
|
202
|
+
|
|
203
|
+
### Modified files
|
|
204
|
+
|
|
205
|
+
1. **`src/types.ts`**
|
|
206
|
+
- Remove `NotificationDetails`, `ParentSnapshot`, `EnvInfo` interface definitions.
|
|
207
|
+
- Add `AgentIdentity` and `AgentPromptConfig` interfaces.
|
|
208
|
+
- Make `AgentConfig` extend `AgentIdentity` and `AgentPromptConfig`.
|
|
209
|
+
- Remove fields from `AgentConfig` body that are now inherited (`name`, `displayName`, `description`, `promptMode`, `systemPrompt`).
|
|
210
|
+
|
|
211
|
+
2. **`src/notification.ts`**
|
|
212
|
+
- Add `NotificationDetails` interface (moved from `types.ts`).
|
|
213
|
+
- Replace `createNotificationSystem` factory with `NotificationManager` class implementing `NotificationSystem`.
|
|
214
|
+
- Export `NotificationManager` (named export) and keep `NotificationSystem` interface export.
|
|
215
|
+
- Remove `createNotificationSystem` export.
|
|
216
|
+
|
|
217
|
+
3. **`src/parent-snapshot.ts`**
|
|
218
|
+
- Add `ParentSnapshot` interface (moved from `types.ts`).
|
|
219
|
+
|
|
220
|
+
4. **`src/env.ts`**
|
|
221
|
+
- Add `EnvInfo` interface (moved from `types.ts`).
|
|
222
|
+
|
|
223
|
+
5. **`src/prompts.ts`**
|
|
224
|
+
- Change `config` parameter type from `AgentConfig` to `AgentPromptConfig`.
|
|
225
|
+
- Update import to use `AgentPromptConfig` from `./types.js`.
|
|
226
|
+
|
|
227
|
+
6. **`src/index.ts`**
|
|
228
|
+
- Update `NotificationDetails` import from `./types.js` to `./notification.js`.
|
|
229
|
+
- Replace `createNotificationSystem(deps)` with `new NotificationManager(deps)`.
|
|
230
|
+
- Update import: `NotificationManager` from `./notification.js`.
|
|
231
|
+
|
|
232
|
+
7. **`src/renderer.ts`**
|
|
233
|
+
- Update `NotificationDetails` import from `./types.js` to `./notification.js`.
|
|
234
|
+
|
|
235
|
+
8. **`src/agent-runner.ts`**
|
|
236
|
+
- Update `ParentSnapshot` import from `./types.js` to `./parent-snapshot.js`.
|
|
237
|
+
|
|
238
|
+
9. **`src/agent-manager.ts`**
|
|
239
|
+
- Update `ParentSnapshot` import from `./types.js` to `./parent-snapshot.js`.
|
|
240
|
+
|
|
241
|
+
10. **`src/session-config.ts`**
|
|
242
|
+
- Update `EnvInfo` import from `./types.js` to `./env.js`.
|
|
243
|
+
|
|
244
|
+
11. **`src/ui/conversation-viewer.ts`**
|
|
245
|
+
- Define `ConversationViewerOptions` interface.
|
|
246
|
+
- Replace 7 positional constructor parameters with single `options: ConversationViewerOptions` parameter.
|
|
247
|
+
- Assign private fields from `options` in constructor body.
|
|
248
|
+
|
|
249
|
+
### Unchanged files
|
|
250
|
+
|
|
251
|
+
- `src/agent-types.ts` — `DEFAULT_AGENT_NAMES` already moved in #108.
|
|
252
|
+
- `src/invocation-config.ts` — narrowing is a separate concern.
|
|
253
|
+
- `src/ui/agent-menu.ts` — legitimately uses most `AgentConfig` fields.
|
|
254
|
+
|
|
255
|
+
### Test files
|
|
256
|
+
|
|
257
|
+
12. **`test/notification.test.ts`**
|
|
258
|
+
- Replace `createNotificationSystem(deps)` calls with `new NotificationManager(deps)`.
|
|
259
|
+
- Update import.
|
|
260
|
+
- All existing assertions stay — the public API is identical.
|
|
261
|
+
|
|
262
|
+
13. **`test/conversation-viewer.test.ts`**
|
|
263
|
+
- Replace positional `new ConversationViewer(tui, session, record, activity, theme, done, registry)` calls with `new ConversationViewer({ tui, session, record, activity, theme, done, registry })`.
|
|
264
|
+
- All existing assertions stay.
|
|
265
|
+
|
|
266
|
+
14. **`test/prompts.test.ts`**
|
|
267
|
+
- Verify existing test fixtures satisfy `AgentPromptConfig` (they should — tests already pass `name`, `promptMode`, `systemPrompt`).
|
|
268
|
+
- May need to narrow mock type annotations.
|
|
269
|
+
|
|
270
|
+
15. **Other test files importing relocated types** (`test/parent-snapshot.test.ts`, `test/renderer.test.ts`)
|
|
271
|
+
- Update import paths if they import directly from `types.ts` (most import from the source module already).
|
|
272
|
+
|
|
273
|
+
## Test Impact Analysis
|
|
274
|
+
|
|
275
|
+
1. **New unit tests enabled:**
|
|
276
|
+
- `NotificationManager` as a class can be tested with standard `new` + method calls — no factory indirection.
|
|
277
|
+
However, the existing factory tests are already clean and simply switch to `new NotificationManager(deps)`.
|
|
278
|
+
No fundamentally new test capabilities.
|
|
279
|
+
|
|
280
|
+
2. **Existing tests that simplify:**
|
|
281
|
+
- `conversation-viewer.test.ts` — the options bag makes test construction more readable and resilient to parameter additions.
|
|
282
|
+
Each test only needs to specify the fields it cares about (with a helper providing defaults).
|
|
283
|
+
- `prompts.test.ts` — narrower `AgentPromptConfig` parameter means test fixtures can omit 19 irrelevant `AgentConfig` fields.
|
|
284
|
+
|
|
285
|
+
3. **Existing tests that must stay as-is:**
|
|
286
|
+
- `notification.test.ts` — all factory tests stay, just switch constructor syntax.
|
|
287
|
+
- `conversation-viewer.test.ts` — all render and input tests stay, just switch constructor syntax.
|
|
288
|
+
- `parent-snapshot.test.ts`, `env.test.ts`, `renderer.test.ts` — behavior unchanged.
|
|
289
|
+
|
|
290
|
+
## TDD Order
|
|
291
|
+
|
|
292
|
+
1. **Move `NotificationDetails` from `types.ts` to `notification.ts`.**
|
|
293
|
+
Cut the interface from `types.ts`, paste into `notification.ts`.
|
|
294
|
+
Update importers: `index.ts`, `renderer.ts` (change import from `./types.js` to `./notification.js`).
|
|
295
|
+
`notification.ts` already imports from `./types.js` for `AgentRecord` — no circular dependency.
|
|
296
|
+
Run `pnpm run check`.
|
|
297
|
+
Commit: `refactor: move NotificationDetails to notification.ts`
|
|
298
|
+
|
|
299
|
+
2. **Move `ParentSnapshot` from `types.ts` to `parent-snapshot.ts`.**
|
|
300
|
+
Cut the interface from `types.ts`, paste into `parent-snapshot.ts`.
|
|
301
|
+
Update importers: `agent-manager.ts`, `agent-runner.ts` (change import from `./types.js` to `./parent-snapshot.js`).
|
|
302
|
+
Run `pnpm run check`.
|
|
303
|
+
Commit: `refactor: move ParentSnapshot to parent-snapshot.ts`
|
|
304
|
+
|
|
305
|
+
3. **Move `EnvInfo` from `types.ts` to `env.ts`.**
|
|
306
|
+
Cut the interface from `types.ts`, paste into `env.ts`.
|
|
307
|
+
Update importers: `session-config.ts`, `prompts.ts` (change import from `./types.js` to `./env.js`).
|
|
308
|
+
`env.ts` already imports `ShellExec` from `./types.js` — no circular dependency.
|
|
309
|
+
Run `pnpm run check`.
|
|
310
|
+
Commit: `refactor: move EnvInfo to env.ts`
|
|
311
|
+
|
|
312
|
+
4. **Convert `createNotificationSystem` to `NotificationManager` class.**
|
|
313
|
+
Replace the factory with a class implementing `NotificationSystem`.
|
|
314
|
+
Move `pendingNudges` and timer logic to private instance state.
|
|
315
|
+
Convert inner functions to methods.
|
|
316
|
+
Keep `NotificationSystem` interface, `NotificationDeps` interface, and `NUDGE_HOLD_MS` constant unchanged.
|
|
317
|
+
Remove `createNotificationSystem` export, add `NotificationManager` export.
|
|
318
|
+
Update `index.ts`: `new NotificationManager(deps)` instead of `createNotificationSystem(deps)`.
|
|
319
|
+
Update `test/notification.test.ts`: `new NotificationManager(deps)` instead of `createNotificationSystem(deps)`, update import.
|
|
320
|
+
Run `pnpm vitest run test/notification.test.ts`.
|
|
321
|
+
Commit: `refactor: convert createNotificationSystem to NotificationManager class`
|
|
322
|
+
|
|
323
|
+
5. **Convert `ConversationViewer` constructor to options bag.**
|
|
324
|
+
Define `ConversationViewerOptions` interface.
|
|
325
|
+
Replace 7 positional parameters with `options: ConversationViewerOptions`.
|
|
326
|
+
Assign private fields from options in constructor body.
|
|
327
|
+
Update all call sites in `test/conversation-viewer.test.ts` and `src/index.ts` (or wherever `new ConversationViewer(...)` is called).
|
|
328
|
+
Run `pnpm vitest run test/conversation-viewer.test.ts`.
|
|
329
|
+
Commit: `refactor: convert ConversationViewer to options bag constructor`
|
|
330
|
+
|
|
331
|
+
6. **Define narrow `AgentIdentity` and `AgentPromptConfig` interfaces.**
|
|
332
|
+
Add interfaces to `types.ts`.
|
|
333
|
+
Make `AgentConfig` extend both; remove inherited fields from `AgentConfig` body.
|
|
334
|
+
Narrow `prompts.ts` `buildAgentPrompt` parameter from `AgentConfig` to `AgentPromptConfig`.
|
|
335
|
+
Update `test/prompts.test.ts` type annotations if needed.
|
|
336
|
+
Run `pnpm run check` and `pnpm vitest run`.
|
|
337
|
+
Commit: `refactor: define AgentIdentity and AgentPromptConfig subset interfaces`
|
|
338
|
+
|
|
339
|
+
## Risks and Mitigations
|
|
340
|
+
|
|
341
|
+
| Risk | Mitigation |
|
|
342
|
+
| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
343
|
+
| Moving types breaks import paths in files not caught by grep | Run `pnpm run check` (full `tsc --noEmit`) after each type relocation step. |
|
|
344
|
+
| `NotificationManager` class changes test mock patterns (e.g., spreading instances) | Tests already use the factory return as an opaque object — switching to `new` is mechanical. No spread patterns in notification tests. |
|
|
345
|
+
| `ConversationViewer` options bag breaks all test call sites at once | All 314 lines of tests use the same 7-arg pattern. A search-and-replace handles it. The options bag is a single-step change. |
|
|
346
|
+
| `AgentConfig extends AgentIdentity, AgentPromptConfig` changes structural compatibility | `extends` is purely additive — existing code passing `AgentConfig` anywhere still works. Narrowed consumers gain type safety, nothing breaks. |
|
|
347
|
+
| `prompts.ts` parameter narrowing breaks callers passing `AgentConfig` | `AgentConfig extends AgentPromptConfig`, so all existing call sites are compatible without changes. |
|
|
348
|
+
|
|
349
|
+
## Open Questions
|
|
350
|
+
|
|
351
|
+
- Whether to define an `AgentSessionConfig` subset for `session-config.ts` (8 fields) — deferred because the narrowing ratio (8 of 22) yields less clarity benefit than `AgentIdentity` (4 of 22) or `AgentPromptConfig` (3 of 22).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 114
|
|
3
|
+
issue_title: "refactor(pi-subagents): narrow AgentToolDeps and AgentMenuDeps"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #114 — narrow AgentToolDeps and AgentMenuDeps
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-21T21:43:48-04:00)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Narrowed `AgentToolDeps` from 9 to 6 fields and `AgentMenuDeps` from 8 to 7 fields.
|
|
13
|
+
Moved `subagents:created` event emission from the Agent tool to a new `AgentManagerObserver.onAgentCreated` method.
|
|
14
|
+
Extracted `buildTypeListText` to `tools/helpers.ts`, derived description text inside `createAgentTool`, removed dead `emitEvent` from `AgentMenuDeps`, and narrowed `agentActivity` to typed `AgentActivityAccess`/`AgentActivityReader` interfaces.
|
|
15
|
+
Test count increased from 638 to 660.
|
|
16
|
+
Released as `pi-subagents-v6.9.0`.
|
|
17
|
+
|
|
18
|
+
### Observations
|
|
19
|
+
|
|
20
|
+
#### What went well
|
|
21
|
+
|
|
22
|
+
- The `ask_user` gate during planning was well-targeted.
|
|
23
|
+
The first question (where to move `emitEvent`) had a clear answer.
|
|
24
|
+
The second (description-text derivation) genuinely needed user input, and the user requested more context via the "I could use more context" response — the follow-up `preview`-type question with fenced code blocks handled this cleanly.
|
|
25
|
+
- The 6-step TDD plan mapped to implementation with only one deviation (see below), caught exactly where the workflow is designed to catch it (the `pnpm run check` step).
|
|
26
|
+
- All 6 prerequisites (#108, #109, #110, #112, #113, #118) were verified as closed before planning.
|
|
27
|
+
The observer issue (#112) was correctly identified from a `gh issue list` grep despite not being explicitly numbered in the issue body (the issue said "the observer issue").
|
|
28
|
+
|
|
29
|
+
#### What caused friction (agent side)
|
|
30
|
+
|
|
31
|
+
- `missing-context` (self-identified) — Step 6 narrowed `agentActivity` from `Map<string, AgentActivityTracker>` to `AgentActivityAccess` (which exposes only `get`/`set`/`delete`), but the test in `agent-tool.test.ts` used `.has()` on the map.
|
|
32
|
+
The `pnpm run check` typecheck caught `Property 'has' does not exist on type 'AgentActivityAccess'`.
|
|
33
|
+
Fixed by replacing `.has(id)` with `.get(id) !== undefined` in the same commit.
|
|
34
|
+
Impact: one extra read + edit cycle (~30 seconds), no rework.
|
|
35
|
+
|
|
36
|
+
#### What caused friction (user side)
|
|
37
|
+
|
|
38
|
+
- Nothing — no user corrections or redirections needed during the session.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 115
|
|
3
|
+
issue_title: "refactor(pi-subagents): decompose agent-tool.ts into foreground/background modules"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #115 — decompose agent-tool.ts into foreground/background modules
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-21T22:30:00-04:00)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Decomposed the 579-line `tools/agent-tool.ts` into focused modules: `foreground-runner.ts` (spinner, streaming, cleanup) and `background-spawner.ts` (activity setup, notification wiring), with `agent-tool.ts` remaining as the orchestrator (411 lines).
|
|
13
|
+
Before extracting, fixed two upstream API gaps: widened `onSessionCreated` callback to `(session, record)` to eliminate a `listAgents()` reverse-search, and added `toolCallId` to `AgentSpawnConfig` so the manager wires `NotificationState` at spawn time.
|
|
14
|
+
Test count increased from 641 to 690.
|
|
15
|
+
Released as `pi-subagents-v6.9.1`.
|
|
16
|
+
|
|
17
|
+
### Observations
|
|
18
|
+
|
|
19
|
+
#### What went well
|
|
20
|
+
|
|
21
|
+
- The revised plan (after user feedback) was structurally clean — fixing API gaps first made the extraction trivial; each extracted module received only what it needed without workarounds.
|
|
22
|
+
- TDD execution was smooth: 6 steps, all green after each commit, only minor deviations (type annotation issue, `index.ts` call-site fix).
|
|
23
|
+
|
|
24
|
+
#### What caused friction (agent side)
|
|
25
|
+
|
|
26
|
+
- `wrong-abstraction` (user-caught) — The initial plan was a mechanical code-move that defined 4 new interfaces (`ForegroundRunDeps`, `BackgroundSpawnDeps`, `ForegroundRunParams`, `BackgroundSpawnParams`) to paper over two upstream API gaps: a `listAgents()` reverse-search in the foreground `onSessionCreated` callback, and a post-spawn `record.notification` mutation in the background path.
|
|
27
|
+
The user asked *"What dependencies are still missing for these split tools, that they want, rather than some low level state or collaborators that they have?"*
|
|
28
|
+
— redirecting me to fix the API surface before extracting.
|
|
29
|
+
Impact: entire plan rewritten (~15 minutes), but the revised plan was significantly cleaner and the implementation went smoothly.
|
|
30
|
+
|
|
31
|
+
- `missing-context` (self-identified) — During step 5 (foreground extraction), two tests in `foreground-runner.test.ts` failed because mock sessions were `{}` objects lacking a `subscribe` method required by `subscribeUIObserver`.
|
|
32
|
+
The function's dependency on `SubscribableSession` (requiring `.subscribe()`) wasn't accounted for in the test mock.
|
|
33
|
+
Impact: one test fix cycle (~2 minutes), no rework.
|
|
34
|
+
|
|
35
|
+
- `missing-context` (self-identified) — Annotating `runForeground` with an explicit `Promise<AgentToolResult<any>>` return type widened the content array type from `{ type: "text", text: string }[]` to `(TextContent | ImageContent)[]`, breaking existing `content[0].text` patterns in tests.
|
|
36
|
+
Fixed by removing the explicit annotation and letting TypeScript infer the narrow type.
|
|
37
|
+
Impact: three edit cycles to diagnose and fix (~5 minutes).
|
|
38
|
+
|
|
39
|
+
- `missing-context` (self-identified) — Step 1 removed `listAgents` from `AgentToolManager` but didn't update the construction site in `index.ts`.
|
|
40
|
+
`pnpm run check` caught it in step 4.
|
|
41
|
+
Impact: one-line fix in the same commit, no rework.
|
|
42
|
+
|
|
43
|
+
#### What caused friction (user side)
|
|
44
|
+
|
|
45
|
+
- The initial plan's size and mechanical nature required a user redirect.
|
|
46
|
+
The `/plan-issue` prompt's Design Overview section asks to sketch consumer call sites for *new collaborators*, but the same Tell-Don't-Ask check should apply to *extracted* modules' interactions with their upstream dependencies.
|
|
47
|
+
A prompt tweak could help the agent catch this pattern earlier.
|
|
48
|
+
|
|
49
|
+
### Changes made
|
|
50
|
+
|
|
51
|
+
1. `.pi/prompts/plan-issue.md` — added extraction-specific Tell-Don't-Ask verification step to the Design Overview section: sketch the extracted module's upstream interactions before planning the extraction, fix API gaps first.
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -13,10 +13,12 @@ import { AgentRecord } from "./agent-record.js";
|
|
|
13
13
|
import type { AgentRunner } from "./agent-runner.js";
|
|
14
14
|
import { AgentTypeRegistry } from "./agent-types.js";
|
|
15
15
|
import { debugLog } from "./debug.js";
|
|
16
|
+
import { NotificationState } from "./notification-state.js";
|
|
17
|
+
import type { ParentSnapshot } from "./parent-snapshot.js";
|
|
16
18
|
import { buildParentSnapshot } from "./parent-snapshot.js";
|
|
17
19
|
import { subscribeRecordObserver } from "./record-observer.js";
|
|
18
20
|
import type { RunConfig } from "./runtime.js";
|
|
19
|
-
import type { AgentInvocation, IsolationMode,
|
|
21
|
+
import type { AgentInvocation, IsolationMode, ShellExec, SubagentType, ThinkingLevel } from "./types.js";
|
|
20
22
|
import type { WorktreeManager } from "./worktree.js";
|
|
21
23
|
import { WorktreeState } from "./worktree-state.js";
|
|
22
24
|
|
|
@@ -72,12 +74,14 @@ export interface AgentSpawnConfig {
|
|
|
72
74
|
invocation?: AgentInvocation;
|
|
73
75
|
/** Parent abort signal — when aborted, the subagent is also stopped. */
|
|
74
76
|
signal?: AbortSignal;
|
|
75
|
-
/** Called when the agent session is created — the
|
|
76
|
-
onSessionCreated?: (session: AgentSession) => void;
|
|
77
|
+
/** Called when the agent session is created — receives the session and the agent's record. */
|
|
78
|
+
onSessionCreated?: (session: AgentSession, record: AgentRecord) => void;
|
|
77
79
|
/** Path to the parent session's JSONL file (for deriving the subagent session directory). */
|
|
78
80
|
parentSessionFile?: string;
|
|
79
81
|
/** Session ID of the parent agent (stored in the child session's parentSession header). */
|
|
80
82
|
parentSessionId?: string;
|
|
83
|
+
/** Tool call ID for background notification wiring. When set, spawn attaches NotificationState. */
|
|
84
|
+
toolCallId?: string;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
export class AgentManager {
|
|
@@ -155,6 +159,10 @@ export class AgentManager {
|
|
|
155
159
|
});
|
|
156
160
|
this.agents.set(id, record);
|
|
157
161
|
|
|
162
|
+
if (options.toolCallId) {
|
|
163
|
+
record.notification = new NotificationState(options.toolCallId);
|
|
164
|
+
}
|
|
165
|
+
|
|
158
166
|
if (options.isBackground) {
|
|
159
167
|
this.observer?.onAgentCreated(record);
|
|
160
168
|
}
|
|
@@ -244,7 +252,7 @@ export class AgentManager {
|
|
|
244
252
|
unsubRecordObserver = subscribeRecordObserver(session, record, {
|
|
245
253
|
onCompact: (r, info) => this.observer?.onAgentCompacted(r, info),
|
|
246
254
|
});
|
|
247
|
-
options.onSessionCreated?.(session);
|
|
255
|
+
options.onSessionCreated?.(session, record);
|
|
248
256
|
},
|
|
249
257
|
})
|
|
250
258
|
.then(({ responseText, session, aborted, steered, sessionFile }) => {
|
package/src/agent-runner.ts
CHANGED
|
@@ -15,9 +15,10 @@ import {
|
|
|
15
15
|
import type { AgentConfigLookup } from "./agent-types.js";
|
|
16
16
|
import { extractText } from "./context.js";
|
|
17
17
|
import { detectEnv } from "./env.js";
|
|
18
|
+
import type { ParentSnapshot } from "./parent-snapshot.js";
|
|
18
19
|
import { assembleSessionConfig } from "./session-config.js";
|
|
19
20
|
import { deriveSubagentSessionDir } from "./session-dir.js";
|
|
20
|
-
import type {
|
|
21
|
+
import type { ShellExec, SubagentType, ThinkingLevel } from "./types.js";
|
|
21
22
|
|
|
22
23
|
/** Names of tools registered by this extension that subagents must NOT inherit. */
|
|
23
24
|
const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
|
package/src/env.ts
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { debugLog } from "./debug.js";
|
|
6
|
-
import type {
|
|
6
|
+
import type { ShellExec } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export interface EnvInfo {
|
|
9
|
+
isGitRepo: boolean;
|
|
10
|
+
branch: string;
|
|
11
|
+
platform: string;
|
|
12
|
+
}
|
|
7
13
|
|
|
8
14
|
export async function detectEnv(exec: ShellExec, cwd: string): Promise<EnvInfo> {
|
|
9
15
|
let isGitRepo = false;
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { AgentTypeRegistry } from "./agent-types.js";
|
|
|
18
18
|
import { loadCustomAgents } from "./custom-agents.js";
|
|
19
19
|
import { SessionLifecycleHandler, ToolStartHandler } from "./handlers/index.js";
|
|
20
20
|
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
21
|
-
import { buildEventData,
|
|
21
|
+
import { buildEventData, type NotificationDetails, NotificationManager } from "./notification.js";
|
|
22
22
|
import { createNotificationRenderer } from "./renderer.js";
|
|
23
23
|
import { createSubagentRuntime } from "./runtime.js";
|
|
24
24
|
import { publishSubagentsService, unpublishSubagentsService } from "./service.js";
|
|
@@ -28,7 +28,6 @@ import { createAgentTool } from "./tools/agent-tool.js";
|
|
|
28
28
|
import { createGetResultTool } from "./tools/get-result-tool.js";
|
|
29
29
|
import { getModelLabelFromConfig } from "./tools/helpers.js";
|
|
30
30
|
import { createSteerTool } from "./tools/steer-tool.js";
|
|
31
|
-
import { type NotificationDetails } from "./types.js";
|
|
32
31
|
import { createAgentsMenuHandler } from "./ui/agent-menu.js";
|
|
33
32
|
import {
|
|
34
33
|
AgentWidget,
|
|
@@ -48,7 +47,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
48
47
|
// ---- Notification system ----
|
|
49
48
|
// runtime.widget is assigned after AgentManager construction; arrow closures
|
|
50
49
|
// capture `runtime` by reference so they always read the current value.
|
|
51
|
-
const notifications =
|
|
50
|
+
const notifications = new NotificationManager({
|
|
52
51
|
sendMessage: (msg, opts) => pi.sendMessage(msg, opts),
|
|
53
52
|
agentActivity: runtime.agentActivity,
|
|
54
53
|
markFinished: (id) => runtime.markFinished(id),
|
|
@@ -169,7 +168,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
169
168
|
resume: (id, prompt, signal) => manager.resume(id, prompt, signal),
|
|
170
169
|
getRecord: (id) => manager.getRecord(id),
|
|
171
170
|
getMaxConcurrent: () => settings.maxConcurrent,
|
|
172
|
-
listAgents: () => manager.listAgents(),
|
|
173
171
|
},
|
|
174
172
|
widget: {
|
|
175
173
|
setUICtx: (ctx) => runtime.setUICtx(ctx as UICtx),
|