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
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ interface IInstalledDiscoveryItem {
readonly itemId?: string;
readonly removable?: boolean;
readonly mcpServerId?: string;
readonly mcpConnectorName?: string;
readonly disabled?: boolean;
readonly catalogResource?: ICustomizationMarketplaceResource;
readonly promptDetail?: IAICustomizationListItem;
Expand Down Expand Up @@ -391,6 +392,7 @@ class DiscoveryResultRenderer implements IListRenderer<DiscoveryListEntry, IDisc
const type = installed ? element.type : getCatalogType(element.resource);
const resource = installed ? element.catalogResource : element.resource;
const marketplaceState = installed && element.catalogResource ? this.getInstallState(element.catalogResource) : undefined;
const opensInstalled = installed && (!marketplaceState || marketplaceState.kind === 'installed');
const installationDetail = marketplaceState && marketplaceState.kind !== 'installed' ? getInstallationStateLabel(marketplaceState, isCopilotConnectorResource(resource)) : undefined;
const detail = [
type ? getTypeLabel(type) : !installed ? element.resource.mediaType : undefined,
Expand All @@ -412,8 +414,7 @@ class DiscoveryResultRenderer implements IListRenderer<DiscoveryListEntry, IDisc
templateData.name.removeAttribute('rel');
templateData.detail.textContent = detail;
templateData.description.textContent = description;
const hasInstalledDetail = installed && !!(element.promptDetail || element.pluginDetail || element.mcpDetail);
templateData.primaryAction.setAttribute('aria-label', installed && (hasInstalledDetail || !element.catalogResource)
templateData.primaryAction.setAttribute('aria-label', opensInstalled
? localize('customizationDiscovery.openInstalled', "Open installed customization {0}", name)
: localize('customizationDiscovery.openDetails', "View details for {0}", name));
templateData.elementDisposables.add(this.hoverService.setupDelayedHover(templateData.name, { content: name }));
Expand All @@ -425,16 +426,10 @@ class DiscoveryResultRenderer implements IListRenderer<DiscoveryListEntry, IDisc
open();
}));
};
if (element.kind === 'installed') {
registerOpenListeners(() => {
if (element.promptDetail || element.pluginDetail || element.mcpDetail || !element.catalogResource) {
this.onOpenInstalled(element);
return;
}
this.onOpenDetails(element.catalogResource);
});
if (element.kind === 'installed' && opensInstalled) {
registerOpenListeners(() => this.onOpenInstalled(element));
} else {
registerOpenListeners(() => this.onOpenDetails(element.resource));
registerOpenListeners(() => this.onOpenDetails(element.kind === 'installed' ? element.catalogResource! : element.resource));
}

if (!installed) {
Expand Down Expand Up @@ -743,10 +738,11 @@ export class AICustomizationDiscoveryPage extends Disposable implements IAICusto
));
this._register(this.resultList.onDidOpen(event => {
if (event.element?.kind === 'installed') {
if (event.element.promptDetail || event.element.pluginDetail || event.element.mcpDetail || !event.element.catalogResource) {
const resource = event.element.catalogResource;
if (!resource || this.getInstallState(resource).kind === 'installed') {
this.openInstalledItem(event.element);
} else {
this.openMarketplaceItem(event.element.catalogResource, 'search');
this.openMarketplaceItem(resource, 'search');
}
} else if (event.element?.kind === 'available') {
this.openMarketplaceItem(event.element.resource, 'search');
Expand Down Expand Up @@ -1436,6 +1432,9 @@ export class AICustomizationDiscoveryPage extends Disposable implements IAICusto
type,
section: getSectionForCatalogType(type),
catalogResource: resource,
uri: state.target.kind === 'skill' || state.target.kind === 'plugin' ? state.target.uri : undefined,
mcpServerId: state.target.kind === 'mcp' ? state.target.id : undefined,
mcpConnectorName: state.target.kind === 'copilotConnector' ? state.target.name : undefined,
});
}
} else {
Expand Down Expand Up @@ -1579,13 +1578,16 @@ export class AICustomizationDiscoveryPage extends Disposable implements IAICusto
const card = DOM.append(parent, $('.customization-discovery-card'));
const resourceKey = getCustomizationMarketplaceResourceKey(item);
card.dataset.resourceKey = resourceKey;
const state = this.getInstallState(item);
const primaryAction = createCustomizationCardPrimaryAction(
card,
localize('customizationDiscovery.openDetails', "View details for {0}", item.displayName),
state.kind === 'installed'
? localize('customizationDiscovery.openInstalled', "Open installed customization {0}", item.displayName)
: localize('customizationDiscovery.openDetails', "View details for {0}", item.displayName),
'customization-discovery-card-primary',
);
this.browsePrimaryActions.set(resourceKey, primaryAction);
this.browseDisposables.add(DOM.addDisposableListener(primaryAction, DOM.EventType.CLICK, () => this.openMarketplaceItem(item, 'browse')));
this.browseDisposables.add(DOM.addDisposableListener(primaryAction, DOM.EventType.CLICK, () => this.openCatalogItem(item, 'browse')));
const icon = DOM.append(primaryAction, $('.customization-discovery-card-icon'));
const type = getCatalogType(item);
renderCustomizationMarketplaceIcon(
Expand All @@ -1609,7 +1611,6 @@ export class AICustomizationDiscoveryPage extends Disposable implements IAICusto
this.browseDisposables.add(this.hoverService.setupDelayedHover(name, { content: item.displayName }));
this.browseDisposables.add(this.hoverService.setupDelayedHover(description, { content: item.description }));
const actions = DOM.append(card, $('.customization-discovery-card-actions'));
const state = this.getInstallState(item);
const setupUrl = state.kind === 'unavailable' ? state.setupUrl : undefined;
const installError = this.installErrors.get(getCustomizationMarketplaceResourceKey(item));
const cancellable = isCancellableConnectorOperation(item, state);
Expand Down Expand Up @@ -1867,10 +1868,29 @@ export class AICustomizationDiscoveryPage extends Disposable implements IAICusto
});
}

