@gotgenes/pi-subagents 6.8.3 → 6.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/docs/architecture/architecture.md +39 -28
- package/docs/plans/0114-narrow-agent-tool-menu-deps.md +279 -0
- package/docs/plans/0115-decompose-agent-tool.md +337 -0
- package/docs/retro/0113-disambiguate-spawn-options.md +29 -0
- package/docs/retro/0114-narrow-agent-tool-menu-deps.md +38 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +16 -3
- package/src/index.ts +9 -32
- package/src/tools/agent-tool.ts +48 -215
- package/src/tools/background-spawner.ts +116 -0
- package/src/tools/foreground-runner.ts +175 -0
- package/src/tools/helpers.ts +83 -1
- package/src/ui/agent-menu.ts +9 -2
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.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.0...pi-subagents-v6.9.1) (2026-05-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* plan decompose agent-tool.ts into foreground/background modules ([#115](https://github.com/gotgenes/pi-packages/issues/115)) ([4b9aefb](https://github.com/gotgenes/pi-packages/commit/4b9aefb18fe0431a033d90f10bcbe7d37c53a6b8))
|
|
14
|
+
* **retro:** add retro notes for issue [#114](https://github.com/gotgenes/pi-packages/issues/114) ([e6095e7](https://github.com/gotgenes/pi-packages/commit/e6095e7465f482ac18756305b9740f1101dbd41a))
|
|
15
|
+
* update architecture for E1 agent-tool decomposition ([#115](https://github.com/gotgenes/pi-packages/issues/115)) ([8bccf0a](https://github.com/gotgenes/pi-packages/commit/8bccf0ab90787d906c82c5a362c0763d3b7bd2f5))
|
|
16
|
+
|
|
17
|
+
## [6.9.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.3...pi-subagents-v6.9.0) (2026-05-22)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* add onAgentCreated to AgentManagerObserver ([e2f1c12](https://github.com/gotgenes/pi-packages/commit/e2f1c1273ce560231cc98ef3c7e214efd2c20f47))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
* 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))
|
|
28
|
+
* plan narrow AgentToolDeps and AgentMenuDeps ([#114](https://github.com/gotgenes/pi-packages/issues/114)) ([0f7e953](https://github.com/gotgenes/pi-packages/commit/0f7e95362043109c4113cfe3f7b848e296f118c9))
|
|
29
|
+
* **retro:** add retro notes for issue [#113](https://github.com/gotgenes/pi-packages/issues/113) ([6b3c280](https://github.com/gotgenes/pi-packages/commit/6b3c2800e6d7728b8b620172e4acb1068b65e6c4))
|
|
30
|
+
|
|
8
31
|
## [6.8.3](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.2...pi-subagents-v6.8.3) (2026-05-22)
|
|
9
32
|
|
|
10
33
|
|
|
@@ -54,10 +54,12 @@ settings.ts — persistent operational settings; `SettingsManager`
|
|
|
54
54
|
service.ts — SubagentsService interface + Symbol.for() accessors
|
|
55
55
|
service-adapter.ts — SubagentsService implementation wrapping AgentManager
|
|
56
56
|
|
|
57
|
-
tools/agent-tool.ts
|
|
58
|
-
tools/
|
|
59
|
-
tools/
|
|
60
|
-
tools/
|
|
57
|
+
tools/agent-tool.ts — Agent tool definition, parameter validation, dispatch
|
|
58
|
+
tools/foreground-runner.ts — foreground execution loop (spinner, streaming, result)
|
|
59
|
+
tools/background-spawner.ts — background spawn (activity setup, notification wiring)
|
|
60
|
+
tools/get-result-tool.ts — get_subagent_result tool
|
|
61
|
+
tools/steer-tool.ts — steer_subagent tool
|
|
62
|
+
tools/helpers.ts — shared tool utilities (textResult, buildDetails, getStatusNote, …)
|
|
61
63
|
|
|
62
64
|
handlers/lifecycle.ts — session_start, session_before_switch, session_shutdown
|
|
63
65
|
handlers/tool-start.ts — tool_execution_start handler
|
|
@@ -259,10 +261,12 @@ src/
|
|
|
259
261
|
├── index.ts — slimmed entry point: init, tool registration
|
|
260
262
|
├── runtime.ts — SubagentRuntime: session-scoped state + methods
|
|
261
263
|
├── tools/
|
|
262
|
-
│ ├── agent-tool.ts — Agent tool definition
|
|
264
|
+
│ ├── agent-tool.ts — Agent tool definition, parameter validation, dispatch
|
|
265
|
+
│ ├── foreground-runner.ts — foreground execution loop (spinner, streaming, result)
|
|
266
|
+
│ ├── background-spawner.ts — background spawn (activity setup, notification wiring)
|
|
263
267
|
│ ├── get-result-tool.ts — get_subagent_result tool
|
|
264
268
|
│ ├── steer-tool.ts — steer_subagent tool
|
|
265
|
-
│ └── helpers.ts — shared tool utilities
|
|
269
|
+
│ └── helpers.ts — shared tool utilities (textResult, buildDetails, getStatusNote, …)
|
|
266
270
|
├── handlers/
|
|
267
271
|
│ ├── lifecycle.ts — session_start, session_before_switch, session_shutdown
|
|
268
272
|
│ └── tool-start.ts — tool_execution_start handler
|
|
@@ -377,17 +381,17 @@ Each step is sequenced so it makes the next step easier.
|
|
|
377
381
|
|
|
378
382
|
### Current smells
|
|
379
383
|
|
|
380
|
-
| Smell | Location
|
|
381
|
-
| ------------------------------ |
|
|
382
|
-
| ~~Global mutable state~~ | ~~`agent-types.ts`~~
|
|
383
|
-
| Closure bag as class | `createNotificationSystem()`
|
|
384
|
-
| ~~Mutable state bag~~ | ~~`AgentActivity` (7 fields)~~
|
|
385
|
-
| ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~
|
|
386
|
-
| ~~Post-construction mutation~~ | ~~`AgentRecord` non-transition state~~
|
|
387
|
-
| ~~Fire-and-forget callbacks~~ | ~~`AgentManagerOptions`~~
|
|
388
|
-
| ~~Duplicate `SpawnOptions`~~ | ~~`service.ts` + `agent-manager.ts`~~
|
|
389
|
-
| Type dumping ground | `types.ts`
|
|
390
|
-
| Wide dependency bags
|
|
384
|
+
| Smell | Location | Evidence |
|
|
385
|
+
| ------------------------------ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
386
|
+
| ~~Global mutable state~~ | ~~`agent-types.ts`~~ | **Fixed #108**: `AgentTypeRegistry` class; `reloadCustomAgents` callback removed from `AgentToolDeps` and `AgentMenuDeps` |
|
|
387
|
+
| Closure bag as class | `createNotificationSystem()` | Returns 4 functions sharing closure state (`pendingNudges`, timers) |
|
|
388
|
+
| ~~Mutable state bag~~ | ~~`AgentActivity` (7 fields)~~ | **Fixed #110**: `AgentActivityTracker` class; `ui-observer.ts` calls transition methods; widget, notification, agent-tool use read-only accessors |
|
|
389
|
+
| ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~ | **Fixed #109**: `SettingsManager` class; 6 callback fields collapsed to `settings: SettingsManager`; `AgentMenuDeps` now 8 fields |
|
|
390
|
+
| ~~Post-construction mutation~~ | ~~`AgentRecord` non-transition state~~ | **Fixed #111**: `ExecutionState`, `WorktreeState`, `NotificationState` collaborators; `pendingSteers` moved to `AgentManager`; stats encapsulated behind mutation methods |
|
|
391
|
+
| ~~Fire-and-forget callbacks~~ | ~~`AgentManagerOptions`~~ | **Fixed #112**: `AgentManagerObserver` interface; `observer` replaces 3 callbacks; `index.ts` constructs one observer object instead of 3 closure lambdas |
|
|
392
|
+
| ~~Duplicate `SpawnOptions`~~ | ~~`service.ts` + `agent-manager.ts`~~ | **Fixed #113**: internal type renamed to `AgentSpawnConfig`; public `SpawnOptions` in `service.ts` unchanged |
|
|
393
|
+
| 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 |
|
|
394
|
+
| ~~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
395
|
|
|
392
396
|
### Step A: Extract state into classes (foundation, parallel)
|
|
393
397
|
|
|
@@ -437,7 +441,7 @@ Split post-construction mutation into phase-specific collaborators, each born co
|
|
|
437
441
|
|
|
438
442
|
- **`ExecutionState`** (`session`, `outputFile`) — constructed in `onSessionCreated`, attached as `record.execution`.
|
|
439
443
|
- **`WorktreeState`** (`path`, `branch`, `cleanupResult`) — constructed at worktree setup, attached as `record.worktreeState`.
|
|
440
|
-
- **`NotificationState`** (`toolCallId`, `resultConsumed`) — constructed by
|
|
444
|
+
- **`NotificationState`** (`toolCallId`, `resultConsumed`) — constructed by `AgentManager.spawn()` when `toolCallId` is provided in `AgentSpawnConfig`, attached as `record.notification`.
|
|
441
445
|
- **`pendingSteers`** moved to `Map<string, string[]>` on `AgentManager`; steer-tool and service-adapter call `manager.queueSteer()`.
|
|
442
446
|
- Stats (`toolUses`, `lifetimeUsage`, `compactionCount`) encapsulated behind mutation methods (`incrementToolUses`, `addUsage`, `incrementCompactions`) with read-only getters.
|
|
443
447
|
- `AgentRecordInit` trimmed from 19 optional fields to 4 construction-time fields.
|
|
@@ -462,19 +466,26 @@ With the registry class, settings manager, and observer in place, the dependency
|
|
|
462
466
|
Internal `SpawnOptions` in `agent-manager.ts` renamed to `AgentSpawnConfig`.
|
|
463
467
|
Public `SpawnOptions` in `service.ts` is unchanged.
|
|
464
468
|
|
|
465
|
-
#### D2. Narrow `AgentToolDeps` and `AgentMenuDeps` (#114)
|
|
469
|
+
#### D2. Narrow `AgentToolDeps` and `AgentMenuDeps` (#114) ✅
|
|
466
470
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
+
**Done.**
|
|
472
|
+
`AgentToolDeps` 9 → 6: removed `emitEvent` (moved to `AgentManagerObserver.onAgentCreated`), `typeListText`, `availableTypesText` (derived inside `createAgentTool`); `agentActivity` narrowed to `AgentActivityAccess`.
|
|
473
|
+
`AgentMenuDeps` 8 → 7: removed dead `emitEvent` field; `agentActivity` narrowed to `AgentActivityReader`.
|
|
474
|
+
`buildTypeListText` extracted to `tools/helpers.ts`.
|
|
475
|
+
|
|
476
|
+
| Bag | Before | After | How |
|
|
477
|
+
| --------------- | -------- | ----- | ------------------------------------------------------------------------------------------------------------- |
|
|
478
|
+
| `AgentToolDeps` | 9 fields | 6 | `emitEvent` → observer; `typeListText`/`availableTypesText` derived from registry; `agentActivity` narrowed |
|
|
479
|
+
| `AgentMenuDeps` | 8 fields | 7 | Dead `emitEvent` removed; `agentActivity` narrowed to read-only `AgentActivityReader` |
|
|
471
480
|
|
|
472
481
|
### Step E: Decompose large files and relocate types (parallel)
|
|
473
482
|
|
|
474
|
-
#### E1. Split `agent-tool.ts` foreground/background (#115)
|
|
483
|
+
#### E1. Split `agent-tool.ts` foreground/background (#115) ✅
|
|
475
484
|
|
|
476
|
-
|
|
477
|
-
|
|
485
|
+
**Done.**
|
|
486
|
+
Fixed two upstream API gaps before extracting: `onSessionCreated` now receives `(session, record)` (eliminating a `listAgents()` reverse-search), and `AgentSpawnConfig` accepts `toolCallId` (moving `NotificationState` wiring into `AgentManager.spawn()`).
|
|
487
|
+
Extracted `foreground-runner.ts` (~175 lines) and `background-spawner.ts` (~116 lines).
|
|
488
|
+
`agent-tool.ts` reduced from 579 → 411 lines (orchestrator + rendering).
|
|
478
489
|
|
|
479
490
|
#### E2. Type housekeeping (#116)
|
|
480
491
|
|
|
@@ -494,8 +505,8 @@ The 654-line file splits along a natural seam.
|
|
|
494
505
|
| Closure-bag "classes" | ~~2~~ 1 (`createNotificationSystem`; settings free functions **fixed #109**) | 0 |
|
|
495
506
|
| Externally-mutated state bags | ~~2~~ ~~1~~ **0** (`AgentRecord` **fixed #111**; `AgentActivity` **fixed #110**) | 0 ✓ |
|
|
496
507
|
| `AgentManagerOptions` fields | 8 | 5 |
|
|
497
|
-
| `AgentToolDeps` fields | ~~9~~ **
|
|
498
|
-
| `AgentMenuDeps` fields | ~~13~~ **
|
|
508
|
+
| `AgentToolDeps` fields | ~~9~~ **6** (−3 text fields derived; −1 emitEvent → observer; activity narrowed) | 6 ✓ |
|
|
509
|
+
| `AgentMenuDeps` fields | ~~13~~ **7** (−6 settings #109; −1 registry #108; −1 dead emitEvent #114) | 7 ✓ |
|
|
499
510
|
| `SpawnOptions` callback fields | 1 (`onSessionCreated`) | 0 |
|
|
500
511
|
| Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain | 0 |
|
|
501
512
|
| Types in `types.ts` without a natural home | 4 | 0 |
|
|
@@ -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.
|