Skip to content
This repository was archived by the owner on May 20, 2026. It is now read-only.

Commit b85db26

Browse files
committed
feat: wire plugins through CopilotCLI customization provider
Consume the new chat.plugins API and plumb it through to the CopilotCLI customization provider: - Mirror updated chatPromptFiles d.ts with ChatPluginResource - Add plugins/onDidChangePlugins to IChatPromptFileService interface - Wire vscode.chat.plugins in ChatPromptFileService implementation - Add Plugins to CopilotCLI provider supportedTypes (unhides section) - Add getPluginItems() to CopilotCLI provider - Subscribe to onDidChangePlugins for provider invalidation - Fix all test mocks for new interface member - Add plugin tests (type, name, combined items, onDidChange) Depends on: microsoft/vscode josh/plugins-chatpromptfiles-api
1 parent e56be92 commit b85db26

8 files changed

Lines changed: 88 additions & 4 deletions

File tree

src/extension/chatSessions/common/chatPromptFileService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,14 @@ export interface IChatPromptFileService extends IDisposable {
5050
* from all sources (workspace, user, and extension-provided).
5151
*/
5252
readonly skills: readonly ChatResource[];
53+
54+
/**
55+
* An event that fires when the list of {@link plugins plugins} changes.
56+
*/
57+
readonly onDidChangePlugins: Event<void>;
58+
59+
/**
60+
* The list of currently installed agent plugins.
61+
*/
62+
readonly plugins: readonly ChatResource[];
5363
}

src/extension/chatSessions/copilotcli/node/test/copilotCLISkills.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ class TestChatPromptFileService extends Disposable implements IChatPromptFileSer
3434
readonly onDidChangeCustomAgents: Event<void> = Event.None;
3535
readonly onDidChangeInstructions: Event<void> = Event.None;
3636
readonly onDidChangeSkills: Event<void> = Event.None;
37+
readonly onDidChangePlugins: Event<void> = Event.None;
3738
readonly customAgents: readonly ChatResource[] = [];
3839
readonly customAgentPromptFiles: readonly ParsedPromptFile[] = [];
3940
readonly instructions: readonly ChatResource[] = [];
4041
skills: readonly ChatResource[] = [];
42+
readonly plugins: readonly ChatResource[] = [];
4143
}
4244

4345
function createWorkspaceService(folders: URI[] = [URI.file('/workspace')]): IWorkspaceService {

src/extension/chatSessions/copilotcli/node/test/copilotCliAgents.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ class TestChatPromptFileService extends Disposable implements IChatPromptFileSer
4949
readonly onDidChangeCustomAgents: Event<void> = this._onDidChangeCustomAgents.event;
5050
readonly onDidChangeInstructions: Event<void> = Event.None;
5151
readonly onDidChangeSkills: Event<void> = Event.None;
52+
readonly onDidChangePlugins: Event<void> = Event.None;
5253
readonly customAgents: readonly import('vscode').ChatResource[] = [];
5354
readonly instructions: readonly import('vscode').ChatResource[] = [];
5455
readonly skills: readonly import('vscode').ChatResource[] = [];
56+
readonly plugins: readonly import('vscode').ChatResource[] = [];
5557

5658
constructor(private _customAgentPromptFiles: ParsedPromptFile[] = []) {
5759
super();

src/extension/chatSessions/copilotcli/vscode-node/test/testHelpers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,11 @@ export class MockChatPromptFileService extends Disposable implements IChatPrompt
237237
customAgents: ChatResource[] = [];
238238
instructions: ChatResource[] = [];
239239
skills: ChatResource[] = [];
240+
plugins: ChatResource[] = [];
240241
private readonly _onDidChangeCustomAgents = this._register(new Emitter<void>());
241242
private readonly _onDidChangeInstructions = this._register(new Emitter<void>());
242243
private readonly _onDidChangeSkills = this._register(new Emitter<void>());
244+
private readonly _onDidChangePlugins = this._register(new Emitter<void>());
243245

244246
get onDidChangeCustomAgents() {
245247
return this._onDidChangeCustomAgents.event;
@@ -252,6 +254,10 @@ export class MockChatPromptFileService extends Disposable implements IChatPrompt
252254
get onDidChangeSkills() {
253255
return this._onDidChangeSkills.event;
254256
}
257+
258+
get onDidChangePlugins() {
259+
return this._onDidChangePlugins.event;
260+
}
255261
get customAgentPromptFiles() {
256262
return [];
257263
}

src/extension/chatSessions/vscode-node/chatPromptFileService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export class ChatPromptFileService extends Disposable implements IChatPromptFile
2222
readonly onDidChangeInstructions: Event<void> = this._onDidChangeInstructions.event;
2323
private readonly _onDidChangeSkills = this._register(new Emitter<void>());
2424
readonly onDidChangeSkills: Event<void> = this._onDidChangeSkills.event;
25+
private readonly _onDidChangePlugins = this._register(new Emitter<void>());
26+
readonly onDidChangePlugins: Event<void> = this._onDidChangePlugins.event;
2527

2628
private _customAgents: ParsedPromptFile[] = [];
2729
private refreshCts: CancellationTokenSource | undefined;
@@ -43,6 +45,10 @@ export class ChatPromptFileService extends Disposable implements IChatPromptFile
4345
this._register(vscode.chat.onDidChangeSkills(() => {
4446
this._onDidChangeSkills.fire();
4547
}));
48+
49+
this._register(vscode.chat.onDidChangePlugins(() => {
50+
this._onDidChangePlugins.fire();
51+
}));
4652
this.triggerRefreshCustomAgents();
4753
}
4854

@@ -62,6 +68,10 @@ export class ChatPromptFileService extends Disposable implements IChatPromptFile
6268
return vscode.chat.skills;
6369
}
6470

71+
get plugins(): readonly vscode.ChatResource[] {
72+
return vscode.chat.plugins;
73+
}
74+
6575
override dispose(): void {
6676
this.refreshCts?.dispose(true);
6777
this.refreshCts = undefined;

src/extension/chatSessions/vscode-node/copilotCLICustomizationProvider.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class CopilotCLICustomizationProvider extends Disposable implements vscod
2525
vscode.ChatSessionCustomizationType.Agent,
2626
vscode.ChatSessionCustomizationType.Skill,
2727
vscode.ChatSessionCustomizationType.Instructions,
28+
vscode.ChatSessionCustomizationType.Plugins,
2829
],
2930
};
3031
}
@@ -39,19 +40,22 @@ export class CopilotCLICustomizationProvider extends Disposable implements vscod
3940
this._register(this.chatPromptFileService.onDidChangeCustomAgents(() => this._onDidChange.fire()));
4041
this._register(this.chatPromptFileService.onDidChangeInstructions(() => this._onDidChange.fire()));
4142
this._register(this.chatPromptFileService.onDidChangeSkills(() => this._onDidChange.fire()));
43+
this._register(this.chatPromptFileService.onDidChangePlugins(() => this._onDidChange.fire()));
4244
this._register(this.copilotCLIAgents.onDidChangeAgents(() => this._onDidChange.fire()));
4345
}
4446

