From a3b880fffd94cbf7755d9213bdac5b94655d7f4f Mon Sep 17 00:00:00 2001 From: Elijah King Date: Mon, 13 Apr 2026 15:37:22 -0700 Subject: [PATCH 01/11] onboarding: include theme preview SVGs in build output The theme preview SVGs were not bundled in desktop or web builds, causing blank theme cards for users running built products. Fixes #309380 --- build/gulpfile.vscode.ts | 1 + build/gulpfile.vscode.web.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index 080d97f3197cf..1e216b7f5ad92 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -97,6 +97,7 @@ const vscodeResourceIncludes = [ // Welcome 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', + 'out-build/vs/workbench/contrib/welcomeOnboarding/browser/media/*.svg', // Sessions 'out-build/vs/sessions/contrib/chat/browser/media/*.svg', diff --git a/build/gulpfile.vscode.web.ts b/build/gulpfile.vscode.web.ts index 3e6b29adfe9fa..9af2afecb38ba 100644 --- a/build/gulpfile.vscode.web.ts +++ b/build/gulpfile.vscode.web.ts @@ -74,6 +74,7 @@ export const vscodeWebResourceIncludes = [ // Welcome 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', + 'out-build/vs/workbench/contrib/welcomeOnboarding/browser/media/*.svg', // Extensions 'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', From f4e87d96942da72dff49d6ac22f41ebdde3d2ff1 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Mon, 13 Apr 2026 16:01:33 -0700 Subject: [PATCH 02/11] onboarding: trigger chat setup after sign-in to resolve entitlements After the user signs in through the onboarding walkthrough, trigger the full chat setup flow in the background (sign-up, extension install, entitlement resolution) so the 'Finish Setup' status bar indicator is resolved by the time onboarding completes. Uses disableChatViewReveal and DefaultSetup strategy to avoid showing dialogs or popping the chat panel during onboarding. Fixes #309529 --- .../browser/onboardingVariationA.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts index c9d06f401f7f5..3a78cdfc0eb91 100644 --- a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts +++ b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts @@ -30,7 +30,8 @@ import { IQuickInputService } from '../../../../platform/quickinput/common/quick import { IFileService } from '../../../../platform/files/common/files.js'; import { IPathService } from '../../../services/path/common/pathService.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { InstallChatEvent, InstallChatClassification } from '../../chat/browser/chatSetup/chatSetup.js'; +import { InstallChatEvent, InstallChatClassification, ChatSetupStrategy } from '../../chat/browser/chatSetup/chatSetup.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { OnboardingStepId, ONBOARDING_STEPS, @@ -135,6 +136,7 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi @IFileService private readonly fileService: IFileService, @IPathService private readonly pathService: IPathService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @ICommandService private readonly commandService: ICommandService, ) { super(); @@ -561,6 +563,11 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi if (account) { this._userSignedIn = true; this.telemetryService.publicLog2('commandCenter.chatInstall', { installResult: 'installed', installDuration: watch.elapsed(), signUpErrorCode: undefined, provider }); + // Run chat setup in the background (sign-up, extension install, entitlement resolution) + this.commandService.executeCommand('workbench.action.chat.triggerSetup', undefined, { + disableChatViewReveal: true, + setupStrategy: ChatSetupStrategy.DefaultSetup, + }); this._nextStep(); } } catch (error) { @@ -592,6 +599,10 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi if (account) { this._userSignedIn = true; this.telemetryService.publicLog2('commandCenter.chatInstall', { installResult: 'installed', installDuration: watch.elapsed(), signUpErrorCode: undefined, provider }); + this.commandService.executeCommand('workbench.action.chat.triggerSetup', undefined, { + disableChatViewReveal: true, + setupStrategy: ChatSetupStrategy.DefaultSetup, + }); this._nextStep(); } } catch (error) { From 7fc1b0526eade6d9b59f81780eca2e061be70e7d Mon Sep 17 00:00:00 2001 From: Elijah King Date: Mon, 13 Apr 2026 16:25:34 -0700 Subject: [PATCH 03/11] onboarding: add Extension Marketplace keybinding tip to extensions step Shows Cmd+Shift+X (Ctrl+Shift+X on Windows/Linux) in the extensions step subtitle so users know how to browse the full marketplace. Fixes #309617 --- .../browser/onboardingVariationA.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts index 3a78cdfc0eb91..c9117b6055fe7 100644 --- a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts +++ b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts @@ -367,6 +367,8 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi this._renderAgentSessionsSubtitle(this.subtitleEl); } else if (stepId === OnboardingStepId.Personalize) { this._renderPersonalizeSubtitle(this.subtitleEl); + } else if (stepId === OnboardingStepId.Extensions) { + this._renderExtensionsSubtitle(this.subtitleEl); } else { this.subtitleEl.textContent = getOnboardingStepSubtitle(stepId); } @@ -776,6 +778,20 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi ); } + private _renderExtensionsSubtitle(container: HTMLElement): void { + clearNode(container); + const modifier = isMacintosh ? 'Cmd' : 'Ctrl'; + container.append( + localize('onboarding.extensions.subtitle.prefix', "Install extensions to enhance your workflow. Press "), + this._createKbd(localize({ key: 'onboarding.extensions.subtitle.modifier', comment: ['Keyboard modifier key'] }, "{0}", modifier)), + '+', + this._createKbd(localize('onboarding.extensions.subtitle.shift', "Shift")), + '+', + this._createKbd(localize('onboarding.extensions.subtitle.x', "X")), + localize('onboarding.extensions.subtitle.suffix', " to browse the Extension Marketplace."), + ); + } + private _createThemeCard(parent: HTMLElement, theme: IOnboardingThemeOption, allCards: HTMLElement[]): void { const card = this._registerStepFocusable(append(parent, $('div.onboarding-a-theme-card'))); allCards.push(card); From 9d2bda2516aee9326b5c30ab6da35d60df20bd64 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Mon, 13 Apr 2026 16:32:48 -0700 Subject: [PATCH 04/11] onboarding: screenreader accessibility fixes - Add aria-label to extension install buttons with extension name (#309382) - Make feature cards (Local, Cloud, CLI, Inline) tabbable with role/aria-label (#309525) - Add ARIA alerts on theme, keymap, extension install, and AI preference actions (#309521) Fixes #309382 Fixes #309521 Fixes #309525 --- .../browser/onboardingVariationA.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts index c9117b6055fe7..9c2dcb921ed4a 100644 --- a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts +++ b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts @@ -32,6 +32,7 @@ import { IPathService } from '../../../services/path/common/pathService.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { InstallChatEvent, InstallChatClassification, ChatSetupStrategy } from '../../chat/browser/chatSetup/chatSetup.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { OnboardingStepId, ONBOARDING_STEPS, @@ -137,6 +138,7 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi @IPathService private readonly pathService: IPathService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ICommandService private readonly commandService: ICommandService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { super(); @@ -756,6 +758,7 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi } pill.classList.add('selected'); pill.setAttribute('aria-checked', 'true'); + this.accessibilityService.alert(localize('onboarding.keymap.selected.alert', "{0} keyboard mapping selected", keymap.label)); })); } const selectedKeymapIndex = keymapOptions.findIndex(k => k.id === this.selectedKeymapId); @@ -822,6 +825,7 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi } card.classList.add('selected'); card.setAttribute('aria-checked', 'true'); + this.accessibilityService.alert(localize('onboarding.theme.selected.alert', "{0} theme selected", theme.label)); })); this.stepDisposables.add(addDisposableListener(card, EventType.KEY_DOWN, (e: KeyboardEvent) => { @@ -868,6 +872,7 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi const installBtn = this._registerStepFocusable(append(row, $('button.onboarding-a-ext-install'))); installBtn.type = 'button'; installBtn.textContent = localize('onboarding.ext.install', "Install"); + installBtn.setAttribute('aria-label', localize('onboarding.ext.install.aria', "Install {0}", ext.name)); this.stepDisposables.add(addDisposableListener(installBtn, EventType.CLICK, () => { this._logAction('installExtension', undefined, ext.id); @@ -877,6 +882,8 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi () => { installBtn.textContent = localize('onboarding.ext.installed', "Installed"); installBtn.classList.add('installed'); + installBtn.setAttribute('aria-label', localize('onboarding.ext.installed.aria', "{0} installed", ext.name)); + this.accessibilityService.alert(localize('onboarding.ext.installed.alert', "{0} has been installed", ext.name)); }, () => { installBtn.textContent = localize('onboarding.ext.install', "Install"); @@ -1095,6 +1102,7 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi c.setAttribute('aria-checked', c.dataset.id === option.id ? 'true' : 'false'); } this._applyAiPreference(option.id); + this.accessibilityService.alert(localize('onboarding.aiPref.selected.alert', "{0} selected", option.label)); })); } const selectedAiIndex = ONBOARDING_AI_PREFERENCE_OPTIONS.findIndex(o => o.id === this.selectedAiMode); @@ -1175,7 +1183,10 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi } private _createFeatureCard(parent: HTMLElement, icon: ThemeIcon, title: string, description?: string): HTMLElement { - const card = append(parent, $('div.onboarding-a-feature-card')); + const card = this._registerStepFocusable(append(parent, $('div.onboarding-a-feature-card'))); + card.setAttribute('tabindex', '0'); + card.setAttribute('role', 'group'); + card.setAttribute('aria-label', title); const iconCol = append(card, $('div.onboarding-a-feature-icon')); iconCol.appendChild(renderIcon(icon)); const textCol = append(card, $('div.onboarding-a-feature-text')); From ad8a1de3057de40bfb97505a73f345dda47fe5c9 Mon Sep 17 00:00:00 2001 From: "vs-code-engineering[bot]" <122617954+vs-code-engineering[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:50:02 -0700 Subject: [PATCH 05/11] [cherry-pick] Update rate limit message (#309657) Co-authored-by: vs-code-engineering[bot] --- .../src/platform/chat/common/commonTypes.ts | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/extensions/copilot/src/platform/chat/common/commonTypes.ts b/extensions/copilot/src/platform/chat/common/commonTypes.ts index c869cb511073a..fad49df32483f 100644 --- a/extensions/copilot/src/platform/chat/common/commonTypes.ts +++ b/extensions/copilot/src/platform/chat/common/commonTypes.ts @@ -198,7 +198,7 @@ export type ChatResponse = FetchResponse; export type ChatResponses = FetchResponse; -function getRateLimitMessage(fetchResult: ChatFetchError): string { +function getRateLimitMessage(fetchResult: ChatFetchError, copilotPlan: string | undefined): string { if (fetchResult.type !== ChatFetchResponseType.RateLimited) { throw new Error('Expected RateLimited error'); } @@ -225,8 +225,41 @@ function getRateLimitMessage(fetchResult: ChatFetchError): string { }); } if (fetchResult.capiError?.code?.startsWith('user_global_rate_limited')) { + if (copilotPlan === 'free' || copilotPlan === 'individual' || copilotPlan === 'individual_pro') { + return l10n.t({ + message: 'You\'ve hit your global rate limit. Please upgrade your plan or wait {0} for your limit to reset. [Learn More]({1})', + args: [retryAfterString, 'https://aka.ms/github-copilot-rate-limit-error'], + comment: [`{Locked=']({'}`] + }); + } + + return l10n.t({ + message: 'You\'ve hit your global rate limit. Please wait {0} for your limit to reset. [Learn More]({1})', + args: [retryAfterString, 'https://aka.ms/github-copilot-rate-limit-error'], + comment: [`{Locked=']({'}`] + }); + } + if (fetchResult.capiError?.code?.startsWith('user_weekly_rate_limited')) { + if (fetchResult.retryAfter) { + const resetDate = new Date(Date.now() + fetchResult.retryAfter * 1000); + const resetDateString = resetDate.toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: '2-digit' }); + if (copilotPlan === 'free' || copilotPlan === 'individual' || copilotPlan === 'individual_pro') { + return l10n.t({ + message: 'You\'ve reached your weekly rate limit. Please upgrade your plan or wait for your limit to reset on {0}. [Learn More]({1})', + args: [resetDateString, 'https://aka.ms/github-copilot-rate-limit-error'], + comment: [`{Locked=']({'}`] + }); + } + + return l10n.t({ + message: 'You\'ve reached your weekly rate limit. Please wait for your limit to reset on {0}. [Learn More]({1})', + args: [resetDateString, 'https://aka.ms/github-copilot-rate-limit-error'], + comment: [`{Locked=']({'}`] + }); + } + return l10n.t({ - message: 'You\'ve hit your global rate limit. Please upgrade your plan or wait {0} for your limit to reset. [Learn More]({1})', + message: 'You\'ve reached your weekly rate limit. Please wait {0} for your limit to reset. [Learn More]({1})', args: [retryAfterString, 'https://aka.ms/github-copilot-rate-limit-error'], comment: [`{Locked=']({'}`] }); @@ -332,7 +365,7 @@ function getErrorDetailsFromChatFetchErrorInner(fetchResult: ChatFetchError, cop break; case ChatFetchResponseType.RateLimited: details = { - message: getRateLimitMessage(fetchResult), + message: getRateLimitMessage(fetchResult, copilotPlan), level: ChatErrorLevel.Info, isRateLimited: true }; From d40a1359b1b8b8df1597a4966dbf9cb1e73212b6 Mon Sep 17 00:00:00 2001 From: "vs-code-engineering[bot]" Date: Mon, 13 Apr 2026 23:29:47 +0000 Subject: [PATCH 06/11] [cherry-pick] Don't show onboarding on web --- .../contrib/welcomeGettingStarted/browser/startupPage.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts index 42ff55300e834..af332edd79309 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/startupPage.ts @@ -31,6 +31,7 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { AuxiliaryBarMaximizedContext } from '../../../common/contextkeys.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { getActiveElement } from '../../../../base/browser/dom.js'; +import { isWeb } from '../../../../base/common/platform.js'; import { IOnboardingService } from '../../welcomeOnboarding/common/onboardingService.js'; import { ONBOARDING_STORAGE_KEY } from '../../welcomeOnboarding/common/onboardingTypes.js'; @@ -235,6 +236,10 @@ export class StartupPageRunnerContribution extends Disposable implements IWorkbe return; // skip welcome flag is set } + if (isWeb && !this.environmentService.remoteAuthority) { + return; // not supported on web without remote authority (e.g. github.dev) + } + if (!this.configurationService.getValue('workbench.welcomePage.experimentalOnboarding')) { return; // experimental onboarding is disabled } From 842d292fb07f0b45212b31b3a6f5110537485139 Mon Sep 17 00:00:00 2001 From: "vs-code-engineering[bot]" <122617954+vs-code-engineering[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 03:49:23 +0000 Subject: [PATCH 07/11] [cherry-pick] AI Customization UX fixes batch (#309304) (#309704) Co-authored-by: vs-code-engineering[bot] --- .../aiCustomizationManagementEditor.ts | 156 ++++++------------ .../aiCustomizationWelcomePage.ts | 15 +- .../aiCustomizationWelcomePageClassic.ts | 4 +- ...CustomizationWelcomePagePromptLaunchers.ts | 69 +++++++- .../media/aiCustomizationManagement.css | 60 ------- .../aiCustomizationWelcomePromptLaunchers.css | 27 +++ ...aiCustomizationManagementEditor.fixture.ts | 3 +- .../aiCustomizationWelcomePages.fixture.ts | 3 + 8 files changed, 157 insertions(+), 180 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts index 170e64f301108..720e380f8c55b 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts @@ -70,7 +70,6 @@ import { IResolvedTextEditorModel, ITextModelService } from '../../../../../edit import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { getSimpleEditorOptions } from '../../../codeEditor/browser/simpleEditorOptions.js'; import { IWorkingCopyService } from '../../../../services/workingCopy/common/workingCopyService.js'; -import { IFileDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { INotificationService } from '../../../../../platform/notification/common/notification.js'; @@ -88,7 +87,6 @@ import { ICustomizationHarnessService, CustomizationHarness, matchesWorkspaceSub import { ChatConfiguration } from '../../common/constants.js'; import { AICustomizationWelcomePage } from './aiCustomizationWelcomePage.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { IChatWidgetService } from '../chat.js'; const $ = DOM.$; @@ -172,6 +170,7 @@ interface ISectionItem { readonly id: AICustomizationManagementSection; readonly label: string; readonly icon: ThemeIcon; + readonly description: string; count: number; } @@ -210,20 +209,25 @@ interface ISectionItemTemplateData { readonly icon: HTMLElement; readonly label: HTMLElement; readonly count: HTMLElement; + readonly templateDisposables: DisposableStore; } class SectionItemRenderer implements IListRenderer { readonly templateId = 'sectionItem'; + constructor(private readonly hoverService: IHoverService) { } + renderTemplate(container: HTMLElement): ISectionItemTemplateData { container.classList.add('section-list-item'); const icon = DOM.append(container, $('.section-icon')); const label = DOM.append(container, $('.section-label')); const count = DOM.append(container, $('.section-count')); - return { container, icon, label, count }; + const templateDisposables = new DisposableStore(); + return { container, icon, label, count, templateDisposables }; } renderElement(element: ISectionItem, index: number, templateData: ISectionItemTemplateData): void { + templateData.templateDisposables.clear(); templateData.icon.className = 'section-icon'; templateData.icon.classList.add(...ThemeIcon.asClassNameArray(element.icon)); templateData.label.textContent = element.label; @@ -234,9 +238,12 @@ class SectionItemRenderer implements IListRenderer this.disposeBuiltinEditingSessions())); // Build sections from the workspace service configuration - const sectionInfo: Record = { - [AICustomizationManagementSection.Agents]: { label: localize('agents', "Agents"), icon: agentIcon }, - [AICustomizationManagementSection.Skills]: { label: localize('skills', "Skills"), icon: skillIcon }, - [AICustomizationManagementSection.Instructions]: { label: localize('instructions', "Instructions"), icon: instructionsIcon }, - [AICustomizationManagementSection.Prompts]: { label: localize('prompts', "Prompts"), icon: promptIcon }, - [AICustomizationManagementSection.Hooks]: { label: localize('hooks', "Hooks"), icon: hookIcon }, - [AICustomizationManagementSection.McpServers]: { label: localize('mcpServers', "MCP Servers"), icon: Codicon.server }, - [AICustomizationManagementSection.Plugins]: { label: localize('plugins', "Plugins"), icon: pluginIcon }, - [AICustomizationManagementSection.Models]: { label: localize('models', "Models"), icon: Codicon.vm }, + const sectionInfo: Record = { + [AICustomizationManagementSection.Agents]: { label: localize('agents', "Agents"), icon: agentIcon, description: localize('agentsDesc', "Define custom agents with specialized personas, tool access, and instructions for specific tasks.") }, + [AICustomizationManagementSection.Skills]: { label: localize('skills', "Skills"), icon: skillIcon, description: localize('skillsDesc', "Create reusable skill files that provide domain-specific knowledge and workflows.") }, + [AICustomizationManagementSection.Instructions]: { label: localize('instructions', "Instructions"), icon: instructionsIcon, description: localize('instructionsDesc', "Set always-on instructions that guide AI behavior across your workspace or user profile.") }, + [AICustomizationManagementSection.Prompts]: { label: localize('prompts', "Prompts"), icon: promptIcon, description: localize('promptsDesc', "Reusable prompt templates that can be invoked as slash commands.") }, + [AICustomizationManagementSection.Hooks]: { label: localize('hooks', "Hooks"), icon: hookIcon, description: localize('hooksDesc', "Configure automated actions triggered by events like saving files or running tasks.") }, + [AICustomizationManagementSection.McpServers]: { label: localize('mcpServers', "MCP Servers"), icon: Codicon.server, description: localize('mcpServersDesc', "Connect external tool servers that extend AI capabilities with custom tools and data sources.") }, + [AICustomizationManagementSection.Plugins]: { label: localize('plugins', "Plugins"), icon: pluginIcon, description: localize('pluginsDesc', "Install and manage agent plugins that add additional tools, skills, and integrations.") }, + [AICustomizationManagementSection.Models]: { label: localize('models', "Models"), icon: Codicon.vm, description: localize('modelsDesc', "Configure and manage language models available for use.") }, }; for (const id of this.workspaceService.managementSections) { const info = sectionInfo[id]; @@ -432,9 +432,7 @@ export class AICustomizationManagementEditor extends EditorPane { layout: (width, _, height) => { this.sidebarContainer.style.width = `${width}px`; if (height !== undefined) { - const footerHeight = this.folderPickerContainer?.offsetHeight ?? 0; - const listHeight = height - 8 - footerHeight; - this.sectionsList.layout(listHeight, width); + this.sectionsList.layout(height - 8, width); } }, }, savedWidth, undefined, true); @@ -544,7 +542,7 @@ export class AICustomizationManagementEditor extends EditorPane { 'AICustomizationManagementSections', sectionsListContainer, new SectionItemDelegate(), - [new SectionItemRenderer()], + [new SectionItemRenderer(this.hoverService)], { multipleSelectionSupport: false, setRowLineHeight: false, @@ -617,10 +615,6 @@ export class AICustomizationManagementEditor extends EditorPane { } })); - // Folder picker (sessions window only) - if (this.workspaceService.isSessionsWindow) { - this.createFolderPicker(sidebarContent); - } } private createSidebarHeader(sidebarContent: HTMLElement): void { @@ -629,6 +623,7 @@ export class AICustomizationManagementEditor extends EditorPane { // Home/overview button const homeButton = this.homeButton = DOM.append(headerRow, $('button.sidebar-home-button')); homeButton.setAttribute('aria-label', localize('homeButton', "Overview")); + this.editorDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), homeButton, localize('homeButtonTooltip', "Back to overview"))); const homeIcon = DOM.append(homeButton, $('span.sidebar-home-icon')); homeIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.home)); homeIcon.setAttribute('aria-hidden', 'true'); @@ -741,67 +736,6 @@ export class AICustomizationManagementEditor extends EditorPane { }); } - private createFolderPicker(sidebarContent: HTMLElement): void { - const footer = this.folderPickerContainer = DOM.append(sidebarContent, $('.sidebar-folder-picker')); - - const button = DOM.append(footer, $('button.folder-picker-button')); - button.setAttribute('aria-label', localize('browseFolder', "Browse folder")); - - const folderIcon = DOM.append(button, $(`.codicon.codicon-${Codicon.folder.id}`)); - folderIcon.classList.add('folder-picker-icon'); - - this.folderPickerLabel = DOM.append(button, $('span.folder-picker-label')); - - this.folderPickerClearButton = DOM.append(footer, $('button.folder-picker-clear')); - this.folderPickerClearButton.setAttribute('aria-label', localize('clearFolderOverride', "Reset to session folder")); - DOM.append(this.folderPickerClearButton, $(`.codicon.codicon-${Codicon.close.id}`)); - - // Clicking the main button opens the folder dialog - this.editorDisposables.add(DOM.addDisposableListener(button, 'click', () => { - this.browseForFolder(); - })); - - // Clear button resets to session default - this.editorDisposables.add(DOM.addDisposableListener(this.folderPickerClearButton, 'click', () => { - this.workspaceService.clearOverrideProjectRoot(); - })); - - // Hover showing full path - this.editorDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), button, () => { - const root = this.workspaceService.getActiveProjectRoot(); - return root?.fsPath ?? ''; - })); - - // Keep label and clear button in sync with the active root - this.editorDisposables.add(autorun(reader => { - const root = this.workspaceService.activeProjectRoot.read(reader); - const hasOverride = this.workspaceService.hasOverrideProjectRoot.read(reader); - this.updateFolderPickerLabel(root, hasOverride); - })); - } - - private updateFolderPickerLabel(root: URI | undefined, hasOverride: boolean): void { - if (this.folderPickerLabel) { - this.folderPickerLabel.textContent = root ? basename(root) : localize('noFolder', "No folder"); - } - if (this.folderPickerClearButton) { - this.folderPickerClearButton.style.display = hasOverride ? '' : 'none'; - } - } - - private async browseForFolder(): Promise { - const result = await this.fileDialogService.showOpenDialog({ - canSelectFolders: true, - canSelectFiles: false, - canSelectMany: false, - title: localize('selectFolder', "Select Folder to Explore"), - defaultUri: this.workspaceService.getActiveProjectRoot(), - }); - if (result?.[0]) { - this.workspaceService.setOverrideProjectRoot(result[0]); - } - } - private createWelcomePage(parent: HTMLElement): void { this.welcomePage = this.editorDisposables.add(new AICustomizationWelcomePage( parent, @@ -814,33 +748,35 @@ export class AICustomizationManagementEditor extends EditorPane { this.group.closeEditor(this.input); } }, - prefillChat: (query, options) => { - if (this.workspaceService.isSessionsWindow) { - const widget = this.chatWidgetService.lastFocusedWidget; - if (widget) { - this.chatWidgetService.reveal(widget).then(() => { - widget.setInput(query); - widget.focusInput(); - }); - } else { + prefillChat: async (query, options) => { + try { + if (this.workspaceService.isSessionsWindow) { const sessionsViewId = 'workbench.view.sessions.chat'; - this.viewsService.openView(sessionsViewId, true).then(view => { - const chatView = view as unknown as { prefillInput?(text: string): void; sendQuery?(text: string): void } | undefined; - if (options?.isPartialQuery && chatView?.prefillInput) { - chatView.prefillInput(query); - } else if (chatView?.sendQuery) { - chatView.sendQuery(query); - } - }); + if (options?.newChat) { + await this.commandService.executeCommand('workbench.action.sessions.newChat'); + } + const view = await this.viewsService.openView(sessionsViewId, true); + const chatView = view as unknown as { prefillInput?(text: string): void; sendQuery?(text: string): void } | undefined; + if (options?.isPartialQuery && chatView?.prefillInput) { + chatView.prefillInput(query); + } else if (chatView?.sendQuery) { + chatView.sendQuery(query); + } + } else { + if (options?.newChat) { + await this.commandService.executeCommand('workbench.action.chat.newChat'); + } + await this.commandService.executeCommand('workbench.action.chat.open', { query, isPartialQuery: options?.isPartialQuery ?? false }); } - } else { - this.commandService.executeCommand('workbench.action.chat.open', { query, isPartialQuery: options?.isPartialQuery ?? false }); + } catch (err) { + onUnexpectedError(err); } }, }, this.commandService, this.workspaceService, this.configurationService, + this.hoverService, )); this.welcomePage.rebuildCards(new Set(this.sections.map(s => s.id))); } @@ -1050,6 +986,7 @@ export class AICustomizationManagementEditor extends EditorPane { // Clear persisted section so welcome shows next time this.storageService.remove(AI_CUSTOMIZATION_MANAGEMENT_SELECTED_SECTION_KEY, StorageScope.PROFILE); + this.welcomePage?.reset(); this.updateContentVisibility(); this.ensureSectionsListReflectsActiveSection(undefined); } @@ -1529,6 +1466,7 @@ export class AICustomizationManagementEditor extends EditorPane { this.editorActionButton = DOM.append(editorHeader, $('button.editor-back-button')); this.editorActionButton.setAttribute('aria-label', localize('backToList', "Back to list")); + this.editorDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this.editorActionButton, localize('backToListTooltip', "Back to list"))); this.editorActionButtonIcon = DOM.append(this.editorActionButton, $(`.codicon.codicon-${Codicon.arrowLeft.id}.editor-action-button-icon`)); this.editorActionButtonIcon.setAttribute('aria-hidden', 'true'); this.editorDisposables.add(DOM.addDisposableListener(this.editorActionButton, 'click', () => { @@ -1962,6 +1900,7 @@ export class AICustomizationManagementEditor extends EditorPane { const detailHeader = DOM.append(this.mcpDetailContainer, $('.editor-header')); const backButton = DOM.append(detailHeader, $('button.editor-back-button')); backButton.setAttribute('aria-label', localize('backToMcpList', "Back to MCP servers")); + this.editorDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), backButton, localize('backToMcpListTooltip', "Back to MCP servers"))); const backIconEl = DOM.append(backButton, $(`.codicon.codicon-${Codicon.arrowLeft.id}`)); backIconEl.setAttribute('aria-hidden', 'true'); this.editorDisposables.add(DOM.addDisposableListener(backButton, 'click', () => { @@ -2025,6 +1964,7 @@ export class AICustomizationManagementEditor extends EditorPane { const detailHeader = DOM.append(this.pluginDetailContainer, $('.editor-header')); const backButton = DOM.append(detailHeader, $('button.editor-back-button')); backButton.setAttribute('aria-label', localize('backToPluginList', "Back to plugins")); + this.editorDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), backButton, localize('backToPluginListTooltip', "Back to plugins"))); const backIconEl = DOM.append(backButton, $(`.codicon.codicon-${Codicon.arrowLeft.id}`)); backIconEl.setAttribute('aria-hidden', 'true'); this.editorDisposables.add(DOM.addDisposableListener(backButton, 'click', () => { diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePage.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePage.ts index f899e74404820..5fcbc7cc20b22 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePage.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePage.ts @@ -11,6 +11,7 @@ import { AICustomizationManagementSection, AI_CUSTOMIZATION_WELCOME_PAGE_VARIANT import { IAICustomizationWorkspaceService, IWelcomePageFeatures } from '../../common/aiCustomizationWorkspaceService.js'; import { ClassicAICustomizationWelcomePage } from './aiCustomizationWelcomePageClassic.js'; import { PromptLaunchersAICustomizationWelcomePage } from './aiCustomizationWelcomePagePromptLaunchers.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; const $ = DOM.$; @@ -21,14 +22,19 @@ export interface IWelcomePageCallbacks { /** * Prefill the chat input with a query. In the sessions window this * uses the sessions chat widget; in core VS Code it opens the chat view. + * + * @param options.newChat When true, always opens a new chat instead of + * reusing the active one. */ - prefillChat(query: string, options?: { isPartialQuery?: boolean }): void; + prefillChat(query: string, options?: { isPartialQuery?: boolean; newChat?: boolean }): void; } export interface IAICustomizationWelcomePageImplementation extends IDisposable { readonly container: HTMLElement; rebuildCards(visibleSectionIds: ReadonlySet): void; focus(): void; + /** Called when the welcome page becomes visible after navigation — clears any transient state. */ + reset?(): void; } /** @@ -48,6 +54,7 @@ export class AICustomizationWelcomePage extends Disposable { private readonly commandService: ICommandService, private readonly workspaceService: IAICustomizationWorkspaceService, private readonly configurationService: IConfigurationService, + private readonly hoverService: IHoverService, ) { super(); @@ -72,6 +79,10 @@ export class AICustomizationWelcomePage extends Disposable { this.implementation.value?.focus(); } + reset(): void { + this.implementation.value?.reset?.(); + } + private renderImplementation(): void { DOM.clearNode(this.container); this.implementation.value = this.createImplementation(); @@ -81,7 +92,7 @@ export class AICustomizationWelcomePage extends Disposable { private createImplementation(): IAICustomizationWelcomePageImplementation { switch (this.getVariant()) { case 'promptLaunchers': - return new PromptLaunchersAICustomizationWelcomePage(this.container, this.welcomePageFeatures, this.callbacks, this.commandService, this.workspaceService); + return new PromptLaunchersAICustomizationWelcomePage(this.container, this.welcomePageFeatures, this.callbacks, this.commandService, this.workspaceService, this.hoverService); case 'classic': default: return new ClassicAICustomizationWelcomePage(this.container, this.welcomePageFeatures, this.callbacks, this.commandService, this.workspaceService); diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePageClassic.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePageClassic.ts index 95ac4bd0be771..1c37166a03fe6 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePageClassic.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePageClassic.ts @@ -110,7 +110,7 @@ export class ClassicAICustomizationWelcomePage extends Disposable implements IAI this._register(DOM.addDisposableListener(gettingStarted, 'click', () => { this.callbacks.closeEditor(); if (this.workspaceService.isSessionsWindow) { - this.callbacks.prefillChat('Generate agent customizations. ', { isPartialQuery: true }); + this.callbacks.prefillChat('Generate agent customizations. ', { isPartialQuery: true, newChat: true }); } else { this.commandService.executeCommand('workbench.action.chat.open', { query: '/init ', isPartialQuery: true }); } @@ -169,7 +169,7 @@ export class ClassicAICustomizationWelcomePage extends Disposable implements IAI this.callbacks.closeEditor(); if (this.workspaceService.isSessionsWindow) { const typeLabel = category.label.toLowerCase().replace(/s$/, ''); - this.callbacks.prefillChat(`Create me a custom ${typeLabel} that `, { isPartialQuery: true }); + this.callbacks.prefillChat(`Create me a custom ${typeLabel} that `, { isPartialQuery: true, newChat: true }); } else { this.workspaceService.generateCustomization(category.promptType!); } diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePagePromptLaunchers.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePagePromptLaunchers.ts index 69a31e7d84831..685f9e226f786 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePagePromptLaunchers.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationWelcomePagePromptLaunchers.ts @@ -15,6 +15,8 @@ import { agentIcon, instructionsIcon, pluginIcon, skillIcon, hookIcon } from './ import { IAICustomizationWorkspaceService, IWelcomePageFeatures } from '../../common/aiCustomizationWorkspaceService.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; import type { IAICustomizationWelcomePageImplementation, IWelcomePageCallbacks } from './aiCustomizationWelcomePage.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { getDefaultHoverDelegate } from '../../../../../base/browser/ui/hover/hoverDelegateFactory.js'; const $ = DOM.$; @@ -34,6 +36,10 @@ export class PromptLaunchersAICustomizationWelcomePage extends Disposable implem private cardsContainer: HTMLElement | undefined; private inputElement: HTMLInputElement | undefined; + private sentLabel: HTMLElement | undefined; + private submitBtn: HTMLElement | undefined; + private inputRow: HTMLElement | undefined; + private readonly categoryDescriptions: IPromptLaunchersCategoryDescription[] = [ { id: AICustomizationManagementSection.Agents, @@ -83,6 +89,7 @@ export class PromptLaunchersAICustomizationWelcomePage extends Disposable implem private readonly callbacks: IWelcomePageCallbacks, _commandService: ICommandService, private readonly workspaceService: IAICustomizationWorkspaceService, + private readonly hoverService: IHoverService, ) { super(); @@ -107,39 +114,89 @@ export class PromptLaunchersAICustomizationWelcomePage extends Disposable implem description.textContent = localize('gettingStartedDesc', "Describe your preferences and conventions to draft agents, skills, and instructions."); const inputRow = DOM.append(gettingStarted, $('.welcome-prompts-input-row')); + this.inputRow = inputRow; this.inputElement = DOM.append(inputRow, $('input.welcome-prompts-input')) as HTMLInputElement; this.inputElement.type = 'text'; this.inputElement.placeholder = localize('workflowInputPlaceholder', "Prefer concise commits, thorough reviews, and tested code..."); this.inputElement.setAttribute('aria-label', localize('workflowInputAriaLabel', "Describe your preferences to customize your agent")); const submitBtn = DOM.append(inputRow, $('button.welcome-prompts-input-submit')); + this.submitBtn = submitBtn; submitBtn.setAttribute('aria-label', localize('workflowSubmitAriaLabel', "Customize agent")); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), submitBtn, localize('workflowSubmitTooltip', "Open in Chat"))); const chevron = DOM.append(submitBtn, $('span.codicon.codicon-arrow-up')); chevron.setAttribute('aria-hidden', 'true'); + const updateSubmitState = () => { + const hasValue = !!(this.inputElement?.value?.trim()); + (submitBtn as HTMLButtonElement).disabled = !hasValue; + submitBtn.classList.toggle('welcome-prompts-input-submit-disabled', !hasValue); + }; + const submit = () => { const value = this.inputElement?.value?.trim(); - this.callbacks.closeEditor(); + if (!value) { + return; + } let query: string; if (this.workspaceService.isSessionsWindow) { - query = value ? `Generate agent customizations. ${value}` : 'Generate agent customizations. '; + query = `Generate agent customizations. ${value}`; } else { - query = value ? `/init ${value}` : '/init '; + query = `/init ${value}`; } - this.callbacks.prefillChat(query, { isPartialQuery: !value }); + + // Show confirmation immediately — before prefillChat so it's visible + // even if prefillChat navigates focus away from this editor + if (this.inputElement) { + this.inputElement.value = ''; + } + updateSubmitState(); + inputRow.classList.add('sent'); + submitBtn.style.display = 'none'; + if (this.sentLabel) { + this.sentLabel.remove(); + } + this.sentLabel = DOM.append(inputRow, $('span.welcome-prompts-sent-label')); + this.sentLabel.textContent = localize('sentToChat', "Sent to chat \u2713"); + + this.callbacks.prefillChat(query, { isPartialQuery: false, newChat: true }); }; - this._register(DOM.addDisposableListener(submitBtn, 'click', submit)); + + this._register(DOM.addDisposableListener(submitBtn, 'click', e => { e.stopPropagation(); submit(); })); this._register(DOM.addDisposableListener(this.inputElement, 'keydown', (e: KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); submit(); } })); + this._register(DOM.addDisposableListener(this.inputElement, 'input', () => { + updateSubmitState(); + // Typing restores the input row from sent state + this._clearSentState(); + })); + updateSubmitState(); } this.cardsContainer = DOM.append(welcomeInner, $('.welcome-prompts-cards')); } + private _clearSentState(): void { + if (this.sentLabel) { + this.sentLabel.remove(); + this.sentLabel = undefined; + } + if (this.submitBtn) { + this.submitBtn.style.display = ''; + } + if (this.inputRow) { + this.inputRow.classList.remove('sent'); + } + } + + reset(): void { + this._clearSentState(); + } + rebuildCards(visibleSectionIds: ReadonlySet): void { if (!this.cardsContainer) { return; @@ -175,7 +232,7 @@ export class PromptLaunchersAICustomizationWelcomePage extends Disposable implem this.callbacks.closeEditor(); if (this.workspaceService.isSessionsWindow) { const typeLabel = category.label.toLowerCase().replace(/s$/, ''); - this.callbacks.prefillChat(`Create me a custom ${typeLabel} that `, { isPartialQuery: true }); + this.callbacks.prefillChat(`Create me a custom ${typeLabel} that `, { isPartialQuery: true, newChat: true }); } else { this.workspaceService.generateCustomization(category.promptType!); } diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationManagement.css b/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationManagement.css index 32e99b01d0b1c..4517c4e41451c 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationManagement.css +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationManagement.css @@ -77,66 +77,6 @@ overflow: hidden; } -/* Folder picker footer (sessions window only) */ -.ai-customization-management-editor .sidebar-folder-picker { - flex-shrink: 0; - display: flex; - align-items: center; - gap: 2px; - padding: 6px 4px; - border-top: 1px solid var(--vscode-sideBarSectionHeader-border, transparent); -} - -.ai-customization-management-editor .folder-picker-button { - display: flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 0; - padding: 4px 6px; - border: none; - border-radius: 4px; - background: transparent; - color: var(--vscode-descriptionForeground); - cursor: pointer; - font-size: 13px; -} - -.ai-customization-management-editor .folder-picker-button:hover { - background-color: var(--vscode-list-hoverBackground); -} - -.ai-customization-management-editor .folder-picker-icon { - flex-shrink: 0; - font-size: 14px; -} - -.ai-customization-management-editor .folder-picker-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.ai-customization-management-editor .folder-picker-clear { - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - width: 22px; - height: 22px; - padding: 0; - border: none; - border-radius: 4px; - background: transparent; - color: var(--vscode-descriptionForeground); - cursor: pointer; - font-size: 12px; -} - -.ai-customization-management-editor .folder-picker-clear:hover { - background-color: var(--vscode-list-hoverBackground); -} - /* Section list items */ .ai-customization-management-editor .section-list-item { display: flex; diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationWelcomePromptLaunchers.css b/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationWelcomePromptLaunchers.css index a1b4e449debc6..4f917fbcdc93c 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationWelcomePromptLaunchers.css +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/media/aiCustomizationWelcomePromptLaunchers.css @@ -129,6 +129,33 @@ outline-offset: -1px; } +.ai-customization-management-editor .welcome-prompts-input-submit.welcome-prompts-input-submit-disabled { + opacity: 0.4; + cursor: default; + pointer-events: none; +} + +.ai-customization-management-editor .welcome-prompts-input-row.sent { + justify-content: center; +} + +.ai-customization-management-editor .welcome-prompts-input-row.sent .welcome-prompts-input { + display: none; +} + +.ai-customization-management-editor .welcome-prompts-sent-label { + font-size: 12px; + color: var(--vscode-descriptionForeground); + padding: 0 8px; + white-space: nowrap; + animation: welcomePromptsSentFadeIn 0.2s ease; +} + +@keyframes welcomePromptsSentFadeIn { + from { opacity: 0; transform: translateY(2px); } + to { opacity: 1; transform: translateY(0); } +} + .ai-customization-management-editor .welcome-prompts-input-helper { font-size: 12px; line-height: 1.5; diff --git a/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationManagementEditor.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationManagementEditor.fixture.ts index 6d9f30a89e80e..d79ff8d6bd526 100644 --- a/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationManagementEditor.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationManagementEditor.fixture.ts @@ -14,7 +14,7 @@ import { constObservable, observableValue } from '../../../../../base/common/obs import { URI } from '../../../../../base/common/uri.js'; import { mock } from '../../../../../base/test/common/mock.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; -import { IDialogService, IFileDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { IListService, ListService } from '../../../../../platform/list/browser/listService.js'; import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; @@ -499,7 +499,6 @@ async function renderEditor(ctx: ComponentFixtureContext, options: IRenderEditor reg.defineInstance(IWorkingCopyService, new class extends mock() { override readonly onDidChangeDirty = Event.None; }()); - reg.defineInstance(IFileDialogService, new class extends mock() { }()); reg.defineInstance(IExtensionService, new class extends mock() { }()); reg.defineInstance(IQuickInputService, new class extends mock() { }()); reg.defineInstance(IViewsService, new class extends mock() { diff --git a/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationWelcomePages.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationWelcomePages.fixture.ts index ae229cc4c76ea..47ed3b442e240 100644 --- a/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationWelcomePages.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/sessions/aiCustomizationWelcomePages.fixture.ts @@ -18,6 +18,7 @@ import { AICustomizationWelcomePage } from '../../../../contrib/chat/browser/aiC import { ClassicAICustomizationWelcomePage } from '../../../../contrib/chat/browser/aiCustomization/aiCustomizationWelcomePageClassic.js'; import { PromptLaunchersAICustomizationWelcomePage } from '../../../../contrib/chat/browser/aiCustomization/aiCustomizationWelcomePagePromptLaunchers.js'; import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils.js'; +import { NullHoverService } from '../../../../../platform/hover/test/browser/nullHoverService.js'; import '../../../../../platform/theme/common/colors/inputColors.js'; import '../../../../../platform/theme/common/colors/listColors.js'; @@ -115,6 +116,7 @@ function renderPromptLaunchersWelcomePage(ctx: ComponentFixtureContext): void { }, createMockCommandService(), workspaceService, + NullHoverService, )); page.rebuildCards(visibleSections); } @@ -137,6 +139,7 @@ function renderSelectedWelcomePage(ctx: ComponentFixtureContext, variant: AICust createMockCommandService(), workspaceService, configService, + NullHoverService, )); page.rebuildCards(visibleSections); } From f029795bb6446ba50048080fc7d6672ad7522b70 Mon Sep 17 00:00:00 2001 From: "vs-code-engineering[bot]" <122617954+vs-code-engineering[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:49:51 +0000 Subject: [PATCH 08/11] [cherry-pick] Stop sending top_p to Anthropic Messages API (#309719) Co-authored-by: vs-code-engineering[bot] --- extensions/copilot/src/platform/endpoint/node/messagesApi.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/copilot/src/platform/endpoint/node/messagesApi.ts b/extensions/copilot/src/platform/endpoint/node/messagesApi.ts index 7e0ac181c2d5c..8f298ca2b4cb5 100644 --- a/extensions/copilot/src/platform/endpoint/node/messagesApi.ts +++ b/extensions/copilot/src/platform/endpoint/node/messagesApi.ts @@ -225,7 +225,6 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I ...messagesResult, stream: true, tools: finalTools.length > 0 ? finalTools : undefined, - top_p: options.postOptions.top_p, max_tokens: options.postOptions.max_tokens, thinking: thinkingConfig, ...(effort ? { output_config: { effort } } : {}), From c36ec58d22e0d889b14d12c9f7af4508ad394f15 Mon Sep 17 00:00:00 2001 From: "vs-code-engineering[bot]" <122617954+vs-code-engineering[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 06:29:28 +0000 Subject: [PATCH 09/11] [cherry-pick] Explicitly set display: summarized (#309721) Co-authored-by: vs-code-engineering[bot] --- .../copilot/src/platform/endpoint/node/messagesApi.ts | 4 ++-- .../src/platform/endpoint/test/node/messagesApi.spec.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/copilot/src/platform/endpoint/node/messagesApi.ts b/extensions/copilot/src/platform/endpoint/node/messagesApi.ts index 8f298ca2b4cb5..28095403bcdea 100644 --- a/extensions/copilot/src/platform/endpoint/node/messagesApi.ts +++ b/extensions/copilot/src/platform/endpoint/node/messagesApi.ts @@ -142,12 +142,12 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I // is configured for the model, and the model supports thinking. reasoningEffort (if present) // is used only to configure the effort level when thinking is enabled, not to gate it. const reasoningEffort = options.modelCapabilities?.reasoningEffort; - let thinkingConfig: { type: 'enabled' | 'adaptive'; budget_tokens?: number } | undefined; + let thinkingConfig: { type: 'enabled' | 'adaptive'; budget_tokens?: number; display?: 'summarized' } | undefined; if (options.modelCapabilities?.enableThinking) { const configuredBudget = configurationService.getConfig(ConfigKey.AnthropicThinkingBudget); const thinkingExplicitlyDisabled = configuredBudget === 0; if (endpoint.supportsAdaptiveThinking && !thinkingExplicitlyDisabled) { - thinkingConfig = { type: 'adaptive' }; + thinkingConfig = { type: 'adaptive', display: 'summarized' }; } else if (!thinkingExplicitlyDisabled && endpoint.maxThinkingBudget && endpoint.minThinkingBudget) { const maxTokens = options.postOptions.max_tokens ?? 1024; const minBudget = endpoint.minThinkingBudget ?? 1024; diff --git a/extensions/copilot/src/platform/endpoint/test/node/messagesApi.spec.ts b/extensions/copilot/src/platform/endpoint/test/node/messagesApi.spec.ts index 4a53bdbe8eb1e..da00bbb684bd2 100644 --- a/extensions/copilot/src/platform/endpoint/test/node/messagesApi.spec.ts +++ b/extensions/copilot/src/platform/endpoint/test/node/messagesApi.spec.ts @@ -737,7 +737,7 @@ describe('createMessagesRequestBody reasoning effort', () => { const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint); - expect(body.thinking).toEqual({ type: 'adaptive' }); + expect(body.thinking).toEqual({ type: 'adaptive', display: 'summarized' }); expect(body.output_config).toEqual({ effort: 'high' }); }); @@ -752,7 +752,7 @@ describe('createMessagesRequestBody reasoning effort', () => { const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint); - expect(body.thinking).toEqual({ type: 'adaptive' }); + expect(body.thinking).toEqual({ type: 'adaptive', display: 'summarized' }); expect(body.output_config).toBeUndefined(); }); @@ -767,7 +767,7 @@ describe('createMessagesRequestBody reasoning effort', () => { const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint); - expect(body.thinking).toEqual({ type: 'adaptive' }); + expect(body.thinking).toEqual({ type: 'adaptive', display: 'summarized' }); expect(body.output_config).toBeUndefined(); }); @@ -797,7 +797,7 @@ describe('createMessagesRequestBody reasoning effort', () => { const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint); - expect(body.thinking).toEqual({ type: 'adaptive' }); + expect(body.thinking).toEqual({ type: 'adaptive', display: 'summarized' }); expect(body.output_config).toBeUndefined(); }); From a48a840e3fd15c2210adb55fed5b0d25d6403d7d Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:40:20 +0200 Subject: [PATCH 10/11] Manually add licenses requested by OSS tool (#309754) --- cglicenses.json | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/cglicenses.json b/cglicenses.json index 19b217756dfc5..3dffb9585b724 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -830,5 +830,111 @@ "", "THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ] + }, + { + "name": "@anthropic-ai/claude-agent-sdk", + "licenseDetail": [ + "© Anthropic PBC. All rights reserved. Use is subject to Anthropic's Commercial Terms of Service." + ] + }, + { + "name": "@github/blackbird-external-ingest-utils", + "licenseDetail": [ + "MIT License" + ] + }, + { + "name": "@microsoft/dev-tunnels-connections", + "licenseDetail": [ + "MIT License" + ] + }, + { + "name": "@microsoft/dev-tunnels-contracts", + "licenseDetail": [ + "MIT License" + ] + }, + { + "name": "@microsoft/dev-tunnels-management", + "licenseDetail": [ + "MIT License" + ] + }, + { + "name": "brorand", + "licenseDetail": [ + "This software is licensed under the MIT License.", + "", + "Copyright Fedor Indutny, 2014.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + "name": "emitter-listener", + "licenseDetail": [ + "BSD-2-Clause" + ] + }, + { + "name": "bignumber.js", + "licenseDetail": [ + "Copyright © <2026> Michael Mclaughlin", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + "name": "miller-rabin", + "licenseDetail": [ + "This software is licensed under the MIT License.", + "", + "Copyright Fedor Indutny, 2014.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + "name": "gcp-metadata", + "fullLicenseTextUri": "https://github.com/googleapis/google-cloud-node-core/blob/76ba85a7f55c6e82943008b4eceb07a0f58b39e1/LICENSE" + }, + { + "name": "randombytes", + "licenseDetail": [ + "MIT License", + "", + "Copyright (c) 2017 crypto-browserify", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ] } ] From d857f5fa5a70c27927f68cc5f730e36246d3e2d4 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 14 Apr 2026 14:01:03 +0500 Subject: [PATCH 11/11] NES: enable trigger on active editor change by default (#309489) (#309528) * NES: enable trigger on active editor change by default - Set triggerOnEditorChangeAfterSeconds default to 10 (was undefined) - Set triggerOnEditorChangeStrategy default to AfterAcceptance (was Always) - Replace unconditional same-line cooldown bypass with smart reset: cooldown is enforced within a file session but clears when switching away and returning, so NES re-triggers on the same line after a round-trip to another file - Update tests to match new defaults and behavior * fix indentation --- extensions/copilot/package.json | 1 + .../vscode-node/inlineEditTriggerer.spec.ts | 34 ++++++++++++------- .../vscode-node/inlineEditTriggerer.ts | 21 +++++++----- .../common/configurationService.ts | 4 +-- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index b00e2f1d0a6b9..74e73d9ca76d3 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -4435,6 +4435,7 @@ "number", "null" ], + "default": 10, "markdownDescription": "%github.copilot.config.inlineEdits.triggerOnEditorChangeAfterSeconds%", "tags": [ "advanced", diff --git a/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts b/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts index 2193f7f6ee84e..972253a394ff0 100644 --- a/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts +++ b/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts @@ -392,6 +392,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; // Configure to trigger on document switch void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); @@ -504,6 +505,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; const triggerAfterSeconds = 30; // Configure to trigger on document switch @@ -930,6 +932,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py'), 'line1\nline2'); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); // Edit doc1 and trigger @@ -1148,6 +1151,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); // Fire an undo change — this should still update lastEditTimestamp @@ -1216,25 +1220,31 @@ suite('InlineEditTriggerer', () => { assert.strictEqual(firedEvents.length, 0, 'Should not fire on doc switch during rejection cooldown'); }); - test('Same-line cooldown is bypassed when triggerOnActiveEditorChange is set', () => { - const { document, textEditor } = createTextDocument(); + test('Same-line cooldown is bypassed after switching away and back', () => { + const doc1 = createTextDocument(undefined, Uri.file('file1.py')); + const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; - // Enable triggerOnActiveEditorChange — this bypasses same-line cooldown - void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); - - triggerTextChange(document); - triggerTextSelectionChange(textEditor, new Selection(0, 5, 0, 5)); + // Edit doc1 and trigger on line 0 + triggerTextChange(doc1.document); + triggerTextSelectionChange(doc1.textEditor, new Selection(0, 5, 0, 5)); const initialCount = firedEvents.length; assert.isAtLeast(initialCount, 1, 'First trigger should fire'); - // Same line, different column — normally would be in cooldown, - // but triggerOnActiveEditorChange is set so cooldown is bypassed - triggerTextSelectionChange(textEditor, new Selection(0, 10, 0, 10)); + // Same line — cooldown blocks + triggerTextSelectionChange(doc1.textEditor, new Selection(0, 10, 0, 10)); + assert.strictEqual(firedEvents.length, initialCount, 'Same-line cooldown should block'); + + // Switch to doc2 + triggerTextChange(doc2.document); + triggerTextSelectionChange(doc2.textEditor, new Selection(0, 0, 0, 0)); + const countAfterDoc2 = firedEvents.length; - assert.isAtLeast(firedEvents.length, initialCount + 1, - 'Same-line cooldown should be bypassed when triggerOnActiveEditorChange is set'); + // Switch back to doc1, same line — cooldown should be cleared by the doc switch + triggerTextSelectionChange(doc1.textEditor, new Selection(0, 10, 0, 10)); + assert.isAtLeast(firedEvents.length, countAfterDoc2 + 1, + 'Same-line cooldown should be bypassed after switching away and back'); }); test('Output pane documents are ignored for selection changes', () => { diff --git a/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts b/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts index 9eaf836c1d3f9..b50da76660495 100644 --- a/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts +++ b/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts @@ -157,6 +157,12 @@ export class InlineEditTriggerer extends Disposable { return; } + // When the user switches to a different file and comes back, clear same-line + // cooldowns so the triggerer fires again on the same line. + if (!isSameDoc) { + mostRecentChange.lineNumberTriggers.clear(); + } + const hadRecentEdit = this._hasRecentEdit(mostRecentChange); if (!hadRecentEdit || !this._hasRecentTrigger()) { // The edit is too old or the provider was not triggered recently (we might be @@ -221,17 +227,14 @@ export class InlineEditTriggerer extends Disposable { /** * Returns true if the same-line cooldown is active and we should skip triggering. * - * The cooldown is bypassed when: - * - `triggerOnActiveEditorChange` is configured, OR - * - we're in a notebook cell and the current document differs from the one that - * originally triggered the change (user moved to a different cell). + * The cooldown is bypassed when we're in a notebook cell and the current document + * differs from the one that originally triggered the change (user moved to a + * different cell). + * + * When the user switches to a different file and comes back, line triggers are + * cleared (see {@link _handleSelectionChange}), so the cooldown naturally resets. */ private _isSameLineCooldownActive(mostRecentChange: LastChange, selectionLine: number, currentDocument: vscode.TextDocument): boolean { - const triggerOnActiveEditorChange = this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, this._expService); - if (triggerOnActiveEditorChange) { - return false; // cooldown bypassed - } - // In a notebook, if the user moved to a different cell, bypass the cooldown if (isNotebookCell(currentDocument.uri) && currentDocument !== mostRecentChange.documentTrigger) { return false; // cooldown bypassed diff --git a/extensions/copilot/src/platform/configuration/common/configurationService.ts b/extensions/copilot/src/platform/configuration/common/configurationService.ts index 63cdf7c02f8f1..e2fc192a2304e 100644 --- a/extensions/copilot/src/platform/configuration/common/configurationService.ts +++ b/extensions/copilot/src/platform/configuration/common/configurationService.ts @@ -659,7 +659,7 @@ export namespace ConfigKey { /** Maximum number of tool calls the execution subagent can make */ export const ExecutionSubagentToolCallLimit = defineSetting('chat.executionSubagent.toolCallLimit', ConfigType.ExperimentBased, 10); - export const InlineEditsTriggerOnEditorChangeAfterSeconds = defineAndMigrateExpSetting('chat.advanced.inlineEdits.triggerOnEditorChangeAfterSeconds', 'chat.inlineEdits.triggerOnEditorChangeAfterSeconds', undefined); + export const InlineEditsTriggerOnEditorChangeAfterSeconds = defineAndMigrateExpSetting('chat.advanced.inlineEdits.triggerOnEditorChangeAfterSeconds', 'chat.inlineEdits.triggerOnEditorChangeAfterSeconds', 10); export const InlineEditsNextCursorPredictionDisplayLine = defineAndMigrateExpSetting('chat.advanced.inlineEdits.nextCursorPrediction.displayLine', 'chat.inlineEdits.nextCursorPrediction.displayLine', true); export const InlineEditsNextCursorPredictionCurrentFileMaxTokens = defineAndMigrateExpSetting('chat.advanced.inlineEdits.nextCursorPrediction.currentFileMaxTokens', 'chat.inlineEdits.nextCursorPrediction.currentFileMaxTokens', 3000); export const InlineEditsRenameSymbolSuggestions = defineSetting('chat.inlineEdits.renameSymbolSuggestions', ConfigType.ExperimentBased, true); @@ -777,7 +777,7 @@ export namespace ConfigKey { export const InlineEditsSpeculativeRequestsAutoExpandEditWindowLines = defineTeamInternalSetting('chat.advanced.inlineEdits.speculativeRequestsAutoExpandEditWindowLines', ConfigType.ExperimentBased, SpeculativeRequestsAutoExpandEditWindowLines.Off, SpeculativeRequestsAutoExpandEditWindowLines.VALIDATOR); export const InlineEditsExtraDebounceInlineSuggestion = defineTeamInternalSetting('chat.advanced.inlineEdits.extraDebounceInlineSuggestion', ConfigType.ExperimentBased, 0); export const InlineEditsDebounceOnSelectionChange = defineTeamInternalSetting('chat.advanced.inlineEdits.debounceOnSelectionChange', ConfigType.ExperimentBased, undefined); - export const InlineEditsTriggerOnEditorChangeStrategy = defineTeamInternalSetting('chat.advanced.inlineEdits.triggerOnEditorChangeStrategy', ConfigType.ExperimentBased, triggerOptions.DocumentSwitchTriggerStrategy.Always, triggerOptions.DocumentSwitchTriggerStrategy.VALIDATOR); + export const InlineEditsTriggerOnEditorChangeStrategy = defineTeamInternalSetting('chat.advanced.inlineEdits.triggerOnEditorChangeStrategy', ConfigType.ExperimentBased, triggerOptions.DocumentSwitchTriggerStrategy.AfterAcceptance, triggerOptions.DocumentSwitchTriggerStrategy.VALIDATOR); export const InlineEditsProviderId = defineTeamInternalSetting('chat.advanced.inlineEdits.providerId', ConfigType.ExperimentBased, undefined); export const InlineEditsUnification = defineTeamInternalSetting('chat.advanced.inlineEdits.unification', ConfigType.ExperimentBased, false); export const InlineEditsNextCursorPredictionModelName = defineTeamInternalSetting('chat.advanced.inlineEdits.nextCursorPrediction.modelName', ConfigType.ExperimentBased, 'copilot-suggestions-himalia-001');