@@ -10,7 +10,7 @@ import { Limiter, SequencerByKey } from '../../../../base/common/async.js';
1010import { Emitter } from '../../../../base/common/event.js' ;
1111import { Disposable , DisposableMap } from '../../../../base/common/lifecycle.js' ;
1212import { FileAccess } from '../../../../base/common/network.js' ;
13- import { delimiter , dirname } from '../../../../base/common/path.js' ;
13+ import { basename , delimiter , dirname } from '../../../../base/common/path.js' ;
1414import { URI } from '../../../../base/common/uri.js' ;
1515import { generateUuid } from '../../../../base/common/uuid.js' ;
1616import { IParsedPlugin , parsePlugin } from '../../../agentPlugins/common/pluginParsers.js' ;
@@ -19,7 +19,7 @@ import { IInstantiationService } from '../../../instantiation/common/instantiati
1919import { ILogService } from '../../../log/common/log.js' ;
2020import { localize } from '../../../../nls.js' ;
2121import { IAgentPluginManager , ISyncedCustomization } from '../../common/agentPluginManager.js' ;
22- import { AgentSession , IAgent , IAgentAttachment , IAgentCreateSessionConfig , IAgentCreateSessionResult , IAgentDescriptor , IAgentMessageEvent , IAgentModelInfo , IAgentProgressEvent , IAgentResolveSessionConfigParams , IAgentSessionConfigCompletionsParams , IAgentSessionMetadata , IAgentSessionProjectInfo , IAgentSubagentStartedEvent , IAgentToolCompleteEvent , IAgentToolStartEvent } from '../../common/agentService.js' ;
22+ import { AgentHostSessionConfigBranchNameHintKey , AgentSession , IAgent , IAgentAttachment , IAgentCreateSessionConfig , IAgentCreateSessionResult , IAgentDescriptor , IAgentMessageEvent , IAgentModelInfo , IAgentProgressEvent , IAgentResolveSessionConfigParams , IAgentSessionConfigCompletionsParams , IAgentSessionMetadata , IAgentSessionProjectInfo , IAgentSubagentStartedEvent , IAgentToolCompleteEvent , IAgentToolStartEvent } from '../../common/agentService.js' ;
2323import type { IResolveSessionConfigResult , ISessionConfigCompletionsResult } from '../../common/state/protocol/commands.js' ;
2424import { ISessionDataService } from '../../common/sessionDataService.js' ;
2525import { CustomizationStatus , ICustomizationRef , SessionInputResponseKind , type ISessionInputAnswer , type IPendingMessage , type PolicyState } from '../../common/state/sessionState.js' ;
@@ -38,6 +38,18 @@ interface ICreatedWorktree {
3838 readonly worktree : URI ;
3939}
4040
41+ export function getCopilotWorktreesRoot ( repositoryRoot : URI ) : URI {
42+ return URI . joinPath ( repositoryRoot , '..' , `${ basename ( repositoryRoot . fsPath ) } .worktrees` ) ;
43+ }
44+
45+ export function getCopilotWorktreeName ( branchName : string ) : string {
46+ return branchName . replace ( / \/ / g, '-' ) ;
47+ }
48+
49+ export function getCopilotWorktreeBranchName ( sessionId : string , branchNameHint : string | undefined ) : string {
50+ return `agents/${ branchNameHint ? `${ branchNameHint } -${ sessionId . substring ( 0 , 8 ) } ` : sessionId } ` ;
51+ }
52+
4153/**
4254 * Agent provider backed by the Copilot SDK {@link CopilotClient}.
4355 */
@@ -288,40 +300,40 @@ export class CopilotAgent extends Disposable implements IAgent {
288300
289301 async resolveSessionConfig ( params : IAgentResolveSessionConfigParams ) : Promise < IResolveSessionConfigResult > {
290302 const gitInfo = params . workingDirectory ? await this . _getGitInfo ( params . workingDirectory ) : undefined ;
291- const targetValue = params . config ?. target === 'folder' || params . config ?. target === 'worktree'
292- ? params . config . target
303+ const isolationValue = params . config ?. isolation === 'folder' || params . config ?. isolation === 'worktree'
304+ ? params . config . isolation
293305 : gitInfo ? 'worktree' : 'folder' ;
294306
295- const values : Record < string , string > = { target : targetValue } ;
307+ const values : Record < string , string > = { isolation : isolationValue } ;
296308 if ( gitInfo ) {
297- values . branch = typeof params . config ?. branch === 'string' && targetValue === 'worktree'
309+ values . branch = typeof params . config ?. branch === 'string' && isolationValue === 'worktree'
298310 ? params . config . branch
299311 : gitInfo . currentBranch ;
300312 }
301313
302314 const properties : IResolveSessionConfigResult [ 'schema' ] [ 'properties' ] = {
303- target : {
315+ isolation : {
304316 type : 'string' ,
305- title : localize ( 'agentHost.sessionConfig.target ' , "Target " ) ,
306- description : localize ( 'agentHost.sessionConfig.targetDescription ' , "Where the agent should make changes" ) ,
317+ title : localize ( 'agentHost.sessionConfig.isolation ' , "Isolation " ) ,
318+ description : localize ( 'agentHost.sessionConfig.isolationDescription ' , "Where the agent should make changes" ) ,
307319 enum : gitInfo ? [ 'folder' , 'worktree' ] : [ 'folder' ] ,
308- enumLabels : gitInfo ? [ localize ( 'agentHost.sessionConfig.target .folder' , "Folder" ) , localize ( 'agentHost.sessionConfig.target .worktree' , "Worktree" ) ] : [ localize ( 'agentHost.sessionConfig.target .folder' , "Folder" ) ] ,
309- enumDescriptions : gitInfo ? [ localize ( 'agentHost.sessionConfig.target .folderDescription' , "Work directly in the folder" ) , localize ( 'agentHost.sessionConfig.target .worktreeDescription' , "Create a Git worktree for isolation" ) ] : [ localize ( 'agentHost.sessionConfig.target .folderDescription' , "Work directly in the folder" ) ] ,
320+ enumLabels : gitInfo ? [ localize ( 'agentHost.sessionConfig.isolation .folder' , "Folder" ) , localize ( 'agentHost.sessionConfig.isolation .worktree' , "Worktree" ) ] : [ localize ( 'agentHost.sessionConfig.isolation .folder' , "Folder" ) ] ,
321+ enumDescriptions : gitInfo ? [ localize ( 'agentHost.sessionConfig.isolation .folderDescription' , "Work directly in the folder" ) , localize ( 'agentHost.sessionConfig.isolation .worktreeDescription' , "Create a Git worktree for isolation" ) ] : [ localize ( 'agentHost.sessionConfig.isolation .folderDescription' , "Work directly in the folder" ) ] ,
310322 enumIcons : gitInfo ? [ 'folder' , 'worktree' ] : [ 'folder' ] ,
311323 default : gitInfo ? 'worktree' : 'folder' ,
312324 readOnly : ! gitInfo ,
313325 } ,
314326 } ;
315327
316328 if ( gitInfo ) {
317- const branchReadOnly = targetValue === 'folder' ;
329+ const branchReadOnly = isolationValue === 'folder' ;
318330 properties . branch = {
319331 type : 'string' ,
320332 title : localize ( 'agentHost.sessionConfig.branch' , "Branch" ) ,
321333 description : localize ( 'agentHost.sessionConfig.branchDescription' , "Base branch to work from" ) ,
322- enum : branchReadOnly ? [ gitInfo . currentBranch ] : gitInfo . recentBranches ,
323- enumLabels : branchReadOnly ? [ gitInfo . currentBranch ] : gitInfo . recentBranches ,
324- enumIcons : branchReadOnly ? [ 'git-branch' ] : gitInfo . recentBranches . map ( ( ) => 'git-branch' ) ,
334+ enum : [ gitInfo . currentBranch ] ,
335+ enumLabels : [ gitInfo . currentBranch ] ,
336+ enumIcons : [ 'git-branch' ] ,
325337 default : gitInfo . currentBranch ,
326338 enumDynamic : ! branchReadOnly ,
327339 readOnly : branchReadOnly ,
@@ -608,26 +620,21 @@ export class CopilotAgent extends Disposable implements IAgent {
608620 return agentSession ;
609621 }
610622
611- private async _getGitInfo ( workingDirectory : URI ) : Promise < { currentBranch : string ; recentBranches : string [ ] } | undefined > {
623+ private async _getGitInfo ( workingDirectory : URI ) : Promise < { currentBranch : string } | undefined > {
612624 if ( ! await this . _gitService . isInsideWorkTree ( workingDirectory ) ) {
613625 return undefined ;
614626 }
615627
616628 const currentBranch = await this . _gitService . getCurrentBranch ( workingDirectory ) ?? 'HEAD' ;
617- const recentBranches = await this . _getBranches ( workingDirectory ) ;
618- return { currentBranch, recentBranches : this . _prependUnique ( currentBranch , recentBranches ) } ;
629+ return { currentBranch } ;
619630 }
620631
621632 private async _getBranches ( workingDirectory : URI , query ?: string ) : Promise < string [ ] > {
622633 return this . _gitService . getBranches ( workingDirectory , { query, limit : CopilotAgent . _BRANCH_COMPLETION_LIMIT } ) ;
623634 }
624635
625- private _prependUnique ( value : string , values : readonly string [ ] ) : string [ ] {
626- return [ value , ...values . filter ( candidate => candidate !== value ) ] ;
627- }
628-
629636 private async _resolveSessionWorkingDirectory ( config : IAgentCreateSessionConfig | undefined , sessionId : string ) : Promise < URI | undefined > {
630- if ( config ?. config ?. target !== 'worktree' || ! config . workingDirectory || typeof config . config . branch !== 'string' ) {
637+ if ( config ?. config ?. isolation !== 'worktree' || ! config . workingDirectory || typeof config . config . branch !== 'string' ) {
631638 return config ?. workingDirectory ;
632639 }
633640
@@ -636,10 +643,12 @@ export class CopilotAgent extends Disposable implements IAgent {
636643 return config . workingDirectory ;
637644 }
638645
639- const worktreesRoot = URI . joinPath ( repositoryRoot , '..' , '.copilot-worktrees' ) ;
640- const worktree = URI . joinPath ( worktreesRoot , sessionId ) ;
646+ const worktreesRoot = getCopilotWorktreesRoot ( repositoryRoot ) ;
647+ const branchNameHint = config . config [ AgentHostSessionConfigBranchNameHintKey ] ;
648+ const branchName = getCopilotWorktreeBranchName ( sessionId , branchNameHint ) ;
649+ const worktree = URI . joinPath ( worktreesRoot , getCopilotWorktreeName ( branchName ) ) ;
641650 await fs . mkdir ( worktreesRoot . fsPath , { recursive : true } ) ;
642- await this . _gitService . addWorktree ( repositoryRoot , worktree , `copilot/ ${ sessionId } ` , config . config . branch ) ;
651+ await this . _gitService . addWorktree ( repositoryRoot , worktree , branchName , config . config . branch ) ;
643652 this . _createdWorktrees . set ( sessionId , { repositoryRoot, worktree } ) ;
644653 return worktree ;
645654 }
0 commit comments