@gotgenes/pi-subagents 6.8.1 → 6.8.3
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 +11 -10
- package/docs/plans/0112-replace-agent-manager-callbacks.md +241 -0
- package/docs/plans/0113-disambiguate-spawn-options.md +155 -0
- package/docs/retro/0112-replace-agent-manager-callbacks.md +35 -0
- package/docs/retro/0123-remove-vi-fn-cast-smell.md +49 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +20 -23
- package/src/index.ts +24 -20
- package/src/tools/agent-tool.ts +3 -3
- package/src/ui/agent-menu.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [6.8.3](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.2...pi-subagents-v6.8.3) (2026-05-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* mark Step D1 complete in architecture.md ([#113](https://github.com/gotgenes/pi-packages/issues/113)) ([b42de23](https://github.com/gotgenes/pi-packages/commit/b42de238710931348336d6e7fd81bb000a0ab584))
|
|
14
|
+
* plan disambiguate SpawnOptions (public vs internal) ([#113](https://github.com/gotgenes/pi-packages/issues/113)) ([2f3cebc](https://github.com/gotgenes/pi-packages/commit/2f3cebc6623e0ba20c73145d8e7c6b9ffae6f875))
|
|
15
|
+
* **retro:** add retro notes for issue [#112](https://github.com/gotgenes/pi-packages/issues/112) ([2a59ed4](https://github.com/gotgenes/pi-packages/commit/2a59ed4e4f5462cdbf8df96e4b675a9c5ec6eb9d))
|
|
16
|
+
|
|
17
|
+
## [6.8.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.1...pi-subagents-v6.8.2) (2026-05-22)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
* mark Step C complete in architecture.md ([#112](https://github.com/gotgenes/pi-packages/issues/112)) ([9f6e783](https://github.com/gotgenes/pi-packages/commit/9f6e783ab4ddcabdc24224e02aa631b93b0fb6d4))
|
|
23
|
+
* plan replace AgentManager callbacks with observer interface ([#112](https://github.com/gotgenes/pi-packages/issues/112)) ([b32dde1](https://github.com/gotgenes/pi-packages/commit/b32dde1e473611b2734a287e88c8d155642405a7))
|
|
24
|
+
* **retro:** add retro notes for issue [#123](https://github.com/gotgenes/pi-packages/issues/123) ([e9333ee](https://github.com/gotgenes/pi-packages/commit/e9333ee2af70e821ba1bace63bdbd7befa72ce84))
|
|
25
|
+
|
|
8
26
|
## [6.8.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.0...pi-subagents-v6.8.1) (2026-05-21)
|
|
9
27
|
|
|
10
28
|
|
|
@@ -384,8 +384,8 @@ Each step is sequenced so it makes the next step easier.
|
|
|
384
384
|
| ~~Mutable state bag~~ | ~~`AgentActivity` (7 fields)~~ | **Fixed #110**: `AgentActivityTracker` class; `ui-observer.ts` calls transition methods; widget, notification, agent-tool use read-only accessors |
|
|
385
385
|
| ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~ | **Fixed #109**: `SettingsManager` class; 6 callback fields collapsed to `settings: SettingsManager`; `AgentMenuDeps` now 8 fields |
|
|
386
386
|
| ~~Post-construction mutation~~ | ~~`AgentRecord` non-transition state~~ | **Fixed #111**: `ExecutionState`, `WorktreeState`, `NotificationState` collaborators; `pendingSteers` moved to `AgentManager`; stats encapsulated behind mutation methods |
|
|
387
|
-
| Fire-and-forget callbacks
|
|
388
|
-
| Duplicate `SpawnOptions
|
|
387
|
+
| ~~Fire-and-forget callbacks~~ | ~~`AgentManagerOptions`~~ | **Fixed #112**: `AgentManagerObserver` interface; `observer` replaces 3 callbacks; `index.ts` constructs one observer object instead of 3 closure lambdas |
|
|
388
|
+
| ~~Duplicate `SpawnOptions`~~ | ~~`service.ts` + `agent-manager.ts`~~ | **Fixed #113**: internal type renamed to `AgentSpawnConfig`; public `SpawnOptions` in `service.ts` unchanged |
|
|
389
389
|
| Type dumping ground | `types.ts` | `NotificationDetails` used only by notification/renderer; ~~`DEFAULT_AGENT_NAMES` moved to `AgentTypeRegistry` (#108)~~; `AgentConfig` (21 fields) consumers use 2–4 each |
|
|
390
390
|
| Wide dependency bags | `AgentToolDeps` (7), `AgentMenuDeps` (8) | Settings narrowed (#109); registry narrowed (#108); more narrowing planned in D steps |
|
|
391
391
|
|
|
@@ -445,21 +445,22 @@ Split post-construction mutation into phase-specific collaborators, each born co
|
|
|
445
445
|
Each piece is born complete at the moment its information is available.
|
|
446
446
|
The record doesn't accumulate half-baked state — it receives fully constructed collaborators.
|
|
447
447
|
|
|
448
|
-
### Step C: Replace `AgentManager` callbacks with observer (#112)
|
|
448
|
+
### Step C: Replace `AgentManager` callbacks with observer (#112) ✅
|
|
449
449
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
`index.ts` constructs
|
|
453
|
-
`AgentManagerOptions` drops from
|
|
450
|
+
**Done.**
|
|
451
|
+
`AgentManagerObserver` interface replaces `onStart`/`onComplete`/`onCompact`.
|
|
452
|
+
`index.ts` constructs one observer object instead of 3 closure lambdas.
|
|
453
|
+
`AgentManagerOptions` drops from 9 → 7 fields.
|
|
454
454
|
|
|
455
455
|
### Step D: Disambiguate `SpawnOptions` and narrow dependency bags
|
|
456
456
|
|
|
457
457
|
With the registry class, settings manager, and observer in place, the dependency bags shrink naturally.
|
|
458
458
|
|
|
459
|
-
#### D1. Disambiguate `SpawnOptions` (#113)
|
|
459
|
+
#### D1. Disambiguate `SpawnOptions` (#113) ✅
|
|
460
460
|
|
|
461
|
-
|
|
462
|
-
|
|
461
|
+
**Done.**
|
|
462
|
+
Internal `SpawnOptions` in `agent-manager.ts` renamed to `AgentSpawnConfig`.
|
|
463
|
+
Public `SpawnOptions` in `service.ts` is unchanged.
|
|
463
464
|
|
|
464
465
|
#### D2. Narrow `AgentToolDeps` and `AgentMenuDeps` (#114)
|
|
465
466
|
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 112
|
|
3
|
+
issue_title: "refactor(pi-subagents): replace AgentManager callbacks with observer interface"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Replace AgentManager callbacks with observer interface
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`AgentManagerOptions` accepts three fire-and-forget callbacks — `onStart`, `onComplete`, `onCompact` — that `index.ts` wires as closure lambdas capturing `runtime`, `pi`, `notifications`, and other extension state.
|
|
11
|
+
This is the same callback-threading pattern that was replaced with direct session subscriptions in #100, but one level up.
|
|
12
|
+
The callbacks are notification-style (fire-and-forget, no return value) and match the observer pattern exactly.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Replace the three callback parameters on `AgentManagerOptions` with a single `observer?: AgentManagerObserver` interface.
|
|
17
|
+
- `index.ts` constructs one observer object instead of three independent closure lambdas.
|
|
18
|
+
- `AgentManagerOptions` shrinks by two net fields (remove 3 callbacks, add 1 observer).
|
|
19
|
+
- Preserve all existing behavior: lifecycle events, record persistence, notification dispatch, compaction events.
|
|
20
|
+
- Non-breaking refactor — no public API changes (the callbacks are internal to the package).
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- Extracting the observer implementation from `index.ts` into its own module — that can follow if the object grows.
|
|
25
|
+
- Changing `RecordObserverOptions` (session-level observer) — it remains a separate concern at a different layer.
|
|
26
|
+
- Narrowing `AgentToolDeps` or `AgentMenuDeps` — tracked in #114.
|
|
27
|
+
- Disambiguating `SpawnOptions` — tracked in #113.
|
|
28
|
+
|
|
29
|
+
## Background
|
|
30
|
+
|
|
31
|
+
### Current callback wiring
|
|
32
|
+
|
|
33
|
+
`agent-manager.ts` defines three callback type aliases and stores them as private fields:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
export type OnAgentStart = (record: AgentRecord) => void;
|
|
37
|
+
export type OnAgentComplete = (record: AgentRecord) => void;
|
|
38
|
+
export type OnAgentCompact = (record: AgentRecord, info: CompactionInfo) => void;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`AgentManager` invokes them at three points:
|
|
42
|
+
|
|
43
|
+
1. `onStart` — called in `startAgent()` after `record.markRunning()`.
|
|
44
|
+
2. `onComplete` — called in `startAgent()`'s `.then()` and `.catch()` handlers for background agents, and in `drainQueue()` on late failure.
|
|
45
|
+
3. `onCompact` — relayed through `subscribeRecordObserver()` → `RecordObserverOptions.onCompact`.
|
|
46
|
+
|
|
47
|
+
`index.ts` builds ~30 lines of closure lambdas (lines 73–116) that capture `pi`, `notifications`, and `buildEventData`.
|
|
48
|
+
|
|
49
|
+
### Observer pattern already established
|
|
50
|
+
|
|
51
|
+
`record-observer.ts` and `ui/ui-observer.ts` are session-level observers that subscribe directly to session events.
|
|
52
|
+
This refactoring applies the same principle at the manager level — grouping related notification callbacks into a single interface.
|
|
53
|
+
|
|
54
|
+
### Dependency: issue #111 (AgentRecord lifecycle split)
|
|
55
|
+
|
|
56
|
+
Issue #111 is closed.
|
|
57
|
+
The observer interface is designed against the current record shape, which reflects the post-#111 lifecycle split.
|
|
58
|
+
|
|
59
|
+
### Architecture reference
|
|
60
|
+
|
|
61
|
+
Phase 7, Step C in `docs/architecture/architecture.md`.
|
|
62
|
+
|
|
63
|
+
## Design Overview
|
|
64
|
+
|
|
65
|
+
### Observer interface
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
export interface AgentManagerObserver {
|
|
69
|
+
onAgentStarted(record: AgentRecord): void;
|
|
70
|
+
onAgentCompleted(record: AgentRecord): void;
|
|
71
|
+
onAgentCompacted(record: AgentRecord, info: CompactionInfo): void;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
All three methods are fire-and-forget (void return, no async).
|
|
76
|
+
The interface uses past-tense naming (`Started`, `Completed`, `Compacted`) to signal that these are after-the-fact notifications, not hooks that influence the operation.
|
|
77
|
+
|
|
78
|
+
### `AgentManagerOptions` change
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export interface AgentManagerOptions {
|
|
82
|
+
runner: AgentRunner;
|
|
83
|
+
worktrees: WorktreeManager;
|
|
84
|
+
exec: ShellExec;
|
|
85
|
+
registry: AgentTypeRegistry;
|
|
86
|
+
getMaxConcurrent?: () => number;
|
|
87
|
+
getRunConfig?: () => RunConfig;
|
|
88
|
+
observer?: AgentManagerObserver; // replaces onStart, onComplete, onCompact
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Fields go from 9 → 7 (remove 3 callbacks, add 1 observer).
|
|
93
|
+
|
|
94
|
+
### AgentManager internal changes
|
|
95
|
+
|
|
96
|
+
The three private fields (`onStart`, `onComplete`, `onCompact`) become one: `private observer?: AgentManagerObserver`.
|
|
97
|
+
Call sites change from `this.onStart?.(record)` to `this.observer?.onAgentStarted(record)`.
|
|
98
|
+
|
|
99
|
+
The `onCompact` relay into `subscribeRecordObserver` changes from:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
onCompact: (r, info) => this.onCompact?.(r, info),
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
to:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
onCompact: (r, info) => this.observer?.onAgentCompacted(r, info),
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `CompactionInfo` stays in `agent-manager.ts`
|
|
112
|
+
|
|
113
|
+
`CompactionInfo` is a data shape consumed by the observer interface and already defined in `agent-manager.ts`.
|
|
114
|
+
It stays co-located since both `AgentManagerObserver` and `CompactionInfo` are exported from the same module.
|
|
115
|
+
`record-observer.ts` continues to import `CompactionInfo` from `agent-manager.ts`.
|
|
116
|
+
|
|
117
|
+
### Observer construction in `index.ts`
|
|
118
|
+
|
|
119
|
+
The three closure lambdas collapse into one object literal:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const observer: AgentManagerObserver = {
|
|
123
|
+
onAgentStarted(record) {
|
|
124
|
+
pi.events.emit("subagents:started", {
|
|
125
|
+
id: record.id, type: record.type, description: record.description,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
onAgentCompleted(record) {
|
|
129
|
+
const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
|
|
130
|
+
const eventData = buildEventData(record);
|
|
131
|
+
if (isError) pi.events.emit("subagents:failed", eventData);
|
|
132
|
+
else pi.events.emit("subagents:completed", eventData);
|
|
133
|
+
|
|
134
|
+
pi.appendEntry("subagents:record", { /* same fields as today */ });
|
|
135
|
+
|
|
136
|
+
if (record.notification?.resultConsumed) {
|
|
137
|
+
notifications.cleanupCompleted(record.id);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
notifications.sendCompletion(record);
|
|
141
|
+
},
|
|
142
|
+
onAgentCompacted(record, info) {
|
|
143
|
+
pi.events.emit("subagents:compacted", {
|
|
144
|
+
id: record.id, type: record.type, description: record.description,
|
|
145
|
+
reason: info.reason, tokensBefore: info.tokensBefore,
|
|
146
|
+
compactionCount: record.compactionCount,
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Passed as `observer` to `new AgentManager({ ..., observer })`.
|
|
153
|
+
|
|
154
|
+
### Removed exports
|
|
155
|
+
|
|
156
|
+
The three callback type aliases are removed:
|
|
157
|
+
|
|
158
|
+
- `OnAgentStart`
|
|
159
|
+
- `OnAgentComplete`
|
|
160
|
+
- `OnAgentCompact`
|
|
161
|
+
|
|
162
|
+
These are only imported in `agent-manager.test.ts` — no production consumers outside `agent-manager.ts`.
|
|
163
|
+
|
|
164
|
+
## Module-Level Changes
|
|
165
|
+
|
|
166
|
+
### `src/agent-manager.ts`
|
|
167
|
+
|
|
168
|
+
- Add `AgentManagerObserver` interface (3 methods).
|
|
169
|
+
- Remove `OnAgentStart`, `OnAgentComplete`, `OnAgentCompact` type aliases.
|
|
170
|
+
- Replace `onStart`, `onComplete`, `onCompact` fields in `AgentManagerOptions` with `observer?: AgentManagerObserver`.
|
|
171
|
+
- Replace three private fields with `private observer?: AgentManagerObserver`.
|
|
172
|
+
- Update constructor to assign `this.observer = options.observer`.
|
|
173
|
+
- Update all call sites: `this.onStart?.(record)` → `this.observer?.onAgentStarted(record)`, etc.
|
|
174
|
+
- The `onCompact` relay to `subscribeRecordObserver` uses `this.observer?.onAgentCompacted`.
|
|
175
|
+
|
|
176
|
+
### `src/index.ts`
|
|
177
|
+
|
|
178
|
+
- Replace the three closure-lambda properties (`onComplete:`, `onStart:`, `onCompact:`) with a single `observer` object literal.
|
|
179
|
+
- Import `AgentManagerObserver` from `agent-manager.ts`.
|
|
180
|
+
- Remove any now-unused callback type imports.
|
|
181
|
+
|
|
182
|
+
### `src/record-observer.ts`
|
|
183
|
+
|
|
184
|
+
- No changes.
|
|
185
|
+
`RecordObserverOptions.onCompact` stays as-is — it is the session-level relay, not the manager-level observer.
|
|
186
|
+
`CompactionInfo` import from `agent-manager.ts` stays unchanged.
|
|
187
|
+
|
|
188
|
+
### `test/agent-manager.test.ts`
|
|
189
|
+
|
|
190
|
+
- Update imports: remove `OnAgentStart`, `OnAgentComplete`, `OnAgentCompact`; add `AgentManagerObserver`.
|
|
191
|
+
- Update `createManager` helper: replace the three callback overrides with `observer?: Partial<AgentManagerObserver>`.
|
|
192
|
+
The factory spreads a no-op default observer with test-provided overrides.
|
|
193
|
+
- Update each test that wires a callback to construct an observer object instead.
|
|
194
|
+
|
|
195
|
+
## Test Impact Analysis
|
|
196
|
+
|
|
197
|
+
1. No new unit tests are enabled by this refactoring — it's a 1:1 shape change.
|
|
198
|
+
2. No existing tests become redundant — each test exercises a specific lifecycle scenario (race condition, foreground vs background, error handling, queue drain).
|
|
199
|
+
3. All existing `agent-manager.test.ts` tests stay, with mechanical updates to the observer wiring.
|
|
200
|
+
The assertions remain the same (e.g., "observer.onAgentCompleted fires with `resultConsumed=false` when `markConsumed` called after await").
|
|
201
|
+
|
|
202
|
+
## TDD Order
|
|
203
|
+
|
|
204
|
+
### Step 1: Introduce `AgentManagerObserver` interface alongside existing callbacks
|
|
205
|
+
|
|
206
|
+
Add the interface to `agent-manager.ts`.
|
|
207
|
+
No production behavior changes — the interface is unused at this point.
|
|
208
|
+
|
|
209
|
+
- Commit: `refactor: add AgentManagerObserver interface`
|
|
210
|
+
|
|
211
|
+
### Step 2: Switch `AgentManager` internals to observer
|
|
212
|
+
|
|
213
|
+
1. Replace the three `onStart`/`onComplete`/`onCompact` fields on `AgentManagerOptions` with `observer?: AgentManagerObserver`.
|
|
214
|
+
2. Replace the three private fields with one `private observer?: AgentManagerObserver`.
|
|
215
|
+
3. Update all internal call sites (`this.onStart?.(record)` → `this.observer?.onAgentStarted(record)`, etc.).
|
|
216
|
+
4. Remove the three callback type aliases (`OnAgentStart`, `OnAgentComplete`, `OnAgentCompact`).
|
|
217
|
+
5. Update `test/agent-manager.test.ts`: change imports, update `createManager` helper and all test call sites to construct observer objects instead of individual callbacks.
|
|
218
|
+
6. Run `pnpm run check` to verify types.
|
|
219
|
+
|
|
220
|
+
- Commit: `refactor: replace AgentManager callbacks with observer interface (#112)`
|
|
221
|
+
|
|
222
|
+
### Step 3: Update `index.ts` to construct observer
|
|
223
|
+
|
|
224
|
+
1. Replace the three closure-lambda properties in the `new AgentManager({...})` call with a single `observer` object.
|
|
225
|
+
2. Import `AgentManagerObserver` type.
|
|
226
|
+
3. Remove unused callback type imports.
|
|
227
|
+
4. Run full test suite.
|
|
228
|
+
|
|
229
|
+
- Commit: `refactor: construct AgentManagerObserver in index.ts (#112)`
|
|
230
|
+
|
|
231
|
+
## Risks and Mitigations
|
|
232
|
+
|
|
233
|
+
| Risk | Mitigation |
|
|
234
|
+
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
235
|
+
| Tests break silently because esbuild ignores excess properties — old `onComplete` fields pass through without error | Step 2 removes the type aliases, so TypeScript catches any test still using the old shape via `pnpm run check` |
|
|
236
|
+
| `onComplete` error-swallowing behavior changes | The `try/catch` around `this.onComplete?.(record)` in the `.then()` path moves exactly to `this.observer?.onAgentCompleted(record)` — same guard, same catch, same debugLog |
|
|
237
|
+
| `Partial<AgentManagerObserver>` in test factory silently drops required methods | Tests that need a specific method construct it explicitly; the factory only provides a convenient default for tests that don't care about any observer method |
|
|
238
|
+
|
|
239
|
+
## Open Questions
|
|
240
|
+
|
|
241
|
+
- None — the issue's proposed design is unambiguous and aligns with the established session-observer pattern.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 113
|
|
3
|
+
issue_title: "refactor(pi-subagents): disambiguate SpawnOptions (public vs internal)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Disambiguate SpawnOptions (public vs internal)
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`SpawnOptions` is defined in two places with the same name but incompatible shapes:
|
|
11
|
+
|
|
12
|
+
- `service.ts` (public API): 8 fields, JSON-friendly (`model` is `string`, `thinkingLevel` is `string`, uses `foreground` not `isBackground`).
|
|
13
|
+
- `agent-manager.ts` (internal): 13 fields, runtime types (`model` is `Model<any>`, `thinkingLevel` is `ThinkingLevel`, includes `signal`, `onSessionCreated`, `invocation`).
|
|
14
|
+
|
|
15
|
+
The name collision makes it ambiguous which type a reader is working with when they see `SpawnOptions` in a signature.
|
|
16
|
+
`service-adapter.ts` manually converts between the two shapes.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Rename the internal `SpawnOptions` in `agent-manager.ts` to `AgentSpawnConfig`.
|
|
21
|
+
- Keep the public `SpawnOptions` in `service.ts` unchanged — it's the published API.
|
|
22
|
+
- Update all internal consumers (`agent-tool.ts`, `agent-menu.ts`, `agent-manager.ts`) to use the new name.
|
|
23
|
+
- Update the `SpawnArgs` internal interface in `agent-manager.ts` to reference `AgentSpawnConfig`.
|
|
24
|
+
- Non-breaking refactor — the public API surface is unchanged.
|
|
25
|
+
|
|
26
|
+
## Non-Goals
|
|
27
|
+
|
|
28
|
+
- Splitting `AgentSpawnConfig` into agent-configuration fields vs execution/lifecycle fields — the issue mentions this as a "consider" item; defer to a follow-up if the type grows further.
|
|
29
|
+
- Narrowing `AgentToolDeps` or `AgentMenuDeps` — tracked in #114.
|
|
30
|
+
- Removing `onSessionCreated` — it's a legitimate per-spawn callback used by `agent-tool.ts` for UI streaming, structurally different from the lifecycle observer (#112).
|
|
31
|
+
|
|
32
|
+
## Background
|
|
33
|
+
|
|
34
|
+
### Current consumers of internal `SpawnOptions`
|
|
35
|
+
|
|
36
|
+
| File | How it references `SpawnOptions` |
|
|
37
|
+
| --------------------- | ------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `agent-manager.ts` | Defines the type; uses it in `spawn()`, `spawnAndWait()`, and `SpawnArgs` |
|
|
39
|
+
| `tools/agent-tool.ts` | Imports and uses in `AgentToolManager.spawn` and `AgentToolManager.spawnAndWait` signatures |
|
|
40
|
+
| `ui/agent-menu.ts` | Imports and uses in `AgentMenuManager.spawnAndWait` signature |
|
|
41
|
+
|
|
42
|
+
### Public `SpawnOptions` in `service.ts`
|
|
43
|
+
|
|
44
|
+
Defined alongside `SubagentsService`.
|
|
45
|
+
Used by `service-adapter.ts` at the conversion boundary.
|
|
46
|
+
Published via `package.json` exports — **not touched by this change**.
|
|
47
|
+
|
|
48
|
+
### Dependency: issue #112 (observer refactor)
|
|
49
|
+
|
|
50
|
+
Issue #112 is closed.
|
|
51
|
+
The observer eliminated `onStart`/`onComplete`/`onCompact` from `AgentManagerOptions`.
|
|
52
|
+
`onSessionCreated` remains on the internal `SpawnOptions` (now `AgentSpawnConfig`) — it's per-spawn, not per-manager.
|
|
53
|
+
|
|
54
|
+
### Architecture reference
|
|
55
|
+
|
|
56
|
+
Phase 7, Step D1 in `docs/architecture/architecture.md`.
|
|
57
|
+
|
|
58
|
+
## Design Overview
|
|
59
|
+
|
|
60
|
+
This is a pure rename — no structural or behavioral changes.
|
|
61
|
+
The internal `SpawnOptions` becomes `AgentSpawnConfig`.
|
|
62
|
+
Every `import type { SpawnOptions }` from `"../agent-manager.js"` or `"./agent-manager.js"` becomes `import type { AgentSpawnConfig }`.
|
|
63
|
+
|
|
64
|
+
The name `AgentSpawnConfig` was chosen because:
|
|
65
|
+
|
|
66
|
+
1. It disambiguates from the public `SpawnOptions`.
|
|
67
|
+
2. It follows the established naming convention in this package (`AgentRecord`, `AgentInvocation`, `AgentTypeRegistry`).
|
|
68
|
+
3. "Config" conveys that this is a configuration bag assembled by the caller and consumed by the manager — not a service-level options type.
|
|
69
|
+
|
|
70
|
+
### Type shape (unchanged)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
export interface AgentSpawnConfig {
|
|
74
|
+
description: string;
|
|
75
|
+
model?: Model<any>;
|
|
76
|
+
maxTurns?: number;
|
|
77
|
+
isolated?: boolean;
|
|
78
|
+
inheritContext?: boolean;
|
|
79
|
+
thinkingLevel?: ThinkingLevel;
|
|
80
|
+
isBackground?: boolean;
|
|
81
|
+
bypassQueue?: boolean;
|
|
82
|
+
isolation?: IsolationMode;
|
|
83
|
+
invocation?: AgentInvocation;
|
|
84
|
+
signal?: AbortSignal;
|
|
85
|
+
onSessionCreated?: (session: AgentSession) => void;
|
|
86
|
+
parentSessionFile?: string;
|
|
87
|
+
parentSessionId?: string;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Module-Level Changes
|
|
92
|
+
|
|
93
|
+
### `src/agent-manager.ts`
|
|
94
|
+
|
|
95
|
+
- Rename `export interface SpawnOptions` → `export interface AgentSpawnConfig`.
|
|
96
|
+
- Update `SpawnArgs.options` type from `SpawnOptions` to `AgentSpawnConfig`.
|
|
97
|
+
- Update `spawn()` parameter type from `SpawnOptions` to `AgentSpawnConfig`.
|
|
98
|
+
- Update `spawnAndWait()` parameter type from `Omit<SpawnOptions, "isBackground">` to `Omit<AgentSpawnConfig, "isBackground">`.
|
|
99
|
+
|
|
100
|
+
### `src/tools/agent-tool.ts`
|
|
101
|
+
|
|
102
|
+
- Change import from `SpawnOptions` to `AgentSpawnConfig`.
|
|
103
|
+
- Update `AgentToolManager.spawn` and `AgentToolManager.spawnAndWait` signatures.
|
|
104
|
+
|
|
105
|
+
### `src/ui/agent-menu.ts`
|
|
106
|
+
|
|
107
|
+
- Change import from `SpawnOptions` to `AgentSpawnConfig`.
|
|
108
|
+
- Update `AgentMenuManager.spawnAndWait` signature.
|
|
109
|
+
|
|
110
|
+
### `src/service.ts`
|
|
111
|
+
|
|
112
|
+
- No changes — the public `SpawnOptions` stays as-is.
|
|
113
|
+
|
|
114
|
+
### `src/service-adapter.ts`
|
|
115
|
+
|
|
116
|
+
- No changes — it already uses `unknown` for the spawn options parameter in `AgentManagerLike`.
|
|
117
|
+
|
|
118
|
+
### `test/agent-manager.test.ts`
|
|
119
|
+
|
|
120
|
+
- No import changes needed — the test file does not import `SpawnOptions`.
|
|
121
|
+
- One test description string mentions "SpawnOptions" → update to "AgentSpawnConfig" for accuracy.
|
|
122
|
+
|
|
123
|
+
## Test Impact Analysis
|
|
124
|
+
|
|
125
|
+
1. No new tests are enabled by this rename — it's a 1:1 name substitution.
|
|
126
|
+
2. No existing tests become redundant.
|
|
127
|
+
3. All existing tests stay as-is — they construct raw object literals that structurally satisfy the type regardless of its name.
|
|
128
|
+
|
|
129
|
+
## TDD Order
|
|
130
|
+
|
|
131
|
+
### Step 1: Rename `SpawnOptions` to `AgentSpawnConfig` and update all consumers
|
|
132
|
+
|
|
133
|
+
1. Rename the interface in `agent-manager.ts`.
|
|
134
|
+
2. Update `SpawnArgs`, `spawn()`, and `spawnAndWait()` in `agent-manager.ts`.
|
|
135
|
+
3. Update the import and signatures in `tools/agent-tool.ts`.
|
|
136
|
+
4. Update the import and signature in `ui/agent-menu.ts`.
|
|
137
|
+
5. Update the test description string in `test/agent-manager.test.ts`.
|
|
138
|
+
6. Run `pnpm run check` to verify types.
|
|
139
|
+
7. Run `pnpm vitest run` to verify all tests pass.
|
|
140
|
+
|
|
141
|
+
- Commit: `refactor: rename internal SpawnOptions to AgentSpawnConfig (#113)`
|
|
142
|
+
|
|
143
|
+
This is a single-step refactor because the rename is mechanical and all consumers must be updated atomically for the type checker to stay green.
|
|
144
|
+
|
|
145
|
+
## Risks and Mitigations
|
|
146
|
+
|
|
147
|
+
| Risk | Mitigation |
|
|
148
|
+
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
149
|
+
| Missed consumer still references old name | `pnpm run check` will catch any unresolved `SpawnOptions` import from `agent-manager.ts` since the export no longer exists |
|
|
150
|
+
| Test descriptions become misleading | Grep for "SpawnOptions" in test files and update any description strings that reference the old name |
|
|
151
|
+
| Confusion with `service.ts` `SpawnOptions` during review | The plan is scoped to internal-only changes; `service.ts` is explicitly listed as "no changes" |
|
|
152
|
+
|
|
153
|
+
## Open Questions
|
|
154
|
+
|
|
155
|
+
- None — the rename is unambiguous and aligns with both the issue proposal and the architecture doc.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 112
|
|
3
|
+
issue_title: "refactor(pi-subagents): replace AgentManager callbacks with observer interface"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #112 — replace AgentManager callbacks with observer interface
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-21T21:00:00-04:00)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Replaced three fire-and-forget callback fields (`onStart`, `onComplete`, `onCompact`) on `AgentManagerOptions` with a single `AgentManagerObserver` interface.
|
|
13
|
+
The refactoring touched `agent-manager.ts`, `index.ts`, and `agent-manager.test.ts` with zero test-count delta (652/652).
|
|
14
|
+
Released as `pi-subagents-v6.8.2`.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
#### What went well
|
|
19
|
+
|
|
20
|
+
- The issue description was thorough and unambiguous — no `ask_user` needed during planning, and the design mapped directly to implementation.
|
|
21
|
+
- Self-identified the testing skill's single-call-site rule during execution: the plan split Steps 2 and 3 into separate commits, but `AgentManagerOptions` has one call site in `index.ts`, so both had to land together.
|
|
22
|
+
Merged them without rework.
|
|
23
|
+
- Pre-existing lint issue (unused `ExecutionState` import in `agent-manager.ts`) caught and fixed proactively during the lint step.
|
|
24
|
+
|
|
25
|
+
#### What caused friction (agent side)
|
|
26
|
+
|
|
27
|
+
- `instruction-violation` (self-identified) — The plan wrote TDD Steps 2 and 3 as separate commits, but the testing skill says "when a TDD step changes an interface that has a single call site, the step must include updating that call site."
|
|
28
|
+
The planning phase loaded the testing skill but didn't apply this specific rule when structuring the TDD order.
|
|
29
|
+
Impact: added friction but no rework — caught during execution when `pnpm run check` surfaced the expected type error in `index.ts` after Step 2.
|
|
30
|
+
- `other` (tool interaction) — One `Edit` tool `oldText` match failure in `agent-manager.ts` because the selected block included a blank line that didn't exist between `subscribeRecordObserver` and `options.onSessionCreated`.
|
|
31
|
+
Impact: one extra read + edit cycle (~30 seconds).
|
|
32
|
+
|
|
33
|
+
#### What caused friction (user side)
|
|
34
|
+
|
|
35
|
+
- Nothing — the session ran without user corrections or redirections.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 123
|
|
3
|
+
issue_title: "refactor(pi-subagents): remove vi.fn() cast smell from test helpers"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #123 — remove vi.fn() cast smell from test helpers
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-22T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned and implemented removal of all 9 `as ReturnType<typeof vi.fn>` casts and 5 `vi.mocked()` calls across 3 test files, replacing them with named typed mock variables.
|
|
13
|
+
Released as `pi-subagents-v6.8.1`.
|
|
14
|
+
Pure test hygiene — no production code changes, no behavioral changes, 652 tests unchanged.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
#### What went well
|
|
19
|
+
|
|
20
|
+
- **Three-file scope executed cleanly once the plan was right.**
|
|
21
|
+
Each file was an independent commit with no cross-file dependencies.
|
|
22
|
+
The named-variable pattern (`const mockGetRecord = vi.fn<AgentManagerLike["getRecord"]>()`) worked identically across all three test files despite different mock construction styles (factory function vs `beforeEach` assignment).
|
|
23
|
+
|
|
24
|
+
#### What caused friction (agent side)
|
|
25
|
+
|
|
26
|
+
- `scope-drift` — The initial plan scoped the fix to `service-adapter.test.ts` only, listing `lifecycle.test.ts` and `tool-start.test.ts` as explicit Non-Goals — despite the planning-phase grep showing all 9 cast sites across 3 files.
|
|
27
|
+
The user redirected with "let's eliminate this pattern of behavior."
|
|
28
|
+
Updated the GitHub issue body, rewrote the plan, and amended the commit.
|
|
29
|
+
Impact: plan rewrite (~2 minutes), no implementation rework.
|
|
30
|
+
User-caught.
|
|
31
|
+
- `missing-context` — Imported `MockInstance` from `vitest` during step 1 (`service-adapter.test.ts`) but actually used `ReturnType<typeof vi.fn<...>>` — matching the pattern used in the other two files.
|
|
32
|
+
The unused import was caught by `pnpm run lint` at the post-implementation check, not proactively.
|
|
33
|
+
Impact: one extra edit + amend cycle.
|
|
34
|
+
Self-identified (via lint).
|
|
35
|
+
- `other` — Ran `git commit --amend` to fix the unused import but HEAD was step 3's commit (`tool-start`), not step 1's (`service-adapter`).
|
|
36
|
+
The amend landed the `service-adapter.test.ts` change into the wrong commit with the wrong message.
|
|
37
|
+
Required `git reset --soft` back to the plan commit and recommitting all 3 files.
|
|
38
|
+
Impact: ~1 minute of git surgery, clean result.
|
|
39
|
+
Self-identified.
|
|
40
|
+
|
|
41
|
+
#### What caused friction (user side)
|
|
42
|
+
|
|
43
|
+
- The issue body scoped the fix to `service-adapter.test.ts` only, which the agent followed literally.
|
|
44
|
+
The broader intent ("eliminate this pattern") was implicit.
|
|
45
|
+
Flagging the desired scope as "all files with this pattern" in the issue body would have avoided the plan rewrite.
|
|
46
|
+
|
|
47
|
+
### Changes made
|
|
48
|
+
|
|
49
|
+
1. Wrote retro file at `packages/pi-subagents/docs/retro/0123-remove-vi-fn-cast-smell.md`.
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -13,7 +13,6 @@ 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 type { ExecutionState } from "./execution-state.js";
|
|
17
16
|
import { buildParentSnapshot } from "./parent-snapshot.js";
|
|
18
17
|
import { subscribeRecordObserver } from "./record-observer.js";
|
|
19
18
|
import type { RunConfig } from "./runtime.js";
|
|
@@ -21,11 +20,15 @@ import type { AgentInvocation, IsolationMode, ParentSnapshot, ShellExec, Subagen
|
|
|
21
20
|
import type { WorktreeManager } from "./worktree.js";
|
|
22
21
|
import { WorktreeState } from "./worktree-state.js";
|
|
23
22
|
|
|
24
|
-
export type OnAgentComplete = (record: AgentRecord) => void;
|
|
25
|
-
export type OnAgentStart = (record: AgentRecord) => void;
|
|
26
|
-
export type OnAgentCompact = (record: AgentRecord, info: CompactionInfo) => void;
|
|
27
23
|
export type CompactionInfo = { reason: "manual" | "threshold" | "overflow"; tokensBefore: number };
|
|
28
24
|
|
|
25
|
+
/** Observer interface for agent lifecycle notifications. */
|
|
26
|
+
export interface AgentManagerObserver {
|
|
27
|
+
onAgentStarted(record: AgentRecord): void;
|
|
28
|
+
onAgentCompleted(record: AgentRecord): void;
|
|
29
|
+
onAgentCompacted(record: AgentRecord, info: CompactionInfo): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
29
32
|
/** Default max concurrent background agents. */
|
|
30
33
|
const DEFAULT_MAX_CONCURRENT = 4;
|
|
31
34
|
|
|
@@ -37,19 +40,17 @@ export interface AgentManagerOptions {
|
|
|
37
40
|
/** Injected getter for the concurrency limit — owned by SettingsManager. */
|
|
38
41
|
getMaxConcurrent?: () => number;
|
|
39
42
|
getRunConfig?: () => RunConfig;
|
|
40
|
-
|
|
41
|
-
onComplete?: OnAgentComplete;
|
|
42
|
-
onCompact?: OnAgentCompact;
|
|
43
|
+
observer?: AgentManagerObserver;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
interface SpawnArgs {
|
|
46
47
|
snapshot: ParentSnapshot;
|
|
47
48
|
type: SubagentType;
|
|
48
49
|
prompt: string;
|
|
49
|
-
options:
|
|
50
|
+
options: AgentSpawnConfig;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
export interface
|
|
53
|
+
export interface AgentSpawnConfig {
|
|
53
54
|
description: string;
|
|
54
55
|
model?: Model<any>;
|
|
55
56
|
maxTurns?: number;
|
|
@@ -80,9 +81,7 @@ export interface SpawnOptions {
|
|
|
80
81
|
export class AgentManager {
|
|
81
82
|
private agents = new Map<string, AgentRecord>();
|
|
82
83
|
private cleanupInterval: ReturnType<typeof setInterval>;
|
|
83
|
-
private
|
|
84
|
-
private onStart?: OnAgentStart;
|
|
85
|
-
private onCompact?: OnAgentCompact;
|
|
84
|
+
private readonly observer?: AgentManagerObserver;
|
|
86
85
|
private readonly runner: AgentRunner;
|
|
87
86
|
private readonly worktrees: WorktreeManager;
|
|
88
87
|
private readonly exec: ShellExec;
|
|
@@ -102,9 +101,7 @@ export class AgentManager {
|
|
|
102
101
|
this.worktrees = options.worktrees;
|
|
103
102
|
this.exec = options.exec;
|
|
104
103
|
this.registry = options.registry;
|
|
105
|
-
this.
|
|
106
|
-
this.onStart = options.onStart;
|
|
107
|
-
this.onCompact = options.onCompact;
|
|
104
|
+
this.observer = options.observer;
|
|
108
105
|
this.getRunConfig = options.getRunConfig;
|
|
109
106
|
this._getMaxConcurrent = options.getMaxConcurrent ?? (() => DEFAULT_MAX_CONCURRENT);
|
|
110
107
|
// Cleanup completed agents after 10 minutes (but keep sessions for resume)
|
|
@@ -141,7 +138,7 @@ export class AgentManager {
|
|
|
141
138
|
ctx: ExtensionContext,
|
|
142
139
|
type: SubagentType,
|
|
143
140
|
prompt: string,
|
|
144
|
-
options:
|
|
141
|
+
options: AgentSpawnConfig,
|
|
145
142
|
): string {
|
|
146
143
|
const id = randomUUID().slice(0, 17);
|
|
147
144
|
const abortController = new AbortController();
|
|
@@ -196,7 +193,7 @@ export class AgentManager {
|
|
|
196
193
|
|
|
197
194
|
record.markRunning(Date.now());
|
|
198
195
|
if (options.isBackground) this.runningBackground++;
|
|
199
|
-
this.
|
|
196
|
+
this.observer?.onAgentStarted(record);
|
|
200
197
|
|
|
201
198
|
// Wire parent abort signal to stop the subagent when the parent is interrupted
|
|
202
199
|
let detachParentSignal: (() => void) | undefined;
|
|
@@ -239,7 +236,7 @@ export class AgentManager {
|
|
|
239
236
|
}
|
|
240
237
|
// Subscribe record observer for stats accumulation
|
|
241
238
|
unsubRecordObserver = subscribeRecordObserver(session, record, {
|
|
242
|
-
onCompact: (r, info) => this.
|
|
239
|
+
onCompact: (r, info) => this.observer?.onAgentCompacted(r, info),
|
|
243
240
|
});
|
|
244
241
|
options.onSessionCreated?.(session);
|
|
245
242
|
},
|
|
@@ -268,7 +265,7 @@ export class AgentManager {
|
|
|
268
265
|
|
|
269
266
|
if (options.isBackground) {
|
|
270
267
|
this.runningBackground--;
|
|
271
|
-
try { this.
|
|
268
|
+
try { this.observer?.onAgentCompleted(record); } catch (err) { debugLog("onAgentCompleted observer", err); }
|
|
272
269
|
this.drainQueue();
|
|
273
270
|
}
|
|
274
271
|
return responseText;
|
|
@@ -290,7 +287,7 @@ export class AgentManager {
|
|
|
290
287
|
|
|
291
288
|
if (options.isBackground) {
|
|
292
289
|
this.runningBackground--;
|
|
293
|
-
this.
|
|
290
|
+
this.observer?.onAgentCompleted(record);
|
|
294
291
|
this.drainQueue();
|
|
295
292
|
}
|
|
296
293
|
return "";
|
|
@@ -311,7 +308,7 @@ export class AgentManager {
|
|
|
311
308
|
// Late failure (e.g. strict worktree-isolation) — surface on the record
|
|
312
309
|
// so the user/agent can see it via /agents, then keep draining.
|
|
313
310
|
record.markError(err);
|
|
314
|
-
this.
|
|
311
|
+
this.observer?.onAgentCompleted(record);
|
|
315
312
|
}
|
|
316
313
|
}
|
|
317
314
|
}
|
|
@@ -324,7 +321,7 @@ export class AgentManager {
|
|
|
324
321
|
ctx: ExtensionContext,
|
|
325
322
|
type: SubagentType,
|
|
326
323
|
prompt: string,
|
|
327
|
-
options: Omit<
|
|
324
|
+
options: Omit<AgentSpawnConfig, "isBackground">,
|
|
328
325
|
): Promise<AgentRecord> {
|
|
329
326
|
const id = this.spawn(ctx, type, prompt, { ...options, isBackground: false });
|
|
330
327
|
const record = this.agents.get(id)!;
|
|
@@ -347,7 +344,7 @@ export class AgentManager {
|
|
|
347
344
|
record.resetForResume(Date.now());
|
|
348
345
|
|
|
349
346
|
const unsubResume = subscribeRecordObserver(session, record, {
|
|
350
|
-
onCompact: (r, info) => this.
|
|
347
|
+
onCompact: (r, info) => this.observer?.onAgentCompacted(r, info),
|
|
351
348
|
});
|
|
352
349
|
|
|
353
350
|
try {
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { defineTool, type ExtensionAPI, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { AgentManager } from "./agent-manager.js";
|
|
15
|
+
import { AgentManager, type AgentManagerObserver } from "./agent-manager.js";
|
|
16
16
|
import { getAgentConversation, resumeAgent, runAgent, steerAgent } from "./agent-runner.js";
|
|
17
17
|
import { AgentTypeRegistry } from "./agent-types.js";
|
|
18
18
|
import { loadCustomAgents } from "./custom-agents.js";
|
|
@@ -64,14 +64,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
64
64
|
});
|
|
65
65
|
settings.load();
|
|
66
66
|
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
// Observer: receives agent lifecycle notifications and dispatches events/notifications.
|
|
68
|
+
const observer: AgentManagerObserver = {
|
|
69
|
+
onAgentStarted(record) {
|
|
70
|
+
// Emit started event when agent transitions to running (including from queue).
|
|
71
|
+
pi.events.emit("subagents:started", {
|
|
72
|
+
id: record.id,
|
|
73
|
+
type: record.type,
|
|
74
|
+
description: record.description,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
onAgentCompleted(record) {
|
|
78
|
+
// Emit lifecycle event based on terminal status.
|
|
75
79
|
const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
|
|
76
80
|
const eventData = buildEventData(record);
|
|
77
81
|
if (isError) {
|
|
@@ -80,14 +84,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
80
84
|
pi.events.emit("subagents:completed", eventData);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
// Persist final record for cross-extension history reconstruction
|
|
87
|
+
// Persist final record for cross-extension history reconstruction.
|
|
84
88
|
pi.appendEntry("subagents:record", {
|
|
85
89
|
id: record.id, type: record.type, description: record.description,
|
|
86
90
|
status: record.status, result: record.result, error: record.error,
|
|
87
91
|
startedAt: record.startedAt, completedAt: record.completedAt,
|
|
88
92
|
});
|
|
89
93
|
|
|
90
|
-
// Skip notification if result was already consumed via get_subagent_result
|
|
94
|
+
// Skip notification if result was already consumed via get_subagent_result.
|
|
91
95
|
if (record.notification?.resultConsumed) {
|
|
92
96
|
notifications.cleanupCompleted(record.id);
|
|
93
97
|
return;
|
|
@@ -95,15 +99,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
95
99
|
|
|
96
100
|
notifications.sendCompletion(record);
|
|
97
101
|
},
|
|
98
|
-
|
|
99
|
-
// Emit started event when agent transitions to running (including from queue)
|
|
100
|
-
pi.events.emit("subagents:started", {
|
|
101
|
-
id: record.id,
|
|
102
|
-
type: record.type,
|
|
103
|
-
description: record.description,
|
|
104
|
-
});
|
|
105
|
-
},
|
|
106
|
-
onCompact: (record, info) => {
|
|
102
|
+
onAgentCompacted(record, info) {
|
|
107
103
|
// Emit compacted event when agent's session compacts (preserves count on record).
|
|
108
104
|
pi.events.emit("subagents:compacted", {
|
|
109
105
|
id: record.id,
|
|
@@ -114,6 +110,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
114
110
|
compactionCount: record.compactionCount,
|
|
115
111
|
});
|
|
116
112
|
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const manager = new AgentManager({
|
|
116
|
+
runner: { run: runAgent, resume: resumeAgent },
|
|
117
|
+
worktrees: new GitWorktreeManager(process.cwd()),
|
|
118
|
+
exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
|
|
119
|
+
registry,
|
|
120
|
+
observer,
|
|
117
121
|
getMaxConcurrent: () => settings.maxConcurrent,
|
|
118
122
|
getRunConfig: () => settings,
|
|
119
123
|
});
|
package/src/tools/agent-tool.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
|
-
import type {
|
|
4
|
+
import type { AgentSpawnConfig } from "../agent-manager.js";
|
|
5
5
|
import { normalizeMaxTurns } from "../agent-runner.js";
|
|
6
6
|
import { AgentTypeRegistry } from "../agent-types.js";
|
|
7
7
|
import { resolveAgentInvocationConfig } from "../invocation-config.js";
|
|
@@ -75,8 +75,8 @@ export function buildDetails(
|
|
|
75
75
|
|
|
76
76
|
/** Narrow manager interface — only the methods the Agent tool calls. */
|
|
77
77
|
export interface AgentToolManager {
|
|
78
|
-
spawn: (ctx: ExtensionContext, type: string, prompt: string, opts:
|
|
79
|
-
spawnAndWait: (ctx: ExtensionContext, type: string, prompt: string, opts: Omit<
|
|
78
|
+
spawn: (ctx: ExtensionContext, type: string, prompt: string, opts: AgentSpawnConfig) => string;
|
|
79
|
+
spawnAndWait: (ctx: ExtensionContext, type: string, prompt: string, opts: Omit<AgentSpawnConfig, "isBackground">) => Promise<AgentRecord>;
|
|
80
80
|
resume: (id: string, prompt: string, signal: AbortSignal) => Promise<AgentRecord | undefined>;
|
|
81
81
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
82
82
|
getMaxConcurrent: () => number;
|
package/src/ui/agent-menu.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import type {
|
|
5
|
+
import type { AgentSpawnConfig } from "../agent-manager.js";
|
|
6
6
|
import {
|
|
7
7
|
AgentTypeRegistry,
|
|
8
8
|
BUILTIN_TOOL_NAMES,
|
|
@@ -19,7 +19,7 @@ export interface AgentMenuManager {
|
|
|
19
19
|
listAgents: () => AgentRecord[];
|
|
20
20
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
21
21
|
/** Used by generate wizard to spawn an agent that writes the .md file. */
|
|
22
|
-
spawnAndWait: (ctx: ExtensionContext, type: string, prompt: string, opts: Omit<
|
|
22
|
+
spawnAndWait: (ctx: ExtensionContext, type: string, prompt: string, opts: Omit<AgentSpawnConfig, "isBackground">) => Promise<AgentRecord>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Narrow settings interface required by the agent menu. */
|