chat: remove providerApi.enabled setting and legacy code paths (re-land with fix)#308545
chat: remove providerApi.enabled setting and legacy code paths (re-land with fix)#308545joshspicer wants to merge 2 commits intomainfrom
Conversation
* chat: remove providerApi.enabled setting and legacy code paths The provider API is now permanently enabled. Remove the chat.customizations.providerApi.enabled setting, the false-path conditional that registered built-in CLI/Claude harnesses in core, the entire fetchCoreItemsForSection legacy item-loading pipeline, and related dead code (filterItemsForCore, applyBuiltinGroupKeys, storageToIcon, findPluginUri, isBuiltin/extensionLabel properties, kill-switch listener in mainThreadChatAgents2). CLI harness code is preserved for the sessions window. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: fix chat customizations editor empty by adding built-in itemProvider After removing fetchCoreItemsForSection (the IPromptsService fallback path), the sessions CLI harness had no itemProvider, causing the management editor to return [] for all sections while sidebar counts remained correct. Fix: add a built-in IExternalCustomizationItemProvider to the CLI harness in SessionsCustomizationHarnessService that wraps IPromptsService directly. This uses the same provider API path as core VS Code, keeping the architecture consistent. Also extends CustomizationHarnessServiceBase with Disposable so subclasses can register disposables via _register. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: dispose CustomizationHarnessServiceBase in tests after extending Disposable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Screenshot ChangesBase: Changed (37)Removed (2) |
There was a problem hiding this comment.
Pull request overview
This PR re-lands the removal of the chat.customizations.providerApi.enabled setting and associated legacy discovery paths, and fixes the regression where the Local (VS Code) harness showed empty customization sections by adding a built-in itemProvider backed by IPromptsService.
Changes:
- Removes the provider API kill-switch setting and cleans up legacy/Claude harness code paths.
- Adds a built-in
itemProviderfor the core VS Code harness (and sessions CLI harness) so the provider-based UI path has items without requiring an extension provider. - Updates docs, fixtures, and tests to reflect the streamlined harness model and improved disposal.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationManagementEditor.fixture.ts | Removes Claude harness fixture wiring from UI fixtures. |
| src/vs/workbench/contrib/chat/test/common/customizationHarnessService.test.ts | Ensures harness service instances are disposed in tests. |
| src/vs/workbench/contrib/chat/common/customizationHarnessService.ts | Removes Claude harness support and makes the base harness service disposable. |
| src/vs/workbench/contrib/chat/common/constants.ts | Removes chat.customizations.providerApi.enabled from configuration constants. |
| src/vs/workbench/contrib/chat/browser/chat.contribution.ts | Removes the configuration registration for the provider API kill-switch. |
| src/vs/workbench/contrib/chat/browser/aiCustomization/customizationHarnessService.ts | Adds Local harness itemProvider backed by IPromptsService. |
| src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationListWidget.ts | Removes legacy core discovery path; list now relies on provider-backed harnesses. |
| src/vs/workbench/api/browser/mainThreadChatAgents2.ts | Removes kill-switch-based provider registration guards/cleanup. |
| src/vs/sessions/contrib/chat/browser/customizationHarnessService.ts | Adds sessions CLI harness itemProvider backed by IPromptsService. |
| src/vs/sessions/AI_CUSTOMIZATIONS.md | Updates sessions docs to match the new harness model (no Claude, no kill-switch). |
Copilot's findings
Comments suppressed due to low confidence (2)
src/vs/workbench/contrib/chat/browser/aiCustomization/customizationHarnessService.ts:66
- For instructions, prompts, and hooks the built-in
itemProvideruseslistPromptFiles(...)and only forwardsname(often falling back to basename) and omits important metadata used by the provider UI path (e.g. instructionpattern→badge/groupKey and parseddescription). To keep the Local harness UI consistent with the previous core discovery behavior, consider sourcing:
- instructions from
promptsService.getInstructionFiles()(and mappatterntogroupKey+badge/badgeTooltip), - prompts from
promptsService.getPromptSlashCommands()(for parsed name/description),
while still returning them asIExternalCustomizationItemobjects.
const [agents, skills, instructions, prompts, hooks] = await Promise.all([
promptsService.getCustomAgents(token),
promptsService.findAgentSkills(token),
promptsService.listPromptFiles(PromptsType.instructions, token),
promptsService.listPromptFiles(PromptsType.prompt, token),
promptsService.listPromptFiles(PromptsType.hook, token),
]);
const items: IExternalCustomizationItem[] = [];
for (const agent of agents ?? []) {
items.push({ uri: agent.uri, type: PromptsType.agent, name: agent.name, description: agent.description });
}
for (const skill of skills ?? []) {
items.push({ uri: skill.uri, type: PromptsType.skill, name: skill.name, description: skill.description });
}
for (const file of instructions) {
items.push({ uri: file.uri, type: PromptsType.instructions, name: file.name ?? basename(file.uri.path) });
}
for (const file of prompts) {
items.push({ uri: file.uri, type: PromptsType.prompt, name: file.name ?? basename(file.uri.path) });
}
for (const file of hooks) {
items.push({ uri: file.uri, type: PromptsType.hook, name: file.name ?? basename(file.uri.path) });
}
src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationListWidget.ts:846
- The list context menu always adds “Copy Full Path/Relative Path”, but items can have non-
fileURIs (the provider path explicitly supports this). For non-file schemesuri.fsPathmay be empty or misleading, so these actions can end up copying an unusable path. Consider only showing these actions whenitem.uri.scheme === Schemas.file, or change the actions to copy the full URI string for non-file schemes.
// Add copy path actions
const copyActions = [
new Separator(),
new Action('copyFullPath', localize('copyFullPath', "Copy Full Path"), undefined, true, async () => {
await this.clipboardService.writeText(item.uri.fsPath);
}),
new Action('copyRelativePath', localize('copyRelativePath', "Copy Relative Path"), undefined, true, async () => {
const basePath = this.workspaceService.getActiveProjectRoot();
if (basePath && item.uri.fsPath.startsWith(basePath.fsPath)) {
const relative = item.uri.fsPath.substring(basePath.fsPath.length + 1);
await this.clipboardService.writeText(relative);
} else {
// Fallback to workspace-relative via label service
const relativePath = this.labelService.getUriLabel(item.uri, { relative: true });
await this.clipboardService.writeText(relativePath);
}
}),
];
- Files reviewed: 10/10 changed files
- Comments generated: 2
| const [agents, skills, instructions, prompts, hooks] = await Promise.all([ | ||
| promptsService.getCustomAgents(token), | ||
| promptsService.findAgentSkills(token), | ||
| promptsService.listPromptFiles(PromptsType.instructions, token), | ||
| promptsService.listPromptFiles(PromptsType.prompt, token), | ||
| promptsService.listPromptFiles(PromptsType.hook, token), | ||
| ]); | ||
|
|
||
| const items: IExternalCustomizationItem[] = []; | ||
|
|
||
| for (const agent of agents ?? []) { | ||
| items.push({ uri: agent.uri, type: PromptsType.agent, name: agent.name, description: agent.description }); | ||
| } | ||
| for (const skill of skills ?? []) { | ||
| items.push({ uri: skill.uri, type: PromptsType.skill, name: skill.name, description: skill.description }); | ||
| } | ||
| for (const file of instructions) { | ||
| items.push({ uri: file.uri, type: PromptsType.instructions, name: file.name ?? basename(file.uri.path) }); | ||
| } | ||
| for (const file of prompts) { | ||
| items.push({ uri: file.uri, type: PromptsType.prompt, name: file.name ?? basename(file.uri.path) }); | ||
| } | ||
| for (const file of hooks) { | ||
| items.push({ uri: file.uri, type: PromptsType.hook, name: file.name ?? basename(file.uri.path) }); | ||
| } | ||
|
|
||
| return items; |
There was a problem hiding this comment.
The new built-in itemProvider only returns enabled items (e.g. getCustomAgents()/findAgentSkills() skip disabled files) and never sets enabled: false. This means disabled agents/skills/instructions/prompts won’t be shown in the management UI and can’t be re-enabled from the list. Consider building the provider result from promptsService.listPromptFiles(...) + getDisabledPromptFiles(...) (and/or getDiscoveryInfo(...)) so disabled items are included with enabled: false, while still enriching names/descriptions for enabled items via the parsed APIs.
This issue also appears on line 42 of the same file.
| const [agents, skills, instructions, prompts, hooks] = await Promise.all([ | |
| promptsService.getCustomAgents(token), | |
| promptsService.findAgentSkills(token), | |
| promptsService.listPromptFiles(PromptsType.instructions, token), | |
| promptsService.listPromptFiles(PromptsType.prompt, token), | |
| promptsService.listPromptFiles(PromptsType.hook, token), | |
| ]); | |
| const items: IExternalCustomizationItem[] = []; | |
| for (const agent of agents ?? []) { | |
| items.push({ uri: agent.uri, type: PromptsType.agent, name: agent.name, description: agent.description }); | |
| } | |
| for (const skill of skills ?? []) { | |
| items.push({ uri: skill.uri, type: PromptsType.skill, name: skill.name, description: skill.description }); | |
| } | |
| for (const file of instructions) { | |
| items.push({ uri: file.uri, type: PromptsType.instructions, name: file.name ?? basename(file.uri.path) }); | |
| } | |
| for (const file of prompts) { | |
| items.push({ uri: file.uri, type: PromptsType.prompt, name: file.name ?? basename(file.uri.path) }); | |
| } | |
| for (const file of hooks) { | |
| items.push({ uri: file.uri, type: PromptsType.hook, name: file.name ?? basename(file.uri.path) }); | |
| } | |
| return items; | |
| const [ | |
| agents, | |
| skills, | |
| agentFiles, | |
| disabledAgentFiles, | |
| skillFiles, | |
| disabledSkillFiles, | |
| instructions, | |
| disabledInstructions, | |
| prompts, | |
| disabledPrompts, | |
| hooks, | |
| disabledHooks, | |
| ] = await Promise.all([ | |
| promptsService.getCustomAgents(token), | |
| promptsService.findAgentSkills(token), | |
| promptsService.listPromptFiles(PromptsType.agent, token), | |
| promptsService.getDisabledPromptFiles(PromptsType.agent, token), | |
| promptsService.listPromptFiles(PromptsType.skill, token), | |
| promptsService.getDisabledPromptFiles(PromptsType.skill, token), | |
| promptsService.listPromptFiles(PromptsType.instructions, token), | |
| promptsService.getDisabledPromptFiles(PromptsType.instructions, token), | |
| promptsService.listPromptFiles(PromptsType.prompt, token), | |
| promptsService.getDisabledPromptFiles(PromptsType.prompt, token), | |
| promptsService.listPromptFiles(PromptsType.hook, token), | |
| promptsService.getDisabledPromptFiles(PromptsType.hook, token), | |
| ]); | |
| const items = new Map<string, IExternalCustomizationItem>(); | |
| const keyFor = (type: PromptsType, uri: { toString(): string }) => `${type}:${uri.toString()}`; | |
| const addPromptFileItems = ( | |
| type: PromptsType, | |
| files: ReadonlyArray<{ uri: { path: string; toString(): string }; name?: string }>, | |
| enabled: boolean, | |
| ) => { | |
| for (const file of files) { | |
| items.set(keyFor(type, file.uri), { | |
| uri: file.uri, | |
| type, | |
| name: file.name ?? basename(file.uri.path), | |
| enabled, | |
| }); | |
| } | |
| }; | |
| addPromptFileItems(PromptsType.agent, disabledAgentFiles, false); | |
| addPromptFileItems(PromptsType.agent, agentFiles, true); | |
| addPromptFileItems(PromptsType.skill, disabledSkillFiles, false); | |
| addPromptFileItems(PromptsType.skill, skillFiles, true); | |
| addPromptFileItems(PromptsType.instructions, disabledInstructions, false); | |
| addPromptFileItems(PromptsType.instructions, instructions, true); | |
| addPromptFileItems(PromptsType.prompt, disabledPrompts, false); | |
| addPromptFileItems(PromptsType.prompt, prompts, true); | |
| addPromptFileItems(PromptsType.hook, disabledHooks, false); | |
| addPromptFileItems(PromptsType.hook, hooks, true); | |
| for (const agent of agents ?? []) { | |
| items.set(keyFor(PromptsType.agent, agent.uri), { | |
| uri: agent.uri, | |
| type: PromptsType.agent, | |
| name: agent.name, | |
| description: agent.description, | |
| enabled: true, | |
| }); | |
| } | |
| for (const skill of skills ?? []) { | |
| items.set(keyFor(PromptsType.skill, skill.uri), { | |
| uri: skill.uri, | |
| type: PromptsType.skill, | |
| name: skill.name, | |
| description: skill.description, | |
| enabled: true, | |
| }); | |
| } | |
| return [...items.values()]; |
| const itemProvider: IExternalCustomizationItemProvider = { | ||
| onDidChange: emitter.event, | ||
| async provideChatSessionCustomizations(token: CancellationToken): Promise<IExternalCustomizationItem[] | undefined> { | ||
| const [agents, skills, instructions, prompts, hooks] = await Promise.all([ | ||
| promptsService.getCustomAgents(token), | ||
| promptsService.findAgentSkills(token), | ||
| promptsService.listPromptFiles(PromptsType.instructions, token), | ||
| promptsService.listPromptFiles(PromptsType.prompt, token), | ||
| promptsService.listPromptFiles(PromptsType.hook, token), | ||
| ]); | ||
|
|
||
| const items: IExternalCustomizationItem[] = []; | ||
|
|
||
| for (const agent of agents ?? []) { | ||
| items.push({ uri: agent.uri, type: PromptsType.agent, name: agent.name, description: agent.description }); | ||
| } | ||
| for (const skill of skills ?? []) { | ||
| items.push({ uri: skill.uri, type: PromptsType.skill, name: skill.name, description: skill.description }); | ||
| } | ||
| for (const file of instructions) { | ||
| items.push({ uri: file.uri, type: PromptsType.instructions, name: file.name ?? basename(file.uri.path) }); | ||
| } | ||
| for (const file of prompts) { | ||
| items.push({ uri: file.uri, type: PromptsType.prompt, name: file.name ?? basename(file.uri.path) }); | ||
| } | ||
| for (const file of hooks) { | ||
| items.push({ uri: file.uri, type: PromptsType.hook, name: file.name ?? basename(file.uri.path) }); | ||
| } | ||
|
|
||
| return items; | ||
| }, | ||
| }; |
There was a problem hiding this comment.
The sessions window adds the same built-in itemProvider behavior as core, and it has the same issue: disabled files are omitted (and enabled: false is never set), so disabled customizations won’t appear and can’t be re-enabled from the management list. Consider including all files via listPromptFiles(...) + getDisabledPromptFiles(...) and marking disabled ones with enabled: false.
da099f8 to
cc43826
Compare
d510258 to
c291a2a
Compare
…tions The cherry-picked PR (#308341) removed the promptsService-based core discovery path from the AI Customizations list widget, leaving only the provider-based path. The Local (VSCode) harness had no itemProvider, causing empty pages since no extension registers a provider for type 'vscode'. Add a built-in itemProvider wrapping IPromptsService to the core CustomizationHarnessService, matching the pattern already used by the Sessions CLI harness.
c291a2a to
c17bc97
Compare
Summary
Re-lands #308341 (reverted in #308448) with the fix for the empty Local harness page.
What happened
PR #308341 removed the
chat.customizations.providerApi.enabledsetting and all legacy core discovery code paths (~500 lines). However, the Local (VSCode) harness had noitemProvider, and no extension registers a customization provider for session type'vscode'(extensions register'claude-code'and'copilotcli'). This causedfetchItemsForSection()in the list widget to return[]— empty pages for all sections when the Local harness was selected.The fix
Added a built-in
itemProviderwrappingIPromptsServiceto the coreCustomizationHarnessService, matching the pattern already used by the Sessions CLI harness (added in the original PR's second commit). Items flow through the existing provider code path (fetchProviderItemsForSection→fetchItemsFromProvider→_inferStorageAndGroup) — no parallel discovery path restored.Commits
itemProviderto the VS Code core harnessValidation
npm run compile-check-ts-native)