Path: blob/main/src/vs/workbench/contrib/chat/browser/agentPluginsView.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../../base/browser/dom.js';6import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';7import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';8import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js';9import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js';10import { Action, IAction, Separator } from '../../../../base/common/actions.js';11import { RunOnceScheduler } from '../../../../base/common/async.js';12import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';13import { Codicon } from '../../../../base/common/codicons.js';14import { Event } from '../../../../base/common/event.js';15import { Disposable, DisposableStore, disposeIfDisposable, IDisposable, isDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';16import { ThemeIcon } from '../../../../base/common/themables.js';17import { autorun, derived, IObservable, IReaderWithStore } from '../../../../base/common/observable.js';18import { IPagedModel, PagedModel } from '../../../../base/common/paging.js';19import { dirname } from '../../../../base/common/resources.js';20import { localize, localize2 } from '../../../../nls.js';21import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';22import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';23import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';24import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';25import { IHoverService } from '../../../../platform/hover/browser/hover.js';26import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';27import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';28import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';29import { ILabelService } from '../../../../platform/label/common/label.js';30import { WorkbenchPagedList } from '../../../../platform/list/browser/listService.js';31import { IOpenerService } from '../../../../platform/opener/common/opener.js';32import { Registry } from '../../../../platform/registry/common/platform.js';33import { IThemeService } from '../../../../platform/theme/common/themeService.js';34import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPane.js';35import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';36import { IWorkbenchContribution } from '../../../common/contributions.js';37import { IViewDescriptorService, IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js';38import { IEditorService } from '../../../services/editor/common/editorService.js';39import { VIEW_CONTAINER } from '../../extensions/browser/extensions.contribution.js';40import { manageExtensionIcon } from '../../extensions/browser/extensionsIcons.js';41import { AbstractExtensionsListView } from '../../extensions/browser/extensionsViews.js';42import { DefaultViewsContext, extensionsFilterSubMenu, IExtensionsWorkbenchService, SearchAgentPluginsContext } from '../../extensions/common/extensions.js';43import { ChatContextKeys } from '../common/actions/chatContextKeys.js';44import { IAgentPlugin, IAgentPluginService } from '../common/plugins/agentPluginService.js';45import { isContributionEnabled } from '../common/enablement.js';46import { IPluginInstallService } from '../common/plugins/pluginInstallService.js';47import { hasSourceChanged, IMarketplacePlugin, IPluginMarketplaceService } from '../common/plugins/pluginMarketplaceService.js';48import { AgentPluginEditorInput } from './agentPluginEditor/agentPluginEditorInput.js';49import { AgentPluginItemKind, IAgentPluginItem, IInstalledPluginItem, IMarketplacePluginItem } from './agentPluginEditor/agentPluginItems.js';50import { getInstalledPluginContextMenuActions, InstallPluginAction, OpenPluginReadmeAction } from './agentPluginActions.js';51import { InstalledAgentPluginsViewId, HasInstalledAgentPluginsContext } from './chat.js';5253//#region Item model5455function installedPluginToItem(plugin: IAgentPlugin, labelService: ILabelService, outdated?: IObservable<IMarketplacePlugin | undefined>): IInstalledPluginItem {56const name = plugin.label;57const description = plugin.fromMarketplace?.description ?? labelService.getUriLabel(dirname(plugin.uri), { relative: true });58const marketplace = plugin.fromMarketplace?.marketplace;59return { kind: AgentPluginItemKind.Installed, name, description, marketplace, plugin, outdated };60}6162function marketplacePluginToItem(plugin: IMarketplacePlugin): IMarketplacePluginItem {63return {64kind: AgentPluginItemKind.Marketplace,65name: plugin.name,66description: plugin.description,67source: plugin.source,68sourceDescriptor: plugin.sourceDescriptor,69marketplace: plugin.marketplace,70marketplaceReference: plugin.marketplaceReference,71marketplaceType: plugin.marketplaceType,72readmeUri: plugin.readmeUri,73};74}7576//#endregion7778//#region Actions7980//#region Actions8182class UpdatePluginAction extends Action {83static readonly ID = 'agentPlugin.update';8485constructor(86private readonly plugin: IAgentPlugin,87private readonly liveMarketplacePlugin: IMarketplacePlugin,88@IPluginInstallService private readonly pluginInstallService: IPluginInstallService,89@IPluginMarketplaceService private readonly pluginMarketplaceService: IPluginMarketplaceService,90) {91super(UpdatePluginAction.ID, localize('update', "Update"), 'extension-action label prominent install');92}9394override async run(): Promise<void> {95if (await this.pluginInstallService.updatePlugin(this.liveMarketplacePlugin)) {96this.pluginMarketplaceService.addInstalledPlugin(this.plugin.uri, this.liveMarketplacePlugin);97}98}99}100101class ManagePluginAction extends Action {102static readonly ID = 'agentPlugin.manage';103static readonly CLASS = `extension-action icon manage ${ThemeIcon.asClassName(manageExtensionIcon)}`;104105private _actionViewItem: DropDownActionViewItem | null = null;106107constructor(108private readonly getActionGroups: () => IAction[][],109@IInstantiationService private readonly instantiationService: IInstantiationService,110) {111super(ManagePluginAction.ID, '', ManagePluginAction.CLASS, true);112this.tooltip = localize('manage', "Manage");113}114115createActionViewItem(options: IActionViewItemOptions): DropDownActionViewItem {116this._actionViewItem = this.instantiationService.createInstance(DropDownActionViewItem, this, options);117return this._actionViewItem;118}119120override async run(): Promise<void> {121this._actionViewItem?.showMenu(this.getActionGroups());122}123}124125class DropDownActionViewItem extends ActionViewItem {126constructor(127action: IAction,128options: IActionViewItemOptions,129@IContextMenuService private readonly contextMenuService: IContextMenuService,130) {131super(null, action, { ...options, icon: true, label: false });132}133134showMenu(actionGroups: IAction[][]): void {135if (!this.element) {136return;137}138const actions = actionGroups.flatMap(group => [...group, new Separator()]);139if (actions.length > 0) {140actions.pop();141}142const { left, top, height } = dom.getDomNodePagePosition(this.element);143this.contextMenuService.showContextMenu({144getAnchor: () => ({ x: left, y: top + height + 10 }),145getActions: () => actions,146onHide: () => disposeIfDisposable(actions),147});148}149}150151//#endregion152153//#region Renderer154155interface IAgentPluginTemplateData {156root: HTMLElement;157name: HTMLElement;158description: HTMLElement;159detail: HTMLElement;160actionbar: ActionBar;161disposables: IDisposable[];162elementDisposables: IDisposable[];163}164165class AgentPluginRenderer implements IPagedRenderer<IAgentPluginItem, IAgentPluginTemplateData> {166167static readonly templateId = 'agentPlugin';168readonly templateId = AgentPluginRenderer.templateId;169170constructor(171@IInstantiationService private readonly instantiationService: IInstantiationService,172) { }173174renderTemplate(root: HTMLElement): IAgentPluginTemplateData {175const element = dom.append(root, dom.$('.agent-plugin-item.extension-list-item'));176const details = dom.append(element, dom.$('.details'));177const headerContainer = dom.append(details, dom.$('.header-container'));178const header = dom.append(headerContainer, dom.$('.header'));179const name = dom.append(header, dom.$('span.name'));180const description = dom.append(details, dom.$('.description.ellipsis'));181const footer = dom.append(details, dom.$('.footer'));182const detailContainer = dom.append(footer, dom.$('.publisher-container'));183const detail = dom.append(detailContainer, dom.$('span.publisher-name'));184const actionbar = new ActionBar(footer, {185focusOnlyEnabledItems: true,186actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {187if (action instanceof ManagePluginAction) {188return action.createActionViewItem(options);189}190return undefined;191}192});193actionbar.setFocusable(false);194return { root, name, description, detail, actionbar, disposables: [actionbar], elementDisposables: [] };195}196197renderPlaceholder(_index: number, data: IAgentPluginTemplateData): void {198data.name.textContent = '';199data.description.textContent = '';200data.detail.textContent = '';201data.actionbar.clear();202this.disposeElement(undefined, 0, data);203}204205renderElement(element: IAgentPluginItem, _index: number, data: IAgentPluginTemplateData): void {206this.disposeElement(undefined, 0, data);207208data.name.textContent = element.name;209data.description.textContent = element.description;210211data.elementDisposables.push(autorun(reader => {212data.root.classList.toggle('disabled', element.kind === AgentPluginItemKind.Installed && !isContributionEnabled(element.plugin.enablement.read(reader)));213}));214215const updateActions = (reader: IReaderWithStore) => {216data.actionbar.clear();217if (element.kind === AgentPluginItemKind.Marketplace) {218data.detail.textContent = element.marketplace;219const installAction = this.instantiationService.createInstance(InstallPluginAction, element);220reader.store.add(installAction);221data.actionbar.push([installAction], { icon: true, label: true });222} else {223data.detail.textContent = element.marketplace ?? '';224const actions: Action[] = [];225const livePlugin = element.outdated?.read(reader);226if (livePlugin) {227const updateAction = this.instantiationService.createInstance(UpdatePluginAction, element.plugin, livePlugin);228reader.store.add(updateAction);229actions.push(updateAction);230}231const manageAction = this.instantiationService.createInstance(ManagePluginAction,232() => getInstalledPluginContextMenuActions(element.plugin, this.instantiationService));233reader.store.add(manageAction);234actions.push(manageAction);235data.actionbar.push(actions, { icon: true, label: true });236}237};238239data.elementDisposables.push(autorun(updateActions));240}241242disposeElement(_element: IAgentPluginItem | undefined, _index: number, data: IAgentPluginTemplateData): void {243for (const d of data.elementDisposables) {244d.dispose();245}246data.elementDisposables = [];247}248249disposeTemplate(data: IAgentPluginTemplateData): void {250for (const d of data.disposables) {251d.dispose();252}253this.disposeElement(undefined, 0, data);254}255}256257//#endregion258259//#region List View260261interface IAgentPluginsListViewOptions {262installedOnly?: boolean;263}264265export class AgentPluginsListView extends AbstractExtensionsListView<IAgentPluginItem> {266267private readonly actionStore = this._register(new DisposableStore());268private readonly queryCts = new MutableDisposable<CancellationTokenSource>();269private list: WorkbenchPagedList<IAgentPluginItem> | null = null;270private listContainer: HTMLElement | null = null;271private currentQuery = '@agentPlugins';272private readonly refreshOnPluginsChangedScheduler = this._register(new RunOnceScheduler(() => {273if (this.list) {274void this.show(this.currentQuery);275}276}, 0));277private bodyTemplate: {278messageContainer: HTMLElement;279messageBox: HTMLElement;280pluginsList: HTMLElement;281} | undefined;282283constructor(284private readonly listOptions: IAgentPluginsListViewOptions,285options: IViewletViewOptions,286@IKeybindingService keybindingService: IKeybindingService,287@IContextMenuService contextMenuService: IContextMenuService,288@IInstantiationService instantiationService: IInstantiationService,289@IThemeService themeService: IThemeService,290@IHoverService hoverService: IHoverService,291@IConfigurationService configurationService: IConfigurationService,292@IContextKeyService contextKeyService: IContextKeyService,293@IViewDescriptorService viewDescriptorService: IViewDescriptorService,294@IOpenerService openerService: IOpenerService,295@IAgentPluginService private readonly agentPluginService: IAgentPluginService,296@IPluginMarketplaceService private readonly pluginMarketplaceService: IPluginMarketplaceService,297@IPluginInstallService private readonly pluginInstallService: IPluginInstallService,298@ILabelService private readonly labelService: ILabelService,299@IEditorService private readonly editorService: IEditorService,300) {301super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);302303this._register(autorun(reader => {304const plugins = this.agentPluginService.plugins.read(reader);305for (const plugin of plugins) {306plugin.enablement.read(reader);307}308if (this.list && this.isBodyVisible()) {309this.refreshOnPluginsChangedScheduler.schedule();310}311}));312313this._register(this.pluginMarketplaceService.onDidChangeMarketplaces(() => {314if (this.list && this.isBodyVisible()) {315this.refreshOnPluginsChangedScheduler.schedule();316}317}));318}319320protected override renderBody(container: HTMLElement): void {321super.renderBody(container);322323const messageContainer = dom.append(container, dom.$('.message-container'));324const messageBox = dom.append(messageContainer, dom.$('.message'));325const pluginsList = dom.$('.agent-plugins-list');326327this.bodyTemplate = { pluginsList, messageBox, messageContainer };328329this.listContainer = dom.append(container, pluginsList);330this.list = this._register(this.instantiationService.createInstance(WorkbenchPagedList,331`${this.id}-Agent-Plugins`,332this.listContainer,333{334getHeight() { return 72; },335getTemplateId: () => AgentPluginRenderer.templateId,336},337[this.instantiationService.createInstance(AgentPluginRenderer)],338{339multipleSelectionSupport: false,340setRowLineHeight: false,341horizontalScrolling: false,342accessibilityProvider: {343getAriaLabel(item: IAgentPluginItem | null): string {344return item?.name ?? '';345},346getWidgetAriaLabel(): string {347return localize('agentPlugins', "Agent Plugins");348}349},350overrideStyles: getLocationBasedViewColors(this.viewDescriptorService.getViewLocationById(this.id)).listOverrideStyles,351}) as WorkbenchPagedList<IAgentPluginItem>);352353this._register(this.list.onContextMenu(e => this.onContextMenu(e), this));354355this._register(Event.debounce(Event.filter(this.list.onDidOpen, e => e.element !== null), (_, event) => event, 75, true)(options => {356this.editorService.openEditor(357this.instantiationService.createInstance(AgentPluginEditorInput, options.element!),358options.editorOptions359);360}));361}362363private onContextMenu(e: IListContextMenuEvent<IAgentPluginItem>): void {364if (!e.element) {365return;366}367368const actions = this.getContextMenuActions(e.element);369if (actions.length === 0) {370return;371}372373this.contextMenuService.showContextMenu({374getAnchor: () => e.anchor,375getActions: () => actions,376});377}378379private getContextMenuActions(item: IAgentPluginItem): IAction[] {380let actions: IAction[];381if (item.kind === AgentPluginItemKind.Installed) {382const groups = getInstalledPluginContextMenuActions(item.plugin, this.instantiationService);383actions = groups.flatMap(group => [...group, new Separator()]);384if (actions.length > 0) {385actions.pop();386}387} else {388actions = [];389if (item.readmeUri) {390actions.push(this.instantiationService.createInstance(OpenPluginReadmeAction, item.readmeUri));391}392actions.push(this.instantiationService.createInstance(InstallPluginAction, item));393}394395this.actionStore.clear();396for (const action of actions) {397if (isDisposable(action)) {398this.actionStore.add(action);399}400}401402return actions;403}404405protected override layoutBody(height: number, width: number): void {406super.layoutBody(height, width);407this.list?.layout(height, width);408}409410async show(query: string): Promise<IPagedModel<IAgentPluginItem>> {411this.currentQuery = query;412const stripped = query.replace(/@agentPlugins/i, '').trim();413const isRecommended = /^@recommended$/i.test(stripped);414const isInstalled = /(?:^|\s)@installed(?:\s|$)/i.test(stripped);415const text = isRecommended ? '' : stripped.replace(/(?:^|\s)@installed(?:\s|$)/gi, ' ').trim().toLowerCase();416417let installed = this.queryInstalled();418if (text) {419installed = installed.filter(p =>420p.name.toLowerCase().includes(text) ||421p.description.toLowerCase().includes(text) ||422(p.marketplace ?? '').toLowerCase().includes(text)423);424}425426// When @recommended, filter to plugins listed in workspace recommendations.427if (isRecommended) {428const recommended = this.pluginMarketplaceService.recommendedPlugins.get();429installed = installed.filter(p => {430const marketplace = p.plugin.fromMarketplace;431if (!marketplace) {432return false;433}434const key = `${marketplace.name}@${marketplace.marketplace}`;435return recommended.has(key);436});437}438439let items: IAgentPluginItem[] = installed;440441if (!this.listOptions.installedOnly && !isInstalled) {442const marketplacePlugins = await this.queryMarketplacePlugins();443let filteredMp = marketplacePlugins;444445if (isRecommended) {446// When @recommended, filter marketplace plugins to those in recommendations.447const recommended = this.pluginMarketplaceService.recommendedPlugins.get();448filteredMp = filteredMp.filter(p => {449const key = `${p.name}@${p.marketplace}`;450return recommended.has(key);451});452} else {453const lowerText = text.toLowerCase();454filteredMp = filteredMp.filter(p => p.name.toLowerCase().includes(lowerText) || p.description.toLowerCase().includes(lowerText) || p.marketplace.toLowerCase().includes(lowerText));455}456457const marketplace = filteredMp.map(marketplacePluginToItem);458459// Filter out marketplace items that are already installed460const installedPaths = new Set(installed.map(i => i.plugin.uri.toString()));461const filteredMarketplace = marketplace.filter(m => {462const expectedUri = this.pluginInstallService.getPluginInstallUri({463name: m.name,464description: m.description,465version: '',466source: m.source,467sourceDescriptor: m.sourceDescriptor,468marketplace: m.marketplace,469marketplaceReference: m.marketplaceReference,470marketplaceType: m.marketplaceType,471});472return !installedPaths.has(expectedUri.toString());473});474475items = [...installed, ...filteredMarketplace];476}477478const model = new PagedModel(items);479if (this.list) {480this.list.model = model;481}482this.updateBody(model.length);483return model;484}485486/**487* Builds the installed plugin list using only cached marketplace data488* (no IO). The cached data is populated by {@link fetchMarketplacePlugins}489* and exposed via the {@link IPluginMarketplaceService.lastFetchedPlugins}490* observable, which the view's autorun subscribes to for reactivity.491*/492private queryInstalled(): IInstalledPluginItem[] {493const marketplaceObs = derived(reader => {494const cachedMarketplace = this.pluginMarketplaceService.lastFetchedPlugins.read(reader);495const marketplaceByKey = new Map<string, IMarketplacePlugin>();496for (const mp of cachedMarketplace) {497marketplaceByKey.set(`${mp.marketplaceReference.canonicalId}::${mp.name}`, mp);498}499500501// Read fresh installed plugin metadata from the store (not from502// IAgentPlugin.fromMarketplace which may be stale after an update).503const installedByUri = new Map<string, IMarketplacePlugin>();504for (const entry of this.pluginMarketplaceService.installedPlugins.read(reader)) {505installedByUri.set(entry.pluginUri.toString(), entry.plugin);506}507508return { marketplaceByKey, installedByUri };509});510511512const plugins = this.agentPluginService.plugins.get();513return plugins.map(p => {514const isOutdated = derived(reader => {515const { marketplaceByKey, installedByUri } = marketplaceObs.read(reader);516const storedPlugin = installedByUri.get(p.uri.toString()) ?? p.fromMarketplace;517if (storedPlugin) {518const key = `${storedPlugin.marketplaceReference.canonicalId}::${storedPlugin.name}`;519const live = marketplaceByKey.get(key);520if (live && hasSourceChanged(storedPlugin.sourceDescriptor, live.sourceDescriptor)) {521return live;522}523}524525return undefined;526});527return installedPluginToItem(p, this.labelService, isOutdated);528});529}530531private async queryMarketplacePlugins(): Promise<IMarketplacePlugin[]> {532this.queryCts.value?.cancel();533const cts = new CancellationTokenSource();534this.queryCts.value = cts;535536try {537return await this.pluginMarketplaceService.fetchMarketplacePlugins(cts.token);538} catch {539return [];540}541}542543private updateBody(count: number): void {544if (this.bodyTemplate) {545this.bodyTemplate.pluginsList.classList.toggle('hidden', count === 0);546this.bodyTemplate.messageContainer.classList.toggle('hidden', count > 0);547if (count === 0 && this.isBodyVisible()) {548this.bodyTemplate.messageBox.textContent = localize('noAgentPlugins', "No agent plugins found.");549}550}551}552}553554//#endregion555556//#region Browse command557558class AgentPluginsBrowseCommand extends Action2 {559constructor() {560super({561id: 'workbench.agentPlugins.browse',562title: localize2('agentPlugins.browse', "Agent Plugins"),563tooltip: localize2('agentPlugins.browse.tooltip', "Browse Agent Plugins"),564icon: Codicon.search,565precondition: ContextKeyExpr.and(ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate()),566menu: [{567id: extensionsFilterSubMenu,568group: '1_predefined',569order: 2,570when: ContextKeyExpr.and(ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate()),571}, {572id: MenuId.ViewTitle,573when: ContextKeyExpr.and(ContextKeyExpr.equals('view', InstalledAgentPluginsViewId), ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate()),574group: 'navigation',575}],576});577}578579async run(accessor: ServicesAccessor) {580accessor.get(IExtensionsWorkbenchService).openSearch('@agentPlugins ');581}582}583584class CheckForPluginUpdatesCommand extends Action2 {585constructor() {586super({587id: 'workbench.agentPlugins.checkForUpdates',588title: localize2('agentPlugins.checkForUpdates', "Update Plugins"),589category: localize2('chat.category', "Chat"),590precondition: ChatContextKeys.enabled,591f1: true,592});593}594595async run(accessor: ServicesAccessor) {596await accessor.get(IPluginInstallService).updateAllPlugins({}, CancellationToken.None);597}598}599600class ForceUpdatePluginsCommand extends Action2 {601constructor() {602super({603id: 'workbench.agentPlugins.forceUpdate',604title: localize2('agentPlugins.forceUpdate', "Update Plugins (Force)"),605category: localize2('chat.category', "Chat"),606precondition: ChatContextKeys.enabled,607f1: true,608});609}610611async run(accessor: ServicesAccessor) {612await accessor.get(IPluginInstallService).updateAllPlugins({ force: true }, CancellationToken.None);613}614}615616//#endregion617//#region Views contribution618619export class AgentPluginsViewsContribution extends Disposable implements IWorkbenchContribution {620621static ID = 'workbench.chat.agentPlugins.views.contribution';622623constructor(624@IContextKeyService contextKeyService: IContextKeyService,625@IAgentPluginService agentPluginService: IAgentPluginService,626) {627super();628629const hasInstalledKey = HasInstalledAgentPluginsContext.bindTo(contextKeyService);630this._register(autorun(reader => {631hasInstalledKey.set(agentPluginService.plugins.read(reader).length > 0);632}));633634registerAction2(AgentPluginsBrowseCommand);635registerAction2(CheckForPluginUpdatesCommand);636registerAction2(ForceUpdatePluginsCommand);637638Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([639{640id: InstalledAgentPluginsViewId,641name: localize2('agent-plugins-installed', "Agent Plugins - Installed"),642ctorDescriptor: new SyncDescriptor(AgentPluginsListView, [{ installedOnly: true }]),643when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledAgentPluginsContext, ChatContextKeys.Setup.hidden.negate()),644weight: 30,645order: 5,646canToggleVisibility: true,647},648{649id: 'workbench.views.agentPlugins.default.marketplace',650name: localize2('agent-plugins', "Agent Plugins"),651ctorDescriptor: new SyncDescriptor(AgentPluginsListView, [{}]),652when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledAgentPluginsContext.toNegated(), ChatContextKeys.Setup.hidden.negate()),653weight: 30,654order: 5,655canToggleVisibility: true,656hideByDefault: true,657},658{659id: 'workbench.views.agentPlugins.marketplace',660name: localize2('agent-plugins', "Agent Plugins"),661ctorDescriptor: new SyncDescriptor(AgentPluginsListView, [{}]),662when: ContextKeyExpr.and(SearchAgentPluginsContext, ChatContextKeys.Setup.hidden.negate()),663},664], VIEW_CONTAINER);665}666}667668//#endregion669670671