@gotgenes/pi-subagents 5.0.0 → 5.2.0
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 +41 -0
- package/README.md +176 -133
- package/docs/architecture/architecture.md +141 -92
- package/docs/decisions/0001-deferred-patches.md +11 -5
- package/docs/plans/0048-implement-subagents-api.md +2 -1
- package/docs/plans/0049-remove-group-join-output-file-rpc.md +22 -5
- package/docs/plans/0051-update-adr-0001-hard-fork.md +2 -1
- package/docs/plans/0052-remove-scheduled-subagents.md +4 -2
- package/docs/plans/0057-structured-debug-logging.md +154 -0
- package/docs/plans/0069-create-subagent-runtime.md +345 -0
- package/docs/retro/0049-remove-group-join-output-file-rpc.md +15 -4
- package/docs/retro/0051-update-adr-0001-hard-fork.md +7 -3
- package/docs/retro/0053-extract-model-resolution-from-execute.md +14 -4
- package/docs/retro/0054-decompose-index-into-modules.md +20 -5
- package/docs/retro/0057-structured-debug-logging.md +77 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +13 -5
- package/src/agent-runner.ts +13 -26
- package/src/custom-agents.ts +5 -2
- package/src/debug.ts +14 -0
- package/src/env.ts +5 -3
- package/src/index.ts +37 -28
- package/src/memory.ts +5 -2
- package/src/notification.ts +3 -2
- package/src/output-file.ts +4 -1
- package/src/runtime.ts +62 -0
- package/src/skill-loader.ts +3 -1
- package/src/tools/agent-tool.ts +4 -2
- package/src/ui/agent-menu.ts +16 -13
- package/src/worktree.ts +14 -12
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 69
|
|
3
|
+
issue_title: "refactor: eliminate module-scope mutable state in pi-subagents — create SubagentRuntime"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create SubagentRuntime
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`pi-subagents` still uses pre-refactor patterns that `pi-permission-system` eliminated in #43.
|
|
11
|
+
`agent-runner.ts` holds module-scope mutable `let` variables (`defaultMaxTurns`, `graceTurns`) with getter/setter pairs that are called from `settings.ts` via callback injection.
|
|
12
|
+
`index.ts` holds closure-scoped `let` variables (`currentCtx`, `widget`) and a `Map` (`agentActivity`) that are captured by arrow closures and cannot be tested in isolation.
|
|
13
|
+
Both patterns hide real dependencies behind module-scope and closure-scope state, making isolated testing impossible.
|
|
14
|
+
|
|
15
|
+
## Goals
|
|
16
|
+
|
|
17
|
+
- Introduce a `SubagentRuntime` interface and `createSubagentRuntime()` factory in a new `src/runtime.ts`.
|
|
18
|
+
- Move `defaultMaxTurns`, `graceTurns`, `agentActivity`, `currentCtx`, and the widget reference into the runtime.
|
|
19
|
+
- Thread `defaultMaxTurns` and `graceTurns` through `RunOptions` so `agent-runner.ts` reads them from its call-time options — not from module scope.
|
|
20
|
+
- Give `AgentManager` a config getter so it can pass runtime values in `RunOptions`.
|
|
21
|
+
- Reduce `index.ts` to a composition root that creates the runtime and passes it to factories — no closure-scoped mutable `let` variables remain.
|
|
22
|
+
- Remove the module-scope `let` declarations and getter/setter exports from `agent-runner.ts`.
|
|
23
|
+
- No behavior change; pure structural refactor.
|
|
24
|
+
|
|
25
|
+
## Non-Goals
|
|
26
|
+
|
|
27
|
+
- Refactoring `AgentManager` into an options-object constructor (follow-up cleanup).
|
|
28
|
+
- Extracting event handlers into separate files.
|
|
29
|
+
- Changing tool behavior or the `SubagentsService` interface.
|
|
30
|
+
- Changing the `SettingsAppliers` interface in `settings.ts` — the callback pattern is already clean; only the closure targets change.
|
|
31
|
+
|
|
32
|
+
## Background
|
|
33
|
+
|
|
34
|
+
### Prior art
|
|
35
|
+
|
|
36
|
+
`pi-permission-system` solved the identical problem in #43.
|
|
37
|
+
`src/runtime.ts` there defines an `ExtensionRuntime` interface with all mutable state, a `createExtensionRuntime()` factory, and pure helper functions like `refreshExtensionConfig(runtime, ctx)` that write to the runtime instead of module-scope variables.
|
|
38
|
+
The extension's `index.ts` calls `createExtensionRuntime()` once and passes the runtime to handlers and factories.
|
|
39
|
+
This plan follows the same pattern.
|
|
40
|
+
|
|
41
|
+
### Module-scope state in agent-runner.ts
|
|
42
|
+
|
|
43
|
+
Two `let` variables and four getter/setter exports:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
let defaultMaxTurns: number | undefined;
|
|
47
|
+
let graceTurns = 5;
|
|
48
|
+
|
|
49
|
+
export function getDefaultMaxTurns(): number | undefined { ... }
|
|
50
|
+
export function setDefaultMaxTurns(n: number | undefined): void { ... }
|
|
51
|
+
export function getGraceTurns(): number { ... }
|
|
52
|
+
export function setGraceTurns(n: number): void { ... }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`runAgent` reads both from module scope during the turn-limit subscription callback:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const maxTurns = normalizeMaxTurns(
|
|
59
|
+
options.maxTurns ?? agentConfig?.maxTurns ?? defaultMaxTurns,
|
|
60
|
+
);
|
|
61
|
+
// ...
|
|
62
|
+
} else if (softLimitReached && turnCount >= maxTurns + graceTurns) {
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Closure-scoped state in index.ts
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const agentActivity = new Map<string, AgentActivity>();
|
|
69
|
+
let widget: AgentWidget;
|
|
70
|
+
let currentCtx: { pi: unknown; ctx: unknown } | undefined;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`widget` is assigned *after* `AgentManager` construction, but `notifications` closes over it immediately via arrow callbacks (`(id) => widget.markFinished(id)`).
|
|
74
|
+
`currentCtx` is written by `session_start` and read by `createSubagentsService`.
|
|
75
|
+
`agentActivity` is shared across the notification system, widget, agent tool, and menu handler.
|
|
76
|
+
|
|
77
|
+
### Settings flow
|
|
78
|
+
|
|
79
|
+
`settings.ts` defines `SettingsAppliers` with three setter callbacks.
|
|
80
|
+
`applyAndEmitLoaded(appliers, emit)` loads persisted settings and calls them.
|
|
81
|
+
`index.ts` wires the appliers to `setDefaultMaxTurns` / `setGraceTurns` from `agent-runner.ts` and `manager.setMaxConcurrent`.
|
|
82
|
+
After this refactor, the appliers closure targets change to the runtime — the `SettingsAppliers` interface itself stays the same.
|
|
83
|
+
|
|
84
|
+
### Data flow for defaultMaxTurns / graceTurns
|
|
85
|
+
|
|
86
|
+
Current: `settings.ts → setDefaultMaxTurns() → module-scope let → runAgent reads module scope`.
|
|
87
|
+
|
|
88
|
+
After: `settings.ts → applier closure → runtime.defaultMaxTurns → AgentManager.getRunConfig() → RunOptions → runAgent reads options`.
|
|
89
|
+
|
|
90
|
+
### Relevant constraints from AGENTS.md
|
|
91
|
+
|
|
92
|
+
- Keep modules focused and composable (one concern per file).
|
|
93
|
+
- Prefer explicit configuration over hidden behavior.
|
|
94
|
+
- Pi SDK imports stay out of business-logic modules — `runtime.ts` must not import Pi SDK types.
|
|
95
|
+
- Do not read `process.env` / `process.cwd()` inside library functions — accept as parameter.
|
|
96
|
+
- Narrow interfaces per consumer — do not pass a shared dependency bag when a function only uses a subset.
|
|
97
|
+
|
|
98
|
+
## Design Overview
|
|
99
|
+
|
|
100
|
+
### SubagentRuntime interface
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
export interface SubagentRuntime {
|
|
104
|
+
// ── Execution config (was module-scope in agent-runner.ts) ──
|
|
105
|
+
defaultMaxTurns: number | undefined;
|
|
106
|
+
graceTurns: number;
|
|
107
|
+
|
|
108
|
+
// ── Session state (was closure-scoped in index.ts) ──
|
|
109
|
+
currentCtx: { pi: unknown; ctx: unknown } | undefined;
|
|
110
|
+
readonly agentActivity: Map<string, AgentActivity>;
|
|
111
|
+
widget: AgentWidget | null;
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The interface is flat (no sub-objects) to match the prior art in `pi-permission-system`.
|
|
116
|
+
`agentActivity` is `readonly` because the Map itself is never replaced — only its entries change.
|
|
117
|
+
`widget` is nullable because it is constructed after `AgentManager` and assigned later.
|
|
118
|
+
|
|
119
|
+
### createSubagentRuntime factory
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
export function createSubagentRuntime(): SubagentRuntime {
|
|
123
|
+
return {
|
|
124
|
+
defaultMaxTurns: undefined,
|
|
125
|
+
graceTurns: 5,
|
|
126
|
+
currentCtx: undefined,
|
|
127
|
+
agentActivity: new Map(),
|
|
128
|
+
widget: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
No parameters needed — the factory returns defaults.
|
|
134
|
+
Tests construct a fresh runtime per test for isolation.
|
|
135
|
+
|
|
136
|
+
### RunConfig — narrow interface for agent-manager
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
export interface RunConfig {
|
|
140
|
+
readonly defaultMaxTurns: number | undefined;
|
|
141
|
+
readonly graceTurns: number;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`AgentManager` receives `getRunConfig?: () => RunConfig` as a constructor parameter.
|
|
146
|
+
When constructing `RunOptions` for `runAgent`, it calls `getRunConfig()` and spreads the values.
|
|
147
|
+
During the lift-and-shift phase (before module-scope removal), `runAgent` falls back to the module-scope values when the RunOptions fields are absent.
|
|
148
|
+
|
|
149
|
+
### RunOptions changes
|
|
150
|
+
|
|
151
|
+
Two new optional fields:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
export interface RunOptions {
|
|
155
|
+
// ... existing fields ...
|
|
156
|
+
/** Default max turns from runtime config. Overridden by per-agent maxTurns. */
|
|
157
|
+
defaultMaxTurns?: number;
|
|
158
|
+
/** Grace turns after soft limit steer. */
|
|
159
|
+
graceTurns?: number;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`runAgent` changes its resolution chain from:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const maxTurns = normalizeMaxTurns(
|
|
167
|
+
options.maxTurns ?? agentConfig?.maxTurns ?? defaultMaxTurns,
|
|
168
|
+
);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
To:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const maxTurns = normalizeMaxTurns(
|
|
175
|
+
options.maxTurns ?? agentConfig?.maxTurns ?? options.defaultMaxTurns,
|
|
176
|
+
);
|
|
177
|
+
const effectiveGraceTurns = options.graceTurns ?? 5;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### normalizeMaxTurns stays in agent-runner.ts
|
|
181
|
+
|
|
182
|
+
`normalizeMaxTurns` is a pure function used by both the runtime setter logic (in `index.ts` wire-up) and `runAgent`'s maxTurns resolution.
|
|
183
|
+
It stays exported from `agent-runner.ts`.
|
|
184
|
+
|
|
185
|
+
### index.ts wire-up changes
|
|
186
|
+
|
|
187
|
+
After refactoring, the extension factory:
|
|
188
|
+
|
|
189
|
+
1. Calls `createSubagentRuntime()` to get the runtime.
|
|
190
|
+
2. Wires `applyAndEmitLoaded` appliers to write to `runtime.defaultMaxTurns` and `runtime.graceTurns` (with normalization).
|
|
191
|
+
3. Passes `getRunConfig: () => ({ defaultMaxTurns: runtime.defaultMaxTurns, graceTurns: runtime.graceTurns })` to `AgentManager`.
|
|
192
|
+
4. Uses `runtime.agentActivity` instead of a local `const agentActivity`.
|
|
193
|
+
5. Uses `runtime.currentCtx` instead of a local `let currentCtx`.
|
|
194
|
+
6. Sets `runtime.widget = new AgentWidget(...)` instead of a local `let widget`.
|
|
195
|
+
7. Arrow closures in notification deps, tool deps, and menu deps reference `runtime.widget!` / `runtime.agentActivity` / `runtime.currentCtx` by capturing `runtime` by reference.
|
|
196
|
+
|
|
197
|
+
No closure-scoped `let` variables remain.
|
|
198
|
+
|
|
199
|
+
### Edge cases
|
|
200
|
+
|
|
201
|
+
- **Widget null access**: Notification system callbacks reference `runtime.widget!.markFinished(id)`.
|
|
202
|
+
This is safe because notifications only fire after agents complete, which is always after widget construction.
|
|
203
|
+
The `!` assertion documents the invariant.
|
|
204
|
+
- **currentCtx undefined**: `getCtx: () => runtime.currentCtx` behaves identically to the current `() => currentCtx` — the arrow closure captures the runtime object by reference and reads the field at call time.
|
|
205
|
+
- **Backward compatibility during lift-and-shift**: During intermediate steps, `runAgent` falls back to module-scope state when `options.defaultMaxTurns` / `options.graceTurns` are absent, so the test suite stays green throughout.
|
|
206
|
+
|
|
207
|
+
## Module-Level Changes
|
|
208
|
+
|
|
209
|
+
### `src/runtime.ts` (new)
|
|
210
|
+
|
|
211
|
+
- `SubagentRuntime` interface — all mutable state fields.
|
|
212
|
+
- `RunConfig` interface — narrow config subset for `AgentManager`.
|
|
213
|
+
- `createSubagentRuntime()` factory — returns a fresh runtime with defaults.
|
|
214
|
+
|
|
215
|
+
### `src/agent-runner.ts` (modified)
|
|
216
|
+
|
|
217
|
+
- Add `defaultMaxTurns?: number` and `graceTurns?: number` to `RunOptions`.
|
|
218
|
+
- Update `runAgent`'s maxTurns resolution to prefer `options.defaultMaxTurns` over module scope (step 2), then remove module scope entirely (step 6).
|
|
219
|
+
- Update `graceTurns` usage in the turn-limit callback to prefer `options.graceTurns` over module scope (step 2), then remove fallback (step 6).
|
|
220
|
+
- Remove `let defaultMaxTurns`, `let graceTurns`, `getDefaultMaxTurns`, `setDefaultMaxTurns`, `getGraceTurns`, `setGraceTurns` exports (step 6).
|
|
221
|
+
- `normalizeMaxTurns` stays exported (pure function, no state dependency).
|
|
222
|
+
|
|
223
|
+
### `src/agent-manager.ts` (modified)
|
|
224
|
+
|
|
225
|
+
- Add optional `getRunConfig?: () => RunConfig` parameter to constructor.
|
|
226
|
+
- In `startAgent`, call `getRunConfig?.()` and pass `defaultMaxTurns` and `graceTurns` in the `RunOptions` object given to `runAgent`.
|
|
227
|
+
|
|
228
|
+
### `src/index.ts` (modified)
|
|
229
|
+
|
|
230
|
+
- Import `createSubagentRuntime` from `./runtime.js`.
|
|
231
|
+
- Create `const runtime = createSubagentRuntime()` at the top of the factory.
|
|
232
|
+
- Replace `const agentActivity = new Map<>()` with `runtime.agentActivity`.
|
|
233
|
+
- Replace `let widget: AgentWidget` with `runtime.widget`.
|
|
234
|
+
- Replace `let currentCtx` with `runtime.currentCtx`.
|
|
235
|
+
- Wire `applyAndEmitLoaded` appliers to `runtime.defaultMaxTurns` / `runtime.graceTurns` with normalization.
|
|
236
|
+
- Pass `getRunConfig` to `AgentManager` constructor.
|
|
237
|
+
- Update `snapshotSettings` to read from `runtime.defaultMaxTurns` / `runtime.graceTurns`.
|
|
238
|
+
- Remove imports of `getDefaultMaxTurns`, `setDefaultMaxTurns`, `getGraceTurns`, `setGraceTurns` from `agent-runner.js`.
|
|
239
|
+
- All arrow closures in notification, tool, menu, and service deps capture `runtime` by reference.
|
|
240
|
+
|
|
241
|
+
### `test/runtime.test.ts` (new)
|
|
242
|
+
|
|
243
|
+
- Factory returns expected defaults.
|
|
244
|
+
- Fields are independently mutable.
|
|
245
|
+
- Multiple instances are isolated.
|
|
246
|
+
|
|
247
|
+
### `test/agent-runner-settings.test.ts` (modified → removed or substantially rewritten)
|
|
248
|
+
|
|
249
|
+
- Current tests exercise `setDefaultMaxTurns` / `getDefaultMaxTurns` / `setGraceTurns` / `getGraceTurns` as module-scope getters/setters.
|
|
250
|
+
- After step 6 removes those exports, these tests must migrate.
|
|
251
|
+
- `normalizeMaxTurns` tests stay as-is (the function remains exported).
|
|
252
|
+
- Setter-behavior tests (clamping, unlimited marker) become tests of the normalization logic applied in `index.ts` wire-up or `runtime.test.ts`.
|
|
253
|
+
- The `runAgent` integration with `defaultMaxTurns` / `graceTurns` is tested via RunOptions in `agent-runner.test.ts`.
|
|
254
|
+
|
|
255
|
+
### `test/agent-manager.test.ts` (modified)
|
|
256
|
+
|
|
257
|
+
- Constructor calls gain `getRunConfig` parameter (or omit it — default is no-op).
|
|
258
|
+
- Existing tests pass `undefined` for `getRunConfig` (backward compatible).
|
|
259
|
+
- New tests verify that `runAgent` receives `defaultMaxTurns` / `graceTurns` from `getRunConfig`.
|
|
260
|
+
|
|
261
|
+
## Test Impact Analysis
|
|
262
|
+
|
|
263
|
+
### New unit tests enabled by the extraction
|
|
264
|
+
|
|
265
|
+
1. `test/runtime.test.ts` — `createSubagentRuntime` factory returns correct defaults, fields are independently mutable, multiple instances don't share state.
|
|
266
|
+
2. `test/agent-runner.test.ts` additions — `runAgent` uses `options.defaultMaxTurns` and `options.graceTurns` when provided, with correct fallback behavior.
|
|
267
|
+
3. `test/agent-manager.test.ts` additions — `AgentManager` calls `getRunConfig()` and passes values in `RunOptions`.
|
|
268
|
+
|
|
269
|
+
### Existing tests that become redundant
|
|
270
|
+
|
|
271
|
+
- `test/agent-runner-settings.test.ts` tests for `setDefaultMaxTurns` / `getDefaultMaxTurns` / `setGraceTurns` / `getGraceTurns` — these getter/setter pairs are removed.
|
|
272
|
+
The normalization behavior they test is preserved via `normalizeMaxTurns` (which stays) and runtime wire-up tests.
|
|
273
|
+
|
|
274
|
+
### Existing tests that stay as-is
|
|
275
|
+
|
|
276
|
+
- `test/settings.test.ts` — tests `SettingsAppliers` via mock callbacks; interface unchanged.
|
|
277
|
+
- `test/service-adapter.test.ts` — tests `AdapterDeps` via mock callbacks; `getCtx` interface unchanged.
|
|
278
|
+
- `test/agent-runner.test.ts` — existing final-output-capture and usage-callback tests are unaffected (they don't test maxTurns/graceTurns state).
|
|
279
|
+
- All other test files (agent-types, custom-agents, notification, renderer, tools, UI, etc.) — no dependency on the moved state.
|
|
280
|
+
|
|
281
|
+
## TDD Order
|
|
282
|
+
|
|
283
|
+
1. **Create `src/runtime.ts` with SubagentRuntime interface and factory.**
|
|
284
|
+
Write `test/runtime.test.ts` testing factory defaults and instance isolation.
|
|
285
|
+
Commit: `feat: add SubagentRuntime interface and factory`
|
|
286
|
+
|
|
287
|
+
2. **Add `defaultMaxTurns` and `graceTurns` to RunOptions; update `runAgent` to prefer them over module scope.**
|
|
288
|
+
In `agent-runner.ts`, add two optional fields to `RunOptions`.
|
|
289
|
+
Change maxTurns resolution to `options.maxTurns ?? agentConfig?.maxTurns ?? options.defaultMaxTurns ?? defaultMaxTurns` (backward compatible — module-scope fallback retained).
|
|
290
|
+
Change graceTurns usage to `options.graceTurns ?? graceTurns` (module-scope fallback retained).
|
|
291
|
+
Add tests in `agent-runner.test.ts` verifying that when `options.defaultMaxTurns` / `options.graceTurns` are provided, they are used.
|
|
292
|
+
Run `pnpm run check` to verify types.
|
|
293
|
+
Commit: `feat: thread defaultMaxTurns and graceTurns through RunOptions`
|
|
294
|
+
|
|
295
|
+
3. **Wire `AgentManager` to pass runtime config in RunOptions.**
|
|
296
|
+
Add `getRunConfig?: () => RunConfig` as the 5th constructor parameter (optional, backward compatible).
|
|
297
|
+
In `startAgent`, call `getRunConfig?.()` and spread into the RunOptions for `runAgent`.
|
|
298
|
+
Add agent-manager test verifying `runAgent` receives the config values.
|
|
299
|
+
Existing tests omit the param — green with no changes.
|
|
300
|
+
Commit: `refactor: agent-manager threads run config into RunOptions`
|
|
301
|
+
|
|
302
|
+
4. **Wire SubagentRuntime into index.ts — replace closure-scoped state.**
|
|
303
|
+
Import `createSubagentRuntime` and call it at the top of the factory.
|
|
304
|
+
Replace `const agentActivity`, `let widget`, and `let currentCtx` with runtime fields.
|
|
305
|
+
Wire settings appliers to `runtime.defaultMaxTurns` (via `normalizeMaxTurns`) and `runtime.graceTurns` (via `Math.max(1, n)`).
|
|
306
|
+
Pass `getRunConfig` callback to `AgentManager`.
|
|
307
|
+
Update `snapshotSettings` to read from runtime.
|
|
308
|
+
Remove imports of `getDefaultMaxTurns`, `setDefaultMaxTurns`, `getGraceTurns`, `setGraceTurns`.
|
|
309
|
+
Run full test suite.
|
|
310
|
+
Commit: `refactor: wire SubagentRuntime into extension factory`
|
|
311
|
+
|
|
312
|
+
5. **Remove module-scope state from `agent-runner.ts`.**
|
|
313
|
+
Delete `let defaultMaxTurns`, `let graceTurns`, and all four getter/setter functions.
|
|
314
|
+
Remove the module-scope fallback from `runAgent`'s resolution chain — `options.defaultMaxTurns` and `options.graceTurns` are now the sole source (with hardcoded defaults as a safety net: `undefined` and `5`).
|
|
315
|
+
Update `test/agent-runner-settings.test.ts`: remove tests for deleted getters/setters, keep `normalizeMaxTurns` tests.
|
|
316
|
+
Run `pnpm run check` and full test suite.
|
|
317
|
+
Commit: `refactor: remove module-scope mutable state from agent-runner`
|
|
318
|
+
|
|
319
|
+
6. **Final cleanup and acceptance verification.**
|
|
320
|
+
Verify acceptance criteria: `agent-runner.ts` contains no module-scope mutable state.
|
|
321
|
+
`index.ts` contains no closure-scoped `let` variables that outlive their initialization block.
|
|
322
|
+
`SubagentRuntime` interface exists with all mutable session state.
|
|
323
|
+
Tests can construct a runtime and pass it to factories without importing `index.ts`.
|
|
324
|
+
Full test suite passes.
|
|
325
|
+
Remove any dead imports or vestigial code.
|
|
326
|
+
Commit: `refactor: finalize SubagentRuntime migration (#69)`
|
|
327
|
+
|
|
328
|
+
## Risks and Mitigations
|
|
329
|
+
|
|
330
|
+
| Risk | Mitigation |
|
|
331
|
+
| --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
332
|
+
| Backward-compatibility break during incremental migration — removing module-scope state before all consumers switch to RunOptions | Lift-and-shift: steps 2–3 introduce the new path alongside the old (module-scope fallback); step 5 removes the old path only after all consumers use the new path. |
|
|
333
|
+
| AgentManager constructor gains a 5th positional parameter — fragile and hard to read | Parameter is optional with no default behavior change. Plan notes this as a follow-up cleanup (convert to options object). |
|
|
334
|
+
| `runtime.widget!` non-null assertions in notification closures could NPE if initialization order changes | Assertion documents the invariant; widget is always constructed before any agent can complete. Add a defensive `if (!runtime.widget) return;` guard in the notification callbacks as a safety net. |
|
|
335
|
+
| `normalizeMaxTurns` stays in `agent-runner.ts` after getter/setter removal — unclear ownership | `normalizeMaxTurns` is a pure function used by the turn-limit logic in `runAgent`. It belongs in the module that uses it. If a future refactor moves turn-limit logic, the function moves with it. |
|
|
336
|
+
| Test file `agent-runner-settings.test.ts` needs substantial rewrite — risk of losing coverage | Keep `normalizeMaxTurns` tests intact (they test the same pure function). The setter/getter behavior tests are replaced by runtime factory tests and RunOptions integration tests that cover the same normalization logic. |
|
|
337
|
+
|
|
338
|
+
## Open Questions
|
|
339
|
+
|
|
340
|
+
- Should `AgentManager`'s constructor be converted from positional parameters to a named-options object?
|
|
341
|
+
This is natural cleanup but widens the blast radius.
|
|
342
|
+
Defer to a follow-up issue if the 5th positional parameter feels too fragile during implementation.
|
|
343
|
+
- Should `SubagentRuntime` include utility methods (e.g., `reset()`, `shutdown()`) for session lifecycle?
|
|
344
|
+
The issue's acceptance criteria focus on state ownership, not lifecycle methods.
|
|
345
|
+
Defer until a pattern of scattered resets emerges in practice.
|
|
@@ -23,15 +23,26 @@ A new issue (#61) was filed to port the output-file format to Pi's official JSON
|
|
|
23
23
|
|
|
24
24
|
#### What caused friction (agent side)
|
|
25
25
|
|
|
26
|
-
- `missing-context` — Included `output-file.ts` removal in the initial plan without questioning its debugging value, despite AGENTS.md's rule "Ask before removing functionality or changing defaults."
|
|
26
|
+
- `missing-context` — Included `output-file.ts` removal in the initial plan without questioning its debugging value, despite AGENTS.md's rule "Ask before removing functionality or changing defaults."
|
|
27
|
+
The issue body explicitly listed it for removal so I followed the spec literally.
|
|
28
|
+
Impact: required plan revision (amend commit), scope-narrowing comment on issue, and filing #61 — roughly 10 minutes of rework, but produced a better design.
|
|
27
29
|
|
|
28
|
-
- `missing-context` — When asked whether output-file adheres to Pi's session format, searched the web (`web_search` for "Claude Code session JSONL format") instead of checking the local `~/development/pi/pi` monorepo.
|
|
30
|
+
- `missing-context` — When asked whether output-file adheres to Pi's session format, searched the web (`web_search` for "Claude Code session JSONL format") instead of checking the local `~/development/pi/pi` monorepo.
|
|
31
|
+
The user had to explicitly say "~/development/pi/pi has the code for Pi's JSONL format."
|
|
32
|
+
Impact: one extra round-trip and less authoritative initial answer (Claude Code's format vs Pi's `SessionManager`).
|
|
33
|
+
Self-identified after user redirect.
|
|
29
34
|
|
|
30
|
-
- `instruction-violation` (self-identified) — Shell-escaped the `gh issue comment` body incorrectly; backtick-wrapped `src/output-file.ts` was interpreted by bash.
|
|
35
|
+
- `instruction-violation` (self-identified) — Shell-escaped the `gh issue comment` body incorrectly; backtick-wrapped `src/output-file.ts` was interpreted by bash.
|
|
36
|
+
Caught immediately via `gh issue view` and fixed with `--edit-last`.
|
|
37
|
+
Impact: trivial — one extra command.
|
|
31
38
|
|
|
32
39
|
#### What caused friction (user side)
|
|
33
40
|
|
|
34
|
-
- The issue body listed output-file for removal without noting its debugging value.
|
|
41
|
+
- The issue body listed output-file for removal without noting its debugging value.
|
|
42
|
+
The user's "How confident are we in getting rid of the logging system?"
|
|
43
|
+
intervention was the correction.
|
|
44
|
+
If the issue had marked output-file removal as "tentative pending debugging value assessment," the plan would have surfaced it as a design decision from the start.
|
|
45
|
+
Minor — the discussion was quick and productive.
|
|
35
46
|
|
|
36
47
|
### Changes made
|
|
37
48
|
|
|
@@ -22,12 +22,16 @@ The change was planned, implemented, shipped, and released as `pi-subagents-v1.0
|
|
|
22
22
|
|
|
23
23
|
#### What caused friction (agent side)
|
|
24
24
|
|
|
25
|
-
- No friction observed.
|
|
25
|
+
- No friction observed.
|
|
26
|
+
The task was unambiguous and the tooling well-suited.
|
|
26
27
|
|
|
27
28
|
#### What caused friction (user side)
|
|
28
29
|
|
|
29
|
-
- No friction observed.
|
|
30
|
+
- No friction observed.
|
|
31
|
+
The session required no user input beyond invoking the three slash commands.
|
|
30
32
|
|
|
31
33
|
### Follow-ups identified
|
|
32
34
|
|
|
33
|
-
- The `package-pi-subagents` skill (`.pi/skills/package-pi-subagents/SKILL.md`) still frames the fork as "a friendly fork… carrying a small number of patches" with priorities like "stays as close to upstream as possible."
|
|
35
|
+
- The `package-pi-subagents` skill (`.pi/skills/package-pi-subagents/SKILL.md`) still frames the fork as "a friendly fork… carrying a small number of patches" with priorities like "stays as close to upstream as possible."
|
|
36
|
+
This framing is now stale given the hard-fork commitment.
|
|
37
|
+
A separate issue should update the skill to reflect the architecture document's posture.
|
|
@@ -17,14 +17,24 @@ Also fixed a pre-existing `rumdl` glob-quoting bug in `package.json` discovered
|
|
|
17
17
|
|
|
18
18
|
#### What went well
|
|
19
19
|
|
|
20
|
-
- Pre-existing lint bug surfaced and fixed: the `rumdl check '*.md' 'docs/**/*.md'` command in `package.json` used single-quoted globs that prevented shell expansion.
|
|
20
|
+
- Pre-existing lint bug surfaced and fixed: the `rumdl check '*.md' 'docs/**/*.md'` command in `package.json` used single-quoted globs that prevented shell expansion.
|
|
21
|
+
Verified as pre-existing (reproduced on prior commit via `git stash`), cleanly isolated into its own `fix:` commit.
|
|
22
|
+
This was a genuine find — the lint had been silently broken.
|
|
21
23
|
|
|
22
24
|
#### What caused friction (agent side)
|
|
23
25
|
|
|
24
|
-
- `missing-context` — In step 6 (refactoring `index.ts`), replaced the `resolveModel` import with `resolveInvocationModel` without first checking whether `resolveModel` was still used elsewhere in the file.
|
|
26
|
+
- `missing-context` — In step 6 (refactoring `index.ts`), replaced the `resolveModel` import with `resolveInvocationModel` without first checking whether `resolveModel` was still used elsewhere in the file.
|
|
27
|
+
Two other call sites (`createSubagentsService` at line 386 and `getModelLabel` at line 1043) still needed it.
|
|
28
|
+
The plan explicitly listed `getModelLabel` as a non-goal that continues using `resolveModel`, so the information was available.
|
|
29
|
+
Caught immediately via `grep` after the edit and fixed in the same commit.
|
|
30
|
+
Impact: one extra edit + grep cycle, no rework.
|
|
25
31
|
|
|
26
|
-
- `missing-context` — The plan's type definitions specified `model: unknown` for `ModelResolutionResult`, but downstream code in `index.ts` accesses `.id` and `.name` on the model and passes it where `Model<any>` is expected.
|
|
32
|
+
- `missing-context` — The plan's type definitions specified `model: unknown` for `ModelResolutionResult`, but downstream code in `index.ts` accesses `.id` and `.name` on the model and passes it where `Model<any>` is expected.
|
|
33
|
+
The plan's risk section flagged this ("reducing but not eliminating the `any`"), yet the implementation went with `unknown` first, requiring a correction after `pnpm run check` failed with 4 type errors.
|
|
34
|
+
Changed to `model: any` to match the existing `resolveModel` return type.
|
|
35
|
+
Impact: one extra edit cycle within the same commit, no rework.
|
|
27
36
|
|
|
28
37
|
#### What caused friction (user side)
|
|
29
38
|
|
|
30
|
-
- None observed.
|
|
39
|
+
- None observed.
|
|
40
|
+
The issue was well-scoped with clear acceptance criteria, making planning and execution straightforward.
|
|
@@ -18,20 +18,35 @@ Filed follow-up #66 (replace `as any` casts with proper SDK types) and #67 (flak
|
|
|
18
18
|
|
|
19
19
|
#### What went well
|
|
20
20
|
|
|
21
|
-
- Leaf-first extraction order worked cleanly — helpers, then renderer, then notification, then tools, then menu.
|
|
21
|
+
- Leaf-first extraction order worked cleanly — helpers, then renderer, then notification, then tools, then menu.
|
|
22
|
+
Each step left the repo green with no cascading breakage.
|
|
22
23
|
- The `createNotificationSystem` factory pattern with arrow-closure capture of `widget` (assigned after `AgentManager` construction) preserved the existing deferred-reference semantics without restructuring initialization order.
|
|
23
24
|
|
|
24
25
|
#### What caused friction (agent side)
|
|
25
26
|
|
|
26
|
-
- `wrong-abstraction` — Applied the code-style skill's "keep Pi SDK imports out of business-logic modules" rule to tool/menu modules, which are SDK consumers, not business logic.
|
|
27
|
+
- `wrong-abstraction` — Applied the code-style skill's "keep Pi SDK imports out of business-logic modules" rule to tool/menu modules, which are SDK consumers, not business logic.
|
|
28
|
+
Used `unknown` for `ExtensionContext`, `AgentSession`, `ModelRegistry` in factory dep interfaces, requiring 9 `as any` casts in `index.ts`.
|
|
29
|
+
User caught this post-ship.
|
|
30
|
+
Impact: filed #66 as a follow-up cleanup; the casts are cosmetic (no runtime effect) but degrade type safety.
|
|
31
|
+
Fixed the code-style skill to clarify the boundary. (user-caught)
|
|
27
32
|
|
|
28
|
-
- `missing-context` — Four test files (`notification.test.ts`, `get-result-tool.test.ts`, `steer-tool.test.ts`, `agent-tool.test.ts`) omitted `compactionCount: 0` from `AgentRecord` factories.
|
|
33
|
+
- `missing-context` — Four test files (`notification.test.ts`, `get-result-tool.test.ts`, `steer-tool.test.ts`, `agent-tool.test.ts`) omitted `compactionCount: 0` from `AgentRecord` factories.
|
|
34
|
+
Caught at the final `pnpm run check` step, not during test writing.
|
|
35
|
+
The testing skill already says "grep for ALL test files that construct a compatible mock."
|
|
36
|
+
Impact: one extra fix cycle delegated to a subagent, no rework beyond that step. (self-identified)
|
|
29
37
|
|
|
30
|
-
- `other` — `Edit` tool failed 3 times matching the UTF-8 middle dot (`·`, U+00B7) in the steer tool's `stateParts.join(" · ")` line.
|
|
38
|
+
- `other` — `Edit` tool failed 3 times matching the UTF-8 middle dot (`·`, U+00B7) in the steer tool's `stateParts.join(" · ")` line.
|
|
39
|
+
The third attempt produced a partial match that left the file in a broken state (dangling orphan code after the replacement anchor).
|
|
40
|
+
Required `git restore` and a fallback to `python3` line-range replacement.
|
|
41
|
+
The same `python3` approach for the menu extraction lost the closing `}` of the default export function.
|
|
42
|
+
Impact: ~5 minutes of rework across the two extraction steps, plus one `git restore`.
|
|
31
43
|
|
|
32
44
|
#### What caused friction (user side)
|
|
33
45
|
|
|
34
|
-
- The `as any` casts could have been caught earlier if the user had flagged the `unknown` types during the planning phase.
|
|
46
|
+
- The `as any` casts could have been caught earlier if the user had flagged the `unknown` types during the planning phase.
|
|
47
|
+
However, the plan didn't prescribe exact interface types — that was an implementation decision.
|
|
48
|
+
The user's post-ship review ("Why did we have to cast `as any`?
|
|
49
|
+
Take a look at `packages/pi-permission-system/` as a model") was an efficient redirect that immediately scoped the investigation.
|
|
35
50
|
|
|
36
51
|
### Changes made
|
|
37
52
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 57
|
|
3
|
+
issue_title: "feat: structured debug logging for silenced catch blocks"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #57 — structured debug logging for silenced catch blocks
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-19T10:30:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Added `src/debug.ts` with `debugLog` and `isDebug()`, then threaded `debugLog` into ~20 silent `catch` blocks across 9 files.
|
|
13
|
+
All 7 TDD cycles went green on the first pass with no rework.
|
|
14
|
+
Shipped as `pi-subagents-v5.1.0`, then followed up with a `refactor:` commit converting `DEBUG` (module-level constant) to `isDebug()` (function getter) during the retro.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
#### What went well
|
|
19
|
+
|
|
20
|
+
- The plan's "Non-Goals" section correctly excluded `usage.ts` and `settings.ts` before implementation started, and a post-TDD `grep -rn 'catch\s*{'` confirmed only those two in-scope-excluded files remained.
|
|
21
|
+
Closing the loop with a verification query is worth repeating.
|
|
22
|
+
- The scope of the change was so well-defined (the issue listed exact file names) that no `ask_user` call was needed during planning.
|
|
23
|
+
|
|
24
|
+
#### What caused friction (agent side)
|
|
25
|
+
|
|
26
|
+
- `missing-context` — When loading the `ask-user` skill I guessed `.pi/skills/ask-user/SKILL.md` before reading the actual `<location>` tag in `AGENTS.md`, triggering an ENOENT error and a follow-up `find` call.
|
|
27
|
+
Impact: 2 extra tool calls, no rework. (self-identified)
|
|
28
|
+
|
|
29
|
+
- `other` — The plan's TDD Order step 1 stated *"the test skill documents this pattern"* for `vi.resetModules()` + dynamic import when testing module-level env constants — but the testing skill does not have that entry.
|
|
30
|
+
The aspiration was recorded rather than verified.
|
|
31
|
+
During the retro, the user's question ("should that be a function getter instead?") led to a better outcome: replace the module-level constant with `isDebug()` so `vi.stubEnv()` alone works, consistent with how every other `process.env` read in this codebase is structured.
|
|
32
|
+
Impact: one retro-phase `refactor:` commit; the approach shipped in `v5.1.0` was technically correct but unnecessarily complex to test.
|
|
33
|
+
|
|
34
|
+
#### What caused friction (user side)
|
|
35
|
+
|
|
36
|
+
- The initial issue proposal chose the module-level-constant pattern (common in Node.js tooling like the `debug` package).
|
|
37
|
+
A note in the issue or plan about preferring function-based env reads for testability would have caught this at design time rather than post-ship.
|
|
38
|
+
That said, the retro question was efficient — a single targeted redirect resolved it cleanly.
|
|
39
|
+
|
|
40
|
+
### Changes made
|
|
41
|
+
|
|
42
|
+
1. `packages/pi-subagents/src/debug.ts` — replaced `export const DEBUG` with `export function isDebug()`.
|
|
43
|
+
2. `packages/pi-subagents/test/debug.test.ts` — simplified to static import + `vi.stubEnv()` only; removed all `vi.resetModules()` + dynamic `import()` calls.
|
|
44
|
+
3. `.pi/skills/testing/SKILL.md` — added bullet: prefer reading `process.env` inside functions; `vi.stubEnv()` alone is insufficient for module-level constants.
|
|
45
|
+
|
|
46
|
+
## Follow-up Retrospective (2026-05-19T11:15:00Z)
|
|
47
|
+
|
|
48
|
+
### Session summary
|
|
49
|
+
|
|
50
|
+
The user asked how many `process.*` reads exist in `pi-subagents`.
|
|
51
|
+
Audit found 9 sites: 4 acceptable (wiring layer, detection functions, injectable defaults), 2 genuine injection gaps, and 1 mild case.
|
|
52
|
+
Filed #76 (`AgentManager.dispose()` reads `process.cwd()` without a stored `cwd`) and #77 (`createAgentsMenuHandler` hardcodes `process.cwd()` when `AgentMenuDeps` already injects the personal-side equivalent).
|
|
53
|
+
|
|
54
|
+
### Observations
|
|
55
|
+
|
|
56
|
+
#### What went well
|
|
57
|
+
|
|
58
|
+
- The `isDebug()` refactor naturally led the user to ask a broader design question about `process.*` access patterns, producing two well-scoped follow-up issues without manual triage.
|
|
59
|
+
- The audit categorization (genuinely problematic vs. acceptable) was clean — presenting a table with verdicts per site let the user decide scope without re-reading source.
|
|
60
|
+
|
|
61
|
+
#### What caused friction (agent side)
|
|
62
|
+
|
|
63
|
+
- `premature-convergence` — The original plan accepted the module-level `DEBUG` constant without checking how the rest of the codebase reads `process.env`.
|
|
64
|
+
The code-style skill said "keep IO at the edges" but didn't name `process.*` specifically, so the rule wasn't applied.
|
|
65
|
+
Impact: one post-ship `refactor:` commit to replace `DEBUG` with `isDebug()`; the pattern was technically correct but inconsistent with codebase conventions. (user-caught)
|
|
66
|
+
|
|
67
|
+
#### What caused friction (user side)
|
|
68
|
+
|
|
69
|
+
- Nothing notable.
|
|
70
|
+
The user's two redirecting questions ("should that be a function?"
|
|
71
|
+
and "how many places access `process.*`?") were well-timed interventions that broadened scope productively.
|
|
72
|
+
|
|
73
|
+
### Changes made
|
|
74
|
+
|
|
75
|
+
1. `.pi/skills/code-style/SKILL.md` — added bullet: do not read `process.env`, `process.cwd()`, or `process.platform` inside library/utility functions; accept the value as a parameter.
|
|
76
|
+
2. Filed #76 — inject `cwd` into `AgentManager` constructor.
|
|
77
|
+
3. Filed #77 — add `projectAgentsDir` to `AgentMenuDeps`.
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
import type { Model } from "@earendil-works/pi-ai";
|
|
11
11
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { resumeAgent, runAgent, type ToolActivity } from "./agent-runner.js";
|
|
13
|
+
import { debugLog } from "./debug.js";
|
|
14
|
+
import type { RunConfig } from "./runtime.js";
|
|
13
15
|
import type { AgentInvocation, AgentRecord, IsolationMode, SubagentType, ThinkingLevel } from "./types.js";
|
|
14
16
|
import { addUsage } from "./usage.js";
|
|
15
17
|
import { cleanupWorktree, createWorktree, pruneWorktrees, } from "./worktree.js";
|
|
@@ -71,6 +73,7 @@ export class AgentManager {
|
|
|
71
73
|
private onStart?: OnAgentStart;
|
|
72
74
|
private onCompact?: OnAgentCompact;
|
|
73
75
|
private maxConcurrent: number;
|
|
76
|
+
private getRunConfig?: () => RunConfig;
|
|
74
77
|
|
|
75
78
|
/** Queue of background agents waiting to start. */
|
|
76
79
|
private queue: { id: string; args: SpawnArgs }[] = [];
|
|
@@ -82,10 +85,12 @@ export class AgentManager {
|
|
|
82
85
|
maxConcurrent = DEFAULT_MAX_CONCURRENT,
|
|
83
86
|
onStart?: OnAgentStart,
|
|
84
87
|
onCompact?: OnAgentCompact,
|
|
88
|
+
getRunConfig?: () => RunConfig,
|
|
85
89
|
) {
|
|
86
90
|
this.onComplete = onComplete;
|
|
87
91
|
this.onStart = onStart;
|
|
88
92
|
this.onCompact = onCompact;
|
|
93
|
+
this.getRunConfig = getRunConfig;
|
|
89
94
|
this.maxConcurrent = maxConcurrent;
|
|
90
95
|
// Cleanup completed agents after 10 minutes (but keep sessions for resume)
|
|
91
96
|
this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);
|
|
@@ -181,10 +186,13 @@ export class AgentManager {
|
|
|
181
186
|
}
|
|
182
187
|
const detach = () => { detachParentSignal?.(); detachParentSignal = undefined; };
|
|
183
188
|
|
|
189
|
+
const runConfig = this.getRunConfig?.();
|
|
184
190
|
const promise = runAgent(ctx, type, prompt, {
|
|
185
191
|
pi,
|
|
186
192
|
model: options.model,
|
|
187
193
|
maxTurns: options.maxTurns,
|
|
194
|
+
defaultMaxTurns: runConfig?.defaultMaxTurns,
|
|
195
|
+
graceTurns: runConfig?.graceTurns,
|
|
188
196
|
isolated: options.isolated,
|
|
189
197
|
inheritContext: options.inheritContext,
|
|
190
198
|
thinkingLevel: options.thinkingLevel,
|
|
@@ -230,7 +238,7 @@ export class AgentManager {
|
|
|
230
238
|
|
|
231
239
|
// Final flush of streaming output file
|
|
232
240
|
if (record.outputCleanup) {
|
|
233
|
-
try { record.outputCleanup(); } catch {
|
|
241
|
+
try { record.outputCleanup(); } catch (err) { debugLog("outputCleanup", err); }
|
|
234
242
|
record.outputCleanup = undefined;
|
|
235
243
|
}
|
|
236
244
|
|
|
@@ -246,7 +254,7 @@ export class AgentManager {
|
|
|
246
254
|
|
|
247
255
|
if (options.isBackground) {
|
|
248
256
|
this.runningBackground--;
|
|
249
|
-
try { this.onComplete?.(record); } catch {
|
|
257
|
+
try { this.onComplete?.(record); } catch (err) { debugLog("onComplete callback", err); }
|
|
250
258
|
this.drainQueue();
|
|
251
259
|
}
|
|
252
260
|
return responseText;
|
|
@@ -263,7 +271,7 @@ export class AgentManager {
|
|
|
263
271
|
|
|
264
272
|
// Final flush of streaming output file on error
|
|
265
273
|
if (record.outputCleanup) {
|
|
266
|
-
try { record.outputCleanup(); } catch {
|
|
274
|
+
try { record.outputCleanup(); } catch (err) { debugLog("outputCleanup on error", err); }
|
|
267
275
|
record.outputCleanup = undefined;
|
|
268
276
|
}
|
|
269
277
|
|
|
@@ -272,7 +280,7 @@ export class AgentManager {
|
|
|
272
280
|
try {
|
|
273
281
|
const wtResult = cleanupWorktree(ctx.cwd, record.worktree, options.description);
|
|
274
282
|
record.worktreeResult = wtResult;
|
|
275
|
-
} catch {
|
|
283
|
+
} catch (err) { debugLog("cleanupWorktree on agent error", err); }
|
|
276
284
|
}
|
|
277
285
|
|
|
278
286
|
if (options.isBackground) {
|
|
@@ -477,6 +485,6 @@ export class AgentManager {
|
|
|
477
485
|
}
|
|
478
486
|
this.agents.clear();
|
|
479
487
|
// Prune any orphaned git worktrees (crash recovery)
|
|
480
|
-
try { pruneWorktrees(process.cwd()); } catch {
|
|
488
|
+
try { pruneWorktrees(process.cwd()); } catch (err) { debugLog("pruneWorktrees on dispose", err); }
|
|
481
489
|
}
|
|
482
490
|
}
|