@gotgenes/pi-subagents 5.8.0 → 5.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/docs/architecture/architecture.md +9 -10
- package/docs/plans/0066-replace-as-any-with-sdk-types.md +215 -0
- package/docs/retro/0070-extract-event-handlers.md +49 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +1 -1
- package/src/index.ts +9 -9
- package/src/notification.ts +4 -1
- package/src/tools/agent-tool.ts +9 -7
- package/src/tools/get-result-tool.ts +2 -1
- package/src/tools/steer-tool.ts +2 -1
- package/src/ui/agent-menu.ts +20 -31
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
|
+
## [5.8.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v5.8.0...pi-subagents-v5.8.1) (2026-05-20)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* plan replace as-any casts with SDK types ([#66](https://github.com/gotgenes/pi-packages/issues/66)) ([a8728bf](https://github.com/gotgenes/pi-packages/commit/a8728bf695a11f649fb908a6a31a55842b76ce32))
|
|
14
|
+
* **retro:** add retro notes for issue [#70](https://github.com/gotgenes/pi-packages/issues/70) ([0668956](https://github.com/gotgenes/pi-packages/commit/0668956e99383df7a62fddde57dd4182bf491a5e))
|
|
15
|
+
* **retro:** record architecture update for [#70](https://github.com/gotgenes/pi-packages/issues/70) ([49d4fae](https://github.com/gotgenes/pi-packages/commit/49d4faee4ee711e385dbf59334b0b737897d26a6))
|
|
16
|
+
* update architecture roadmap with [#87](https://github.com/gotgenes/pi-packages/issues/87), [#70](https://github.com/gotgenes/pi-packages/issues/70) status ([97a2da1](https://github.com/gotgenes/pi-packages/commit/97a2da11cbd73c9f84eb2158e2437cbe8c749208))
|
|
17
|
+
|
|
8
18
|
## [5.8.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v5.7.0...pi-subagents-v5.8.0) (2026-05-20)
|
|
9
19
|
|
|
10
20
|
|
|
@@ -374,14 +374,13 @@ These build on Phase 1 and should land after it.
|
|
|
374
374
|
- Removed all runtime imports of `agent-runner.ts` and `worktree.ts` from `agent-manager.ts` (only `import type` remains).
|
|
375
375
|
- Migrated all tests from `vi.mock()` module stubs to `vi.fn()` interface stubs.
|
|
376
376
|
|
|
377
|
-
3. **gotgenes/pi-packages#87** — Evolve `SubagentRuntime` from data bag to object with methods
|
|
378
|
-
-
|
|
377
|
+
3. **gotgenes/pi-packages#87** ✓ — Evolve `SubagentRuntime` from data bag to object with methods
|
|
378
|
+
- Added session-context methods (`setSessionContext`, `clearSessionContext`) and widget delegation methods (`setUICtx`, `onTurnStart`, `markFinished`, `updateWidget`, `ensureTimer`).
|
|
379
379
|
- Prerequisite for #70 — without runtime methods, extracted handlers would move LoD violations and output-argument smells into handler classes.
|
|
380
380
|
|
|
381
|
-
4. **gotgenes/pi-packages#70** — Extract event handlers into `src/handlers/`
|
|
382
|
-
-
|
|
383
|
-
-
|
|
384
|
-
- Target: `src/index.ts` ≤150 lines.
|
|
381
|
+
4. **gotgenes/pi-packages#70** ✓ — Extract event handlers into `src/handlers/`
|
|
382
|
+
- Moved the four inline lambdas (`session_start`, `session_before_switch`, `session_shutdown`, `tool_execution_start`) into `SessionLifecycleHandler` and `ToolStartHandler` classes.
|
|
383
|
+
- Handlers call methods on narrow runtime interfaces — no raw field writes, no `widget!` reach-throughs.
|
|
385
384
|
|
|
386
385
|
### Phase 3: Interface polish
|
|
387
386
|
|
|
@@ -407,7 +406,7 @@ Small cleanups that are safest after the structural changes settle.
|
|
|
407
406
|
### Dependency graph
|
|
408
407
|
|
|
409
408
|
```text
|
|
410
|
-
#69 (SubagentRuntime) ✓ ──► #87 (runtime methods) ─┬─► #70 (handler extraction)
|
|
409
|
+
#69 (SubagentRuntime) ✓ ──► #87 (runtime methods) ✓ ─┬─► #70 (handler extraction) ✓
|
|
411
410
|
│
|
|
412
411
|
#71 (pure assembler) ✓ │
|
|
413
412
|
#80 (config lookup) ✓ │
|
|
@@ -427,11 +426,11 @@ Small cleanups that are safest after the structural changes settle.
|
|
|
427
426
|
The recommended sequence is:
|
|
428
427
|
|
|
429
428
|
```text
|
|
430
|
-
#69 ✓ → #71 ✓ → #80 ✓ → #76 ✓ → #84 ✓ → #72 ✓ → #87 → #70 → #66 → #77 → #61
|
|
429
|
+
#69 ✓ → #71 ✓ → #80 ✓ → #76 ✓ → #84 ✓ → #72 ✓ → #87 ✓ → #70 ✓ → #66 → #77 → #61
|
|
431
430
|
```
|
|
432
431
|
|
|
433
|
-
Phase 1 is complete; Phase 2 is
|
|
434
|
-
The next issue is #
|
|
432
|
+
Phase 1 is complete; Phase 2 is complete.
|
|
433
|
+
The next issue is #66 (replace `as any` casts with proper SDK types).
|
|
435
434
|
Issue #22 is a parallel cross-extension track and does not gate the structural work.
|
|
436
435
|
|
|
437
436
|
## Relationship with upstream
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 66
|
|
3
|
+
issue_title: "refactor: replace `as any` casts in extracted tool/menu factories with proper SDK types"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Replace `as any` casts in factory dep interfaces with proper SDK types
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
The decomposition in #54 introduced 14 `as any` casts at the wiring boundary in `src/index.ts`.
|
|
11
|
+
These exist because the factory dep interfaces (`AgentToolManager`, `GetResultDeps`, `SteerToolDeps`, `AgentMenuDeps`, `NotificationDeps`) declare `unknown` or `object` for parameters that are actually well-typed SDK exports.
|
|
12
|
+
Every cast papers over a real type that the SDK already exports.
|
|
13
|
+
For comparison, `pi-permission-system` imports SDK types directly in its internal interfaces and has zero `as any` casts.
|
|
14
|
+
|
|
15
|
+
## Goals
|
|
16
|
+
|
|
17
|
+
- Replace every `as any` cast in `src/index.ts` by typing the corresponding dep interface parameter with the actual SDK type.
|
|
18
|
+
- Keep factory modules decoupled from `AgentManager` where possible — use shared types (`types.ts`) or SDK imports.
|
|
19
|
+
- No runtime behavior change — this is purely a type-safety improvement.
|
|
20
|
+
- Existing tests continue to pass without modification (factory `execute` methods use `ctx: any` or `ctx: unknown`, so test mocks are unaffected).
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- Fixing `as any` casts in `agent-runner.ts`, `conversation-viewer.ts`, `tools/helpers.ts`, or `tools/agent-tool.ts:550` — those access untyped SDK message internals, a different concern.
|
|
25
|
+
- Removing the `ctx as UICtx` cast (line 196) — that is a named cast, not `as any`.
|
|
26
|
+
- Removing `as any` from test files (e.g., `agent-menu.test.ts`'s `ctx as any`) — test mocks are intentionally narrow.
|
|
27
|
+
|
|
28
|
+
## Background
|
|
29
|
+
|
|
30
|
+
### Current cast inventory (`src/index.ts`)
|
|
31
|
+
|
|
32
|
+
| Line | Cast | Root cause |
|
|
33
|
+
| ---- | ----------------------------------------------- | --------------------------------------------------------------------------- |
|
|
34
|
+
| 59 | `msg as any, opts as any` | `NotificationDeps.sendMessage(msg: unknown, opts: unknown)` |
|
|
35
|
+
| 188 | `ctx as any, opts as any` | `AgentToolManager.spawn(ctx: unknown, …, opts: object)` |
|
|
36
|
+
| 189 | `ctx as any, opts as any` | `AgentToolManager.spawnAndWait(ctx: unknown, …, opts: object)` |
|
|
37
|
+
| 208 | `}) as any` | Cascading from above — `createAgentTool` return doesn't match `defineTool` |
|
|
38
|
+
| 215 | `session as any` | `GetResultDeps.getConversation(session: unknown)` |
|
|
39
|
+
| 223 | `session as any` | `SteerToolDeps.steerAgent(session: unknown, …)` |
|
|
40
|
+
| 232 | `(piArg ?? pi) as any, ctx as any, opts as any` | `AgentMenuManager.spawnAndWait(pi: unknown, ctx: unknown, …, opts: object)` |
|
|
41
|
+
| 242 | `registry as any` | `AgentMenuDeps.getModelLabel(…, registry?: unknown)` |
|
|
42
|
+
| 271 | `ctx as any` | `MenuContext` not structurally compatible with `ExtensionCommandContext` |
|
|
43
|
+
|
|
44
|
+
### SDK types available
|
|
45
|
+
|
|
46
|
+
All are exported from `@earendil-works/pi-coding-agent`:
|
|
47
|
+
|
|
48
|
+
- `ExtensionContext` — tool execute context (has `ui`, `modelRegistry`, `cwd`, etc.)
|
|
49
|
+
- `ExtensionCommandContext` — command handler context (extends `ExtensionContext`)
|
|
50
|
+
- `ExtensionAPI` — the `pi` object passed to extensions
|
|
51
|
+
- `AgentSession` — session handle (has `steer()`, `messages`, etc.)
|
|
52
|
+
|
|
53
|
+
`ModelRegistry` is already defined locally in `src/model-resolver.ts` and matches the SDK shape.
|
|
54
|
+
`SpawnOptions` is defined locally in `src/agent-manager.ts` (not currently exported).
|
|
55
|
+
|
|
56
|
+
### Relevant AGENTS.md constraints
|
|
57
|
+
|
|
58
|
+
- "Avoid `any` unless absolutely necessary."
|
|
59
|
+
- "Keep Pi SDK imports out of business-logic modules."
|
|
60
|
+
Tool definitions, event handlers, and command handlers are SDK consumers — they may import SDK types directly.
|
|
61
|
+
The restriction targets pure helpers, utilities, and domain modules.
|
|
62
|
+
- "When writing event handlers that consume Pi SDK types, prefer lean local payload interfaces over full SDK event types."
|
|
63
|
+
|
|
64
|
+
The factory modules (`tools/agent-tool.ts`, `tools/get-result-tool.ts`, `tools/steer-tool.ts`, `ui/agent-menu.ts`) are SDK consumers (they define tools and command handlers), so importing SDK types is acceptable.
|
|
65
|
+
The notification module (`notification.ts`) is a pure helper — it should use narrow local types rather than SDK imports.
|
|
66
|
+
|
|
67
|
+
## Design Overview
|
|
68
|
+
|
|
69
|
+
### Strategy per interface
|
|
70
|
+
|
|
71
|
+
1. **`NotificationDeps.sendMessage`** — define narrow inline parameter types matching `ExtensionAPI.sendMessage`'s shape.
|
|
72
|
+
No SDK import needed; the notification module stays SDK-independent.
|
|
73
|
+
|
|
74
|
+
2. **`AgentToolManager`** — import `ExtensionContext` for `ctx`, export + import `SpawnOptions` from `agent-manager.ts` for `opts`.
|
|
75
|
+
The tool module is an SDK consumer, so SDK imports are acceptable.
|
|
76
|
+
|
|
77
|
+
3. **`AgentToolWidget.setUICtx`** — stays `ctx: unknown` (not `as any`; currently `ctx as UICtx`).
|
|
78
|
+
Out of scope.
|
|
79
|
+
|
|
80
|
+
4. **`GetResultDeps.getConversation`** — import `AgentSession` for `session`.
|
|
81
|
+
|
|
82
|
+
5. **`SteerToolDeps.steerAgent`** — import `AgentSession` for `session`.
|
|
83
|
+
|
|
84
|
+
6. **`AgentMenuManager.spawnAndWait`** — import `ExtensionAPI` for `pi`, `ExtensionContext` for `ctx`, export + import `SpawnOptions` for `opts`.
|
|
85
|
+
|
|
86
|
+
7. **`AgentMenuDeps.getModelLabel`** — import `ModelRegistry` from `../model-resolver.js` for `registry`.
|
|
87
|
+
|
|
88
|
+
8. **`MenuContext` structural compatibility** — switch `MenuUI` from property syntax (strict function types) to method syntax (bivariant), and type `modelRegistry` as `ModelRegistry`.
|
|
89
|
+
This makes `ExtensionCommandContext` structurally assignable to `MenuContext`, removing the cast without broadening the handler's dependency on the full SDK type.
|
|
90
|
+
Tests continue using narrow mocks because the handler's parameter is still the narrow `MenuContext`.
|
|
91
|
+
|
|
92
|
+
### `SpawnOptions` export
|
|
93
|
+
|
|
94
|
+
`SpawnOptions` in `agent-manager.ts` is currently a private interface.
|
|
95
|
+
Exporting it as a named `type` export adds no runtime cost and avoids duplicating the 15-field type in each factory.
|
|
96
|
+
Both `tools/agent-tool.ts` and `ui/agent-menu.ts` already import `AgentRecord` from `types.ts`, so an intra-package `import type` from `agent-manager.ts` follows the same pattern.
|
|
97
|
+
|
|
98
|
+
### Cascading cast resolution
|
|
99
|
+
|
|
100
|
+
The `createAgentTool({…}) as any` cast on line 208 exists because TypeScript cannot verify the returned object satisfies `ToolDefinition` when inner types are `unknown`/`object`.
|
|
101
|
+
Once the inner types are correct, the factory return type matches `ToolDefinition` naturally, and the outer `as any` cast resolves without further changes.
|
|
102
|
+
|
|
103
|
+
## Module-Level Changes
|
|
104
|
+
|
|
105
|
+
### `src/agent-manager.ts`
|
|
106
|
+
|
|
107
|
+
- Export the existing `SpawnOptions` interface (add `export` keyword).
|
|
108
|
+
|
|
109
|
+
### `src/notification.ts`
|
|
110
|
+
|
|
111
|
+
- Replace `sendMessage: (msg: unknown, opts: unknown) => void` with narrow inline types:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
sendMessage: (
|
|
115
|
+
msg: { customType: string; content: string; display?: boolean; details?: unknown },
|
|
116
|
+
opts?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
|
117
|
+
) => void;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `src/tools/agent-tool.ts`
|
|
121
|
+
|
|
122
|
+
- Add `import type { ExtensionContext } from "@earendil-works/pi-coding-agent";`
|
|
123
|
+
- Add `import type { SpawnOptions } from "../agent-manager.js";`
|
|
124
|
+
- `AgentToolManager.spawn`: `ctx: unknown` → `ctx: ExtensionContext`, `opts: object` → `opts: SpawnOptions`
|
|
125
|
+
- `AgentToolManager.spawnAndWait`: same changes, opts as `Omit<SpawnOptions, "isBackground">`
|
|
126
|
+
|
|
127
|
+
### `src/tools/get-result-tool.ts`
|
|
128
|
+
|
|
129
|
+
- Add `import type { AgentSession } from "@earendil-works/pi-coding-agent";`
|
|
130
|
+
- `GetResultDeps.getConversation`: `session: unknown` → `session: AgentSession`
|
|
131
|
+
|
|
132
|
+
### `src/tools/steer-tool.ts`
|
|
133
|
+
|
|
134
|
+
- Add `import type { AgentSession } from "@earendil-works/pi-coding-agent";`
|
|
135
|
+
- `SteerToolDeps.steerAgent`: `session: unknown` → `session: AgentSession`
|
|
136
|
+
|
|
137
|
+
### `src/ui/agent-menu.ts`
|
|
138
|
+
|
|
139
|
+
- Add `import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";`
|
|
140
|
+
- Add `import type { SpawnOptions } from "../agent-manager.js";`
|
|
141
|
+
- Add `import type { ModelRegistry } from "../model-resolver.js";`
|
|
142
|
+
- `AgentMenuManager.spawnAndWait`: `pi: unknown` → `pi: ExtensionAPI | null`, `ctx: unknown` → `ctx: ExtensionContext`, `opts: object` → `opts: Omit<SpawnOptions, "isBackground">`
|
|
143
|
+
- `AgentMenuDeps.getModelLabel`: `registry?: unknown` → `registry?: ModelRegistry`
|
|
144
|
+
- `MenuUI`: switch from property syntax to method syntax for structural compatibility with `ExtensionUIContext`.
|
|
145
|
+
Update `notify` second parameter from `level: string` to `type?: string` to match SDK.
|
|
146
|
+
- `MenuContext.modelRegistry`: `unknown` → `ModelRegistry`
|
|
147
|
+
|
|
148
|
+
### `src/index.ts`
|
|
149
|
+
|
|
150
|
+
- Remove all 14 `as any` casts.
|
|
151
|
+
- No new imports needed (types already used transitively).
|
|
152
|
+
|
|
153
|
+
## Test Impact Analysis
|
|
154
|
+
|
|
155
|
+
1. **No new unit tests needed** — this is a type-only refactoring with no behavioral change.
|
|
156
|
+
2. **No existing tests become redundant** — the tests exercise tool execution and menu behavior, not type signatures.
|
|
157
|
+
3. **Existing tests must stay as-is** — factory `execute` methods use `ctx: any` or `ctx: unknown`, so `makeCtx()` test helpers remain compatible.
|
|
158
|
+
Test-side `as any` casts (e.g., `handler(ctx as any)` in `agent-menu.test.ts`) are out of scope.
|
|
159
|
+
|
|
160
|
+
## TDD Order
|
|
161
|
+
|
|
162
|
+
Since this is a type-only change, each step is verified by `pnpm run check` (tsc) rather than new vitest tests.
|
|
163
|
+
The full test suite (`pnpm vitest run`) is run at the end as a regression check.
|
|
164
|
+
|
|
165
|
+
1. **Export `SpawnOptions`** — add `export` to the existing `interface SpawnOptions` in `agent-manager.ts`.
|
|
166
|
+
Verify: `pnpm run check`.
|
|
167
|
+
Commit: `refactor: export SpawnOptions from agent-manager`
|
|
168
|
+
|
|
169
|
+
2. **Type `NotificationDeps`** — replace `(msg: unknown, opts: unknown)` with narrow inline types in `notification.ts`.
|
|
170
|
+
Remove `msg as any, opts as any` casts (line 59) in `index.ts`.
|
|
171
|
+
Verify: `pnpm run check`.
|
|
172
|
+
Commit: `refactor: type NotificationDeps.sendMessage parameters (#66)`
|
|
173
|
+
|
|
174
|
+
3. **Type `AgentToolManager`** — import `ExtensionContext` and `SpawnOptions`, update `spawn` and `spawnAndWait` signatures in `agent-tool.ts`.
|
|
175
|
+
Remove `ctx as any, opts as any` casts (lines 188–189) and the cascading `}) as any` cast (line 208) in `index.ts`.
|
|
176
|
+
Verify: `pnpm run check`.
|
|
177
|
+
Commit: `refactor: type AgentToolManager with ExtensionContext and SpawnOptions (#66)`
|
|
178
|
+
|
|
179
|
+
4. **Type `GetResultDeps`** — import `AgentSession`, update `getConversation` in `get-result-tool.ts`.
|
|
180
|
+
Remove `session as any` cast (line 215) in `index.ts`.
|
|
181
|
+
Verify: `pnpm run check`.
|
|
182
|
+
Commit: `refactor: type GetResultDeps.getConversation with AgentSession (#66)`
|
|
183
|
+
|
|
184
|
+
5. **Type `SteerToolDeps`** — import `AgentSession`, update `steerAgent` in `steer-tool.ts`.
|
|
185
|
+
Remove `session as any` cast (line 223) in `index.ts`.
|
|
186
|
+
Verify: `pnpm run check`.
|
|
187
|
+
Commit: `refactor: type SteerToolDeps.steerAgent with AgentSession (#66)`
|
|
188
|
+
|
|
189
|
+
6. **Type `AgentMenuManager` + `AgentMenuDeps`** — import `ExtensionAPI`, `ExtensionContext`, `SpawnOptions`, `ModelRegistry`.
|
|
190
|
+
Update `spawnAndWait` and `getModelLabel` signatures in `agent-menu.ts`.
|
|
191
|
+
Remove `(piArg ?? pi) as any, ctx as any, opts as any` (line 232) and `registry as any` (line 242) casts in `index.ts`.
|
|
192
|
+
Verify: `pnpm run check`.
|
|
193
|
+
Commit: `refactor: type AgentMenu interfaces with SDK types (#66)`
|
|
194
|
+
|
|
195
|
+
7. **Align `MenuContext` with `ExtensionCommandContext`** — switch `MenuUI` to method syntax, fix `notify` parameter, type `modelRegistry` as `ModelRegistry`.
|
|
196
|
+
Remove `ctx as any` cast (line 271) in `index.ts`.
|
|
197
|
+
Verify: `pnpm run check`.
|
|
198
|
+
Commit: `refactor: align MenuContext for structural ExtensionCommandContext compat (#66)`
|
|
199
|
+
|
|
200
|
+
8. **Final verification** — run `pnpm vitest run` for the full test suite.
|
|
201
|
+
Grep `src/index.ts` for remaining `as any` — expect zero.
|
|
202
|
+
Commit: none (verification only).
|
|
203
|
+
|
|
204
|
+
## Risks and Mitigations
|
|
205
|
+
|
|
206
|
+
| Risk | Mitigation |
|
|
207
|
+
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
208
|
+
| Exporting `SpawnOptions` from `agent-manager.ts` couples factory modules to the manager's type | The coupling is type-only (`import type`), adds no runtime dependency, and follows the existing pattern (`AgentRecord` in `types.ts` is already shared). |
|
|
209
|
+
| `MenuUI` method-syntax change could break structural compatibility with test mocks | Test mocks use `vi.fn()` which is bivariant; method syntax on the `MenuUI` interface only affects how TypeScript checks assignability from `ExtensionUIContext`, not from mock objects. |
|
|
210
|
+
| SDK type exports could change in a future Pi release | The SDK types used (`ExtensionContext`, `AgentSession`, `ExtensionAPI`) are stable public API. `ModelRegistry` is a local interface. |
|
|
211
|
+
| Cascading `as any` on `createAgentTool` may not resolve automatically | If TypeScript still cannot infer the return type, add explicit `satisfies ToolDefinition<…>` or a return type annotation. Verified in step 3. |
|
|
212
|
+
|
|
213
|
+
## Open Questions
|
|
214
|
+
|
|
215
|
+
- None — the issue's "Proposed change" section is unambiguous and all SDK types are confirmed available.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 70
|
|
3
|
+
issue_title: "refactor: extract event handlers from pi-subagents index.ts into src/handlers/"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #70 — extract event handlers from index.ts
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-20T19:30:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned and implemented #70 — extracting four inline event handler lambdas from `src/index.ts` into `src/handlers/lifecycle.ts` (`SessionLifecycleHandler` class) and `src/handlers/tool-start.ts` (`ToolStartHandler` class).
|
|
13
|
+
During planning, the user identified that the initial plain-function design missed shared collaborators and structural smells, prompting a redesign to class-based handlers and a predecessor issue (#87) to evolve `SubagentRuntime` from a data bag to an object with methods.
|
|
14
|
+
Released as `pi-subagents-v5.8.0` with 8 new handler tests (520 total).
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
#### What went well
|
|
19
|
+
|
|
20
|
+
- The user's progressive questioning during planning ("Are you confident?", "Keep digging", Kent Beck quote) surfaced two concrete structural smells — output arguments on `runtime.currentCtx` writes and 8 LoD violations via `runtime.widget!` reach-throughs — that the initial design would have just relocated rather than fixed.
|
|
21
|
+
- Filing predecessor #87 was the right sequencing call.
|
|
22
|
+
By the time #70 executed, the runtime had proper methods and the handler extraction was purely mechanical.
|
|
23
|
+
- The 3-step TDD cycle executed cleanly with zero rework or deviations from the plan.
|
|
24
|
+
The only issue was a `tsc` type error (`vi.fn()` return type not assignable to `() => void`) caught during the post-TDD type check and fixed in-place before the final commit.
|
|
25
|
+
|
|
26
|
+
#### What caused friction (agent side)
|
|
27
|
+
|
|
28
|
+
- `premature-convergence` — The initial plan used plain functions with per-call `LifecycleDeps`/`ToolStartDeps` interfaces, the first viable approach, without analyzing whether handlers shared collaborators.
|
|
29
|
+
The user had to prompt three times before I switched to class-based handlers with constructor-injected shared deps.
|
|
30
|
+
Impact: three rounds of plan revision before the design was correct; no rework commits, but significant planning churn.
|
|
31
|
+
|
|
32
|
+
- `instruction-violation` (user-caught) — The `/plan-issue` prompt says "load the `design-review` skill and run its checklist on the affected modules."
|
|
33
|
+
I loaded the skill but did not actually run the checklist (grep for access patterns, check LoD, check output arguments) until the user explicitly told me to "keep digging."
|
|
34
|
+
Had I run the checklist proactively, the output-argument and LoD findings would have surfaced on the first pass, and the predecessor issue (#87) would have been identified without user escalation.
|
|
35
|
+
Impact: user had to escalate three times; planning took ~3× longer than necessary.
|
|
36
|
+
|
|
37
|
+
- `wrong-abstraction` — I initially reasoned at the mechanical level ("handlers are small, 1–5 lines, so plain functions are fine") instead of the structural level ("do these handlers share collaborators that a class captures naturally?").
|
|
38
|
+
The code-style skill explicitly says "Do not pass a shared dependency bag to functions that only use a subset" — but I applied it backwards (splitting into per-function deps) rather than recognizing the shared deps as a class cohesion signal.
|
|
39
|
+
Impact: same as premature-convergence above; folded into the same rework cycle.
|
|
40
|
+
|
|
41
|
+
#### What caused friction (user side)
|
|
42
|
+
|
|
43
|
+
- The progressive escalation approach (question → directive → quote) was effective pedagogically but required three turns of user attention on what a proactive design-review checklist run would have caught automatically.
|
|
44
|
+
Earlier intervention with a specific redirect (e.g., "Run the design-review checklist before writing the plan") could have resolved it in one turn.
|
|
45
|
+
|
|
46
|
+
### Changes made
|
|
47
|
+
|
|
48
|
+
1. Retro file created at `packages/pi-subagents/docs/retro/0070-extract-event-handlers.md`.
|
|
49
|
+
2. Updated `packages/pi-subagents/docs/architecture/architecture.md` — marked #87 and #70 as done (✓), updated Phase 2 status to complete, updated next-issue pointer to #66.
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -56,7 +56,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
56
56
|
// runtime.widget is assigned after AgentManager construction; arrow closures
|
|
57
57
|
// capture `runtime` by reference so they always read the current value.
|
|
58
58
|
const notifications = createNotificationSystem({
|
|
59
|
-
sendMessage: (msg, opts) => pi.sendMessage(msg
|
|
59
|
+
sendMessage: (msg, opts) => pi.sendMessage(msg, opts),
|
|
60
60
|
agentActivity: runtime.agentActivity,
|
|
61
61
|
markFinished: (id) => runtime.markFinished(id),
|
|
62
62
|
updateWidget: () => runtime.updateWidget(),
|
|
@@ -185,8 +185,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
185
185
|
|
|
186
186
|
pi.registerTool(defineTool(createAgentTool({
|
|
187
187
|
manager: {
|
|
188
|
-
spawn: (ctx, type, prompt, opts) => manager.spawn(pi, ctx
|
|
189
|
-
spawnAndWait: (ctx, type, prompt, opts) => manager.spawnAndWait(pi, ctx
|
|
188
|
+
spawn: (ctx, type, prompt, opts) => manager.spawn(pi, ctx, type, prompt, opts),
|
|
189
|
+
spawnAndWait: (ctx, type, prompt, opts) => manager.spawnAndWait(pi, ctx, type, prompt, opts),
|
|
190
190
|
resume: (id, prompt, signal) => manager.resume(id, prompt, signal),
|
|
191
191
|
getRecord: (id) => manager.getRecord(id),
|
|
192
192
|
getMaxConcurrent: () => manager.getMaxConcurrent(),
|
|
@@ -205,14 +205,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
205
205
|
availableTypesText: getAvailableTypes().join(", "),
|
|
206
206
|
agentDir: getAgentDir(),
|
|
207
207
|
getDefaultMaxTurns: () => runtime.defaultMaxTurns,
|
|
208
|
-
})
|
|
208
|
+
})));
|
|
209
209
|
|
|
210
210
|
// ---- get_subagent_result tool ----
|
|
211
211
|
|
|
212
212
|
pi.registerTool(defineTool(createGetResultTool({
|
|
213
213
|
getRecord: (id) => manager.getRecord(id),
|
|
214
214
|
cancelNudge: (key) => notifications.cancelNudge(key),
|
|
215
|
-
getConversation: (session) => getAgentConversation(session
|
|
215
|
+
getConversation: (session) => getAgentConversation(session),
|
|
216
216
|
})));
|
|
217
217
|
|
|
218
218
|
// ---- steer_subagent tool ----
|
|
@@ -220,7 +220,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
220
220
|
pi.registerTool(defineTool(createSteerTool({
|
|
221
221
|
getRecord: (id) => manager.getRecord(id),
|
|
222
222
|
emitEvent: (name, data) => pi.events.emit(name, data),
|
|
223
|
-
steerAgent: (session, message) => steerAgent(session
|
|
223
|
+
steerAgent: (session, message) => steerAgent(session, message),
|
|
224
224
|
})));
|
|
225
225
|
|
|
226
226
|
// ---- /agents interactive menu ----
|
|
@@ -229,7 +229,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
229
229
|
manager: {
|
|
230
230
|
listAgents: () => manager.listAgents(),
|
|
231
231
|
getRecord: (id) => manager.getRecord(id),
|
|
232
|
-
spawnAndWait: (piArg, ctx, type, prompt, opts) => manager.spawnAndWait(
|
|
232
|
+
spawnAndWait: (piArg, ctx, type, prompt, opts) => manager.spawnAndWait(piArg ?? pi, ctx, type, prompt, opts),
|
|
233
233
|
getMaxConcurrent: () => manager.getMaxConcurrent(),
|
|
234
234
|
setMaxConcurrent: (n) => manager.setMaxConcurrent(n),
|
|
235
235
|
},
|
|
@@ -239,7 +239,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
239
239
|
const cfg = resolveAgentConfig(type);
|
|
240
240
|
if (!cfg.model) return 'inherit';
|
|
241
241
|
if (registry) {
|
|
242
|
-
const resolved = resolveModel(cfg.model, registry
|
|
242
|
+
const resolved = resolveModel(cfg.model, registry);
|
|
243
243
|
if (typeof resolved === 'string') return 'inherit';
|
|
244
244
|
}
|
|
245
245
|
return getModelLabelFromConfig(cfg.model);
|
|
@@ -268,6 +268,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
268
268
|
|
|
269
269
|
pi.registerCommand('agents', {
|
|
270
270
|
description: 'Manage agents',
|
|
271
|
-
handler: async (_args, ctx) => { await agentsMenuHandler(ctx
|
|
271
|
+
handler: async (_args, ctx) => { await agentsMenuHandler(ctx); },
|
|
272
272
|
});
|
|
273
273
|
}
|
package/src/notification.ts
CHANGED
|
@@ -109,7 +109,10 @@ export function buildEventData(record: AgentRecord) {
|
|
|
109
109
|
|
|
110
110
|
/** Narrow deps for the notification system — only the methods it actually calls. */
|
|
111
111
|
export interface NotificationDeps {
|
|
112
|
-
sendMessage: (
|
|
112
|
+
sendMessage: (
|
|
113
|
+
msg: { customType: string; content: string; display: boolean; details?: unknown },
|
|
114
|
+
opts?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
|
115
|
+
) => void;
|
|
113
116
|
agentActivity: Map<string, AgentActivity>;
|
|
114
117
|
markFinished: (id: string) => void;
|
|
115
118
|
updateWidget: () => void;
|
package/src/tools/agent-tool.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
3
|
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import type { SpawnOptions } from "../agent-manager.js";
|
|
3
5
|
import { normalizeMaxTurns } from "../agent-runner.js";
|
|
4
6
|
import { resolveAgentConfig, resolveType } from "../agent-types.js";
|
|
5
7
|
import { resolveAgentInvocationConfig } from "../invocation-config.js";
|
|
@@ -121,8 +123,8 @@ export function buildDetails(
|
|
|
121
123
|
|
|
122
124
|
/** Narrow manager interface — only the methods the Agent tool calls. */
|
|
123
125
|
export interface AgentToolManager {
|
|
124
|
-
spawn: (ctx:
|
|
125
|
-
spawnAndWait: (ctx:
|
|
126
|
+
spawn: (ctx: ExtensionContext, type: string, prompt: string, opts: SpawnOptions) => string;
|
|
127
|
+
spawnAndWait: (ctx: ExtensionContext, type: string, prompt: string, opts: Omit<SpawnOptions, "isBackground">) => Promise<AgentRecord>;
|
|
126
128
|
resume: (id: string, prompt: string, signal: AbortSignal) => Promise<AgentRecord | undefined>;
|
|
127
129
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
128
130
|
getMaxConcurrent: () => number;
|
|
@@ -353,8 +355,8 @@ Guidelines:
|
|
|
353
355
|
execute: async (
|
|
354
356
|
toolCallId: string,
|
|
355
357
|
params: Record<string, unknown>,
|
|
356
|
-
signal: AbortSignal,
|
|
357
|
-
onUpdate: ((update:
|
|
358
|
+
signal: AbortSignal | undefined,
|
|
359
|
+
onUpdate: ((update: AgentToolResult<any>) => void) | undefined,
|
|
358
360
|
ctx: any,
|
|
359
361
|
) => {
|
|
360
362
|
// Ensure we have UI context for widget rendering
|
|
@@ -436,7 +438,7 @@ Guidelines:
|
|
|
436
438
|
const record = await deps.manager.resume(
|
|
437
439
|
params.resume as string,
|
|
438
440
|
params.prompt as string,
|
|
439
|
-
signal,
|
|
441
|
+
signal ?? new AbortController().signal,
|
|
440
442
|
);
|
|
441
443
|
if (!record) {
|
|
442
444
|
return textResult(`Failed to resume agent "${params.resume}".`);
|
|
@@ -465,7 +467,7 @@ Guidelines:
|
|
|
465
467
|
|
|
466
468
|
try {
|
|
467
469
|
id = deps.manager.spawn(ctx, subagentType, params.prompt as string, {
|
|
468
|
-
description: params.description,
|
|
470
|
+
description: params.description as string,
|
|
469
471
|
model,
|
|
470
472
|
maxTurns: effectiveMaxTurns,
|
|
471
473
|
isolated,
|
|
@@ -585,7 +587,7 @@ Guidelines:
|
|
|
585
587
|
subagentType,
|
|
586
588
|
params.prompt as string,
|
|
587
589
|
{
|
|
588
|
-
description: params.description,
|
|
590
|
+
description: params.description as string,
|
|
589
591
|
model,
|
|
590
592
|
maxTurns: effectiveMaxTurns,
|
|
591
593
|
isolated,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { Type } from "@sinclair/typebox";
|
|
2
3
|
import type { AgentRecord } from "../types.js";
|
|
3
4
|
import { formatDuration, getDisplayName } from "../ui/agent-widget.js";
|
|
@@ -8,7 +9,7 @@ import { formatLifetimeTokens, textResult } from "./helpers.js";
|
|
|
8
9
|
export interface GetResultDeps {
|
|
9
10
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
10
11
|
cancelNudge: (key: string) => void;
|
|
11
|
-
getConversation: (session:
|
|
12
|
+
getConversation: (session: AgentSession) => string | undefined;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/** Create the get_subagent_result tool definition (without Pi SDK wrapper). */
|
package/src/tools/steer-tool.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { Type } from "@sinclair/typebox";
|
|
2
3
|
import type { AgentRecord } from "../types.js";
|
|
3
4
|
import { getSessionContextPercent } from "../usage.js";
|
|
@@ -7,7 +8,7 @@ import { formatLifetimeTokens, textResult } from "./helpers.js";
|
|
|
7
8
|
export interface SteerToolDeps {
|
|
8
9
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
9
10
|
emitEvent: (name: string, data: unknown) => void;
|
|
10
|
-
steerAgent: (session:
|
|
11
|
+
steerAgent: (session: AgentSession, message: string) => Promise<void>;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/** Create the steer_subagent tool definition (without Pi SDK wrapper). */
|
package/src/ui/agent-menu.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { SpawnOptions } from "../agent-manager.js";
|
|
4
6
|
import {
|
|
5
7
|
BUILTIN_TOOL_NAMES,
|
|
6
8
|
getAllTypes,
|
|
7
9
|
resolveAgentConfig,
|
|
8
10
|
resolveType,
|
|
9
11
|
} from "../agent-types.js";
|
|
12
|
+
import type { ModelRegistry } from "../model-resolver.js";
|
|
10
13
|
import type { AgentConfig, AgentRecord } from "../types.js";
|
|
11
14
|
import type { AgentActivity } from "./agent-widget.js";
|
|
12
15
|
import { formatDuration, getDisplayName } from "./agent-widget.js";
|
|
@@ -18,7 +21,7 @@ export interface AgentMenuManager {
|
|
|
18
21
|
listAgents: () => AgentRecord[];
|
|
19
22
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
20
23
|
/** Used by generate wizard to spawn an agent that writes the .md file. */
|
|
21
|
-
spawnAndWait: (pi:
|
|
24
|
+
spawnAndWait: (pi: ExtensionAPI | null, ctx: ExtensionContext, type: string, prompt: string, opts: Omit<SpawnOptions, "isBackground">) => Promise<AgentRecord>;
|
|
22
25
|
getMaxConcurrent: () => number;
|
|
23
26
|
setMaxConcurrent: (n: number) => void;
|
|
24
27
|
}
|
|
@@ -28,7 +31,7 @@ export interface AgentMenuDeps {
|
|
|
28
31
|
reloadCustomAgents: () => void;
|
|
29
32
|
agentActivity: Map<string, AgentActivity>;
|
|
30
33
|
/** Resolve model label for a given agent type + registry. */
|
|
31
|
-
getModelLabel: (type: string, registry?:
|
|
34
|
+
getModelLabel: (type: string, registry?: ModelRegistry) => string;
|
|
32
35
|
/** Snapshot current settings for persistence. */
|
|
33
36
|
snapshotSettings: () => { maxConcurrent: number; defaultMaxTurns: number; graceTurns: number };
|
|
34
37
|
/** Save settings and return a notification result. */
|
|
@@ -50,20 +53,6 @@ export interface AgentMenuDeps {
|
|
|
50
53
|
|
|
51
54
|
// ---- Narrow UI context types ----
|
|
52
55
|
|
|
53
|
-
interface MenuUI {
|
|
54
|
-
select: (title: string, options: string[]) => Promise<string | undefined>;
|
|
55
|
-
input: (prompt: string, defaultValue?: string) => Promise<string | undefined>;
|
|
56
|
-
confirm: (title: string, message: string) => Promise<boolean>;
|
|
57
|
-
editor: (title: string, content: string) => Promise<string | undefined>;
|
|
58
|
-
notify: (message: string, level: string) => void;
|
|
59
|
-
custom: <T>(factory: any, options: any) => Promise<T>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface MenuContext {
|
|
63
|
-
ui: MenuUI;
|
|
64
|
-
modelRegistry?: unknown;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
56
|
// ---- Factory ----
|
|
68
57
|
|
|
69
58
|
/**
|
|
@@ -83,7 +72,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
83
72
|
return undefined;
|
|
84
73
|
}
|
|
85
74
|
|
|
86
|
-
async function showAgentsMenu(ctx:
|
|
75
|
+
async function showAgentsMenu(ctx: ExtensionContext) {
|
|
87
76
|
deps.reloadCustomAgents();
|
|
88
77
|
const allNames = getAllTypes();
|
|
89
78
|
|
|
@@ -137,7 +126,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
137
126
|
}
|
|
138
127
|
}
|
|
139
128
|
|
|
140
|
-
async function showAllAgentsList(ctx:
|
|
129
|
+
async function showAllAgentsList(ctx: ExtensionContext) {
|
|
141
130
|
const allNames = getAllTypes();
|
|
142
131
|
if (allNames.length === 0) {
|
|
143
132
|
ctx.ui.notify("No agents.", "info");
|
|
@@ -191,7 +180,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
191
180
|
}
|
|
192
181
|
}
|
|
193
182
|
|
|
194
|
-
async function showRunningAgents(ctx:
|
|
183
|
+
async function showRunningAgents(ctx: ExtensionContext) {
|
|
195
184
|
const agents = deps.manager.listAgents();
|
|
196
185
|
if (agents.length === 0) {
|
|
197
186
|
ctx.ui.notify("No agents.", "info");
|
|
@@ -215,7 +204,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
215
204
|
await showRunningAgents(ctx);
|
|
216
205
|
}
|
|
217
206
|
|
|
218
|
-
async function viewAgentConversation(ctx:
|
|
207
|
+
async function viewAgentConversation(ctx: ExtensionContext, record: AgentRecord) {
|
|
219
208
|
if (!record.session) {
|
|
220
209
|
ctx.ui.notify(
|
|
221
210
|
`Agent is ${record.status === "queued" ? "queued" : "expired"} — no session available.`,
|
|
@@ -245,7 +234,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
245
234
|
);
|
|
246
235
|
}
|
|
247
236
|
|
|
248
|
-
async function showAgentDetail(ctx:
|
|
237
|
+
async function showAgentDetail(ctx: ExtensionContext, name: string) {
|
|
249
238
|
if (resolveType(name) == null) {
|
|
250
239
|
ctx.ui.notify(`Agent config not found for "${name}".`, "warning");
|
|
251
240
|
return;
|
|
@@ -312,7 +301,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
312
301
|
}
|
|
313
302
|
}
|
|
314
303
|
|
|
315
|
-
async function ejectAgent(ctx:
|
|
304
|
+
async function ejectAgent(ctx: ExtensionContext, name: string, cfg: AgentConfig) {
|
|
316
305
|
const location = await ctx.ui.select("Choose location", [
|
|
317
306
|
"Project (.pi/agents/)",
|
|
318
307
|
`Personal (${deps.personalAgentsDir})`,
|
|
@@ -363,7 +352,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
363
352
|
ctx.ui.notify(`Ejected ${name} to ${targetPath}`, "info");
|
|
364
353
|
}
|
|
365
354
|
|
|
366
|
-
async function disableAgent(ctx:
|
|
355
|
+
async function disableAgent(ctx: ExtensionContext, name: string) {
|
|
367
356
|
const file = findAgentFile(name);
|
|
368
357
|
if (file) {
|
|
369
358
|
const content = readFileSync(file.path, "utf-8");
|
|
@@ -397,7 +386,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
397
386
|
ctx.ui.notify(`Disabled ${name} (${targetPath})`, "info");
|
|
398
387
|
}
|
|
399
388
|
|
|
400
|
-
async function enableAgent(ctx:
|
|
389
|
+
async function enableAgent(ctx: ExtensionContext, name: string) {
|
|
401
390
|
const file = findAgentFile(name);
|
|
402
391
|
if (!file) return;
|
|
403
392
|
|
|
@@ -416,7 +405,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
416
405
|
}
|
|
417
406
|
}
|
|
418
407
|
|
|
419
|
-
async function showCreateWizard(ctx:
|
|
408
|
+
async function showCreateWizard(ctx: ExtensionContext) {
|
|
420
409
|
const location = await ctx.ui.select("Choose location", [
|
|
421
410
|
"Project (.pi/agents/)",
|
|
422
411
|
`Personal (${deps.personalAgentsDir})`,
|
|
@@ -440,7 +429,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
440
429
|
}
|
|
441
430
|
}
|
|
442
431
|
|
|
443
|
-
async function showGenerateWizard(ctx:
|
|
432
|
+
async function showGenerateWizard(ctx: ExtensionContext, targetDir: string) {
|
|
444
433
|
const description = await ctx.ui.input("Describe what this agent should do");
|
|
445
434
|
if (!description) return;
|
|
446
435
|
|
|
@@ -526,7 +515,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
526
515
|
}
|
|
527
516
|
}
|
|
528
517
|
|
|
529
|
-
async function showManualWizard(ctx:
|
|
518
|
+
async function showManualWizard(ctx: ExtensionContext, targetDir: string) {
|
|
530
519
|
const name = await ctx.ui.input("Agent name (filename, no spaces)");
|
|
531
520
|
if (!name) return;
|
|
532
521
|
|
|
@@ -621,7 +610,7 @@ ${systemPrompt}
|
|
|
621
610
|
ctx.ui.notify(`Created ${targetPath}`, "info");
|
|
622
611
|
}
|
|
623
612
|
|
|
624
|
-
async function showSettings(ctx:
|
|
613
|
+
async function showSettings(ctx: ExtensionContext) {
|
|
625
614
|
const choice = await ctx.ui.select("Settings", [
|
|
626
615
|
`Max concurrency (current: ${deps.manager.getMaxConcurrent()})`,
|
|
627
616
|
`Default max turns (current: ${deps.getDefaultMaxTurns() ?? "unlimited"})`,
|
|
@@ -677,13 +666,13 @@ ${systemPrompt}
|
|
|
677
666
|
}
|
|
678
667
|
}
|
|
679
668
|
|
|
680
|
-
function notifyApplied(ctx:
|
|
669
|
+
function notifyApplied(ctx: ExtensionContext, successMsg: string) {
|
|
681
670
|
const { message, level } = deps.saveSettings(deps.snapshotSettings(), successMsg);
|
|
682
|
-
ctx.ui.notify(message, level);
|
|
671
|
+
ctx.ui.notify(message, level as "info" | "warning" | "error");
|
|
683
672
|
}
|
|
684
673
|
|
|
685
674
|
// Return the handler function
|
|
686
|
-
return async (ctx:
|
|
675
|
+
return async (ctx: ExtensionContext) => {
|
|
687
676
|
await showAgentsMenu(ctx);
|
|
688
677
|
};
|
|
689
678
|
}
|