@@ -1050,11 +1050,23 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
10501050 /**
10511051 * Map to track pending requests for untitled sessions.
10521052 * Key = Untitled Session Id
1053- * Vlaue = Map of Request Id to the Promise of the request being handled
1053+ * Value = Map of Request Id to the Promise of the request being handled
10541054 * So if we have multiple requests (can happen when steering) for the same untitled session.
10551055 */
10561056 private readonly pendingRequestsForUntitledSessions = new Map < string , Map < string , Promise < vscode . ChatResult | void > > > ( ) ;
1057- private readonly pendingCommitWorktreeChangesBySession = new Map < string , Promise < void > > ( ) ;
1057+
1058+ /**
1059+ * Tracks in-flight requests per session so we can coordinate worktree
1060+ * commit / PR handling and cleanup.
1061+ *
1062+ * We generally cannot have parallel requests for the same session, but when
1063+ * steering is involved there can be multiple requests in flight for a
1064+ * single session (the original request continues running while steering
1065+ * requests are processed). This map records all active requests for each
1066+ * session so that any worktree-related actions are deferred until the last
1067+ * in-flight request for that session has completed.
1068+ */
1069+ private readonly pendingRequestBySession = new Map < string , Set < vscode . ChatRequest > > ( ) ;
10581070
10591071 /**
10601072 * Outer request handler that supports *yielding* for session steering.
@@ -1065,7 +1077,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
10651077 * previous request (status is `InProgress` or `NeedsInput`).
10661078 * 2. VS Code signals this by setting `context.yieldRequested = true` on the
10671079 * *previous* request's context object.
1068- * 3. This handler polls `context.yieldRequested` every 500 ms. Once detected
1080+ * 3. This handler polls `context.yieldRequested` every 100 ms. Once detected
10691081 * the outer `Promise.race` resolves, returning control to VS Code so it
10701082 * can dispatch the new (steering) request.
10711083 * 4. Crucially, the inner `handleRequestImpl` promise is **not** cancelled
@@ -1106,7 +1118,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
11061118 }
11071119 }
11081120
1109- private sendTelemetryForHandleRequest ( request : vscode . ChatRequest , context : vscode . ChatContext , result : vscode . ChatResult | void , error : unknown ) : void {
1121+ private sendTelemetryForHandleRequest ( request : vscode . ChatRequest , context : vscode . ChatContext ) : void {
11101122 const { chatSessionContext } = context ;
11111123 const hasChatSessionItem = String ( ! ! chatSessionContext ?. chatSessionItem ) ;
11121124 let isUntitled = String ( chatSessionContext ?. isUntitled ) ;
@@ -1138,11 +1150,12 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
11381150 let { chatSessionContext } = context ;
11391151 const disposables = new DisposableStore ( ) ;
11401152 let sessionId : string | undefined = undefined ;
1153+ let sdkSessionId : string | undefined = undefined ;
11411154 try {
11421155
11431156 const initialOptions = chatSessionContext ?. initialSessionOptions ;
11441157 if ( initialOptions && chatSessionContext ) {
1145- if ( initialOptions && initialOptions . length > 0 ) {
1158+ if ( initialOptions . length > 0 ) {
11461159 const sessionResource = chatSessionContext . chatSessionItem . resource ;
11471160 const sessionId = SessionIdForCLI . parse ( sessionResource ) ;
11481161 for ( const opt of initialOptions ) {
@@ -1186,7 +1199,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
11861199 }
11871200 }
11881201
1189- this . sendTelemetryForHandleRequest ( request , context , undefined , undefined ) ;
1202+ this . sendTelemetryForHandleRequest ( request , context ) ;
11901203
11911204 const [ authInfo , ] = await Promise . all ( [ this . copilotCLISDK . getAuthInfo ( ) . catch ( ( ex ) => this . logService . error ( ex , 'Authorization failed' ) ) , this . lockRepoOptionForSession ( context , token ) ] ) ;
11921205 if ( ! authInfo ) {
@@ -1242,7 +1255,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
12421255 }
12431256 return { } ;
12441257 }
1245-
1258+ sdkSessionId = session . object . sessionId ;
12461259 this . copilotCLIAgents . trackSessionAgent ( session . object . sessionId , agent ?. name ) ;
12471260 if ( isUntitled && ! this . useController ) {
12481261 disposables . add ( toDisposable ( ( ) => this . sessionItemProvider . untitledSessionIdMapping . delete ( session . object . sessionId ) ) ) ;
@@ -1259,6 +1272,10 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
12591272 // No need to track the temproary workspace folders as its been persisted.
12601273 this . folderRepositoryManager . deleteUntitledSessionFolder ( id ) ;
12611274 }
1275+ const requestsForSession = this . pendingRequestBySession . get ( session . object . sessionId ) ?? new Set < vscode . ChatRequest > ( ) ;
1276+ requestsForSession . add ( request ) ;
1277+ this . pendingRequestBySession . set ( session . object . sessionId , requestsForSession ) ;
1278+
12621279 // Check if we have context stored for this request (created in createCLISessionAndSubmitRequest, work around)
12631280 const contextForRequest = this . contextForRequest . get ( session . object . sessionId ) ;
12641281 this . contextForRequest . delete ( session . object . sessionId ) ;
@@ -1269,22 +1286,22 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
12691286 const { prompt, attachments } = contextForRequest ;
12701287 this . contextForRequest . delete ( session . object . sessionId ) ;
12711288 await session . object . handleRequest ( request , { prompt } , attachments , model , authInfo , token ) ;
1272- await this . commitWorktreeChangesIfNeeded ( session . object , token ) ;
1289+ await this . commitWorktreeChangesIfNeeded ( request , session . object , token ) ;
12731290 } else if ( request . command && ! request . prompt && ! isUntitled ) {
12741291 const input = ( copilotCLICommands as readonly string [ ] ) . includes ( request . command )
12751292 ? { command : request . command as CopilotCLICommand }
12761293 : { prompt : `/${ request . command } ` } ;
12771294 await session . object . handleRequest ( request , input , [ ] , model , authInfo , token ) ;
1278- await this . commitWorktreeChangesIfNeeded ( session . object , token ) ;
1295+ await this . commitWorktreeChangesIfNeeded ( request , session . object , token ) ;
12791296 } else if ( request . prompt && Object . values ( builtinSlashSCommands ) . includes ( request . prompt ) ) {
12801297 await session . object . handleRequest ( request , { prompt : request . prompt } , [ ] , model , authInfo , token ) ;
1281- await this . commitWorktreeChangesIfNeeded ( session . object , token ) ;
1298+ await this . commitWorktreeChangesIfNeeded ( request , session . object , token ) ;
12821299 } else {
12831300 // Construct the full prompt with references to be sent to CLI.
12841301 const plan = request . modeInstructions2 ? isCopilotCLIPlanAgent ( request . modeInstructions2 ) : false ;
12851302 const { prompt, attachments } = await this . promptResolver . resolvePrompt ( request , undefined , [ ] , session . object . workspace , [ ] , token ) ;
12861303 await session . object . handleRequest ( request , { prompt, plan } , attachments , model , authInfo , token ) ;
1287- await this . commitWorktreeChangesIfNeeded ( session . object , token ) ;
1304+ await this . commitWorktreeChangesIfNeeded ( request , session . object , token ) ;
12881305 }
12891306
12901307 if ( isUntitled && ! token . isCancellationRequested ) {
@@ -1322,6 +1339,15 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
13221339 throw ex ;
13231340 }
13241341 finally {
1342+ if ( sdkSessionId ) {
1343+ const requestsForSession = this . pendingRequestBySession . get ( sdkSessionId ) ;
1344+ if ( requestsForSession ) {
1345+ requestsForSession . delete ( request ) ;
1346+ if ( requestsForSession . size === 0 ) {
1347+ this . pendingRequestBySession . delete ( sdkSessionId ) ;
1348+ }
1349+ }
1350+ }
13251351 if ( chatSessionContext ?. chatSessionItem . resource ) {
13261352 this . sessionItemProvider . notifySessionsChange ( ) ;
13271353 }
@@ -1408,17 +1434,21 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
14081434 }
14091435 }
14101436
1411- private async commitWorktreeChangesIfNeeded ( session : ICopilotCLISession , token : vscode . CancellationToken ) : Promise < void > {
1412- if ( token . isCancellationRequested ) {
1437+ private async commitWorktreeChangesIfNeeded ( request : vscode . ChatRequest , session : ICopilotCLISession , token : vscode . CancellationToken ) : Promise < void > {
1438+ const pendingRequests = this . pendingRequestBySession . get ( session . sessionId ) ;
1439+ if ( pendingRequests && pendingRequests . size > 1 ) {
1440+ // We still have pending requests for this session, which means the user has done some steering.
1441+ // Wait for all requests to complete, the last request to complete will handle the commit.
1442+ pendingRequests . delete ( request ) ;
14131443 return ;
14141444 }
14151445
1416- const existingPromise = this . pendingCommitWorktreeChangesBySession . get ( session . sessionId ) ;
1417- if ( existingPromise ) {
1418- return existingPromise ;
1446+ if ( token . isCancellationRequested ) {
1447+ pendingRequests ?. delete ( request ) ;
1448+ return ;
14191449 }
14201450
1421- const commitPromise = ( async ( ) => {
1451+ try {
14221452 if ( session . status === vscode . ChatSessionStatus . Completed ) {
14231453 const workingDirectory = getWorkingDirectory ( session . workspace ) ;
14241454 if ( isIsolationEnabled ( session . workspace ) ) {
@@ -1434,14 +1464,9 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
14341464 }
14351465
14361466 await this . handlePullRequestCreated ( session ) ;
1437- } ) ( ) . finally ( ( ) => {
1438- if ( this . pendingCommitWorktreeChangesBySession . get ( session . sessionId ) === commitPromise ) {
1439- this . pendingCommitWorktreeChangesBySession . delete ( session . sessionId ) ;
1440- }
1441- } ) ;
1442-
1443- this . pendingCommitWorktreeChangesBySession . set ( session . sessionId , commitPromise ) ;
1444- return commitPromise ;
1467+ } finally {
1468+ pendingRequests ?. delete ( request ) ;
1469+ }
14451470 }
14461471
14471472 private async handlePullRequestCreated ( session : ICopilotCLISession ) : Promise < void > {
@@ -1698,7 +1723,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
16981723 // Now that we've delegated it to a session, we can get out of here.
16991724 // Else if the request takes say 10 minutes, the caller would be blocked for that long.
17001725 session . object . handleRequest ( request , { prompt } , attachments , model , authInfo , token )
1701- . then ( ( ) => this . commitWorktreeChangesIfNeeded ( session . object , token ) )
1726+ . then ( ( ) => this . commitWorktreeChangesIfNeeded ( request , session . object , token ) )
17021727 . catch ( error => {
17031728 this . logService . error ( `Failed to handle CLI session request: ${ error } ` ) ;
17041729 // Optionally: stream.error(error) to notify the user
0 commit comments