Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/vs/workbench/api/browser/mainThreadChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { ICustomizationHarnessService, IExternalCustomizationItem, IExternalCust
import { AICustomizationManagementSection, BUILTIN_STORAGE } from '../../contrib/chat/common/aiCustomizationWorkspaceService.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { IAgentPluginService } from '../../contrib/chat/common/plugins/agentPluginService.js';
import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';

interface AgentData {
dispose: () => void;
Expand Down Expand Up @@ -136,6 +137,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IAgentPluginService private readonly _agentPluginService: IAgentPluginService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2);
Expand Down Expand Up @@ -655,6 +657,11 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
}

async $registerChatSessionCustomizationProvider(handle: number, chatSessionType: string, metadata: IChatSessionCustomizationProviderMetadataDto, extensionId: ExtensionIdentifier): Promise<void> {
if (this._environmentService.isSessionsWindow) {
this._logService.trace(`[MainThreadChatAgents2] Sessions window does not use the customization provider API, ignoring registration from ${extensionId.value}`);
return;
}

if (!this._configurationService.getValue<boolean>('chat.customizations.providerApi.enabled')) {
this._logService.trace(`[MainThreadChatAgents2] Customization provider API is disabled, ignoring registration from ${extensionId.value}`);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ export class AICustomizationListWidget extends Disposable {
private searchQuery: string = '';
private readonly collapsedGroups = new Set<string>();
private readonly dropdownActionDisposables = this._register(new DisposableStore());
private _loadItemsSeq = 0;

private readonly delayedFilter = new Delayer<void>(200);

Expand Down Expand Up @@ -646,11 +647,16 @@ export class AICustomizationListWidget extends Disposable {
this.refresh();
}));

// Subscribe to the active provider's onDidChange event
// Subscribe to the active provider's onDidChange event.
// Read both activeHarness and availableHarnesses so that the
// subscription is re-established when a new provider harness
// registers (availableHarnesses changes) even if activeHarness
// was already set to the harness id from persisted state.
const providerChangeDisposable = this._register(new MutableDisposable());
const syncChangeDisposable = this._register(new MutableDisposable());
this._register(autorun(reader => {
this.harnessService.activeHarness.read(reader);
this.harnessService.availableHarnesses.read(reader);
const activeDescriptor = this.harnessService.getActiveDescriptor();
if (activeDescriptor.itemProvider) {
providerChangeDisposable.value = activeDescriptor.itemProvider.onDidChange(() => this.refresh());
Expand Down Expand Up @@ -778,6 +784,7 @@ export class AICustomizationListWidget extends Disposable {
this._register(this.promptsService.onDidChangeCustomAgents(() => this.refresh()));
this._register(this.promptsService.onDidChangeSlashCommands(() => this.refresh()));
this._register(this.promptsService.onDidChangeSkills(() => this.refresh()));
this._register(this.promptsService.onDidChangeInstructions(() => this.refresh()));

// Refresh on file deletions so the list updates after inline delete actions
this._register(this.fileService.onDidFilesChange(e => {
Expand Down Expand Up @@ -1159,9 +1166,12 @@ export class AICustomizationListWidget extends Disposable {

/**
* Loads items for the current section.
* Uses a sequence counter so that stale results from concurrent
* calls (e.g. overlapping autorun refreshes) are discarded.
*/
private async loadItems(): Promise<void> {
const section = this.currentSection;
const seq = ++this._loadItemsSeq;
let items: IAICustomizationListItem[];
try {
items = await this.fetchItemsForSection(section);
Expand All @@ -1170,8 +1180,8 @@ export class AICustomizationListWidget extends Disposable {
items = [];
}

if (this.currentSection !== section) {
return; // section changed while loading
if (this.currentSection !== section || this._loadItemsSeq !== seq) {
return; // section changed or a newer load started while loading
}

this.allItems = items;
Expand Down Expand Up @@ -1936,13 +1946,26 @@ export class AICustomizationListWidget extends Disposable {

this.displayEntries = entries;
} else {
// Standard provider layout: group by inferred storage/groupKey
const groups: { groupKey: string; label: string; icon: ThemeIcon; description: string; items: IAICustomizationListItem[] }[] = [
{ 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: [] },
{ groupKey: PromptsStorage.extension, label: localize('extensionGroup', "Extensions"), icon: extensionIcon, description: localize('extensionGroupDescription', "Read-only customizations provided by installed extensions."), items: [] },
{ groupKey: BUILTIN_STORAGE, label: localize('builtinGroup', "Built-in"), icon: builtinIcon, description: localize('builtinGroupDescription', "Built-in customizations shipped with the application."), 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: [] },
Comment thread
joshspicer marked this conversation as resolved.
{ 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: [] },
Comment thread
joshspicer marked this conversation as resolved.
{ groupKey: BUILTIN_STORAGE, label: localize('builtinGroup', "Built-in"), icon: builtinIcon, description: localize('builtinGroupDescription', "Built-in customizations shipped with the application."), 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: [] },
{ groupKey: PromptsStorage.extension, label: localize('extensionGroup', "Extensions"), icon: extensionIcon, description: localize('extensionGroupDescription', "Read-only customizations provided by installed extensions."), items: [] },
{ groupKey: BUILTIN_STORAGE, label: localize('builtinGroup', "Built-in"), icon: builtinIcon, description: localize('builtinGroupDescription', "Built-in customizations shipped with the application."), items: [] },
];

for (const item of matchedItems) {
const key = item.groupKey ?? item.storage ?? PromptsStorage.local;
Expand Down
Loading