@gotgenes/pi-subagents 6.13.0 → 6.13.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 +13 -0
- package/docs/architecture/architecture.md +299 -162
- package/docs/retro/0136-decompose-agent-menu.md +43 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ 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.13.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.13.0...pi-subagents-v6.13.1) (2026-05-23)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* **pi-subagents:** add dependency bag convention to Phase 9 ([72436cd](https://github.com/gotgenes/pi-packages/commit/72436cd51d7a3058de6bb17673f3552fd4e61340))
|
|
14
|
+
* **pi-subagents:** add issue references to Phase 9 steps ([43e9b9c](https://github.com/gotgenes/pi-packages/commit/43e9b9c2f2d816f0802783752ce0bcc8022d4a33))
|
|
15
|
+
* **pi-subagents:** add Phase 9 roadmap to architecture.md ([6fb1ad8](https://github.com/gotgenes/pi-packages/commit/6fb1ad892df3e85cca35361a0995adcdf3b0569b))
|
|
16
|
+
* **pi-subagents:** convert dependency graphs to Mermaid diagrams ([d2571e8](https://github.com/gotgenes/pi-packages/commit/d2571e8558faf2c6728068bfafbbf24beb812f7c))
|
|
17
|
+
* **pi-subagents:** refine Step M to include execute decomposition ([262f570](https://github.com/gotgenes/pi-packages/commit/262f5708d54bb6aa30ccd405bed1e89bfd5ea999))
|
|
18
|
+
* **pi-subagents:** remove progress markers from architecture.md ([aca298b](https://github.com/gotgenes/pi-packages/commit/aca298ba7e1da44c48dc442b2602a4202d4943a3))
|
|
19
|
+
* **retro:** add retro notes for issue [#136](https://github.com/gotgenes/pi-packages/issues/136) ([20384ac](https://github.com/gotgenes/pi-packages/commit/20384ac2040ae4334565f303f327bb007d9b4501))
|
|
20
|
+
|
|
8
21
|
## [6.13.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.12.1...pi-subagents-v6.13.0) (2026-05-23)
|
|
9
22
|
|
|
10
23
|
|
|
@@ -4,23 +4,23 @@ This document describes the architecture of the pi-subagents fork: a focused, co
|
|
|
4
4
|
|
|
5
5
|
## Design principles
|
|
6
6
|
|
|
7
|
-
1. **Narrow core**
|
|
7
|
+
1. **Narrow core** - the extension owns agent spawning, execution, and result retrieval.
|
|
8
8
|
Everything else is a consumer.
|
|
9
|
-
2. **Composable by default**
|
|
10
|
-
3. **Typed API boundary**
|
|
9
|
+
2. **Composable by default** - other extensions can spawn agents, observe their lifecycle, and display their state without importing this package directly.
|
|
10
|
+
3. **Typed API boundary** - this package exports a `SubagentsService` interface and `Symbol.for()` accessors (`publishSubagentsService` / `getSubagentsService`).
|
|
11
11
|
Consumers declare this package as an optional peer dependency and use dynamic import for compile-time types.
|
|
12
|
-
The runtime bridge is `Symbol.for("@gotgenes/pi-subagents:service")` on `globalThis`
|
|
13
|
-
4. **No scheduling**
|
|
12
|
+
The runtime bridge is `Symbol.for("@gotgenes/pi-subagents:service")` on `globalThis` - no separate API package.
|
|
13
|
+
4. **No scheduling** - in-process scheduling is removed from the core.
|
|
14
14
|
Scheduling is a separate concern that any extension can implement by calling `spawn()` on the published API.
|
|
15
|
-
5. **UI extraction is deferred**
|
|
15
|
+
5. **UI extraction is deferred** - the widget, conversation viewer, and `/agents` command menu stay in the core for now.
|
|
16
16
|
They are the first candidate for extraction once the API boundary is proven stable.
|
|
17
|
-
6. **Snapshot, don't capture**
|
|
17
|
+
6. **Snapshot, don't capture** - mutable parent state (ctx, session, model) is read once at spawn time and frozen into a `ParentSnapshot` data object.
|
|
18
18
|
No live references survive past the spawn call.
|
|
19
|
-
7. **Subscribe, don't thread**
|
|
20
|
-
8. **Construct complete**
|
|
19
|
+
7. **Subscribe, don't thread** - observation of agent progress uses direct session-event subscription, not callback parameters threaded through multiple layers.
|
|
20
|
+
8. **Construct complete** - objects are born with all their dependencies.
|
|
21
21
|
If state isn't available yet, the object that needs it doesn't exist yet.
|
|
22
|
-
No post-construction field writes from external code
|
|
23
|
-
9. **State owns its mutations**
|
|
22
|
+
No post-construction field writes from external code - if an object can't be instantiated ready-to-go, the prep work hasn't been done and the right dependencies haven't been identified.
|
|
23
|
+
9. **State owns its mutations** - mutable state lives in a class whose methods enforce valid transitions and invariants.
|
|
24
24
|
Free functions that mutate module-scoped variables, closure-captured bags-of-functions, and external writes to shared interfaces are replaced by classes that encapsulate the state they manage.
|
|
25
25
|
|
|
26
26
|
## Current state
|
|
@@ -28,62 +28,62 @@ This document describes the architecture of the pi-subagents fork: a focused, co
|
|
|
28
28
|
The extension is organized into 39 focused modules with a typed `SubagentsService` API boundary.
|
|
29
29
|
|
|
30
30
|
```text
|
|
31
|
-
index.ts
|
|
32
|
-
agent-manager.ts
|
|
33
|
-
agent-runner.ts
|
|
34
|
-
session-config.ts
|
|
35
|
-
agent-types.ts
|
|
36
|
-
agent-record.ts
|
|
37
|
-
types.ts
|
|
38
|
-
runtime.ts
|
|
39
|
-
parent-snapshot.ts
|
|
40
|
-
|
|
41
|
-
prompts.ts
|
|
42
|
-
context.ts
|
|
43
|
-
memory.ts
|
|
44
|
-
skill-loader.ts
|
|
45
|
-
env.ts
|
|
46
|
-
|
|
47
|
-
worktree.ts
|
|
48
|
-
usage.ts
|
|
49
|
-
model-resolver.ts
|
|
50
|
-
invocation-config.ts
|
|
51
|
-
session-dir.ts
|
|
52
|
-
settings.ts
|
|
53
|
-
|
|
54
|
-
service.ts
|
|
55
|
-
service-adapter.ts
|
|
56
|
-
|
|
57
|
-
tools/agent-tool.ts
|
|
58
|
-
tools/foreground-runner.ts
|
|
59
|
-
tools/background-spawner.ts
|
|
60
|
-
tools/get-result-tool.ts
|
|
61
|
-
tools/steer-tool.ts
|
|
62
|
-
tools/helpers.ts
|
|
63
|
-
|
|
64
|
-
handlers/lifecycle.ts
|
|
65
|
-
handlers/tool-start.ts
|
|
66
|
-
|
|
67
|
-
notification.ts
|
|
68
|
-
renderer.ts
|
|
69
|
-
record-observer.ts
|
|
70
|
-
|
|
71
|
-
ui/display.ts
|
|
72
|
-
ui/agent-widget.ts
|
|
73
|
-
ui/agent-menu.ts
|
|
74
|
-
ui/conversation-viewer.ts
|
|
75
|
-
ui/ui-observer.ts
|
|
76
|
-
|
|
77
|
-
default-agents.ts
|
|
78
|
-
custom-agents.ts
|
|
79
|
-
debug.ts
|
|
31
|
+
index.ts - entry point, tool registration, event wiring
|
|
32
|
+
agent-manager.ts - lifecycle, concurrency, queue
|
|
33
|
+
agent-runner.ts - session creation, turn loop, tool filtering
|
|
34
|
+
session-config.ts - pure session-config assembler
|
|
35
|
+
agent-types.ts - type registry (defaults + custom .md files)
|
|
36
|
+
agent-record.ts - agent record with encapsulated status transitions
|
|
37
|
+
types.ts - shared type definitions
|
|
38
|
+
runtime.ts - SubagentRuntime factory (session-scoped state)
|
|
39
|
+
parent-snapshot.ts - immutable snapshot of parent session state
|
|
40
|
+
|
|
41
|
+
prompts.ts - system prompt assembly
|
|
42
|
+
context.ts - parent conversation extraction
|
|
43
|
+
memory.ts - persistent MEMORY.md per agent
|
|
44
|
+
skill-loader.ts - preload .pi/skills into prompts
|
|
45
|
+
env.ts - git/platform detection
|
|
46
|
+
|
|
47
|
+
worktree.ts - git worktree isolation
|
|
48
|
+
usage.ts - token usage tracking
|
|
49
|
+
model-resolver.ts - fuzzy model name resolution
|
|
50
|
+
invocation-config.ts - merge tool params with agent config
|
|
51
|
+
session-dir.ts - subagent session directory derivation
|
|
52
|
+
settings.ts - persistent operational settings; `SettingsManager` class owns all three in-memory values
|
|
53
|
+
|
|
54
|
+
service.ts - SubagentsService interface + Symbol.for() accessors
|
|
55
|
+
service-adapter.ts - SubagentsService implementation wrapping AgentManager
|
|
56
|
+
|
|
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, ...)
|
|
63
|
+
|
|
64
|
+
handlers/lifecycle.ts - session_start, session_before_switch, session_shutdown
|
|
65
|
+
handlers/tool-start.ts - tool_execution_start handler
|
|
66
|
+
|
|
67
|
+
notification.ts - completion nudges, custom message renderer
|
|
68
|
+
renderer.ts - notification TUI component
|
|
69
|
+
record-observer.ts - session-event observer for record statistics
|
|
70
|
+
|
|
71
|
+
ui/display.ts - pure formatters, display helpers, and shared types (Theme, AgentDetails)
|
|
72
|
+
ui/agent-widget.ts - above-editor live status widget
|
|
73
|
+
ui/agent-menu.ts - /agents slash command menu
|
|
74
|
+
ui/conversation-viewer.ts - scrollable session overlay
|
|
75
|
+
ui/ui-observer.ts - session-event observer for UI streaming
|
|
76
|
+
|
|
77
|
+
default-agents.ts - embedded default agent configs (general-purpose, Explore, Plan)
|
|
78
|
+
custom-agents.ts - user-defined agent .md file loader
|
|
79
|
+
debug.ts - debug logging utility
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
### Observation model
|
|
83
83
|
|
|
84
84
|
Record statistics (tool uses, token usage, compaction counts) are updated by `record-observer.ts`, which subscribes directly to session events.
|
|
85
85
|
UI streaming (active tools, response text, turn counts) is handled by `ui/ui-observer.ts`, which subscribes to the same session events independently.
|
|
86
|
-
Neither observer wraps or forwards the other
|
|
86
|
+
Neither observer wraps or forwards the other - both subscribe directly to the session.
|
|
87
87
|
|
|
88
88
|
The widget reads agent state by polling a shared `Map<string, AgentActivityTracker>` on `SubagentRuntime` every 80 ms. The conversation viewer subscribes directly to `AgentSession` objects.
|
|
89
89
|
|
|
@@ -111,31 +111,31 @@ They declare this package as an optional peer dependency and use dynamic import
|
|
|
111
111
|
### What the core owns
|
|
112
112
|
|
|
113
113
|
- The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
|
|
114
|
-
- `AgentManager`
|
|
115
|
-
- `agent-runner`
|
|
116
|
-
- `session-config`
|
|
117
|
-
- `SubagentRuntime`
|
|
118
|
-
- `ParentSnapshot`
|
|
119
|
-
- `record-observer`
|
|
120
|
-
- Agent type registry
|
|
114
|
+
- `AgentManager` - spawn, queue, abort, resume, concurrency control.
|
|
115
|
+
- `agent-runner` - session creation, turn loop, tool filtering, extension binding (Patches 2 and 3).
|
|
116
|
+
- `session-config` - pure configuration assembler (extracted from `agent-runner`).
|
|
117
|
+
- `SubagentRuntime` - session-scoped state bag with methods.
|
|
118
|
+
- `ParentSnapshot` - immutable snapshot of parent session state, captured once at spawn time.
|
|
119
|
+
- `record-observer` - session-event observer that updates record statistics without callback threading.
|
|
120
|
+
- Agent type registry - default agents, custom `.md` file loading.
|
|
121
121
|
- Prompt assembly, context extraction, memory, skills, environment.
|
|
122
122
|
- Worktree isolation.
|
|
123
123
|
- Token usage tracking.
|
|
124
124
|
- Session directory derivation and persisted `SessionManager` for subagent transcripts.
|
|
125
125
|
- Settings persistence.
|
|
126
|
-
- Internal UI (widget, conversation viewer, `/agents` menu)
|
|
126
|
+
- Internal UI (widget, conversation viewer, `/agents` menu) - these stay until the API boundary is proven, then move to a separate extension.
|
|
127
127
|
|
|
128
128
|
### What the core dropped
|
|
129
129
|
|
|
130
|
-
- **Scheduling** (`schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`)
|
|
130
|
+
- **Scheduling** (`schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`) - removed (#52).
|
|
131
131
|
Any extension that wants scheduling can implement it by calling `getSubagentsService()?.spawn(...)` on a timer.
|
|
132
|
-
- **Ad-hoc RPC** (`cross-extension-rpc.ts`)
|
|
133
|
-
- **Group join** (`group-join.ts`)
|
|
132
|
+
- **Ad-hoc RPC** (`cross-extension-rpc.ts`) - replaced by the typed `SubagentsService` published via `Symbol.for()` (#49).
|
|
133
|
+
- **Group join** (`group-join.ts`) - removed (#49).
|
|
134
134
|
Individual completion notifications are sufficient.
|
|
135
|
-
- **Output file** (`output-file.ts`)
|
|
135
|
+
- **Output file** (`output-file.ts`) - replaced by `session-dir.ts` + `SessionManager.create()` (#61).
|
|
136
136
|
Subagent transcripts are now written in Pi's official JSONL session format.
|
|
137
|
-
- **Callback threading**
|
|
138
|
-
- **Live `ctx` capture**
|
|
137
|
+
- **Callback threading** - the three-layer `on*` callback chain through `SpawnOptions` → `AgentManager` → `RunOptions` was replaced by direct session-event subscriptions (#100).
|
|
138
|
+
- **Live `ctx` capture** - `SpawnArgs` previously held a mutable `ctx: ExtensionContext` reference that could go stale in the concurrency queue.
|
|
139
139
|
Replaced by `ParentSnapshot`, an immutable data object captured once at spawn time (#99).
|
|
140
140
|
|
|
141
141
|
## SubagentsService
|
|
@@ -175,10 +175,10 @@ The dynamic import provides compile-time types; the `Symbol.for()` key is the ac
|
|
|
175
175
|
See `src/service.ts` for the canonical definition.
|
|
176
176
|
Key types:
|
|
177
177
|
|
|
178
|
-
- `SubagentsService`
|
|
179
|
-
- `SubagentRecord`
|
|
180
|
-
- `SpawnOptions`
|
|
181
|
-
- `SUBAGENT_EVENTS`
|
|
178
|
+
- `SubagentsService` - `spawn`, `getRecord`, `listAgents`, `abort`, `steer`, `waitForAll`, `hasRunning`.
|
|
179
|
+
- `SubagentRecord` - serializable agent snapshot (no live session objects).
|
|
180
|
+
- `SpawnOptions` - `description`, `model`, `maxTurns`, `thinkingLevel`, `isolated`, `inheritContext`, `foreground`, `bypassQueue`, `isolation`.
|
|
181
|
+
- `SUBAGENT_EVENTS` - channel constants for `pi.events` subscriptions.
|
|
182
182
|
|
|
183
183
|
### Accessor pattern
|
|
184
184
|
|
|
@@ -208,7 +208,7 @@ The core emits events on `pi.events` that any extension can observe:
|
|
|
208
208
|
| `subagents:completed` | `{ id, type, status, result?, error? }` | Agent finishes |
|
|
209
209
|
| `subagents:activity` | `{ id, toolName?, textDelta?, turnCount? }` | Streaming progress |
|
|
210
210
|
|
|
211
|
-
These are fire-and-forget broadcast events
|
|
211
|
+
These are fire-and-forget broadcast events - no request IDs, no reply channels.
|
|
212
212
|
|
|
213
213
|
### Consumer example: scheduling extension
|
|
214
214
|
|
|
@@ -259,30 +259,30 @@ The original monolithic `index.ts` has been decomposed into focused modules:
|
|
|
259
259
|
|
|
260
260
|
```text
|
|
261
261
|
src/
|
|
262
|
-
├── index.ts
|
|
263
|
-
├── runtime.ts
|
|
262
|
+
├── index.ts - slimmed entry point: init, tool registration
|
|
263
|
+
├── runtime.ts - SubagentRuntime: session-scoped state + methods
|
|
264
264
|
├── tools/
|
|
265
|
-
│ ├── agent-tool.ts
|
|
266
|
-
│ ├── foreground-runner.ts
|
|
267
|
-
│ ├── background-spawner.ts
|
|
268
|
-
│ ├── get-result-tool.ts
|
|
269
|
-
│ ├── steer-tool.ts
|
|
270
|
-
│ └── helpers.ts
|
|
265
|
+
│ ├── agent-tool.ts - Agent tool definition, parameter validation, dispatch
|
|
266
|
+
│ ├── foreground-runner.ts - foreground execution loop (spinner, streaming, result)
|
|
267
|
+
│ ├── background-spawner.ts - background spawn (activity setup, notification wiring)
|
|
268
|
+
│ ├── get-result-tool.ts - get_subagent_result tool
|
|
269
|
+
│ ├── steer-tool.ts - steer_subagent tool
|
|
270
|
+
│ └── helpers.ts - shared tool utilities (textResult, buildDetails, getStatusNote, ...)
|
|
271
271
|
├── handlers/
|
|
272
|
-
│ ├── lifecycle.ts
|
|
273
|
-
│ └── tool-start.ts
|
|
274
|
-
├── notification.ts
|
|
275
|
-
├── renderer.ts
|
|
276
|
-
├── ui/agent-menu.ts
|
|
277
|
-
├── ui/agent-config-editor.ts
|
|
278
|
-
├── ui/agent-creation-wizard.ts
|
|
279
|
-
├── ui/agent-file-ops.ts
|
|
280
|
-
├── service-adapter.ts
|
|
272
|
+
│ ├── lifecycle.ts - session_start, session_before_switch, session_shutdown
|
|
273
|
+
│ └── tool-start.ts - tool_execution_start handler
|
|
274
|
+
├── notification.ts - completion nudges, custom renderer
|
|
275
|
+
├── renderer.ts - notification TUI component
|
|
276
|
+
├── ui/agent-menu.ts - /agents slash command menu (orchestration, listing, settings)
|
|
277
|
+
├── ui/agent-config-editor.ts - agent detail view (edit/delete/eject/disable/enable)
|
|
278
|
+
├── ui/agent-creation-wizard.ts - agent creation (AI-generation and manual-form)
|
|
279
|
+
├── ui/agent-file-ops.ts - AgentFileOps interface + FsAgentFileOps implementation
|
|
280
|
+
├── service-adapter.ts - SubagentsService implementation wrapping AgentManager
|
|
281
281
|
└── (existing domain modules unchanged)
|
|
282
282
|
```
|
|
283
283
|
|
|
284
284
|
Each extracted module receives narrow constructor-injected dependencies rather than closing over module-level state.
|
|
285
|
-
Handlers call methods on narrow runtime interfaces
|
|
285
|
+
Handlers call methods on narrow runtime interfaces - no raw field writes, no `widget!` reach-throughs.
|
|
286
286
|
|
|
287
287
|
## Phase plan
|
|
288
288
|
|
|
@@ -314,27 +314,34 @@ Model strings are resolved inside the adapter.
|
|
|
314
314
|
Extracted tools, notifications, activity tracking, event handlers, and the `/agents` command into separate modules.
|
|
315
315
|
Created `SubagentRuntime` factory to hold session-scoped state.
|
|
316
316
|
|
|
317
|
-
### Phase 6 (
|
|
317
|
+
### Phase 6 (deferred): Extract UI to `@gotgenes/pi-subagents-ui`
|
|
318
318
|
|
|
319
|
-
|
|
319
|
+
The widget, conversation viewer, `/agents` command, notifications, and activity tracking are candidates for extraction to a separate extension that consumes `SubagentsService` + lifecycle events.
|
|
320
320
|
This phase is deferred until the API boundary is proven stable in production.
|
|
321
321
|
|
|
322
322
|
### Phase 7: Encapsulation and dependency narrowing
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
Every mutable state bag became a class, every dependency bag narrowed to what its consumer uses, every callback became either a method on a collaborator or an event on an observable.
|
|
325
325
|
|
|
326
|
-
The work is sequenced so each change makes the next change easy.
|
|
327
326
|
See the [Encapsulation roadmap](#encapsulation-roadmap) section for the full breakdown.
|
|
328
327
|
|
|
329
328
|
### Phase 8: Testability, display extraction, and menu decomposition
|
|
330
329
|
|
|
331
|
-
|
|
330
|
+
Eliminated `vi.mock()` module mocking in the two most fragile test suites by injecting IO-touching collaborators; consolidated shared test fixtures; extracted display helpers into a reusable module; decomposed the largest UI file.
|
|
332
331
|
|
|
333
332
|
See the [Phase 8 roadmap](#phase-8-roadmap) section for the full breakdown.
|
|
334
333
|
|
|
334
|
+
### Phase 9: Observation consolidation, ctx elimination, and remaining mocks
|
|
335
|
+
|
|
336
|
+
Target: consolidate the dual observation model so stats live in one place; remove `ExtensionContext` from all internal APIs; eliminate remaining `vi.mock()` calls and `as any` casts; split widget rendering from lifecycle; apply dependency bag convention.
|
|
337
|
+
|
|
338
|
+
See the [Phase 9 roadmap](#phase-9-roadmap) section for the full breakdown.
|
|
339
|
+
Issues: #144, #145, #146, #147, #148.
|
|
340
|
+
|
|
335
341
|
## Structural refactoring roadmap
|
|
336
342
|
|
|
337
|
-
Phases 1
|
|
343
|
+
Phases 1-5, 7, and 8 are complete.
|
|
344
|
+
Phase 6 (UI extraction) is deferred.
|
|
338
345
|
See `git log` for the full history; issue references are preserved below for traceability.
|
|
339
346
|
|
|
340
347
|
| Phase | Issue | Summary |
|
|
@@ -361,7 +368,7 @@ Issue #102 consolidated test `AgentRecord` construction into a shared factory.
|
|
|
361
368
|
Replaced live `ctx: ExtensionContext` capture in `SpawnArgs` with an immutable `ParentSnapshot` data object.
|
|
362
369
|
The snapshot is taken once at spawn time; queued agents execute against frozen state rather than a potentially stale session reference.
|
|
363
370
|
`runAgent()` accepts `ParentSnapshot` instead of `ctx`.
|
|
364
|
-
`pi: ExtensionAPI` was removed from `SpawnArgs`
|
|
371
|
+
`pi: ExtensionAPI` was removed from `SpawnArgs` - `runAgent()` accepts a `ShellExec` function instead.
|
|
365
372
|
|
|
366
373
|
### Step 3: Session-event observation (#100)
|
|
367
374
|
|
|
@@ -384,9 +391,9 @@ Replaced three-layer callback threading with direct session subscriptions.
|
|
|
384
391
|
|
|
385
392
|
## Encapsulation roadmap
|
|
386
393
|
|
|
387
|
-
|
|
394
|
+
Phase 7 encapsulated mutable state into classes, replaced callbacks with semantic components, and narrowed dependency bags.
|
|
388
395
|
|
|
389
|
-
Each step
|
|
396
|
+
Each step was sequenced so it made the next step easier.
|
|
390
397
|
|
|
391
398
|
### Resolved smells
|
|
392
399
|
|
|
@@ -419,7 +426,7 @@ Wrapped the module-scoped `agents` Map and free functions in `agent-types.ts` in
|
|
|
419
426
|
|
|
420
427
|
Encapsulated settings load/save/apply cycle into `SettingsManager` (in `settings.ts`).
|
|
421
428
|
Owns `defaultMaxTurns`, `graceTurns`, `maxConcurrent` with normalizing property accessors.
|
|
422
|
-
Added `applyMaxConcurrent(n)`, `applyDefaultMaxTurns(n)`, `applyGraceTurns(n)`
|
|
429
|
+
Added `applyMaxConcurrent(n)`, `applyDefaultMaxTurns(n)`, `applyGraceTurns(n)` - each owns the full consequence chain: normalize → set in memory → notify callback → persist → emit event → return toast.
|
|
423
430
|
The 6 settings-related fields in `AgentMenuDeps` collapsed to `settings: AgentMenuSettings`.
|
|
424
431
|
|
|
425
432
|
#### A3. AgentActivityTracker class (#110)
|
|
@@ -432,9 +439,9 @@ The shared map on `SubagentRuntime` is `Map<string, AgentActivityTracker>`.
|
|
|
432
439
|
|
|
433
440
|
Split post-construction mutation into phase-specific collaborators, each born complete:
|
|
434
441
|
|
|
435
|
-
- **`ExecutionState`** (`session`, `outputFile`)
|
|
436
|
-
- **`WorktreeState`** (`path`, `branch`, `cleanupResult`)
|
|
437
|
-
- **`NotificationState`** (`toolCallId`, `resultConsumed`)
|
|
442
|
+
- **`ExecutionState`** (`session`, `outputFile`) - constructed in `onSessionCreated`.
|
|
443
|
+
- **`WorktreeState`** (`path`, `branch`, `cleanupResult`) - constructed at worktree setup.
|
|
444
|
+
- **`NotificationState`** (`toolCallId`, `resultConsumed`) - constructed by `AgentManager.spawn()` when `toolCallId` is provided.
|
|
438
445
|
- **`pendingSteers`** moved to `Map<string, string[]>` on `AgentManager`.
|
|
439
446
|
- Stats encapsulated behind mutation methods with read-only getters.
|
|
440
447
|
- `AgentRecordInit` trimmed from 19 optional fields to 4 construction-time fields.
|
|
@@ -490,16 +497,15 @@ Extracted `foreground-runner.ts` (~175 lines) and `background-spawner.ts` (~116
|
|
|
490
497
|
|
|
491
498
|
### Dependency graph
|
|
492
499
|
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
B
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
E2 (Type housekeeping) ── can start after A1, runs parallel to later steps
|
|
500
|
+
```mermaid
|
|
501
|
+
flowchart LR
|
|
502
|
+
A1["A1: Registry"] --> D2["D2: Narrow deps"]
|
|
503
|
+
A2["A2: Settings"] --> A2b["A2b: Apply"] --> D2
|
|
504
|
+
A3["A3: Activity Tracker"] --> D2
|
|
505
|
+
B["B: Record lifecycle"] --> D2
|
|
506
|
+
B --> C["C: Observer"] --> D1["D1: SpawnOptions"] --> D2
|
|
507
|
+
D2 --> E1["E1: agent-tool split"]
|
|
508
|
+
A1 --> E2["E2: Type housekeeping"]
|
|
503
509
|
```
|
|
504
510
|
|
|
505
511
|
---
|
|
@@ -507,44 +513,44 @@ E2 (Type housekeeping) ── can start after A1, runs parallel to later steps
|
|
|
507
513
|
## Phase 8 roadmap
|
|
508
514
|
|
|
509
515
|
Phase 7 eliminated all structural smells (mutable state, closure bags, callback threading, wide dependency bags).
|
|
510
|
-
Phase 8
|
|
516
|
+
Phase 8 targeted the next layer: testability friction, display module cohesion, and menu decomposition.
|
|
511
517
|
|
|
512
|
-
|
|
513
|
-
|
|
518
|
+
Steps G and H eliminated 11 of the original 12 `vi.mock()` calls in the runner tests, removing fragile call-sequence assertions in favour of injected stubs.
|
|
519
|
+
Step G resolved `session-config.test.ts`; Step H resolved both `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts`.
|
|
514
520
|
|
|
515
|
-
The display and menu improvements were identified during Phase 7 but deferred because they
|
|
516
|
-
|
|
521
|
+
The display and menu improvements were identified during Phase 7 but deferred because they did not gate encapsulation work.
|
|
522
|
+
The display extraction unblocked menu decomposition.
|
|
517
523
|
|
|
518
|
-
### Test pain points
|
|
524
|
+
### Test pain points (resolved)
|
|
519
525
|
|
|
520
|
-
| Symptom
|
|
521
|
-
|
|
|
522
|
-
|
|
|
523
|
-
|
|
|
524
|
-
|
|
|
525
|
-
| 3× duplicated `mockSession()`
|
|
526
|
-
| 3× duplicated `makeDeps()`
|
|
527
|
-
| Weak assertions | lifecycle, renderer, session-config tests | `toHaveBeenCalled()` without args, `toContain()` on large strings |
|
|
526
|
+
| Symptom | Resolution |
|
|
527
|
+
| ------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
528
|
+
| 7 `vi.mock()` calls in `agent-runner.test.ts` | Step H (#133): injected `RunnerIO` stubs |
|
|
529
|
+
| 7 `vi.mock()` calls in `agent-runner-extension-tools.test.ts` | Step H (#133): same |
|
|
530
|
+
| 52 `as any` casts across test suite | Step I (#134): reduced to 15 |
|
|
531
|
+
| 3× duplicated `mockSession()` | Step F (#131): shared `createMockSession()` in `test/helpers/` |
|
|
532
|
+
| 3× duplicated `makeDeps()` | Step F (#131): shared `createToolDeps()` in `test/helpers/` |
|
|
528
533
|
|
|
529
|
-
|
|
530
|
-
The pattern is clear: modules that accept collaborators through injection produce resilient tests; modules that import collaborators directly produce fragile mock-heavy tests.
|
|
534
|
+
The well-designed test suites - `agent-manager.test.ts` (1 mock, DI via `AgentRunner` interface), `notification.test.ts` (0 mocks, pure functions + DI), and `agent-tool.test.ts` (0 mocks, tests via deps bag) - confirmed the pattern: modules that accept collaborators through injection produce resilient tests; modules that import collaborators directly produce fragile mock-heavy tests.
|
|
531
535
|
|
|
532
536
|
### Step F: Shared test fixtures (#131)
|
|
533
537
|
|
|
534
|
-
|
|
538
|
+
Consolidated duplicated mock factories into `test/helpers/`.
|
|
535
539
|
|
|
536
|
-
1. `createMockSession()`
|
|
537
|
-
2. `createToolDeps()`
|
|
540
|
+
1. `createMockSession()` - subscribable event bus with `emit()` helper; replaced 3 hand-rolled copies.
|
|
541
|
+
2. `createToolDeps()` - builds `AgentToolDeps` with sensible defaults and override support; replaced 3 `makeDeps()` copies.
|
|
542
|
+
3. `makeRecord()` - `AgentRecord` factory with sensible defaults; replaced scattered inline construction.
|
|
543
|
+
4. `STUB_CTX` - shared stub `ExtensionContext` constant; centralised unavoidable bridge casts.
|
|
538
544
|
|
|
539
|
-
Impact:
|
|
545
|
+
Impact: reduced test boilerplate; single source of truth for mock shapes; changes to dep interfaces propagate automatically.
|
|
540
546
|
|
|
541
|
-
### Step G: Inject IO collaborators into session-config (#132)
|
|
547
|
+
### Step G: Inject IO collaborators into session-config (#132)
|
|
542
548
|
|
|
543
549
|
`assembleSessionConfig` now accepts `io: AssemblerIO` as a required parameter.
|
|
544
550
|
`index.ts` constructs the real `AssemblerIO` from direct imports via the `RunnerIO.assemblerIO` field (wired in Step H).
|
|
545
|
-
`session-config.test.ts` injects stubs
|
|
551
|
+
`session-config.test.ts` injects stubs - all 4 `vi.mock()` calls eliminated, assertions shifted to `SessionConfig` output properties.
|
|
546
552
|
|
|
547
|
-
### Step H: Inject SDK boundary into agent-runner (#133)
|
|
553
|
+
### Step H: Inject SDK boundary into agent-runner (#133)
|
|
548
554
|
|
|
549
555
|
`runAgent()` now accepts `io: RunnerIO` as a required parameter bundling all IO collaborators: `detectEnv`, `getAgentDir`, `createResourceLoader`, `deriveSessionDir`, `createSessionManager`, `createSettingsManager`, `createSession`, and `assemblerIO`.
|
|
550
556
|
|
|
@@ -553,7 +559,7 @@ Impact: reduces test boilerplate; single source of truth for mock shapes; change
|
|
|
553
559
|
|
|
554
560
|
Impact: all 7 `vi.mock()` calls eliminated from both `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts`; tests verify behavior (turn limits, tool filtering, response collection) through injected stubs; SDK imports moved to the extension entry point.
|
|
555
561
|
|
|
556
|
-
### Step I: Reduce `as any` casts in tests (#134)
|
|
562
|
+
### Step I: Reduce `as any` casts in tests (#134)
|
|
557
563
|
|
|
558
564
|
Reduced `as any` count from 93 to 15 (plus 13 explicit `as unknown as T` bridge casts).
|
|
559
565
|
|
|
@@ -566,40 +572,171 @@ Production changes:
|
|
|
566
572
|
- `textResult()` return no longer casts `details as any`.
|
|
567
573
|
- `toAgentSession()` helper and `STUB_CTX` constant centralise unavoidable bridge casts.
|
|
568
574
|
|
|
569
|
-
Remaining 15 `as any` casts are: 8 menu-handler `ctx as any` (deferred
|
|
575
|
+
Remaining 15 `as any` casts are: 8 menu-handler `ctx as any` (deferred - requires `AgentManager.spawn` to accept `ParentSnapshot` directly), 2 `print-mode.test.ts` (same ExtensionContext/API pattern), 2 private-field test access, 1 `createSession` SDK bridge in `index.ts`, 1 `foreground-runner.ts` `AgentToolResult<any>` detail, 1 `stub-ctx.ts` comment.
|
|
570
576
|
|
|
571
|
-
### Step J: Extract display helpers (#135)
|
|
577
|
+
### Step J: Extract display helpers (#135)
|
|
572
578
|
|
|
573
579
|
`ui/display.ts` now contains all pure formatters, display helpers, constants, and shared types (`Theme`, `AgentDetails`).
|
|
574
580
|
`agent-widget.ts` dropped from 522 → ~340 lines.
|
|
575
581
|
All consumer modules (menu, tools, renderer, conversation viewer) import from `ui/display.ts` directly.
|
|
576
582
|
`test/agent-widget.test.ts` renamed to `test/display.test.ts`.
|
|
577
583
|
|
|
578
|
-
### Step K: Decompose agent-menu.ts (#136)
|
|
584
|
+
### Step K: Decompose agent-menu.ts (#136)
|
|
579
585
|
|
|
580
586
|
`agent-menu.ts` (668 lines) decomposed into four modules:
|
|
581
587
|
|
|
582
|
-
1. `ui/agent-file-ops.ts`
|
|
583
|
-
2. `ui/agent-config-editor.ts`
|
|
584
|
-
3. `ui/agent-creation-wizard.ts`
|
|
585
|
-
4. `ui/agent-menu.ts`
|
|
588
|
+
1. `ui/agent-file-ops.ts` - `AgentFileOps` interface (`exists`, `read`, `write`, `remove`, `ensureDir`, `findAgentFile`) + `FsAgentFileOps` production implementation.
|
|
589
|
+
2. `ui/agent-config-editor.ts` - `showAgentDetail` with edit/delete/reset/eject/disable/enable transitions (~200 lines).
|
|
590
|
+
3. `ui/agent-creation-wizard.ts` - AI-generation and manual-form creation paths (~250 lines).
|
|
591
|
+
4. `ui/agent-menu.ts` - menu orchestration, agent listing, running-agent viewer, settings form (~300 lines).
|
|
586
592
|
|
|
587
593
|
Impact: `agent-menu.ts` dropped from 668 → 296 lines; extracted modules receive `AgentFileOps` via injection; `vi.mock("node:fs")` eliminated from `agent-menu.test.ts`.
|
|
588
594
|
|
|
589
595
|
### Step dependencies
|
|
590
596
|
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
G
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
└── K (Menu decomposition) ────────────────────┘
|
|
597
|
+
```mermaid
|
|
598
|
+
flowchart LR
|
|
599
|
+
subgraph testability["Testability track"]
|
|
600
|
+
F["F: Shared fixtures"] --> G["G: session-config IO"] --> H["H: agent-runner SDK"] --> I["I: Reduce as-any"]
|
|
601
|
+
end
|
|
602
|
+
subgraph display["Display track"]
|
|
603
|
+
J["J: Display extraction"] --> K["K: Menu decomposition"]
|
|
604
|
+
end
|
|
600
605
|
```
|
|
601
606
|
|
|
602
|
-
|
|
607
|
+
The two tracks are independent and can proceed in parallel.
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## Phase 9 roadmap
|
|
612
|
+
|
|
613
|
+
Phases 7 and 8 addressed structural encapsulation and testability.
|
|
614
|
+
Phase 9 targets the next layer: observation model consolidation, `ExtensionContext` elimination from internal APIs, remaining `vi.mock()` / `as any` casts, and dependency bag cleanup.
|
|
615
|
+
|
|
616
|
+
### Current smells
|
|
617
|
+
|
|
618
|
+
| Smell | Location | Evidence | Severity |
|
|
619
|
+
| ------------------------------------------------ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
620
|
+
| Dual observation | `record-observer.ts`, `ui-observer.ts` | Both independently count tool uses and accumulate lifetime usage from the same session events; consumers use `activity?.toolUses ?? record.toolUses` fallbacks | High |
|
|
621
|
+
| `execute` does config resolution for its callees | `agent-tool.ts` (145-line `execute`) | ~60 lines unpack config, resolve model, compute metadata, repack into 16-field bags for spawners; `ctx` threaded 4 layers deep | Medium |
|
|
622
|
+
| Wide `ctx` in menu handlers | `agent-menu.ts`, `agent-config-editor.ts`, `agent-creation-wizard.ts` | Functions declare `ctx: ExtensionContext` but only call `ctx.ui.select/confirm/input/notify/editor`; 43 `ctx as any` casts across 3 test files | Medium |
|
|
623
|
+
| `record.execution?.session` traversal | 15+ callsites across tools, notification, widget, menu | Callers reach through `ExecutionState` to access session and outputFile - Law of Demeter violation | Medium |
|
|
624
|
+
| Direct SDK import in `conversation-viewer.ts` | `conversation-viewer.test.ts` | Hoisted `vi.mock("@earendil-works/pi-tui")` to intercept `wrapTextWithAnsi` | Low |
|
|
625
|
+
| Widget mixes rendering, lifecycle, and state | `agent-widget.ts` (370 lines) | `renderWidget` is ~109 lines mixing data collection, formatting, and overflow layout; constructor takes 3 concrete collaborators | Low |
|
|
626
|
+
| `deps.` prefix noise in function bodies | 12 modules across tools, UI, notification, service-adapter | Functions accept a `deps` bag and access every field as `deps.foo`; hides real dependencies and lengthens every call line | Low |
|
|
627
|
+
|
|
628
|
+
### Dependency bag convention
|
|
629
|
+
|
|
630
|
+
Applied incrementally as each step touches a module:
|
|
631
|
+
|
|
632
|
+
- **≤4 fields** — accept as plain parameters; drop the interface.
|
|
633
|
+
- **≥5 fields** — keep a named interface but destructure in the function signature (`{ manager, widget }: ForegroundDeps`) so the function body uses bare names, not `deps.foo`.
|
|
634
|
+
|
|
635
|
+
This eliminates the `deps.` prefix noise across ~124 callsites in 12 modules.
|
|
636
|
+
|
|
637
|
+
### Step L: Consolidate observation model (#144)
|
|
638
|
+
|
|
639
|
+
Remove `_toolUses` and `_lifetimeUsage` from `AgentActivityTracker`.
|
|
640
|
+
UI consumers read stats from `AgentRecord` instead of the tracker.
|
|
641
|
+
The UI observer retains event subscriptions for re-render triggers but no longer accumulates stats independently.
|
|
642
|
+
|
|
643
|
+
Add `session` and `outputFile` convenience getters on `AgentRecord` to hide the `execution?.` traversal.
|
|
644
|
+
The 15+ callsites that navigate `record.execution?.session` simplify to `record.session`.
|
|
645
|
+
|
|
646
|
+
Apply the dependency bag convention to touched modules: `NotificationDeps` (4 fields) becomes plain parameters on `NotificationManager` constructor.
|
|
647
|
+
|
|
648
|
+
Impact: eliminates dual counting; removes `??` fallback pattern from widget and conversation viewer; hides `ExecutionState` structure from consumers.
|
|
649
|
+
|
|
650
|
+
### Step M: Decompose execute and push ExtensionContext to the boundary (#145)
|
|
651
|
+
|
|
652
|
+
`execute` is 145 lines with three responsibilities mixed together:
|
|
653
|
+
|
|
654
|
+
1. **Boundary extraction** (~5 lines) - read `ctx.model`, `ctx.modelRegistry`, `ctx.ui`, `ctx.sessionManager`, call `buildParentSnapshot(ctx)`.
|
|
655
|
+
2. **Config resolution** (~60 lines) - resolve agent type, merge invocation config, resolve model, compute max turns, build tags and display metadata.
|
|
656
|
+
3. **Dispatch** (~80 lines) - resume / background / foreground, each passing 14-16 field parameter bags.
|
|
657
|
+
|
|
658
|
+
The config resolution section is working for the dependencies: manually unpacking `resolvedConfig` field by field, computing derived values, then repacking everything into massive objects for `spawnBackground` and `runForeground`.
|
|
659
|
+
The 16-field bags are the symptom - they exist because the resolution happened in the wrong place.
|
|
660
|
+
|
|
661
|
+
The fix has two parts:
|
|
662
|
+
|
|
663
|
+
1. **Extract config resolution** into a pure function (e.g. `resolveSpawnConfig`) that accepts the raw tool params, registry, model info, and settings, and returns a single `ResolvedSpawnConfig` object.
|
|
664
|
+
`execute` becomes: extract ctx → resolve config → dispatch.
|
|
665
|
+
`spawnBackground` and `runForeground` receive `ResolvedSpawnConfig` instead of 16 individual fields.
|
|
666
|
+
2. **Push `ctx` to the boundary.**
|
|
667
|
+
`execute` extracts everything from `ctx` in its first few lines.
|
|
668
|
+
`foreground-runner.ts` and `background-spawner.ts` receive domain values (`snapshot`, `parentSessionFile`, `parentSessionId`) instead of `ctx`.
|
|
669
|
+
`AgentManager.spawn()` and `spawnAndWait()` accept `ParentSnapshot` instead of `ExtensionContext`.
|
|
670
|
+
`service-adapter.ts` calls `buildParentSnapshot(session.ctx)` at its boundary.
|
|
671
|
+
|
|
672
|
+
After this step, `ExtensionContext` appears only in:
|
|
673
|
+
|
|
674
|
+
- `agent-tool.ts execute` (SDK callback - unavoidable)
|
|
675
|
+
- `service-adapter.ts` (cross-extension boundary)
|
|
676
|
+
- `index.ts` (extension entry point)
|
|
677
|
+
- Menu handlers (addressed by Step N)
|
|
678
|
+
|
|
679
|
+
Apply the dependency bag convention to touched modules: `ForegroundDeps` (3 fields) and `BackgroundDeps` (3 fields) become plain parameters; `AdapterDeps` (3 fields) becomes plain parameters; `AgentToolDeps` (6 fields) is destructured in the signature.
|
|
680
|
+
|
|
681
|
+
Impact: `execute` drops from ~145 to ~30 lines; eliminates 16-field parameter bags; eliminates 1 `vi.mock()` call in `agent-manager.test.ts`; `foreground-runner` and `background-spawner` tests no longer need `ctx` mocks; `AgentManager` operates entirely on domain types.
|
|
682
|
+
|
|
683
|
+
### Step N: Narrow UI context for menu handlers (#146)
|
|
684
|
+
|
|
685
|
+
Define a `MenuUI` interface with `select`, `confirm`, `input`, `notify`, and `editor` methods.
|
|
686
|
+
Menu handler functions (`showAgentsMenu`, `showAgentDetail`, `showCreateWizard`, etc.) accept `MenuUI` instead of `ExtensionContext`.
|
|
687
|
+
`index.ts` passes `ctx.ui` at the call site.
|
|
688
|
+
|
|
689
|
+
Creation wizard’s `spawnAndWait` call changes: the narrow `AgentMenuManager.spawnAndWait` accepts `ParentSnapshot` (enabled by Step M) instead of `ExtensionContext`.
|
|
690
|
+
|
|
691
|
+
Apply the dependency bag convention to touched modules: `AgentConfigEditorDeps` (4 fields), `SteerToolDeps` (4 fields), and `GetResultDeps` (4 fields) become plain parameters; `AgentMenuDeps` (8 fields) and `AgentCreationWizardDeps` (5 fields) are destructured in the signature.
|
|
692
|
+
|
|
693
|
+
After Steps M and N, `ExtensionContext` appears only at true boundaries: `agent-tool.ts execute` (SDK callback), `service-adapter.ts` (cross-extension bridge), and `index.ts` (extension entry point).
|
|
694
|
+
|
|
695
|
+
Impact: eliminates ~43 `ctx as any` casts across menu, editor, and wizard test files; tests construct a plain object satisfying `MenuUI` with no cast.
|
|
696
|
+
|
|
697
|
+
### Step O: Inject text wrapping into ConversationViewer (#147)
|
|
698
|
+
|
|
699
|
+
Accept a `wrapText` function via `ConversationViewerOptions`.
|
|
700
|
+
`index.ts` passes the real `wrapTextWithAnsi` import.
|
|
701
|
+
Tests inject a stub or the real function directly - no module-level mock needed.
|
|
702
|
+
|
|
703
|
+
Apply the dependency bag convention: `ConversationViewerOptions` is destructured in the constructor signature.
|
|
704
|
+
|
|
705
|
+
Impact: eliminates the hoisted `vi.mock("@earendil-works/pi-tui")` in `conversation-viewer.test.ts`.
|
|
706
|
+
|
|
707
|
+
### Step P: Split AgentWidget rendering (#148)
|
|
708
|
+
|
|
709
|
+
Extract pure rendering functions from `AgentWidget` into `ui/widget-renderer.ts`.
|
|
710
|
+
The widget becomes a thin lifecycle/polling wrapper that calls pure render functions.
|
|
711
|
+
Rendering functions receive data (agent list, activity map, registry) and return formatted strings - testable without widget lifecycle.
|
|
712
|
+
|
|
713
|
+
Depends on Step L: once the tracker drops stats fields, the renderer reads from `AgentRecord` for tool uses and usage, and from `AgentActivityTracker` only for live UI state (active tools, response text, turn count).
|
|
714
|
+
|
|
715
|
+
### Step dependencies
|
|
716
|
+
|
|
717
|
+
```mermaid
|
|
718
|
+
flowchart LR
|
|
719
|
+
subgraph observation["Observation track"]
|
|
720
|
+
L["L: Consolidate observation #144"] --> P["P: Split widget rendering #148"]
|
|
721
|
+
end
|
|
722
|
+
subgraph ctx["ctx elimination track"]
|
|
723
|
+
M["M: Decompose execute / push ctx #145"] --> N["N: Narrow UI context #146"]
|
|
724
|
+
end
|
|
725
|
+
O["O: Inject text wrapping #147"]
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
The three tracks are independent of each other.
|
|
729
|
+
|
|
730
|
+
### Projected impact
|
|
731
|
+
|
|
732
|
+
| Metric | Before | After |
|
|
733
|
+
| ---------------------------------- | ------------------------ | ------------------------ |
|
|
734
|
+
| `vi.mock()` calls remaining | 4 | 1 (`print-mode.test.ts`) |
|
|
735
|
+
| `as any` casts remaining | 45 | ~5 |
|
|
736
|
+
| Independent tool-use counters | 2 | 1 |
|
|
737
|
+
| `record.execution?.` traversals | 15+ | 0 |
|
|
738
|
+
| `ExtensionContext` in domain types | 1 (`AgentManager.spawn`) | 0 |
|
|
739
|
+
| `deps.` prefix accesses | ~124 | 0 |
|
|
603
740
|
|
|
604
741
|
---
|
|
605
742
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 136
|
|
3
|
+
issue_title: "Decompose `agent-menu.ts`"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #136 — Decompose `agent-menu.ts`
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-22T20:10:00-04:00)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Decomposed `agent-menu.ts` (668 lines) into four focused modules: `agent-file-ops.ts`, `agent-config-editor.ts`, `agent-creation-wizard.ts`, and a slimmed-down `agent-menu.ts` (296 lines).
|
|
13
|
+
Three TDD cycles shipped cleanly, adding 47 tests (714 → 761) and eliminating `vi.mock("node:fs")` from the menu test file.
|
|
14
|
+
Released as `pi-subagents-v6.13.0`.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
#### What went well
|
|
19
|
+
|
|
20
|
+
- The three-cycle TDD plan (file-ops → config-editor → creation-wizard) produced clean incremental commits with no rework.
|
|
21
|
+
Each cycle left the repo green for both tests and type-check.
|
|
22
|
+
- The creation wizard naturally produced narrower interfaces (`WizardManager`, `WizardRegistry`) than the plan specified — a positive deviation from the plan's `AgentTypeRegistry` concrete type, following ISP more strictly.
|
|
23
|
+
- The large edit removing ~170 lines of extracted functions from `agent-menu.ts` in Cycle 2 landed correctly on the first attempt, thanks to using exact `oldText` matching with the full function bodies.
|
|
24
|
+
|
|
25
|
+
#### What caused friction (agent side)
|
|
26
|
+
|
|
27
|
+
- `missing-context` — The config-editor test factory used `Partial<AgentConfigEditorDeps>` for the overrides parameter.
|
|
28
|
+
The `...overrides` spread created a union type that erased `Mock<...>` methods from `fileOps`, producing 28 `TS2339` errors on `pnpm run check`.
|
|
29
|
+
The testing skill warns about return-type annotations but not about `Partial<Interface>` in overrides — the same erasure mechanism applies through a different path.
|
|
30
|
+
Impact: one extra edit-check cycle to remove the `Partial<>` annotation and overrides parameter.
|
|
31
|
+
Self-identified (caught on `pnpm run check` before commit).
|
|
32
|
+
|
|
33
|
+
- `missing-context` — The config-editor test had an unused `ctx` variable from an earlier draft of the "disable-only file" test, caught only by the linter after the Cycle 3 commit.
|
|
34
|
+
Impact: required amending the Cycle 3 commit; added friction but no rework.
|
|
35
|
+
Self-identified (caught by `pnpm run lint`).
|
|
36
|
+
|
|
37
|
+
#### What caused friction (user side)
|
|
38
|
+
|
|
39
|
+
- None observed — the user's plan was unambiguous and the prerequisite (#135) was already implemented.
|
|
40
|
+
|
|
41
|
+
### Changes made
|
|
42
|
+
|
|
43
|
+
1. Added a `Partial<Interface>` type-erasure bullet to `.pi/skills/testing/SKILL.md`.
|