Missing prompt contribution should not break chat (#281451)

This commit is contained in:
Paul
2025-12-04 22:54:11 -08:00
committed by GitHub
parent 541da45a06
commit a04f628b77
2 changed files with 70 additions and 2 deletions
@@ -16,7 +16,7 @@ import { IModelService } from '../../../../../../editor/common/services/model.js
import { localize } from '../../../../../../nls.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js';
import { IFileService } from '../../../../../../platform/files/common/files.js';
import { FileOperationError, FileOperationResult, IFileService } from '../../../../../../platform/files/common/files.js';
import { IExtensionService } from '../../../../../services/extensions/common/extensions.js';
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
import { ILabelService } from '../../../../../../platform/label/common/label.js';
@@ -387,7 +387,7 @@ export class PromptsService extends Disposable implements IPromptsService {
let agentFiles = await this.listPromptFiles(PromptsType.agent, token);
const disabledAgents = this.getDisabledPromptFiles(PromptsType.agent);
agentFiles = agentFiles.filter(promptPath => !disabledAgents.has(promptPath.uri));
const customAgents = await Promise.all(
const customAgentsResults = await Promise.allSettled(
agentFiles.map(async (promptPath): Promise<ICustomAgent> => {
const uri = promptPath.uri;
const ast = await this.parseNew(uri, token);
@@ -432,6 +432,23 @@ export class PromptsService extends Disposable implements IPromptsService {
return { uri, name, description, model, tools, handOffs, argumentHint, target, infer, agentInstructions, source };
})
);
const customAgents: ICustomAgent[] = [];
for (let i = 0; i < customAgentsResults.length; i++) {
const result = customAgentsResults[i];
if (result.status === 'fulfilled') {
customAgents.push(result.value);
} else {
const uri = agentFiles[i].uri;
const error = result.reason;
if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
this.logger.warn(`[computeCustomAgents] Skipping agent file that does not exist: ${uri}`, error.message);
} else {
this.logger.error(`[computeCustomAgents] Failed to parse agent file: ${uri}`, error);
}
}
}
return customAgents;
}
@@ -1202,6 +1202,57 @@ suite('PromptsService', () => {
registered.dispose();
});
test('Contributed agent file that does not exist should not crash', async () => {
const nonExistentUri = URI.parse('file://extensions/my-extension/nonexistent.agent.md');
const existingUri = URI.parse('file://extensions/my-extension/existing.agent.md');
const extension = {
identifier: { value: 'test.my-extension' }
} as unknown as IExtensionDescription;
// Only create the existing file
await mockFiles(fileService, [
{
path: existingUri.path,
contents: [
'---',
'name: \'Existing Agent\'',
'description: \'An agent that exists\'',
'---',
'I am an existing agent.',
]
}
]);
// Register both agents (one exists, one doesn't)
const registered1 = service.registerContributedFile(
PromptsType.agent,
'NonExistent Agent',
'An agent that does not exist',
nonExistentUri,
extension
);
const registered2 = service.registerContributedFile(
PromptsType.agent,
'Existing Agent',
'An agent that exists',
existingUri,
extension
);
// Verify that getCustomAgents doesn't crash and returns only the valid agent
const agents = await service.getCustomAgents(CancellationToken.None);
// Should only get the existing agent, not the non-existent one
assert.strictEqual(agents.length, 1, 'Should only return the agent that exists');
assert.strictEqual(agents[0].name, 'Existing Agent');
assert.strictEqual(agents[0].description, 'An agent that exists');
assert.strictEqual(agents[0].uri.toString(), existingUri.toString());
registered1.dispose();
registered2.dispose();
});
});
suite('findClaudeSkills', () => {