@gotgenes/pi-subagents 11.1.0 → 11.3.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 +19 -0
- package/docs/architecture/architecture.md +229 -161
- package/docs/architecture/history/phase-15-domain-model-evolution.md +73 -0
- package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +180 -0
- package/docs/plans/0256-extract-worktree-isolation.md +256 -0
- package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +109 -0
- package/docs/retro/0256-extract-worktree-isolation.md +45 -0
- package/package.json +1 -1
- package/src/lifecycle/agent-manager.ts +10 -25
- package/src/lifecycle/agent.ts +52 -45
- package/src/lifecycle/worktree-isolation.ts +59 -0
- package/src/service/service-adapter.ts +1 -1
- package/src/lifecycle/worktree-state.ts +0 -45
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 256
|
|
3
|
+
issue_title: "Extract WorktreeIsolation collaborator"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #256 — Extract WorktreeIsolation collaborator
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-28T23:44:23Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a numbered implementation plan for extracting a `WorktreeIsolation` collaborator (Phase 16, Step 1) that owns the worktree lifecycle (`setup`, `path`, `cleanup`) so `Agent` tells one collaborator instead of orchestrating `_worktrees` + `_isolation` + `worktreeState` itself.
|
|
13
|
+
The plan covers the new module, `Agent`/`AgentManager`/`service-adapter` wiring, the `WorktreeState` deletion, doc updates, and a 4-cycle TDD order.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- Decision: fold `WorktreeState` into `WorktreeIsolation` (delete `worktree-state.ts`) rather than wrap it.
|
|
18
|
+
The architecture target table already lists `WorktreeIsolation` as absorbing `worktrees` + `isolation` + `worktreeState`, and the user confirmed a fold preference when the doc had already decided it.
|
|
19
|
+
- `WorktreeManager.cleanup(wt, ...)` mutates `wt.branch` in place; `WorktreeIsolation` must store a mutable `WorktreeInfo` (`_info`) to preserve that behavior — flagged as the top risk.
|
|
20
|
+
- `AgentInit` net field change is −1 (removes `worktrees` + `isolation`, adds `worktree`), not −2 as the issue text loosely states; instance fields drop by 2 and `setupWorktree()` is removed.
|
|
21
|
+
- The `missing worktrees dependency` defensive branch becomes structurally impossible (collaborator is only built with a manager) and is dropped.
|
|
22
|
+
- Verified no consumer imports the `WorktreeCleanupResult`/`WorktreeInfo` re-exports from `worktree-state.ts` — they all import from `worktree.ts`, so deletion is safe.
|
|
23
|
+
- Step 2 (the integration) is a single commit because the type checker forbids removing `AgentInit` fields while call sites still pass them; bulk of `agent.test.ts` is untouched, only worktree helpers/describe blocks change.
|
|
24
|
+
- Doc updates needed: architecture class diagram + layout listing, and the package `SKILL.md` Lifecycle domain row (module count stays 9).
|
|
25
|
+
- This step is independent of Step 2 (#257, `ChildSessionFactory`) per the architecture's Track A.
|
|
26
|
+
|
|
27
|
+
## Stage: Implementation — TDD (2026-05-29T00:01:54Z)
|
|
28
|
+
|
|
29
|
+
### Session summary
|
|
30
|
+
|
|
31
|
+
Implemented all 4 planned TDD cycles: added `WorktreeIsolation` + unit tests, wired it into `Agent`/`AgentManager`/`service-adapter` (removing `_worktrees`/`_isolation`/`worktreeState`/`setupWorktree()`), deleted the folded `WorktreeState` class and its test, and updated the architecture doc + package skill.
|
|
32
|
+
Full suite green at 1047 tests (baseline 1053; +7 new `worktree-isolation` tests, −4 removed `setupWorktree` tests, −9 removed `worktree-state` tests); `check`, `lint`, and `fallow dead-code` all clean.
|
|
33
|
+
|
|
34
|
+
### Observations
|
|
35
|
+
|
|
36
|
+
- One pre-existing baseline failure: `rumdl` flagged 5 orphaned issue link definitions (`[#227]`–`[#232]`, minus the still-used `[#231]`) in `architecture.md`, introduced by an earlier Phase 15 archive commit.
|
|
37
|
+
Fixed as a separate `docs:` cleanup commit before starting TDD to establish a green baseline.
|
|
38
|
+
- Deviation from a literal 1:1 test mapping: `WorktreeIsolation` deliberately exposes `path` + `cleanupResult` but no `branch` getter (branch is an internal `_info` detail surfaced via `cleanupResult`).
|
|
39
|
+
The two `agent-manager.test.ts` tests that asserted `worktreeState.branch` now assert `record.worktree?.path` and `record.worktree?.cleanupResult`.
|
|
40
|
+
Noted in the Step 2 commit body.
|
|
41
|
+
- `Agent.worktree` is `readonly` (set at construction), unlike the old mutable public `worktreeState` field.
|
|
42
|
+
Tests that previously mutated `record.worktreeState = new WorktreeState(...)` after construction were reworked to pass a pre-`setup()` `WorktreeIsolation` via the constructor (`createSetUpWorktree` helper in `agent.test.ts`; `setUpWorktree` helper in `service-adapter.test.ts`).
|
|
43
|
+
- `createTestAgent` spreads `init` into the `Agent` constructor, so injecting `worktree` needed no helper change.
|
|
44
|
+
- The Step 2 integration landed cleanly in a single commit as the plan predicted; the type checker pinpointed every stale call site.
|
|
45
|
+
- Pre-completion reviewer: PASS (all deterministic checks, acceptance criteria, conventional commits, docs, code design, test artifacts, Mermaid, and dead-code gates green).
|
package/package.json
CHANGED
|
@@ -14,8 +14,8 @@ import type { AgentRunner } from "#src/lifecycle/agent-runner";
|
|
|
14
14
|
import type { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
|
|
15
15
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
16
16
|
import type { WorktreeManager } from "#src/lifecycle/worktree";
|
|
17
|
+
import { WorktreeIsolation } from "#src/lifecycle/worktree-isolation";
|
|
17
18
|
|
|
18
|
-
import { subscribeAgentObserver } from "#src/observation/record-observer";
|
|
19
19
|
import type { RunConfig } from "#src/runtime";
|
|
20
20
|
import type { AgentInvocation, CompactionInfo, IsolationMode, ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
|
|
21
21
|
|
|
@@ -130,12 +130,14 @@ export class AgentManager {
|
|
|
130
130
|
maxTurns: options.maxTurns,
|
|
131
131
|
isolated: options.isolated,
|
|
132
132
|
thinkingLevel: options.thinkingLevel,
|
|
133
|
-
isolation: options.isolation,
|
|
134
133
|
parentSession: options.parentSession,
|
|
135
134
|
signal: options.signal,
|
|
136
135
|
// Shared deps
|
|
137
136
|
runner: this.runner,
|
|
138
|
-
|
|
137
|
+
worktree:
|
|
138
|
+
options.isolation === "worktree"
|
|
139
|
+
? new WorktreeIsolation(this.worktrees, id)
|
|
140
|
+
: undefined,
|
|
139
141
|
observer: this.buildObserver(options),
|
|
140
142
|
getRunConfig: this.getRunConfig,
|
|
141
143
|
});
|
|
@@ -173,34 +175,17 @@ export class AgentManager {
|
|
|
173
175
|
|
|
174
176
|
/**
|
|
175
177
|
* Resume an existing agent session with a new prompt.
|
|
178
|
+
* Delegates to Agent.resume(), which owns the observer subscription lifecycle.
|
|
176
179
|
*/
|
|
177
180
|
async resume(
|
|
178
181
|
id: string,
|
|
179
182
|
prompt: string,
|
|
180
183
|
signal?: AbortSignal,
|
|
181
184
|
): Promise<Agent | undefined> {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
record.resetForResume(Date.now());
|
|
187
|
-
|
|
188
|
-
const unsubResume = subscribeAgentObserver(session, record, {
|
|
189
|
-
onCompact: (r, info) => this.observer?.onAgentCompacted(r, info),
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const responseText = await this.runner.resume(session, prompt, {
|
|
194
|
-
signal,
|
|
195
|
-
});
|
|
196
|
-
record.markCompleted(responseText);
|
|
197
|
-
} catch (err) {
|
|
198
|
-
record.markError(err);
|
|
199
|
-
} finally {
|
|
200
|
-
unsubResume();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return record;
|
|
185
|
+
const agent = this.agents.get(id);
|
|
186
|
+
if (!agent?.session) return undefined;
|
|
187
|
+
await agent.resume(prompt, signal);
|
|
188
|
+
return agent;
|
|
204
189
|
}
|
|
205
190
|
|
|
206
191
|
getRecord(id: string): Agent | undefined {
|
package/src/lifecycle/agent.ts
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
* Behavior (abort, steer buffering, worktree setup) lives on the agent
|
|
12
12
|
* rather than on AgentManager — each agent manages its own lifecycle concerns.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Worktree isolation is delegated to an optional WorktreeIsolation collaborator
|
|
15
|
+
* (set at construction when isolation is requested); its presence IS the mode.
|
|
16
|
+
*
|
|
17
|
+
* Phase-specific collaborators (execution, notification) are attached
|
|
15
18
|
* after construction as lifecycle information becomes available.
|
|
16
19
|
*/
|
|
17
20
|
|
|
@@ -23,12 +26,11 @@ import type { ExecutionState } from "#src/lifecycle/execution-state";
|
|
|
23
26
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
24
27
|
import type { LifetimeUsage } from "#src/lifecycle/usage";
|
|
25
28
|
import { addUsage } from "#src/lifecycle/usage";
|
|
26
|
-
import type {
|
|
27
|
-
import { WorktreeState } from "#src/lifecycle/worktree-state";
|
|
29
|
+
import type { WorktreeIsolation } from "#src/lifecycle/worktree-isolation";
|
|
28
30
|
import { NotificationState } from "#src/observation/notification-state";
|
|
29
31
|
import { subscribeAgentObserver } from "#src/observation/record-observer";
|
|
30
32
|
import type { RunConfig } from "#src/runtime";
|
|
31
|
-
import type { AgentInvocation, CompactionInfo,
|
|
33
|
+
import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
|
|
32
34
|
|
|
33
35
|
/** Per-agent lifecycle observer — created by AgentManager for each spawn. */
|
|
34
36
|
export interface AgentLifecycleObserver {
|
|
@@ -67,7 +69,7 @@ export interface AgentInit {
|
|
|
67
69
|
|
|
68
70
|
// Shared deps (required for run(), optional for tests)
|
|
69
71
|
runner?: AgentRunner;
|
|
70
|
-
|
|
72
|
+
worktree?: WorktreeIsolation;
|
|
71
73
|
observer?: AgentLifecycleObserver;
|
|
72
74
|
getRunConfig?: () => RunConfig;
|
|
73
75
|
|
|
@@ -78,7 +80,6 @@ export interface AgentInit {
|
|
|
78
80
|
maxTurns?: number;
|
|
79
81
|
isolated?: boolean;
|
|
80
82
|
thinkingLevel?: ThinkingLevel;
|
|
81
|
-
isolation?: IsolationMode;
|
|
82
83
|
parentSession?: ParentSessionInfo;
|
|
83
84
|
isBackground?: boolean;
|
|
84
85
|
signal?: AbortSignal;
|
|
@@ -124,7 +125,8 @@ export class Agent {
|
|
|
124
125
|
|
|
125
126
|
// Shared deps — optional (required for run())
|
|
126
127
|
private readonly _runner?: AgentRunner;
|
|
127
|
-
|
|
128
|
+
/** Worktree isolation collaborator — present only when isolation: "worktree". */
|
|
129
|
+
readonly worktree?: WorktreeIsolation;
|
|
128
130
|
readonly observer?: AgentLifecycleObserver;
|
|
129
131
|
private readonly _getRunConfig?: () => RunConfig;
|
|
130
132
|
|
|
@@ -135,37 +137,13 @@ export class Agent {
|
|
|
135
137
|
private readonly _maxTurns?: number;
|
|
136
138
|
private readonly _isolated?: boolean;
|
|
137
139
|
private readonly _thinkingLevel?: ThinkingLevel;
|
|
138
|
-
private readonly _isolation?: IsolationMode;
|
|
139
140
|
private readonly _parentSession?: ParentSessionInfo;
|
|
140
141
|
private readonly _signal?: AbortSignal;
|
|
141
142
|
|
|
142
143
|
// Phase-specific collaborators — each born complete when their info becomes available
|
|
143
144
|
execution?: ExecutionState;
|
|
144
|
-
worktreeState?: WorktreeState;
|
|
145
145
|
notification?: NotificationState;
|
|
146
146
|
|
|
147
|
-
/**
|
|
148
|
-
* Create a git worktree for isolated execution, set worktreeState, and return the worktree path.
|
|
149
|
-
* Returns undefined if isolation is not "worktree".
|
|
150
|
-
* Throws if worktree creation fails (strict isolation).
|
|
151
|
-
* Uses this._worktrees and this._isolation (set at construction).
|
|
152
|
-
*/
|
|
153
|
-
setupWorktree(): string | undefined {
|
|
154
|
-
if (this._isolation !== "worktree") return undefined;
|
|
155
|
-
if (!this._worktrees) {
|
|
156
|
-
throw new Error("Agent not configured for worktree isolation — missing worktrees dependency");
|
|
157
|
-
}
|
|
158
|
-
const wt = this._worktrees.create(this.id);
|
|
159
|
-
if (!wt) {
|
|
160
|
-
throw new Error(
|
|
161
|
-
'Cannot run with isolation: "worktree" — not a git repo, no commits yet, or `git worktree add` failed. ' +
|
|
162
|
-
'Initialize git and commit at least once, or omit `isolation`.',
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
this.worktreeState = new WorktreeState(wt);
|
|
166
|
-
return wt.path;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
147
|
// Steer buffer — messages queued before the session is ready
|
|
170
148
|
private _pendingSteers: string[] = [];
|
|
171
149
|
/** Number of steer messages waiting to be delivered. */
|
|
@@ -205,7 +183,7 @@ export class Agent {
|
|
|
205
183
|
|
|
206
184
|
// Shared deps
|
|
207
185
|
this._runner = init.runner;
|
|
208
|
-
this.
|
|
186
|
+
this.worktree = init.worktree;
|
|
209
187
|
this.observer = init.observer;
|
|
210
188
|
this._getRunConfig = init.getRunConfig;
|
|
211
189
|
|
|
@@ -216,7 +194,6 @@ export class Agent {
|
|
|
216
194
|
this._maxTurns = init.maxTurns;
|
|
217
195
|
this._isolated = init.isolated;
|
|
218
196
|
this._thinkingLevel = init.thinkingLevel;
|
|
219
|
-
this._isolation = init.isolation;
|
|
220
197
|
this._parentSession = init.parentSession;
|
|
221
198
|
this._signal = init.signal;
|
|
222
199
|
|
|
@@ -247,9 +224,10 @@ export class Agent {
|
|
|
247
224
|
this.wireSignal(this._signal, () => this.abort());
|
|
248
225
|
|
|
249
226
|
try {
|
|
250
|
-
this.
|
|
227
|
+
this.worktree?.setup();
|
|
251
228
|
} catch (err) {
|
|
252
229
|
this.markError(err);
|
|
230
|
+
this.releaseListeners();
|
|
253
231
|
this.observer?.onRunFinished?.(this);
|
|
254
232
|
return;
|
|
255
233
|
}
|
|
@@ -258,7 +236,7 @@ export class Agent {
|
|
|
258
236
|
try {
|
|
259
237
|
const result = await this._runner.run(this._snapshot, this.type, this._prompt, {
|
|
260
238
|
context: {
|
|
261
|
-
cwd: this.
|
|
239
|
+
cwd: this.worktree?.path,
|
|
262
240
|
parentSession: this._parentSession,
|
|
263
241
|
},
|
|
264
242
|
model: this._model,
|
|
@@ -285,6 +263,39 @@ export class Agent {
|
|
|
285
263
|
}
|
|
286
264
|
}
|
|
287
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Resume an existing session with a new prompt, managing the observer
|
|
268
|
+
* subscription lifecycle internally (same wiring as run()).
|
|
269
|
+
*
|
|
270
|
+
* Requires runner and an existing session (set when the original run created it).
|
|
271
|
+
* The returned promise always resolves (errors are captured internally).
|
|
272
|
+
* The parent signal flows straight through to runner.resume — resume does not
|
|
273
|
+
* route through this.abortController.
|
|
274
|
+
*/
|
|
275
|
+
async resume(prompt: string, signal?: AbortSignal): Promise<void> {
|
|
276
|
+
if (!this._runner) {
|
|
277
|
+
throw new Error("Agent not configured for execution — missing runner");
|
|
278
|
+
}
|
|
279
|
+
const session = this.session;
|
|
280
|
+
if (!session) {
|
|
281
|
+
throw new Error("Agent not configured for resume — missing session");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.resetForResume(Date.now());
|
|
285
|
+
this.attachObserver(subscribeAgentObserver(session, this, {
|
|
286
|
+
onCompact: (r, info) => this.observer?.onCompacted?.(r, info),
|
|
287
|
+
}));
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const responseText = await this._runner.resume(session, prompt, { signal });
|
|
291
|
+
this.markCompleted(responseText);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
this.markError(err);
|
|
294
|
+
} finally {
|
|
295
|
+
this.releaseListeners();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
288
299
|
/** Increment tool use count. Called by record-observer on tool_execution_end. */
|
|
289
300
|
incrementToolUses(): void {
|
|
290
301
|
this._toolUses++;
|
|
@@ -431,11 +442,9 @@ export class Agent {
|
|
|
431
442
|
this.releaseListeners();
|
|
432
443
|
|
|
433
444
|
let finalResult = result.responseText;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
finalResult += `\n\n---\nChanges saved to branch \`${wtResult.branch}\`. Merge with: \`git merge ${wtResult.branch}\``;
|
|
438
|
-
}
|
|
445
|
+
const wtResult = this.worktree?.cleanup(this.description);
|
|
446
|
+
if (wtResult?.hasChanges && wtResult.branch) {
|
|
447
|
+
finalResult += `\n\n---\nChanges saved to branch \`${wtResult.branch}\`. Merge with: \`git merge ${wtResult.branch}\``;
|
|
439
448
|
}
|
|
440
449
|
|
|
441
450
|
if (result.aborted) this.markAborted(finalResult);
|
|
@@ -455,11 +464,9 @@ export class Agent {
|
|
|
455
464
|
this.markError(err);
|
|
456
465
|
this.releaseListeners();
|
|
457
466
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
} catch (cleanupErr) { debugLog("cleanupWorktree on agent error", cleanupErr); }
|
|
462
|
-
}
|
|
467
|
+
try {
|
|
468
|
+
this.worktree?.cleanup(this.description);
|
|
469
|
+
} catch (cleanupErr) { debugLog("cleanupWorktree on agent error", cleanupErr); }
|
|
463
470
|
|
|
464
471
|
this.observer?.onRunFinished?.(this);
|
|
465
472
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-isolation.ts — WorktreeIsolation: collaborator that owns the
|
|
3
|
+
* git-worktree lifecycle for an isolated agent.
|
|
4
|
+
*
|
|
5
|
+
* Constructed by AgentManager only when isolation === "worktree", bound to a
|
|
6
|
+
* WorktreeManager and the agent id. Agent tells it `setup()` and
|
|
7
|
+
* `cleanup(description)` instead of managing worktree internals itself.
|
|
8
|
+
*
|
|
9
|
+
* The presence/absence of this collaborator IS the isolation mode: Agent calls
|
|
10
|
+
* `this.worktree?.setup()` rather than checking an isolation string.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { WorktreeCleanupResult, WorktreeInfo, WorktreeManager } from "#src/lifecycle/worktree";
|
|
14
|
+
|
|
15
|
+
export class WorktreeIsolation {
|
|
16
|
+
private _info?: WorktreeInfo;
|
|
17
|
+
private _cleanupResult?: WorktreeCleanupResult;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly worktrees: WorktreeManager,
|
|
21
|
+
private readonly agentId: string,
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
/** Absolute worktree path — undefined before setup(). */
|
|
25
|
+
get path(): string | undefined {
|
|
26
|
+
return this._info?.path;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Cleanup outcome — undefined until cleanup() runs. */
|
|
30
|
+
get cleanupResult(): WorktreeCleanupResult | undefined {
|
|
31
|
+
return this._cleanupResult;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create the git worktree and store its info.
|
|
36
|
+
* Throws on failure (strict isolation — no silent fallback).
|
|
37
|
+
*/
|
|
38
|
+
setup(): void {
|
|
39
|
+
const wt = this.worktrees.create(this.agentId);
|
|
40
|
+
if (!wt) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Cannot run with isolation: "worktree" — not a git repo, no commits yet, or `git worktree add` failed. ' +
|
|
43
|
+
"Initialize git and commit at least once, or omit `isolation`.",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
this._info = wt;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Perform worktree cleanup and record the result.
|
|
51
|
+
* No-op returning { hasChanges: false } if setup never ran.
|
|
52
|
+
*/
|
|
53
|
+
cleanup(description: string): WorktreeCleanupResult {
|
|
54
|
+
if (!this._info) return { hasChanges: false };
|
|
55
|
+
const result = this.worktrees.cleanup(this._info, description);
|
|
56
|
+
this._cleanupResult = result;
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -128,7 +128,7 @@ export function toSubagentRecord(record: Agent): SubagentRecord {
|
|
|
128
128
|
if (record.result !== undefined) out.result = record.result;
|
|
129
129
|
if (record.error !== undefined) out.error = record.error;
|
|
130
130
|
if (record.completedAt !== undefined) out.completedAt = record.completedAt;
|
|
131
|
-
const worktreeResult = record.
|
|
131
|
+
const worktreeResult = record.worktree?.cleanupResult;
|
|
132
132
|
if (worktreeResult !== undefined) out.worktreeResult = worktreeResult;
|
|
133
133
|
|
|
134
134
|
return out;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* worktree-state.ts — WorktreeState: lifecycle-phase object for worktree-isolated agents.
|
|
3
|
-
*
|
|
4
|
-
* Constructed once when the worktree is set up (before the run begins).
|
|
5
|
-
* Only exists for agents with isolation: "worktree".
|
|
6
|
-
* cleanupResult is recorded once at completion or error — it is not set at construction.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { WorktreeCleanupResult, WorktreeInfo, WorktreeManager } from "#src/lifecycle/worktree";
|
|
10
|
-
|
|
11
|
-
export type { WorktreeCleanupResult, WorktreeInfo };
|
|
12
|
-
|
|
13
|
-
export class WorktreeState {
|
|
14
|
-
/** Absolute path to the worktree directory. */
|
|
15
|
-
readonly path: string;
|
|
16
|
-
/** Branch name created for this worktree. */
|
|
17
|
-
readonly branch: string;
|
|
18
|
-
|
|
19
|
-
private _cleanupResult?: WorktreeCleanupResult;
|
|
20
|
-
|
|
21
|
-
constructor(info: WorktreeInfo) {
|
|
22
|
-
this.path = info.path;
|
|
23
|
-
this.branch = info.branch;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Result of the worktree cleanup — undefined until recordCleanup is called. */
|
|
27
|
-
get cleanupResult(): WorktreeCleanupResult | undefined {
|
|
28
|
-
return this._cleanupResult;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Record the cleanup result. Called once on agent completion or error. */
|
|
32
|
-
recordCleanup(result: WorktreeCleanupResult): void {
|
|
33
|
-
this._cleanupResult = result;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Perform worktree cleanup and record the result.
|
|
38
|
-
* Tell-Don't-Ask: callers no longer need to orchestrate cleanup + recordCleanup separately.
|
|
39
|
-
*/
|
|
40
|
-
performCleanup(worktrees: WorktreeManager, description: string): WorktreeCleanupResult {
|
|
41
|
-
const result = worktrees.cleanup(this, description);
|
|
42
|
-
this._cleanupResult = result;
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
}
|