Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class CopilotCLICustomizationProvider extends Disposable implements vscod
this._register(this.copilotCLIAgents.onDidChangeAgents(() => this._onDidChange.fire()));
}

async provideChatSessionCustomizations(token: vscode.CancellationToken): Promise<vscode.ChatSessionCustomizationItem[]> {
async provideChatSessionCustomizations(_sessionResource: vscode.Uri, token: vscode.CancellationToken): Promise<vscode.ChatSessionCustomizationItem[]> {
const [agents, instructions, skills, hooks, plugins] = await Promise.all([
this.getAgentItems(token),
this.getInstructionItems(token),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class TestCustomInstructionsService extends MockCustomInstructionsService {
}

describe('CopilotCLICustomizationProvider', () => {
const testSessionResource = URI.parse('copilotcli:///test-session');
let disposables: DisposableStore;
let mockPromptsService: MockPromptsService;
let mockCopilotCLIAgents: MockCopilotCLIAgents;
Expand Down Expand Up @@ -146,7 +147,7 @@ describe('CopilotCLICustomizationProvider', () => {

it('only returns items whose type is in supportedTypes', async () => {
mockCopilotCLIAgents.setAgents([makeAgentInfo('explore', 'Explore')]);
const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const supported = new Set(CopilotCLICustomizationProvider.metadata.supportedTypes!.map(t => t.id));
for (const item of items) {
expect(supported.has(item.type.id), `item "${item.name}" has type "${item.type.id}" not in supportedTypes`).toBe(true);
Expand All @@ -155,7 +156,7 @@ describe('CopilotCLICustomizationProvider', () => {

it('does not set groupKey for items with synthetic URIs (vscode infers grouping)', async () => {
mockCopilotCLIAgents.setAgents([makeAgentInfo('explore', 'Explore')]);
const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const builtinItems = items.filter(i => i.uri.scheme !== 'file');
for (const item of builtinItems) {
expect(item.groupKey, `item "${item.name}" should not have groupKey (vscode infers)`).toBeUndefined();
Expand All @@ -165,7 +166,7 @@ describe('CopilotCLICustomizationProvider', () => {

describe('provideChatSessionCustomizations', () => {
it('returns empty array when no files exist', async () => {
const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items).toEqual([]);
});

Expand All @@ -175,7 +176,7 @@ describe('CopilotCLICustomizationProvider', () => {
makeAgentInfo('task', 'Multi-step tasks'),
]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const agentItems = items.filter((i: vscode.ChatSessionCustomizationItem) => i.type === FakeChatSessionCustomizationType.Agent);
expect(agentItems).toHaveLength(2);
expect(agentItems[0].name).toBe('explore');
Expand All @@ -186,7 +187,7 @@ describe('CopilotCLICustomizationProvider', () => {
const fileUri = URI.file('/workspace/.github/explore.agent.md');
mockCopilotCLIAgents.setAgents([makeFileAgentInfo('explore', fileUri, 'Explore agent')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const agentItems = items.filter((i: vscode.ChatSessionCustomizationItem) => i.type === FakeChatSessionCustomizationType.Agent);
expect(agentItems).toHaveLength(1);
expect(agentItems[0].uri).toEqual(fileUri);
Expand All @@ -196,7 +197,7 @@ describe('CopilotCLICustomizationProvider', () => {
it('uses synthetic URI for SDK-only agents', async () => {
mockCopilotCLIAgents.setAgents([makeAgentInfo('task', 'Task agent')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const agentItems = items.filter((i: vscode.ChatSessionCustomizationItem) => i.type === FakeChatSessionCustomizationType.Agent);
expect(agentItems).toHaveLength(1);
expect(agentItems[0].uri.scheme).toBe('copilotcli');
Expand All @@ -207,15 +208,15 @@ describe('CopilotCLICustomizationProvider', () => {
it('uses displayName from agents when available', async () => {
mockCopilotCLIAgents.setAgents([makeAgentInfo('code-review', 'Reviews code', 'Code Review')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items[0].name).toBe('Code Review');
});

it('returns instructions with on-demand groupKey when no applyTo pattern', async () => {
const uri = URI.file('/workspace/.github/copilot-instructions.md');
mockPromptsService.setInstructions([makeInstruction(uri, 'copilot-instructions', undefined)]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items).toHaveLength(1);
expect(items[0].uri).toBe(uri);
expect(items[0].type).toBe(FakeChatSessionCustomizationType.Instructions);
Expand All @@ -226,7 +227,7 @@ describe('CopilotCLICustomizationProvider', () => {
const uri = URI.file('/workspace/.github/skills/lint-check/SKILL.md');
mockPromptsService.setSkills([makeSkill(uri, 'lint-check')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items).toHaveLength(1);
expect(items[0].uri).toBe(uri);
expect(items[0].type).toBe(FakeChatSessionCustomizationType.Skill);
Expand All @@ -237,7 +238,7 @@ describe('CopilotCLICustomizationProvider', () => {
const uri = URI.file('/workspace/.copilot/skills/my-skill/SKILL.md');
mockPromptsService.setSkills([makeSkill(uri, 'my-skill')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items).toHaveLength(1);
expect(items[0].name).toBe('my-skill');
});
Expand All @@ -249,15 +250,15 @@ describe('CopilotCLICustomizationProvider', () => {
mockPromptsService.setHooks([makeHook(URI.file('/workspace/.copilot/hooks/pre-commit.json'))]);
mockPromptsService.setPlugins([makePlugin(URI.file('/workspace/.copilot/plugins/my-plugin'))]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items).toHaveLength(5);
});

it('returns hooks with correct type and name', async () => {
const uri = URI.file('/workspace/.copilot/hooks/diagnostics.json');
mockPromptsService.setHooks([makeHook(uri)]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items).toHaveLength(1);
expect(items[0].uri).toBe(uri);
expect(items[0].type).toBe(FakeChatSessionCustomizationType.Hook);
Expand All @@ -267,7 +268,7 @@ describe('CopilotCLICustomizationProvider', () => {
it('strips .json extension from hook file name', async () => {
mockPromptsService.setHooks([makeHook(URI.file('/workspace/.copilot/hooks/security-checks.json'))]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items[0].name).toBe('security-checks');
});

Expand All @@ -277,7 +278,7 @@ describe('CopilotCLICustomizationProvider', () => {
makeHook(URI.file('/workspace/.copilot/hooks/diagnostics.json')),
]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const hookItems = items.filter((i: vscode.ChatSessionCustomizationItem) => i.type === FakeChatSessionCustomizationType.Hook);
expect(hookItems).toHaveLength(2);
});
Expand All @@ -286,7 +287,7 @@ describe('CopilotCLICustomizationProvider', () => {
const uri = URI.file('/workspace/.copilot/plugins/lint-rules');
mockPromptsService.setPlugins([makePlugin(uri)]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
expect(items).toHaveLength(1);
expect(items[0].uri).toEqual(uri);
expect(items[0].type).toBe(FakeChatSessionCustomizationType.Plugins);
Expand All @@ -300,7 +301,7 @@ describe('CopilotCLICustomizationProvider', () => {
mockPromptsService.setInstructions([makeInstruction(uri, 'copilot-instructions', undefined)]);
mockCustomInstructionsService.setAgentInstructionUris([uri]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(1);
expect(instrItems[0].groupKey).toBe('agent-instructions');
Expand All @@ -316,7 +317,7 @@ describe('CopilotCLICustomizationProvider', () => {
mockPromptsService.setInstructions([]);
mockCustomInstructionsService.setAgentInstructionUris([agentsUri, claudeUri, copilotUri]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(3);
expect(instrItems.every(i => i.groupKey === 'agent-instructions')).toBe(true);
Expand Down Expand Up @@ -345,7 +346,7 @@ describe('CopilotCLICustomizationProvider', () => {
mockPromptsService.setInstructions([]);
mockCustomInstructionsService.setAgentInstructionUris([]);

const items = await testProvider.provideChatSessionCustomizations(undefined!);
const items = await testProvider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(2);
expect(instrItems.every(i => i.groupKey === 'agent-instructions')).toBe(true);
Expand All @@ -356,7 +357,7 @@ describe('CopilotCLICustomizationProvider', () => {
const uri = URI.file('/workspace/.github/style.instructions.md');
mockPromptsService.setInstructions([makeInstruction(uri, 'style instructions', 'src/**/*.ts')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(1);
expect(instrItems[0].groupKey).toBe('context-instructions');
Expand All @@ -368,7 +369,7 @@ describe('CopilotCLICustomizationProvider', () => {
const uri = URI.file('/workspace/.github/global.instructions.md');
mockPromptsService.setInstructions([makeInstruction(uri, 'global instructions', '**')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(1);
expect(instrItems[0].groupKey).toBe('context-instructions');
Expand All @@ -380,7 +381,7 @@ describe('CopilotCLICustomizationProvider', () => {
const uri = URI.file('/workspace/.github/refactor.instructions.md');
mockPromptsService.setInstructions([makeInstruction(uri, 'refactor instructions', undefined, 'Refactoring guidelines')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(1);
expect(instrItems[0].groupKey).toBe('on-demand-instructions');
Expand All @@ -392,7 +393,7 @@ describe('CopilotCLICustomizationProvider', () => {
const uri = URI.file('/workspace/.github/testing.instructions.md');
mockPromptsService.setInstructions([makeInstruction(uri, 'testing instructions', '**/*.spec.ts', 'Testing standards')]);

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(1);
expect(instrItems[0].description).toBe('Testing standards');
Expand All @@ -408,7 +409,7 @@ describe('CopilotCLICustomizationProvider', () => {
mockPromptsService.setFileContent(contextUri, '---\napplyTo: \'src/**\'\n---\nStyle rules.');
mockPromptsService.setFileContent(onDemandUri, '---\ndescription: Refactoring\n---\nRefactor tips.');

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(3);

Expand All @@ -431,7 +432,7 @@ describe('CopilotCLICustomizationProvider', () => {
mockPromptsService.setInstructions([makeInstruction(uri, 'plain instructions', undefined)]);
mockPromptsService.setFileContent(uri, 'Just plain text, no frontmatter.');

const items = await provider.provideChatSessionCustomizations(undefined!);
const items = await provider.provideChatSessionCustomizations(testSessionResource, undefined!);
const instrItems = items.filter(i => i.type === FakeChatSessionCustomizationType.Instructions);
expect(instrItems).toHaveLength(1);
expect(instrItems[0].groupKey).toBe('on-demand-instructions');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class ClaudeCustomizationProvider extends Disposable implements vscode.Ch
this._register(this.workspaceService.onDidChangeWorkspaceFolders(() => this._onDidChange.fire()));
}

async provideChatSessionCustomizations(token: vscode.CancellationToken): Promise<vscode.ChatSessionCustomizationItem[]> {
async provideChatSessionCustomizations(_sessionResource: vscode.Uri, token: vscode.CancellationToken): Promise<vscode.ChatSessionCustomizationItem[]> {
const items: vscode.ChatSessionCustomizationItem[] = [];

// Agents: hybrid approach — file-based .claude/ agents merged with SDK-provided agents.
Expand Down
Loading
Loading