From 00fa27eb04ccacc24fe3eaf36371d1b326c1760c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 2 Jul 2025 17:00:03 -0400 Subject: [PATCH 01/15] use `TimeoutTimer` to properly cancel timeout for suggest widget discoverability update (#253497) fix #253460 --- .../suggest/browser/terminalSuggestAddon.ts | 1 + .../browser/terminalSuggestShownTracker.ts | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index ac0444c8f78..1f5365fb007 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -907,6 +907,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } hideSuggestWidget(cancelAnyRequest: boolean): void { + this._discoverability?.resetTimer(); if (cancelAnyRequest) { this._cancellationTokenSource?.cancel(); this._cancellationTokenSource = undefined; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestShownTracker.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestShownTracker.ts index 6fb1932dd14..ab45b94fb48 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestShownTracker.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestShownTracker.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { TimeoutTimer } from '../../../../../base/common/async.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { TerminalShellType } from '../../../../../platform/terminal/common/terminal.js'; @@ -23,7 +24,7 @@ interface ITerminalSuggestShownTracker extends IDisposable { export class TerminalSuggestShownTracker extends Disposable implements ITerminalSuggestShownTracker { private _done: boolean; private _count: number; - private _timeout: Timeout | undefined; + private _timeout: TimeoutTimer | undefined; private _start: number | undefined; private _firstShownTracker: { shell: Set; window: boolean } | undefined = undefined; @@ -51,6 +52,14 @@ export class TerminalSuggestShownTracker extends Disposable implements ITerminal this._firstShownTracker = undefined; } + resetTimer(): void { + if (this._timeout) { + this._timeout.cancel(); + this._timeout = undefined; + } + this._start = undefined; + } + update(widgetElt: HTMLElement | undefined): void { if (this._done) { return; @@ -63,10 +72,11 @@ export class TerminalSuggestShownTracker extends Disposable implements ITerminal if (this._count >= TERMINAL_SUGGEST_DISCOVERABILITY_MAX_COUNT) { this._setDone(widgetElt); } else if (!this._start) { + this.resetTimer(); this._start = Date.now(); - this._timeout = setTimeout(() => { + this._timeout = this._register(new TimeoutTimer(() => { this._setDone(widgetElt); - }, TERMINAL_SUGGEST_DISCOVERABILITY_MIN_MS); + }, TERMINAL_SUGGEST_DISCOVERABILITY_MIN_MS)); } } @@ -77,7 +87,7 @@ export class TerminalSuggestShownTracker extends Disposable implements ITerminal widgetElt.classList.remove('increased-discoverability'); } if (this._timeout) { - clearTimeout(this._timeout); + this._timeout.cancel(); this._timeout = undefined; } this._start = undefined; From 0f58db74803c27f10770568b4bd8fec037bc2cdc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 3 Jul 2025 07:49:43 +0200 Subject: [PATCH 02/15] add installing label action (#253712) Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> --- .../contrib/mcp/browser/mcpServerActions.ts | 23 ++++++++++++++++++- .../contrib/mcp/browser/mcpServerEditor.ts | 3 ++- .../contrib/mcp/browser/mcpServersView.ts | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts index 879b5f72215..928d25c8372 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts @@ -12,7 +12,7 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { manageExtensionIcon } from '../../extensions/browser/extensionsIcons.js'; import { getDomNodePagePosition } from '../../../../base/browser/dom.js'; -import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab } from '../common/mcpTypes.js'; +import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerInstallState } from '../common/mcpTypes.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { McpCommandIds } from '../common/mcpCommandIds.js'; @@ -116,6 +116,9 @@ export class InstallAction extends McpServerAction { if (!this.mcpServer?.gallery && !this.mcpServer?.installable) { return; } + if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) { + return; + } this.class = InstallAction.CLASS; this.enabled = true; this.label = localize('install', "Install"); @@ -145,6 +148,20 @@ export class InstallAction extends McpServerAction { } } +export class InstallingLabelAction extends McpServerAction { + + private static readonly LABEL = localize('installing', "Installing"); + private static readonly CLASS = `${McpServerAction.LABEL_ACTION_CLASS} install installing`; + + constructor() { + super('extension.installing', InstallingLabelAction.LABEL, InstallingLabelAction.CLASS, false); + } + + update(): void { + this.class = `${InstallingLabelAction.CLASS}${this.mcpServer && this.mcpServer.installState === McpServerInstallState.Installing ? '' : ' hide'}`; + } +} + export class UninstallAction extends McpServerAction { static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent uninstall`; @@ -166,6 +183,10 @@ export class UninstallAction extends McpServerAction { if (!this.mcpServer.local) { return; } + if (this.mcpServer.installState !== McpServerInstallState.Installed) { + this.enabled = false; + return; + } this.class = UninstallAction.CLASS; this.enabled = true; this.label = localize('uninstall', "Uninstall"); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index bd88574cc4b..be635988841 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -39,7 +39,7 @@ import { IExtensionService } from '../../../services/extensions/common/extension import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IMcpServerEditorOptions, IWorkbenchMcpServer, McpServerContainers } from '../common/mcpTypes.js'; import { InstallCountWidget, McpServerIconWidget, McpServerWidget, onClick, PublisherWidget, RatingsWidget } from './mcpServerWidgets.js'; -import { DropDownAction, InstallAction, ManageMcpServerAction, UninstallAction } from './mcpServerActions.js'; +import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction, UninstallAction } from './mcpServerActions.js'; import { McpServerEditorInput } from './mcpServerEditorInput.js'; import { ILocalMcpServer, IMcpServerManifest, IMcpServerPackage, PackageType } from '../../../../platform/mcp/common/mcpManagement.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; @@ -220,6 +220,7 @@ export class McpServerEditor extends EditorPane { const actions = [ this.instantiationService.createInstance(InstallAction, true), + this.instantiationService.createInstance(InstallingLabelAction), this.instantiationService.createInstance(UninstallAction), this.instantiationService.createInstance(ManageMcpServerAction, true), ]; diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 2e743bcfd5a..f54a3bd4327 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -25,7 +25,7 @@ import { getLocationBasedViewColors, ViewPane } from '../../../browser/parts/vie import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; import { IViewDescriptorService, IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js'; import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, mcpServerIcon, McpServerInstallState } from '../common/mcpTypes.js'; -import { DropDownAction, InstallAction, ManageMcpServerAction } from './mcpServerActions.js'; +import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction } from './mcpServerActions.js'; import { PublisherWidget, InstallCountWidget, RatingsWidget, McpServerIconWidget } from './mcpServerWidgets.js'; import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; @@ -321,6 +321,7 @@ class McpServerRenderer implements IListRenderer Date: Thu, 3 Jul 2025 11:59:42 +0200 Subject: [PATCH 03/15] If a mode has no tools specified, it should include all tools (#253843) --- .../contrib/chat/browser/chatSelectedTools.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- .../chat/browser/languageModelToolsService.ts | 12 +++++++++--- .../promptSyntax/promptToolsCodeLensProvider.ts | 2 +- .../contrib/chat/common/languageModelToolsService.ts | 2 +- .../test/common/mockLanguageModelToolsService.ts | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts index ef3f732ed15..6bf867e7e38 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts @@ -124,7 +124,7 @@ export class ChatSelectedTools extends Disposable { let currentMap = this._sessionStates.get(currentMode.id); let defaultEnablement = false; if (!currentMap && currentMode.kind === ChatModeKind.Agent && currentMode.customTools) { - currentMap = this._toolsService.toToolAndToolSetEnablementMap(new Set(currentMode.customTools.read(r))); + currentMap = this._toolsService.toToolAndToolSetEnablementMap(currentMode.customTools.read(r)); } if (!currentMap) { currentMap = this._selectedTools.read(r); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index dd82570e524..d6a95c8cb8d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1909,7 +1909,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // if not tools to enable are present, we are done if (tools !== undefined && this.input.currentModeKind === ChatModeKind.Agent) { - const enablementMap = this.toolsService.toToolAndToolSetEnablementMap(new Set(tools)); + const enablementMap = this.toolsService.toToolAndToolSetEnablementMap(tools); this.input.selectedToolsModel.set(enablementMap, true); } diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 965f71dd31d..a13152e5427 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -484,13 +484,19 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo return result; } - toToolAndToolSetEnablementMap(toolOrToolSetNames: Set): Map { + /** + * Create a map that contains all tools and toolsets with their enablement state. + * @param toolOrToolSetNames A list of tool or toolset names to check for enablement. If undefined, all tools and toolsets are enabled. + * @returns A map of tool or toolset instances to their enablement state. + */ + toToolAndToolSetEnablementMap(enabledToolOrToolSetNames: readonly string[] | undefined): Map { + const toolOrToolSetNames = enabledToolOrToolSetNames ? new Set(enabledToolOrToolSetNames) : undefined; const result = new Map(); for (const tool of this._tools.values()) { - result.set(tool.data, tool.data.toolReferenceName !== undefined && toolOrToolSetNames.has(tool.data.toolReferenceName)); + result.set(tool.data, tool.data.toolReferenceName !== undefined && (toolOrToolSetNames === undefined || toolOrToolSetNames.has(tool.data.toolReferenceName))); } for (const toolSet of this._toolSets) { - result.set(toolSet, toolOrToolSetNames.has(toolSet.referenceName)); + result.set(toolSet, (toolOrToolSetNames === undefined || toolOrToolSetNames.has(toolSet.referenceName))); } return result; } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 018237936be..3d904edb187 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -78,7 +78,7 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider private async updateTools(model: ITextModel, tools: PromptToolsMetadata) { - const selectedToolsNow = tools.value ? this.languageModelToolsService.toToolAndToolSetEnablementMap(new Set(tools.value)) : new Map(); + const selectedToolsNow = tools.value ? this.languageModelToolsService.toToolAndToolSetEnablementMap(tools.value) : new Map(); const newSelectedAfter = await this.instantiationService.invokeFunction(showToolsPicker, localize('placeholder', "Select tools"), undefined, selectedToolsNow); if (!newSelectedAfter) { return; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index be257861899..f1df32784ce 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -275,7 +275,7 @@ export interface ILanguageModelToolsService { resetToolAutoConfirmation(): void; cancelToolCallsForRequest(requestId: string): void; toToolEnablementMap(toolOrToolSetNames: Set): Record; - toToolAndToolSetEnablementMap(toolOrToolSetNames: Set): IToolAndToolSetEnablementMap; + toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[] | undefined): IToolAndToolSetEnablementMap; readonly toolSets: IObservable>; getToolSet(id: string): ToolSet | undefined; diff --git a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts index 511ff781e42..69bdd684bfd 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -76,7 +76,7 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService throw new Error('Method not implemented.'); } - toToolAndToolSetEnablementMap(toolOrToolSetNames: Set): Map { + toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[] | undefined): Map { throw new Error('Method not implemented.'); } } From 4950db1f74217e09ceecb05fa51e826b69627251 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 3 Jul 2025 11:59:45 +0200 Subject: [PATCH 04/15] layout - never show secondary sidebar by default if empty (fix #253855) (#253828) --- src/vs/workbench/browser/layout.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index ec7757a570e..352b7df5b1a 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -633,10 +633,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService, coreExperimentationService: ICoreExperimentationService): void { this._mainContainerDimension = getClientArea(this.parent, DEFAULT_WINDOW_DIMENSIONS); // running with fallback to ensure no error is thrown (https://github.com/microsoft/vscode/issues/240242) - this.stateModel = new LayoutStateModel(this.storageService, this.configurationService, this.contextService, coreExperimentationService, this.environmentService); + this.stateModel = new LayoutStateModel(this.storageService, this.configurationService, this.contextService, coreExperimentationService); this.stateModel.load({ mainContainerDimension: this._mainContainerDimension, - resetLayout: Boolean(this.layoutOptions?.resetLayout) + resetLayout: Boolean(this.layoutOptions?.resetLayout), + isAuxiliaryBarEmpty: this.viewDescriptorService + .getViewContainersByLocation(ViewContainerLocation.AuxiliaryBar) + .find(viewContainer => this.hasViews(viewContainer.id))?.id !== undefined }); this._register(this.stateModel.onDidChangeState(change => { @@ -2789,6 +2792,7 @@ enum LegacyWorkbenchLayoutSettings { interface ILayoutStateLoadConfiguration { readonly mainContainerDimension: IDimension; readonly resetLayout: boolean; + readonly isAuxiliaryBarEmpty: boolean; } class LayoutStateModel extends Disposable { @@ -2804,8 +2808,7 @@ class LayoutStateModel extends Disposable { private readonly storageService: IStorageService, private readonly configurationService: IConfigurationService, private readonly contextService: IWorkspaceContextService, - private readonly coreExperimentationService: ICoreExperimentationService, - private readonly environmentService: IBrowserWorkbenchEnvironmentService + private readonly coreExperimentationService: ICoreExperimentationService ) { super(); @@ -2868,9 +2871,8 @@ class LayoutStateModel extends Disposable { LayoutStateKeys.SIDEBAR_HIDDEN.defaultValue = workbenchState === WorkbenchState.EMPTY; LayoutStateKeys.AUXILIARYBAR_SIZE.defaultValue = Math.min(300, mainContainerDimension.width / 4); LayoutStateKeys.AUXILIARYBAR_HIDDEN.defaultValue = (() => { - const configuration = this.configurationService.inspect(WorkbenchLayoutSettings.AUXILIARYBAR_DEFAULT_VISIBILITY); - if (configuration.defaultValue !== 'hidden' && isWeb && !this.environmentService.remoteAuthority) { - return true; // TODO@bpasero revisit this when Chat is available in serverless web + if (configuration.isAuxiliaryBarEmpty) { + return true; // require a view in the auxiliary bar to show it by default } switch (this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_DEFAULT_VISIBILITY)) { From 791e7ef3dbd801366087e896d79681dcc0b49a7a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 3 Jul 2025 03:02:26 -0700 Subject: [PATCH 05/15] Fix cancelled tool calls missing from history (#253780) * Fix cancelled tool calls missing from history When they are cancelled by sending a followup message Fix microsoft/vscode-copilot#18495 * Add comment --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d6a95c8cb8d..e6620e58803 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -185,6 +185,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private isRequestPaused: IContextKey; private canRequestBePaused: IContextKey; private agentInInput: IContextKey; + private currentRequest: Promise | undefined; private _visible = false; @@ -1626,6 +1627,11 @@ export class ChatWidget extends Disposable implements IChatWidget { } this.chatService.cancelCurrentRequestForSession(this.viewModel.sessionId); + if (this.currentRequest) { + // We have to wait the current request to be properly cancelled so that it has a chance to update the model with its result metadata. + // This is awkward, it's basically a limitation of the chat provider-based agent. + await Promise.race([this.currentRequest, timeout(1000)]); + } this.input.validateAgentMode(); @@ -1654,7 +1660,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (result) { this.input.acceptInput(isUserQuery); this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand }); - result.responseCompletePromise.then(() => { + this.currentRequest = result.responseCompletePromise.then(() => { const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; this.chatAccessibilityService.acceptResponse(lastResponse, requestId, options?.isVoiceInput); @@ -1665,6 +1671,8 @@ export class ChatWidget extends Disposable implements IChatWidget { this.input.setValue(question, false); } } + + this.currentRequest = undefined; }); if (this.viewModel?.editing) { From f075c4377992e7b8eba5eda8152aad39e03fa9d6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 3 Jul 2025 03:03:33 -0700 Subject: [PATCH 06/15] Use custom mode properly for all requests (#253781) It was not used in the setup, retry, or error retry cases --- .../workbench/contrib/chat/browser/actions/chatTitleActions.ts | 3 +-- src/vs/workbench/contrib/chat/browser/chat.ts | 3 ++- .../browser/chatContentParts/chatConfirmationContentPart.ts | 2 +- .../chat/browser/chatContentParts/chatErrorConfirmationPart.ts | 3 +-- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 3 +-- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 6c0e276c612..23e25bdad55 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -262,9 +262,8 @@ export function registerChatTitleActions() { chatService.resendRequest(request!, { userSelectedModelId: languageModelId, - userSelectedTools: widget?.getUserSelectedTools(), attempt: (request?.attempt ?? -1) + 1, - mode: widget?.input.currentModeKind, + ...widget?.getModeRequestOptions(), }); } }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 25e0792faeb..ccdd8ffcc65 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -17,6 +17,7 @@ import { IChatAgentCommand, IChatAgentData } from '../common/chatAgents.js'; import { IChatResponseModel } from '../common/chatModel.js'; import { IParsedChatRequest } from '../common/chatParserTypes.js'; import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js'; +import { IChatSendRequestOptions } from '../common/chatService.js'; import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel } from '../common/chatViewModel.js'; import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; @@ -208,7 +209,7 @@ export interface IChatWidget { focusLastMessage(): void; focusInput(): void; hasInputFocus(): boolean; - getUserSelectedTools(): Record | undefined; + getModeRequestOptions(): Partial; getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined; getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[]; getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[]; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts index 249d3343bcc..3a215c401c6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts @@ -56,7 +56,7 @@ export class ChatConfirmationContentPart extends Disposable implements IChatCont const widget = chatWidgetService.getWidgetBySessionId(element.sessionId); options.userSelectedModelId = widget?.input.currentLanguageModel; options.mode = widget?.input.currentModeKind; - options.userSelectedTools = widget?.getUserSelectedTools(); + Object.assign(options, widget?.getModeRequestOptions()); if (await this.chatService.sendRequest(element.sessionId, prompt, options)) { confirmation.isUsed = true; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts index 654d2256b53..1d4f9d18389 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts @@ -61,8 +61,7 @@ export class ChatErrorConfirmationContentPart extends Disposable implements ICha options.confirmation = buttonData.label; const widget = chatWidgetService.getWidgetBySessionId(element.sessionId); options.userSelectedModelId = widget?.input.currentLanguageModel; - options.userSelectedTools = widget?.getUserSelectedTools(); - options.mode = widget?.input.currentModeKind; + Object.assign(options, widget?.getModeRequestOptions()); if (await chatService.sendRequest(element.sessionId, prompt, options)) { this._onDidChangeHeight.fire(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index dd98ad46471..9cdd2f7ddf8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -327,9 +327,9 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { } await chatService.resendRequest(requestModel, { + ...widget?.getModeRequestOptions(), mode, userSelectedModelId: languageModel, - userSelectedTools: widget?.getUserSelectedTools() }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e6620e58803..8faac4fb00f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1646,14 +1646,13 @@ export class ChatWidget extends Disposable implements IChatWidget { } const result = await this.chatService.sendRequest(this.viewModel.sessionId, requestInputs.input, { - mode: this.input.currentModeKind, userSelectedModelId: this.input.currentLanguageModel, location: this.location, locationData: this._location.resolveData?.(), parserContext: { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind }, attachedContext: requestInputs.attachedContext.asArray(), noCommandDetection: options?.noCommandDetection, - userSelectedTools: this.getUserSelectedTools(), + ...this.getModeRequestOptions(), modeInstructions: this.input.currentModeObs.get().body?.get() }); From c66b907bd73e601e120fdba47066f73a2ea240be Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 3 Jul 2025 03:08:13 -0700 Subject: [PATCH 07/15] Make Agent mode the first entry (#253783) Fix #253620 --- src/vs/workbench/contrib/chat/common/chatModes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 6cabc697dcd..f805a65d42e 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -185,7 +185,7 @@ export class ChatModeService extends Disposable implements IChatModeService { ]; if (this.chatAgentService.hasToolsAgent) { - builtinModes.push(ChatMode.Agent); + builtinModes.unshift(ChatMode.Agent); } builtinModes.push(ChatMode.Edit); return builtinModes; From a30b6fb92dffdef2066f1b08bb4d55247c8d6605 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 3 Jul 2025 03:17:33 -0700 Subject: [PATCH 08/15] Don't patch [text] as an incomplete markdown link (#253786) Fix microsoft/vscode-copilot#19103 --- src/vs/base/browser/markdownRenderer.ts | 2 +- src/vs/base/test/browser/markdownRenderer.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 06aec51b9bb..04e4ea0ef9e 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -727,7 +727,7 @@ function completeSingleLinePattern(token: marked.Tokens.Text | marked.Tokens.Par } // Contains the start of link text, and no following tokens contain the link target - else if (lastLine.match(/(^|\s)\[\w*/)) { + else if (lastLine.match(/(^|\s)\[\w*[^\]]*$/)) { return completeLinkText(token); } } diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index cd2a7ceda50..cacc1bb404b 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -958,6 +958,14 @@ suite('MarkdownRenderer', () => { assert.deepStrictEqual(newTokens, tokens); }); + test('square braces in text', () => { + const incomplete = 'hello [what] is going on'; + const tokens = marked.marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + test('complete link', () => { const incomplete = 'text [link](http://microsoft.com)'; const tokens = marked.marked.lexer(incomplete); From 42491d8408b999bf74a5d8fd788cd9451d1e9968 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:22:52 +0200 Subject: [PATCH 09/15] Remove bad contribution for now (#253810) Fixes https://github.com/microsoft/vscode/issues/253690 --- .../browser/authentication.contribution.ts | 101 +++++++++--------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts index b150e3daeaa..693975bc45a 100644 --- a/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts +++ b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts @@ -7,7 +7,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { localize } from '../../../../nls.js'; import { registerAction2 } from '../../../../platform/actions/common/actions.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; -import { IExtensionManifest, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; +import { IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; @@ -20,7 +20,6 @@ import { IAuthenticationUsageService } from '../../../services/authentication/br import { ManageAccountPreferencesForMcpServerAction } from './actions/manageAccountPreferencesForMcpServerAction.js'; import { ManageTrustedMcpServersForAccountAction } from './actions/manageTrustedMcpServersForAccountAction.js'; import { RemoveDynamicAuthenticationProvidersAction } from './actions/manageDynamicAuthenticationProvidersAction.js'; -import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IAuthenticationQueryService } from '../../../services/authentication/common/authenticationQuery.js'; import { IMcpRegistry } from '../../mcp/common/mcpRegistryTypes.js'; import { autorun } from '../../../../base/common/observable.js'; @@ -116,61 +115,61 @@ class AuthenticationUsageContribution implements IWorkbenchContribution { } } -class AuthenticationExtensionsContribution extends Disposable implements IWorkbenchContribution { - static ID = 'workbench.contrib.authenticationExtensions'; +// class AuthenticationExtensionsContribution extends Disposable implements IWorkbenchContribution { +// static ID = 'workbench.contrib.authenticationExtensions'; - constructor( - @IExtensionService private readonly _extensionService: IExtensionService, - @IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService, - @IAuthenticationService private readonly _authenticationService: IAuthenticationService - ) { - super(); - void this.run(); - this._register(this._extensionService.onDidChangeExtensions(this._onDidChangeExtensions, this)); - this._register( - Event.any( - this._authenticationService.onDidChangeDeclaredProviders, - this._authenticationService.onDidRegisterAuthenticationProvider - )(() => this._cleanupRemovedExtensions()) - ); - } +// constructor( +// @IExtensionService private readonly _extensionService: IExtensionService, +// @IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService, +// @IAuthenticationService private readonly _authenticationService: IAuthenticationService +// ) { +// super(); +// void this.run(); +// this._register(this._extensionService.onDidChangeExtensions(this._onDidChangeExtensions, this)); +// this._register( +// Event.any( +// this._authenticationService.onDidChangeDeclaredProviders, +// this._authenticationService.onDidRegisterAuthenticationProvider +// )(() => this._cleanupRemovedExtensions()) +// ); +// } - async run(): Promise { - await this._extensionService.whenInstalledExtensionsRegistered(); - this._cleanupRemovedExtensions(); - } +// async run(): Promise { +// await this._extensionService.whenInstalledExtensionsRegistered(); +// this._cleanupRemovedExtensions(); +// } - private _onDidChangeExtensions(delta: { readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }): void { - if (delta.removed.length > 0) { - this._cleanupRemovedExtensions(delta.removed); - } - } +// private _onDidChangeExtensions(delta: { readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }): void { +// if (delta.removed.length > 0) { +// this._cleanupRemovedExtensions(delta.removed); +// } +// } - private _cleanupRemovedExtensions(removedExtensions?: readonly IExtensionDescription[]): void { - const extensionIdsToRemove = removedExtensions - ? new Set(removedExtensions.map(e => e.identifier.value)) - : new Set(this._extensionService.extensions.map(e => e.identifier.value)); +// private _cleanupRemovedExtensions(removedExtensions?: readonly IExtensionDescription[]): void { +// const extensionIdsToRemove = removedExtensions +// ? new Set(removedExtensions.map(e => e.identifier.value)) +// : new Set(this._extensionService.extensions.map(e => e.identifier.value)); - // If we are cleaning up specific removed extensions, we only remove those. - const isTargetedCleanup = !!removedExtensions; +// // If we are cleaning up specific removed extensions, we only remove those. +// const isTargetedCleanup = !!removedExtensions; - const providerIds = this._authenticationQueryService.getProviderIds(); - for (const providerId of providerIds) { - this._authenticationQueryService.provider(providerId).forEachAccount(account => { - account.extensions().forEach(extension => { - const shouldRemove = isTargetedCleanup - ? extensionIdsToRemove.has(extension.extensionId) - : !extensionIdsToRemove.has(extension.extensionId); +// const providerIds = this._authenticationQueryService.getProviderIds(); +// for (const providerId of providerIds) { +// this._authenticationQueryService.provider(providerId).forEachAccount(account => { +// account.extensions().forEach(extension => { +// const shouldRemove = isTargetedCleanup +// ? extensionIdsToRemove.has(extension.extensionId) +// : !extensionIdsToRemove.has(extension.extensionId); - if (shouldRemove) { - extension.removeUsage(); - extension.setAccessAllowed(false); - } - }); - }); - } - } -} +// if (shouldRemove) { +// extension.removeUsage(); +// extension.setAccessAllowed(false); +// } +// }); +// }); +// } +// } +// } class AuthenticationMcpContribution extends Disposable implements IWorkbenchContribution { static ID = 'workbench.contrib.authenticationMcp'; @@ -216,5 +215,5 @@ class AuthenticationMcpContribution extends Disposable implements IWorkbenchCont registerWorkbenchContribution2(AuthenticationContribution.ID, AuthenticationContribution, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(AuthenticationUsageContribution.ID, AuthenticationUsageContribution, WorkbenchPhase.Eventually); -registerWorkbenchContribution2(AuthenticationExtensionsContribution.ID, AuthenticationExtensionsContribution, WorkbenchPhase.Eventually); +// registerWorkbenchContribution2(AuthenticationExtensionsContribution.ID, AuthenticationExtensionsContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(AuthenticationMcpContribution.ID, AuthenticationMcpContribution, WorkbenchPhase.Eventually); From 9f88ccaac49476e00dbcbc6107543cfb2b4d504c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 3 Jul 2025 10:24:20 +0000 Subject: [PATCH 10/15] Engineering - disable pipeline (#253650) --- build/azure-pipelines/product-build-macos.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build/azure-pipelines/product-build-macos.yml b/build/azure-pipelines/product-build-macos.yml index 7be9e7cfa00..cc8985c07ca 100644 --- a/build/azure-pipelines/product-build-macos.yml +++ b/build/azure-pipelines/product-build-macos.yml @@ -1,9 +1,6 @@ pr: none -trigger: - batch: true - branches: - include: ["main"] +trigger: none parameters: - name: VSCODE_QUALITY From 648bffe15e63c50c1e96617515e8a0c975298872 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 3 Jul 2025 03:30:06 -0700 Subject: [PATCH 11/15] clear chat on remote agent success (#253767) --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 726c4d43304..73b6a898546 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -643,6 +643,12 @@ export class CreateRemoteAgentJobAction extends Action2 { chatModel.acceptResponseProgress(addedRequest, { content, kind: 'markdownContent' }); chatModel.setResponse(addedRequest, {}); chatModel.completeResponse(addedRequest); + + // Clear chat (start a new chat) + if (resultMarkdown) { + widget.clear(); + } + } finally { remoteJobCreatingKey.set(false); } From 7ce94dd09ab8cc96884d18abdbdd9825ff38e7a7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 2 Jul 2025 14:43:12 +0200 Subject: [PATCH 12/15] Code chat - add option to maximize the chat panel (fix #253306) --- .../terminal-suggest/src/completions/code.ts | 4 ++++ src/vs/platform/environment/common/argv.ts | 1 + src/vs/platform/environment/node/argv.ts | 3 ++- .../chat/electron-browser/chat.contribution.ts | 18 +++++++++++++++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/extensions/terminal-suggest/src/completions/code.ts b/extensions/terminal-suggest/src/completions/code.ts index 58e8726b0e8..2c85b83f1c1 100644 --- a/extensions/terminal-suggest/src/completions/code.ts +++ b/extensions/terminal-suggest/src/completions/code.ts @@ -824,6 +824,10 @@ export const codeTunnelSubcommands: Fig.Subcommand[] = [ template: 'filepaths', }, }, + { + name: ['--maximize'], + description: 'Maximize the chat session view.', + }, { name: ['-h', '--help'], description: 'Print usage', diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 6c74026f3d5..8f83c924b85 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -28,6 +28,7 @@ export interface NativeParsedArgs { _: string[]; 'add-file'?: string[]; mode?: string; + maximize?: boolean; help?: boolean; }; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 417624ed476..3053c420925 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -55,7 +55,8 @@ export const OPTIONS: OptionDescriptions> = { '_': { type: 'string[]', description: localize('prompt', "The prompt to use as chat.") }, 'mode': { type: 'string', cat: 'o', alias: 'm', args: 'mode', description: localize('chatMode', "The mode to use for the chat session. Available options: 'ask', 'edit', 'agent', or the identifier of a custom mode. Defaults to 'agent'.") }, 'add-file': { type: 'string[]', cat: 'o', alias: 'a', args: 'path', description: localize('addFile', "Add files as context to the chat session.") }, - 'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") } + 'maximize': { type: 'boolean', cat: 'o', description: localize('chatMaximize', "Maximize the chat session view.") }, + 'help': { type: 'boolean', alias: 'h', description: localize('help', "Print usage.") } } }, 'serve-web': { diff --git a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts index 05f9f8c87ca..e9434bfd546 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts @@ -23,6 +23,10 @@ import { resolve } from '../../../../base/common/path.js'; import { showChatView } from '../browser/chat.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { ViewContainerLocation } from '../../../common/views.js'; class NativeBuiltinToolsContribution extends Disposable implements IWorkbenchContribution { @@ -49,7 +53,9 @@ class ChatCommandLineHandler extends Disposable { @ICommandService private readonly commandService: ICommandService, @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, @IViewsService private readonly viewsService: IViewsService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -84,6 +90,16 @@ class ChatCommandLineHandler extends Disposable { }; const chatWidget = await showChatView(this.viewsService); + + if (args.maximize) { + const location = this.contextKeyService.getContextKeyValue(ChatContextKeys.panelLocation.key); + if (location === ViewContainerLocation.AuxiliaryBar) { + this.layoutService.setAuxiliaryBarMaximized(true); + } else if (location === ViewContainerLocation.Panel && !this.layoutService.isPanelMaximized()) { + this.layoutService.toggleMaximizedPanel(); + } + } + await chatWidget?.waitForReady(); await this.commandService.executeCommand(ACTION_ID_NEW_CHAT); await this.commandService.executeCommand(CHAT_OPEN_ACTION_ID, opts); From c59715207b04d1ca3d164fc81bd1b60c5747f7d4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 2 Jul 2025 14:53:37 +0200 Subject: [PATCH 13/15] Provide option to open `code chat` in empty workspace or existing workspace (fix #253383) --- .../terminal-suggest/src/completions/code.ts | 8 ++++++++ src/vs/code/electron-main/main.ts | 15 ++++++++++++--- src/vs/platform/environment/common/argv.ts | 2 ++ src/vs/platform/environment/node/argv.ts | 2 ++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/extensions/terminal-suggest/src/completions/code.ts b/extensions/terminal-suggest/src/completions/code.ts index 2c85b83f1c1..99e1371b181 100644 --- a/extensions/terminal-suggest/src/completions/code.ts +++ b/extensions/terminal-suggest/src/completions/code.ts @@ -828,6 +828,14 @@ export const codeTunnelSubcommands: Fig.Subcommand[] = [ name: ['--maximize'], description: 'Maximize the chat session view.', }, + { + name: ['-r', '--reuse-window'], + description: 'Force to use the last active window for the chat session', + }, + { + name: ['-n', '--new-window'], + description: 'Force to open an empty window for the chat session', + }, { name: ['-h', '--help'], description: 'Print usage', diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index dc6983c8b0d..90721608230 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -507,9 +507,18 @@ class CodeMain { } if (args.chat) { - // If we are started with chat subcommand, the current working - // directory is always the path to open - args._ = [cwd()]; + if (args.chat['new-window']) { + // Apply `--new-window` flag to the main arguments + args['new-window'] = true; + } else if (args.chat['reuse-window']) { + // Apply `--reuse-window` flag to the main arguments + args['reuse-window'] = true; + } else { + // Unless we are started with specific instructions about + // new windows or reusing existing ones, always take the + // current working directory as workspace to open. + args._ = [cwd()]; + } } return args; diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 8f83c924b85..185ce5ad1b1 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -29,6 +29,8 @@ export interface NativeParsedArgs { 'add-file'?: string[]; mode?: string; maximize?: boolean; + 'reuse-window'?: boolean; + 'new-window'?: boolean; help?: boolean; }; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 3053c420925..b5958ee0ec3 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -56,6 +56,8 @@ export const OPTIONS: OptionDescriptions> = { 'mode': { type: 'string', cat: 'o', alias: 'm', args: 'mode', description: localize('chatMode', "The mode to use for the chat session. Available options: 'ask', 'edit', 'agent', or the identifier of a custom mode. Defaults to 'agent'.") }, 'add-file': { type: 'string[]', cat: 'o', alias: 'a', args: 'path', description: localize('addFile', "Add files as context to the chat session.") }, 'maximize': { type: 'boolean', cat: 'o', description: localize('chatMaximize', "Maximize the chat session view.") }, + 'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindowForChat', "Force to use the last active window for the chat session.") }, + 'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindowForChat', "Force to open an empty window for the chat session.") }, 'help': { type: 'boolean', alias: 'h', description: localize('help', "Print usage.") } } }, From 7f22d076265b0101b009bc7d3017a1f87c6e0656 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 2 Jul 2025 17:59:21 +0200 Subject: [PATCH 14/15] fix tests --- extensions/terminal-suggest/src/test/completions/code.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts index 323024e06f4..9022c0c5a3e 100644 --- a/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -72,7 +72,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { const categoryOptions = ['azure', 'data science', 'debuggers', 'extension packs', 'education', 'formatters', 'keymaps', 'language packs', 'linters', 'machine learning', 'notebooks', 'programming languages', 'scm providers', 'snippets', 'testing', 'themes', 'visualization', 'other']; const logOptions = ['critical', 'error', 'warn', 'info', 'debug', 'trace', 'off']; const syncOptions = ['on', 'off']; - const chatOptions = ['--add-file ', '--help', '--mode ', '-a ', '-h', '-m ']; + const chatOptions = ['--add-file ', '--help', '--maximize', '--mode ', '--new-window', '--reuse-window', '-a ', '-h', '-m ', '-n', '-r']; const typingTests: ITestSpec[] = []; for (let i = 1; i < executable.length; i++) { @@ -281,7 +281,7 @@ export function createCodeTunnelTestSpecs(executable: string): ITestSpec[] { { input: `${executable} tunnel unregister |`, expectedCompletions: [...commonFlags] }, { input: `${executable} tunnel service |`, expectedCompletions: [...commonFlags, 'help', 'install', 'log', 'uninstall'] }, { input: `${executable} tunnel help |`, expectedCompletions: helpSubcommands }, - { input: `${executable} chat |`, expectedCompletions: ['--mode ', '--add-file ', '--help', '-m ', '-a ', '-h'] }, + { input: `${executable} chat |`, expectedCompletions: ['--mode ', '--add-file ', '--help', '--maximize', '--new-window', '--reuse-window', '-m ', '-a ', '-h', '-n', '-r'] }, { input: `${executable} chat --mode |`, expectedCompletions: ['agent', 'ask', 'edit'] }, { input: `${executable} chat --add-file |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, { input: `${executable} serve-web |`, expectedCompletions: serveWebSubcommandsAndFlags }, From c10a833054b2f4cf11cc25e1468cf31b98045bec Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 3 Jul 2025 12:37:27 +0200 Subject: [PATCH 15/15] Agent confirmation badge doesn't clear if window is closed (fix #253418) (#253669) --- .../windows/electron-main/windowImpl.ts | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 31d0dc59843..3030b1c3198 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -8,7 +8,7 @@ import { DeferredPromise, RunOnceScheduler, timeout, Delayer } from '../../../ba import { CancellationToken } from '../../../base/common/cancellation.js'; import { toErrorMessage } from '../../../base/common/errorMessage.js'; import { Emitter, Event } from '../../../base/common/event.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; +import { Disposable, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js'; import { FileAccess, Schemas } from '../../../base/common/network.js'; import { getMarks, mark } from '../../../base/common/performance.js'; import { isBigSurOrNewer, isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; @@ -84,6 +84,29 @@ const enum ReadyState { READY } +class DockBadgeManager { + + static readonly INSTANCE = new DockBadgeManager(); + + private readonly windows = new Set(); + + acquireBadge(window: IBaseWindow): IDisposable { + this.windows.add(window.id); + + electron.app.setBadgeCount(isLinux ? 1 /* only numbers supported */ : undefined /* generic dot */); + + return { + dispose: () => { + this.windows.delete(window.id); + + if (this.windows.size === 0) { + electron.app.setBadgeCount(0); + } + } + }; + } +} + export abstract class BaseWindow extends Disposable implements IBaseWindow { //#region Events @@ -325,18 +348,18 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { case FocusMode.Notify: if (isMacintosh) { - this.setFocusNotificationBadge(undefined /* generic dot */); + this.showFocusNotificationBadge(); // On macOS we have direct API to bounce the dock icon electron.app.dock?.bounce('informational'); } else if (isWindows) { - this.setFocusNotificationBadge(undefined /* generic dot */); + this.showFocusNotificationBadge(); // On Windows, calling focus() will bounce the taskbar icon // https://github.com/electron/electron/issues/2867 this.win?.focus(); } else if (isLinux) { - this.setFocusNotificationBadge(1 /* only number supported */); + this.showFocusNotificationBadge(); // On Linux, there seems to be no way to bounce the taskbar icon // as calling focus() will actually steal focus away. @@ -352,18 +375,16 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { } } - private hasFocusNotificationBadge = false; + private readonly focusNotificationBadgeDisposable = this._register(new MutableDisposable()); - private setFocusNotificationBadge(count?: number): void { - electron.app.setBadgeCount(count); - this.hasFocusNotificationBadge = true; + private showFocusNotificationBadge(): void { + if (!this.focusNotificationBadgeDisposable.value) { + this.focusNotificationBadgeDisposable.value = DockBadgeManager.INSTANCE.acquireBadge(this); + } } private clearFocusNotificationBadge(): void { - if (this.hasFocusNotificationBadge) { - electron.app.setBadgeCount(0); - this.hasFocusNotificationBadge = false; - } + this.focusNotificationBadgeDisposable.clear(); } private doFocusWindow() {