122
return result;
123
}
125
>
/** Parameters for {@link WorktreeIsolation.resolveIsolationConfig}. */
126
>
export interface IResolveIsolationConfigRequest {
127
>
readonly workingDirectory: URI | undefined;
128
>
readonly config: Record<string, unknown> | undefined;
129
>
}
130
>
131
>
/**
132
>
* The isolation + branch schema contribution for an agent's
133
>
* `resolveSessionConfig`. Callers merge {@link isolationProperty} (and
134
>
* {@link branchProperty} / {@link worktreeBranchPrefixProperty} when present)
135
>
* into their own schema and merge the default values ({@link isolationValue} /
136
>
* {@link branchDefault}) into the defaults bag they pass to `validateOrDefault`.
137
>
*/
138
>
export interface IIsolationConfigContribution {
139
>
readonly isolationProperty: ISchemaProperty<'folder' | 'worktree'>;
140
>
readonly branchProperty: ISchemaProperty<string> | undefined;
141
>
/**
142
>
* Read-only carrier for the client's `git.branchPrefix`. Declared for both
143
>
* isolations (like `branch`) so the value rides `_config.values` and
144
>
* survives isolation toggles; the host only consumes it for worktree
145
>
* isolation (see {@link WorktreeIsolation.resolveWorkingDirectory}).
146
>
*/
147
>
readonly worktreeBranchPrefixProperty: ISchemaProperty<string> | undefined;
148
>
/** Read-only carrier for the client's `git.worktreeIncludeFiles`. */
149
>
readonly worktreeIncludeFilesProperty: ISchemaProperty<readonly string[]> | undefined;
150
>
readonly isolationValue: 'folder' | 'worktree';
151
>
readonly branchDefault: string | undefined;
152
>
readonly branchValue: string | undefined;
153
>
}
154
>
155
>
/** Parameters for {@link WorktreeIsolation.resolveWorkingDirectory}. */
156
>
export interface IResolveWorkingDirectoryRequest {
157
>
readonly sessionUri: URI;
158
>
readonly sessionId: string;
159
>
readonly workingDirectory: URI | undefined;
160
>
readonly config: Record<string, unknown> | undefined;
161
>
readonly prompt?: string;
162
>
readonly githubToken?: string;
163
>
}
164
>
165
>
/**
166
>
* Shared, per-agent controller for git-worktree session isolation. Owns the
167
>
* full machinery Copilot pioneered so Codex and Claude get identical behavior:
168
>
*
169
>
* - advertising the `isolation` (`folder` / `worktree`) and `branch` session
170
>
* config properties from `resolveSessionConfig` ({@link resolveIsolationConfig});
171
>
* - completing branch names for the branch picker ({@link branchCompletions});
172
>
* - creating the worktree on materialization and persisting its metadata
173
>
* ({@link resolveWorkingDirectory});
174
>
* - surfacing the "Created isolated worktree" announcement live on the first
175
>
* turn ({@link takePendingAnnouncement}) and on restore
176
>
* ({@link applyRestoreAnnouncement});
177
>
* - cleaning up / recreating the worktree on dispose, archive, and unarchive.
178
>
*
179
>
* A single host-owned instance serves every agent: the orchestrator
180
>
* ({@link AgentService}) creates it and drives the lifecycle so individual
181
>
* agents stay unaware of the folder-vs-worktree distinction. Session state
182
>
* (`_createdWorktrees`, pending markers, pending announcements) is keyed by the
183
>
* globally-unique sessionId, so sharing one instance across agents is safe.
184
>
*/
185
>
export class WorktreeIsolation extends Disposable {
186
>
187
>
/**
188
>
* Worktrees created by this agent in the current process, keyed by
189
>
* sessionId. Used to remove the worktree on dispose / error and to
190
>
* enumerate live worktrees during shutdown.
191
>
*/
192
>
private readonly _createdWorktrees = new Map<string, ICreatedWorktree>();
193
>
194
>
/**
195
>
* Per-session announcement (markdown) emitted as a synthetic streaming
196
>
* markdown part the first time the session sends a message. Surfaces the
197
>
* "Created isolated worktree for branch X" message live during the first
198
>
* turn; the same announcement is re-injected on restore via
199
>
* {@link applyRestoreAnnouncement}.
200
>
*/
201
>
private readonly _pendingFirstTurnAnnouncements = new Map<string, string>();
202
>
203
>
/**
204
>
* SessionIds of freshly-created worktree-isolation sessions whose worktree
205
>
* has not yet been created (creation is deferred to the first send so the
206
>
* user's prompt can drive branch naming). While a session is in this set the
207
>
* host reports its working directory as "pending" ({@link isWorkingDirectoryPending})
208
>
* so agents defer prewarming / materializing until {@link resolveOnFirstSend}
209
>
* runs. Never populated for restored sessions — their worktree already exists
210
>
* on disk and their persisted working directory already points at it.
211
>
*/
212
>
private readonly _pending = new Set<string>();
213
>
214
>
/** Fixed log label; one host-owned instance serves every agent. */
215
>
private readonly _logLabel = 'AgentHost';
216
>
217
>
/**
218
>
* Serializes the worktree lifecycle per session so a first-send creation
219
>
* ({@link resolveOnFirstSend}) never interleaves with archive/unarchive
220
>
* cleanup ({@link cleanupWorktreeOnArchive} / {@link recreateWorktreeOnUnarchive})
221
>
* or dispose ({@link removeCreatedWorktree}) for the same session — the
222
>
* guarantee each agent previously enforced with its own sequencer.
223
>
*/
224
>
private readonly _sequencer = new SequencerByKey<string>();
225
>
private readonly _worktreeCreationSequencer = new SequencerByKey<string>();
226
>
227
>
/** Branch-name generator for worktree sessions; created from {@link ICopilotApiService} unless a test supplies an override. */
228
>
private readonly _branchNameGenerator: IAgentBranchNameGenerator;
229
>
230
>
constructor(
231
branchNameGenerator: IAgentBranchNameGenerator | undefined,
232
@IAgentHostGitService private readonly _gitService: IAgentHostGitService,