4547
async provideChatSessionCustomizations(_token: vscode.CancellationToken): Promise<vscode.ChatSessionCustomizationItem[]> {
4648
const agents = await this.getAgentItems();
4749
const instructions = this.getInstructionItems();
4850
const skills = this.getSkillItems();
51+
const plugins = this.getPluginItems();
4952

5053
this.logService.debug(`[CopilotCLICustomizationProvider] agents (${agents.length}): ${agents.map(a => a.name).join(', ') || '(none)'}`);
5154
this.logService.debug(`[CopilotCLICustomizationProvider] instructions (${instructions.length}): ${instructions.map(i => i.name).join(', ') || '(none)'}`);
5255
this.logService.debug(`[CopilotCLICustomizationProvider] skills (${skills.length}): ${skills.map(s => s.name).join(', ') || '(none)'}`);
56+
this.logService.debug(`[CopilotCLICustomizationProvider] plugins (${plugins.length}): ${plugins.map(p => p.name).join(', ') || '(none)'}`);
5357

54-
const items = [...agents, ...instructions, ...skills];
58+
const items = [...agents, ...instructions, ...skills, ...plugins];
5559
this.logService.debug(`[CopilotCLICustomizationProvider] total: ${items.length} items`);
5660
return items;
5761
}
@@ -91,6 +95,17 @@ export class CopilotCLICustomizationProvider extends Disposable implements vscod
9195
name: deriveNameFromUri(s.uri, SKILL_FILENAME),
9296
}));
9397
}
98+
99+
/**
100+
* Collects all plugin items from the prompt file service.
101+
*/
102+
private getPluginItems(): vscode.ChatSessionCustomizationItem[] {
103+
return this.chatPromptFileService.plugins.map(p => ({
104+
uri: p.uri,
105+
type: vscode.ChatSessionCustomizationType.Plugins,
106+
name: basename(p.uri),
107+
}));
108+
}
94109
}
95110

