Skip to content

fix: customization provider API rendering bugs and sessions window isolation#307745

Open
joshspicer wants to merge 5 commits intomainfrom
josh/customization-provider-fixes
Open

fix: customization provider API rendering bugs and sessions window isolation#307745
joshspicer wants to merge 5 commits intomainfrom
josh/customization-provider-fixes

Conversation

@joshspicer
Copy link
Copy Markdown
Member

Fixes several bugs in the Chat Customizations UI when using the ChatSessionCustomizationProvider API (chat.customizations.providerApi.enabled: true).

Fixes

1. Race condition in loadItems() — sequence counter

Multiple concurrent loadItems() calls overlap when autoruns fire simultaneously (harness registers → availableHarnesses fires, then activeHarness fires, then setSection fires). Without serialization, a slow earlier call (core path) can resolve after the correct provider-path call and overwrite allItems with empty results. A sequence counter ensures only the latest call's result is applied.

2. Missing onDidChangeInstructions subscription

The widget subscribed to onDidChangeCustomAgents, onDidChangeSlashCommands, and onDidChangeSkills but not onDidChangeInstructions. Instruction file discovery completing after the initial load never triggered a widget refresh.

3. Provider onDidChange autorun not re-established

The autorun that subscribes to itemProvider.onDidChange only read activeHarness. If the harness ID was persisted from a previous session, activeHarness never changed when the CLI harness registered, so the subscription was never set up. Now also reads availableHarnesses.

4. Instruction items dropped in filterItemsForProvider

filterItemsForProvider only had storage-based groups (local, user, extension, builtin). Provider-supplied instruction items have semantic groupKey values (context-instructions, on-demand-instructions, agent-instructions) which didn't match any group — causing all instruction items to be silently dropped (allItems: 0). This was a regression from #307226.

5. Sessions window ignores provider API

The sessions window manages its own harnesses via SessionsCustomizationHarnessService and the remoteAgentHost contribution. Extension-contributed harnesses via the provider API should not be registered in the sessions window.

Multiple concurrent loadItems() calls can overlap when autoruns fire
simultaneously. Without serialization, a slow earlier call can resolve
after the correct one and overwrite allItems with stale/empty results.
The sequence counter ensures only the latest call's result is applied.
The list widget subscribed to onDidChangeCustomAgents,
onDidChangeSlashCommands, and onDidChangeSkills but not
onDidChangeInstructions. This meant instruction file discovery
completing after the initial load never triggered a widget refresh.
The autorun that subscribes to itemProvider.onDidChange only read
activeHarness. If the harness ID was persisted from a previous session,
activeHarness never changed when the CLI harness registered, so the
subscription was never set up. Now also reads availableHarnesses to
re-fire when harnesses are added/removed.
filterItemsForProvider only had storage-based groups (local, user,
extension, builtin). Provider-supplied instruction items have semantic
groupKey values like 'context-instructions' and 'on-demand-instructions'
which didn't match any group, causing all instruction items to be
silently dropped (allItems: 0). Add instruction-semantic groups when
the current section is Instructions, matching filterItemsForCore.
The sessions window manages its own harnesses via
SessionsCustomizationHarnessService and the remoteAgentHost
contribution. Extension-contributed harnesses via the provider API
should not be registered in the sessions window.
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 fixes multiple rendering/refresh issues in the AI Customizations UI when using the ChatSessionCustomizationProvider API, and prevents provider API registrations from impacting the Sessions window.

Changes:

  • Prevent stale concurrent loadItems() results from overwriting newer results via a sequence counter.
  • Ensure the customization list refreshes when instruction discovery completes (onDidChangeInstructions) and when new provider harnesses register (re-establish onDidChange subscription).
  • Fix instruction provider grouping so semantic instruction groupKeys are routed into the correct collapsible headers; ignore provider registrations in the Sessions window.
