33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import type { Session , SessionEvent , SessionOptions , SweCustomAgent , internal } from '@github/copilot/sdk' ;
6+ import type { internal , Session , SessionEvent , SessionOptions , SweCustomAgent } from '@github/copilot/sdk' ;
77import type { CancellationToken , ChatRequest , Uri } from 'vscode' ;
88import { INativeEnvService } from '../../../../platform/env/common/envService' ;
9+ import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext' ;
910import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService' ;
1011import { RelativePattern } from '../../../../platform/filesystem/common/fileTypes' ;
1112import { ILogService } from '../../../../platform/log/common/logService' ;
13+ import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService' ;
1214import { createServiceIdentifier } from '../../../../util/common/services' ;
1315import { coalesce } from '../../../../util/vs/base/common/arrays' ;
14- import { disposableTimeout , raceCancellation , raceCancellationError } from '../../../../util/vs/base/common/async' ;
16+ import { disposableTimeout , raceCancellation , raceCancellationError , Sequencer } from '../../../../util/vs/base/common/async' ;
1517import { Emitter , Event } from '../../../../util/vs/base/common/event' ;
1618import { Lazy } from '../../../../util/vs/base/common/lazy' ;
1719import { Disposable , DisposableMap , IDisposable , IReference , RefCountedDisposable , toDisposable } from '../../../../util/vs/base/common/lifecycle' ;
@@ -24,6 +26,8 @@ import { CopilotCLISession, ICopilotCLISession } from './copilotcliSession';
2426import { getCopilotLogger } from './logger' ;
2527import { ICopilotCLIMCPHandler } from './mcpHandler' ;
2628
29+ const COPILOT_CLI_WORKSPACE_SPECIFIC_SESSIONS_KEY = 'github.copilot.cli.workspaceSpecificSessions' ;
30+
2731export interface ICopilotCLISessionItem {
2832 readonly id : string ;
2933 readonly label : string ;
@@ -67,6 +71,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
6771
6872 private sessionMutexForGetSession = new Map < string , Mutex > ( ) ;
6973
74+ private readonly _mementoUpdator = new Sequencer ( ) ;
7075 constructor (
7176 @ILogService protected readonly logService : ILogService ,
7277 @ICopilotCLISDK private readonly copilotCLISDK : ICopilotCLISDK ,
@@ -75,6 +80,8 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
7580 @IFileSystemService private readonly fileSystem : IFileSystemService ,
7681 @ICopilotCLIMCPHandler private readonly mcpHandler : ICopilotCLIMCPHandler ,
7782 @ICopilotCLIAgents private readonly agents : ICopilotCLIAgents ,
83+ @IVSCodeExtensionContext private readonly context : IVSCodeExtensionContext ,
84+ @IWorkspaceService private readonly workspaceService : IWorkspaceService ,
7885 ) {
7986 super ( ) ;
8087 this . monitorSessionFiles ( ) ;
@@ -111,12 +118,18 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
111118
112119 async _getAllSessions ( token : CancellationToken ) : Promise < readonly ICopilotCLISessionItem [ ] > {
113120 try {
121+ const mementoUpdateCompleted = this . _mementoUpdator . queue ( async ( ) => Promise . resolve ( ) ) ;
114122 const sessionManager = await raceCancellationError ( this . getSessionManager ( ) , token ) ;
115123 const sessionMetadataList = await raceCancellationError ( sessionManager . listSessions ( ) , token ) ;
116124
125+ // Wait for any pending memento updates to complete before filtering sessions.
126+ await mementoUpdateCompleted ;
117127 // Convert SessionMetadata to ICopilotCLISession
118128 const diskSessions : ICopilotCLISessionItem [ ] = coalesce ( await Promise . all (
119129 sessionMetadataList . map ( async ( metadata ) => {
130+ if ( this . shouldExcludeSession ( metadata . sessionId ) ) {
131+ return ;
132+ }
120133 const id = metadata . sessionId ;
121134 const startTime = metadata . startTime . getTime ( ) ;
122135 const endTime = metadata . modifiedTime . getTime ( ) ;
@@ -195,7 +208,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
195208 const sessionManager = await raceCancellationError ( this . getSessionManager ( ) , token ) ;
196209 const sdkSession = await sessionManager . createSession ( options . toSessionOptions ( ) ) ;
197210 this . logService . trace ( `[CopilotCLISession] Created new CopilotCLI session ${ sdkSession . sessionId } .` ) ;
198-
211+ void this . updateSessionInWorkspace ( sdkSession . sessionId , 'add' ) ;
199212
200213 return this . createCopilotSession ( sdkSession , options , sessionManager ) ;
201214 }
@@ -280,6 +293,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
280293 }
281294
282295 public async deleteSession ( sessionId : string ) : Promise < void > {
296+ void this . updateSessionInWorkspace ( sessionId , 'delete' ) ;
283297 try {
284298 {
285299 const session = this . _sessionWrappers . get ( sessionId ) ;
@@ -301,6 +315,37 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
301315 this . _onDidChangeSessions . fire ( ) ;
302316 }
303317 }
318+
319+ private async updateSessionInWorkspace ( sessionId : string , operation : 'add' | 'delete' ) : Promise < void > {
320+ // If we're not in a workspace, do not track sessions as these are global sessions.
321+ if ( this . workspaceService . getWorkspaceFolders ( ) . length === 0 ) {
322+ return ;
323+ }
324+ this . _mementoUpdator . queue ( async ( ) => {
325+ let trackedSessions = this . context . workspaceState . get < Record < string , { createdDateTime : number } > > ( COPILOT_CLI_WORKSPACE_SPECIFIC_SESSIONS_KEY , { } ) ;
326+ if ( operation === 'add' ) {
327+ trackedSessions [ sessionId ] = { createdDateTime : Date . now ( ) } ;
328+ } else {
329+ delete trackedSessions [ sessionId ] ;
330+ }
331+
332+ // If we have 100 entries or more, sort by created time and keep the recent 100 and drop the rest.
333+ if ( Object . keys ( trackedSessions ) . length >= 100 ) {
334+ const sortedSessions = Object . entries ( trackedSessions ) . sort ( ( a , b ) => b [ 1 ] . createdDateTime - a [ 1 ] . createdDateTime ) ;
335+ trackedSessions = Object . fromEntries ( sortedSessions . slice ( 0 , 100 ) ) ;
336+ }
337+ await this . context . workspaceState . update ( COPILOT_CLI_WORKSPACE_SPECIFIC_SESSIONS_KEY , trackedSessions ) ;
338+ } ) ;
339+ }
340+
341+ private shouldExcludeSession ( sessionId : string ) : boolean {
342+ if ( this . workspaceService . getWorkspaceFolders ( ) . length === 0 ) {
343+ return false ;
344+ }
345+ const trackedSessions = this . context . workspaceState . get < Record < string , { createdDateTime : number } > > ( COPILOT_CLI_WORKSPACE_SPECIFIC_SESSIONS_KEY , { } ) ;
346+ return ! ( sessionId in trackedSessions ) ;
347+ }
348+
304349}
305350
306351function labelFromPrompt ( prompt : string ) : string {
0 commit comments