@gotgenes/pi-subagents 6.9.0 → 6.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/docs/architecture/architecture.md +36 -31
- package/docs/plans/0115-decompose-agent-tool.md +337 -0
- package/docs/plans/0116-type-housekeeping.md +351 -0
- package/docs/retro/0114-narrow-agent-tool-menu-deps.md +38 -0
- package/docs/retro/0115-decompose-agent-tool.md +51 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +12 -4
- package/src/agent-runner.ts +2 -1
- package/src/env.ts +7 -1
- package/src/index.ts +2 -4
- package/src/notification.ts +48 -33
- package/src/parent-snapshot.ts +20 -1
- package/src/prompts.ts +3 -2
- package/src/renderer.ts +1 -1
- package/src/session-config.ts +2 -1
- package/src/tools/agent-tool.ts +33 -201
- package/src/tools/background-spawner.ts +116 -0
- package/src/tools/foreground-runner.ts +175 -0
- package/src/tools/helpers.ts +45 -1
- package/src/types.ts +14 -46
- package/src/ui/agent-menu.ts +1 -1
- package/src/ui/conversation-viewer.ts +27 -10
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.9.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.1...pi-subagents-v6.9.2) (2026-05-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* mark E2 type housekeeping done in architecture ([#116](https://github.com/gotgenes/pi-packages/issues/116)) ([89f18a8](https://github.com/gotgenes/pi-packages/commit/89f18a82fac39b4a62be8e440355b859afaa6d2f))
|
|
14
|
+
* plan type housekeeping and small structural cleanups ([#116](https://github.com/gotgenes/pi-packages/issues/116)) ([e1cbd26](https://github.com/gotgenes/pi-packages/commit/e1cbd269961a1bff3b11e2e916733a79c39a087d))
|
|
15
|
+
* **retro:** add retro notes for issue [#115](https://github.com/gotgenes/pi-packages/issues/115) ([05b8809](https://github.com/gotgenes/pi-packages/commit/05b88093f7b70ea886b6c7cb5f0cc96161a95df6))
|
|
16
|
+
|
|
17
|
+
## [6.9.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.9.0...pi-subagents-v6.9.1) (2026-05-22)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
* 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))
|
|
23
|
+
* **retro:** add retro notes for issue [#114](https://github.com/gotgenes/pi-packages/issues/114) ([e6095e7](https://github.com/gotgenes/pi-packages/commit/e6095e7465f482ac18756305b9740f1101dbd41a))
|
|
24
|
+
* 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))
|
|
25
|
+
|
|
8
26
|
## [6.9.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.8.3...pi-subagents-v6.9.0) (2026-05-22)
|
|
9
27
|
|
|
10
28
|
|
|
@@ -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
|
|
@@ -380,13 +384,13 @@ Each step is sequenced so it makes the next step easier.
|
|
|
380
384
|
| Smell | Location | Evidence |
|
|
381
385
|
| ------------------------------ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
382
386
|
| ~~Global mutable state~~ | ~~`agent-types.ts`~~ | **Fixed #108**: `AgentTypeRegistry` class; `reloadCustomAgents` callback removed from `AgentToolDeps` and `AgentMenuDeps` |
|
|
383
|
-
| Closure bag as class
|
|
387
|
+
| ~~Closure bag as class~~ | ~~`createNotificationSystem()`~~ | **Fixed #116**: `NotificationManager` class; `pendingNudges` and timer state are private fields |
|
|
384
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 |
|
|
385
389
|
| ~~Settings relay~~ | ~~`AgentMenuDeps` (13 fields)~~ | **Fixed #109**: `SettingsManager` class; 6 callback fields collapsed to `settings: SettingsManager`; `AgentMenuDeps` now 8 fields |
|
|
386
390
|
| ~~Post-construction mutation~~ | ~~`AgentRecord` non-transition state~~ | **Fixed #111**: `ExecutionState`, `WorktreeState`, `NotificationState` collaborators; `pendingSteers` moved to `AgentManager`; stats encapsulated behind mutation methods |
|
|
387
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 |
|
|
388
392
|
| ~~Duplicate `SpawnOptions`~~ | ~~`service.ts` + `agent-manager.ts`~~ | **Fixed #113**: internal type renamed to `AgentSpawnConfig`; public `SpawnOptions` in `service.ts` unchanged |
|
|
389
|
-
| Type dumping ground
|
|
393
|
+
| ~~Type dumping ground~~ | ~~`types.ts`~~ | **Fixed #116**: `NotificationDetails` → `notification.ts`; `ParentSnapshot` → `parent-snapshot.ts`; `EnvInfo` → `env.ts`; `AgentIdentity` and `AgentPromptConfig` narrow subsets |
|
|
390
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)
|
|
@@ -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.
|
|
@@ -476,34 +480,35 @@ Public `SpawnOptions` in `service.ts` is unchanged.
|
|
|
476
480
|
|
|
477
481
|
### Step E: Decompose large files and relocate types (parallel)
|
|
478
482
|
|
|
479
|
-
#### E1. Split `agent-tool.ts` foreground/background (#115)
|
|
483
|
+
#### E1. Split `agent-tool.ts` foreground/background (#115) ✅
|
|
480
484
|
|
|
481
|
-
|
|
482
|
-
|
|
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).
|
|
483
489
|
|
|
484
|
-
#### E2. Type housekeeping (#116)
|
|
490
|
+
#### ~~E2. Type housekeeping (#116)~~ — **Done**
|
|
485
491
|
|
|
486
|
-
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
489
|
-
-
|
|
490
|
-
-
|
|
491
|
-
-
|
|
492
|
-
- Define narrow `AgentConfig` subset interfaces for consumers that use 2–4 fields of the 21-field type.
|
|
492
|
+
- Moved `NotificationDetails` from `types.ts` to `notification.ts`.
|
|
493
|
+
- Moved `ParentSnapshot` from `types.ts` to `parent-snapshot.ts` (`DEFAULT_AGENT_NAMES` was already moved in #108).
|
|
494
|
+
- Moved `EnvInfo` from `types.ts` to `env.ts`.
|
|
495
|
+
- Converted `createNotificationSystem` closure to `NotificationManager` class.
|
|
496
|
+
- Converted `ConversationViewer` constructor from 7 positional parameters to `ConversationViewerOptions` bag.
|
|
497
|
+
- Defined `AgentIdentity` and `AgentPromptConfig` narrow subsets; `AgentConfig extends` both; `buildAgentPrompt` narrowed to `AgentPromptConfig`.
|
|
493
498
|
|
|
494
499
|
### Expected impact
|
|
495
500
|
|
|
496
|
-
| Metric | Before
|
|
497
|
-
| ------------------------------------------ |
|
|
498
|
-
| Module-scoped mutable state | ~~1 (`agent-types.ts` Map)~~
|
|
499
|
-
| Closure-bag "classes" | ~~2~~ 1 (`createNotificationSystem
|
|
500
|
-
| Externally-mutated state bags | ~~2~~ ~~1~~ **0** (`AgentRecord` **fixed #111**; `AgentActivity` **fixed #110**)
|
|
501
|
-
| `AgentManagerOptions` fields | 8
|
|
502
|
-
| `AgentToolDeps` fields | ~~9~~ **6** (−3 text fields derived; −1 emitEvent → observer; activity narrowed)
|
|
503
|
-
| `AgentMenuDeps` fields | ~~13~~ **7** (−6 settings #109; −1 registry #108; −1 dead emitEvent #114)
|
|
504
|
-
| `SpawnOptions` callback fields | 1 (`onSessionCreated`)
|
|
505
|
-
| Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain
|
|
506
|
-
| Types in `types.ts` without a natural home | 4
|
|
501
|
+
| Metric | Before | After |
|
|
502
|
+
| ------------------------------------------ | -------------------------------------------------------------------------------------- | ------- |
|
|
503
|
+
| Module-scoped mutable state | ~~1 (`agent-types.ts` Map)~~ | **0** ✓ |
|
|
504
|
+
| Closure-bag "classes" | ~~2~~ ~~1~~ **0** (`createNotificationSystem` **fixed #116**; settings **fixed #109**) | 0 ✓ |
|
|
505
|
+
| Externally-mutated state bags | ~~2~~ ~~1~~ **0** (`AgentRecord` **fixed #111**; `AgentActivity` **fixed #110**) | 0 ✓ |
|
|
506
|
+
| `AgentManagerOptions` fields | 8 | 5 |
|
|
507
|
+
| `AgentToolDeps` fields | ~~9~~ **6** (−3 text fields derived; −1 emitEvent → observer; activity narrowed) | 6 ✓ |
|
|
508
|
+
| `AgentMenuDeps` fields | ~~13~~ **7** (−6 settings #109; −1 registry #108; −1 dead emitEvent #114) | 7 ✓ |
|
|
509
|
+
| `SpawnOptions` callback fields | 1 (`onSessionCreated`) | 0 |
|
|
510
|
+
| Callbacks threaded through deps | ~~8~~ 0 remaining settings callbacks (**fixed #109**); `emitEvent` ×3 remain | 0 |
|
|
511
|
+
| Types in `types.ts` without a natural home | ~~4~~ **0** ✓ (all moved #116) | 0 ✓ |
|
|
507
512
|
|
|
508
513
|
### Dependency graph
|
|
509
514
|
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 115
|
|
3
|
+
issue_title: "refactor(pi-subagents): decompose agent-tool.ts into foreground/background modules"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Decompose agent-tool.ts into foreground/background modules
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`tools/agent-tool.ts` is the largest file in the package at 579 lines.
|
|
11
|
+
The `execute` function handles three distinct execution paths — resume, background spawn, and foreground streaming — each with different dependencies.
|
|
12
|
+
Before those paths can be cleanly extracted, two upstream API gaps force the tool to work around the manager:
|
|
13
|
+
|
|
14
|
+
1. The foreground `onSessionCreated` callback loops through `manager.listAgents()` matching by session object just to discover the agent's ID — because `onSessionCreated` only receives the session, not the record.
|
|
15
|
+
2. The background path mutates `record.notification` after spawn — reaching into the record returned by `getRecord()` to attach a `NotificationState` — because the manager has no way to wire notification state at spawn time.
|
|
16
|
+
|
|
17
|
+
These workarounds would simply move into the extracted modules unchanged.
|
|
18
|
+
Fixing the API gaps first makes the extraction clean: each extracted module receives what it actually needs from the manager, without reaching through or reverse-searching.
|
|
19
|
+
|
|
20
|
+
## Goals
|
|
21
|
+
|
|
22
|
+
- Widen `onSessionCreated` to `(session, record)` so callers receive the agent ID and record directly.
|
|
23
|
+
- Accept `toolCallId` in `AgentSpawnConfig` so the manager wires `record.notification` internally for background agents.
|
|
24
|
+
- Extract the foreground execution loop into `tools/foreground-runner.ts`.
|
|
25
|
+
- Extract the background spawn path into `tools/background-spawner.ts`.
|
|
26
|
+
- Move `getStatusNote` and `buildDetails` to `tools/helpers.ts`.
|
|
27
|
+
- Keep `agent-tool.ts` as the orchestrator (~250 lines): tool definition, parameter validation, shared setup, dispatch, resume.
|
|
28
|
+
- Preserve all existing behavior.
|
|
29
|
+
|
|
30
|
+
## Non-Goals
|
|
31
|
+
|
|
32
|
+
- Extracting the resume path (~27 lines) — too small to warrant a separate file.
|
|
33
|
+
- Extracting `renderCall`/`renderResult` — tightly coupled to the tool definition.
|
|
34
|
+
- Changing `AgentToolDeps` shape — #114 already narrowed it.
|
|
35
|
+
- Removing `onSessionCreated` from `AgentSpawnConfig` entirely — it is still useful for UI observer wiring that the manager should not own.
|
|
36
|
+
|
|
37
|
+
## Background
|
|
38
|
+
|
|
39
|
+
### Prerequisite status
|
|
40
|
+
|
|
41
|
+
| Issue | Title | Status |
|
|
42
|
+
| ----- | ------------------------------------------ | ------- |
|
|
43
|
+
| #114 | Narrow `AgentToolDeps` and `AgentMenuDeps` | ✅ Done |
|
|
44
|
+
|
|
45
|
+
### Current API gaps
|
|
46
|
+
|
|
47
|
+
#### Gap 1: foreground ID discovery
|
|
48
|
+
|
|
49
|
+
The foreground path needs the agent ID *during* execution (inside `onSessionCreated`, before `spawnAndWait` resolves) to register the activity tracker in the widget.
|
|
50
|
+
The manager's internal `onSessionCreated` handler already has `id` and `record` in scope but passes only `session` to the caller's callback.
|
|
51
|
+
The tool works around this by iterating `listAgents()` and matching by session identity:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
onSessionCreated: (session) => {
|
|
55
|
+
for (const a of deps.manager.listAgents()) {
|
|
56
|
+
if (a.execution?.session === session) {
|
|
57
|
+
fgId = a.id;
|
|
58
|
+
deps.agentActivity.set(a.id, fgState);
|
|
59
|
+
// ...
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This is a violation of Tell-Don't-Ask: the tool asks the manager for data it already has.
|
|
66
|
+
|
|
67
|
+
#### Gap 2: post-spawn notification mutation
|
|
68
|
+
|
|
69
|
+
The background path calls `manager.spawn()`, then immediately calls `manager.getRecord(id)` to mutate `record.notification`:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const id = deps.manager.spawn(ctx, subagentType, prompt, { ... });
|
|
73
|
+
const record = deps.manager.getRecord(id);
|
|
74
|
+
if (record) {
|
|
75
|
+
record.notification = new NotificationState(toolCallId);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This is an output argument — the tool writes back into a record it doesn't own.
|
|
80
|
+
The notification could be wired at spawn time if the manager accepted a `toolCallId`.
|
|
81
|
+
|
|
82
|
+
### Relevant design principles
|
|
83
|
+
|
|
84
|
+
- **Tell-Don't-Ask** (code-design skill): the `listAgents()` loop asks the manager for data it already has.
|
|
85
|
+
- **Output arguments** (code-design skill): writing `record.notification` after spawn mutates an object owned by the manager.
|
|
86
|
+
- **SRP**: foreground streaming and background spawning are independent concerns.
|
|
87
|
+
- **One concern per file** (AGENTS.md): the file mixes orchestration, streaming, spawning, and formatting.
|
|
88
|
+
|
|
89
|
+
## Design Overview
|
|
90
|
+
|
|
91
|
+
### Phase 1: Fix manager API gaps
|
|
92
|
+
|
|
93
|
+
#### Widen `onSessionCreated` to include record
|
|
94
|
+
|
|
95
|
+
Change the callback signature in both `AgentSpawnConfig` and the runner's `RunOptions`:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// agent-manager.ts — AgentSpawnConfig
|
|
99
|
+
onSessionCreated?: (session: AgentSession, record: AgentRecord) => void;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The manager's internal handler already has `record` in scope — pass it through:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// In startAgent(), existing line:
|
|
106
|
+
options.onSessionCreated?.(session);
|
|
107
|
+
// Becomes:
|
|
108
|
+
options.onSessionCreated?.(session, record);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The runner's `onSessionCreated` stays `(session: AgentSession) => void` — it doesn't know about records.
|
|
112
|
+
The manager wraps the runner callback and adds `record` before forwarding to the caller.
|
|
113
|
+
|
|
114
|
+
This lets the foreground tool callback access `record.id` directly, eliminating the `listAgents()` loop.
|
|
115
|
+
|
|
116
|
+
#### Accept `toolCallId` in `AgentSpawnConfig`
|
|
117
|
+
|
|
118
|
+
Add an optional `toolCallId` field to `AgentSpawnConfig`:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
export interface AgentSpawnConfig {
|
|
122
|
+
// ... existing fields ...
|
|
123
|
+
/** Tool call ID for background notification wiring. When set, spawn attaches NotificationState. */
|
|
124
|
+
toolCallId?: string;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
In `AgentManager.spawn()`, after creating the record:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
if (options.toolCallId) {
|
|
132
|
+
record.notification = new NotificationState(options.toolCallId);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This moves the notification wiring into the manager, eliminating the post-spawn mutation in the tool.
|
|
137
|
+
|
|
138
|
+
### Phase 2: Extract modules
|
|
139
|
+
|
|
140
|
+
With the API gaps fixed, the extracted modules no longer need `listAgents` or `getRecord` or post-spawn record mutation.
|
|
141
|
+
|
|
142
|
+
#### Foreground runner
|
|
143
|
+
|
|
144
|
+
After the `onSessionCreated` widening, the foreground callback simplifies to:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
onSessionCreated: (session, record) => {
|
|
148
|
+
fgState.setSession(session);
|
|
149
|
+
unsubUI = subscribeUIObserver(session, fgState, streamUpdate);
|
|
150
|
+
fgId = record.id;
|
|
151
|
+
deps.agentActivity.set(record.id, fgState);
|
|
152
|
+
deps.widget.ensureTimer();
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The `runForeground` function receives narrow deps:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
export interface ForegroundDeps {
|
|
160
|
+
manager: { spawnAndWait: AgentToolManager["spawnAndWait"] };
|
|
161
|
+
widget: { ensureTimer(): void; markFinished(id: string): void };
|
|
162
|
+
agentActivity: AgentActivityAccess;
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
No `listAgents` needed — the record is delivered by the callback.
|
|
167
|
+
|
|
168
|
+
#### Background spawner
|
|
169
|
+
|
|
170
|
+
After the `toolCallId` change, the background path simplifies to:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const id = deps.manager.spawn(ctx, subagentType, prompt, {
|
|
174
|
+
...spawnConfig,
|
|
175
|
+
toolCallId,
|
|
176
|
+
});
|
|
177
|
+
// No getRecord + mutation needed — notification already wired
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The `spawnBackground` function receives narrow deps:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
export interface BackgroundDeps {
|
|
184
|
+
manager: { spawn: AgentToolManager["spawn"]; getRecord: AgentToolManager["getRecord"]; getMaxConcurrent: AgentToolManager["getMaxConcurrent"] };
|
|
185
|
+
widget: { ensureTimer(): void; update(): void };
|
|
186
|
+
agentActivity: AgentActivityAccess;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`getRecord` is still needed for building the result message (checking `status`, `execution.outputFile`), but not for mutation.
|
|
191
|
+
|
|
192
|
+
#### What stays in agent-tool.ts
|
|
193
|
+
|
|
194
|
+
- `AgentToolDeps`, `AgentToolManager`, `AgentToolWidget`, `AgentActivityAccess` interfaces.
|
|
195
|
+
- `createAgentTool` factory: tool name/label/description, parameters schema, `renderCall`, `renderResult`.
|
|
196
|
+
- Execute's shared setup: registry reload, type resolution, model resolution, config assembly, detail base.
|
|
197
|
+
- Resume path (~27 lines).
|
|
198
|
+
- Dispatch to `spawnBackground()` or `runForeground()`.
|
|
199
|
+
|
|
200
|
+
#### Helpers relocation
|
|
201
|
+
|
|
202
|
+
`getStatusNote` and `buildDetails` move to `tools/helpers.ts`.
|
|
203
|
+
Both are pure formatting functions with no dependency on `AgentToolDeps`.
|
|
204
|
+
|
|
205
|
+
### Post-extraction file sizes (estimated)
|
|
206
|
+
|
|
207
|
+
| File | Lines |
|
|
208
|
+
| ----------------------- | -------------- |
|
|
209
|
+
| `agent-tool.ts` | ~250 (was 579) |
|
|
210
|
+
| `foreground-runner.ts` | ~110 |
|
|
211
|
+
| `background-spawner.ts` | ~70 |
|
|
212
|
+
| `helpers.ts` additions | ~50 |
|
|
213
|
+
|
|
214
|
+
## Module-Level Changes
|
|
215
|
+
|
|
216
|
+
### Modified files
|
|
217
|
+
|
|
218
|
+
1. **`src/agent-manager.ts`**
|
|
219
|
+
- Change `onSessionCreated` in `AgentSpawnConfig` to `(session: AgentSession, record: AgentRecord) => void`.
|
|
220
|
+
- Pass `record` as second argument in `startAgent`'s internal `onSessionCreated` call.
|
|
221
|
+
- Add optional `toolCallId?: string` to `AgentSpawnConfig`.
|
|
222
|
+
- Wire `record.notification = new NotificationState(options.toolCallId)` in `spawn()` when present.
|
|
223
|
+
- Add `NotificationState` import.
|
|
224
|
+
|
|
225
|
+
2. **`src/tools/agent-tool.ts`**
|
|
226
|
+
- Update `onSessionCreated` callbacks to accept `(session, record)`.
|
|
227
|
+
- Remove `listAgents()` loop in foreground callback — use `record.id` directly.
|
|
228
|
+
- Pass `toolCallId` in background spawn config — remove post-spawn `getRecord` + mutation.
|
|
229
|
+
- Remove foreground block → `runForeground()` call.
|
|
230
|
+
- Remove background block → `spawnBackground()` call.
|
|
231
|
+
- Remove `getStatusNote`, `buildDetails` → imported from `helpers.ts`.
|
|
232
|
+
- Remove `listAgents` from `AgentToolManager` interface (no longer needed).
|
|
233
|
+
- Remove unused imports: `NotificationState`, `describeActivity`, `SPINNER`, `formatMs`.
|
|
234
|
+
|
|
235
|
+
3. **`src/tools/helpers.ts`**
|
|
236
|
+
- Add `getStatusNote()` and `buildDetails()` (relocated from `agent-tool.ts`).
|
|
237
|
+
|
|
238
|
+
### New files
|
|
239
|
+
|
|
240
|
+
4. **`src/tools/foreground-runner.ts`**
|
|
241
|
+
- `ForegroundDeps` interface, `runForeground()` function.
|
|
242
|
+
- Owns: spinner interval, `AgentActivityTracker` creation, `subscribeUIObserver`, streaming `onUpdate`, cleanup, result formatting via `buildDetails`/`getStatusNote`.
|
|
243
|
+
|
|
244
|
+
5. **`src/tools/background-spawner.ts`**
|
|
245
|
+
- `BackgroundDeps` interface, `spawnBackground()` function.
|
|
246
|
+
- Owns: `AgentActivityTracker` creation, `subscribeUIObserver`, activity map registration, widget update, launch message formatting.
|
|
247
|
+
|
|
248
|
+
### Test files
|
|
249
|
+
|
|
250
|
+
6. **`test/agent-manager.test.ts`**
|
|
251
|
+
- Update mock runner calls to pass `record` in `onSessionCreated`.
|
|
252
|
+
- Add test: `spawn` wires `NotificationState` when `toolCallId` is provided.
|
|
253
|
+
- Add test: `spawn` does not wire `NotificationState` when `toolCallId` is absent.
|
|
254
|
+
|
|
255
|
+
7. **`test/tools/agent-tool.test.ts`**
|
|
256
|
+
- Update `onSessionCreated` mock signatures if needed (structural — tests call through `execute`).
|
|
257
|
+
- Existing tests remain as integration tests for the dispatch path.
|
|
258
|
+
|
|
259
|
+
8. **`test/tools/helpers.test.ts`** (new or extended)
|
|
260
|
+
- Unit tests for `getStatusNote` (all status branches) and `buildDetails`.
|
|
261
|
+
|
|
262
|
+
9. **`test/tools/foreground-runner.test.ts`** (new)
|
|
263
|
+
- Spinner lifecycle, streaming updates, cleanup on success/error, result formatting, fallback note.
|
|
264
|
+
|
|
265
|
+
10. **`test/tools/background-spawner.test.ts`** (new)
|
|
266
|
+
- Activity tracker registered, widget updated, queued message, launch message format.
|
|
267
|
+
|
|
268
|
+
## Test Impact Analysis
|
|
269
|
+
|
|
270
|
+
1. **New unit tests enabled:**
|
|
271
|
+
- `foreground-runner.test.ts` tests spinner lifecycle and streaming with narrow mocks (no full `AgentToolDeps`).
|
|
272
|
+
- `background-spawner.test.ts` tests activity registration and message formatting in isolation.
|
|
273
|
+
- `helpers.test.ts` tests `getStatusNote` and `buildDetails` as pure functions.
|
|
274
|
+
- `agent-manager.test.ts` tests notification wiring at the manager level — moved from tool-level integration.
|
|
275
|
+
|
|
276
|
+
2. **Existing tests that simplify:**
|
|
277
|
+
- `agent-tool.test.ts` background tests no longer need to verify notification wiring (now the manager's job).
|
|
278
|
+
- The "registers activity in agentActivity map" test stays but becomes a dispatch-level integration test.
|
|
279
|
+
|
|
280
|
+
3. **Existing tests that must stay:**
|
|
281
|
+
- All `agent-tool.test.ts` tests exercise the full dispatch path and remain valuable as integration tests.
|
|
282
|
+
- All `agent-manager.test.ts` tests that fire `onSessionCreated` must update the mock signature to `(session, record)`.
|
|
283
|
+
|
|
284
|
+
## TDD Order
|
|
285
|
+
|
|
286
|
+
1. **Widen `onSessionCreated` callback to include record.**
|
|
287
|
+
Change `AgentSpawnConfig.onSessionCreated` signature to `(session, record)`.
|
|
288
|
+
Update `startAgent` to pass `record` as second argument.
|
|
289
|
+
Update `agent-tool.ts` foreground callback to use `record.id` instead of `listAgents()` loop.
|
|
290
|
+
Remove `listAgents` from `AgentToolManager` interface.
|
|
291
|
+
Update `agent-manager.test.ts` mock runner calls.
|
|
292
|
+
Test: verify foreground callback receives `record.id` (existing integration tests pass).
|
|
293
|
+
Commit: `refactor: widen onSessionCreated to include record`
|
|
294
|
+
|
|
295
|
+
2. **Accept `toolCallId` in `AgentSpawnConfig`.**
|
|
296
|
+
Add `toolCallId?: string` to `AgentSpawnConfig`.
|
|
297
|
+
Wire `NotificationState` in `spawn()` when `toolCallId` is provided.
|
|
298
|
+
Update `agent-tool.ts` background path to pass `toolCallId` instead of post-spawn mutation.
|
|
299
|
+
Test: `agent-manager.test.ts` — `spawn` wires notification when `toolCallId` present, skips when absent.
|
|
300
|
+
Commit: `refactor: wire NotificationState at spawn time via toolCallId`
|
|
301
|
+
|
|
302
|
+
3. **Relocate `getStatusNote` and `buildDetails` to `tools/helpers.ts`.**
|
|
303
|
+
Move both functions.
|
|
304
|
+
Update imports in `agent-tool.ts`.
|
|
305
|
+
Test: unit tests for `getStatusNote` (all branches) and `buildDetails`.
|
|
306
|
+
Commit: `refactor: move getStatusNote and buildDetails to tools/helpers`
|
|
307
|
+
|
|
308
|
+
4. **Extract `spawnBackground` into `tools/background-spawner.ts`.**
|
|
309
|
+
Define `BackgroundDeps` interface and `spawnBackground()` function.
|
|
310
|
+
Replace background block in `execute` with a call to `spawnBackground()`.
|
|
311
|
+
Remove unused imports from `agent-tool.ts`.
|
|
312
|
+
Test: `background-spawner.test.ts` — activity registration, widget update, launch message.
|
|
313
|
+
Commit: `refactor: extract background spawn to tools/background-spawner`
|
|
314
|
+
|
|
315
|
+
5. **Extract `runForeground` into `tools/foreground-runner.ts`.**
|
|
316
|
+
Define `ForegroundDeps` interface and `runForeground()` function.
|
|
317
|
+
Replace foreground block in `execute` with a call to `runForeground()`.
|
|
318
|
+
Remove unused imports from `agent-tool.ts`.
|
|
319
|
+
Test: `foreground-runner.test.ts` — spinner lifecycle, streaming, cleanup, result formatting.
|
|
320
|
+
Commit: `refactor: extract foreground execution to tools/foreground-runner`
|
|
321
|
+
|
|
322
|
+
6. **Verify integration.**
|
|
323
|
+
Run full test suite and `pnpm run check`.
|
|
324
|
+
Commit: `test: verify agent-tool decomposition integration`
|
|
325
|
+
|
|
326
|
+
## Risks and Mitigations
|
|
327
|
+
|
|
328
|
+
| Risk | Mitigation |
|
|
329
|
+
| ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
330
|
+
| Widening `onSessionCreated` signature is a breaking change to `AgentSpawnConfig` | `AgentSpawnConfig` is internal (not in package `exports`). The only external caller is `agent-tool.ts`. All test mocks update in the same step. |
|
|
331
|
+
| `toolCallId` on `AgentSpawnConfig` couples the manager to notification concerns | The manager already owns the record lifecycle. `NotificationState` is a record collaborator like `execution` and `worktreeState` — the manager already wires those. `toolCallId` is a data-in, not a behavior coupling. |
|
|
332
|
+
| Runner's `onSessionCreated` signature stays `(session)` while manager's is `(session, record)` | The manager wraps the runner's callback — the runner never sees the record. No change to runner interface. |
|
|
333
|
+
| Circular imports between new modules and `helpers.ts` | `helpers.ts` is a leaf module. The new modules import from it but it imports nothing from them. |
|
|
334
|
+
|
|
335
|
+
## Open Questions
|
|
336
|
+
|
|
337
|
+
None — the design is unambiguous after resolving the two API gaps.
|