fix: customization provider API rendering bugs and sessions window isolation#307745
fix: customization provider API rendering bugs and sessions window isolation#307745joshspicer wants to merge 5 commits intomainfrom
Conversation
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.
There was a problem hiding this comment.
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-establishonDidChangesubscription). - 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: [] }, |
There was a problem hiding this comment.
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.
| { 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: [] }, |
| // 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: [] }, |
There was a problem hiding this comment.
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.
Fixes several bugs in the Chat Customizations UI when using the
ChatSessionCustomizationProviderAPI (chat.customizations.providerApi.enabled: true).Fixes
1. Race condition in
loadItems()— sequence counterMultiple concurrent
loadItems()calls overlap when autoruns fire simultaneously (harness registers →availableHarnessesfires, thenactiveHarnessfires, thensetSectionfires). Without serialization, a slow earlier call (core path) can resolve after the correct provider-path call and overwriteallItemswith empty results. A sequence counter ensures only the latest call's result is applied.2. Missing
onDidChangeInstructionssubscriptionThe widget subscribed to
onDidChangeCustomAgents,onDidChangeSlashCommands, andonDidChangeSkillsbut notonDidChangeInstructions. Instruction file discovery completing after the initial load never triggered a widget refresh.3. Provider
onDidChangeautorun not re-establishedThe autorun that subscribes to
itemProvider.onDidChangeonly readactiveHarness. If the harness ID was persisted from a previous session,activeHarnessnever changed when the CLI harness registered, so the subscription was never set up. Now also readsavailableHarnesses.4. Instruction items dropped in
filterItemsForProviderfilterItemsForProvideronly had storage-based groups (local,user,extension,builtin). Provider-supplied instruction items have semanticgroupKeyvalues (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
SessionsCustomizationHarnessServiceand theremoteAgentHostcontribution. Extension-contributed harnesses via the provider API should not be registered in the sessions window.