private openCatalogItem(resource: ICustomizationMarketplaceResource, mode: ICustomizationMarketplaceOrigin['mode']): void {
const type = getCatalogType(resource);
const state = this.getInstallState(resource);
if (type && state.kind === 'installed' && this.callbacks.openInstalled) {
this.callbacks.openInstalled({
section: getSectionForCatalogType(type),
name: resource.displayName,
uri: state.target.kind === 'skill' || state.target.kind === 'plugin' ? state.target.uri : undefined,
mcpServerId: state.target.kind === 'mcp' ? state.target.id : undefined,
mcpConnectorName: state.target.kind === 'copilotConnector' ? state.target.name : undefined,
});
return;
}
this.openMarketplaceItem(resource, mode);
}

private openInstalledItem(item: IInstalledDiscoveryItem): void {
this.callbacks.openInstalled?.({
section: item.section,
name: item.name,
uri: item.uri,
mcpServerId: item.mcpServerId,
mcpConnectorName: item.mcpConnectorName,
promptDetail: item.promptDetail,
pluginDetail: item.pluginDetail,
mcpDetail: item.mcpDetail,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ import { IAgentHostCustomizationService } from '../agentSessions/agentHost/agent
import { EmbeddedExtensionToolsDetail } from './embeddedExtensionToolsDetail.js';
import { ICustomizationHarnessService, type ICustomizationSourceFolder } from '../../common/customizationHarnessService.js';
import { ChatConfiguration } from '../../common/constants.js';
import { AICustomizationWelcomePage, type ICustomizationMarketplaceOrigin, type ICustomizationMigrationCategorySummary } from './aiCustomizationWelcomePage.js';
import { AICustomizationWelcomePage, type ICustomizationMarketplaceOrigin, type ICustomizationMigrationCategorySummary, type IInstalledCustomizationTarget } from './aiCustomizationWelcomePage.js';
import { ICustomizationMarketplaceInstallService } from '../../common/customizationMarketplaceInstallService.js';
import { type CustomizationMigrationTargetFolders, type IMigratedCustomizationsWithFailureReasonsResult, migrateCustomizations, resolveWorkspaceMigrationTargetFolder } from './customizationMigration.js';
import { CUSTOMIZATION_MIGRATION_CATEGORIES, CustomizationMigrationCategoryId, getCustomizationMigrationCategory, homepageMigrationCategories, type ICustomizationMigrationCategory } from './customizationMigrationCategories.js';
Expand Down Expand Up @@ -343,6 +343,7 @@ export class AICustomizationManagementEditor extends EditorPane {
private sectionsList!: WorkbenchList<ISectionItem>;
private contentContainer!: HTMLElement;
private listWidget!: AICustomizationListWidget;
private listWidgetSectionLoad: Promise<void> = Promise.resolve();
private mcpListWidget: McpListWidget | undefined;
private pluginListWidget: PluginListWidget | undefined;
private modelsWidget: ChatModelsWidget | undefined;
Expand Down Expand Up @@ -954,19 +955,7 @@ export class AICustomizationManagementEditor extends EditorPane {
{
selectSection: (section) => this.selectSection(section),
selectSectionWithMarketplace: (section) => this.selectSection(section, { showMarketplace: true }),
openInstalled: target => {
const origin: CustomizationDetailBaseOrigin = { kind: 'discover' };
this.selectSection(target.section);
if (target.promptDetail) {
void this.openCustomizationItem(target.promptDetail, origin);
} else if (target.pluginDetail) {
void this.showEmbeddedPluginDetail(target.pluginDetail, origin);
} else if (target.mcpDetail) {
void this.showEmbeddedMcpDetail(target.mcpDetail, origin);
} else if (target.uri) {
void this.revealCustomizationByUri(target.uri);
}
},
openInstalled: target => void this.revealInstalledCustomization(target),
openMarketplaceItem: (resource, origin) => {
this.showMarketplaceDetail(resource, origin);
},
Expand Down Expand Up @@ -3067,8 +3056,9 @@ export class AICustomizationManagementEditor extends EditorPane {
this.updateContentVisibility();

// Load items for the new section (only for prompts-based sections)
this.listWidgetSectionLoad = Promise.resolve();
if (this.isPromptsSection(section)) {
void this.listWidget.setSection(section);
this.listWidgetSectionLoad = this.listWidget.setSection(section);
}

// Re-layout after visibility change so the newly-visible widget can
Expand Down Expand Up @@ -3154,6 +3144,12 @@ export class AICustomizationManagementEditor extends EditorPane {
const welcomeVisible = isWelcome && !isEditorMode && !isMigrationMode && !isDetailMode;
this.welcomePage.container.style.display = welcomeVisible ? '' : 'none';
this.welcomePage.setVisible(this.isVisible() && welcomeVisible);
this.homeButton?.classList.toggle('selected', welcomeVisible);
if (welcomeVisible) {
this.homeButton?.setAttribute('aria-current', 'page');
} else {
this.homeButton?.removeAttribute('aria-current');
}
}
if (this.promptsContentContainer) {
this.promptsContentContainer.style.display = !isEditorMode && !isMigrationMode && !isDetailMode && isPromptsSection ? '' : 'none';
Expand Down Expand Up @@ -3681,6 +3677,26 @@ export class AICustomizationManagementEditor extends EditorPane {
}
}

public async revealInstalledCustomization(target: IInstalledCustomizationTarget): Promise<void> {
this.selectSection(target.section);
await this.listWidgetSectionLoad;
if (this.isPromptsSection(target.section) && target.uri) {
await this.revealCustomizationByUri(target.uri);
return;
}
for (let attempt = 0; attempt < 10; attempt++) {
const revealed = target.section === AICustomizationManagementSection.Plugins && target.uri
? await this.pluginListWidget?.revealAndSelectItemByUri(target.uri)
: target.section === AICustomizationManagementSection.McpServers
? this.mcpListWidget?.revealAndSelectServer(target.mcpServerId, target.name, target.mcpConnectorName)
: true;
if (revealed !== false) {
return;
}
await timeout(100);
}
}

/**
* Scrolls the active list widget so the last item is visible.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,7 @@ export interface ICustomizationMigrationCategorySummary {
export interface IWelcomePageCallbacks {
selectSection(section: AICustomizationManagementSection): void;
selectSectionWithMarketplace(section: AICustomizationManagementSection): void;
openInstalled?(target: {
readonly section: AICustomizationManagementSection;
readonly uri?: URI;
readonly promptDetail?: IAICustomizationListItem;
readonly pluginDetail?: IAgentPluginItem;
readonly mcpDetail?: IMcpServerDetailInput;
}): void;
openInstalled?(target: IInstalledCustomizationTarget): void;
openMarketplaceItem(resource: ICustomizationMarketplaceResource, origin: ICustomizationMarketplaceOrigin): void;
closeEditor(): void;
reviewMigrations(): void;
Expand All @@ -58,6 +52,17 @@ export interface IWelcomePageCallbacks {
prefillChat(query: string, options?: { isPartialQuery?: boolean; newChat?: boolean }): void;
}

export interface IInstalledCustomizationTarget {
readonly section: AICustomizationManagementSection;
readonly name: string;
readonly uri?: URI;
readonly mcpServerId?: string;
readonly mcpConnectorName?: string;
readonly promptDetail?: IAICustomizationListItem;
readonly pluginDetail?: IAgentPluginItem;
readonly mcpDetail?: IMcpServerDetailInput;
}
Comment thread
houghj16 marked this conversation as resolved.

export interface ICustomizationMarketplaceOrigin {
readonly resourceKey: string;
readonly mode: 'browse' | 'search';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3179,6 +3179,52 @@ export class McpListWidget extends Disposable {
}
}

revealAndSelectServer(serverId: string | undefined, serverName: string, connectorName?: string): boolean {
if (this.searchQuery) {
this.searchInput.value = '';
this.searchQuery = '';
this.delayedFilter.cancel();
void this.filterServers();
return false;
}
const entry = this.getVisibleMcpEntries().find(entry => {
if (connectorName) {
return entry.type === 'builtin-item' && entry.connector?.connector.name === connectorName;
}
if (serverId) {
switch (entry.type) {
case 'server-item':
case 'session-server-item':
return entry.server.id === serverId;
case 'builtin-item':
return entry.id === serverId || entry.activeSessionServer?.id === serverId;
case 'group-header':
case 'marketplace-item':
return false;
}
}
switch (entry.type) {
case 'server-item':
return entry.server.label === serverName;
case 'session-server-item':
return entry.server.name === serverName;
case 'builtin-item':
return entry.label === serverName || entry.connector?.connector.displayName === serverName;
case 'group-header':
case 'marketplace-item':
return false;
}
});
if (!entry) {
return false;
}
this.list.reveal(entry);
this.list.setFocus([entry]);
this.list.setSelection([entry]);
this.list.domFocus();
return true;
}

/**
* Focuses the list.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@
background-color: var(--vscode-list-hoverBackground);
}

.ai-customization-management-editor .sidebar-home-button.selected {
background-color: var(--vscode-list-inactiveSelectionBackground);
color: var(--vscode-list-inactiveSelectionForeground);
outline: var(--vscode-strokeThickness) solid var(--vscode-contrastActiveBorder, transparent);
outline-offset: -1px;
}

.ai-customization-management-editor .sidebar-home-button:focus-visible {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: -1px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2226,6 +2226,30 @@ export class PluginListWidget extends Disposable {
}
}

async revealAndSelectItemByUri(uri: URI): Promise<boolean> {
if (this.browseMode) {
this.toggleBrowseMode(false);
}
if (this.searchQuery) {
this.searchInput.value = '';
this.searchQuery = '';
this.delayedFilter.cancel();
}
await this.filterPlugins();
const entry = this.getVisiblePluginEntries().find(entry =>
entry.type === 'plugin-item' && isEqual(entry.item.plugin.uri, uri)
|| entry.type === 'remote-item' && isEqual(entry.item.uri, uri)
);
if (!entry) {
return false;
}
this.list.reveal(entry);
this.list.setFocus([entry]);
this.list.setSelection([entry]);
this.list.domFocus();
return true;
}

focus(): void {
if (this.cardScrollableNode.style.display !== 'none') {
if (this.firstCardFocusElement) {
Expand Down
Loading
Loading