Skip to content

chat: remove providerApi.enabled setting and legacy code paths (re-land with fix)#308545

Closed
joshspicer wants to merge 2 commits intomainfrom
copilot/efficient-gerbil
Closed

chat: remove providerApi.enabled setting and legacy code paths (re-land with fix)#308545
joshspicer wants to merge 2 commits intomainfrom
copilot/efficient-gerbil

Conversation

@joshspicer
Copy link
Copy Markdown
Member

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.enabled setting and all legacy core discovery code paths (~500 lines). However, the Local (VSCode) harness had no itemProvider, and no extension registers a customization provider for session type 'vscode' (extensions register 'claude-code' and 'copilotcli'). This caused fetchItemsForSection() in the list widget to return [] — empty pages for all sections when the Local harness was selected.

The fix

Added a built-in itemProvider wrapping IPromptsService to the core CustomizationHarnessService, 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 (fetchProviderItemsForSectionfetchItemsFromProvider_inferStorageAndGroup) — no parallel discovery path restored.

Commits

  1. Cherry-pick of chat: remove providerApi.enabled setting and legacy code paths #308341 — re-applies the providerApi cleanup
  2. Fix — adds itemProvider to the VS Code core harness

Validation

  • ✅ TypeScript compilation clean (npm run compile-check-ts-native)
  • ✅ All 15 customizationHarnessService unit tests pass
  • ✅ Agent-browser visual confirmation: Chat Customizations editor shows items (Agents 15, Skills 24, Prompts 7, Hooks 1, MCP Servers 2, Plugins 1)

* 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>
Copilot AI review requested due to automatic review settings April 8, 2026 16:36
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Screenshot Changes

Base: 27a9b8ee Current: e06bbffe

Changed (37)

chat/aiCustomizations/aiCustomizationListWidget/InstructionsTabWithItems/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationListWidget/InstructionsTabWithItems/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/WelcomePage/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/WelcomePage/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/LocalHarness/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/LocalHarness/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/CliHarness/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/CliHarness/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/Sessions/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/Sessions/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/SessionsSkillsTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/SessionsSkillsTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/McpServersTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/McpServersTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/AgentsTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/AgentsTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/SkillsTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/SkillsTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/InstructionsTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/InstructionsTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/HooksTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/HooksTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PromptsTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PromptsTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PluginsTab/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PluginsTab/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PromptsTabScrolled/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PromptsTabScrolled/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/McpServersTabScrolled/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/McpServersTabScrolled/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PluginsTabScrolled/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/PluginsTabScrolled/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/McpServersTabNarrow/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/McpServersTabNarrow/Light
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/AgentsTabNarrow/Dark
Before After
before after
chat/aiCustomizations/aiCustomizationManagementEditor/AgentsTabNarrow/Light
Before After
before after
editor/inlineChatAffordance/InlineChatOverlay/Light
Before After
before after

Removed (2)

chat/aiCustomizations/aiCustomizationManagementEditor/ClaudeHarness/Dark

baseline

chat/aiCustomizations/aiCustomizationManagementEditor/ClaudeHarness/Light

baseline

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 itemProvider for 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 itemProvider uses listPromptFiles(...) and only forwards name (often falling back to basename) and omits important metadata used by the provider UI path (e.g. instruction pattern→badge/groupKey and parsed description). To keep the Local harness UI consistent with the previous core discovery behavior, consider sourcing:
  • instructions from promptsService.getInstructionFiles() (and map pattern to groupKey + badge/badgeTooltip),
  • prompts from promptsService.getPromptSlashCommands() (for parsed name/description),
    while still returning them as IExternalCustomizationItem objects.
				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-file URIs (the provider path explicitly supports this). For non-file schemes uri.fsPath may be empty or misleading, so these actions can end up copying an unusable path. Consider only showing these actions when item.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

Comment on lines +42 to +68
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;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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()];

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +72
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;
},
};
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
…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.
@joshspicer joshspicer force-pushed the copilot/efficient-gerbil branch from c291a2a to c17bc97 Compare April 8, 2026 18:44
@joshspicer joshspicer closed this Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants