Remove await in discovery phase (#300537)

This commit is contained in:
Paul
2026-03-10 13:52:16 -07:00
committed by GitHub
parent f01d41c784
commit d71bb75a3a
4 changed files with 63 additions and 131 deletions

View File

@@ -493,14 +493,6 @@ export interface IPromptsService extends IDisposable {
*/
readonly onDidChangeSkills: Event<void>;
/**
* Gets detailed discovery information for a prompt type.
* This includes all files found and their load/skip status with reasons.
* Used for diagnostics and config-info displays.
* @param sessionResource Optional session resource to scope debug logging to a specific session.
*/
getPromptDiscoveryInfo(type: PromptsType, token: CancellationToken, sessionResource?: URI): Promise<IPromptDiscoveryInfo>;
/**
* Gets all hooks collected from hooks.json files.
* The result is cached and invalidated when hook files change.

View File

@@ -559,7 +559,24 @@ export class PromptsService extends Disposable implements IPromptsService {
}
public async getPromptSlashCommands(token: CancellationToken, sessionResource?: URI): Promise<readonly IChatPromptSlashCommand[]> {
return await this.cachedSlashCommands.get(token);
const sw = StopWatch.create();
const result = await this.cachedSlashCommands.get(token);
if (sessionResource) {
const elapsed = sw.elapsed();
void this.getPromptSlashCommandDiscoveryInfo(token).catch(() => undefined).then(discoveryInfo => {
const details = result.length === 1
? localize("promptsService.resolvedSlashCommand", "Resolved {0} slash command in {1}ms", result.length, elapsed.toFixed(1))
: localize("promptsService.resolvedSlashCommands", "Resolved {0} slash commands in {1}ms", result.length, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadSlashCommands", "Load Slash Commands"),
details,
discoveryInfo,
category: 'discovery',
});
});
}
return result;
}
private async computePromptSlashCommands(token: CancellationToken): Promise<readonly IChatPromptSlashCommand[]> {
@@ -645,16 +662,17 @@ export class PromptsService extends Disposable implements IPromptsService {
const result = await this.cachedCustomAgents.get(token);
if (sessionResource) {
const elapsed = sw.elapsed();
const discoveryInfo = await this.getAgentDiscoveryInfo(token);
const details = result.length === 1
? localize("promptsService.resolvedAgent", "Resolved {0} agent in {1}ms", result.length, elapsed.toFixed(1))
: localize("promptsService.resolvedAgents", "Resolved {0} agents in {1}ms", result.length, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadAgents", "Load Agents"),
details,
discoveryInfo,
category: 'discovery',
void this.getAgentDiscoveryInfo(token).catch(() => undefined).then(discoveryInfo => {
const details = result.length === 1
? localize("promptsService.resolvedAgent", "Resolved {0} agent in {1}ms", result.length, elapsed.toFixed(1))
: localize("promptsService.resolvedAgents", "Resolved {0} agents in {1}ms", result.length, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadAgents", "Load Agents"),
details,
discoveryInfo,
category: 'discovery',
});
});
}
return result;
@@ -1061,16 +1079,17 @@ export class PromptsService extends Disposable implements IPromptsService {
const result = await this.cachedSkills.get(token);
if (sessionResource) {
const elapsed = sw.elapsed();
const discoveryInfo = await this.getSkillDiscoveryInfo(token);
const details = result.length === 1
? localize("promptsService.resolvedSkill", "Resolved {0} skill in {1}ms", result.length, elapsed.toFixed(1))
: localize("promptsService.resolvedSkills", "Resolved {0} skills in {1}ms", result.length, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadSkills", "Load Skills"),
details,
discoveryInfo,
category: 'discovery',
void this.getSkillDiscoveryInfo(token).catch(() => undefined).then(discoveryInfo => {
const details = result.length === 1
? localize("promptsService.resolvedSkill", "Resolved {0} skill in {1}ms", result.length, elapsed.toFixed(1))
: localize("promptsService.resolvedSkills", "Resolved {0} skills in {1}ms", result.length, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadSkills", "Load Skills"),
details,
discoveryInfo,
category: 'discovery',
});
});
}
return result;
@@ -1184,17 +1203,18 @@ export class PromptsService extends Disposable implements IPromptsService {
const result = await this.cachedHooks.get(token);
if (sessionResource) {
const elapsed = sw.elapsed();
const hookCount = result ? Object.values(result.hooks).reduce((sum, arr) => sum + arr.length, 0) : 0;
const discoveryInfo = await this.getHookDiscoveryInfo(token);
const details = hookCount === 1
? localize("promptsService.resolvedHook", "Resolved {0} hook in {1}ms", hookCount, elapsed.toFixed(1))
: localize("promptsService.resolvedHooks", "Resolved {0} hooks in {1}ms", hookCount, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadHooks", "Load Hooks"),
details,
discoveryInfo,
category: 'discovery',
void this.getHookDiscoveryInfo(token).catch(() => undefined).then(discoveryInfo => {
const hookCount = result ? Object.values(result.hooks).reduce((sum, arr) => sum + arr.length, 0) : 0;
const details = hookCount === 1
? localize("promptsService.resolvedHook", "Resolved {0} hook in {1}ms", hookCount, elapsed.toFixed(1))
: localize("promptsService.resolvedHooks", "Resolved {0} hooks in {1}ms", hookCount, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadHooks", "Load Hooks"),
details,
discoveryInfo,
category: 'discovery',
});
});
}
return result;
@@ -1205,16 +1225,17 @@ export class PromptsService extends Disposable implements IPromptsService {
const result = await this.listPromptFiles(PromptsType.instructions, token);
if (sessionResource) {
const elapsed = sw.elapsed();
const discoveryInfo = await this.getInstructionsDiscoveryInfo(token);
const details = result.length === 1
? localize("promptsService.resolvedInstruction", "Resolved {0} instruction in {1}ms", result.length, elapsed.toFixed(1))
: localize("promptsService.resolvedInstructions", "Resolved {0} instructions in {1}ms", result.length, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadInstructions", "Load Instructions"),
details,
discoveryInfo,
category: 'discovery',
void this.getInstructionsDiscoveryInfo(token).catch(() => undefined).then(discoveryInfo => {
const details = result.length === 1
? localize("promptsService.resolvedInstruction", "Resolved {0} instruction in {1}ms", result.length, elapsed.toFixed(1))
: localize("promptsService.resolvedInstructions", "Resolved {0} instructions in {1}ms", result.length, elapsed.toFixed(1));
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.loadInstructions", "Load Instructions"),
details,
discoveryInfo,
category: 'discovery',
});
});
}
return result;
@@ -1318,58 +1339,6 @@ export class PromptsService extends Disposable implements IPromptsService {
return { hooks: result, hasDisabledClaudeHooks };
}
public async getPromptDiscoveryInfo(type: PromptsType, token: CancellationToken, sessionResource?: URI): Promise<IPromptDiscoveryInfo> {
if (sessionResource) {
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.discoveryStart", "Discovery {0} (Start)", type),
category: 'discovery',
});
}
const files: IPromptFileDiscoveryResult[] = [];
let result: IPromptDiscoveryInfo;
if (type === PromptsType.skill) {
result = await this.getSkillDiscoveryInfo(token);
} else if (type === PromptsType.agent) {
result = await this.getAgentDiscoveryInfo(token);
} else if (type === PromptsType.prompt) {
result = await this.getPromptSlashCommandDiscoveryInfo(token);
} else if (type === PromptsType.instructions) {
result = await this.getInstructionsDiscoveryInfo(token);
} else if (type === PromptsType.hook) {
result = await this.getHookDiscoveryInfo(token);
} else {
result = { type, files };
}
const loadedCount = result.files.filter(f => f.status === 'loaded').length;
const skippedCount = result.files.filter(f => f.status === 'skipped').length;
// Add source folder diagnostics if not already present
if (!result.sourceFolders) {
const sourceFolders = await this._collectSourceFolderDiagnostics(type);
result = { ...result, sourceFolders };
}
if (sessionResource) {
const details = localize(
"promptsService.discoveryResult",
"{0} loaded, {1} skipped",
loadedCount,
skippedCount,
);
this._onDidLogDiscovery.fire({
sessionResource,
name: localize("promptsService.discoveryEnd", "Discovery {0} (End)", type),
details,
discoveryInfo: result,
category: 'discovery',
});
}
return result;
}
private async getSkillDiscoveryInfo(token: CancellationToken): Promise<IPromptDiscoveryInfo> {
const useAgentSkills = this.configurationService.getValue(PromptsConfig.USE_AGENT_SKILLS);

View File

@@ -67,8 +67,6 @@ export class MockPromptsService implements IPromptsService {
registerPromptFileProvider(extension: IExtensionDescription, type: PromptsType, provider: { providePromptFiles: (context: IPromptFileContext, token: CancellationToken) => Promise<IPromptFileResource[] | undefined> }): IDisposable { throw new Error('Method not implemented.'); }
findAgentSkills(token: CancellationToken, sessionResource?: URI): Promise<IAgentSkill[] | undefined> { throw new Error('Method not implemented.'); }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getPromptDiscoveryInfo(_type: any, _token: CancellationToken, _sessionResource?: URI): Promise<any> { throw new Error('Method not implemented.'); }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getHooks(_token: CancellationToken, _sessionResource?: URI): Promise<any> { throw new Error('Method not implemented.'); }
getInstructionFiles(_token: CancellationToken, _sessionResource?: URI): Promise<readonly IPromptPath[]> { throw new Error('Method not implemented.'); }
dispose(): void { }

View File

@@ -3670,33 +3670,6 @@ suite('PromptsService', () => {
assert.strictEqual(reTrustedResult.hooks[HookType.PreToolUse]?.length, 1);
});
test('discovery info marks hooks as skipped when workspace is untrusted', async function () {
workspaceContextService.setWorkspace(testWorkspace(URI.file('/test-workspace')));
testConfigService.setUserConfiguration(PromptsConfig.USE_CHAT_HOOKS, true);
testConfigService.setUserConfiguration(PromptsConfig.HOOKS_LOCATION_KEY, { [HOOKS_SOURCE_FOLDER]: true });
await mockFiles(fileService, [
{
path: '/test-workspace/.github/hooks/my-hook.json',
contents: [
JSON.stringify({
hooks: {
[HookType.PreToolUse]: [
{ type: 'command', command: 'echo test' },
],
},
}),
],
},
]);
await workspaceTrustService.setWorkspaceTrust(false);
const discoveryInfo = await service.getPromptDiscoveryInfo(PromptsType.hook, CancellationToken.None);
assert.strictEqual(discoveryInfo.files.length, 1, 'Expected one discovery result');
assert.strictEqual(discoveryInfo.files[0].status, 'skipped');
assert.strictEqual(discoveryInfo.files[0].skipReason, 'workspace-untrusted');
});
test('suppresses plugin hooks when workspace is untrusted', async function () {
testConfigService.setUserConfiguration(PromptsConfig.USE_CHAT_HOOKS, true);
testConfigService.setUserConfiguration(PromptsConfig.HOOKS_LOCATION_KEY, {});