@@ -68,6 +68,25 @@ const PROMPT_FILE_DISCOVERY_CONCURRENCY = 10;
6868export class PromptsService extends Disposable implements IPromptsService {
6969 public declare readonly _serviceBrand : undefined ;
7070
71+ /**
72+ * Bounds how many prompt files discovery reads in parallel.
73+ *
74+ * Owned by the service rather than created per invocation on purpose: an
75+ * invalidation clears the cached discovery promise without cancelling the
76+ * computation it was tracking, so several passes can run at once. A limiter
77+ * per invocation would give each pass its own quota and the aggregate would
78+ * still grow with the number of passes, which is the exhaustion this bound
79+ * exists to prevent.
80+ */
81+ private readonly _discoveryLimiter = this . _register ( new Limiter < unknown > ( PROMPT_FILE_DISCOVERY_CONCURRENCY ) ) ;
82+
83+ /**
84+ * Queues a discovery file read on the shared, service-wide limiter.
85+ */
86+ private queueDiscoveryRead < T > ( task : ( ) => Promise < T > ) : Promise < T > {
87+ return this . _discoveryLimiter . queue ( task ) as Promise < T > ;
88+ }
89+
7190 /**
7291 * Prompt files locator utility.
7392 */
@@ -546,8 +565,7 @@ export class PromptsService extends Disposable implements IPromptsService {
546565 ...enabledSkills ,
547566 ] ;
548567
549- const slashCommandLimiter = new Limiter < ISlashCommandDiscoveryResult > ( PROMPT_FILE_DISCOVERY_CONCURRENCY ) ;
550- const parseResults = await Promise . all ( slashCommandFiles . map ( promptPath => slashCommandLimiter . queue ( async ( ) => {
568+ const parseResults = await Promise . all ( slashCommandFiles . map ( promptPath => this . queueDiscoveryRead ( async ( ) => {
551569 try {
552570 const parsedPromptFile = await this . parseNew ( promptPath . uri , token ) ;
553571 let rawName : string ;
@@ -746,8 +764,7 @@ export class PromptsService extends Disposable implements IPromptsService {
746764 const userHome = userHomeUri . scheme === Schemas . file ? userHomeUri . fsPath : userHomeUri . path ;
747765 const defaultFolder = this . workspaceService . getWorkspace ( ) . folders [ 0 ] ;
748766
749- const agentLimiter = new Limiter < IAgentDiscoveryResult > ( PROMPT_FILE_DISCOVERY_CONCURRENCY ) ;
750- const files = await Promise . all ( allAgentFiles . map ( promptPath => agentLimiter . queue ( async ( ) : Promise < IAgentDiscoveryResult > => {
767+ const files = await Promise . all ( allAgentFiles . map ( promptPath => this . queueDiscoveryRead ( async ( ) : Promise < IAgentDiscoveryResult > => {
751768 const uri = promptPath . uri ;
752769 const isEnabled = ! disabledAgents . has ( uri ) ;
753770
@@ -1270,8 +1287,7 @@ export class PromptsService extends Disposable implements IPromptsService {
12701287 sourceUri ?: URI ;
12711288 hasDisabledClaudeHooks ?: boolean ;
12721289 } ;
1273- const hookLimiter = new Limiter < HookFileResult > ( PROMPT_FILE_DISCOVERY_CONCURRENCY ) ;
1274- const fileResults = await Promise . all ( hookFiles . map ( hookFile => hookLimiter . queue ( async ( ) : Promise < HookFileResult > => {
1290+ const fileResults = await Promise . all ( hookFiles . map ( hookFile => this . queueDiscoveryRead ( async ( ) : Promise < HookFileResult > => {
12751291 const name = basename ( hookFile . uri ) ;
12761292
12771293 // Plugins are handled separately down below because they do their own parsing+interpolation
0 commit comments