@@ -8,22 +8,26 @@ import { IKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js';
88import { Button } from '../../../../../base/browser/ui/button/button.js' ;
99import { HighlightedLabel } from '../../../../../base/browser/ui/highlightedlabel/highlightedLabel.js' ;
1010import { InputBox } from '../../../../../base/browser/ui/inputbox/inputBox.js' ;
11- import { IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js' ;
11+ import { IListContextMenuEvent , IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js' ;
1212import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js' ;
1313import { Checkbox , TriStateCheckbox } from '../../../../../base/browser/ui/toggle/toggle.js' ;
14+ import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js' ;
15+ import { IAnchor } from '../../../../../base/browser/ui/contextview/contextview.js' ;
16+ import { Action } from '../../../../../base/common/actions.js' ;
1417import { Delayer } from '../../../../../base/common/async.js' ;
1518import { CancellationToken , CancellationTokenSource } from '../../../../../base/common/cancellation.js' ;
1619import { Codicon } from '../../../../../base/common/codicons.js' ;
1720import { Emitter } from '../../../../../base/common/event.js' ;
1821import { IMatch , matchesContiguousSubString } from '../../../../../base/common/filters.js' ;
1922import { KeyCode } from '../../../../../base/common/keyCodes.js' ;
2023import { Disposable , DisposableStore , toDisposable } from '../../../../../base/common/lifecycle.js' ;
21- import { autorun , derived , IObservable , IReader , observableValue } from '../../../../../base/common/observable.js' ;
24+ import { autorun , derived , IObservable , IReader , observableSignalFromEvent , observableValue } from '../../../../../base/common/observable.js' ;
2225import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js' ;
2326import { URI } from '../../../../../base/common/uri.js' ;
2427import { localize } from '../../../../../nls.js' ;
2528import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js' ;
26- import { IContextViewService } from '../../../../../platform/contextview/browser/contextView.js' ;
29+ import { IContextMenuService , IContextViewService } from '../../../../../platform/contextview/browser/contextView.js' ;
30+ import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js' ;
2731import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js' ;
2832import { WorkbenchList } from '../../../../../platform/list/browser/listService.js' ;
2933import { IOpenerService } from '../../../../../platform/opener/common/opener.js' ;
@@ -167,6 +171,8 @@ export class ToolsListWidget extends Disposable {
167171 @ILanguageModelToolsService private readonly _toolsService : ILanguageModelToolsService ,
168172 @IAgentHostToolSetEnablementService private readonly _enablementService : IAgentHostToolSetEnablementService ,
169173 @IContextViewService private readonly _contextViewService : IContextViewService ,
174+ @IContextMenuService private readonly _contextMenuService : IContextMenuService ,
175+ @IDialogService private readonly _dialogService : IDialogService ,
170176 @IOpenerService private readonly _openerService : IOpenerService ,
171177 @IInstantiationService private readonly _instantiationService : IInstantiationService ,
172178 @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService : IExtensionsWorkbenchService ,
@@ -298,6 +304,8 @@ export class ToolsListWidget extends Disposable {
298304 this . _onDidSelectExtension . fire ( e . element ) ;
299305 }
300306 } ) ) ;
307+
308+ this . _register ( this . _galleryList . onContextMenu ( e => this . _onGalleryContextMenu ( e ) ) ) ;
301309 }
302310
303311 private _readState ( reader : IReader ) : IToolEnablementState {
@@ -325,7 +333,10 @@ export class ToolsListWidget extends Disposable {
325333 }
326334
327335 private _createViewModel ( ) : IObservable < readonly IToolSetViewModel [ ] > {
336+ // Refresh when extensions change so tool sets from an uninstalled extension drop out immediately (their tools linger in the extension host until reload).
337+ const extensionsChanged = observableSignalFromEvent ( this , this . _extensionsWorkbenchService . onChange ) ;
328338 return derived ( reader => {
339+ extensionsChanged . read ( reader ) ;
329340 const query = this . _searchQuery . read ( reader ) . trim ( ) ;
330341
331342 const result : IToolSetViewModel [ ] = [ ] ;
@@ -344,6 +355,14 @@ export class ToolsListWidget extends Disposable {
344355 if ( ts . deprecated ) {
345356 return undefined ;
346357 }
358+ // Hide extension-provided sets whose extension is gone or being removed.
359+ if ( ts . source . type === 'extension' ) {
360+ const extensionId = ts . source . extensionId ;
361+ const installed = this . _extensionsWorkbenchService . local . find ( e => ExtensionIdentifier . equals ( e . identifier . id , extensionId ) ) ;
362+ if ( ! installed || installed . state === ExtensionState . Uninstalling || installed . state === ExtensionState . Uninstalled ) {
363+ return undefined ;
364+ }
365+ }
347366 const memberTools = Array . from ( ts . getTools ( reader ) ) ;
348367 if ( memberTools . length === 0 ) {
349368 return undefined ;
@@ -604,6 +623,17 @@ export class ToolsListWidget extends Disposable {
604623 toggleExpand ( ) ;
605624 } ) ) ;
606625
626+ // Extension-provided tool sets can be uninstalled via the context menu.
627+ this . _rowStore . add ( DOM . addDisposableListener ( row , 'contextmenu' , e => {
628+ const extension = this . _resolveExtensionForToolSet ( ts ) ;
629+ if ( ! extension ) {
630+ return ;
631+ }
632+ DOM . EventHelper . stop ( e , true ) ;
633+ const anchor : HTMLElement | StandardMouseEvent = e . button === 2 ? new StandardMouseEvent ( DOM . getWindow ( row ) , e ) : row ;
634+ this . _showExtensionContextMenu ( anchor , extension ) ;
635+ } ) ) ;
636+
607637 const group = DOM . append ( this . _treeContainer , $ ( '.tools-list-children' ) ) ;
608638 group . id = `tools-group-${ ts . id } ` ;
609639 group . setAttribute ( 'role' , 'group' ) ;
@@ -888,8 +918,56 @@ export class ToolsListWidget extends Disposable {
888918 private _currentState ( ) : IToolEnablementState {
889919 return this . _enablementService . getState ( this . _sessionType ) ;
890920 }
891- }
892921
922+ /** Resolve the installed, non-builtin extension backing an extension-provided tool set. */
923+ private _resolveExtensionForToolSet ( ts : IToolSet ) : IExtension | undefined {
924+ if ( ts . source . type !== 'extension' ) {
925+ return undefined ;
926+ }
927+ const source = ts . source ;
928+ const extension = this . _extensionsWorkbenchService . local . find ( e => ExtensionIdentifier . equals ( e . identifier . id , source . extensionId ) ) ;
929+ if ( ! extension || extension . local ?. isBuiltin ) {
930+ return undefined ;
931+ }
932+ return extension ;
933+ }
934+
935+ private _onGalleryContextMenu ( e : IListContextMenuEvent < IExtension > ) : void {
936+ const extension = e . element ;
937+ if ( ! extension || extension . state !== ExtensionState . Installed || extension . local ?. isBuiltin ) {
938+ return ;
939+ }
940+ this . _showExtensionContextMenu ( e . anchor , extension ) ;
941+ }
942+
943+ private _showExtensionContextMenu ( anchor : HTMLElement | StandardMouseEvent | IAnchor , extension : IExtension ) : void {
944+ const disposables = new DisposableStore ( ) ;
945+ const uninstallAction = disposables . add ( new Action (
946+ 'toolsList.uninstallExtension' ,
947+ localize ( 'uninstallExtension' , "Uninstall Extension" ) ,
948+ undefined ,
949+ true ,
950+ ( ) => this . _uninstallExtension ( extension ) ,
951+ ) ) ;
952+ this . _contextMenuService . showContextMenu ( {
953+ getAnchor : ( ) => anchor ,
954+ getActions : ( ) => [ uninstallAction ] ,
955+ onHide : ( ) => disposables . dispose ( ) ,
956+ } ) ;
957+ }
958+
959+ private async _uninstallExtension ( extension : IExtension ) : Promise < void > {
960+ const result = await this . _dialogService . confirm ( {
961+ message : localize ( 'confirmUninstallToolExtension' , "Do you want to uninstall the extension '{0}'?" , extension . displayName ) ,
962+ detail : localize ( 'confirmUninstallToolExtensionDetail' , "This extension may contribute more than tools. Uninstalling it removes all of its contributions." ) ,
963+ primaryButton : localize ( 'uninstallExtensionBtn' , "Uninstall Extension" ) ,
964+ type : 'question' ,
965+ } ) ;
966+ if ( result . confirmed ) {
967+ await this . _extensionsWorkbenchService . uninstall ( extension ) ;
968+ }
969+ }
970+ }
893971
894972/**
895973 * The Copilot CLI's built-in tools, surfaced read-only for reference. Mirrored from the published
0 commit comments