@gotgenes/pi-subagents 6.17.0 → 6.17.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 +27 -0
- package/docs/architecture/architecture.md +588 -536
- package/docs/architecture/history/phase-1-api-boundary.md +8 -0
- package/docs/architecture/history/phase-2-remove-scheduling.md +9 -0
- package/docs/architecture/history/phase-3-remove-rpc-groupjoin.md +11 -0
- package/docs/architecture/history/phase-4-implement-service.md +8 -0
- package/docs/architecture/history/phase-5-decompose-index.md +42 -0
- package/docs/architecture/history/phase-7-encapsulation.md +173 -0
- package/docs/architecture/history/phase-8-testability.md +103 -0
- package/docs/architecture/history/phase-9-observation-ctx.md +122 -0
- package/docs/plans/0164-reorganize-into-domain-directories.md +409 -0
- package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +40 -0
- package/docs/retro/0164-reorganize-into-domain-directories.md +46 -0
- package/package.json +5 -1
- package/src/{agent-types.ts → config/agent-types.ts} +2 -2
- package/src/{custom-agents.ts → config/custom-agents.ts} +3 -3
- package/src/{default-agents.ts → config/default-agents.ts} +1 -1
- package/src/{invocation-config.ts → config/invocation-config.ts} +1 -1
- package/src/handlers/index.ts +2 -2
- package/src/index.ts +26 -26
- package/src/{agent-manager.ts → lifecycle/agent-manager.ts} +11 -11
- package/src/{agent-record.ts → lifecycle/agent-record.ts} +6 -6
- package/src/{agent-runner.ts → lifecycle/agent-runner.ts} +6 -6
- package/src/{parent-snapshot.ts → lifecycle/parent-snapshot.ts} +1 -1
- package/src/{worktree-state.ts → lifecycle/worktree-state.ts} +1 -1
- package/src/{worktree.ts → lifecycle/worktree.ts} +1 -1
- package/src/{notification.ts → observation/notification.ts} +4 -4
- package/src/{record-observer.ts → observation/record-observer.ts} +2 -2
- package/src/{renderer.ts → observation/renderer.ts} +2 -2
- package/src/runtime.ts +2 -2
- package/src/{service-adapter.ts → service/service-adapter.ts} +5 -5
- package/src/{service.ts → service/service.ts} +1 -1
- package/src/{env.ts → session/env.ts} +2 -2
- package/src/{memory.ts → session/memory.ts} +2 -2
- package/src/{prompts.ts → session/prompts.ts} +2 -2
- package/src/{session-config.ts → session/session-config.ts} +5 -5
- package/src/{skill-loader.ts → session/skill-loader.ts} +2 -2
- package/src/tools/agent-tool.ts +11 -12
- package/src/tools/background-spawner.ts +8 -8
- package/src/tools/foreground-runner.ts +14 -14
- package/src/tools/get-result-tool.ts +5 -5
- package/src/tools/helpers.ts +4 -4
- package/src/tools/spawn-config.ts +6 -6
- package/src/tools/steer-tool.ts +3 -3
- package/src/types.ts +1 -1
- package/src/ui/agent-activity-tracker.ts +1 -1
- package/src/ui/agent-config-editor.ts +4 -4
- package/src/ui/agent-creation-wizard.ts +5 -5
- package/src/ui/agent-menu.ts +10 -10
- package/src/ui/agent-widget.ts +5 -5
- package/src/ui/conversation-viewer.ts +6 -6
- package/src/ui/display.ts +2 -2
- package/src/ui/ui-observer.ts +1 -1
- package/src/ui/widget-renderer.ts +5 -5
- package/vitest.config.ts +14 -0
- /package/src/{execution-state.ts → lifecycle/execution-state.ts} +0 -0
- /package/src/{usage.ts → lifecycle/usage.ts} +0 -0
- /package/src/{notification-state.ts → observation/notification-state.ts} +0 -0
- /package/src/{context.ts → session/context.ts} +0 -0
- /package/src/{model-resolver.ts → session/model-resolver.ts} +0 -0
- /package/src/{session-dir.ts → session/session-dir.ts} +0 -0
|
@@ -4,92 +4,365 @@ 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
|
+
## Domain model
|
|
27
27
|
|
|
28
|
-
The extension is organized
|
|
28
|
+
The extension is organized around six domains, each responsible for one aspect of managing agents.
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
flowchart TB
|
|
32
|
+
subgraph config["Config domain"]
|
|
33
|
+
direction TB
|
|
34
|
+
AgentTypeRegistry["AgentTypeRegistry\n(registry of agent types)"]
|
|
35
|
+
DefaultAgents["default-agents\n(built-in types)"]
|
|
36
|
+
CustomAgents["custom-agents\n(user .md files)"]
|
|
37
|
+
InvocationConfig["invocation-config\n(per-call merge)"]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph session["Session domain"]
|
|
41
|
+
direction TB
|
|
42
|
+
SessionConfig["assembleSessionConfig\n(pure assembler)"]
|
|
43
|
+
Prompts["prompts\n(system prompt)"]
|
|
44
|
+
Context["context\n(parent history)"]
|
|
45
|
+
Memory["memory\n(MEMORY.md)"]
|
|
46
|
+
SkillLoader["skill-loader\n(preload skills)"]
|
|
47
|
+
Env["env\n(git/platform)"]
|
|
48
|
+
ModelResolver["model-resolver\n(fuzzy match)"]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
subgraph lifecycle["Lifecycle domain"]
|
|
52
|
+
direction TB
|
|
53
|
+
AgentManager["AgentManager\n(spawn, queue, abort)"]
|
|
54
|
+
AgentRunner["agent-runner\n(session, turns, results)"]
|
|
55
|
+
AgentRecord["AgentRecord\n(status state machine)"]
|
|
56
|
+
ParentSnapshot["ParentSnapshot\n(frozen parent state)"]
|
|
57
|
+
Worktree["worktree\n(git isolation)"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
subgraph observation["Observation domain"]
|
|
61
|
+
direction TB
|
|
62
|
+
RecordObserver["record-observer\n(stats via events)"]
|
|
63
|
+
Notification["notification\n(completion nudges)"]
|
|
64
|
+
UIObserver["ui-observer\n(streaming state)"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
subgraph tools["Tools domain"]
|
|
68
|
+
direction TB
|
|
69
|
+
AgentTool["Agent tool\n(dispatch)"]
|
|
70
|
+
SpawnConfig["spawn-config\n(resolve params)"]
|
|
71
|
+
FgRunner["foreground-runner"]
|
|
72
|
+
BgSpawner["background-spawner"]
|
|
73
|
+
GetResult["get_subagent_result"]
|
|
74
|
+
Steer["steer_subagent"]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
subgraph ui["UI domain"]
|
|
78
|
+
direction TB
|
|
79
|
+
Widget["agent-widget\n(live status)"]
|
|
80
|
+
ConvViewer["conversation-viewer\n(session overlay)"]
|
|
81
|
+
Menu["agent-menu\n(slash command)"]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
AgentTool --> AgentManager
|
|
85
|
+
AgentManager --> AgentRunner
|
|
86
|
+
AgentRunner --> SessionConfig
|
|
87
|
+
SessionConfig --> AgentTypeRegistry
|
|
88
|
+
SessionConfig --> Prompts & Memory & SkillLoader & Env
|
|
89
|
+
AgentTypeRegistry --> DefaultAgents & CustomAgents
|
|
90
|
+
RecordObserver -.->|subscribes| AgentRunner
|
|
91
|
+
UIObserver -.->|subscribes| AgentRunner
|
|
92
|
+
Widget -.->|polls| AgentManager
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Key domain types
|
|
96
|
+
|
|
97
|
+
```mermaid
|
|
98
|
+
classDiagram
|
|
99
|
+
class AgentRecord {
|
|
100
|
+
+id: string
|
|
101
|
+
+type: SubagentType
|
|
102
|
+
+description: string
|
|
103
|
+
+status: AgentRecordStatus
|
|
104
|
+
+result?: string
|
|
105
|
+
+error?: string
|
|
106
|
+
+toolUses: number
|
|
107
|
+
+lifetimeUsage: LifetimeUsage
|
|
108
|
+
+execution?: ExecutionState
|
|
109
|
+
+worktreeState?: WorktreeState
|
|
110
|
+
+notification?: NotificationState
|
|
111
|
+
+markRunning()
|
|
112
|
+
+markCompleted()
|
|
113
|
+
+markAborted()
|
|
114
|
+
+markSteered()
|
|
115
|
+
+markError()
|
|
116
|
+
+markStopped()
|
|
117
|
+
+resetForResume()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class AgentManager {
|
|
121
|
+
+spawn(snapshot, type, prompt, config)
|
|
122
|
+
+spawnAndWait(snapshot, type, prompt, config)
|
|
123
|
+
+resume(id, snapshot, exec)
|
|
124
|
+
+getRecord(id): AgentRecord
|
|
125
|
+
+listAgents(): AgentRecord[]
|
|
126
|
+
+abort(id)
|
|
127
|
+
+queueSteer(id, message)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class AgentTypeRegistry {
|
|
131
|
+
+resolveType(type): string
|
|
132
|
+
+resolveAgentConfig(type): AgentConfig
|
|
133
|
+
+reload()
|
|
134
|
+
+getToolNamesForType(type): string[]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class ParentSnapshot {
|
|
138
|
+
+cwd: string
|
|
139
|
+
+systemPrompt: string
|
|
140
|
+
+model: unknown
|
|
141
|
+
+modelRegistry: unknown
|
|
142
|
+
+parentContext?: string
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class SubagentsService {
|
|
146
|
+
+spawn(type, prompt, options?)
|
|
147
|
+
+getRecord(id): SubagentRecord
|
|
148
|
+
+listAgents(): SubagentRecord[]
|
|
149
|
+
+abort(id)
|
|
150
|
+
+steer(id, message)
|
|
151
|
+
+waitForAll()
|
|
152
|
+
+hasRunning(): boolean
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
AgentManager --> AgentRecord : creates/manages
|
|
156
|
+
AgentManager --> ParentSnapshot : receives at spawn
|
|
157
|
+
SubagentsService --> AgentManager : wraps via adapter
|
|
158
|
+
AgentManager --> AgentTypeRegistry : resolves types
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Agent lifecycle
|
|
162
|
+
|
|
163
|
+
```mermaid
|
|
164
|
+
stateDiagram-v2
|
|
165
|
+
[*] --> queued : spawn (background, at capacity)
|
|
166
|
+
[*] --> running : spawn (foreground or under limit)
|
|
167
|
+
queued --> running : capacity available
|
|
168
|
+
running --> completed : all turns finished
|
|
169
|
+
running --> error : unhandled exception
|
|
170
|
+
running --> aborted : abort() called
|
|
171
|
+
running --> stopped : max turns reached
|
|
172
|
+
running --> steered : steer message injected
|
|
173
|
+
steered --> running : continues with message
|
|
174
|
+
completed --> running : resetForResume
|
|
175
|
+
stopped --> running : resetForResume
|
|
176
|
+
error --> running : resetForResume
|
|
177
|
+
aborted --> running : resetForResume
|
|
178
|
+
completed --> [*]
|
|
179
|
+
error --> [*]
|
|
180
|
+
aborted --> [*]
|
|
181
|
+
stopped --> [*]
|
|
182
|
+
|
|
183
|
+
note right of running
|
|
184
|
+
markCompleted, markAborted,
|
|
185
|
+
markSteered, and markError
|
|
186
|
+
are no-ops when status is stopped
|
|
187
|
+
end note
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Note: `markStopped` always succeeds regardless of current status.
|
|
191
|
+
Other terminal transitions guard against overwriting `stopped` — once an agent is stopped, only `resetForResume` can return it to `running`.
|
|
192
|
+
|
|
193
|
+
## Execution flow
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
sequenceDiagram
|
|
197
|
+
participant LLM as Parent LLM
|
|
198
|
+
participant Tool as Agent tool
|
|
199
|
+
participant Spawn as spawn-config
|
|
200
|
+
participant Mgr as AgentManager
|
|
201
|
+
participant Runner as agent-runner
|
|
202
|
+
participant Asm as assembleSessionConfig
|
|
203
|
+
participant Child as Child session
|
|
204
|
+
|
|
205
|
+
LLM->>Tool: Agent(type, prompt, ...)
|
|
206
|
+
Tool->>Spawn: resolveSpawnConfig(params)
|
|
207
|
+
Spawn-->>Tool: ResolvedSpawnConfig
|
|
208
|
+
Tool->>Mgr: spawn(snapshot, type, prompt, config)
|
|
209
|
+
Mgr->>Runner: runAgent(record, snapshot, options, io)
|
|
210
|
+
Runner->>Asm: assembleSessionConfig(type, ctx, opts, env, registry, io)
|
|
211
|
+
Asm-->>Runner: SessionConfig
|
|
212
|
+
Runner->>Child: create session + run turn loop
|
|
213
|
+
Child-->>Runner: result text
|
|
214
|
+
Runner-->>Mgr: update AgentRecord
|
|
215
|
+
Note over Mgr: record-observer subscribes to session events for stats
|
|
216
|
+
Note over Mgr: ui-observer subscribes for streaming state
|
|
217
|
+
Mgr-->>Tool: AgentRecord
|
|
218
|
+
Tool-->>LLM: formatted result
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Module organization
|
|
222
|
+
|
|
223
|
+
The extension has 53 source files (7,288 LOC) organized into six domains plus entry-point wiring.
|
|
224
|
+
All eight domains now have directories: `config/`, `session/`, `lifecycle/`, `observation/`, `service/`, `tools/`, `ui/`, and `handlers/`.
|
|
225
|
+
Issue #164 moved the 26 previously flat root-level files into the five new domain directories.
|
|
226
|
+
|
|
227
|
+
### Current layout
|
|
228
|
+
|
|
229
|
+
```text
|
|
230
|
+
src/
|
|
231
|
+
├── index.ts entry point, tool registration, event wiring
|
|
232
|
+
├── runtime.ts SubagentRuntime factory (session-scoped state)
|
|
233
|
+
├── types.ts shared type definitions
|
|
234
|
+
├── settings.ts SettingsManager (persistent operational settings)
|
|
235
|
+
├── debug.ts debug logging utility
|
|
236
|
+
│
|
|
237
|
+
│ ── Config domain (agent type definitions and resolution) ──
|
|
238
|
+
├── agent-types.ts AgentTypeRegistry class
|
|
239
|
+
├── default-agents.ts built-in agent configs (general-purpose, Explore, Plan)
|
|
240
|
+
├── custom-agents.ts user-defined agent .md file loader
|
|
241
|
+
├── invocation-config.ts per-call config merge
|
|
242
|
+
│
|
|
243
|
+
│ ── Session domain (session assembly and preparation) ──
|
|
244
|
+
├── session-config.ts pure assembler (main entry)
|
|
245
|
+
├── prompts.ts system prompt building
|
|
246
|
+
├── context.ts parent conversation extraction
|
|
247
|
+
├── memory.ts persistent MEMORY.md per agent
|
|
248
|
+
├── skill-loader.ts skill preloading
|
|
249
|
+
├── env.ts git/platform detection
|
|
250
|
+
├── model-resolver.ts fuzzy model name resolution
|
|
251
|
+
├── session-dir.ts session directory derivation
|
|
252
|
+
│
|
|
253
|
+
│ ── Lifecycle domain (agent execution and state) ──
|
|
254
|
+
├── agent-manager.ts spawn, queue, abort, resume, concurrency
|
|
255
|
+
├── agent-runner.ts session creation, turn loop, tool filtering
|
|
256
|
+
├── agent-record.ts status state machine
|
|
257
|
+
├── parent-snapshot.ts immutable spawn-time parent state
|
|
258
|
+
├── execution-state.ts session/output phase state
|
|
259
|
+
├── worktree.ts git worktree isolation
|
|
260
|
+
├── worktree-state.ts worktree phase state
|
|
261
|
+
├── usage.ts token usage tracking
|
|
262
|
+
│
|
|
263
|
+
│ ── Observation domain (progress tracking and notification) ──
|
|
264
|
+
├── record-observer.ts session-event stats observer
|
|
265
|
+
├── notification.ts completion nudges
|
|
266
|
+
├── notification-state.ts per-agent notification tracking
|
|
267
|
+
├── renderer.ts notification TUI component
|
|
268
|
+
│
|
|
269
|
+
│ ── Service domain (cross-extension API) ──
|
|
270
|
+
├── service.ts SubagentsService interface + Symbol.for() accessors
|
|
271
|
+
├── service-adapter.ts SubagentsService wrapper around AgentManager
|
|
272
|
+
│
|
|
273
|
+
│ ── Tools domain (LLM-facing tool implementations) ──
|
|
274
|
+
├── tools/
|
|
275
|
+
│ ├── agent-tool.ts Agent tool definition, validation, dispatch
|
|
276
|
+
│ ├── spawn-config.ts pure config resolution
|
|
277
|
+
│ ├── foreground-runner.ts foreground execution loop
|
|
278
|
+
│ ├── background-spawner.ts background spawn setup
|
|
279
|
+
│ ├── get-result-tool.ts get_subagent_result tool
|
|
280
|
+
│ ├── steer-tool.ts steer_subagent tool
|
|
281
|
+
│ └── helpers.ts shared tool utilities
|
|
282
|
+
│
|
|
283
|
+
│ ── UI domain (user-facing presentation) ──
|
|
284
|
+
├── ui/
|
|
285
|
+
│ ├── agent-widget.ts above-editor live status widget
|
|
286
|
+
│ ├── widget-renderer.ts pure rendering for widget
|
|
287
|
+
│ ├── agent-menu.ts /agents slash command menu
|
|
288
|
+
│ ├── agent-config-editor.ts agent detail/edit view
|
|
289
|
+
│ ├── agent-creation-wizard.ts agent creation (AI + manual)
|
|
290
|
+
│ ├── conversation-viewer.ts scrollable session overlay
|
|
291
|
+
│ ├── agent-activity-tracker.ts live activity state tracker
|
|
292
|
+
│ ├── agent-file-ops.ts filesystem abstraction
|
|
293
|
+
│ ├── ui-observer.ts session-event observer for streaming
|
|
294
|
+
│ └── display.ts pure formatters and shared types
|
|
295
|
+
│
|
|
296
|
+
│ ── Event handlers ──
|
|
297
|
+
└── handlers/
|
|
298
|
+
├── lifecycle.ts session_start, session_before_switch, session_shutdown
|
|
299
|
+
└── tool-start.ts tool_execution_start handler
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Proposed directory restructuring
|
|
303
|
+
|
|
304
|
+
Move the four ungrouped domains into subdirectories so the filesystem mirrors the domain model.
|
|
305
|
+
Root-level files stay: `index.ts` (entry point), `runtime.ts` (wiring), `types.ts` (shared), `settings.ts`, `debug.ts`.
|
|
29
306
|
|
|
30
307
|
```text
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
notification.ts
|
|
68
|
-
renderer.ts
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
ui/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
default-agents.ts - embedded default agent configs (general-purpose, Explore, Plan)
|
|
79
|
-
custom-agents.ts - user-defined agent .md file loader
|
|
80
|
-
debug.ts - debug logging utility
|
|
308
|
+
src/
|
|
309
|
+
├── index.ts
|
|
310
|
+
├── runtime.ts
|
|
311
|
+
├── types.ts
|
|
312
|
+
├── settings.ts
|
|
313
|
+
├── debug.ts
|
|
314
|
+
│
|
|
315
|
+
├── config/ agent type definitions and resolution
|
|
316
|
+
│ ├── agent-types.ts
|
|
317
|
+
│ ├── default-agents.ts
|
|
318
|
+
│ ├── custom-agents.ts
|
|
319
|
+
│ └── invocation-config.ts
|
|
320
|
+
│
|
|
321
|
+
├── session/ session assembly and preparation
|
|
322
|
+
│ ├── session-config.ts
|
|
323
|
+
│ ├── prompts.ts
|
|
324
|
+
│ ├── context.ts
|
|
325
|
+
│ ├── memory.ts
|
|
326
|
+
│ ├── skill-loader.ts
|
|
327
|
+
│ ├── env.ts
|
|
328
|
+
│ ├── model-resolver.ts
|
|
329
|
+
│ └── session-dir.ts
|
|
330
|
+
│
|
|
331
|
+
├── lifecycle/ agent execution and state tracking
|
|
332
|
+
│ ├── agent-manager.ts
|
|
333
|
+
│ ├── agent-runner.ts
|
|
334
|
+
│ ├── agent-record.ts
|
|
335
|
+
│ ├── parent-snapshot.ts
|
|
336
|
+
│ ├── execution-state.ts
|
|
337
|
+
│ ├── worktree.ts
|
|
338
|
+
│ ├── worktree-state.ts
|
|
339
|
+
│ └── usage.ts
|
|
340
|
+
│
|
|
341
|
+
├── observation/ progress tracking and notification
|
|
342
|
+
│ ├── record-observer.ts
|
|
343
|
+
│ ├── notification.ts
|
|
344
|
+
│ ├── notification-state.ts
|
|
345
|
+
│ └── renderer.ts
|
|
346
|
+
│
|
|
347
|
+
├── service/ cross-extension API boundary
|
|
348
|
+
│ ├── service.ts
|
|
349
|
+
│ └── service-adapter.ts
|
|
350
|
+
│
|
|
351
|
+
├── tools/ (existing)
|
|
352
|
+
├── ui/ (existing)
|
|
353
|
+
└── handlers/ (existing)
|
|
81
354
|
```
|
|
82
355
|
|
|
356
|
+
Root goes from 31 files to 5 files + 8 directories — each directory name tells you what domain it belongs to.
|
|
357
|
+
|
|
83
358
|
### Observation model
|
|
84
359
|
|
|
85
360
|
Record statistics (tool uses, token usage, compaction counts) are updated by `record-observer.ts`, which subscribes directly to session events.
|
|
86
361
|
UI streaming (active tools, response text, turn counts) is handled by `ui/ui-observer.ts`, which subscribes to the same session events independently.
|
|
87
|
-
Neither observer wraps or forwards the other
|
|
362
|
+
Neither observer wraps or forwards the other — both subscribe directly to the session.
|
|
88
363
|
|
|
89
364
|
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.
|
|
90
365
|
|
|
91
|
-
Cross-extension consumers use the typed `SubagentsService` API published via `Symbol.for("@gotgenes/pi-subagents:service")` on `globalThis`.
|
|
92
|
-
|
|
93
366
|
## Cross-extension architecture
|
|
94
367
|
|
|
95
368
|
```mermaid
|
|
@@ -98,7 +371,7 @@ flowchart TD
|
|
|
98
371
|
direction TB
|
|
99
372
|
exports["SubagentsService interface\npublish / getSubagentsService()\nSubagentRecord, SubagentStatus, LifetimeUsage\nSUBAGENT_EVENTS constants"]
|
|
100
373
|
engine["Agent + get_subagent_result + steer_subagent tools\nAgentManager, agent-runner, agent-types\npublishSubagentsService() called at init"]
|
|
101
|
-
|
|
374
|
+
ui_int["Internal UI: widget, viewer, /agents menu\n(candidate for extraction to pi-subagents-ui)"]
|
|
102
375
|
end
|
|
103
376
|
|
|
104
377
|
core -- "Symbol.for() on globalThis" --> sched["scheduling extension\n(hypothetical)"]
|
|
@@ -112,32 +385,28 @@ They declare this package as an optional peer dependency and use dynamic import
|
|
|
112
385
|
### What the core owns
|
|
113
386
|
|
|
114
387
|
- The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
|
|
115
|
-
- `AgentManager`
|
|
116
|
-
- `agent-runner`
|
|
117
|
-
- `session-config`
|
|
118
|
-
- `SubagentRuntime`
|
|
119
|
-
- `ParentSnapshot`
|
|
120
|
-
- `record-observer`
|
|
121
|
-
- Agent type registry
|
|
388
|
+
- `AgentManager` — spawn, queue, abort, resume, concurrency control.
|
|
389
|
+
- `agent-runner` — session creation, turn loop, tool filtering, extension binding (Patches 2 and 3).
|
|
390
|
+
- `session-config` — pure configuration assembler (extracted from `agent-runner`).
|
|
391
|
+
- `SubagentRuntime` — session-scoped state bag with methods.
|
|
392
|
+
- `ParentSnapshot` — immutable snapshot of parent session state, captured once at spawn time.
|
|
393
|
+
- `record-observer` — session-event observer that updates record statistics without callback threading.
|
|
394
|
+
- Agent type registry — default agents, custom `.md` file loading.
|
|
122
395
|
- Prompt assembly, context extraction, memory, skills, environment.
|
|
123
396
|
- Worktree isolation.
|
|
124
397
|
- Token usage tracking.
|
|
125
398
|
- Session directory derivation and persisted `SessionManager` for subagent transcripts.
|
|
126
399
|
- Settings persistence.
|
|
127
|
-
- Internal UI (widget, conversation viewer, `/agents` menu)
|
|
400
|
+
- Internal UI (widget, conversation viewer, `/agents` menu) — these stay until the API boundary is proven, then move to a separate extension.
|
|
128
401
|
|
|
129
402
|
### What the core dropped
|
|
130
403
|
|
|
131
|
-
- **Scheduling** (`schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`)
|
|
132
|
-
|
|
133
|
-
- **
|
|
134
|
-
- **
|
|
135
|
-
|
|
136
|
-
- **
|
|
137
|
-
Subagent transcripts are now written in Pi's official JSONL session format.
|
|
138
|
-
- **Callback threading** - the three-layer `on*` callback chain through `SpawnOptions` → `AgentManager` → `RunOptions` was replaced by direct session-event subscriptions (#100).
|
|
139
|
-
- **Live `ctx` capture** - `SpawnArgs` previously held a mutable `ctx: ExtensionContext` reference that could go stale in the concurrency queue.
|
|
140
|
-
Replaced by `ParentSnapshot`, an immutable data object captured once at spawn time (#99).
|
|
404
|
+
- **Scheduling** (`schedule.ts`, `schedule-store.ts`, `ui/schedule-menu.ts`) — removed (#52).
|
|
405
|
+
- **Ad-hoc RPC** (`cross-extension-rpc.ts`) — replaced by the typed `SubagentsService` published via `Symbol.for()` (#49).
|
|
406
|
+
- **Group join** (`group-join.ts`) — removed (#49).
|
|
407
|
+
- **Output file** (`output-file.ts`) — replaced by `session-dir.ts` + `SessionManager.create()` (#61).
|
|
408
|
+
- **Callback threading** — the three-layer `on*` callback chain was replaced by direct session-event subscriptions (#100).
|
|
409
|
+
- **Live `ctx` capture** — replaced by `ParentSnapshot`, an immutable data object captured once at spawn time (#99).
|
|
141
410
|
|
|
142
411
|
## SubagentsService
|
|
143
412
|
|
|
@@ -176,10 +445,10 @@ The dynamic import provides compile-time types; the `Symbol.for()` key is the ac
|
|
|
176
445
|
See `src/service.ts` for the canonical definition.
|
|
177
446
|
Key types:
|
|
178
447
|
|
|
179
|
-
- `SubagentsService`
|
|
180
|
-
- `SubagentRecord`
|
|
181
|
-
- `SpawnOptions`
|
|
182
|
-
- `SUBAGENT_EVENTS`
|
|
448
|
+
- `SubagentsService` — `spawn`, `getRecord`, `listAgents`, `abort`, `steer`, `waitForAll`, `hasRunning`.
|
|
449
|
+
- `SubagentRecord` — serializable agent snapshot (no live session objects).
|
|
450
|
+
- `SpawnOptions` — `description`, `model`, `maxTurns`, `thinkingLevel`, `isolated`, `inheritContext`, `foreground`, `bypassQueue`, `isolation`.
|
|
451
|
+
- `SUBAGENT_EVENTS` — channel constants for `pi.events` subscriptions.
|
|
183
452
|
|
|
184
453
|
### Accessor pattern
|
|
185
454
|
|
|
@@ -209,527 +478,300 @@ The core emits events on `pi.events` that any extension can observe:
|
|
|
209
478
|
| `subagents:completed` | `{ id, type, status, result?, error? }` | Agent finishes |
|
|
210
479
|
| `subagents:activity` | `{ id, toolName?, textDelta?, turnCount? }` | Streaming progress |
|
|
211
480
|
|
|
212
|
-
These are fire-and-forget broadcast events
|
|
213
|
-
|
|
214
|
-
### Consumer example: scheduling extension
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
export default function (pi) {
|
|
218
|
-
pi.on("session_start", async (event, ctx) => {
|
|
219
|
-
let getSubagentsService;
|
|
220
|
-
try {
|
|
221
|
-
({ getSubagentsService } = await import("@gotgenes/pi-subagents"));
|
|
222
|
-
} catch {
|
|
223
|
-
return; // pi-subagents not installed
|
|
224
|
-
}
|
|
225
|
-
const svc = getSubagentsService();
|
|
226
|
-
if (!svc) return;
|
|
227
|
-
|
|
228
|
-
setInterval(() => {
|
|
229
|
-
svc.spawn("Explore", "Check for stale TODOs", {
|
|
230
|
-
bypassQueue: true,
|
|
231
|
-
});
|
|
232
|
-
}, 60 * 60 * 1000);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Consumer example: transcript extension
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
export default function (pi) {
|
|
241
|
-
pi.events.on("subagents:completed", async (data) => {
|
|
242
|
-
const { id } = data as { id: string };
|
|
243
|
-
let getSubagentsService;
|
|
244
|
-
try {
|
|
245
|
-
({ getSubagentsService } = await import("@gotgenes/pi-subagents"));
|
|
246
|
-
} catch {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
const record = getSubagentsService()?.getRecord(id);
|
|
250
|
-
if (record?.result) {
|
|
251
|
-
fs.appendFileSync("agent-log.jsonl", JSON.stringify(record) + "\n");
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## index.ts decomposition
|
|
258
|
-
|
|
259
|
-
The original monolithic `index.ts` has been decomposed into focused modules:
|
|
260
|
-
|
|
261
|
-
```text
|
|
262
|
-
src/
|
|
263
|
-
├── index.ts - slimmed entry point: init, tool registration
|
|
264
|
-
├── runtime.ts - SubagentRuntime: session-scoped state + methods
|
|
265
|
-
├── tools/
|
|
266
|
-
│ ├── agent-tool.ts - Agent tool definition, parameter validation, dispatch
|
|
267
|
-
│ ├── foreground-runner.ts - foreground execution loop (spinner, streaming, result)
|
|
268
|
-
│ ├── background-spawner.ts - background spawn (activity setup, notification wiring)
|
|
269
|
-
│ ├── get-result-tool.ts - get_subagent_result tool
|
|
270
|
-
│ ├── steer-tool.ts - steer_subagent tool
|
|
271
|
-
│ └── helpers.ts - shared tool utilities (textResult, buildDetails, getStatusNote, ...)
|
|
272
|
-
├── handlers/
|
|
273
|
-
│ ├── lifecycle.ts - session_start, session_before_switch, session_shutdown
|
|
274
|
-
│ └── tool-start.ts - tool_execution_start handler
|
|
275
|
-
├── notification.ts - completion nudges, custom renderer
|
|
276
|
-
├── renderer.ts - notification TUI component
|
|
277
|
-
├── ui/agent-menu.ts - /agents slash command menu (orchestration, listing, settings)
|
|
278
|
-
├── ui/agent-config-editor.ts - agent detail view (edit/delete/eject/disable/enable)
|
|
279
|
-
├── ui/agent-creation-wizard.ts - agent creation (AI-generation and manual-form)
|
|
280
|
-
├── ui/agent-file-ops.ts - AgentFileOps interface + FsAgentFileOps implementation
|
|
281
|
-
├── service-adapter.ts - SubagentsService implementation wrapping AgentManager
|
|
282
|
-
└── (existing domain modules unchanged)
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Each extracted module receives narrow constructor-injected dependencies rather than closing over module-level state.
|
|
286
|
-
Handlers call methods on narrow runtime interfaces - no raw field writes, no `widget!` reach-throughs.
|
|
287
|
-
|
|
288
|
-
## Phase plan
|
|
289
|
-
|
|
290
|
-
### Phase 1: Export `SubagentsService` from this package (#48)
|
|
291
|
-
|
|
292
|
-
Added the `SubagentsService` interface, serializable types, `Symbol.for()` accessor functions, and `SUBAGENT_EVENTS` constants as public exports.
|
|
293
|
-
Wired `service-adapter.ts` to wrap `AgentManager` and call `publishSubagentsService()` at extension init.
|
|
481
|
+
These are fire-and-forget broadcast events — no request IDs, no reply channels.
|
|
294
482
|
|
|
295
|
-
|
|
483
|
+
## Current structural analysis
|
|
296
484
|
|
|
297
|
-
|
|
298
|
-
Removed the `schedule` parameter from the `Agent` tool schema.
|
|
299
|
-
Removed scheduler setup and lifecycle hooks from `index.ts`.
|
|
485
|
+
### Health metrics
|
|
300
486
|
|
|
301
|
-
|
|
487
|
+
| Metric | Value |
|
|
488
|
+
| ------------------------- | ---------------------------- |
|
|
489
|
+
| Health score | 75/100 (B) |
|
|
490
|
+
| Total LOC | 7,288 (53 files) |
|
|
491
|
+
| Dead code | 0 files, 0 exports |
|
|
492
|
+
| Maintainability index | 90.7 (good) |
|
|
493
|
+
| Avg cyclomatic complexity | 1.5 |
|
|
494
|
+
| P90 cyclomatic complexity | 2 |
|
|
495
|
+
| Production duplication | 18 lines (1 clone group) |
|
|
496
|
+
| Test duplication | 71 clone groups, 1,424 lines |
|
|
302
497
|
|
|
303
|
-
|
|
304
|
-
Replaced `output-file.ts` with `SessionManager.create()` + `session-dir.ts` (#61).
|
|
305
|
-
Simplified `index.ts` to use direct individual notifications.
|
|
306
|
-
Lifecycle events emitted on `pi.events` for external consumers.
|
|
498
|
+
### Dependency bag inventory
|
|
307
499
|
|
|
308
|
-
|
|
500
|
+
These interfaces carry hidden dependencies that obscure true coupling.
|
|
501
|
+
Bags with 10+ fields are the highest priority for decomposition.
|
|
309
502
|
|
|
310
|
-
|
|
311
|
-
|
|
503
|
+
| Interface | Fields | Consumers | Severity |
|
|
504
|
+
| --------------------------- | --------- | ------------------------------------------------- | -------- |
|
|
505
|
+
| `ResolvedSpawnConfig` | 15 | foreground-runner, background-spawner, agent-tool | Critical |
|
|
506
|
+
| `AgentSpawnConfig` | 13 | agent-manager (internal) | Critical |
|
|
507
|
+
| `RunOptions` | 12 | agent-runner | High |
|
|
508
|
+
| `SessionConfig` | 11 | agent-runner (output of assembler) | High |
|
|
509
|
+
| `NotificationDetails` | 10 | notification | Medium |
|
|
510
|
+
| `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
|
|
511
|
+
| `RunnerIO` | 9 methods | agent-runner | Medium |
|
|
512
|
+
| `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
|
|
513
|
+
| `AgentToolDeps` | 8 | agent-tool | Low |
|
|
514
|
+
| `AgentMenuDeps` | 8 | agent-menu | Low |
|
|
515
|
+
| `ConversationViewerOptions` | 8 | conversation-viewer | Low |
|
|
516
|
+
| `AgentRecordInit` | 8 | agent-record | Low |
|
|
312
517
|
|
|
313
|
-
###
|
|
518
|
+
### Complexity hotspots
|
|
314
519
|
|
|
315
|
-
|
|
316
|
-
Created `SubagentRuntime` factory to hold session-scoped state.
|
|
520
|
+
Functions with cyclomatic complexity ≥ 21 (critical threshold):
|
|
317
521
|
|
|
318
|
-
|
|
522
|
+
| Function | Cyclomatic | Cognitive | File | Concern |
|
|
523
|
+
| ------------------- | ---------- | --------- | --------------------------- | ---------------------------------- |
|
|
524
|
+
| `buildContentLines` | 30 | 71 | `ui/conversation-viewer.ts` | Formats session events for display |
|
|
525
|
+
| `renderResult` | 26 | 43 | `tools/agent-tool.ts` | Formats agent result for LLM |
|
|
526
|
+
| `showAgentDetail` | 25 | 33 | `ui/agent-config-editor.ts` | Agent detail/edit view |
|
|
527
|
+
| `renderWidgetLines` | 25 | 44 | `ui/widget-renderer.ts` | Renders widget status lines |
|
|
528
|
+
| `ejectAgent` | 21 | 20 | `ui/agent-config-editor.ts` | Eject agent to filesystem |
|
|
529
|
+
| `update` | 21 | 31 | `ui/agent-widget.ts` | Widget lifecycle + polling |
|
|
319
530
|
|
|
320
|
-
|
|
321
|
-
This phase is deferred until the API boundary is proven stable in production.
|
|
531
|
+
### Churn hotspots
|
|
322
532
|
|
|
323
|
-
|
|
533
|
+
Files with highest commit frequency × complexity (accelerating trend):
|
|
324
534
|
|
|
325
|
-
|
|
535
|
+
| Score | File | Commits |
|
|
536
|
+
| ----- | --------------------- | ------- |
|
|
537
|
+
| 85.7 | `index.ts` | 65 |
|
|
538
|
+
| 35.9 | `agent-manager.ts` | 31 |
|
|
539
|
+
| 25.9 | `ui/agent-menu.ts` | 26 |
|
|
540
|
+
| 23.3 | `tools/agent-tool.ts` | 30 |
|
|
326
541
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
### Phase 8: Testability, display extraction, and menu decomposition
|
|
330
|
-
|
|
331
|
-
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
|
-
|
|
333
|
-
See the [Phase 8 roadmap](#phase-8-roadmap) section for the full breakdown.
|
|
334
|
-
|
|
335
|
-
### Phase 9: Observation consolidation, ctx elimination, and remaining mocks
|
|
336
|
-
|
|
337
|
-
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.
|
|
338
|
-
|
|
339
|
-
See the [Phase 9 roadmap](#phase-9-roadmap) section for the full breakdown.
|
|
340
|
-
Issues: #144, #145, #146, #147, #148.
|
|
341
|
-
|
|
342
|
-
## Structural refactoring roadmap
|
|
343
|
-
|
|
344
|
-
Phases 1-5, 7, and 8 are complete.
|
|
345
|
-
Phase 6 (UI extraction) is deferred.
|
|
346
|
-
See `git log` for the full history; issue references are preserved below for traceability.
|
|
347
|
-
|
|
348
|
-
| Phase | Issue | Summary |
|
|
349
|
-
| ------------------ | ------------------ | --------------------------------------------------------------------- |
|
|
350
|
-
| Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
|
|
351
|
-
| Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
|
|
352
|
-
| Interface polish | #66, #77 | SDK types, projectAgentsDir |
|
|
353
|
-
| Features | #61 | JSONL session transcripts |
|
|
354
|
-
|
|
355
|
-
The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
|
|
542
|
+
### Production duplication
|
|
356
543
|
|
|
357
|
-
|
|
544
|
+
One clone group (18 lines) shared between `agent-runner.ts:456-468` and `conversation-viewer.ts:261-278`.
|
|
545
|
+
Both format turn-event content for display — identical iteration over message content items, extracting tool names and text.
|
|
358
546
|
|
|
359
|
-
|
|
547
|
+
### Proposed bag decompositions
|
|
360
548
|
|
|
361
|
-
|
|
549
|
+
#### ResolvedSpawnConfig (15 fields → 3 value objects)
|
|
362
550
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
Issue #102 consolidated test `AgentRecord` construction into a shared factory.
|
|
551
|
+
This bag mixes three concerns: who the agent is, how it should run, and how it should be displayed.
|
|
552
|
+
Each consumer uses a different subset.
|
|
366
553
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
Replaced three-layer callback threading with direct session subscriptions.
|
|
377
|
-
`record-observer.ts` subscribes to the session to update record statistics (tool uses, lifetime usage, compaction count).
|
|
378
|
-
`ui/ui-observer.ts` subscribes to the session to stream UI state (active tools, response text, turn count).
|
|
379
|
-
`SpawnOptions` and `RunOptions` dropped all `on*` callback fields except `onSessionCreated` (which delivers the session object to enable external subscriptions).
|
|
380
|
-
|
|
381
|
-
### Realized impact
|
|
382
|
-
|
|
383
|
-
| Metric | Before | After |
|
|
384
|
-
| --------------------------------- | ------ | ----------------------- |
|
|
385
|
-
| `SpawnOptions` callback fields | 6 | 1 (`onSessionCreated`) |
|
|
386
|
-
| `RunOptions` callback fields | 6 | 1 (`onSessionCreated`) |
|
|
387
|
-
| Callback layers | 3 | 0 (direct subscription) |
|
|
388
|
-
| Live `ctx` references in queue | 1 | 0 (snapshot) |
|
|
389
|
-
| Scattered status-transition sites | 6 | 1 (state machine) |
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## Encapsulation roadmap
|
|
394
|
-
|
|
395
|
-
Phase 7 encapsulated mutable state into classes, replaced callbacks with semantic components, and narrowed dependency bags.
|
|
396
|
-
|
|
397
|
-
Each step was sequenced so it made the next step easier.
|
|
398
|
-
|
|
399
|
-
### Resolved smells
|
|
400
|
-
|
|
401
|
-
All nine smells identified at the start of Phase 7 were resolved:
|
|
402
|
-
|
|
403
|
-
| Smell | Resolution |
|
|
404
|
-
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
405
|
-
| Global mutable state | `AgentTypeRegistry` class (#108); `reloadCustomAgents` callback removed from dep bags |
|
|
406
|
-
| Closure bag as class | `NotificationManager` class (#116); `pendingNudges` and timer state are private fields |
|
|
407
|
-
| Mutable state bag | `AgentActivityTracker` class (#110); transition methods replace external writes |
|
|
408
|
-
| Settings relay | `SettingsManager` class (#109); 6 callback fields collapsed to one object |
|
|
409
|
-
| Post-construction mutation | `ExecutionState`, `WorktreeState`, `NotificationState` collaborators (#111); stats behind mutation methods |
|
|
410
|
-
| Fire-and-forget callbacks | `AgentManagerObserver` interface (#112); one observer object replaces 3 closure lambdas |
|
|
411
|
-
| Duplicate `SpawnOptions` | Internal type renamed to `AgentSpawnConfig` (#113); public `SpawnOptions` unchanged |
|
|
412
|
-
| Type dumping ground | `NotificationDetails`, `ParentSnapshot`, `EnvInfo` moved to their natural modules (#116); narrow subsets defined |
|
|
413
|
-
| Wide dependency bags | `AgentToolDeps` 9 → 6, `AgentMenuDeps` 8 → 7 (#114); `emitEvent` removed; description text derived from registry; `agentActivity` narrowed |
|
|
414
|
-
|
|
415
|
-
### Step A: Extract state into classes (foundation, parallel)
|
|
416
|
-
|
|
417
|
-
These three extractions are independent and can proceed in any order.
|
|
418
|
-
Each eliminates a category of global/closure state and gives orphaned callbacks a natural home.
|
|
419
|
-
|
|
420
|
-
#### A1. AgentTypeRegistry class (#108)
|
|
421
|
-
|
|
422
|
-
Wrapped the module-scoped `agents` Map and free functions in `agent-types.ts` into an injectable class.
|
|
423
|
-
`reloadCustomAgents` callback removed from `AgentToolDeps` and `AgentMenuDeps`; replaced by `registry.reload()`.
|
|
424
|
-
`DEFAULT_AGENT_NAMES` moved from `types.ts` to the registry.
|
|
425
|
-
|
|
426
|
-
#### A2. SettingsManager class (#109, #118)
|
|
427
|
-
|
|
428
|
-
Encapsulated settings load/save/apply cycle into `SettingsManager` (in `settings.ts`).
|
|
429
|
-
Owns `defaultMaxTurns`, `graceTurns`, `maxConcurrent` with normalizing property accessors.
|
|
430
|
-
Added `applyMaxConcurrent(n)`, `applyDefaultMaxTurns(n)`, `applyGraceTurns(n)` - each owns the full consequence chain: normalize → set in memory → notify callback → persist → emit event → return toast.
|
|
431
|
-
The 6 settings-related fields in `AgentMenuDeps` collapsed to `settings: AgentMenuSettings`.
|
|
432
|
-
|
|
433
|
-
#### A3. AgentActivityTracker class (#110)
|
|
434
|
-
|
|
435
|
-
Wrapped the 7-field mutable `AgentActivity` interface in an `AgentActivityTracker` class (`src/ui/agent-activity-tracker.ts`).
|
|
436
|
-
`ui-observer.ts` calls transition methods; consumers use read-only accessors.
|
|
437
|
-
The shared map on `SubagentRuntime` is `Map<string, AgentActivityTracker>`.
|
|
438
|
-
|
|
439
|
-
### Step B: Split AgentRecord lifecycle state (#111)
|
|
440
|
-
|
|
441
|
-
Split post-construction mutation into phase-specific collaborators, each born complete:
|
|
442
|
-
|
|
443
|
-
- **`ExecutionState`** (`session`, `outputFile`) - constructed in `onSessionCreated`.
|
|
444
|
-
- **`WorktreeState`** (`path`, `branch`, `cleanupResult`) - constructed at worktree setup.
|
|
445
|
-
- **`NotificationState`** (`toolCallId`, `resultConsumed`) - constructed by `AgentManager.spawn()` when `toolCallId` is provided.
|
|
446
|
-
- **`pendingSteers`** moved to `Map<string, string[]>` on `AgentManager`.
|
|
447
|
-
- Stats encapsulated behind mutation methods with read-only getters.
|
|
448
|
-
- `AgentRecordInit` trimmed from 19 optional fields to 4 construction-time fields.
|
|
449
|
-
|
|
450
|
-
### Step C: Replace AgentManager callbacks with observer (#112)
|
|
451
|
-
|
|
452
|
-
`AgentManagerObserver` interface replaces `onStart`/`onComplete`/`onCompact`.
|
|
453
|
-
`index.ts` constructs one observer object instead of 3 closure lambdas.
|
|
454
|
-
`AgentManagerOptions` drops from 9 → 7 fields.
|
|
455
|
-
|
|
456
|
-
### Step D: Disambiguate SpawnOptions and narrow dependency bags
|
|
457
|
-
|
|
458
|
-
#### D1. Disambiguate SpawnOptions (#113)
|
|
459
|
-
|
|
460
|
-
Internal `SpawnOptions` in `agent-manager.ts` renamed to `AgentSpawnConfig`.
|
|
461
|
-
Public `SpawnOptions` in `service.ts` unchanged.
|
|
462
|
-
|
|
463
|
-
#### D2. Narrow AgentToolDeps and AgentMenuDeps (#114)
|
|
464
|
-
|
|
465
|
-
| Bag | Before | After | How |
|
|
466
|
-
| --------------- | -------- | ----- | ----------------------------------------------------------------------------------------------------------- |
|
|
467
|
-
| `AgentToolDeps` | 9 fields | 6 | `emitEvent` → observer; `typeListText`/`availableTypesText` derived from registry; `agentActivity` narrowed |
|
|
468
|
-
| `AgentMenuDeps` | 8 fields | 7 | Dead `emitEvent` removed; `agentActivity` narrowed to read-only `AgentActivityReader` |
|
|
469
|
-
|
|
470
|
-
### Step E: Decompose large files and relocate types
|
|
471
|
-
|
|
472
|
-
#### E1. Split agent-tool.ts foreground/background (#115)
|
|
473
|
-
|
|
474
|
-
Extracted `foreground-runner.ts` (~175 lines) and `background-spawner.ts` (~116 lines).
|
|
475
|
-
`agent-tool.ts` reduced from 579 → 411 lines.
|
|
476
|
-
|
|
477
|
-
#### E2. Type housekeeping (#116)
|
|
478
|
-
|
|
479
|
-
- Moved `NotificationDetails`, `ParentSnapshot`, `EnvInfo` to their natural modules.
|
|
480
|
-
- Converted `createNotificationSystem` closure to `NotificationManager` class.
|
|
481
|
-
- Converted `ConversationViewer` constructor from 7 positional parameters to `ConversationViewerOptions` bag.
|
|
482
|
-
- Defined `AgentIdentity` and `AgentPromptConfig` narrow subsets; `buildAgentPrompt` narrowed to `AgentPromptConfig`.
|
|
483
|
-
|
|
484
|
-
### Phase 7 results
|
|
485
|
-
|
|
486
|
-
| Metric | Before | After |
|
|
487
|
-
| ------------------------------------------ | ------ | ----- |
|
|
488
|
-
| Module-scoped mutable state | 1 | 0 |
|
|
489
|
-
| Closure-bag "classes" | 2 | 0 |
|
|
490
|
-
| Externally-mutated state bags | 2 | 0 |
|
|
491
|
-
| `AgentManagerOptions` fields | 9 | 7 |
|
|
492
|
-
| `AgentToolDeps` fields | 9 | 6 |
|
|
493
|
-
| `AgentMenuDeps` fields | 13 | 7 |
|
|
494
|
-
| `SpawnOptions` callback fields | 6 | 1 |
|
|
495
|
-
| `RunOptions` callback fields | 6 | 1 |
|
|
496
|
-
| Callbacks threaded through deps | 8 | 0 |
|
|
497
|
-
| Types in `types.ts` without a natural home | 4 | 0 |
|
|
554
|
+
```typescript
|
|
555
|
+
/** Who this agent is — type resolution result. */
|
|
556
|
+
interface SpawnIdentity {
|
|
557
|
+
subagentType: string;
|
|
558
|
+
rawType: SubagentType;
|
|
559
|
+
fellBack: boolean;
|
|
560
|
+
displayName: string;
|
|
561
|
+
}
|
|
498
562
|
|
|
499
|
-
|
|
563
|
+
/** How the agent should run — execution parameters. */
|
|
564
|
+
interface SpawnExecution {
|
|
565
|
+
prompt: string;
|
|
566
|
+
description: string;
|
|
567
|
+
model: Model<any> | undefined;
|
|
568
|
+
effectiveMaxTurns: number | undefined;
|
|
569
|
+
thinking: ThinkingLevel | undefined;
|
|
570
|
+
inheritContext: boolean;
|
|
571
|
+
runInBackground: boolean;
|
|
572
|
+
isolated: boolean;
|
|
573
|
+
isolation: IsolationMode | undefined;
|
|
574
|
+
agentInvocation: AgentInvocation;
|
|
575
|
+
}
|
|
500
576
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
B --> C["C: Observer"] --> D1["D1: SpawnOptions"] --> D2
|
|
508
|
-
D2 --> E1["E1: agent-tool split"]
|
|
509
|
-
A1 --> E2["E2: Type housekeeping"]
|
|
577
|
+
/** How the agent is presented — display metadata. */
|
|
578
|
+
interface SpawnPresentation {
|
|
579
|
+
modelName: string | undefined;
|
|
580
|
+
agentTags: string[];
|
|
581
|
+
detailBase: Pick<AgentDetails, ...>;
|
|
582
|
+
}
|
|
510
583
|
```
|
|
511
584
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
Phase 7 eliminated all structural smells (mutable state, closure bags, callback threading, wide dependency bags).
|
|
517
|
-
Phase 8 targeted the next layer: testability friction, display module cohesion, and menu decomposition.
|
|
518
|
-
|
|
519
|
-
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.
|
|
520
|
-
Step G resolved `session-config.test.ts`; Step H resolved both `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts`.
|
|
585
|
+
`foreground-runner` and `background-spawner` primarily consume `SpawnExecution` + `SpawnIdentity`.
|
|
586
|
+
`agent-tool` uses all three to build the `AgentSpawnConfig` and the result text.
|
|
587
|
+
After decomposition, each consumer declares its real dependencies explicitly.
|
|
521
588
|
|
|
522
|
-
|
|
523
|
-
The display extraction unblocked menu decomposition.
|
|
589
|
+
#### AgentSpawnConfig (13 fields → extract ParentSessionInfo)
|
|
524
590
|
|
|
525
|
-
|
|
591
|
+
Several fields form a natural cluster around parent session identity:
|
|
526
592
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
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.
|
|
536
|
-
|
|
537
|
-
### Step F: Shared test fixtures (#131)
|
|
538
|
-
|
|
539
|
-
Consolidated duplicated mock factories into `test/helpers/`.
|
|
540
|
-
|
|
541
|
-
1. `createMockSession()` - subscribable event bus with `emit()` helper; replaced 3 hand-rolled copies.
|
|
542
|
-
2. `createToolDeps()` - builds `AgentToolDeps` with sensible defaults and override support; replaced 3 `makeDeps()` copies.
|
|
543
|
-
3. `makeRecord()` - `AgentRecord` factory with sensible defaults; replaced scattered inline construction.
|
|
544
|
-
4. `STUB_CTX` - shared stub `ExtensionContext` constant; centralised unavoidable bridge casts.
|
|
545
|
-
|
|
546
|
-
Impact: reduced test boilerplate; single source of truth for mock shapes; changes to dep interfaces propagate automatically.
|
|
547
|
-
|
|
548
|
-
### Step G: Inject IO collaborators into session-config (#132)
|
|
549
|
-
|
|
550
|
-
`assembleSessionConfig` now accepts `io: AssemblerIO` as a required parameter.
|
|
551
|
-
`index.ts` constructs the real `AssemblerIO` from direct imports via the `RunnerIO.assemblerIO` field (wired in Step H).
|
|
552
|
-
`session-config.test.ts` injects stubs - all 4 `vi.mock()` calls eliminated, assertions shifted to `SessionConfig` output properties.
|
|
553
|
-
|
|
554
|
-
### Step H: Inject SDK boundary into agent-runner (#133)
|
|
555
|
-
|
|
556
|
-
`runAgent()` now accepts `io: RunnerIO` as a required parameter bundling all IO collaborators: `detectEnv`, `getAgentDir`, `createResourceLoader`, `deriveSessionDir`, `createSessionManager`, `createSettingsManager`, `createSession`, and `assemblerIO`.
|
|
557
|
-
|
|
558
|
-
`createAgentRunner(io: RunnerIO): AgentRunner` factory captures the boundary at construction time so `AgentManager` and the `AgentRunner` interface remain unchanged.
|
|
559
|
-
`index.ts` constructs the real `RunnerIO` from Pi SDK imports and sibling modules.
|
|
560
|
-
|
|
561
|
-
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.
|
|
562
|
-
|
|
563
|
-
### Step I: Reduce `as any` casts in tests (#134)
|
|
564
|
-
|
|
565
|
-
Reduced `as any` count from 93 to 15 (plus 13 explicit `as unknown as T` bridge casts).
|
|
566
|
-
|
|
567
|
-
Production changes:
|
|
568
|
-
|
|
569
|
-
- `ResourceLoaderOptions.appendSystemPromptOverride` typed to match `DefaultResourceLoaderOptions`; `createResourceLoader` factory cast removed from `index.ts`.
|
|
570
|
-
- `CreateSessionOptions.settingsManager` / `RunnerIO.createSettingsManager` typed as `SettingsManager`.
|
|
571
|
-
- `WidgetLike` interface in `runtime.ts` narrows the widget field.
|
|
572
|
-
- Local `ToolCallContent` / `BashExecutionMessage` type guards replace `as any` duck-typing in `conversation-viewer.ts` and `agent-runner.ts`.
|
|
573
|
-
- `textResult()` return no longer casts `details as any`.
|
|
574
|
-
- `toAgentSession()` helper and `STUB_CTX` constant centralise unavoidable bridge casts.
|
|
575
|
-
|
|
576
|
-
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.
|
|
577
|
-
|
|
578
|
-
### Step J: Extract display helpers (#135)
|
|
579
|
-
|
|
580
|
-
`ui/display.ts` now contains all pure formatters, display helpers, constants, and shared types (`Theme`, `AgentDetails`).
|
|
581
|
-
`agent-widget.ts` dropped from 522 → ~340 lines.
|
|
582
|
-
All consumer modules (menu, tools, renderer, conversation viewer) import from `ui/display.ts` directly.
|
|
583
|
-
`test/agent-widget.test.ts` renamed to `test/display.test.ts`.
|
|
584
|
-
|
|
585
|
-
### Step K: Decompose agent-menu.ts (#136)
|
|
593
|
+
```typescript
|
|
594
|
+
/** Parent session identity — always travel together. */
|
|
595
|
+
interface ParentSessionInfo {
|
|
596
|
+
parentSessionFile?: string;
|
|
597
|
+
parentSessionId?: string;
|
|
598
|
+
toolCallId?: string;
|
|
599
|
+
}
|
|
600
|
+
```
|
|
586
601
|
|
|
587
|
-
`
|
|
602
|
+
Extracting this from `AgentSpawnConfig` reduces it from 13 to 10 fields and introduces a named concept that currently exists only as scattered optional fields.
|
|
588
603
|
|
|
589
|
-
|
|
590
|
-
2. `ui/agent-config-editor.ts` - `showAgentDetail` with edit/delete/reset/eject/disable/enable transitions (~200 lines).
|
|
591
|
-
3. `ui/agent-creation-wizard.ts` - AI-generation and manual-form creation paths (~250 lines).
|
|
592
|
-
4. `ui/agent-menu.ts` - menu orchestration, agent listing, running-agent viewer, settings form (~300 lines).
|
|
604
|
+
#### RunOptions (12 fields → extract RunContext)
|
|
593
605
|
|
|
594
|
-
|
|
606
|
+
The `RunOptions` bag mixes execution parameters with context information:
|
|
595
607
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
end
|
|
608
|
+
```typescript
|
|
609
|
+
/** Parent context needed to configure the child session. */
|
|
610
|
+
interface RunContext {
|
|
611
|
+
cwd?: string;
|
|
612
|
+
parentSessionFile?: string;
|
|
613
|
+
parentSessionId?: string;
|
|
614
|
+
exec: ShellExec;
|
|
615
|
+
registry: AgentConfigLookup;
|
|
616
|
+
}
|
|
606
617
|
```
|
|
607
618
|
|
|
608
|
-
The
|
|
609
|
-
|
|
610
|
-
---
|
|
611
|
-
|
|
612
|
-
## Phase 9 roadmap
|
|
619
|
+
The remaining `RunOptions` fields (`model`, `maxTurns`, `signal`, `isolated`, `thinkingLevel`, `defaultMaxTurns`, `graceTurns`, `onSessionCreated`) are genuine execution parameters.
|
|
613
620
|
|
|
614
|
-
|
|
615
|
-
Phase 9 targets the next layer: observation model consolidation, `ExtensionContext` elimination from internal APIs, remaining `vi.mock()` / `as any` casts, and dependency bag cleanup.
|
|
621
|
+
#### SessionConfig (11 fields → extract ToolFilterConfig)
|
|
616
622
|
|
|
617
|
-
|
|
623
|
+
Three fields form a cohesive tool-filtering cluster:
|
|
618
624
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
### Dependency bag convention
|
|
625
|
+
```typescript
|
|
626
|
+
/** Tool filtering configuration — used by filterActiveTools. */
|
|
627
|
+
interface ToolFilterConfig {
|
|
628
|
+
toolNames: string[];
|
|
629
|
+
disallowedSet: Set<string> | undefined;
|
|
630
|
+
extensions: boolean | string[];
|
|
631
|
+
}
|
|
632
|
+
```
|
|
628
633
|
|
|
629
|
-
|
|
634
|
+
Extracting this reduces `SessionConfig` from 11 to 8 fields and gives `filterActiveTools` a named input type instead of three positional parameters.
|
|
630
635
|
|
|
631
|
-
|
|
632
|
-
- **≥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`.
|
|
636
|
+
#### RunnerIO (9 methods → 2 focused interfaces)
|
|
633
637
|
|
|
634
|
-
|
|
638
|
+
The IO boundary mixes environment discovery with session factory operations:
|
|
635
639
|
|
|
636
|
-
|
|
640
|
+
```typescript
|
|
641
|
+
/** Environment discovery — detecting paths and platform info. */
|
|
642
|
+
interface EnvironmentIO {
|
|
643
|
+
detectEnv: (exec: ShellExec, cwd: string) => Promise<EnvInfo>;
|
|
644
|
+
getAgentDir: () => string;
|
|
645
|
+
deriveSessionDir: (parentSessionFile: string | undefined, effectiveCwd: string) => string;
|
|
646
|
+
}
|
|
637
647
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
648
|
+
/** Session factory — creating SDK objects. */
|
|
649
|
+
interface SessionFactoryIO {
|
|
650
|
+
createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
|
|
651
|
+
createSessionManager: (cwd: string, sessionDir: string) => SessionManagerLike;
|
|
652
|
+
createSettingsManager: (cwd: string, agentDir: string) => SettingsManager;
|
|
653
|
+
createSession: (opts: CreateSessionOptions) => Promise<{ session: AgentSession }>;
|
|
654
|
+
assemblerIO: AssemblerIO;
|
|
655
|
+
}
|
|
656
|
+
```
|
|
641
657
|
|
|
642
|
-
|
|
643
|
-
The 15+ callsites that navigate `record.execution?.session` simplify to `record.session`.
|
|
658
|
+
The runner would accept `EnvironmentIO & SessionFactoryIO` (keeping backward compatibility) while each piece can be tested independently.
|
|
644
659
|
|
|
645
|
-
|
|
660
|
+
## Improvement roadmap (Phase 10)
|
|
646
661
|
|
|
647
|
-
|
|
662
|
+
Phase 10 addresses the structural gaps identified in this analysis: flat code organization, oversized dependency bags, and complexity hotspots.
|
|
648
663
|
|
|
649
|
-
### Step
|
|
664
|
+
### Step 1: Reorganize source into domain directories ([#164][164]) ✓ Done
|
|
650
665
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
`AgentManager.spawn()` and `spawnAndWait()` accept `ParentSnapshot` instead of `ExtensionContext`.
|
|
654
|
-
`service-adapter.ts` calls `buildParentSnapshot(session.ctx)` at its boundary.
|
|
655
|
-
`foreground-runner` and `background-spawner` receive `ResolvedSpawnConfig` + domain values (`snapshot`, `parentSessionFile`, `parentSessionId`) instead of `ctx`.
|
|
666
|
+
Moved files into `config/`, `session/`, `lifecycle/`, `observation/`, and `service/` subdirectories.
|
|
667
|
+
All `src/` internal imports now use `#src/` path aliases (same style as `test/` files), eliminating relative depth arithmetic for future moves.
|
|
656
668
|
|
|
657
|
-
|
|
658
|
-
|
|
669
|
+
- Domain model is now visible in the filesystem.
|
|
670
|
+
- Root reduced to 5 files + 8 directories (was 31 files + 3 directories).
|
|
671
|
+
- All subsequent steps can move or extract files without `../` import churn.
|
|
659
672
|
|
|
660
|
-
|
|
673
|
+
### Step 2: Decompose ResolvedSpawnConfig ([#165][165])
|
|
661
674
|
|
|
662
|
-
-
|
|
663
|
-
|
|
664
|
-
|
|
675
|
+
Split the 15-field bag into `SpawnIdentity`, `SpawnExecution`, and `SpawnPresentation`.
|
|
676
|
+
Each consumer declares its real dependencies.
|
|
677
|
+
Enables Step 3 (narrowing AgentSpawnConfig, [#166][166]).
|
|
665
678
|
|
|
666
|
-
|
|
679
|
+
### Step 3: Extract ParentSessionInfo from AgentSpawnConfig ([#166][166])
|
|
667
680
|
|
|
668
|
-
|
|
681
|
+
Extract `parentSessionFile`, `parentSessionId`, `toolCallId` into a `ParentSessionInfo` value object.
|
|
682
|
+
Reduces AgentSpawnConfig from 13 to 10 fields.
|
|
669
683
|
|
|
670
|
-
|
|
671
|
-
All inner functions in `agent-menu.ts`, `agent-config-editor.ts`, and `agent-creation-wizard.ts` now accept `(ui: MenuUI)` instead of `(ctx: ExtensionContext)`.
|
|
672
|
-
`index.ts` passes `ctx.ui`, `ctx.modelRegistry`, and `buildParentSnapshot(ctx)` to the handler.
|
|
684
|
+
### Step 4: Narrow RunnerIO ([#167][167])
|
|
673
685
|
|
|
674
|
-
|
|
675
|
-
|
|
686
|
+
Split into `EnvironmentIO` and `SessionFactoryIO`.
|
|
687
|
+
Each half can be tested independently.
|
|
676
688
|
|
|
677
|
-
|
|
689
|
+
### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168])
|
|
678
690
|
|
|
679
|
-
|
|
680
|
-
|
|
691
|
+
Extract the tool-filtering cluster into `ToolFilterConfig`.
|
|
692
|
+
Give `filterActiveTools` a named input type.
|
|
681
693
|
|
|
682
|
-
|
|
694
|
+
### Step 6: Extract RunContext from RunOptions ([#169][169])
|
|
683
695
|
|
|
684
|
-
|
|
696
|
+
Extract context fields into `RunContext`.
|
|
697
|
+
Reduces RunOptions from 12 to 7 fields.
|
|
685
698
|
|
|
686
|
-
### Step
|
|
699
|
+
### Step 7: Reduce buildContentLines complexity ([#170][170])
|
|
687
700
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
Tests inject a stub or the real function directly via options — no module-level mock needed.
|
|
701
|
+
`buildContentLines` in `conversation-viewer.ts` has cognitive complexity 71.
|
|
702
|
+
Extract formatting sub-functions for each content type (tool calls, text, bash output).
|
|
691
703
|
|
|
692
|
-
|
|
704
|
+
### Step 8: Reduce renderResult complexity ([#171][171])
|
|
693
705
|
|
|
694
|
-
|
|
706
|
+
`renderResult` in `agent-tool.ts` has cognitive complexity 43.
|
|
707
|
+
Extract result formatting by status (completed, error, aborted, stopped).
|
|
695
708
|
|
|
696
|
-
### Step
|
|
709
|
+
### Step 9: Extract shared turn-formatting logic ([#172][172])
|
|
697
710
|
|
|
698
|
-
|
|
699
|
-
The widget is now a thin lifecycle/polling wrapper (198 lines, down from 374) that delegates to pure render functions.
|
|
700
|
-
Rendering functions receive data (agent list, activity map, registry) and return formatted strings - testable without widget lifecycle. 23 new unit tests cover all status variants, overflow, tree connectors, and empty states.
|
|
711
|
+
The 18-line production clone between `agent-runner.ts` and `conversation-viewer.ts` extracts into a shared function in the session domain.
|
|
701
712
|
|
|
702
713
|
### Step dependencies
|
|
703
714
|
|
|
704
715
|
```mermaid
|
|
705
716
|
flowchart LR
|
|
706
|
-
subgraph
|
|
707
|
-
|
|
717
|
+
subgraph organization["Code organization"]
|
|
718
|
+
S1["#164: Domain directories"]
|
|
708
719
|
end
|
|
709
|
-
subgraph
|
|
710
|
-
|
|
720
|
+
subgraph bags["Dependency bags"]
|
|
721
|
+
S2["#165: ResolvedSpawnConfig"] --> S3["#166: AgentSpawnConfig"]
|
|
722
|
+
S4["#167: RunnerIO"]
|
|
723
|
+
S5["#168: SessionConfig"]
|
|
724
|
+
S6["#169: RunOptions"]
|
|
711
725
|
end
|
|
712
|
-
|
|
726
|
+
subgraph complexity["Complexity reduction"]
|
|
727
|
+
S7["#170: buildContentLines"]
|
|
728
|
+
S8["#171: renderResult"]
|
|
729
|
+
S9["#172: Shared turn-formatting"]
|
|
730
|
+
end
|
|
731
|
+
S1 --> S2 & S4 & S5 & S6
|
|
732
|
+
S1 --> S7 & S8 & S9
|
|
713
733
|
```
|
|
714
734
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
|
726
|
-
|
|
|
735
|
+
Step 1 ([#164][164], directory restructuring) unblocks all other steps by co-locating related files.
|
|
736
|
+
Steps 2–6 (bag decomposition) and Steps 7–9 (complexity reduction) are independent tracks that can proceed in parallel.
|
|
737
|
+
Within the bag track, Step 2 ([#165][165], ResolvedSpawnConfig) enables Step 3 ([#166][166], AgentSpawnConfig).
|
|
738
|
+
|
|
739
|
+
## Refactoring history
|
|
740
|
+
|
|
741
|
+
Phases 1–5 and 7–9 are complete.
|
|
742
|
+
Phase 6 (UI extraction to a separate package) is deferred.
|
|
743
|
+
Detailed records are preserved in per-phase history files:
|
|
744
|
+
|
|
745
|
+
| Phase | Title | Status | History |
|
|
746
|
+
| ----- | --------------------------------------------------- | -------- | -------------------------------------------------------------------------- |
|
|
747
|
+
| 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
|
|
748
|
+
| 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
|
|
749
|
+
| 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
|
|
750
|
+
| 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
|
|
751
|
+
| 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
|
|
752
|
+
| 6 | Extract UI to separate package | Deferred | — |
|
|
753
|
+
| 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
|
|
754
|
+
| 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
|
|
755
|
+
| 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
|
|
756
|
+
|
|
757
|
+
### Structural refactoring issues
|
|
758
|
+
|
|
759
|
+
| Phase | Issue | Summary |
|
|
760
|
+
| ------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
761
|
+
| Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
|
|
762
|
+
| Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
|
|
763
|
+
| Interface polish | #66, #77 | SDK types, projectAgentsDir |
|
|
764
|
+
| Features | #61 | JSONL session transcripts |
|
|
765
|
+
| AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
|
|
766
|
+
| Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
|
|
767
|
+
| Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
|
|
768
|
+
| Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
|
|
727
769
|
|
|
728
|
-
|
|
770
|
+
The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
|
|
729
771
|
|
|
730
772
|
## Relationship with upstream
|
|
731
773
|
|
|
732
|
-
This fork (`@gotgenes/pi-subagents` in the [gotgenes/pi-packages] monorepo) is
|
|
774
|
+
This fork (`@gotgenes/pi-subagents` in the [gotgenes/pi-packages] monorepo) is a hard fork of [tintinweb/pi-subagents].
|
|
733
775
|
The decomposition diverges materially from upstream's direction.
|
|
734
776
|
|
|
735
777
|
The three upstream PRs (#71, #72, #73) remain open.
|
|
@@ -742,3 +784,13 @@ The upstream test suite is run periodically as a regression canary for the agent
|
|
|
742
784
|
[earendil-works/pi#4207]: https://github.com/earendil-works/pi/issues/4207
|
|
743
785
|
[gotgenes/pi-packages]: https://github.com/gotgenes/pi-packages
|
|
744
786
|
[tintinweb/pi-subagents]: https://github.com/tintinweb/pi-subagents
|
|
787
|
+
|
|
788
|
+
[164]: https://github.com/gotgenes/pi-packages/issues/164
|
|
789
|
+
[165]: https://github.com/gotgenes/pi-packages/issues/165
|
|
790
|
+
[166]: https://github.com/gotgenes/pi-packages/issues/166
|
|
791
|
+
[167]: https://github.com/gotgenes/pi-packages/issues/167
|
|
792
|
+
[168]: https://github.com/gotgenes/pi-packages/issues/168
|
|
793
|
+
[169]: https://github.com/gotgenes/pi-packages/issues/169
|
|
794
|
+
[170]: https://github.com/gotgenes/pi-packages/issues/170
|
|
795
|
+
[171]: https://github.com/gotgenes/pi-packages/issues/171
|
|
796
|
+
[172]: https://github.com/gotgenes/pi-packages/issues/172
|