@gotgenes/pi-subagents 7.2.4 → 7.2.5
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 +10 -0
- package/docs/architecture/architecture.md +21 -20
- package/docs/plans/0196-convert-runner-menu-to-classes.md +268 -0
- package/docs/retro/0196-convert-runner-menu-to-classes.md +42 -0
- package/package.json +1 -1
- package/src/index.ts +14 -28
- package/src/lifecycle/agent-runner.ts +12 -6
- package/src/ui/agent-menu.ts +96 -94
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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
|
+
## [7.2.5](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.2.4...pi-subagents-v7.2.5) (2026-05-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* mark Phase 11 Layer 3 and Layer 4 complete ([bf71795](https://github.com/gotgenes/pi-packages/commit/bf71795649a5b3c14a2b3ff16b2109d131a0ed32))
|
|
14
|
+
* plan convert AgentRunner and AgentsMenuHandler to classes ([#196](https://github.com/gotgenes/pi-packages/issues/196)) ([cd0bd1f](https://github.com/gotgenes/pi-packages/commit/cd0bd1fdec0c87655bdb38f8243084df807b676a))
|
|
15
|
+
* **retro:** add planning stage notes for issue [#196](https://github.com/gotgenes/pi-packages/issues/196) ([677d4bf](https://github.com/gotgenes/pi-packages/commit/677d4bf6619f13eba8d17181efab04cc67e47bbd))
|
|
16
|
+
* **retro:** add TDD stage notes for issue [#196](https://github.com/gotgenes/pi-packages/issues/196) ([72d24ba](https://github.com/gotgenes/pi-packages/commit/72d24ba56b8dc7668ff350ad3f0ba027b996d26e))
|
|
17
|
+
|
|
8
18
|
## [7.2.4](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.2.3...pi-subagents-v7.2.4) (2026-05-25)
|
|
9
19
|
|
|
10
20
|
|
|
@@ -686,45 +686,46 @@ After this step, `AgentManager` structurally satisfies `AgentToolManager` and `S
|
|
|
686
686
|
- Outcome: structural typing connects real objects to tool interfaces without adapters; 0 dead exports (fallow clean)
|
|
687
687
|
- Enables: Layer 3 (class constructors accept real objects directly)
|
|
688
688
|
|
|
689
|
-
### Layer 3: Convert closure factories to classes ([#195][195], [#196][196])
|
|
689
|
+
### Layer 3: Convert closure factories to classes ([#195][195], [#196][196]) ✓ done
|
|
690
690
|
|
|
691
|
-
|
|
691
|
+
All closure factories converted to classes. ✓ Tool factories in [#195][195]; runner and menu in [#196][196]:
|
|
692
692
|
|
|
693
|
-
| Factory | Class
|
|
694
|
-
| -------------------------------- |
|
|
695
|
-
| `createAgentTool({...})` | `AgentTool` ✓
|
|
696
|
-
| `createGetResultTool(...)` | `GetResultTool` ✓
|
|
697
|
-
| `createSteerTool(...)` | `SteerTool` ✓
|
|
698
|
-
| `createAgentRunner(runnerIO)` | `
|
|
699
|
-
| `createAgentsMenuHandler({...})` | `AgentsMenuHandler`
|
|
693
|
+
| Factory | Class | Constructor params |
|
|
694
|
+
| -------------------------------- | ----------------------- | -------------------------------------------------------------------- |
|
|
695
|
+
| `createAgentTool({...})` | `AgentTool` ✓ | `manager`, `runtime`, `settings`, `registry` |
|
|
696
|
+
| `createGetResultTool(...)` | `GetResultTool` ✓ | `manager`, `notifications`, `registry` |
|
|
697
|
+
| `createSteerTool(...)` | `SteerTool` ✓ | `manager`, `events` |
|
|
698
|
+
| `createAgentRunner(runnerIO)` | `ConcreteAgentRunner` ✓ | `io: RunnerIO` |
|
|
699
|
+
| `createAgentsMenuHandler({...})` | `AgentsMenuHandler` ✓ | `manager`, `registry`, `agentActivity`, `settings`, `fileOps`, paths |
|
|
700
700
|
|
|
701
701
|
Each class satisfies the existing interface via structural typing.
|
|
702
702
|
The `defineTool()` wrapper moves into a `toToolDefinition()` method on each tool class.
|
|
703
|
+
`getModelLabel` internalized into `AgentsMenuHandler` (was a 7-line closure in `index.ts`).
|
|
703
704
|
|
|
704
705
|
- Target: `src/tools/*.ts`, `src/lifecycle/agent-runner.ts`, `src/ui/agent-menu.ts`
|
|
705
706
|
- Smell: Category C (closure factories masquerading as classes)
|
|
706
707
|
- Outcome: deps are constructor params (inspectable, testable); no captured closures
|
|
707
708
|
- Enables: Layer 4 (index.ts simplification)
|
|
708
709
|
|
|
709
|
-
### Layer 4: Simplify index.ts (included in [#196][196])
|
|
710
|
+
### Layer 4: Simplify index.ts (included in [#196][196]) ✓ done
|
|
710
711
|
|
|
711
|
-
With
|
|
712
|
+
With all factories converted to classes and `AgentManager` satisfying `AgentMenuManager` structurally:
|
|
712
713
|
|
|
713
714
|
```typescript
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
715
|
+
const agentsMenu = new AgentsMenuHandler(
|
|
716
|
+
manager, registry, runtime.agentActivity,
|
|
717
|
+
settings, new FsAgentFileOps(),
|
|
718
|
+
join(getAgentDir(), "agents"),
|
|
719
|
+
join(process.cwd(), ".pi", "agents"),
|
|
720
|
+
);
|
|
719
721
|
```
|
|
720
722
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
Fan-out drops from 25 to ~15 (internal factories eliminated).
|
|
723
|
+
Eliminated: 4 adapter closures (3 manager method adapters + `getModelLabel`), 4 unused imports.
|
|
724
|
+
Remaining ~15 closures are structural (event registrations, SDK factory callbacks).
|
|
724
725
|
|
|
725
726
|
- Target: `src/index.ts`
|
|
726
727
|
- Smell: Category B (god file) + Category C (adapter closure density)
|
|
727
|
-
- Outcome:
|
|
728
|
+
- Outcome: adapter closure count reduced; `AgentManager` passed directly without wrappers; churn hotspot stabilized
|
|
728
729
|
|
|
729
730
|
### Step dependencies
|
|
730
731
|
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 196
|
|
3
|
+
issue_title: "Convert AgentRunner and AgentsMenuHandler to classes, simplify index.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Convert AgentRunner and AgentsMenuHandler to classes, simplify index.ts
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
Two remaining closure factories in pi-subagents mask class-shaped code:
|
|
11
|
+
|
|
12
|
+
1. `createAgentRunner(runnerIO)` captures `RunnerIO` and returns `{ run, resume }` — the `AgentRunner` interface already exists but lacks a concrete class implementation.
|
|
13
|
+
2. `createAgentsMenuHandler({...})` captures an 8-field deps object and returns a handler function.
|
|
14
|
+
|
|
15
|
+
With #195 (tool factory → class conversions) complete, these are the last two closure factories.
|
|
16
|
+
After converting them, `index.ts` can be simplified: adapter closures drop, fan-out decreases, and the composition root becomes pure object construction.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Convert `createAgentRunner` factory to a concrete `ConcreteAgentRunner` class implementing `AgentRunner`.
|
|
21
|
+
- Convert `createAgentsMenuHandler` factory to an `AgentsMenuHandler` class.
|
|
22
|
+
- Internalize the `getModelLabel` closure from `index.ts` into `AgentsMenuHandler` (it uses `resolveModel` and `getModelLabelFromConfig`, both pure functions the class can import directly).
|
|
23
|
+
- Pass `AgentManager` directly as the `manager` param (it structurally satisfies `AgentMenuManager`), eliminating 3 adapter closures.
|
|
24
|
+
- Simplify `index.ts` by removing eliminated adapter closures and unused imports.
|
|
25
|
+
- Update architecture doc to mark Layer 3 and Layer 4 as done.
|
|
26
|
+
|
|
27
|
+
## Non-Goals
|
|
28
|
+
|
|
29
|
+
- Changing `runAgent()` or `resumeAgent()` function signatures — they remain as free functions called by the class.
|
|
30
|
+
- Removing the `AgentRunner` interface — it stays as the contract for `AgentManager`.
|
|
31
|
+
- Removing `RunnerIO` type — it stays as the IO boundary for `runAgent()`.
|
|
32
|
+
- Changing the `AgentMenuManager` interface — it stays as the narrow contract; `AgentManager` satisfies it structurally.
|
|
33
|
+
- Removing `AgentMenuDeps` — it is replaced by the class constructor; the type itself is removed.
|
|
34
|
+
- Refactoring `NotificationManager`, `SettingsManager`, or `SessionLifecycleHandler` — those are already class-shaped.
|
|
35
|
+
- Phase 12 work (decompose `renderWidgetLines`, consolidate test duplication).
|
|
36
|
+
|
|
37
|
+
## Background
|
|
38
|
+
|
|
39
|
+
### Phase 11 layer structure
|
|
40
|
+
|
|
41
|
+
Phase 11 in `docs/architecture/architecture.md` converts closure factories to classes in four layers:
|
|
42
|
+
|
|
43
|
+
- Layer 0: `SessionContext` interface (#192) ✓
|
|
44
|
+
- Layer 1: Runtime owns context queries (#193) ✓
|
|
45
|
+
- Layer 2: Align interfaces for structural typing (#194) ✓
|
|
46
|
+
- Layer 3: Convert closure factories to classes (#195 tools ✓, #196 runner + menu)
|
|
47
|
+
- Layer 4: Simplify `index.ts` (#196)
|
|
48
|
+
|
|
49
|
+
Issue #196 completes Layer 3 (runner + menu) and Layer 4 (index.ts simplification).
|
|
50
|
+
|
|
51
|
+
### Current state
|
|
52
|
+
|
|
53
|
+
`createAgentRunner` (3 lines) wraps `runAgent`/`resumeAgent` in an object literal:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
export function createAgentRunner(io: RunnerIO): AgentRunner {
|
|
57
|
+
return {
|
|
58
|
+
run: (snapshot, type, prompt, options) => runAgent(snapshot, type, prompt, options, io),
|
|
59
|
+
resume: resumeAgent,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`createAgentsMenuHandler` (200+ lines) captures 8 deps and returns a handler function.
|
|
65
|
+
The deps bag includes `getModelLabel` (a closure built in `index.ts`) and `agentActivity` (a `Map` from runtime).
|
|
66
|
+
|
|
67
|
+
`index.ts` currently has ~23 arrow closures and 27 imports at 229 lines.
|
|
68
|
+
|
|
69
|
+
### Structural typing confirmation
|
|
70
|
+
|
|
71
|
+
`AgentManager` already has `listAgents()`, `getRecord()`, and `spawnAndWait()` methods that structurally satisfy `AgentMenuManager`.
|
|
72
|
+
The `spawnAndWait` parameter type (`Omit<AgentSpawnConfig, "isBackground">`) is a superset of the menu's `{ description, maxTurns }`, so structural typing matches.
|
|
73
|
+
|
|
74
|
+
## Design Overview
|
|
75
|
+
|
|
76
|
+
### ConcreteAgentRunner
|
|
77
|
+
|
|
78
|
+
A minimal class that implements `AgentRunner` by delegating to the free functions:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export class ConcreteAgentRunner implements AgentRunner {
|
|
82
|
+
constructor(private readonly io: RunnerIO) {}
|
|
83
|
+
|
|
84
|
+
async run(snapshot: ParentSnapshot, type: SubagentType, prompt: string, options: RunOptions): Promise<RunResult> {
|
|
85
|
+
return runAgent(snapshot, type, prompt, options, this.io);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async resume(session: AgentSession, prompt: string, options?: ResumeOptions): Promise<string> {
|
|
89
|
+
return resumeAgent(session, prompt, options);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The factory function `createAgentRunner` is removed.
|
|
95
|
+
The free functions `runAgent`, `resumeAgent`, `getAgentConversation`, and `normalizeMaxTurns` remain exported — they are used directly by tests and other modules.
|
|
96
|
+
|
|
97
|
+
### AgentsMenuHandler
|
|
98
|
+
|
|
99
|
+
The class replaces `createAgentsMenuHandler` and `AgentMenuDeps`.
|
|
100
|
+
Constructor params are the subset of deps that are true collaborators:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
export class AgentsMenuHandler {
|
|
104
|
+
constructor(
|
|
105
|
+
private readonly manager: AgentMenuManager,
|
|
106
|
+
private readonly registry: AgentTypeRegistry,
|
|
107
|
+
private readonly agentActivity: AgentActivityReader,
|
|
108
|
+
private readonly settings: AgentMenuSettings,
|
|
109
|
+
private readonly fileOps: AgentFileOps,
|
|
110
|
+
private readonly personalAgentsDir: string,
|
|
111
|
+
private readonly projectAgentsDir: string,
|
|
112
|
+
) {}
|
|
113
|
+
|
|
114
|
+
async handle(ctx: { ui: MenuUI; modelRegistry: ModelRegistry; parentSnapshot: ParentSnapshot }): Promise<void> { ... }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Key design decisions:
|
|
119
|
+
|
|
120
|
+
1. **`agentActivity` stays as a constructor param** — it is a collaborator used in `viewAgentConversation`.
|
|
121
|
+
The issue's proposed signature omits it, but the class needs it at runtime.
|
|
122
|
+
2. **`getModelLabel` is internalized** — the class imports `resolveModel` and `getModelLabelFromConfig` directly and computes the label in a private method.
|
|
123
|
+
This eliminates the closure from `index.ts` and removes the `getModelLabel` field from the deps interface.
|
|
124
|
+
3. **`AgentMenuDeps` is removed** — the class constructor replaces it.
|
|
125
|
+
4. **The `handle` method** replaces the returned function.
|
|
126
|
+
The inner helpers (`showAgentsMenu`, `showAllAgentsList`, etc.) become private methods.
|
|
127
|
+
|
|
128
|
+
### index.ts simplification
|
|
129
|
+
|
|
130
|
+
After both conversions:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Before (adapter closures):
|
|
134
|
+
const agentsMenuHandler = createAgentsMenuHandler({
|
|
135
|
+
manager: {
|
|
136
|
+
listAgents: () => manager.listAgents(),
|
|
137
|
+
getRecord: (id) => manager.getRecord(id),
|
|
138
|
+
spawnAndWait: (...) => manager.spawnAndWait(...),
|
|
139
|
+
},
|
|
140
|
+
registry,
|
|
141
|
+
agentActivity: runtime.agentActivity,
|
|
142
|
+
getModelLabel: (type, modelRegistry) => { ... }, // 7-line closure
|
|
143
|
+
settings,
|
|
144
|
+
fileOps: new FsAgentFileOps(),
|
|
145
|
+
personalAgentsDir: join(getAgentDir(), 'agents'),
|
|
146
|
+
projectAgentsDir: join(process.cwd(), '.pi', 'agents'),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// After:
|
|
150
|
+
const agentsMenu = new AgentsMenuHandler(
|
|
151
|
+
manager, registry, runtime.agentActivity,
|
|
152
|
+
settings, new FsAgentFileOps(),
|
|
153
|
+
join(getAgentDir(), 'agents'),
|
|
154
|
+
join(process.cwd(), '.pi', 'agents'),
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Eliminated closures: 4 (3 manager method adapters + 1 getModelLabel closure).
|
|
159
|
+
Eliminated imports: `getModelLabelFromConfig`, `resolveModel` (from index.ts), `createAgentRunner`, `type RunnerIO`, `createAgentsMenuHandler`.
|
|
160
|
+
|
|
161
|
+
Remaining adapter closures in `index.ts` (~15) are necessary: event handler registrations, SDK factory callbacks, `pi.sendMessage`/`pi.exec` adapters.
|
|
162
|
+
These are structural — they bridge the Pi SDK's callback-based API to the extension's object-oriented internals.
|
|
163
|
+
|
|
164
|
+
## Module-Level Changes
|
|
165
|
+
|
|
166
|
+
### `src/lifecycle/agent-runner.ts`
|
|
167
|
+
|
|
168
|
+
- Add `ConcreteAgentRunner` class implementing `AgentRunner`.
|
|
169
|
+
- Remove `createAgentRunner` factory function.
|
|
170
|
+
- Keep all free functions (`runAgent`, `resumeAgent`, `getAgentConversation`, `normalizeMaxTurns`) and all types exported.
|
|
171
|
+
|
|
172
|
+
### `src/ui/agent-menu.ts`
|
|
173
|
+
|
|
174
|
+
- Replace `createAgentsMenuHandler` factory with `AgentsMenuHandler` class.
|
|
175
|
+
- Remove `AgentMenuDeps` interface.
|
|
176
|
+
- Add private `getModelLabel` method (internalizes the closure from `index.ts`).
|
|
177
|
+
- Convert inner functions (`showAgentsMenu`, `showAllAgentsList`, `showRunningAgents`, `viewAgentConversation`, `showSettings`) to private methods.
|
|
178
|
+
- Add imports: `resolveModel` from `#src/session/model-resolver`, `getModelLabelFromConfig` from `#src/tools/helpers`.
|
|
179
|
+
- Keep exported interfaces: `AgentMenuManager`, `AgentMenuSettings`, `AgentActivityReader`, `MenuUI`.
|
|
180
|
+
|
|
181
|
+
### `src/index.ts`
|
|
182
|
+
|
|
183
|
+
- Replace `createAgentRunner(runnerIO)` with `new ConcreteAgentRunner(runnerIO)`.
|
|
184
|
+
- Replace `createAgentsMenuHandler({...})` with `new AgentsMenuHandler(...)`.
|
|
185
|
+
- Replace `agentsMenuHandler({...})` with `agentsMenu.handle({...})`.
|
|
186
|
+
- Remove adapter closures for `manager.listAgents`, `manager.getRecord`, `manager.spawnAndWait`, and `getModelLabel`.
|
|
187
|
+
- Remove unused imports: `createAgentRunner`, `type RunnerIO` → `ConcreteAgentRunner`; `createAgentsMenuHandler` → `AgentsMenuHandler`; `getModelLabelFromConfig`, `resolveModel`.
|
|
188
|
+
- Net effect: ~15 lines removed, 5 imports removed.
|
|
189
|
+
|
|
190
|
+
### `docs/architecture/architecture.md`
|
|
191
|
+
|
|
192
|
+
- Mark Layer 3 remaining items (runner, menu) as done.
|
|
193
|
+
- Mark Layer 4 as done.
|
|
194
|
+
- Update the factory→class table entries for `createAgentRunner` and `createAgentsMenuHandler` with ✓.
|
|
195
|
+
|
|
196
|
+
## Test Impact Analysis
|
|
197
|
+
|
|
198
|
+
### `test/lifecycle/agent-runner.test.ts` (and siblings)
|
|
199
|
+
|
|
200
|
+
No changes needed.
|
|
201
|
+
Tests call `runAgent()` and `resumeAgent()` directly — they never use `createAgentRunner`.
|
|
202
|
+
The `ConcreteAgentRunner` class is a trivial two-method delegation wrapper tested implicitly through `index.ts` integration and explicitly through one new unit test.
|
|
203
|
+
|
|
204
|
+
### `test/ui/agent-menu.test.ts`
|
|
205
|
+
|
|
206
|
+
Tests need updating:
|
|
207
|
+
|
|
208
|
+
1. Replace `createAgentsMenuHandler(makeDeps())` with `new AgentsMenuHandler(...)`.
|
|
209
|
+
2. Replace `handler(params)` with `handler.handle(params)`.
|
|
210
|
+
3. Remove `getModelLabel` from `makeDeps()` — it is now an internal method.
|
|
211
|
+
4. Remove `AgentMenuDeps` import; update `makeDeps` to construct positional args or a helper that returns a handler directly.
|
|
212
|
+
|
|
213
|
+
No test logic changes — only call-site updates for the new API shape.
|
|
214
|
+
|
|
215
|
+
### New tests
|
|
216
|
+
|
|
217
|
+
One new unit test for `ConcreteAgentRunner`: verify it delegates `run` and `resume` to the underlying functions.
|
|
218
|
+
|
|
219
|
+
## TDD Order
|
|
220
|
+
|
|
221
|
+
1. **Add `ConcreteAgentRunner` class alongside factory.**
|
|
222
|
+
Add the class to `agent-runner.ts`, keep `createAgentRunner` temporarily.
|
|
223
|
+
Add a unit test verifying delegation.
|
|
224
|
+
`test: add ConcreteAgentRunner delegation test`
|
|
225
|
+
|
|
226
|
+
2. **Switch `index.ts` to `ConcreteAgentRunner`, remove factory.**
|
|
227
|
+
Replace `createAgentRunner(runnerIO)` with `new ConcreteAgentRunner(runnerIO)`.
|
|
228
|
+
Remove the `createAgentRunner` factory function.
|
|
229
|
+
Update imports.
|
|
230
|
+
`refactor: replace createAgentRunner with ConcreteAgentRunner class`
|
|
231
|
+
|
|
232
|
+
3. **Convert `createAgentsMenuHandler` to `AgentsMenuHandler` class.**
|
|
233
|
+
Replace factory function with class.
|
|
234
|
+
Move inner functions to private methods.
|
|
235
|
+
Internalize `getModelLabel` as a private method.
|
|
236
|
+
Remove `AgentMenuDeps` interface.
|
|
237
|
+
`refactor: convert createAgentsMenuHandler to AgentsMenuHandler class`
|
|
238
|
+
|
|
239
|
+
4. **Update `agent-menu.test.ts` for class API.**
|
|
240
|
+
Replace `createAgentsMenuHandler(makeDeps())` with class construction.
|
|
241
|
+
Replace `handler(params)` with `handler.handle(params)`.
|
|
242
|
+
Remove `getModelLabel` from test deps.
|
|
243
|
+
All existing tests pass with updated call sites.
|
|
244
|
+
`test: update agent-menu tests for AgentsMenuHandler class`
|
|
245
|
+
|
|
246
|
+
5. **Simplify `index.ts` wiring.**
|
|
247
|
+
Replace `createAgentsMenuHandler({...})` with `new AgentsMenuHandler(...)`.
|
|
248
|
+
Pass `manager` directly (structural typing).
|
|
249
|
+
Remove adapter closures and unused imports.
|
|
250
|
+
`refactor: simplify index.ts wiring for AgentsMenuHandler`
|
|
251
|
+
|
|
252
|
+
6. **Update architecture doc.**
|
|
253
|
+
Mark Layer 3 remaining items and Layer 4 as done in `docs/architecture/architecture.md`.
|
|
254
|
+
`docs: mark Phase 11 Layer 3 and Layer 4 complete`
|
|
255
|
+
|
|
256
|
+
## Risks and Mitigations
|
|
257
|
+
|
|
258
|
+
| Risk | Mitigation |
|
|
259
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
260
|
+
| `AgentManager` might not structurally satisfy `AgentMenuManager` | Confirmed: `listAgents()`, `getRecord()`, `spawnAndWait()` signatures are compatible. `pnpm run check` in step 5 verifies. |
|
|
261
|
+
| Internalized `getModelLabel` might diverge from the closure | The private method uses the same `resolveModel` and `getModelLabelFromConfig` imports — identical logic, just moved. |
|
|
262
|
+
| Tests that use `AgentMenuDeps` type break when removed | Step 4 updates all test call sites before step 5 changes production code. The test file is 215 lines — manageable in one step. |
|
|
263
|
+
| `agentActivity` missing from constructor | Included in the class constructor (diverging from the issue's proposed signature which omits it). |
|
|
264
|
+
|
|
265
|
+
## Open Questions
|
|
266
|
+
|
|
267
|
+
None — the issue's proposed design is clear and the implementation is mechanical.
|
|
268
|
+
The one deviation (keeping `agentActivity` as a constructor param) is necessary and minimal.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 196
|
|
3
|
+
issue_title: "Convert AgentRunner and AgentsMenuHandler to classes, simplify index.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #196 — Convert AgentRunner and AgentsMenuHandler to classes, simplify index.ts
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-25T14:35:46Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a 6-step TDD plan covering the final two closure-factory-to-class conversions (`createAgentRunner` → `ConcreteAgentRunner`, `createAgentsMenuHandler` → `AgentsMenuHandler`) and the subsequent `index.ts` simplification.
|
|
13
|
+
Confirmed that `AgentManager` structurally satisfies `AgentMenuManager`, enabling direct pass-through without adapter closures.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- The issue's proposed `AgentsMenuHandler` constructor omits `agentActivity`, but the class needs it for `viewAgentConversation`.
|
|
18
|
+
Plan includes it as a constructor param — minimal deviation from the issue.
|
|
19
|
+
- `getModelLabel` can be internalized into `AgentsMenuHandler` since it only uses two pure imported functions (`resolveModel`, `getModelLabelFromConfig`) plus the registry (already a constructor param).
|
|
20
|
+
This eliminates a 7-line closure from `index.ts`.
|
|
21
|
+
- Tests for `agent-runner` call `runAgent`/`resumeAgent` directly — no test uses `createAgentRunner`, so the runner conversion has zero test impact.
|
|
22
|
+
- The `agent-menu.test.ts` file is 215 lines and needs call-site updates (factory → class constructor + `.handle()`), but no logic changes.
|
|
23
|
+
- After both conversions, `index.ts` loses ~5 imports and ~4 adapter closures.
|
|
24
|
+
The remaining ~15 closures are structural (event registrations, SDK factory callbacks) and cannot be eliminated.
|
|
25
|
+
|
|
26
|
+
## Stage: Implementation — TDD (2026-05-25T14:56:11Z)
|
|
27
|
+
|
|
28
|
+
### Session summary
|
|
29
|
+
|
|
30
|
+
Completed all 6 plan steps (collapsed into 5 commits: steps 3 and 5 merged).
|
|
31
|
+
Baseline was 854 tests across 53 files; final suite is 856 tests across 54 files (+2 tests, +1 file for `concrete-agent-runner.test.ts`).
|
|
32
|
+
All type-check, lint, and dead-code gates pass clean.
|
|
33
|
+
|
|
34
|
+
### Observations
|
|
35
|
+
|
|
36
|
+
- Plan steps 3 and 5 had to be merged into a single commit: removing `createAgentsMenuHandler` immediately broke `index.ts` imports, so the `index.ts` update could not wait for a separate commit.
|
|
37
|
+
This is a known coupling when a factory's only call site is in `index.ts`.
|
|
38
|
+
- The `AgentsMenuHandler` class constructor includes `agentActivity` as planned (the issue's proposed signature omitted it; the plan's deviation was correct).
|
|
39
|
+
- `getModelLabel` internalization was clean: `resolveModel` and `getModelLabelFromConfig` are pure functions the class imports directly.
|
|
40
|
+
- `AgentManager` structurally satisfies `AgentMenuManager` with no adapter closures — confirmed by `pnpm run check` passing immediately.
|
|
41
|
+
- The `agent-menu.test.ts` refactor replaced `Partial<AgentMenuDeps>` overrides with a `makeHandler(opts)` helper that returns both the handler and collaborator stubs, which is cleaner for assertion.
|
|
42
|
+
- `rumdl` emitted 3 warnings in `pnpm run lint` — these are pre-existing and unrelated to this change (lint passes for markdown linting, the warnings are from biome/eslint steps that auto-fixed nothing).
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument -- Pi SDK types are not fully exported; see upstream Pi SDK for type improvements */
|
|
2
2
|
/**
|
|
3
3
|
* pi-agents — A pi extension providing Claude Code-style autonomous sub-agents.
|
|
4
4
|
*
|
|
@@ -24,7 +24,7 @@ import { AgentTypeRegistry } from "#src/config/agent-types";
|
|
|
24
24
|
import { loadCustomAgents } from "#src/config/custom-agents";
|
|
25
25
|
import { SessionLifecycleHandler, ToolStartHandler } from "#src/handlers/index";
|
|
26
26
|
import { AgentManager, type AgentManagerObserver } from "#src/lifecycle/agent-manager";
|
|
27
|
-
import {
|
|
27
|
+
import { ConcreteAgentRunner, type RunnerIO } from "#src/lifecycle/agent-runner";
|
|
28
28
|
import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
29
29
|
import { GitWorktreeManager } from "#src/lifecycle/worktree";
|
|
30
30
|
import { buildEventData, type NotificationDetails, NotificationManager } from "#src/observation/notification";
|
|
@@ -41,10 +41,9 @@ import { preloadSkills } from "#src/session/skill-loader";
|
|
|
41
41
|
import { SettingsManager } from "#src/settings";
|
|
42
42
|
import { AgentTool } from "#src/tools/agent-tool";
|
|
43
43
|
import { GetResultTool } from "#src/tools/get-result-tool";
|
|
44
|
-
import { getModelLabelFromConfig } from "#src/tools/helpers";
|
|
45
44
|
import { SteerTool } from "#src/tools/steer-tool";
|
|
46
45
|
import { FsAgentFileOps } from "#src/ui/agent-file-ops";
|
|
47
|
-
import {
|
|
46
|
+
import { AgentsMenuHandler } from "#src/ui/agent-menu";
|
|
48
47
|
import { AgentWidget } from "#src/ui/agent-widget";
|
|
49
48
|
|
|
50
49
|
export default function (pi: ExtensionAPI) {
|
|
@@ -147,7 +146,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
147
146
|
};
|
|
148
147
|
|
|
149
148
|
const manager = new AgentManager({
|
|
150
|
-
runner:
|
|
149
|
+
runner: new ConcreteAgentRunner(runnerIO),
|
|
151
150
|
worktrees: new GitWorktreeManager(process.cwd()),
|
|
152
151
|
exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
|
|
153
152
|
registry,
|
|
@@ -193,33 +192,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
193
192
|
|
|
194
193
|
// ---- /agents interactive menu ----
|
|
195
194
|
|
|
196
|
-
const
|
|
197
|
-
manager
|
|
198
|
-
listAgents: () => manager.listAgents(),
|
|
199
|
-
getRecord: (id) => manager.getRecord(id),
|
|
200
|
-
spawnAndWait: (snapshot, type, prompt, opts) => manager.spawnAndWait(snapshot, type, prompt, opts),
|
|
201
|
-
},
|
|
195
|
+
const agentsMenu = new AgentsMenuHandler(
|
|
196
|
+
manager,
|
|
202
197
|
registry,
|
|
203
|
-
|
|
204
|
-
getModelLabel: (type, modelRegistry) => {
|
|
205
|
-
const cfg = registry.resolveAgentConfig(type);
|
|
206
|
-
if (!cfg.model) return 'inherit';
|
|
207
|
-
if (modelRegistry) {
|
|
208
|
-
const resolved = resolveModel(cfg.model, modelRegistry);
|
|
209
|
-
if (typeof resolved === 'string') return 'inherit';
|
|
210
|
-
}
|
|
211
|
-
return getModelLabelFromConfig(cfg.model);
|
|
212
|
-
},
|
|
198
|
+
runtime.agentActivity,
|
|
213
199
|
settings,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
200
|
+
new FsAgentFileOps(),
|
|
201
|
+
join(getAgentDir(), "agents"),
|
|
202
|
+
join(process.cwd(), ".pi", "agents"),
|
|
203
|
+
);
|
|
218
204
|
|
|
219
|
-
pi.registerCommand(
|
|
220
|
-
description:
|
|
205
|
+
pi.registerCommand("agents", {
|
|
206
|
+
description: "Manage agents",
|
|
221
207
|
handler: async (_args, ctx) => {
|
|
222
|
-
await
|
|
208
|
+
await agentsMenu.handle({
|
|
223
209
|
ui: ctx.ui,
|
|
224
210
|
modelRegistry: ctx.modelRegistry,
|
|
225
211
|
parentSnapshot: buildParentSnapshot(ctx),
|
|
@@ -202,17 +202,23 @@ export interface AgentRunner {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
|
-
*
|
|
205
|
+
* Concrete AgentRunner backed by a RunnerIO boundary.
|
|
206
206
|
*
|
|
207
207
|
* Captures io at construction time so AgentManager remains IO-unaware.
|
|
208
208
|
*/
|
|
209
|
-
export
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
export class ConcreteAgentRunner implements AgentRunner {
|
|
210
|
+
constructor(private readonly io: RunnerIO) {}
|
|
211
|
+
|
|
212
|
+
run(snapshot: ParentSnapshot, type: SubagentType, prompt: string, options: RunOptions): Promise<RunResult> {
|
|
213
|
+
return runAgent(snapshot, type, prompt, options, this.io);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
resume(session: AgentSession, prompt: string, options?: ResumeOptions): Promise<string> {
|
|
217
|
+
return resumeAgent(session, prompt, options);
|
|
218
|
+
}
|
|
214
219
|
}
|
|
215
220
|
|
|
221
|
+
|
|
216
222
|
// ── Private helpers ───────────────────────────────────────────────────────────
|
|
217
223
|
|
|
218
224
|
/**
|
package/src/ui/agent-menu.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
3
3
|
import { AgentTypeRegistry } from "#src/config/agent-types";
|
|
4
4
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
5
|
-
import type
|
|
5
|
+
import { type ModelRegistry, resolveModel } from "#src/session/model-resolver";
|
|
6
|
+
import { getModelLabelFromConfig } from "#src/tools/helpers";
|
|
6
7
|
import type { AgentConfig, AgentRecord } from "#src/types";
|
|
7
8
|
import type { AgentActivityTracker } from "#src/ui/agent-activity-tracker";
|
|
8
9
|
import { createAgentConfigEditor } from "#src/ui/agent-config-editor";
|
|
@@ -10,7 +11,7 @@ import { createAgentCreationWizard } from "#src/ui/agent-creation-wizard";
|
|
|
10
11
|
import type { AgentFileOps } from "#src/ui/agent-file-ops";
|
|
11
12
|
import { formatDuration, getDisplayName } from "#src/ui/display";
|
|
12
13
|
|
|
13
|
-
// ----
|
|
14
|
+
// ---- Narrow interfaces ----
|
|
14
15
|
|
|
15
16
|
/** Narrow manager interface for menu operations. */
|
|
16
17
|
export interface AgentMenuManager {
|
|
@@ -43,19 +44,6 @@ export interface AgentActivityReader {
|
|
|
43
44
|
get(id: string): AgentActivityTracker | undefined;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export interface AgentMenuDeps {
|
|
47
|
-
manager: AgentMenuManager;
|
|
48
|
-
registry: AgentTypeRegistry;
|
|
49
|
-
agentActivity: AgentActivityReader;
|
|
50
|
-
/** Resolve model label for a given agent type + registry. */
|
|
51
|
-
getModelLabel: (type: string, registry?: ModelRegistry) => string;
|
|
52
|
-
/** Settings manager — owns in-memory values and persistence. */
|
|
53
|
-
settings: AgentMenuSettings;
|
|
54
|
-
fileOps: AgentFileOps;
|
|
55
|
-
personalAgentsDir: string;
|
|
56
|
-
projectAgentsDir: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
47
|
// ---- Narrow UI context types ----
|
|
60
48
|
|
|
61
49
|
/** Narrow UI interface — only the ctx.ui methods menu handlers actually call. */
|
|
@@ -68,48 +56,74 @@ export interface MenuUI {
|
|
|
68
56
|
custom<R>(component: any, options?: any): Promise<R>;
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
// ----
|
|
59
|
+
// ---- Class ----
|
|
72
60
|
|
|
73
61
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
62
|
+
* Handler for the `/agents` slash command.
|
|
63
|
+
*
|
|
64
|
+
* Call `handle(ctx)` from the Pi command registration to open the interactive menu.
|
|
76
65
|
*/
|
|
77
|
-
export
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
export class AgentsMenuHandler {
|
|
67
|
+
private readonly editor: ReturnType<typeof createAgentConfigEditor>;
|
|
68
|
+
private readonly wizard: ReturnType<typeof createAgentCreationWizard>;
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
private readonly manager: AgentMenuManager,
|
|
72
|
+
private readonly registry: AgentTypeRegistry,
|
|
73
|
+
private readonly agentActivity: AgentActivityReader,
|
|
74
|
+
private readonly settings: AgentMenuSettings,
|
|
75
|
+
private readonly fileOps: AgentFileOps,
|
|
76
|
+
private readonly personalAgentsDir: string,
|
|
77
|
+
private readonly projectAgentsDir: string,
|
|
78
|
+
) {
|
|
79
|
+
this.editor = createAgentConfigEditor(
|
|
80
|
+
fileOps,
|
|
81
|
+
registry,
|
|
82
|
+
personalAgentsDir,
|
|
83
|
+
projectAgentsDir,
|
|
84
|
+
);
|
|
85
|
+
this.wizard = createAgentCreationWizard({
|
|
86
|
+
fileOps,
|
|
87
|
+
manager,
|
|
88
|
+
registry,
|
|
89
|
+
personalAgentsDir,
|
|
90
|
+
projectAgentsDir,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async handle({
|
|
95
|
+
ui,
|
|
96
|
+
modelRegistry,
|
|
97
|
+
parentSnapshot,
|
|
98
|
+
}: {
|
|
99
|
+
ui: MenuUI;
|
|
100
|
+
modelRegistry: ModelRegistry;
|
|
101
|
+
parentSnapshot: ParentSnapshot;
|
|
102
|
+
}): Promise<void> {
|
|
103
|
+
await this.showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private getModelLabel(type: string, modelRegistry?: ModelRegistry): string {
|
|
107
|
+
const cfg = this.registry.resolveAgentConfig(type);
|
|
108
|
+
if (!cfg.model) return "inherit";
|
|
109
|
+
if (modelRegistry) {
|
|
110
|
+
const resolved = resolveModel(cfg.model, modelRegistry);
|
|
111
|
+
if (typeof resolved === "string") return "inherit";
|
|
112
|
+
}
|
|
113
|
+
return getModelLabelFromConfig(cfg.model);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async showAgentsMenu(
|
|
103
117
|
ui: MenuUI,
|
|
104
118
|
modelRegistry: ModelRegistry,
|
|
105
119
|
parentSnapshot: ParentSnapshot,
|
|
106
|
-
) {
|
|
107
|
-
registry.reload();
|
|
108
|
-
const allNames = registry.getAllTypes();
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
this.registry.reload();
|
|
122
|
+
const allNames = this.registry.getAllTypes();
|
|
109
123
|
|
|
110
124
|
const options: string[] = [];
|
|
111
125
|
|
|
112
|
-
const agents = manager.listAgents();
|
|
126
|
+
const agents = this.manager.listAgents();
|
|
113
127
|
if (agents.length > 0) {
|
|
114
128
|
const running = agents.filter(
|
|
115
129
|
(a) => a.status === "running" || a.status === "queued",
|
|
@@ -144,21 +158,21 @@ export function createAgentsMenuHandler({
|
|
|
144
158
|
if (!choice) return;
|
|
145
159
|
|
|
146
160
|
if (choice.startsWith("Running agents (")) {
|
|
147
|
-
await showRunningAgents(ui);
|
|
148
|
-
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
161
|
+
await this.showRunningAgents(ui);
|
|
162
|
+
await this.showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
149
163
|
} else if (choice.startsWith("Agent types (")) {
|
|
150
|
-
await showAllAgentsList(ui, modelRegistry);
|
|
151
|
-
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
164
|
+
await this.showAllAgentsList(ui, modelRegistry);
|
|
165
|
+
await this.showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
152
166
|
} else if (choice === "Create new agent") {
|
|
153
|
-
await wizard.showCreateWizard(ui, parentSnapshot);
|
|
167
|
+
await this.wizard.showCreateWizard(ui, parentSnapshot);
|
|
154
168
|
} else if (choice === "Settings") {
|
|
155
|
-
await showSettings(ui);
|
|
156
|
-
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
169
|
+
await this.showSettings(ui);
|
|
170
|
+
await this.showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
173
|
|
|
160
|
-
async
|
|
161
|
-
const allNames = registry.getAllTypes();
|
|
174
|
+
private async showAllAgentsList(ui: MenuUI, modelRegistry: ModelRegistry): Promise<void> {
|
|
175
|
+
const allNames = this.registry.getAllTypes();
|
|
162
176
|
if (allNames.length === 0) {
|
|
163
177
|
ui.notify("No agents.", "info");
|
|
164
178
|
return;
|
|
@@ -173,9 +187,9 @@ export function createAgentsMenuHandler({
|
|
|
173
187
|
};
|
|
174
188
|
|
|
175
189
|
const entries = allNames.map((name) => {
|
|
176
|
-
const cfg = registry.resolveAgentConfig(name);
|
|
190
|
+
const cfg = this.registry.resolveAgentConfig(name);
|
|
177
191
|
const disabled = cfg.enabled === false;
|
|
178
|
-
const model = getModelLabel(name, modelRegistry);
|
|
192
|
+
const model = this.getModelLabel(name, modelRegistry);
|
|
179
193
|
const indicator = sourceIndicator(cfg);
|
|
180
194
|
const prefix = `${indicator}${name} · ${model}`;
|
|
181
195
|
const desc = disabled ? "(disabled)" : cfg.description;
|
|
@@ -184,11 +198,11 @@ export function createAgentsMenuHandler({
|
|
|
184
198
|
const maxPrefix = Math.max(...entries.map((e) => e.prefix.length));
|
|
185
199
|
|
|
186
200
|
const hasCustom = allNames.some((n) => {
|
|
187
|
-
const c = registry.resolveAgentConfig(n);
|
|
201
|
+
const c = this.registry.resolveAgentConfig(n);
|
|
188
202
|
return !c.isDefault && c.enabled !== false;
|
|
189
203
|
});
|
|
190
204
|
const hasDisabled = allNames.some(
|
|
191
|
-
(n) => registry.resolveAgentConfig(n).enabled === false,
|
|
205
|
+
(n) => this.registry.resolveAgentConfig(n).enabled === false,
|
|
192
206
|
);
|
|
193
207
|
const legendParts: string[] = [];
|
|
194
208
|
if (hasCustom) legendParts.push("• = project ◦ = global");
|
|
@@ -207,21 +221,21 @@ export function createAgentsMenuHandler({
|
|
|
207
221
|
.split(" · ")[0]
|
|
208
222
|
.replace(/^[•◦✕\s]+/, "")
|
|
209
223
|
.trim();
|
|
210
|
-
if (registry.resolveType(agentName) != null) {
|
|
211
|
-
await editor.showAgentDetail(ui, agentName);
|
|
212
|
-
await showAllAgentsList(ui, modelRegistry);
|
|
224
|
+
if (this.registry.resolveType(agentName) != null) {
|
|
225
|
+
await this.editor.showAgentDetail(ui, agentName);
|
|
226
|
+
await this.showAllAgentsList(ui, modelRegistry);
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
229
|
|
|
216
|
-
async
|
|
217
|
-
const agents = manager.listAgents();
|
|
230
|
+
private async showRunningAgents(ui: MenuUI): Promise<void> {
|
|
231
|
+
const agents = this.manager.listAgents();
|
|
218
232
|
if (agents.length === 0) {
|
|
219
233
|
ui.notify("No agents.", "info");
|
|
220
234
|
return;
|
|
221
235
|
}
|
|
222
236
|
|
|
223
237
|
const options = agents.map((a) => {
|
|
224
|
-
const dn = getDisplayName(a.type, registry);
|
|
238
|
+
const dn = getDisplayName(a.type, this.registry);
|
|
225
239
|
const dur = formatDuration(a.startedAt, a.completedAt);
|
|
226
240
|
return `${dn} (${a.description}) · ${a.toolUses} tools · ${a.status} · ${dur}`;
|
|
227
241
|
});
|
|
@@ -233,11 +247,11 @@ export function createAgentsMenuHandler({
|
|
|
233
247
|
if (idx < 0) return;
|
|
234
248
|
const record = agents[idx];
|
|
235
249
|
|
|
236
|
-
await viewAgentConversation(ui, record);
|
|
237
|
-
await showRunningAgents(ui);
|
|
250
|
+
await this.viewAgentConversation(ui, record);
|
|
251
|
+
await this.showRunningAgents(ui);
|
|
238
252
|
}
|
|
239
253
|
|
|
240
|
-
async
|
|
254
|
+
private async viewAgentConversation(ui: MenuUI, record: AgentRecord): Promise<void> {
|
|
241
255
|
const session = record.session;
|
|
242
256
|
if (!session) {
|
|
243
257
|
ui.notify(
|
|
@@ -250,7 +264,7 @@ export function createAgentsMenuHandler({
|
|
|
250
264
|
const { ConversationViewer, VIEWPORT_HEIGHT_PCT } = await import(
|
|
251
265
|
"./conversation-viewer"
|
|
252
266
|
);
|
|
253
|
-
const activity = agentActivity.get(record.id);
|
|
267
|
+
const activity = this.agentActivity.get(record.id);
|
|
254
268
|
|
|
255
269
|
await ui.custom<undefined>(
|
|
256
270
|
(tui: any, theme: any, _keybindings: any, done: any) => {
|
|
@@ -261,7 +275,7 @@ export function createAgentsMenuHandler({
|
|
|
261
275
|
activity,
|
|
262
276
|
theme,
|
|
263
277
|
done,
|
|
264
|
-
registry,
|
|
278
|
+
registry: this.registry,
|
|
265
279
|
wrapText: wrapTextWithAnsi,
|
|
266
280
|
});
|
|
267
281
|
},
|
|
@@ -276,23 +290,23 @@ export function createAgentsMenuHandler({
|
|
|
276
290
|
);
|
|
277
291
|
}
|
|
278
292
|
|
|
279
|
-
async
|
|
293
|
+
private async showSettings(ui: MenuUI): Promise<void> {
|
|
280
294
|
const choice = await ui.select("Settings", [
|
|
281
|
-
`Max concurrency (current: ${settings.maxConcurrent})`,
|
|
282
|
-
`Default max turns (current: ${settings.defaultMaxTurns ?? "unlimited"})`,
|
|
283
|
-
`Grace turns (current: ${settings.graceTurns})`,
|
|
295
|
+
`Max concurrency (current: ${this.settings.maxConcurrent})`,
|
|
296
|
+
`Default max turns (current: ${this.settings.defaultMaxTurns ?? "unlimited"})`,
|
|
297
|
+
`Grace turns (current: ${this.settings.graceTurns})`,
|
|
284
298
|
]);
|
|
285
299
|
if (!choice) return;
|
|
286
300
|
|
|
287
301
|
if (choice.startsWith("Max concurrency")) {
|
|
288
302
|
const val = await ui.input(
|
|
289
303
|
"Max concurrent background agents",
|
|
290
|
-
String(settings.maxConcurrent),
|
|
304
|
+
String(this.settings.maxConcurrent),
|
|
291
305
|
);
|
|
292
306
|
if (val) {
|
|
293
307
|
const n = parseInt(val, 10);
|
|
294
308
|
if (n >= 1) {
|
|
295
|
-
const toast = settings.applyMaxConcurrent(n);
|
|
309
|
+
const toast = this.settings.applyMaxConcurrent(n);
|
|
296
310
|
ui.notify(toast.message, toast.level);
|
|
297
311
|
} else {
|
|
298
312
|
ui.notify("Must be a positive integer.", "warning");
|
|
@@ -301,12 +315,12 @@ export function createAgentsMenuHandler({
|
|
|
301
315
|
} else if (choice.startsWith("Default max turns")) {
|
|
302
316
|
const val = await ui.input(
|
|
303
317
|
"Default max turns before wrap-up (0 = unlimited)",
|
|
304
|
-
String(settings.defaultMaxTurns ?? 0),
|
|
318
|
+
String(this.settings.defaultMaxTurns ?? 0),
|
|
305
319
|
);
|
|
306
320
|
if (val) {
|
|
307
321
|
const n = parseInt(val, 10);
|
|
308
322
|
if (n >= 0) {
|
|
309
|
-
const toast = settings.applyDefaultMaxTurns(n);
|
|
323
|
+
const toast = this.settings.applyDefaultMaxTurns(n);
|
|
310
324
|
ui.notify(toast.message, toast.level);
|
|
311
325
|
} else {
|
|
312
326
|
ui.notify("Must be 0 (unlimited) or a positive integer.", "warning");
|
|
@@ -315,12 +329,12 @@ export function createAgentsMenuHandler({
|
|
|
315
329
|
} else if (choice.startsWith("Grace turns")) {
|
|
316
330
|
const val = await ui.input(
|
|
317
331
|
"Grace turns after wrap-up steer",
|
|
318
|
-
String(settings.graceTurns),
|
|
332
|
+
String(this.settings.graceTurns),
|
|
319
333
|
);
|
|
320
334
|
if (val) {
|
|
321
335
|
const n = parseInt(val, 10);
|
|
322
336
|
if (n >= 1) {
|
|
323
|
-
const toast = settings.applyGraceTurns(n);
|
|
337
|
+
const toast = this.settings.applyGraceTurns(n);
|
|
324
338
|
ui.notify(toast.message, toast.level);
|
|
325
339
|
} else {
|
|
326
340
|
ui.notify("Must be a positive integer.", "warning");
|
|
@@ -328,16 +342,4 @@ export function createAgentsMenuHandler({
|
|
|
328
342
|
}
|
|
329
343
|
}
|
|
330
344
|
}
|
|
331
|
-
|
|
332
|
-
return async ({
|
|
333
|
-
ui,
|
|
334
|
-
modelRegistry,
|
|
335
|
-
parentSnapshot,
|
|
336
|
-
}: {
|
|
337
|
-
ui: MenuUI;
|
|
338
|
-
modelRegistry: ModelRegistry;
|
|
339
|
-
parentSnapshot: ParentSnapshot;
|
|
340
|
-
}) => {
|
|
341
|
-
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
342
|
-
};
|
|
343
345
|
}
|