@gotgenes/pi-subagents 11.2.0 → 11.4.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 +15 -0
- package/docs/architecture/architecture.md +152 -211
- package/docs/architecture/history/phase-15-domain-model-evolution.md +73 -0
- package/docs/decisions/0002-extensions-on-a-minimal-core.md +98 -0
- package/docs/plans/0256-extract-worktree-isolation.md +256 -0
- package/docs/plans/0257-extract-child-session-factory.md +283 -0
- package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +64 -0
- package/docs/retro/0256-extract-worktree-isolation.md +89 -0
- package/docs/retro/0257-extract-child-session-factory.md +31 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/lifecycle/agent-manager.ts +5 -2
- package/src/lifecycle/agent-runner.ts +14 -9
- package/src/lifecycle/agent.ts +18 -45
- package/src/lifecycle/child-lifecycle.ts +89 -0
- package/src/lifecycle/worktree-isolation.ts +59 -0
- package/src/service/service-adapter.ts +1 -1
- package/src/lifecycle/permission-bridge.ts +0 -63
- package/src/lifecycle/worktree-state.ts +0 -45
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,7 +224,7 @@ 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);
|
|
253
230
|
this.releaseListeners();
|
|
@@ -259,7 +236,7 @@ export class Agent {
|
|
|
259
236
|
try {
|
|
260
237
|
const result = await this._runner.run(this._snapshot, this.type, this._prompt, {
|
|
261
238
|
context: {
|
|
262
|
-
cwd: this.
|
|
239
|
+
cwd: this.worktree?.path,
|
|
263
240
|
parentSession: this._parentSession,
|
|
264
241
|
},
|
|
265
242
|
model: this._model,
|
|
@@ -465,11 +442,9 @@ export class Agent {
|
|
|
465
442
|
this.releaseListeners();
|
|
466
443
|
|
|
467
444
|
let finalResult = result.responseText;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
finalResult += `\n\n---\nChanges saved to branch \`${wtResult.branch}\`. Merge with: \`git merge ${wtResult.branch}\``;
|
|
472
|
-
}
|
|
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}\``;
|
|
473
448
|
}
|
|
474
449
|
|
|
475
450
|
if (result.aborted) this.markAborted(finalResult);
|
|
@@ -489,11 +464,9 @@ export class Agent {
|
|
|
489
464
|
this.markError(err);
|
|
490
465
|
this.releaseListeners();
|
|
491
466
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
} catch (cleanupErr) { debugLog("cleanupWorktree on agent error", cleanupErr); }
|
|
496
|
-
}
|
|
467
|
+
try {
|
|
468
|
+
this.worktree?.cleanup(this.description);
|
|
469
|
+
} catch (cleanupErr) { debugLog("cleanupWorktree on agent error", cleanupErr); }
|
|
497
470
|
|
|
498
471
|
this.observer?.onRunFinished?.(this);
|
|
499
472
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* child-lifecycle.ts — Child-execution lifecycle event contract and publisher.
|
|
3
|
+
*
|
|
4
|
+
* The core publishes its child-execution lifecycle as ordered events on the Pi
|
|
5
|
+
* event bus; reactive consumers (permissions, telemetry, UI) subscribe rather
|
|
6
|
+
* than the core reaching out to them (ADR 0002). This module owns the channel
|
|
7
|
+
* names, payload shapes, and the publisher that emits them.
|
|
8
|
+
*
|
|
9
|
+
* The publisher takes an injected `emit` callback so this module stays free of
|
|
10
|
+
* Pi SDK imports — `index.ts` wires it to `pi.events.emit`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Emitted at the start of a child run, before the session is created. */
|
|
14
|
+
export const SUBAGENT_CHILD_SPAWNING = "subagents:child:spawning";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Emitted after the child session is created, immediately before
|
|
18
|
+
* `bindExtensions()`. Carries the child identity consumers need to register
|
|
19
|
+
* the session. Subscribers must register synchronously so the entry lands
|
|
20
|
+
* before binding proceeds (see ADR 0002 / the event-bus synchronous-dispatch
|
|
21
|
+
* guarantee).
|
|
22
|
+
*/
|
|
23
|
+
export const SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created";
|
|
24
|
+
|
|
25
|
+
/** Emitted after the child's prompt resolves (normal, steered, or aborted). */
|
|
26
|
+
export const SUBAGENT_CHILD_COMPLETED = "subagents:child:completed";
|
|
27
|
+
|
|
28
|
+
/** Emitted in the run's `finally` — always fires, on success and error. */
|
|
29
|
+
export const SUBAGENT_CHILD_DISPOSED = "subagents:child:disposed";
|
|
30
|
+
|
|
31
|
+
/** Payload for `subagents:child:spawning`. */
|
|
32
|
+
export interface ChildSpawningEvent {
|
|
33
|
+
agentName: string;
|
|
34
|
+
parentSessionId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Payload for `subagents:child:session-created`. */
|
|
38
|
+
export interface ChildSessionCreatedEvent {
|
|
39
|
+
/** Child session directory — the registry key. */
|
|
40
|
+
sessionDir: string;
|
|
41
|
+
agentName: string;
|
|
42
|
+
parentSessionId?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Payload for `subagents:child:completed`. */
|
|
46
|
+
export interface ChildCompletedEvent {
|
|
47
|
+
sessionDir: string;
|
|
48
|
+
agentName: string;
|
|
49
|
+
/** True if the run was hard-aborted (max turns + grace exceeded). */
|
|
50
|
+
aborted: boolean;
|
|
51
|
+
/** True if the run was steered to wrap up (soft turn limit) but finished. */
|
|
52
|
+
steered: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Payload for `subagents:child:disposed`. */
|
|
56
|
+
export interface ChildDisposedEvent {
|
|
57
|
+
sessionDir: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Narrow emit seam — injected, never imports the Pi SDK. */
|
|
61
|
+
export type LifecycleEmit = (channel: string, data: unknown) => void;
|
|
62
|
+
|
|
63
|
+
/** Publishes the child-execution lifecycle on the event bus. */
|
|
64
|
+
export interface ChildLifecyclePublisher {
|
|
65
|
+
spawning(event: ChildSpawningEvent): void;
|
|
66
|
+
sessionCreated(event: ChildSessionCreatedEvent): void;
|
|
67
|
+
completed(event: ChildCompletedEvent): void;
|
|
68
|
+
disposed(event: ChildDisposedEvent): void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Build a publisher backed by an injected `emit` callback. */
|
|
72
|
+
export function createChildLifecyclePublisher(
|
|
73
|
+
emit: LifecycleEmit,
|
|
74
|
+
): ChildLifecyclePublisher {
|
|
75
|
+
return {
|
|
76
|
+
spawning(event) {
|
|
77
|
+
emit(SUBAGENT_CHILD_SPAWNING, event);
|
|
78
|
+
},
|
|
79
|
+
sessionCreated(event) {
|
|
80
|
+
emit(SUBAGENT_CHILD_SESSION_CREATED, event);
|
|
81
|
+
},
|
|
82
|
+
completed(event) {
|
|
83
|
+
emit(SUBAGENT_CHILD_COMPLETED, event);
|
|
84
|
+
},
|
|
85
|
+
disposed(event) {
|
|
86
|
+
emit(SUBAGENT_CHILD_DISPOSED, event);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -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,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* permission-bridge.ts — Cross-extension bridge to @gotgenes/pi-permission-system.
|
|
3
|
-
*
|
|
4
|
-
* pi-subagents does not import pi-permission-system directly. Instead it
|
|
5
|
-
* accesses the published PermissionsService via a process-global Symbol.for()
|
|
6
|
-
* key, the same mechanism pi-permission-system uses to publish itself.
|
|
7
|
-
*
|
|
8
|
-
* When pi-permission-system is not installed, getPermissionsService() returns
|
|
9
|
-
* undefined and all registration calls are silent no-ops.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The two PermissionsService methods pi-subagents needs.
|
|
14
|
-
*
|
|
15
|
-
* Follows ISP — does not expose the full PermissionsService surface
|
|
16
|
-
* (checkPermission, getToolPermission, etc.) to avoid coupling.
|
|
17
|
-
*/
|
|
18
|
-
interface PermissionsServiceConsumer {
|
|
19
|
-
registerSubagentSession(
|
|
20
|
-
sessionKey: string,
|
|
21
|
-
info: { parentSessionId?: string; agentName: string },
|
|
22
|
-
): void;
|
|
23
|
-
unregisterSubagentSession(sessionKey: string): void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const PERMISSION_SERVICE_KEY = Symbol.for(
|
|
27
|
-
"@gotgenes/pi-permission-system:service",
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
function getPermissionsService(): PermissionsServiceConsumer | undefined {
|
|
31
|
-
return (globalThis as Record<symbol, unknown>)[
|
|
32
|
-
PERMISSION_SERVICE_KEY
|
|
33
|
-
] as PermissionsServiceConsumer | undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Register a child session with pi-permission-system's SubagentSessionRegistry.
|
|
38
|
-
*
|
|
39
|
-
* Must be called after deriving sessionDir but before session.bindExtensions()
|
|
40
|
-
* so isSubagentExecutionContext() hits the registry on the first check during
|
|
41
|
-
* child extension initialization.
|
|
42
|
-
*
|
|
43
|
-
* @param sessionKey - The session directory path (unique per session).
|
|
44
|
-
* @param info - Agent name and optional parent session ID for forwarding.
|
|
45
|
-
*/
|
|
46
|
-
export function registerChildSession(
|
|
47
|
-
sessionKey: string,
|
|
48
|
-
info: { parentSessionId?: string; agentName: string },
|
|
49
|
-
): void {
|
|
50
|
-
getPermissionsService()?.registerSubagentSession(sessionKey, info);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Unregister a child session from pi-permission-system's SubagentSessionRegistry.
|
|
55
|
-
*
|
|
56
|
-
* Must be called in a finally block so cleanup happens on both success and
|
|
57
|
-
* error paths.
|
|
58
|
-
*
|
|
59
|
-
* @param sessionKey - The session directory path used during registration.
|
|
60
|
-
*/
|
|
61
|
-
export function unregisterChildSession(sessionKey: string): void {
|
|
62
|
-
getPermissionsService()?.unregisterSubagentSession(sessionKey);
|
|
63
|
-
}
|
|
@@ -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
|
-
}
|