@gotgenes/pi-subagents 6.8.2 → 6.9.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 CHANGED
@@ -5,6 +5,29 @@ 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.9.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.3...pi-subagents-v6.9.0) (2026-05-22)
9
+
10
+
11
+ ### Features
12
+
13
+ * add onAgentCreated to AgentManagerObserver ([e2f1c12](https://github.com/gotgenes/pi-packages/commit/e2f1c1273ce560231cc98ef3c7e214efd2c20f47))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * mark D2 done, update field counts in architecture.md ([#114](https://github.com/gotgenes/pi-packages/issues/114)) ([702caf4](https://github.com/gotgenes/pi-packages/commit/702caf4187bfa015b0d80361b06b6360fddc31b1))
19
+ * plan narrow AgentToolDeps and AgentMenuDeps ([#114](https://github.com/gotgenes/pi-packages/issues/114)) ([0f7e953](https://github.com/gotgenes/pi-packages/commit/0f7e95362043109c4113cfe3f7b848e296f118c9))
20
+ * **retro:** add retro notes for issue [#113](https://github.com/gotgenes/pi-packages/issues/113) ([6b3c280](https://github.com/gotgenes/pi-packages/commit/6b3c2800e6d7728b8b620172e4acb1068b65e6c4))
21
+
22
+ ## [6.8.3](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.2...pi-subagents-v6.8.3) (2026-05-22)
23
+
24
+
25
+ ### Documentation
26
+
27
+ * 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))
28
+ * plan disambiguate SpawnOptions (public vs internal) ([#113](https://github.com/gotgenes/pi-packages/issues/113)) ([2f3cebc](https://github.com/gotgenes/pi-packages/commit/2f3cebc6623e0ba20c73145d8e7c6b9ffae6f875))
29
+ * **retro:** add retro notes for issue [#112](https://github.com/gotgenes/pi-packages/issues/112) ([2a59ed4](https://github.com/gotgenes/pi-packages/commit/2a59ed4e4f5462cdbf8df96e4b675a9c5ec6eb9d))
30
+
8
31
  ## [6.8.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.1...pi-subagents-v6.8.2) (2026-05-22)
9
32
 
10
33
 
@@ -377,17 +377,17 @@ Each step is sequenced so it makes the next step easier.
377
377
 
378
378
  ### Current smells
379
379
 
380
- | Smell | Location | Evidence |
381
- | ------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
382
- | ~~Global mutable state~~ | ~~`agent-types.ts`~~ | **Fixed #108**: `AgentTypeRegistry` class; `reloadCustomAgents` callback removed from `AgentToolDeps` and `AgentMenuDeps` |
383
- | Closure bag as class | `createNotificationSystem()` | Returns 4 functions sharing closure state (`pendingNudges`, timers) |
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
- | ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~ | **Fixed #109**: `SettingsManager` class; 6 callback fields collapsed to `settings: SettingsManager`; `AgentMenuDeps` now 8 fields |
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~~ | ~~`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` | Two incompatible shapes (JSON-friendly vs runtime types) with the same name |
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
- | Wide dependency bags | `AgentToolDeps` (7), `AgentMenuDeps` (8) | Settings narrowed (#109); registry narrowed (#108); more narrowing planned in D steps |
380
+ | Smell | Location | Evidence |
381
+ | ------------------------------ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
382
+ | ~~Global mutable state~~ | ~~`agent-types.ts`~~ | **Fixed #108**: `AgentTypeRegistry` class; `reloadCustomAgents` callback removed from `AgentToolDeps` and `AgentMenuDeps` |
383
+ | Closure bag as class | `createNotificationSystem()` | Returns 4 functions sharing closure state (`pendingNudges`, timers) |
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
+ | ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~ | **Fixed #109**: `SettingsManager` class; 6 callback fields collapsed to `settings: SettingsManager`; `AgentMenuDeps` now 8 fields |
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~~ | ~~`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
+ | 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
+ | ~~Wide dependency bags~~ | ~~`AgentToolDeps` (9), `AgentMenuDeps` (8)~~ | **Fixed #114**: `AgentToolDeps` 9 → 6; `AgentMenuDeps` 8 → 7; `emitEvent` removed from both; description text derived from registry; `agentActivity` narrowed to typed interfaces |
391
391
 
392
392
  ### Step A: Extract state into classes (foundation, parallel)
393
393
 
@@ -456,17 +456,23 @@ The record doesn't accumulate half-baked state — it receives fully constructed
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
- Rename the internal `SpawnOptions` in `agent-manager.ts` to `AgentSpawnConfig` (or similar) to distinguish it from the JSON-friendly public `SpawnOptions` in `service.ts`.
462
- The two types serve different consumers and should not share a name.
461
+ **Done.**
462
+ Internal `SpawnOptions` in `agent-manager.ts` renamed to `AgentSpawnConfig`.
463
+ Public `SpawnOptions` in `service.ts` is unchanged.
464
+
465
+ #### D2. Narrow `AgentToolDeps` and `AgentMenuDeps` (#114) ✅
463
466
 
464
- #### D2. Narrow `AgentToolDeps` and `AgentMenuDeps` (#114)
467
+ **Done.**
468
+ `AgentToolDeps` 9 → 6: removed `emitEvent` (moved to `AgentManagerObserver.onAgentCreated`), `typeListText`, `availableTypesText` (derived inside `createAgentTool`); `agentActivity` narrowed to `AgentActivityAccess`.
469
+ `AgentMenuDeps` 8 → 7: removed dead `emitEvent` field; `agentActivity` narrowed to `AgentActivityReader`.
470
+ `buildTypeListText` extracted to `tools/helpers.ts`.
465
471
 
466
- | Bag | Before | After | How |
467
- | --------------- | --------- | ----- | ---------------------------------------------------------------------------------------------------------------------- |
468
- | `AgentToolDeps` | 9 fields | ~5 | Registry owns reload; activity tracker is a collaborator; `emitEvent` moves to observer |
469
- | `AgentMenuDeps` | 13 fields | ~6 | Settings manager absorbs 6 fields (#109); apply methods remove `notifyConcurrencyChanged` (#118); registry owns reload |
472
+ | Bag | Before | After | How |
473
+ | --------------- | -------- | ----- | ------------------------------------------------------------------------------------------------------------- |
474
+ | `AgentToolDeps` | 9 fields | 6 | `emitEvent` observer; `typeListText`/`availableTypesText` derived from registry; `agentActivity` narrowed |
475
+ | `AgentMenuDeps` | 8 fields | 7 | Dead `emitEvent` removed; `agentActivity` narrowed to read-only `AgentActivityReader` |
470
476
 
471
477
  ### Step E: Decompose large files and relocate types (parallel)
472
478
 
@@ -493,8 +499,8 @@ The 654-line file splits along a natural seam.
493
499
  | Closure-bag "classes" | ~~2~~ 1 (`createNotificationSystem`; settings free functions **fixed #109**) | 0 |
494
500
  | Externally-mutated state bags | ~~2~~ ~~1~~ **0** (`AgentRecord` **fixed #111**; `AgentActivity` **fixed #110**) | 0 ✓ |
495
501
  | `AgentManagerOptions` fields | 8 | 5 |
496
- | `AgentToolDeps` fields | ~~9~~ **7** (−6 registry #108, −1 settings #109 +1 settings obj) | ~5 |
497
- | `AgentMenuDeps` fields | ~~13~~ **8** (−6 settings #109 collapsed to 1; −1 registry #108) | ~6 |
502
+ | `AgentToolDeps` fields | ~~9~~ **6** (−3 text fields derived; −1 emitEventobserver; activity narrowed) | 6 ✓ |
503
+ | `AgentMenuDeps` fields | ~~13~~ **7** (−6 settings #109; −1 registry #108; −1 dead emitEvent #114) | 7 |
498
504
  | `SpawnOptions` callback fields | 1 (`onSessionCreated`) | 0 |
499
505
  | Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain | 0 |
500
506
  | Types in `types.ts` without a natural home | 4 | 0 |
@@ -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,279 @@
1
+ ---
2
+ issue: 114
3
+ issue_title: "refactor(pi-subagents): narrow AgentToolDeps and AgentMenuDeps"
4
+ ---
5
+
6
+ # Narrow AgentToolDeps and AgentMenuDeps
7
+
8
+ ## Problem Statement
9
+
10
+ With the foundational extractions complete — `AgentTypeRegistry` (#108), `SettingsManager` (#109), `AgentActivityTracker` (#110), and `AgentManagerObserver` (#112) — the two widest dependency bags in the extension still carry fields that belong on their collaborators.
11
+
12
+ `AgentToolDeps` has 9 fields: 3 are description-text strings derivable from the registry, 1 is a raw `emitEvent` callback that the observer should own, and the `agentActivity` Map is passed as raw mutable state.
13
+ `AgentMenuDeps` has 8 fields: `emitEvent` is defined but never referenced in the module (dead field).
14
+
15
+ Each excess field inflates test mock construction and obscures the real dependency surface.
16
+
17
+ ## Goals
18
+
19
+ - Remove `emitEvent` from both `AgentToolDeps` and `AgentMenuDeps`.
20
+ - Remove `typeListText` and `availableTypesText` from `AgentToolDeps` — derive them from `registry` inside `createAgentTool`.
21
+ - Add `onAgentCreated` to `AgentManagerObserver` so the `subagents:created` event is emitted by the observer, not the tool.
22
+ - Narrow `agentActivity` in both interfaces to a typed read/write interface instead of raw `Map`.
23
+ - Final counts: `AgentToolDeps` 9 → 6, `AgentMenuDeps` 8 → 7.
24
+
25
+ ## Non-Goals
26
+
27
+ - Removing `agentDir` from `AgentToolDeps` — it is not derivable from the registry (it comes from the Pi SDK's `getAgentDir()`).
28
+ - Adding presentation methods to `AgentTypeRegistry` — that would mix SRP concerns.
29
+ - Splitting `agent-tool.ts` foreground/background (tracked in #115).
30
+ - Removing `personalAgentsDir` / `projectAgentsDir` from `AgentMenuDeps` — they are genuine menu-level concerns (file management UI).
31
+
32
+ ## Background
33
+
34
+ ### Prerequisite status
35
+
36
+ All prerequisites are implemented and merged:
37
+
38
+ | Issue | Title | Status |
39
+ | ----- | ---------------------------------------------- | ------- |
40
+ | #108 | Extract `AgentTypeRegistry` | ✅ Done |
41
+ | #109 | Extract `SettingsManager` | ✅ Done |
42
+ | #110 | `AgentActivityTracker` | ✅ Done |
43
+ | #112 | Replace `AgentManager` callbacks with observer | ✅ Done |
44
+ | #113 | Disambiguate `SpawnOptions` | ✅ Done |
45
+ | #118 | `SettingsManager` apply methods | ✅ Done |
46
+
47
+ ### Current interfaces
48
+
49
+ ```typescript
50
+ // tools/agent-tool.ts — 9 fields
51
+ interface AgentToolDeps {
52
+ manager: AgentToolManager;
53
+ widget: AgentToolWidget;
54
+ agentActivity: Map<string, AgentActivityTracker>;
55
+ emitEvent: (name: string, data: unknown) => void;
56
+ registry: AgentTypeRegistry;
57
+ typeListText: string;
58
+ availableTypesText: string;
59
+ agentDir: string;
60
+ settings: { readonly defaultMaxTurns: number | undefined };
61
+ }
62
+
63
+ // ui/agent-menu.ts — 8 fields
64
+ interface AgentMenuDeps {
65
+ manager: AgentMenuManager;
66
+ registry: AgentTypeRegistry;
67
+ agentActivity: Map<string, AgentActivityTracker>;
68
+ getModelLabel: (type: string, registry?: ModelRegistry) => string;
69
+ settings: AgentMenuSettings;
70
+ emitEvent: (name: string, data: unknown) => void; // ← dead field
71
+ personalAgentsDir: string;
72
+ projectAgentsDir: string;
73
+ }
74
+ ```
75
+
76
+ ### Relevant design principles
77
+
78
+ - **Dependency width** (code-design skill): do not pass a shared bag to functions that only use a subset.
79
+ - **Output arguments**: `agentActivity` is a raw `Map` mutated via `.set()` and `.delete()` — encapsulate behind methods.
80
+ - **ISP**: consumers should depend on the narrowest interface they need.
81
+
82
+ ## Design Overview
83
+
84
+ ### 1. Move `subagents:created` to observer
85
+
86
+ The `AgentManagerObserver` interface gains an `onAgentCreated` method.
87
+ `AgentManager.spawn()` calls `observer.onAgentCreated(record)` after creating the record.
88
+ The observer implementation in `index.ts` emits `pi.events.emit("subagents:created", ...)`.
89
+ The tool no longer calls `deps.emitEvent(...)`.
90
+
91
+ The event payload includes `id`, `type`, `description`, and `isBackground`.
92
+ Since `isBackground` is known at spawn-time (it's part of the spawn config), the observer has all needed data.
93
+
94
+ ```typescript
95
+ interface AgentManagerObserver {
96
+ onAgentStarted(record: AgentRecord): void;
97
+ onAgentCompleted(record: AgentRecord): void;
98
+ onAgentCompacted(record: AgentRecord, info: CompactionInfo): void;
99
+ onAgentCreated(record: AgentRecord): void; // ← new
100
+ }
101
+ ```
102
+
103
+ ### 2. Derive description text from registry
104
+
105
+ Move `buildTypeListText()` from `index.ts` to `tools/helpers.ts` as a pure function that accepts the registry and `agentDir`:
106
+
107
+ ```typescript
108
+ function buildTypeListText(
109
+ registry: AgentConfigLookup & { getDefaultAgentNames(): string[]; getUserAgentNames(): string[] },
110
+ agentDir: string,
111
+ ): string;
112
+ ```
113
+
114
+ Inside `createAgentTool`, compute both strings from `deps.registry` and `deps.agentDir`:
115
+
116
+ ```typescript
117
+ const typeListText = buildTypeListText(deps.registry, deps.agentDir);
118
+ const availableTypesText = deps.registry.getAvailableTypes().join(", ");
119
+ ```
120
+
121
+ This removes `typeListText` and `availableTypesText` from the interface.
122
+ The helper requires `getModelLabelFromConfig`, which already lives in `tools/helpers.ts`.
123
+
124
+ ### 3. Narrow agentActivity to a typed interface
125
+
126
+ Instead of `Map<string, AgentActivityTracker>`, both interfaces accept a narrow typed interface matching their actual usage:
127
+
128
+ ```typescript
129
+ /** Read/write interface for agent-tool's activity tracking needs. */
130
+ interface AgentActivityAccess {
131
+ get(id: string): AgentActivityTracker | undefined;
132
+ set(id: string, tracker: AgentActivityTracker): void;
133
+ delete(id: string): void;
134
+ }
135
+ ```
136
+
137
+ The `AgentMenuDeps` only reads (`.get()`), so it gets a narrower read-only type:
138
+
139
+ ```typescript
140
+ /** Read-only interface for menu's conversation viewer. */
141
+ interface AgentActivityReader {
142
+ get(id: string): AgentActivityTracker | undefined;
143
+ }
144
+ ```
145
+
146
+ The runtime's `Map<string, AgentActivityTracker>` satisfies both interfaces structurally.
147
+ No wrapper class is needed — `Map` already implements `get/set/delete`.
148
+ The benefit is that the type signature communicates the actual usage pattern.
149
+
150
+ ### 4. Remove dead `emitEvent` from `AgentMenuDeps`
151
+
152
+ This field is defined in the interface but never referenced inside `agent-menu.ts`.
153
+ Remove it from the interface and from the construction site in `index.ts`.
154
+
155
+ ### Final interfaces
156
+
157
+ ```typescript
158
+ // tools/agent-tool.ts — 6 fields (was 9)
159
+ interface AgentToolDeps {
160
+ manager: AgentToolManager;
161
+ widget: AgentToolWidget;
162
+ agentActivity: AgentActivityAccess;
163
+ registry: AgentTypeRegistry;
164
+ agentDir: string;
165
+ settings: { readonly defaultMaxTurns: number | undefined };
166
+ }
167
+
168
+ // ui/agent-menu.ts — 7 fields (was 8)
169
+ interface AgentMenuDeps {
170
+ manager: AgentMenuManager;
171
+ registry: AgentTypeRegistry;
172
+ agentActivity: AgentActivityReader;
173
+ getModelLabel: (type: string, registry?: ModelRegistry) => string;
174
+ settings: AgentMenuSettings;
175
+ personalAgentsDir: string;
176
+ projectAgentsDir: string;
177
+ }
178
+ ```
179
+
180
+ ## Module-Level Changes
181
+
182
+ ### Modified files
183
+
184
+ 1. **`src/agent-manager.ts`** — Call `observer.onAgentCreated(record)` in `spawn()` after creating the record.
185
+ Update `AgentManagerObserver` interface to include `onAgentCreated`.
186
+
187
+ 2. **`src/tools/agent-tool.ts`** — Remove `emitEvent`, `typeListText`, `availableTypesText` from `AgentToolDeps`.
188
+ Add `AgentActivityAccess` interface.
189
+ Derive description text from `registry` + `agentDir` inside `createAgentTool`.
190
+ Remove `deps.emitEvent(...)` call.
191
+ Replace `Map<string, AgentActivityTracker>` with `AgentActivityAccess`.
192
+
193
+ 3. **`src/tools/helpers.ts`** — Add `buildTypeListText(registry, agentDir)` extracted from `index.ts`.
194
+
195
+ 4. **`src/ui/agent-menu.ts`** — Remove `emitEvent` from `AgentMenuDeps`.
196
+ Add `AgentActivityReader` interface.
197
+ Replace `Map<string, AgentActivityTracker>` with `AgentActivityReader`.
198
+
199
+ 5. **`src/index.ts`** — Remove `buildTypeListText` closure.
200
+ Remove `typeListText`, `availableTypesText`, `emitEvent` from the `AgentToolDeps` construction.
201
+ Remove `emitEvent` from the `AgentMenuDeps` construction.
202
+ Add `onAgentCreated` to the observer object.
203
+
204
+ 6. **`test/tools/agent-tool.test.ts`** — Update `makeDeps` factory: remove `emitEvent`, `typeListText`, `availableTypesText`.
205
+ Update assertions that check `emitEvent` was called.
206
+
207
+ 7. **`test/ui/agent-menu.test.ts`** — Update `makeDeps` factory: remove `emitEvent`.
208
+
209
+ 8. **`test/agent-manager.test.ts`** — Add test for `observer.onAgentCreated` being called during spawn.
210
+
211
+ ## Test Impact Analysis
212
+
213
+ 1. **New tests enabled**: `observer.onAgentCreated` can be tested in `agent-manager.test.ts` — verify it fires during `spawn()` with the correct record.
214
+
215
+ 2. **Tests that simplify**: `agent-tool.test.ts` mock factory drops 3 fields (`emitEvent`, `typeListText`, `availableTypesText`).
216
+ Tests asserting `deps.emitEvent` was called become assertions that the observer was invoked (tested at the manager level instead).
217
+ `agent-menu.test.ts` mock factory drops 1 field (`emitEvent`).
218
+
219
+ 3. **Tests that stay**: all tool-behavior tests (spawn paths, resume, error handling) remain — they test the tool's own logic, not the narrowed plumbing.
220
+
221
+ ## TDD Order
222
+
223
+ 1. **Add `onAgentCreated` to observer interface and manager.**
224
+ Add the method to `AgentManagerObserver`.
225
+ Call it in `AgentManager.spawn()`.
226
+ Test: verify `observer.onAgentCreated` fires with the created record.
227
+ Commit: `feat: add onAgentCreated to AgentManagerObserver`
228
+
229
+ 2. **Extract `buildTypeListText` to helpers.**
230
+ Move the function from `index.ts` to `tools/helpers.ts` as a pure function.
231
+ Test: unit test `buildTypeListText` with a mock registry.
232
+ Update `index.ts` to import and call the extracted helper.
233
+ Commit: `refactor: extract buildTypeListText to tools/helpers`
234
+
235
+ 3. **Derive description text inside `createAgentTool`.**
236
+ Remove `typeListText` and `availableTypesText` from `AgentToolDeps`.
237
+ Compute them from `deps.registry` + `deps.agentDir` inside `createAgentTool`.
238
+ Update `index.ts` construction site.
239
+ Update `agent-tool.test.ts` mock factory.
240
+ Test: verify tool description still contains expected text.
241
+ Commit: `refactor: derive description text from registry in createAgentTool`
242
+
243
+ 4. **Remove `emitEvent` from `AgentToolDeps`.**
244
+ Remove the field from the interface.
245
+ Remove the `deps.emitEvent(...)` call in the background spawn path.
246
+ Wire `onAgentCreated` in the `index.ts` observer to emit `subagents:created`.
247
+ Update `agent-tool.test.ts` (remove `emitEvent` from factory, remove/update related assertions).
248
+ Commit: `refactor: remove emitEvent from AgentToolDeps`
249
+
250
+ 5. **Remove dead `emitEvent` from `AgentMenuDeps`.**
251
+ Remove the field from the interface.
252
+ Remove from `index.ts` construction site.
253
+ Update `agent-menu.test.ts` mock factory.
254
+ Commit: `refactor: remove dead emitEvent from AgentMenuDeps`
255
+
256
+ 6. **Narrow `agentActivity` to typed interfaces.**
257
+ Add `AgentActivityAccess` interface to `agent-tool.ts`.
258
+ Add `AgentActivityReader` interface to `agent-menu.ts`.
259
+ Replace `Map<string, AgentActivityTracker>` with the narrow type in both interfaces.
260
+ Update test factories (no functional change — `Map` satisfies both interfaces).
261
+ Commit: `refactor: narrow agentActivity to typed interfaces`
262
+
263
+ ## Risks and Mitigations
264
+
265
+ | Risk | Mitigation |
266
+ | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
267
+ | `subagents:created` event data changes shape when moved to observer | Observer receives the full `AgentRecord` which contains `id`, `type`, `description`. `isBackground` must be derivable from the record or spawn config — verify the record carries this at spawn time. |
268
+ | `buildTypeListText` depends on `getModelLabelFromConfig` which is in `tools/helpers.ts` | This is already the target file — no circular dependency risk. |
269
+ | Third-party consumers rely on `emitEvent` presence in `AgentToolDeps` | `AgentToolDeps` is not in the public `exports` — it's internal. No external API breakage. |
270
+ | `AgentActivityAccess` interface is structurally identical to a subset of `Map` | This is intentional — the `Map` satisfies it without a wrapper. If a non-Map implementation is needed later, the interface is already in place. |
271
+
272
+ ## Open Questions
273
+
274
+ 1. Should `onAgentCreated` receive the full `AgentRecord` or a narrow payload?
275
+ The other observer methods receive `AgentRecord` — consistency suggests the same.
276
+ The `isBackground` flag is needed for the event payload; verify it's available on the record at creation time (it should be, since background is set at spawn).
277
+ 2. Should `steer-tool.ts`'s `emitEvent` (emits `subagents:steered`) also move to the observer?
278
+ It's a separate concern (tool-level steering) and a different event.
279
+ Defer to a follow-up if desired.
@@ -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,29 @@
1
+ ---
2
+ issue: 113
3
+ issue_title: "refactor(pi-subagents): disambiguate SpawnOptions (public vs internal)"
4
+ ---
5
+
6
+ # Retro: #113 — disambiguate SpawnOptions (public vs internal)
7
+
8
+ ## Final Retrospective (2026-05-21T21:10:00-04:00)
9
+
10
+ ### Session summary
11
+
12
+ Renamed the internal `SpawnOptions` in `agent-manager.ts` to `AgentSpawnConfig` to disambiguate it from the public `SpawnOptions` in `service.ts`.
13
+ Pure mechanical rename across 4 files with zero test-count delta (652/652).
14
+ Released as `pi-subagents-v6.8.3`.
15
+
16
+ ### Observations
17
+
18
+ #### What went well
19
+
20
+ - Completely frictionless execution — single-step plan executed exactly as written with no corrections, rework, or failed edits.
21
+ - The session benefited from context already loaded during the preceding #112 cycle (same package, same skills, same source files), which made planning and execution faster.
22
+
23
+ #### What caused friction (agent side)
24
+
25
+ - Nothing — the rename was purely mechanical and the plan matched reality exactly.
26
+
27
+ #### What caused friction (user side)
28
+
29
+ - Nothing — no user intervention needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "6.8.2",
3
+ "version": "6.9.0",
4
4
  "exports": {
5
5
  ".": "./src/service.ts"
6
6
  },
@@ -27,6 +27,8 @@ export interface AgentManagerObserver {
27
27
  onAgentStarted(record: AgentRecord): void;
28
28
  onAgentCompleted(record: AgentRecord): void;
29
29
  onAgentCompacted(record: AgentRecord, info: CompactionInfo): void;
30
+ /** Fires synchronously after a background agent record is created (before startAgent). */
31
+ onAgentCreated(record: AgentRecord): void;
30
32
  }
31
33
 
32
34
  /** Default max concurrent background agents. */
@@ -47,10 +49,10 @@ interface SpawnArgs {
47
49
  snapshot: ParentSnapshot;
48
50
  type: SubagentType;
49
51
  prompt: string;
50
- options: SpawnOptions;
52
+ options: AgentSpawnConfig;
51
53
  }
52
54
 
53
- export interface SpawnOptions {
55
+ export interface AgentSpawnConfig {
54
56
  description: string;
55
57
  model?: Model<any>;
56
58
  maxTurns?: number;
@@ -138,7 +140,7 @@ export class AgentManager {
138
140
  ctx: ExtensionContext,
139
141
  type: SubagentType,
140
142
  prompt: string,
141
- options: SpawnOptions,
143
+ options: AgentSpawnConfig,
142
144
  ): string {
143
145
  const id = randomUUID().slice(0, 17);
144
146
  const abortController = new AbortController();
@@ -153,6 +155,10 @@ export class AgentManager {
153
155
  });
154
156
  this.agents.set(id, record);
155
157
 
158
+ if (options.isBackground) {
159
+ this.observer?.onAgentCreated(record);
160
+ }
161
+
156
162
  const snapshot = buildParentSnapshot(ctx, options.inheritContext);
157
163
  const args: SpawnArgs = { snapshot, type, prompt, options };
158
164
 
@@ -321,7 +327,7 @@ export class AgentManager {
321
327
  ctx: ExtensionContext,
322
328
  type: SubagentType,
323
329
  prompt: string,
324
- options: Omit<SpawnOptions, "isBackground">,
330
+ options: Omit<AgentSpawnConfig, "isBackground">,
325
331
  ): Promise<AgentRecord> {
326
332
  const id = this.spawn(ctx, type, prompt, { ...options, isBackground: false });
327
333
  const record = this.agents.get(id)!;
package/src/index.ts CHANGED
@@ -110,6 +110,15 @@ export default function (pi: ExtensionAPI) {
110
110
  compactionCount: record.compactionCount,
111
111
  });
112
112
  },
113
+ onAgentCreated(record) {
114
+ // Emit created event for background agents (before startAgent / queue drain).
115
+ pi.events.emit("subagents:created", {
116
+ id: record.id,
117
+ type: record.type,
118
+ description: record.description,
119
+ isBackground: true,
120
+ });
121
+ },
113
122
  };
114
123
 
115
124
  const manager = new AgentManager({
@@ -151,33 +160,6 @@ export default function (pi: ExtensionAPI) {
151
160
  const toolStart = new ToolStartHandler(runtime);
152
161
  pi.on("tool_execution_start", (event, ctx) => toolStart.handleToolExecutionStart(event, ctx));
153
162
 
154
- /** Build the full type list text dynamically from the unified registry. */
155
- const buildTypeListText = () => {
156
- const defaultNames = registry.getDefaultAgentNames();
157
- const userNames = registry.getUserAgentNames();
158
-
159
- const defaultDescs = defaultNames.map((name) => {
160
- const cfg = registry.resolveAgentConfig(name);
161
- const modelSuffix = cfg.model ? ` (${getModelLabelFromConfig(cfg.model)})` : "";
162
- return `- ${name}: ${cfg.description}${modelSuffix}`;
163
- });
164
-
165
- const customDescs = userNames.map((name) => {
166
- const cfg = registry.resolveAgentConfig(name);
167
- return `- ${name}: ${cfg.description}`;
168
- });
169
-
170
- return [
171
- "Default agents:",
172
- ...defaultDescs,
173
- ...(customDescs.length > 0 ? ["", "Custom agents:", ...customDescs] : []),
174
- "",
175
- `Custom agents can be defined in .pi/agents/<name>.md (project) or ${getAgentDir()}/agents/<name>.md (global) — they are picked up automatically. Project-level agents override global ones. Creating a .md file with the same name as a default agent overrides it.`,
176
- ].join("\n");
177
- };
178
-
179
- const typeListText = buildTypeListText();
180
-
181
163
  // ---- Agent tool ----
182
164
 
183
165
  pi.registerTool(defineTool(createAgentTool({
@@ -196,10 +178,7 @@ export default function (pi: ExtensionAPI) {
196
178
  markFinished: (id) => runtime.markFinished(id),
197
179
  },
198
180
  agentActivity: runtime.agentActivity,
199
- emitEvent: (name, data) => pi.events.emit(name, data),
200
181
  registry,
201
- typeListText,
202
- availableTypesText: registry.getAvailableTypes().join(", "),
203
182
  agentDir: getAgentDir(),
204
183
  settings,
205
184
  })));
@@ -242,7 +221,6 @@ export default function (pi: ExtensionAPI) {
242
221
  return getModelLabelFromConfig(cfg.model);
243
222
  },
244
223
  settings,
245
- emitEvent: (name, data) => pi.events.emit(name, data),
246
224
  personalAgentsDir: join(getAgentDir(), 'agents'),
247
225
  projectAgentsDir: join(process.cwd(), '.pi', 'agents'),
248
226
  });
@@ -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 { SpawnOptions } from "../agent-manager.js";
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";
@@ -23,7 +23,7 @@ import {
23
23
  } from "../ui/agent-widget.js";
24
24
  import { subscribeUIObserver } from "../ui/ui-observer.js";
25
25
  import type { LifetimeUsage } from "../usage.js";
26
- import { formatLifetimeTokens, textResult } from "./helpers.js";
26
+ import { buildTypeListText, formatLifetimeTokens, textResult } from "./helpers.js";
27
27
 
28
28
  // ---- Agent-tool-specific helpers ----
29
29
 
@@ -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: SpawnOptions) => string;
79
- spawnAndWait: (ctx: ExtensionContext, type: string, prompt: string, opts: Omit<SpawnOptions, "isBackground">) => Promise<AgentRecord>;
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;
@@ -91,14 +91,21 @@ export interface AgentToolWidget {
91
91
  markFinished: (id: string) => void;
92
92
  }
93
93
 
94
+ /**
95
+ * Narrow read/write interface for the agent-tool's agentActivity access.
96
+ * The full Map satisfies this structurally — no wrapper needed.
97
+ */
98
+ export interface AgentActivityAccess {
99
+ get(id: string): AgentActivityTracker | undefined;
100
+ set(id: string, tracker: AgentActivityTracker): void;
101
+ delete(id: string): void;
102
+ }
103
+
94
104
  export interface AgentToolDeps {
95
105
  manager: AgentToolManager;
96
106
  widget: AgentToolWidget;
97
- agentActivity: Map<string, AgentActivityTracker>;
98
- emitEvent: (name: string, data: unknown) => void;
107
+ agentActivity: AgentActivityAccess;
99
108
  registry: AgentTypeRegistry;
100
- typeListText: string;
101
- availableTypesText: string;
102
109
  agentDir: string;
103
110
  /** Narrow settings accessor — only the default max turns is needed here. */
104
111
  settings: { readonly defaultMaxTurns: number | undefined };
@@ -108,6 +115,8 @@ export interface AgentToolDeps {
108
115
 
109
116
  /** Create the Agent tool definition (without Pi SDK wrapper). */
110
117
  export function createAgentTool(deps: AgentToolDeps) {
118
+ const typeListText = buildTypeListText(deps.registry, deps.agentDir);
119
+ const availableTypesText = deps.registry.getAvailableTypes().join(", ");
111
120
  return {
112
121
  name: "Agent" as const,
113
122
  label: "Agent",
@@ -116,7 +125,7 @@ export function createAgentTool(deps: AgentToolDeps) {
116
125
  The Agent tool launches specialized agents that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
117
126
 
118
127
  Available agent types:
119
- ${deps.typeListText}
128
+ ${typeListText}
120
129
 
121
130
  Guidelines:
122
131
  - For parallel work, use run_in_background: true on each agent. Foreground calls run sequentially — only one executes at a time.
@@ -140,7 +149,7 @@ Guidelines:
140
149
  description: "A short (3-5 word) description of the task (shown in UI).",
141
150
  }),
142
151
  subagent_type: Type.String({
143
- description: `The type of specialized agent to use. Available types: ${deps.availableTypesText}. Custom agents from .pi/agents/<name>.md (project) or ${deps.agentDir}/agents/<name>.md (global) are also available.`,
152
+ description: `The type of specialized agent to use. Available types: ${availableTypesText}. Custom agents from .pi/agents/<name>.md (project) or ${deps.agentDir}/agents/<name>.md (global) are also available.`,
144
153
  }),
145
154
  model: Type.Optional(
146
155
  Type.String({
@@ -439,14 +448,6 @@ Guidelines:
439
448
  deps.widget.ensureTimer();
440
449
  deps.widget.update();
441
450
 
442
- // Emit created event
443
- deps.emitEvent("subagents:created", {
444
- id,
445
- type: subagentType,
446
- description: params.description,
447
- isBackground: true,
448
- });
449
-
450
451
  const isQueued = record?.status === "queued";
451
452
  return textResult(
452
453
  `Agent ${isQueued ? "queued" : "started"} in background.\n` +
@@ -1,3 +1,4 @@
1
+ import type { AgentConfigLookup } from "../agent-types.js";
1
2
  import { formatTokens } from "../ui/agent-widget.js";
2
3
  import { getLifetimeTotal, type LifetimeUsage } from "../usage.js";
3
4
 
@@ -12,6 +13,43 @@ export function formatLifetimeTokens(o: { lifetimeUsage: LifetimeUsage }): strin
12
13
  return t > 0 ? formatTokens(t) : "";
13
14
  }
14
15
 
16
+ /**
17
+ * Narrow registry interface needed by buildTypeListText.
18
+ * Extends AgentConfigLookup with the two name-listing methods.
19
+ */
20
+ export interface TypeListRegistry extends AgentConfigLookup {
21
+ getDefaultAgentNames(): string[];
22
+ getUserAgentNames(): string[];
23
+ }
24
+
25
+ /**
26
+ * Build the full agent-type list text for the Agent tool description.
27
+ * Extracted from index.ts so it can be called inside createAgentTool.
28
+ */
29
+ export function buildTypeListText(registry: TypeListRegistry, agentDir: string): string {
30
+ const defaultNames = registry.getDefaultAgentNames();
31
+ const userNames = registry.getUserAgentNames();
32
+
33
+ const defaultDescs = defaultNames.map((name) => {
34
+ const cfg = registry.resolveAgentConfig(name);
35
+ const modelSuffix = cfg.model ? ` (${getModelLabelFromConfig(cfg.model)})` : "";
36
+ return `- ${name}: ${cfg.description}${modelSuffix}`;
37
+ });
38
+
39
+ const customDescs = userNames.map((name) => {
40
+ const cfg = registry.resolveAgentConfig(name);
41
+ return `- ${name}: ${cfg.description}`;
42
+ });
43
+
44
+ return [
45
+ "Default agents:",
46
+ ...defaultDescs,
47
+ ...(customDescs.length > 0 ? ["", "Custom agents:", ...customDescs] : []),
48
+ "",
49
+ `Custom agents can be defined in .pi/agents/<name>.md (project) or ${agentDir}/agents/<name>.md (global) — they are picked up automatically. Project-level agents override global ones. Creating a .md file with the same name as a default agent overrides it.`,
50
+ ].join("\n");
51
+ }
52
+
15
53
  /** Derive a short model label from a model string. */
16
54
  export function getModelLabelFromConfig(model: string): string {
17
55
  // Strip provider prefix (e.g. "anthropic/claude-sonnet-4-6" → "claude-sonnet-4-6")
@@ -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 { SpawnOptions } from "../agent-manager.js";
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<SpawnOptions, "isBackground">) => Promise<AgentRecord>;
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. */
@@ -32,15 +32,22 @@ export interface AgentMenuSettings {
32
32
  applyGraceTurns(n: number): { message: string; level: "info" | "warning" };
33
33
  }
34
34
 
35
+ /**
36
+ * Read-only interface for the agent-menu's agentActivity access.
37
+ * Only the conversation viewer needs to read a tracker by agent ID.
38
+ */
39
+ export interface AgentActivityReader {
40
+ get(id: string): AgentActivityTracker | undefined;
41
+ }
42
+
35
43
  export interface AgentMenuDeps {
36
44
  manager: AgentMenuManager;
37
45
  registry: AgentTypeRegistry;
38
- agentActivity: Map<string, AgentActivityTracker>;
46
+ agentActivity: AgentActivityReader;
39
47
  /** Resolve model label for a given agent type + registry. */
40
48
  getModelLabel: (type: string, registry?: ModelRegistry) => string;
41
49
  /** Settings manager — owns in-memory values and persistence. */
42
50
  settings: AgentMenuSettings;
43
- emitEvent: (name: string, data: unknown) => void;
44
51
  personalAgentsDir: string;
45
52
  projectAgentsDir: string;
46
53
  }