96111
function deriveNameFromUri(uri: vscode.Uri, extensionOrFilename: string): string {

src/extension/chatSessions/vscode-node/test/copilotCLICustomizationProvider.spec.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class FakeChatSessionCustomizationType {
2121
static readonly Instructions = new FakeChatSessionCustomizationType('instructions');
2222
static readonly Prompt = new FakeChatSessionCustomizationType('prompt');
2323
static readonly Hook = new FakeChatSessionCustomizationType('hook');
24+
static readonly Plugins = new FakeChatSessionCustomizationType('plugins');
2425
constructor(readonly id: string) { }
2526
}
2627

@@ -58,27 +59,34 @@ class MockChatPromptFileService extends mock<IChatPromptFileService>() {
5859
override readonly onDidChangeInstructions = this._onDidChangeInstructions.event;
5960
private readonly _onDidChangeSkills = new Emitter<void>();
6061
override readonly onDidChangeSkills = this._onDidChangeSkills.event;
62+
private readonly _onDidChangePlugins = new Emitter<void>();
63+
override readonly onDidChangePlugins = this._onDidChangePlugins.event;
6164

6265
private _customAgents: vscode.ChatResource[] = [];
6366
private _instructions: vscode.ChatResource[] = [];
6467
private _skills: vscode.ChatResource[] = [];
68+
private _plugins: vscode.ChatResource[] = [];
6569

6670
override get customAgents(): readonly vscode.ChatResource[] { return this._customAgents; }
6771
override get instructions(): readonly vscode.ChatResource[] { return this._instructions; }
6872
override get skills(): readonly vscode.ChatResource[] { return this._skills; }
73+
override get plugins(): readonly vscode.ChatResource[] { return this._plugins; }
6974

7075
setCustomAgents(agents: vscode.ChatResource[]) { this._customAgents = agents; }
7176
setInstructions(instructions: vscode.ChatResource[]) { this._instructions = instructions; }
7277
setSkills(skills: vscode.ChatResource[]) { this._skills = skills; }
78+
setPlugins(plugins: vscode.ChatResource[]) { this._plugins = plugins; }
7379

7480
fireCustomAgentsChanged() { this._onDidChangeCustomAgents.fire(); }
7581
fireInstructionsChanged() { this._onDidChangeInstructions.fire(); }
7682
fireSkillsChanged() { this._onDidChangeSkills.fire(); }
83+
firePluginsChanged() { this._onDidChangePlugins.fire(); }
7784

7885
override dispose() {
7986
this._onDidChangeCustomAgents.dispose();
8087
this._onDidChangeInstructions.dispose();
8188
this._onDidChangeSkills.dispose();
89+
this._onDidChangePlugins.dispose();
8290
}
8391
}
8492

@@ -130,13 +138,14 @@ describe('CopilotCLICustomizationProvider', () => {
130138
expect(CopilotCLICustomizationProvider.metadata.iconId).toBe('worktree');
131139
});
132140

133-
it('supports Agent, Skill, and Instructions types', () => {
141+
it('supports Agent, Skill, Instructions, and Plugins types', () => {
134142
const supported = CopilotCLICustomizationProvider.metadata.supportedTypes;
135143
expect(supported).toBeDefined();
136-
expect(supported).toHaveLength(3);
144+
expect(supported).toHaveLength(4);
137145
expect(supported).toContain(FakeChatSessionCustomizationType.Agent);
138146
expect(supported).toContain(FakeChatSessionCustomizationType.Skill);
139147
expect(supported).toContain(FakeChatSessionCustomizationType.Instructions);
148+
expect(supported).toContain(FakeChatSessionCustomizationType.Plugins);
140149
});
141150

142151
it('only returns items whose type is in supportedTypes', async () => {
@@ -240,9 +249,21 @@ describe('CopilotCLICustomizationProvider', () => {
240249
mockCopilotCLIAgents.setAgents([makeAgentInfo('explore', 'Explore')]);
241250
mockPromptFileService.setInstructions([{ uri: URI.file('/workspace/.github/b.instructions.md') }]);
242251
mockPromptFileService.setSkills([{ uri: URI.file('/workspace/.github/skills/c/SKILL.md') }]);
252+
mockPromptFileService.setPlugins([{ uri: URI.file('/workspace/.copilot/plugins/my-plugin') }]);
243253

244254
const items = await provider.provideChatSessionCustomizations(undefined!);
245-
expect(items).toHaveLength(3);
255+
expect(items).toHaveLength(4);
256+
});
257+
258+
it('returns plugins with correct type and name derived from URI', async () => {
259+
const uri = URI.file('/workspace/.copilot/plugins/lint-rules');
260+
mockPromptFileService.setPlugins([{ uri }]);
261+
262+
const items = await provider.provideChatSessionCustomizations(undefined!);
263+
expect(items).toHaveLength(1);
264+
expect(items[0].uri).toEqual(uri);
265+
expect(items[0].type).toBe(FakeChatSessionCustomizationType.Plugins);
266+
expect(items[0].name).toBe('lint-rules');
246267
});
247268
});
248269

@@ -271,6 +292,14 @@ describe('CopilotCLICustomizationProvider', () => {
271292
expect(fired).toBe(true);
272293
});
273294

295+
it('fires when plugins change', () => {
296+
let fired = false;
297+
disposables.add(provider.onDidChange(() => { fired = true; }));
298+
299+
mockPromptFileService.firePluginsChanged();
300+
expect(fired).toBe(true);
301+
});
302+
274303
it('fires when ICopilotCLIAgents agents change', () => {
275304
let fired = false;
276305
disposables.add(provider.onDidChange(() => { fired = true; }));

src/extension/vscode.proposed.chatPromptFiles.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ declare module 'vscode' {
136136
*/
137137
export const skills: readonly ChatResource[];
138138

139+
/**
140+
* An event that fires when the list of {@link plugins plugins} changes.
141+
*/
142+
export const onDidChangePlugins: Event<void>;
143+
144+
/**
145+
* The list of currently installed agent plugins.
146+
*/
147+
export const plugins: readonly ChatResource[];
148+
139149
/**
140150
* Register a provider for custom agents.
141151
* @param provider The custom agent provider.

0 commit comments

Comments
 (0)