Show a summary per file
File Description
src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationListWidget.ts Fixes list refresh races, adds missing instruction change subscription, re-establishes provider onDidChange subscription, and updates provider grouping for instruction items.
src/vs/workbench/api/browser/mainThreadChatAgents2.ts Prevents customization provider API registration from being applied in Sessions windows by early-returning when isSessionsWindow is true.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

{ groupKey: 'context-instructions', label: localize('contextInstructionsGroup', "Included Based on Context"), icon: instructionsIcon, description: localize('contextInstructionsGroupDescription', "Instructions automatically loaded when matching files are part of the context."), items: [] },
{ groupKey: 'on-demand-instructions', label: localize('onDemandInstructionsGroup', "Loaded on Demand"), icon: instructionsIcon, description: localize('onDemandInstructionsGroupDescription', "Instructions loaded only when explicitly referenced."), items: [] },
{ groupKey: PromptsStorage.local, label: localize('workspaceGroup', "Workspace"), icon: workspaceIcon, description: localize('workspaceGroupDescription', "Customizations stored as files in your project folder and shared with your team via version control."), items: [] },
{ groupKey: PromptsStorage.user, label: localize('userGroup', "User"), icon: userIcon, description: localize('userGroupDescription', "Customizations stored locally on your machine in a central location. Private to you and available across all projects."), items: [] },
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

In the Instructions provider layout, the predefined groups list does not include PromptsStorage.extension (or PromptsStorage.plugin). If a provider returns instruction items with groupKey set to one of those storage keys, groups.find(...) will return undefined and the item will be silently dropped. Consider adding the missing storage group(s) here (or adding a fallback/“Other” group) so provider items aren’t lost when they use storage-based group keys.

Suggested change
{ groupKey: PromptsStorage.user, label: localize('userGroup', "User"), icon: userIcon, description: localize('userGroupDescription', "Customizations stored locally on your machine in a central location. Private to you and available across all projects."), items: [] },
{ groupKey: PromptsStorage.user, label: localize('userGroup', "User"), icon: userIcon, description: localize('userGroupDescription', "Customizations stored locally on your machine in a central location. Private to you and available across all projects."), items: [] },
{ groupKey: PromptsStorage.extension, label: localize('extensionGroup', "Extensions"), icon: extensionIcon, description: localize('extensionGroupDescription', "Read-only customizations provided by installed extensions."), items: [] },
{ groupKey: 'plugin', label: localize('pluginGroup', "Plugins"), icon: pluginIcon, description: localize('pluginGroupDescription', "Read-only customizations provided by installed plugins."), items: [] },

Copilot uses AI. Check for mistakes.
Comment on lines +1949 to +1957
// Standard provider layout: group by inferred storage/groupKey.
// Instructions use semantic categories (matching core path) so
// that provider-supplied groupKeys like 'context-instructions'
// are routed to the correct collapsible header.
const groups: { groupKey: string; label: string; icon: ThemeIcon; description: string; items: IAICustomizationListItem[] }[] =
this.currentSection === AICustomizationManagementSection.Instructions
? [
{ groupKey: 'agent-instructions', label: localize('agentInstructionsGroup', "Agent Instructions"), icon: instructionsIcon, description: localize('agentInstructionsGroupDescription', "Instruction files automatically loaded for all agent interactions (e.g. AGENTS.md, CLAUDE.md, copilot-instructions.md)."), items: [] },
{ groupKey: 'context-instructions', label: localize('contextInstructionsGroup', "Included Based on Context"), icon: instructionsIcon, description: localize('contextInstructionsGroupDescription', "Instructions automatically loaded when matching files are part of the context."), items: [] },
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

There doesn’t appear to be any automated coverage for the external-provider path in AICustomizationListWidget (e.g. fetchItemsFromProvider + filterItemsForProvider), including the instruction groupKey routing that previously regressed (#307226). Consider adding a small unit/fixture test that uses a mock itemProvider to return instruction items with semantic groupKeys (and optionally storage-based keys) and asserts they render into the expected group headers, to prevent future silent drops.

Copilot uses AI. Check for mistakes.
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.

3 participants