55
66import type { SessionOptions , SweCustomAgent } from '@github/copilot/sdk' ;
77import type { Uri } from 'vscode' ;
8+ import { RelativePattern } from '../../../../platform/filesystem/common/fileTypes' ;
89import { IAuthenticationService } from '../../../../platform/authentication/common/authentication' ;
910import { ConfigKey , IConfigurationService } from '../../../../platform/configuration/common/configurationService' ;
1011import { IEnvService } from '../../../../platform/env/common/envService' ;
1112import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext' ;
13+ import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService' ;
1214import { ILogService } from '../../../../platform/log/common/logService' ;
1315import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService' ;
1416import { createServiceIdentifier } from '../../../../util/common/services' ;
17+ import { Delayer } from '../../../../util/vs/base/common/async' ;
18+ import { Emitter , Event } from '../../../../util/vs/base/common/event' ;
1519import { Lazy } from '../../../../util/vs/base/common/lazy' ;
16- import { IDisposable , toDisposable } from '../../../../util/vs/base/common/lifecycle' ;
20+ import { Disposable , DisposableStore , IDisposable , toDisposable } from '../../../../util/vs/base/common/lifecycle' ;
1721import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation' ;
1822import { getCopilotLogger } from './logger' ;
1923import { ensureNodePtyShim } from './nodePtyShim' ;
@@ -154,6 +158,7 @@ export class CopilotCLIModels implements ICopilotCLIModels {
154158
155159export interface ICopilotCLIAgents {
156160 readonly _serviceBrand : undefined ;
161+ readonly onDidChangeAgents : Event < void > ;
157162 getDefaultAgent ( ) : Promise < string > ;
158163 resolveAgent ( agentId : string ) : Promise < SweCustomAgent | undefined > ;
159164 setDefaultAgent ( agent : string | undefined ) : Promise < void > ;
@@ -164,16 +169,51 @@ export interface ICopilotCLIAgents {
164169
165170export const ICopilotCLIAgents = createServiceIdentifier < ICopilotCLIAgents > ( 'ICopilotCLIAgents' ) ;
166171
167- export class CopilotCLIAgents implements ICopilotCLIAgents {
172+ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
168173 declare _serviceBrand : undefined ;
169174 private sessionAgents : Record < string , { agentId ?: string ; createdDateTime : number } > = { } ;
170- private _agents ?: Readonly < SweCustomAgent > [ ] ;
175+ private _agentsPromise ?: Promise < Readonly < SweCustomAgent > [ ] > ;
176+ private readonly _onDidChangeAgents = this . _register ( new Emitter < void > ( ) ) ;
177+ readonly onDidChangeAgents : Event < void > = this . _onDidChangeAgents . event ;
178+ private readonly _fileWatchers = this . _register ( new DisposableStore ( ) ) ;
171179 constructor (
172180 @ICopilotCLISDK private readonly copilotCLISDK : ICopilotCLISDK ,
173181 @IVSCodeExtensionContext private readonly extensionContext : IVSCodeExtensionContext ,
174182 @ILogService private readonly logService : ILogService ,
175183 @IConfigurationService private readonly configurationService : IConfigurationService ,
176- ) { }
184+ @IWorkspaceService private readonly workspaceService : IWorkspaceService ,
185+ @IFileSystemService private readonly fileSystemService : IFileSystemService ,
186+ ) {
187+ super ( ) ;
188+ this . setupFileWatchers ( ) ;
189+ void this . getAgents ( ) ;
190+ this . _register ( this . workspaceService . onDidChangeWorkspaceFolders ( ( ) => {
191+ this . setupFileWatchers ( ) ;
192+ this . _refreshAgents ( ) ;
193+ } ) ) ;
194+ }
195+
196+ protected setupFileWatchers ( ) : void {
197+ this . _fileWatchers . clear ( ) ;
198+ const workspaceFolders = this . workspaceService . getWorkspaceFolders ( ) ;
199+ const refresher = this . _fileWatchers . add ( new Delayer ( 500 ) ) ;
200+ for ( const folder of workspaceFolders ) {
201+ const pattern = new RelativePattern ( folder , '.github/agents/*.agent.md' ) ;
202+ const watcher = this . _fileWatchers . add ( this . fileSystemService . createFileSystemWatcher ( pattern ) ) ;
203+ this . _fileWatchers . add ( watcher . onDidCreate ( ( ) => refresher . trigger ( ( ) => this . _refreshAgents ( ) ) ) ) ;
204+ this . _fileWatchers . add ( watcher . onDidChange ( ( ) => refresher . trigger ( ( ) => this . _refreshAgents ( ) ) ) ) ;
205+ this . _fileWatchers . add ( watcher . onDidDelete ( ( ) => refresher . trigger ( ( ) => this . _refreshAgents ( ) ) ) ) ;
206+ }
207+ }
208+
209+ private _refreshAgents ( ) : void {
210+ this . _agentsPromise = undefined ;
211+ this . getAgents ( ) . catch ( ( error ) => {
212+ this . logService . error ( '[CopilotCLIAgents] Failed to refresh agents' , error ) ;
213+ } ) ;
214+ this . _onDidChangeAgents . fire ( ) ;
215+ }
216+
177217 async trackSessionAgent ( sessionId : string , agent : string | undefined ) : Promise < void > {
178218 const details = Object . keys ( this . sessionAgents ) . length ? this . sessionAgents : this . extensionContext . workspaceState . get < Record < string , { agentId ?: string ; createdDateTime : number } > > ( COPILOT_CLI_SESSION_AGENTS_MEMENTO_KEY , this . sessionAgents ) ;
179219
@@ -227,17 +267,16 @@ export class CopilotCLIAgents implements ICopilotCLIAgents {
227267 }
228268
229269 async getAgents ( ) : Promise < Readonly < SweCustomAgent > [ ] > {
230- // Fetching agents from the SDK can be slow, cache the result while allowing background refreshes.
231- const agents = this . _agents ;
232- const promise = this . getAgentsImpl ( ) ;
233-
234- promise . then ( fetchedAgents => {
235- this . _agents = fetchedAgents ;
236- } ) . catch ( ( error ) => {
237- this . logService . error ( '[CopilotCLISession] Failed to fetch custom agents' , error ) ;
238- } ) ;
270+ // Cache the promise to avoid concurrent fetches
271+ if ( ! this . _agentsPromise ) {
272+ this . _agentsPromise = this . getAgentsImpl ( ) . catch ( ( error ) => {
273+ this . logService . error ( '[CopilotCLIAgents] Failed to fetch custom agents' , error ) ;
274+ this . _agentsPromise = undefined ;
275+ return [ ] ;
276+ } ) ;
277+ }
239278
240- return agents ?? promise ;
279+ return this . _agentsPromise ;
241280 }
242281
243282 async getAgentsImpl ( ) : Promise < Readonly < SweCustomAgent > [ ] > {
0 commit comments