From ef748e3095e3808c6f00a798da376a878e6d74cb Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:29:21 -0800 Subject: [PATCH 01/50] Add definition support for nls strings in package.json Fixes #297496 --- .../src/extensionEditingBrowserMain.ts | 4 +- .../src/extensionEditingMain.ts | 4 + .../src/packageDocumentL10nSupport.ts | 77 +++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 extensions/extension-editing/src/packageDocumentL10nSupport.ts diff --git a/extensions/extension-editing/src/extensionEditingBrowserMain.ts b/extensions/extension-editing/src/extensionEditingBrowserMain.ts index f9d6885c6223c..57c969d017020 100644 --- a/extensions/extension-editing/src/extensionEditingBrowserMain.ts +++ b/extensions/extension-editing/src/extensionEditingBrowserMain.ts @@ -5,11 +5,14 @@ import * as vscode from 'vscode'; import { PackageDocument } from './packageDocumentHelper'; +import { PackageDocumentL10nSupport } from './packageDocumentL10nSupport'; export function activate(context: vscode.ExtensionContext) { //package.json suggestions context.subscriptions.push(registerPackageDocumentCompletions()); + //package.json go to definition for NLS strings + context.subscriptions.push(new PackageDocumentL10nSupport()); } function registerPackageDocumentCompletions(): vscode.Disposable { @@ -18,5 +21,4 @@ function registerPackageDocumentCompletions(): vscode.Disposable { return new PackageDocument(document).provideCompletionItems(position, token); } }); - } diff --git a/extensions/extension-editing/src/extensionEditingMain.ts b/extensions/extension-editing/src/extensionEditingMain.ts index c056fbfa975ae..c620b3039541f 100644 --- a/extensions/extension-editing/src/extensionEditingMain.ts +++ b/extensions/extension-editing/src/extensionEditingMain.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { PackageDocument } from './packageDocumentHelper'; +import { PackageDocumentL10nSupport } from './packageDocumentL10nSupport'; import { ExtensionLinter } from './extensionLinter'; export function activate(context: vscode.ExtensionContext) { @@ -15,6 +16,9 @@ export function activate(context: vscode.ExtensionContext) { //package.json code actions for lint warnings context.subscriptions.push(registerCodeActionsProvider()); + // package.json l10n support + context.subscriptions.push(new PackageDocumentL10nSupport()); + context.subscriptions.push(new ExtensionLinter()); } diff --git a/extensions/extension-editing/src/packageDocumentL10nSupport.ts b/extensions/extension-editing/src/packageDocumentL10nSupport.ts new file mode 100644 index 0000000000000..2ec2b876a07fb --- /dev/null +++ b/extensions/extension-editing/src/packageDocumentL10nSupport.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { getLocation, getNodeValue, parseTree, findNodeAtLocation } from 'jsonc-parser'; + + +export class PackageDocumentL10nSupport implements vscode.DefinitionProvider, vscode.Disposable { + + private readonly _registration: vscode.Disposable; + + constructor() { + this._registration = vscode.languages.registerDefinitionProvider( + { language: 'json', pattern: '**/package.json' }, + this, + ); + } + + dispose(): void { + this._registration.dispose(); + } + + public async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { + const nlsRef = this.getNlsReferenceAtPosition(document, position); + if (!nlsRef) { + return undefined; + } + + const nlsUri = vscode.Uri.joinPath(document.uri, '..', 'package.nls.json'); + + try { + const nlsDoc = await vscode.workspace.openTextDocument(nlsUri); + const nlsTree = parseTree(nlsDoc.getText()); + if (!nlsTree) { + return undefined; + } + + const node = findNodeAtLocation(nlsTree, [nlsRef.key]); + if (!node) { + return undefined; + } + + const targetStart = nlsDoc.positionAt(node.offset); + const targetEnd = nlsDoc.positionAt(node.offset + node.length); + return [{ + originSelectionRange: nlsRef.range, + targetUri: nlsUri, + targetRange: new vscode.Range(targetStart, targetEnd), + }]; + } catch { + return undefined; + } + } + + private getNlsReferenceAtPosition(document: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined { + const location = getLocation(document.getText(), document.offsetAt(position)); + if (!location.previousNode || location.previousNode.type !== 'string') { + return undefined; + } + + const value = getNodeValue(location.previousNode); + if (typeof value !== 'string') { + return undefined; + } + + const match = value.match(/^%(.+)%$/); + if (!match) { + return undefined; + } + + const nodeStart = document.positionAt(location.previousNode.offset); + const nodeEnd = document.positionAt(location.previousNode.offset + location.previousNode.length); + return { key: match[1], range: new vscode.Range(nodeStart, nodeEnd) }; + } +} From 88f25bc452b870c61efe79798c53c47c4c01e682 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:20:38 -0800 Subject: [PATCH 02/50] Add reference support too --- .../src/packageDocumentL10nSupport.ts | 191 +++++++++++++++--- 1 file changed, 159 insertions(+), 32 deletions(-) diff --git a/extensions/extension-editing/src/packageDocumentL10nSupport.ts b/extensions/extension-editing/src/packageDocumentL10nSupport.ts index 2ec2b876a07fb..4d844e98d5f71 100644 --- a/extensions/extension-editing/src/packageDocumentL10nSupport.ts +++ b/extensions/extension-editing/src/packageDocumentL10nSupport.ts @@ -4,32 +4,140 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { getLocation, getNodeValue, parseTree, findNodeAtLocation } from 'jsonc-parser'; +import { getLocation, getNodeValue, parseTree, findNodeAtLocation, visit } from 'jsonc-parser'; -export class PackageDocumentL10nSupport implements vscode.DefinitionProvider, vscode.Disposable { +const packageJsonSelector: vscode.DocumentSelector = { language: 'json', pattern: '**/package.json' }; +const packageNlsJsonSelector: vscode.DocumentSelector = { language: 'json', pattern: '**/package.nls.json' }; - private readonly _registration: vscode.Disposable; +export class PackageDocumentL10nSupport implements vscode.DefinitionProvider, vscode.ReferenceProvider, vscode.Disposable { + + private readonly _disposables: vscode.Disposable[] = []; constructor() { - this._registration = vscode.languages.registerDefinitionProvider( - { language: 'json', pattern: '**/package.json' }, - this, - ); + this._disposables.push(vscode.languages.registerDefinitionProvider(packageJsonSelector, this)); + this._disposables.push(vscode.languages.registerDefinitionProvider(packageNlsJsonSelector, this)); + + this._disposables.push(vscode.languages.registerReferenceProvider(packageNlsJsonSelector, this)); + this._disposables.push(vscode.languages.registerReferenceProvider(packageJsonSelector, this)); } dispose(): void { - this._registration.dispose(); + for (const d of this._disposables) { + d.dispose(); + } } public async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { - const nlsRef = this.getNlsReferenceAtPosition(document, position); + const basename = document.uri.path.split('/').pop()?.toLowerCase(); + if (basename === 'package.json') { + return this.provideNlsValueDefinition(document, position); + } + + if (basename === 'package.nls.json') { + return this.provideNlsKeyDefinition(document, position); + } + + return undefined; + } + + private async provideNlsValueDefinition(packageJsonDoc: vscode.TextDocument, position: vscode.Position): Promise { + const nlsRef = this.getNlsReferenceAtPosition(packageJsonDoc, position); + if (!nlsRef) { + return undefined; + } + + const nlsUri = vscode.Uri.joinPath(packageJsonDoc.uri, '..', 'package.nls.json'); + return this.resolveNlsDefinition(nlsRef, nlsUri); + } + + private async provideNlsKeyDefinition(nlsDoc: vscode.TextDocument, position: vscode.Position): Promise { + const nlsKey = this.getNlsKeyDefinitionAtPosition(nlsDoc, position); + if (!nlsKey) { + return undefined; + } + return this.resolveNlsDefinition(nlsKey, nlsDoc.uri); + } + + private async resolveNlsDefinition(origin: { key: string; range: vscode.Range }, nlsUri: vscode.Uri): Promise { + const target = await this.findNlsKeyDeclaration(origin.key, nlsUri); + if (!target) { + return undefined; + } + + return [{ + originSelectionRange: origin.range, + targetUri: target.uri, + targetRange: target.range, + }]; + } + + private getNlsReferenceAtPosition(packageJsonDoc: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined { + const location = getLocation(packageJsonDoc.getText(), packageJsonDoc.offsetAt(position)); + if (!location.previousNode || location.previousNode.type !== 'string') { + return undefined; + } + + const value = getNodeValue(location.previousNode); + if (typeof value !== 'string') { + return undefined; + } + + const match = value.match(/^%(.+)%$/); + if (!match) { + return undefined; + } + + const nodeStart = packageJsonDoc.positionAt(location.previousNode.offset); + const nodeEnd = packageJsonDoc.positionAt(location.previousNode.offset + location.previousNode.length); + return { key: match[1], range: new vscode.Range(nodeStart, nodeEnd) }; + } + + public async provideReferences(document: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise { + const basename = document.uri.path.split('/').pop()?.toLowerCase(); + if (basename === 'package.nls.json') { + return this.provideNlsKeyReferences(document, position, context); + } + if (basename === 'package.json') { + return this.provideNlsValueReferences(document, position, context); + } + return undefined; + } + + private async provideNlsKeyReferences(nlsDoc: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext): Promise { + const nlsKey = this.getNlsKeyDefinitionAtPosition(nlsDoc, position); + if (!nlsKey) { + return undefined; + } + + const packageJsonUri = vscode.Uri.joinPath(nlsDoc.uri, '..', 'package.json'); + return this.findAllNlsReferences(nlsKey.key, packageJsonUri, nlsDoc.uri, context); + } + + private async provideNlsValueReferences(packageJsonDoc: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext): Promise { + const nlsRef = this.getNlsReferenceAtPosition(packageJsonDoc, position); if (!nlsRef) { return undefined; } - const nlsUri = vscode.Uri.joinPath(document.uri, '..', 'package.nls.json'); + const nlsUri = vscode.Uri.joinPath(packageJsonDoc.uri, '..', 'package.nls.json'); + return this.findAllNlsReferences(nlsRef.key, packageJsonDoc.uri, nlsUri, context); + } + + private async findAllNlsReferences(nlsKey: string, packageJsonUri: vscode.Uri, nlsUri: vscode.Uri, context: vscode.ReferenceContext): Promise { + const locations = await this.findNlsReferencesInPackageJson(nlsKey, packageJsonUri); + + if (context.includeDeclaration) { + const decl = await this.findNlsKeyDeclaration(nlsKey, nlsUri); + if (decl) { + locations.push(decl); + } + } + + return locations; + } + private async findNlsKeyDeclaration(nlsKey: string, nlsUri: vscode.Uri): Promise { try { const nlsDoc = await vscode.workspace.openTextDocument(nlsUri); const nlsTree = parseTree(nlsDoc.getText()); @@ -37,41 +145,60 @@ export class PackageDocumentL10nSupport implements vscode.DefinitionProvider, vs return undefined; } - const node = findNodeAtLocation(nlsTree, [nlsRef.key]); - if (!node) { + const node = findNodeAtLocation(nlsTree, [nlsKey]); + if (!node?.parent) { return undefined; } - const targetStart = nlsDoc.positionAt(node.offset); - const targetEnd = nlsDoc.positionAt(node.offset + node.length); - return [{ - originSelectionRange: nlsRef.range, - targetUri: nlsUri, - targetRange: new vscode.Range(targetStart, targetEnd), - }]; + const keyNode = node.parent.children?.[0]; + if (!keyNode) { + return undefined; + } + + const start = nlsDoc.positionAt(keyNode.offset); + const end = nlsDoc.positionAt(keyNode.offset + keyNode.length); + return new vscode.Location(nlsUri, new vscode.Range(start, end)); } catch { return undefined; } } - private getNlsReferenceAtPosition(document: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined { - const location = getLocation(document.getText(), document.offsetAt(position)); - if (!location.previousNode || location.previousNode.type !== 'string') { - return undefined; + private async findNlsReferencesInPackageJson(nlsKey: string, packageJsonUri: vscode.Uri): Promise { + let packageJsonDoc: vscode.TextDocument; + try { + packageJsonDoc = await vscode.workspace.openTextDocument(packageJsonUri); + } catch { + return []; } - const value = getNodeValue(location.previousNode); - if (typeof value !== 'string') { - return undefined; - } + const text = packageJsonDoc.getText(); + const needle = `%${nlsKey}%`; + const locations: vscode.Location[] = []; - const match = value.match(/^%(.+)%$/); - if (!match) { + visit(text, { + onLiteralValue(value, offset, length) { + if (value === needle) { + const start = packageJsonDoc.positionAt(offset); + const end = packageJsonDoc.positionAt(offset + length); + locations.push(new vscode.Location(packageJsonUri, new vscode.Range(start, end))); + } + } + }); + + return locations; + } + + private getNlsKeyDefinitionAtPosition(nlsDoc: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined { + const location = getLocation(nlsDoc.getText(), nlsDoc.offsetAt(position)); + + // Must be on a top-level property key + if (location.path.length !== 1 || !location.isAtPropertyKey || !location.previousNode) { return undefined; } - const nodeStart = document.positionAt(location.previousNode.offset); - const nodeEnd = document.positionAt(location.previousNode.offset + location.previousNode.length); - return { key: match[1], range: new vscode.Range(nodeStart, nodeEnd) }; + const key = location.path[0] as string; + const start = nlsDoc.positionAt(location.previousNode.offset); + const end = nlsDoc.positionAt(location.previousNode.offset + location.previousNode.length); + return { key, range: new vscode.Range(start, end) }; } } From 13a6803cbaab25d8236373e2873c8dbc85f910ba Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 19 Feb 2026 12:18:18 +0000 Subject: [PATCH 03/50] style(quick-input): replace fixed border-radius with variable for consistency --- extensions/theme-2026/themes/styles.css | 1 - src/vs/platform/quickinput/browser/media/quickInput.css | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 4cdefd6e1dbcd..839ad9bab2d64 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -125,7 +125,6 @@ /* Quick Input (Command Palette) */ .monaco-workbench .quick-input-widget { box-shadow: var(--shadow-xl) !important; - border-radius: 12px; background-color: color-mix(in srgb, var(--vscode-quickInput-background) 60%, transparent) !important; backdrop-filter: var(--backdrop-blur-lg); -webkit-backdrop-filter: var(--backdrop-blur-lg); diff --git a/src/vs/platform/quickinput/browser/media/quickInput.css b/src/vs/platform/quickinput/browser/media/quickInput.css index bd509719a3cce..9bf4b5edcd1a6 100644 --- a/src/vs/platform/quickinput/browser/media/quickInput.css +++ b/src/vs/platform/quickinput/browser/media/quickInput.css @@ -9,7 +9,7 @@ z-index: 2550; left: 50%; -webkit-app-region: no-drag; - border-radius: 8px; + border-radius: var(--vscode-cornerRadius-xLarge); } .quick-input-titlebar { From ef7a51a4bb0a2c69f890d3e295385bb27764243d Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 19 Feb 2026 15:38:31 +0000 Subject: [PATCH 04/50] style(suggest-widget): replace fixed border-radius with variable for consistency --- extensions/theme-2026/themes/styles.css | 4 ++-- src/vs/editor/contrib/suggest/browser/media/suggest.css | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 839ad9bab2d64..20d3aad35336a 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -187,9 +187,9 @@ padding: 0; } -.monaco-workbench .monaco-editor .suggest-widget .monaco-list { +/* .monaco-workbench .monaco-editor .suggest-widget .monaco-list { border-radius: var(--radius-lg); -} +} */ .monaco-workbench .quick-input-widget .monaco-list-rows { background: transparent !important; diff --git a/src/vs/editor/contrib/suggest/browser/media/suggest.css b/src/vs/editor/contrib/suggest/browser/media/suggest.css index 755c457fc20bd..2d9fd8b1f7c01 100644 --- a/src/vs/editor/contrib/suggest/browser/media/suggest.css +++ b/src/vs/editor/contrib/suggest/browser/media/suggest.css @@ -10,7 +10,7 @@ z-index: 40; display: flex; flex-direction: column; - border-radius: 3px; + border-radius: var(--vscode-cornerRadius-large); } .monaco-editor .suggest-widget.message { @@ -96,6 +96,7 @@ .monaco-editor .suggest-widget .monaco-list { user-select: none; -webkit-user-select: none; + border-radius: var(--vscode-cornerRadius-large); } /** Styles for each row in the list element **/ From b923a7ae73533e8deb3e6249bfe2dd4cca4a66ea Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 19 Feb 2026 15:42:43 +0000 Subject: [PATCH 05/50] style(quick-input): replace fixed border-radius with variable for consistency --- extensions/theme-2026/themes/styles.css | 2 +- src/vs/platform/quickinput/browser/media/quickInput.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 20d3aad35336a..f624595630154 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -202,7 +202,7 @@ .monaco-workbench .quick-input-widget .quick-input-filter .monaco-inputbox { background: color-mix(in srgb, var(--vscode-input-background) 60%, transparent) !important; - border-radius: 6px; + /* border-radius: 6px; */ } /* Chat Widget */ diff --git a/src/vs/platform/quickinput/browser/media/quickInput.css b/src/vs/platform/quickinput/browser/media/quickInput.css index 9bf4b5edcd1a6..bfad8086272b0 100644 --- a/src/vs/platform/quickinput/browser/media/quickInput.css +++ b/src/vs/platform/quickinput/browser/media/quickInput.css @@ -97,6 +97,10 @@ padding: 6px 6px 4px 6px; } +.quick-input-widget .quick-input-filter .monaco-inputbox { + border-radius: var(--vscode-cornerRadius-medium); +} + .quick-input-widget.hidden-input .quick-input-header { /* reduce margins and paddings when input box hidden */ padding: 0; From 3d0e5d5d4766771bd699e61bfb09299abd8a224d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 2 Mar 2026 21:26:11 +1100 Subject: [PATCH 06/50] feat: add support for Copilot user agents and related functionality (#298610) * feat: add support for Copilot user agents and related functionality * Update comments * Updates --- .../contrib/chat/browser/chat.contribution.ts | 3 +- .../config/promptFileLocations.ts | 16 +++++- .../languageProviders/promptValidator.ts | 9 ++-- .../config/promptFileLocations.test.ts | 40 +++++++++++++++ .../service/promptsService.test.ts | 50 +++++++++++++++++++ 5 files changed, 112 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 2c3b010411927..569411deae595 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -53,7 +53,7 @@ import { ILanguageModelToolsService } from '../common/tools/languageModelToolsSe import { agentPluginDiscoveryRegistry, IAgentPluginService } from '../common/plugins/agentPluginService.js'; import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; -import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, PromptFileSource } from '../common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, PromptFileSource, COPILOT_USER_AGENTS_SOURCE_FOLDER } from '../common/promptSyntax/config/promptFileLocations.js'; import { PromptLanguageFeaturesProvider } from '../common/promptSyntax/promptFileContributions.js'; import { AGENT_DOCUMENTATION_URL, INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL, SKILL_DOCUMENTATION_URL, HOOK_DOCUMENTATION_URL, PromptsType } from '../common/promptSyntax/promptTypes.js'; import { hookFileSchema, HOOK_SCHEMA_URI } from '../common/promptSyntax/hookSchema.js'; @@ -879,6 +879,7 @@ configurationRegistry.registerConfiguration({ default: { [AGENTS_SOURCE_FOLDER]: true, [CLAUDE_AGENTS_SOURCE_FOLDER]: true, + [COPILOT_USER_AGENTS_SOURCE_FOLDER]: true, }, additionalProperties: { type: 'boolean' }, propertyNames: { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts index 70de863c8f815..779974e1e6310 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts @@ -86,6 +86,11 @@ export const AGENTS_SOURCE_FOLDER = '.github/agents'; */ export const CLAUDE_AGENTS_SOURCE_FOLDER = '.claude/agents'; +/** + * Copilot user agents folder. + */ +export const COPILOT_USER_AGENTS_SOURCE_FOLDER = '~/.copilot/agents'; + /** * Claude rules folder. */ @@ -186,6 +191,7 @@ export const DEFAULT_AGENT_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [ { path: AGENTS_SOURCE_FOLDER, source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local }, { path: CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local }, { path: '~/' + CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user }, + { path: COPILOT_USER_AGENTS_SOURCE_FOLDER, source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user }, ]; /** @@ -204,7 +210,7 @@ export const DEFAULT_HOOK_FILE_PATHS: readonly IPromptSourceFolder[] = [ */ function isInAgentsFolder(fileUri: URI): boolean { const dir = dirname(fileUri.path); - return dir.endsWith('/' + AGENTS_SOURCE_FOLDER) || dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER); + return dir.endsWith('/' + AGENTS_SOURCE_FOLDER) || dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER) || isInCopilotAgentsFolder(fileUri); } /** @@ -215,6 +221,14 @@ export function isInClaudeAgentsFolder(fileUri: URI): boolean { return dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER); } +/** + * Helper function to check if a file is directly in the ~/.copilot/agents/ folder. + */ +export function isInCopilotAgentsFolder(fileUri: URI): boolean { + const dir = dirname(fileUri.path); + return dir.endsWith(COPILOT_USER_AGENTS_SOURCE_FOLDER.substring(1)); +} + /** * Helper function to check if a file is inside the .claude/rules/ folder (including subfolders). * Claude rules files (.md) in this folder are treated as instruction files. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index a6058e2c47ec7..7d05dd9ff61fd 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -23,10 +23,9 @@ import { ResourceMap } from '../../../../../../base/common/map.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { IPromptsService, Target } from '../service/promptsService.js'; import { ILabelService } from '../../../../../../platform/label/common/label.js'; -import { AGENTS_SOURCE_FOLDER, CLAUDE_AGENTS_SOURCE_FOLDER, isInClaudeRulesFolder, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js'; +import { AGENTS_SOURCE_FOLDER, isInClaudeAgentsFolder, isInClaudeRulesFolder, isInCopilotAgentsFolder, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js'; import { Lazy } from '../../../../../../base/common/lazy.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { dirname } from '../../../../../../base/common/resources.js'; import { URI } from '../../../../../../base/common/uri.js'; export const MARKERS_OWNER_ID = 'prompts-diagnostics-provider'; @@ -1023,10 +1022,12 @@ export function isVSCodeOrDefaultTarget(target: Target): boolean { export function getTarget(promptType: PromptsType, header: PromptHeader | URI): Target { const uri = header instanceof URI ? header : header.uri; if (promptType === PromptsType.agent) { - const parentDir = dirname(uri); - if (parentDir.path.endsWith(`/${CLAUDE_AGENTS_SOURCE_FOLDER}`)) { + if (isInClaudeAgentsFolder(uri)) { return Target.Claude; } + if (isInCopilotAgentsFolder(uri)) { + return Target.GitHubCopilot; + } if (!(header instanceof URI)) { const target = header.target; if (target === Target.GitHubCopilot || target === Target.VSCode) { diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/promptFileLocations.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/promptFileLocations.test.ts index f6d859e9f2736..f7e22613dee3b 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/promptFileLocations.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/promptFileLocations.test.ts @@ -48,6 +48,36 @@ suite('promptFileLocations', function () { assert.strictEqual(getPromptFileType(uri), undefined); }); + test('.md files in .claude/agents/ subfolder should NOT be recognized as agent files', () => { + const uri = URI.file('/workspace/.claude/agents/subfolder/test.md'); + assert.strictEqual(getPromptFileType(uri), undefined); + }); + + test('.md files in ~/.copilot/agents/ subfolder should NOT be recognized as agent files', () => { + const uri = URI.file('/home/user/.copilot/agents/subfolder/test.md'); + assert.strictEqual(getPromptFileType(uri), undefined); + }); + + test('.md files in .claude/agents/ folder should be recognized as agent files', () => { + const uri = URI.file('/workspace/.claude/agents/demonstrate.md'); + assert.strictEqual(getPromptFileType(uri), PromptsType.agent); + }); + + test('README.md in .claude/agents/ should NOT be recognized as agent file', () => { + const uri = URI.file('/workspace/.claude/agents/README.md'); + assert.strictEqual(getPromptFileType(uri), undefined); + }); + + test('.md files in ~/.copilot/agents/ folder should be recognized as agent files', () => { + const uri = URI.file('/home/user/.copilot/agents/my-agent.md'); + assert.strictEqual(getPromptFileType(uri), PromptsType.agent); + }); + + test('README.md in ~/.copilot/agents/ should NOT be recognized as agent file', () => { + const uri = URI.file('/home/user/.copilot/agents/README.md'); + assert.strictEqual(getPromptFileType(uri), undefined); + }); + test('.md files outside .github/agents/ should not be recognized as agent files', () => { const uri = URI.file('/workspace/test/foo.md'); assert.strictEqual(getPromptFileType(uri), undefined); @@ -130,6 +160,16 @@ suite('promptFileLocations', function () { assert.strictEqual(getCleanPromptName(uri), 'demonstrate'); }); + test('removes .md extension for files in .claude/agents/', () => { + const uri = URI.file('/workspace/.claude/agents/claude-agent.md'); + assert.strictEqual(getCleanPromptName(uri), 'claude-agent'); + }); + + test('removes .md extension for files in ~/.copilot/agents/', () => { + const uri = URI.file('/home/user/.copilot/agents/my-agent.md'); + assert.strictEqual(getCleanPromptName(uri), 'my-agent'); + }); + test('README.md in .github/agents/ should keep .md extension', () => { const uri = URI.file('/workspace/.github/agents/README.md'); assert.strictEqual(getCleanPromptName(uri), 'README.md'); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 90e6605a00dd3..0f3e39807dbe9 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -1177,6 +1177,56 @@ suite('PromptsService', () => { ); }); + test('copilot user agents from ~/.copilot/agents/ should have GitHubCopilot target', async () => { + const rootFolderName = 'copilot-user-agents'; + const rootFolder = `/${rootFolderName}`; + const rootFolderUri = URI.file(rootFolder); + + workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); + + await mockFiles(fileService, [ + { + // Copilot user agent in ~/.copilot/agents/ (resolved from /home/user/.copilot/agents/) + path: '/home/user/.copilot/agents/copilot-user-agent.md', + contents: [ + '---', + 'description: \'Copilot user agent from home folder.\'', + 'tools: [ read ]', + '---', + 'I am a Copilot user agent.', + ] + }, + ]); + + const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) })); + const expected: ICustomAgent[] = [ + { + name: 'copilot-user-agent', + description: 'Copilot user agent from home folder.', + target: Target.GitHubCopilot, + tools: ['read'], + agentInstructions: { + content: 'I am a Copilot user agent.', + toolReferences: [], + metadata: undefined + }, + handOffs: undefined, + model: undefined, + argumentHint: undefined, + visibility: { userInvocable: true, agentInvocable: true }, + agents: undefined, + uri: URI.file('/home/user/.copilot/agents/copilot-user-agent.md'), + source: { storage: PromptsStorage.user } + }, + ]; + + assert.deepEqual( + result, + expected, + 'Agents from ~/.copilot/agents/ must have Target.GitHubCopilot.', + ); + }); + test('agents with .md extension should be recognized, except README.md', async () => { const rootFolderName = 'custom-agents-md-extension'; const rootFolder = `/${rootFolderName}`; From 7b9ab03582ec979cc5c12c42fe61bd70f8fe6be8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:27:13 +0100 Subject: [PATCH 07/50] Sessions - fix tree rendering in the changes view (#298663) --- .../sessions/contrib/changesView/browser/changesView.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/sessions/contrib/changesView/browser/changesView.ts b/src/vs/sessions/contrib/changesView/browser/changesView.ts index 2d29fbe475e0c..040941a0465b5 100644 --- a/src/vs/sessions/contrib/changesView/browser/changesView.ts +++ b/src/vs/sessions/contrib/changesView/browser/changesView.ts @@ -661,11 +661,9 @@ export class ChangesViewPane extends ViewPane { }, compressionEnabled: true, twistieAdditionalCssClass: (e: unknown) => { - if (this.viewMode === ChangesViewMode.List) { - return 'force-no-twistie'; - } - // In tree mode, hide twistie for file items (they are never collapsible) - return isChangesFileItem(e as ChangesTreeElement) ? 'force-no-twistie' : undefined; + return this.viewMode === ChangesViewMode.List + ? 'force-no-twistie' + : undefined; }, } ); From a176e75cf2aa49bdd54b0fec007788525d7a35fa Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 19 Feb 2026 16:20:45 +0000 Subject: [PATCH 08/50] style(chat-input): replace fixed border-radius with variable for consistency --- extensions/theme-2026/themes/styles.css | 12 ++++-------- src/vs/sessions/browser/media/style.css | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index f624595630154..967a0418f6d47 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -187,10 +187,6 @@ padding: 0; } -/* .monaco-workbench .monaco-editor .suggest-widget .monaco-list { - border-radius: var(--radius-lg); -} */ - .monaco-workbench .quick-input-widget .monaco-list-rows { background: transparent !important; } @@ -202,13 +198,9 @@ .monaco-workbench .quick-input-widget .quick-input-filter .monaco-inputbox { background: color-mix(in srgb, var(--vscode-input-background) 60%, transparent) !important; - /* border-radius: 6px; */ } /* Chat Widget */ -.monaco-workbench .interactive-session .chat-input-container { - border-radius: var(--radius-lg); -} .monaco-workbench .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor, .monaco-workbench .interactive-session .chat-editing-session .chat-editing-session-container { @@ -219,6 +211,10 @@ border-radius: 0 0 var(--radius-lg) var(--radius-lg); } +.monaco-workbench.vs .interactive-session .chat-input-container { + box-shadow: inset var(--shadow-sm); +} + .monaco-workbench .part.panel .interactive-session, .monaco-workbench .part.auxiliarybar .interactive-session { position: relative; diff --git a/src/vs/sessions/browser/media/style.css b/src/vs/sessions/browser/media/style.css index c79f7e8d21179..6947f2aff496c 100644 --- a/src/vs/sessions/browser/media/style.css +++ b/src/vs/sessions/browser/media/style.css @@ -46,7 +46,7 @@ /* ---- Chat Input ---- */ .agent-sessions-workbench .interactive-session .chat-input-container { - border-radius: 8px !important; + border-radius: var(--vscode-cornerRadius-large) !important; } .agent-sessions-workbench .interactive-session .interactive-input-part { From 1dc1271ba6ec21e3d6052452acf406e610603ce3 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 19 Feb 2026 16:33:09 +0000 Subject: [PATCH 09/50] style(notifications): update border and border-radius for consistency across notification elements --- extensions/theme-2026/themes/styles.css | 4 ++-- .../browser/parts/notifications/media/notificationsCenter.css | 3 ++- .../browser/parts/notifications/media/notificationsToasts.css | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 967a0418f6d47..1cb845c7e5b44 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -231,7 +231,7 @@ overflow: visible; } -.monaco-workbench .notification-toast { +/* .monaco-workbench .notification-toast { box-shadow: none !important; } @@ -263,7 +263,7 @@ backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); background: color-mix(in srgb, var(--vscode-notifications-background) 60%, transparent) !important; - border: 1px solid var(--vscode-editorWidget-border) !important; + /* border: 1px solid var(--vscode-editorWidget-border) !important; */ box-shadow: var(--shadow-lg) !important; } diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css index 015f11afd5bde..a75978e3c1499 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css @@ -10,7 +10,8 @@ bottom: 29px; /* 22px status bar height + 7px (attempt to position at same location as a toast) */ display: none; overflow: hidden; - border-radius: 4px; + border: 1px solid var(--vscode-editorWidget-border); + border-radius: var(--vscode-cornerRadius-small); } .monaco-workbench.nostatusbar > .notifications-center { diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index 0f5f3fdbfbab1..004b1388202ee 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -10,6 +10,8 @@ bottom: 25px; /* 22px status bar height + 3px */ display: none; overflow: hidden; + box-shadow: 0 0 12px rgba(0, 0, 0, 0.14); + border-radius: var(--vscode-cornerRadius-small); } .monaco-workbench.nostatusbar > .notifications-toasts { From b3bcabf327e6cc5e77e90c11979c65470309c51c Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 19 Feb 2026 17:19:36 +0000 Subject: [PATCH 10/50] style(menu): replace fixed border-radius with CSS variable for consistency style(find-widget): unify border-radius with CSS variable for consistency style(inline-chat-gutter-menu): update border-radius to use CSS variable for consistency --- extensions/theme-2026/themes/styles.css | 8 -------- src/vs/editor/contrib/find/browser/findWidget.css | 8 +++----- .../inlineChat/browser/media/inlineChatOverlayWidget.css | 2 +- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 1cb845c7e5b44..675d95f7fcb8b 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -278,9 +278,6 @@ } /* Context Menus */ -.monaco-workbench .monaco-menu .monaco-action-bar.vertical { - border-radius: var(--radius-lg); -} .monaco-workbench .context-view .monaco-menu { box-shadow: var(--shadow-lg); @@ -296,7 +293,6 @@ } .monaco-workbench .monaco-menu-container > .monaco-scrollable-element { - border-radius: var(--radius-lg) !important; box-shadow: var(--shadow-lg) !important; background: color-mix(in srgb, var(--vscode-menu-background) 60%, transparent) !important; backdrop-filter: var(--backdrop-blur-md); @@ -316,7 +312,6 @@ /* Suggest Widget */ .monaco-workbench .monaco-editor .suggest-widget { box-shadow: var(--shadow-lg); - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); background: color-mix(in srgb, var(--vscode-editorSuggestWidget-background) 60%, transparent) !important; @@ -330,14 +325,11 @@ /* Find Widget */ .monaco-workbench .monaco-editor .find-widget { box-shadow: var(--shadow-lg); - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); - margin-top: 4px !important; } .monaco-workbench .inline-chat-gutter-menu { - border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); diff --git a/src/vs/editor/contrib/find/browser/findWidget.css b/src/vs/editor/contrib/find/browser/findWidget.css index f1dd6191b9580..cbe6028775c93 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.css +++ b/src/vs/editor/contrib/find/browser/findWidget.css @@ -12,15 +12,13 @@ line-height: 19px; transition: transform 200ms linear; padding: 0 4px 0 9px; + margin-top: 4px; box-sizing: border-box; transform: translateY(calc(-100% - 10px)); /* shadow (10px) */ box-shadow: 0 0 8px 2px var(--vscode-widget-shadow); color: var(--vscode-editorWidget-foreground); - border-left: 1px solid var(--vscode-widget-border); - border-right: 1px solid var(--vscode-widget-border); - border-bottom: 1px solid var(--vscode-widget-border); - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; + border: 1px solid var(--vscode-widget-border); + border-radius: var(--vscode-cornerRadius-large); background-color: var(--vscode-editorWidget-background); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatOverlayWidget.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatOverlayWidget.css index 9acc9ecf81703..be58df0988b27 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatOverlayWidget.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatOverlayWidget.css @@ -8,7 +8,7 @@ .inline-chat-gutter-menu { background: var(--vscode-panel-background); border: 1px solid var(--vscode-menu-border, var(--vscode-widget-border)); - border-radius: 8px; + border-radius: var(--vscode-cornerRadius-large); box-shadow: 0 2px 8px var(--vscode-widget-shadow); z-index: 100; } From 75b588a2ae6728ae55582b4eb3689d33844217a4 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 23 Feb 2026 15:53:58 +0000 Subject: [PATCH 11/50] style(dialog): replace fixed border-radius with CSS variable for consistency --- src/vs/base/browser/ui/dialog/dialog.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index c484fa86dbd94..85397fd9278ff 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -30,7 +30,7 @@ min-height: 75px; padding: 10px; transform: translate3d(0px, 0px, 0px); - border-radius: 3px; + border-radius: var(--vscode-cornerRadius-large); } .monaco-dialog-box.align-vertical { From 37e10e57a71ada8e189a31d56c8a23f7e8ae1270 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:11:24 +0000 Subject: [PATCH 12/50] style(dialog): remove fixed border-radius for consistency with theme variables --- extensions/theme-2026/themes/styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 675d95f7fcb8b..688240e7e91c4 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -338,7 +338,6 @@ /* Dialog */ .monaco-workbench .monaco-dialog-box { box-shadow: var(--shadow-xl); - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-lg); -webkit-backdrop-filter: var(--backdrop-blur-lg); background: color-mix(in srgb, var(--vscode-editor-background) 60%, transparent) !important; From f51a4c9a16a00ed5c49fd5b0b59185d46ec578b3 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:22:48 +0000 Subject: [PATCH 13/50] style(hover): update border-radius to use theme variable for consistency --- extensions/theme-2026/themes/styles.css | 2 -- src/vs/editor/contrib/hover/browser/hover.css | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 688240e7e91c4..322fa302ded72 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -263,7 +263,6 @@ backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); background: color-mix(in srgb, var(--vscode-notifications-background) 60%, transparent) !important; - /* border: 1px solid var(--vscode-editorWidget-border) !important; */ box-shadow: var(--shadow-lg) !important; } @@ -367,7 +366,6 @@ .monaco-editor .monaco-hover { background-color: color-mix(in srgb, var(--vscode-editorHoverWidget-background) 60%, transparent) !important; box-shadow: var(--shadow-sm-strong); - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); } diff --git a/src/vs/editor/contrib/hover/browser/hover.css b/src/vs/editor/contrib/hover/browser/hover.css index aedcb6944b324..b33ea5e76bce9 100644 --- a/src/vs/editor/contrib/hover/browser/hover.css +++ b/src/vs/editor/contrib/hover/browser/hover.css @@ -9,7 +9,7 @@ .monaco-editor .monaco-resizable-hover { border: 1px solid var(--vscode-editorHoverWidget-border); - border-radius: 3px; + border-radius: var(--vscode-cornerRadius-large); box-sizing: content-box; } @@ -20,7 +20,7 @@ .monaco-editor .monaco-hover { border: 1px solid var(--vscode-editorHoverWidget-border); - border-radius: 3px; + border-radius: var(--vscode-cornerRadius-large); color: var(--vscode-editorHoverWidget-foreground); background-color: var(--vscode-editorHoverWidget-background); } From 1ac1b461c3d967afd37a31c670725394db2550b5 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:24:34 +0000 Subject: [PATCH 14/50] style(keybindings): restore border-radius for defineKeybindingWidget consistency --- .../workbench/contrib/preferences/browser/media/keybindings.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/preferences/browser/media/keybindings.css b/src/vs/workbench/contrib/preferences/browser/media/keybindings.css index e77553529e87f..3874b5b70f795 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/keybindings.css +++ b/src/vs/workbench/contrib/preferences/browser/media/keybindings.css @@ -5,6 +5,7 @@ .defineKeybindingWidget { padding: 10px; + border-radius: var(--vscode-cornerRadius-large); position: absolute; } From b13422637bb6ceac06f177b5eac93dab669741a3 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:32:45 +0000 Subject: [PATCH 15/50] style(dropdown, debug-toolbar): update border-radius to use theme variable for consistency --- extensions/theme-2026/themes/styles.css | 2 -- src/vs/base/browser/ui/dropdown/dropdown.css | 6 ++++++ .../workbench/contrib/debug/browser/media/debugToolBar.css | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 322fa302ded72..923562b0faf82 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -491,7 +491,6 @@ /* Dropdowns */ .monaco-workbench .monaco-dropdown .dropdown-menu { box-shadow: var(--shadow-lg); - border-radius: var(--radius-lg); } /* SCM */ @@ -503,7 +502,6 @@ /* Debug Toolbar */ .monaco-workbench .debug-toolbar { box-shadow: var(--shadow-lg); - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-lg) !important; -webkit-backdrop-filter: var(--backdrop-blur-lg) !important; } diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css index bfcaee41f98ad..ad528f21e6605 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.css +++ b/src/vs/base/browser/ui/dropdown/dropdown.css @@ -20,6 +20,10 @@ cursor: default; } +.monaco-dropdown .dropdown-menu { + border-radius: var(--vscode-cornerRadius-large); +} + .monaco-dropdown-with-primary { display: flex !important; flex-direction: row; @@ -44,3 +48,5 @@ background-position: center center; background-repeat: no-repeat; } + + diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index 090c53e7f9844..f8a588049f092 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -9,7 +9,7 @@ height: 28px; display: flex; padding-left: 2px; - border-radius: 5px; + border-radius: var(--vscode-cornerRadius-large); left: 0; top: 0; -webkit-app-region: no-drag; From d36272a6c9e908f4fd34fcda3faacf4332ec7eba Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:40:22 +0000 Subject: [PATCH 16/50] style(action-widget): remove border-radius for consistency with theme variables --- extensions/theme-2026/themes/styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 923562b0faf82..1fb9b7748b8ae 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -521,7 +521,6 @@ /* Action Widget */ .monaco-workbench .action-widget { box-shadow: var(--shadow-lg) !important; - border-radius: var(--radius-lg); } /* Parameter Hints */ From c95a8987472766542280e5ebe5dd65b35870d228 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:41:38 +0000 Subject: [PATCH 17/50] style(parameter-hints): update border-radius to use theme variable for consistency --- extensions/theme-2026/themes/styles.css | 1 - src/vs/editor/contrib/parameterHints/browser/parameterHints.css | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 1fb9b7748b8ae..378ceb0be213e 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -526,7 +526,6 @@ /* Parameter Hints */ .monaco-workbench .monaco-editor .parameter-hints-widget { box-shadow: var(--shadow-lg); - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); } diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHints.css b/src/vs/editor/contrib/parameterHints/browser/parameterHints.css index 3efac6c122caa..bf54d22d60ed9 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHints.css @@ -13,6 +13,7 @@ color: var(--vscode-editorHoverWidget-foreground); background-color: var(--vscode-editorHoverWidget-background); border: 1px solid var(--vscode-editorHoverWidget-border); + border-radius: var(--vscode-cornerRadius-large); } .hc-black .monaco-editor .parameter-hints-widget, From 62adc94eca3d0eff3fd3832744689c80247c1c18 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:43:13 +0000 Subject: [PATCH 18/50] style(notebook): update border-radius to use theme variable for consistency --- extensions/theme-2026/themes/styles.css | 1 + src/vs/workbench/contrib/notebook/browser/media/notebook.css | 1 + 2 files changed, 2 insertions(+) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 378ceb0be213e..c73af25ed5742 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -600,6 +600,7 @@ .monaco-workbench .notebookOverlay .monaco-list-row .cell-editor-part:before { border-radius: var(--radius-md); + box-shadow: inset var(--shadow-sm); } .notebookOverlay .monaco-list-row .cell-title-toolbar { diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index dff9db0ecce6f..aaf4aada0c36d 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -294,6 +294,7 @@ left: 0px; top: 0px; bottom: 0px; + border-radius: var(--vscode-cornerRadius-medium); outline-offset: -1px; display: block; position: absolute; From 7a54887ed25501c01b16e1bb7ced0838cd79388a Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:44:18 +0000 Subject: [PATCH 19/50] style(notebook): update action-item border-radius for consistency with theme variable --- extensions/theme-2026/themes/styles.css | 4 ---- src/vs/workbench/contrib/notebook/browser/media/notebook.css | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index c73af25ed5742..8276fdaa96114 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -611,10 +611,6 @@ box-shadow: var(--shadow-sm); } -.notebookOverlay .cell-bottom-toolbar-container .action-item { - border-radius: var(--radius-sm); -} - /* Inline Chat */ .monaco-workbench .monaco-editor .inline-chat { box-shadow: var(--shadow-lg); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index aaf4aada0c36d..11994166b6eb7 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -376,6 +376,11 @@ .notebookOverlay .cell-drag-image .cell-editor-container > div { background: var(--vscode-editor-background) !important; } + +.notebookOverlay .cell-bottom-toolbar-container .action-item { + border-radius: var(--vscode-cornerRadius-small); +} + .notebookOverlay .monaco-list-row .cell-title-toolbar, .notebookOverlay .monaco-list-row.cell-drag-image, .notebookOverlay .cell-bottom-toolbar-container .action-item, From 695348bd65101e702fc848de94bbe76d86664e02 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:50:41 +0000 Subject: [PATCH 20/50] style(notebook): add border-radius to cell title toolbar for consistency with theme variable --- extensions/theme-2026/themes/styles.css | 1 - src/vs/workbench/contrib/notebook/browser/media/notebook.css | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 8276fdaa96114..d029c4317cc5f 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -607,7 +607,6 @@ background-color: var(--vscode-editorWidget-background) !important; backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); - border-radius: var(--radius-md); box-shadow: var(--shadow-sm); } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 11994166b6eb7..54f1f0dfc782c 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -381,6 +381,10 @@ border-radius: var(--vscode-cornerRadius-small); } +.notebookOverlay .monaco-list-row .cell-title-toolbar { + border-radius: var(--vscode-cornerRadius-medium); +} + .notebookOverlay .monaco-list-row .cell-title-toolbar, .notebookOverlay .monaco-list-row.cell-drag-image, .notebookOverlay .cell-bottom-toolbar-container .action-item, From af114f4857d9dbe22a81d9172fecb5e49530c13b Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:51:52 +0000 Subject: [PATCH 21/50] style(inline-chat): update border-radius to use theme variable for consistency --- extensions/theme-2026/themes/styles.css | 1 - .../workbench/contrib/inlineChat/browser/media/inlineChat.css | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index d029c4317cc5f..df4fb46ceed9d 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -614,7 +614,6 @@ .monaco-workbench .monaco-editor .inline-chat { box-shadow: var(--shadow-lg); border: none; - border-radius: var(--radius-lg); } /* Command Center */ diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index da1e670cf8bc7..5c7b116c2bf23 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -6,7 +6,7 @@ .monaco-workbench .inline-chat { color: inherit; - border-radius: 4px; + border-radius: var(--vscode-cornerRadius-large); border: 1px solid var(--vscode-inlineChat-border); box-shadow: 0 2px 4px 0 var(--vscode-widget-shadow); background: var(--vscode-inlineChat-background); From 30c93129921e3312b1c095dfb286da68debc8169 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:53:18 +0000 Subject: [PATCH 22/50] style(titlebar): update border-radius to use theme variable for consistency --- extensions/theme-2026/themes/styles.css | 1 + src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index df4fb46ceed9d..f510afd7a798b 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -619,6 +619,7 @@ /* Command Center */ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { border-radius: var(--radius-lg) !important; + box-shadow: inset var(--shadow-sm) !important; background: color-mix(in srgb, var(--vscode-commandCenter-background) 60%, transparent) !important; -webkit-backdrop-filter: var(--backdrop-blur-md); backdrop-filter: var(--backdrop-blur-md); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 454939c8afc4a..cf0977721d589 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -172,7 +172,7 @@ border: 1px solid var(--vscode-commandCenter-border); overflow: hidden; margin: 0 6px; - border-radius: 4px; + border-radius: var(--vscode-cornerRadius-large); height: 22px; width: 38vw; max-width: 600px; From 154634f755b2ba360da2a9aaf383595042f4ed48 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:54:35 +0000 Subject: [PATCH 23/50] style(dialog): update border-radius for modal block shadow to use theme variable --- extensions/theme-2026/themes/styles.css | 4 ---- src/vs/base/browser/ui/dialog/dialog.css | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index f510afd7a798b..3fcca43be93ba 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -647,10 +647,6 @@ background-color: var(--vscode-commandCenter-activeBackground); } -.monaco-dialog-modal-block .dialog-shadow { - border-radius: var(--radius-lg); -} - .monaco-workbench .unified-quick-access-tabs { background: transparent; } diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 85397fd9278ff..844b50bec385d 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -238,3 +238,7 @@ .monaco-dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button-dropdown > .monaco-dropdown-button { padding: 0 4px; } + +.monaco-dialog-modal-block .dialog-shadow { + border-radius: var(--vscode-cornerRadius-large); +} From 701d3435e292d6f67ad1a81d00b346defc600627 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 12:55:57 +0000 Subject: [PATCH 24/50] style(debug-hover): add border-radius to debug hover widget for consistency with theme variable --- extensions/theme-2026/themes/styles.css | 1 - src/vs/workbench/contrib/debug/browser/media/debugHover.css | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 3fcca43be93ba..dae4a40e292f9 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -508,7 +508,6 @@ .monaco-workbench .debug-hover-widget { box-shadow: var(--shadow-lg); - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); color: var(--vscode-editor-foreground) !important; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index 3a862adbe9fdb..e7dd01a9cfbcc 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -12,6 +12,7 @@ -webkit-user-select: text; word-break: break-all; white-space: pre; + border-radius: var(--vscode-cornerRadius-large); } .monaco-editor .debug-hover-widget .complex-value { From 4a38deb0f058b65d4cde091909c4cbdc57dbb70a Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:49:37 +0100 Subject: [PATCH 25/50] Fix macOS sidebar traffic light spacer rendering with custom titlebar (#298669) --- src/vs/sessions/LAYOUT.md | 3 ++- src/vs/sessions/browser/parts/sidebarPart.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/sessions/LAYOUT.md b/src/vs/sessions/LAYOUT.md index fea9a1c370b86..ad54a086c7fb1 100644 --- a/src/vs/sessions/LAYOUT.md +++ b/src/vs/sessions/LAYOUT.md @@ -440,7 +440,7 @@ The `AuxiliaryBarPart` provides a custom `DropdownWithPrimaryActionViewItem` for The `SidebarPart` includes a footer section (35px height) positioned below the pane composite content. The sidebar uses a custom `layout()` override that reduces the content height by `FOOTER_HEIGHT` and renders a `MenuWorkbenchToolBar` driven by `Menus.SidebarFooter`. The footer hosts the account widget (see Section 3.6). -On macOS native, the sidebar title area includes a traffic light spacer (70px) to push content past the system window controls, which is hidden in fullscreen mode. +On macOS native with custom titlebar, the sidebar title area includes a traffic light spacer (70px) to push content past the system window controls. The spacer is hidden in fullscreen mode and is not created when using native titlebar (since the OS renders traffic lights in its own title bar). --- @@ -640,6 +640,7 @@ interface IPartVisibilityState { | Date | Change | |------|--------| +| 2026-03-02 | Fixed macOS sidebar traffic light spacer to only render with custom titlebar; added `!hasNativeTitlebar()` guard to `SidebarPart.createTitleArea()` so the 70px spacer is not created when using native titlebar (traffic lights are in the OS title bar, not overlapping the sidebar) | | 2026-02-20 | Replaced custom `EditorModal` with standard `ModalEditorPart` via `MODAL_GROUP`; main editor part created but hidden; changed `workbench.editor.useModal` from boolean to enum (`off`/`some`/`all`); sessions config uses `all`; removed `editorModal.ts` and editor modal CSS | | 2026-02-17 | Added `-webkit-app-region: drag` to sidebar title area so it can be used to drag the window; interactive children (actions, composite bar, labels) marked `no-drag`; CSS rules scoped to `.agent-sessions-workbench` in `parts/media/sidebarPart.css` | | 2026-02-13 | Documentation sync: Updated all file names, class names, and references to match current implementation. `AgenticWorkbench` → `Workbench`, `AgenticSidebarPart` → `SidebarPart`, `AgenticAuxiliaryBarPart` → `AuxiliaryBarPart`, `AgenticPanelPart` → `PanelPart`, `agenticWorkbench.ts` → `workbench.ts`, `agenticWorkbenchMenus.ts` → `menus.ts`, `agenticLayoutActions.ts` → `layoutActions.ts`, `AgenticTitleBarWidget` → `SessionsTitleBarWidget`, `AgenticTitleBarContribution` → `SessionsTitleBarContribution`. Removed references to deleted files (`sidebarRevealButton.ts`, `floatingToolbar.ts`, `agentic.contributions.ts`, `agenticTitleBarWidget.ts`). Updated pane composite architecture from `SyncDescriptor`-based to `AgenticPaneCompositePartService`. Moved account widget docs from titlebar to sidebar footer. Added documentation for sidebar footer, project bar, traffic light spacer, card appearance styling, widget directory, and new contrib structure (`accountMenu/`, `chat/`, `configuration/`, `sessions/`). Updated titlebar actions to reflect Run Script split button and Open submenu. Removed Toggle Maximize panel action (no longer registered). Updated contributions section with all current contributions and their locations. | diff --git a/src/vs/sessions/browser/parts/sidebarPart.ts b/src/vs/sessions/browser/parts/sidebarPart.ts index 5f8cce31cf807..b9132d4d13e63 100644 --- a/src/vs/sessions/browser/parts/sidebarPart.ts +++ b/src/vs/sessions/browser/parts/sidebarPart.ts @@ -38,6 +38,8 @@ import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/acti import { isMacintosh, isNative } from '../../../base/common/platform.js'; import { isFullscreen, onDidChangeFullscreen } from '../../../base/browser/browser.js'; import { mainWindow } from '../../../base/browser/window.js'; +import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; +import { hasNativeTitlebar, getTitleBarStyle } from '../../../platform/window/common/window.js'; /** * Sidebar part specifically for agent sessions workbench. @@ -103,6 +105,7 @@ export class SidebarPart extends AbstractPaneCompositePart { @IContextKeyService contextKeyService: IContextKeyService, @IExtensionService extensionService: IExtensionService, @IMenuService menuService: IMenuService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super( Parts.SIDEBAR_PART, @@ -151,7 +154,7 @@ export class SidebarPart extends AbstractPaneCompositePart { // macOS native: the sidebar spans full height and the traffic lights // overlay the top-left corner. Add a fixed-width spacer inside the // title area to push content horizontally past the traffic lights. - if (titleArea && isMacintosh && isNative) { + if (titleArea && isMacintosh && isNative && !hasNativeTitlebar(this.configurationService, getTitleBarStyle(this.configurationService))) { const spacer = $('div.window-controls-container'); spacer.style.width = '70px'; spacer.style.height = '100%'; From 9d61d1947fa967c18f388d7cb9aea07954bfd0f2 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 2 Mar 2026 02:50:36 -0800 Subject: [PATCH 26/50] Add more logging to sanity test runner (#298664) --- test/sanity/src/index.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/test/sanity/src/index.ts b/test/sanity/src/index.ts index a251128892529..60fc2cab5a0ed 100644 --- a/test/sanity/src/index.ts +++ b/test/sanity/src/index.ts @@ -52,8 +52,22 @@ if (testResults) { const mocha = new Mocha(mochaOptions); mocha.addFile(fileURLToPath(new URL('./main.js', import.meta.url))); await mocha.loadFilesAsync(); -mocha.run(failures => { +const runner = mocha.run(failures => { + if (options.verbose) { + console.log(`Mocha test run finished: ${failures} failure(s)`); + } process.exitCode = failures > 0 ? 1 : 0; // Force exit to prevent hanging on open handles (background processes, timers, etc.) - setTimeout(() => process.exit(process.exitCode), 1000); + setTimeout(() => { + if (options.verbose) { + console.log(`Exiting with code ${process.exitCode}`); + } + process.exit(process.exitCode); + }, 1000); }); + +if (options.verbose) { + runner.on('test', (test) => console.log(`Starting: ${test.fullTitle()}`)); + runner.on('pass', (test) => console.log(`Passed: ${test.fullTitle()}`)); + runner.on('fail', (test, err) => console.log(`Failed: ${test.fullTitle()} - ${err.message}`)); +} From 062b8a4a393d9968414f5a6a33b2aa2d60ac95cb Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 10:57:48 +0000 Subject: [PATCH 27/50] style(chat): remove unnecessary border-radius from chat editor and notifications for consistency --- extensions/theme-2026/themes/styles.css | 60 +------------------------ 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index dae4a40e292f9..534955558bf47 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -202,15 +202,6 @@ /* Chat Widget */ -.monaco-workbench .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor, -.monaco-workbench .interactive-session .chat-editing-session .chat-editing-session-container { - border-radius: var(--radius-lg) var(--radius-lg) 0 0; -} - -.monaco-workbench .interactive-input-part:has(.chat-editing-session > .chat-editing-session-container) .chat-input-container { - border-radius: 0 0 var(--radius-lg) var(--radius-lg); -} - .monaco-workbench.vs .interactive-session .chat-input-container { box-shadow: inset var(--shadow-sm); } @@ -231,45 +222,6 @@ overflow: visible; } -/* .monaco-workbench .notification-toast { - box-shadow: none !important; -} - -.monaco-workbench .notifications-list-container { - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); - background: color-mix(in srgb, var(--vscode-notifications-background) 60%, transparent) !important; - box-shadow: var(--shadow-lg) !important; -} - -.monaco-workbench .notifications-center .notifications-list-container { - box-shadow: none !important; -} - -.monaco-workbench .notification-list-item, -.monaco-workbench .notifications-center-header { - background: color-mix(in srgb, var(--vscode-notifications-background) 60%, transparent) !important; -} - -.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast { - opacity: 1; -} - -.monaco-workbench .notification-toast-container .notification-toast { - background-color: transparent !important; -} - -.monaco-workbench .notifications-center { - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); - background: color-mix(in srgb, var(--vscode-notifications-background) 60%, transparent) !important; - box-shadow: var(--shadow-lg) !important; -} - -.monaco-workbench.vs-dark .notifications-center { - background: color-mix(in srgb, var(--vscode-notifications-background) 60%, transparent) !important; -} - /* .monaco-workbench .notifications-list-container, .monaco-workbench > .notifications-center > .notifications-center-header, */ .monaco-workbench .notifications-list-container .monaco-list-rows { @@ -336,16 +288,13 @@ /* Dialog */ .monaco-workbench .monaco-dialog-box { + border: 1px solid var(--vscode-dialog-border); box-shadow: var(--shadow-xl); backdrop-filter: var(--backdrop-blur-lg); -webkit-backdrop-filter: var(--backdrop-blur-lg); background: color-mix(in srgb, var(--vscode-editor-background) 60%, transparent) !important; } -.monaco-workbench.vs-dark .monaco-dialog-box { - border: 1px solid var(--vscode-dialog-border); -} - /* Peek View */ .monaco-workbench .monaco-editor .peekview-widget { box-shadow: var(--shadow-hover); @@ -379,6 +328,7 @@ } .monaco-workbench .defineKeybindingWidget { + border: 1px solid var(--vscode-editorWidget-border); box-shadow: var(--shadow-lg) !important; border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-md); @@ -386,10 +336,6 @@ background-color: color-mix(in srgb, var(--vscode-editorHoverWidget-background) 60%, transparent) !important; } -.monaco-workbench.vs-dark .defineKeybindingWidget { - border: 1px solid var(--vscode-editorWidget-border); -} - .monaco-workbench .chat-editor-overlay-widget, .monaco-workbench .chat-diff-change-content-widget { box-shadow: var(--shadow-md); @@ -598,7 +544,6 @@ /* Notebook */ .monaco-workbench .notebookOverlay .monaco-list-row .cell-editor-part:before { - border-radius: var(--radius-md); box-shadow: inset var(--shadow-sm); } @@ -617,7 +562,6 @@ /* Command Center */ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { - border-radius: var(--radius-lg) !important; box-shadow: inset var(--shadow-sm) !important; background: color-mix(in srgb, var(--vscode-commandCenter-background) 60%, transparent) !important; -webkit-backdrop-filter: var(--backdrop-blur-md); From 1cdf38fd855f435e218e2f95eff561d5f17edb41 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 10:59:14 +0000 Subject: [PATCH 28/50] style(dropdown): remove unnecessary whitespace for cleaner code --- src/vs/base/browser/ui/dropdown/dropdown.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/base/browser/ui/dropdown/dropdown.css b/src/vs/base/browser/ui/dropdown/dropdown.css index ad528f21e6605..0e2d6bdd9f53c 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.css +++ b/src/vs/base/browser/ui/dropdown/dropdown.css @@ -48,5 +48,3 @@ background-position: center center; background-repeat: no-repeat; } - - From 12d5932337910890e23907c844b0dc63b5499c67 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 11:09:56 +0000 Subject: [PATCH 29/50] style(defineKeybindingWidget): remove border-radius for consistency with theme styles --- extensions/theme-2026/themes/styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 534955558bf47..e4008b89a0a6e 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -330,7 +330,6 @@ .monaco-workbench .defineKeybindingWidget { border: 1px solid var(--vscode-editorWidget-border); box-shadow: var(--shadow-lg) !important; - border-radius: var(--radius-lg); backdrop-filter: var(--backdrop-blur-md); -webkit-backdrop-filter: var(--backdrop-blur-md); background-color: color-mix(in srgb, var(--vscode-editorHoverWidget-background) 60%, transparent) !important; From 0914e0c8917d1ab98e6d21a8d89a6cc6376aba50 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 11:11:37 +0000 Subject: [PATCH 30/50] style(notifications): update border-radius for last notification item to use theme variable --- .../browser/parts/notifications/media/notificationsCenter.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css index a75978e3c1499..9f11489cc35d8 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css @@ -60,7 +60,7 @@ } .monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row:last-child { - border-radius: 0px 0px 4px 4px; /* adopt the border radius at the end of the notifications center */ + border-radius: 0px 0px var(--vscode-cornerRadius-small) var(--vscode-cornerRadius-small); /* adopt the border radius at the end of the notifications center */ } /* Icons */ From 95966335143d897340c74adbd4819b081ae6dc60 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 11:12:46 +0000 Subject: [PATCH 31/50] style(notifications): update box-shadow to use theme variable for consistency --- .../browser/parts/notifications/media/notificationsToasts.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index 004b1388202ee..af90372abdc83 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -10,7 +10,7 @@ bottom: 25px; /* 22px status bar height + 3px */ display: none; overflow: hidden; - box-shadow: 0 0 12px rgba(0, 0, 0, 0.14); + box-shadow: 0 0 12px var(--vscode-widget-shadow); border-radius: var(--vscode-cornerRadius-small); } From 2385bef6b66e0a00fb9843536bbb3d5cc352313c Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 11:20:22 +0000 Subject: [PATCH 32/50] fix(terminal): update background color handling in terminal editor --- .../workbench/contrib/terminal/browser/media/terminal.css | 2 +- .../workbench/contrib/terminal/browser/terminalInstance.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 9370b48b871f6..58400159f85ce 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -63,7 +63,7 @@ } .monaco-workbench .terminal-editor .terminal-wrapper { - background-color: var(--vscode-terminal-background, var(--vscode-editorPane-background)); + background-color: var(--vscode-editor-background); } .monaco-workbench .terminal-editor .terminal-wrapper, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index f78a087fbd854..93a50146bc1a9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -2812,13 +2812,13 @@ export class TerminalInstanceColorProvider implements IXtermColorProvider { } getBackgroundColor(theme: IColorTheme) { + if (this._target.object === TerminalLocation.Editor) { + return theme.getColor(editorBackground); + } const terminalBackground = theme.getColor(TERMINAL_BACKGROUND_COLOR); if (terminalBackground) { return terminalBackground; } - if (this._target.object === TerminalLocation.Editor) { - return theme.getColor(editorBackground); - } const location = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!; if (location === ViewContainerLocation.Panel) { return theme.getColor(PANEL_BACKGROUND); From 2160dc5cb3b0ae282c960dcedb3528d89b2f3422 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 2 Mar 2026 12:20:54 +0100 Subject: [PATCH 33/50] make sure action are disabled when cloud is picked --- .../contrib/chat/browser/chat.contribution.ts | 27 +++- .../contrib/chat/browser/runScriptAction.ts | 31 +++- .../browser/sessionsManagementService.ts | 10 ++ .../browser/sessionsTerminalContribution.ts | 24 ++-- .../sessionsTerminalContribution.test.ts | 133 +++++++++--------- 5 files changed, 145 insertions(+), 80 deletions(-) diff --git a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts index 0ace6677cb836..37fe6dc58844e 100644 --- a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts @@ -17,7 +17,7 @@ import { IViewContainersRegistry, IViewsRegistry, ViewContainerLocation, Extensi import { Registry } from '../../../../platform/registry/common/platform.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; -import { ISessionsManagementService, IsNewChatSessionContext } from '../../sessions/browser/sessionsManagementService.js'; +import { IsActiveSessionBackgroundProviderContext, ISessionsManagementService, IsNewChatSessionContext } from '../../sessions/browser/sessionsManagementService.js'; import { Menus } from '../../../browser/menus.js'; import { BranchChatSessionAction } from './branchChatSessionAction.js'; import { RunScriptContribution } from './runScriptAction.js'; @@ -51,7 +51,7 @@ export class OpenSessionWorktreeInVSCodeAction extends Action2 { id: Menus.TitleBarRight, group: 'navigation', order: 10, - when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated()) + when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated(), IsActiveSessionBackgroundProviderContext) }] }); } @@ -92,6 +92,29 @@ export class OpenSessionWorktreeInVSCodeAction extends Action2 { } registerAction2(OpenSessionWorktreeInVSCodeAction); +// Disabled placeholder shown in the titlebar when the active session does not support opening in VS Code +class OpenSessionWorktreeInVSCodeNotAvailableAction extends Action2 { + constructor() { + super({ + id: 'chat.openSessionWorktreeInVSCode.notAvailable', + title: localize2('openInVSCode', 'Open in VS Code'), + tooltip: localize('openInVSCodeNotAvailableTooltip', "Open in VS Code is not available for this session type"), + icon: Codicon.vscodeInsiders, + precondition: ContextKeyExpr.false(), + menu: [{ + id: Menus.TitleBarRight, + group: 'navigation', + order: 9, + when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated(), IsActiveSessionBackgroundProviderContext.toNegated()) + }] + }); + } + + override run(): void { } +} + +registerAction2(OpenSessionWorktreeInVSCodeNotAvailableAction); + class NewChatInSessionsWindowAction extends Action2 { constructor() { diff --git a/src/vs/sessions/contrib/chat/browser/runScriptAction.ts b/src/vs/sessions/contrib/chat/browser/runScriptAction.ts index bacdbac4330b8..e5c024cb2ba0d 100644 --- a/src/vs/sessions/contrib/chat/browser/runScriptAction.ts +++ b/src/vs/sessions/contrib/chat/browser/runScriptAction.ts @@ -9,13 +9,13 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { autorun, derivedOpts, IObservable } from '../../../../base/common/observable.js'; import { localize, localize2 } from '../../../../nls.js'; import { MenuId, registerAction2, Action2, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js'; import { SessionsCategories } from '../../../common/categories.js'; -import { IActiveSessionItem, ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; +import { IActiveSessionItem, IsActiveSessionBackgroundProviderContext, ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; import { Menus } from '../../../browser/menus.js'; import { ISessionsConfigurationService, ITaskEntry, TaskStorageTarget } from './sessionsConfigurationService.js'; -import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js'; import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js'; @@ -284,7 +284,7 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr } } -// Register the Run split button submenu on the workbench title bar +// Register the Run split button submenu on the workbench title bar (background sessions only) MenuRegistry.appendMenuItem(Menus.TitleBarRight, { submenu: RunScriptDropdownMenuId, isSplitButton: true, @@ -292,5 +292,28 @@ MenuRegistry.appendMenuItem(Menus.TitleBarRight, { icon: Codicon.play, group: 'navigation', order: 8, - when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated()) + when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated(), IsActiveSessionBackgroundProviderContext) }); + +// Disabled placeholder shown in the titlebar when the active session does not support running scripts +class RunScriptNotAvailableAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.agentSessions.runScript.notAvailable', + title: localize2('run', "Run"), + tooltip: localize('runScriptNotAvailableTooltip', "Run Script is not available for this session type"), + icon: Codicon.play, + precondition: ContextKeyExpr.false(), + menu: [{ + id: Menus.TitleBarRight, + group: 'navigation', + order: 8, + when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated(), IsActiveSessionBackgroundProviderContext.toNegated()) + }] + }); + } + + override run(): void { } +} + +registerAction2(RunScriptNotAvailableAction); diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts index 85f4a6cbd63d7..546c0b73faf39 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts @@ -7,6 +7,7 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { IObservable, observableValue } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; +import { localize } from '../../../../nls.js'; import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -27,6 +28,12 @@ import { GITHUB_REMOTE_FILE_SCHEME } from '../../fileTreeView/browser/githubFile export const IsNewChatSessionContext = new RawContextKey('isNewChatSession', true); +/** + * True when the active session uses the Background provider type (copilotcli). + * Used to gate actions that require a local worktree (run script, open in VS Code, terminal). + */ +export const IsActiveSessionBackgroundProviderContext = new RawContextKey('isActiveSessionBackgroundProvider', false, localize('isActiveSessionBackgroundProvider', "Whether the active session uses the background agent provider")); + //#region Active Session Service const LAST_SELECTED_SESSION_KEY = 'agentSessions.lastSelectedSession'; @@ -105,6 +112,7 @@ export class SessionsManagementService extends Disposable implements ISessionsMa private readonly _newSession = this._register(new MutableDisposable()); private lastSelectedSession: URI | undefined; private readonly isNewChatSessionContext: IContextKey; + private readonly _isBackgroundProvider: IContextKey; constructor( @IStorageService private readonly storageService: IStorageService, @@ -124,6 +132,7 @@ export class SessionsManagementService extends Disposable implements ISessionsMa // Bind context key to active session state. // isNewSession is false when there are any established sessions in the model. this.isNewChatSessionContext = IsNewChatSessionContext.bindTo(contextKeyService); + this._isBackgroundProvider = IsActiveSessionBackgroundProviderContext.bindTo(contextKeyService); // Load last selected session this.lastSelectedSession = this.loadLastSelectedSession(); @@ -464,6 +473,7 @@ export class SessionsManagementService extends Disposable implements ISessionsMa this.logService.trace('[ActiveSessionService] Active session cleared'); } + this._isBackgroundProvider.set(activeSessionItem?.providerType === AgentSessionProviders.Background); this._activeSession.set(activeSessionItem, undefined); } diff --git a/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts b/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts index 38c8e785ebb74..809716d687b28 100644 --- a/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts +++ b/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts @@ -13,6 +13,7 @@ import { Action2, registerAction2 } from '../../../../platform/actions/common/ac import { ILogService } from '../../../../platform/log/common/log.js'; import { IWorkbenchContribution, getWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; +import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; import { ITerminalService } from '../../../../workbench/contrib/terminal/browser/terminal.js'; import { IPathService } from '../../../../workbench/services/path/common/pathService.js'; import { Menus } from '../../../browser/menus.js'; @@ -22,11 +23,15 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js'; /** - * Returns the cwd URI for the given session: worktree for non-cloud agent - * sessions, repository otherwise, or `undefined` when neither is available. + * Returns the cwd URI for the given session: worktree or repository path for + * background sessions only. Returns `undefined` for non-background sessions + * (Cloud, Local, etc.) which have no local worktree, or when no path is available. */ function getSessionCwd(session: IActiveSessionItem | undefined): URI | undefined { - return session?.worktree ?? session?.repository; + if (session?.providerType !== AgentSessionProviders.Background) { + return undefined; + } + return session.worktree ?? session.repository; } /** @@ -48,14 +53,14 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben @ITerminalService private readonly _terminalService: ITerminalService, @IAgentSessionsService private readonly _agentSessionsService: IAgentSessionsService, @ILogService private readonly _logService: ILogService, + @IPathService private readonly _pathService: IPathService, ) { super(); - // React to active session worktree/repository path changes + // React to active session changes — use worktree/repo for background sessions, home dir otherwise this._register(autorun(reader => { const session = this._sessionsManagementService.activeSession.read(reader); - const targetPath = getSessionCwd(session); - this._onActivePathChanged(targetPath); + this._onActiveSessionChanged(session); })); // When a session is archived, close all terminals for its worktree @@ -103,11 +108,12 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben } } - private async _onActivePathChanged(targetPath: URI | undefined): Promise { - if (!targetPath) { + private async _onActiveSessionChanged(session: IActiveSessionItem | undefined): Promise { + if (!session) { return; } + const targetPath = getSessionCwd(session) ?? await this._pathService.userHome(); const targetFsPath = targetPath.fsPath; if (this._lastTargetFsPath?.toLowerCase() === targetFsPath.toLowerCase()) { return; @@ -143,7 +149,7 @@ class OpenSessionInTerminalAction extends Action2 { menu: [{ id: Menus.TitleBarRight, group: 'navigation', - order: 9, + order: 10, when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated()) }] }); diff --git a/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts b/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts index 5df597398ff60..ebc29e7e13f65 100644 --- a/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts +++ b/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts @@ -18,6 +18,10 @@ import { IAgentSession, IAgentSessionsModel } from '../../../../../workbench/con import { AgentSessionProviders } from '../../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; import { IActiveSessionItem, ISessionsManagementService } from '../../../sessions/browser/sessionsManagementService.js'; import { SessionsTerminalContribution } from '../../browser/sessionsTerminalContribution.js'; +import { TestPathService } from '../../../../../workbench/test/browser/workbenchTestServices.js'; +import { IPathService } from '../../../../../workbench/services/path/common/pathService.js'; + +const HOME_DIR = URI.file('/home/user'); function makeAgentSession(opts: { repository?: URI; @@ -39,10 +43,11 @@ function makeAgentSession(opts: { } as unknown as IActiveSessionItem & IAgentSession; } -function makeNonAgentSession(opts: { repository?: URI; worktree?: URI }): IActiveSessionItem { +function makeNonAgentSession(opts: { repository?: URI; worktree?: URI; providerType?: string }): IActiveSessionItem { return { repository: opts.repository, worktree: opts.worktree, + providerType: opts.providerType ?? AgentSessionProviders.Local, } as IActiveSessionItem; } @@ -111,6 +116,8 @@ suite('SessionsTerminalContribution', () => { } as unknown as IAgentSessionsModel; }); + instantiationService.stub(IPathService, new TestPathService(HOME_DIR)); + contribution = store.add(instantiationService.createInstance(SessionsTerminalContribution)); }); @@ -120,11 +127,11 @@ suite('SessionsTerminalContribution', () => { ensureNoDisposablesAreLeakedInTestSuite(); - // --- getSessionCwd logic (via active session changes) --- + // --- Background provider: uses worktree/repository path --- - test('creates a terminal when active session has a worktree (non-cloud agent)', async () => { + test('creates a terminal at the worktree for a background session', async () => { const worktreeUri = URI.file('/worktree'); - const session = makeAgentSession({ worktree: worktreeUri, repository: URI.file('/repo'), providerType: AgentSessionProviders.Local }); + const session = makeAgentSession({ worktree: worktreeUri, repository: URI.file('/repo'), providerType: AgentSessionProviders.Background }); activeSessionObs.set(session, undefined); await tick(); @@ -132,59 +139,97 @@ suite('SessionsTerminalContribution', () => { assert.strictEqual(createdTerminals[0].cwd.fsPath, worktreeUri.fsPath); }); - test('reate a terminal with repository for cloud agent sessions', async () => { + test('falls back to repository when worktree is undefined for a background session', async () => { const repoUri = URI.file('/repo'); - const workTree = URI.file('/worktree'); - const session = makeAgentSession({ worktree: workTree, repository: repoUri, providerType: AgentSessionProviders.Cloud }); + const session = makeAgentSession({ repository: repoUri, providerType: AgentSessionProviders.Background }); activeSessionObs.set(session, undefined); await tick(); assert.strictEqual(createdTerminals.length, 1); - assert.strictEqual(createdTerminals[0].cwd.fsPath, workTree.fsPath); + assert.strictEqual(createdTerminals[0].cwd.fsPath, repoUri.fsPath); }); - test('creates a terminal with repository for non-agent sessions', async () => { - const repoUri = URI.file('/repo'); - const session = makeNonAgentSession({ repository: repoUri }); + test('does not create a terminal when background session has no paths', async () => { + const session = makeAgentSession({ providerType: AgentSessionProviders.Background }); + activeSessionObs.set(session, undefined); + await tick(); + + assert.strictEqual(createdTerminals.length, 0); + }); + + // --- Non-background providers: use home directory --- + + test('uses home directory for a cloud agent session', async () => { + const session = makeAgentSession({ worktree: URI.file('/worktree'), repository: URI.file('/repo'), providerType: AgentSessionProviders.Cloud }); activeSessionObs.set(session, undefined); await tick(); assert.strictEqual(createdTerminals.length, 1); - assert.strictEqual(createdTerminals[0].cwd.fsPath, repoUri.fsPath); + assert.strictEqual(createdTerminals[0].cwd.fsPath, HOME_DIR.fsPath); + }); + + test('uses home directory for a local agent session', async () => { + const session = makeAgentSession({ worktree: URI.file('/worktree'), providerType: AgentSessionProviders.Local }); + activeSessionObs.set(session, undefined); + await tick(); + + assert.strictEqual(createdTerminals.length, 1); + assert.strictEqual(createdTerminals[0].cwd.fsPath, HOME_DIR.fsPath); }); - test('does not create a terminal when no path is available', async () => { - const session = makeNonAgentSession({}); + test('uses home directory for a non-agent session', async () => { + const session = makeNonAgentSession({ repository: URI.file('/repo') }); activeSessionObs.set(session, undefined); await tick(); + assert.strictEqual(createdTerminals.length, 1); + assert.strictEqual(createdTerminals[0].cwd.fsPath, HOME_DIR.fsPath); + }); + + test('does not recreate terminal when multiple non-background sessions share the home directory', async () => { + const session1 = makeAgentSession({ providerType: AgentSessionProviders.Cloud }); + activeSessionObs.set(session1, undefined); + await tick(); + assert.strictEqual(createdTerminals.length, 1); + + // Different non-background session — same home dir, no new terminal + const session2 = makeAgentSession({ providerType: AgentSessionProviders.Local }); + activeSessionObs.set(session2, undefined); + await tick(); + assert.strictEqual(createdTerminals.length, 1); + }); + + test('does not create a terminal when there is no active session', async () => { + activeSessionObs.set(undefined, undefined); + await tick(); + assert.strictEqual(createdTerminals.length, 0); }); test('does not recreate terminal for the same path', async () => { const worktreeUri = URI.file('/worktree'); - const session1 = makeAgentSession({ worktree: worktreeUri, providerType: AgentSessionProviders.Local }); + const session1 = makeAgentSession({ worktree: worktreeUri, providerType: AgentSessionProviders.Background }); activeSessionObs.set(session1, undefined); await tick(); assert.strictEqual(createdTerminals.length, 1); // Setting a different session with the same worktree should not create a new terminal - const session2 = makeAgentSession({ worktree: worktreeUri, providerType: AgentSessionProviders.Local }); + const session2 = makeAgentSession({ worktree: worktreeUri, providerType: AgentSessionProviders.Background }); activeSessionObs.set(session2, undefined); await tick(); assert.strictEqual(createdTerminals.length, 1); }); - test('creates new terminal when switching to a different path', async () => { + test('creates new terminal when switching to a different background path', async () => { const worktree1 = URI.file('/worktree1'); const worktree2 = URI.file('/worktree2'); - activeSessionObs.set(makeAgentSession({ worktree: worktree1, providerType: AgentSessionProviders.Local }), undefined); + activeSessionObs.set(makeAgentSession({ worktree: worktree1, providerType: AgentSessionProviders.Background }), undefined); await tick(); - activeSessionObs.set(makeAgentSession({ worktree: worktree2, providerType: AgentSessionProviders.Local }), undefined); + activeSessionObs.set(makeAgentSession({ worktree: worktree2, providerType: AgentSessionProviders.Background }), undefined); await tick(); assert.strictEqual(createdTerminals.length, 2); @@ -290,64 +335,22 @@ suite('SessionsTerminalContribution', () => { assert.strictEqual(createdTerminals.length, 2, 'should create a new terminal after the old one was disposed'); }); - // --- agent session with worktree preferred over repository for non-cloud --- - - test('prefers worktree over repository for local agent session', async () => { - const worktreeUri = URI.file('/worktree'); - const repoUri = URI.file('/repo'); - const session = makeAgentSession({ - worktree: worktreeUri, - repository: repoUri, - providerType: AgentSessionProviders.Local, - }); - activeSessionObs.set(session, undefined); - await tick(); - - assert.strictEqual(createdTerminals[0].cwd.fsPath, worktreeUri.fsPath); - }); - - test('falls back to repository when worktree is undefined for agent session', async () => { - const repoUri = URI.file('/repo'); - const session = makeAgentSession({ - repository: repoUri, - providerType: AgentSessionProviders.Local, - }); - activeSessionObs.set(session, undefined); - await tick(); - - assert.strictEqual(createdTerminals[0].cwd.fsPath, repoUri.fsPath); - }); - - test('does not use repository for cloud agent session when worktree exists', async () => { - const worktreeUri = URI.file('/worktree'); - const repoUri = URI.file('/repo'); - const session = makeAgentSession({ - worktree: worktreeUri, - repository: repoUri, - providerType: AgentSessionProviders.Cloud, - }); - activeSessionObs.set(session, undefined); - await tick(); - - assert.strictEqual(createdTerminals[0].cwd.fsPath, worktreeUri.fsPath); - }); - // --- switching back to previously used path reuses terminal --- - test('switching back to a previously used path reuses the existing terminal', async () => { + test('switching back to a previously used background path reuses the existing terminal', async () => { const cwd1 = URI.file('/cwd1'); const cwd2 = URI.file('/cwd2'); - activeSessionObs.set(makeAgentSession({ worktree: cwd1, providerType: AgentSessionProviders.Local }), undefined); + activeSessionObs.set(makeAgentSession({ worktree: cwd1, providerType: AgentSessionProviders.Background }), undefined); await tick(); assert.strictEqual(createdTerminals.length, 1); - activeSessionObs.set(makeAgentSession({ worktree: cwd2, providerType: AgentSessionProviders.Local }), undefined); + activeSessionObs.set(makeAgentSession({ worktree: cwd2, providerType: AgentSessionProviders.Background }), undefined); await tick(); assert.strictEqual(createdTerminals.length, 2); // Switch back to cwd1 - should reuse terminal, not create a new one - activeSessionObs.set(makeAgentSession({ worktree: cwd1, providerType: AgentSessionProviders.Local }), undefined); + activeSessionObs.set(makeAgentSession({ worktree: cwd1, providerType: AgentSessionProviders.Background }), undefined); await tick(); assert.strictEqual(createdTerminals.length, 2, 'should reuse the terminal for cwd1'); }); From f94268cbd8b7e6ce29cce74d4e0052f891715226 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 2 Mar 2026 12:25:45 +0100 Subject: [PATCH 34/50] sessions - allow to run sub-app when using cli (#298685) * sessions - allow to run sub-app when using cli * ccr --- src/vs/code/node/cli.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 8e29f4924766b..5f50659ff46c2 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ChildProcess, spawn, SpawnOptions, StdioOptions } from 'child_process'; -import { chmodSync, existsSync, readFileSync, statSync, truncateSync, unlinkSync } from 'fs'; +import { chmodSync, existsSync, readFileSync, statSync, truncateSync, unlinkSync, promises } from 'fs'; import { homedir, tmpdir } from 'os'; import type { ProfilingSession, Target } from 'v8-inspect-profiler'; import { Event } from '../../base/common/event.js'; @@ -499,8 +499,26 @@ export async function main(argv: string[]): Promise { // This way, Mac does not automatically try to foreground the new instance, which causes // focusing issues when the new instance only sends data to a previous instance and then closes. const spawnArgs = ['-n', '-g']; - // -a opens the given application. - spawnArgs.push('-a', process.execPath); // -a: opens a specific application + + // Figure out the app to launch: with --sessions we try to launch the embedded app + let appToLaunch = process.execPath; + if (args.sessions) { + // process.execPath is e.g. /Applications/Code.app/Contents/MacOS/Electron + // Embedded app is at /Applications/Code.app/Contents/Applications/.app + const contentsPath = dirname(dirname(process.execPath)); + const applicationsPath = join(contentsPath, 'Applications'); + try { + const files = await promises.readdir(applicationsPath); + const embeddedApp = files.find(file => file.endsWith('.app')); + if (embeddedApp) { + appToLaunch = join(applicationsPath, embeddedApp); + argv = argv.filter(arg => arg !== '--sessions'); + } + } catch (error) { + /* may not exist on disk */ + } + } + spawnArgs.push('-a', appToLaunch); // -a opens the given application. if (args.verbose || args.status) { spawnArgs.push('--wait-apps'); // `open --wait-apps`: blocks until the launched app is closed (even if they were already running) From a6ac46b47042da0b55ca2c540e8c816c91b1bdc2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 2 Mar 2026 12:31:57 +0100 Subject: [PATCH 35/50] sessions - resolve items again when trust changes (#298687) --- .../agentSessions/agentSessionsModel.ts | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts index 218dad8cb8f26..2d40294cfeb36 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts @@ -22,6 +22,7 @@ import { IProductService } from '../../../../../platform/product/common/productS import { Registry } from '../../../../../platform/registry/common/platform.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; +import { IWorkspaceTrustManagementService } from '../../../../../platform/workspace/common/workspaceTrust.js'; import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; import { Extensions, IOutputChannelRegistry, IOutputService } from '../../../../services/output/common/output.js'; @@ -398,6 +399,7 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode @IProductService private readonly productService: IProductService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, ) { super(); @@ -426,19 +428,12 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode private registerListeners(): void { - // Sessions changes - this._register(this.chatSessionsService.onDidChangeItemsProviders(({ chatSessionType }) => { - this.resolve(chatSessionType); - })); - this._register(this.chatSessionsService.onDidChangeAvailability(() => { - this.resolve(undefined); - })); - this._register(this.chatSessionsService.onDidChangeSessionItems(({ chatSessionType }) => { - this.updateItems([chatSessionType], CancellationToken.None); - })); - this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(() => { - this.resolve(undefined); - })); + // Sessions updates + this._register(this.chatSessionsService.onDidChangeItemsProviders(({ chatSessionType }) => this.resolve(chatSessionType))); + this._register(this.chatSessionsService.onDidChangeAvailability(() => this.resolve(undefined))); + this._register(this.chatSessionsService.onDidChangeSessionItems(({ chatSessionType }) => this.updateItems([chatSessionType], CancellationToken.None))); + this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(() => this.resolve(undefined))); + this._register(this.workspaceTrustManagementService.onDidChangeTrust(() => this.resolve(undefined))); // State this._register(this.storageService.onWillSaveState(() => { From df5e653ed855bef421dd60a4d0435e7d95a906be Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Mar 2026 12:45:48 +0100 Subject: [PATCH 36/50] fix continuous auth dialogs in cloud session (#298689) --- src/vs/sessions/contrib/chat/browser/repoPicker.ts | 3 +-- .../contrib/sessions/browser/sessionsManagementService.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/sessions/contrib/chat/browser/repoPicker.ts b/src/vs/sessions/contrib/chat/browser/repoPicker.ts index bc263c8234a1d..95e8b039df569 100644 --- a/src/vs/sessions/contrib/chat/browser/repoPicker.ts +++ b/src/vs/sessions/contrib/chat/browser/repoPicker.ts @@ -15,7 +15,6 @@ import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js' import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { INewSession } from './newSession.js'; import { URI } from '../../../../base/common/uri.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../../fileTreeView/browser/githubFileSystemProvider.js'; const OPEN_REPO_COMMAND = 'github.copilot.chat.cloudSessions.openRepository'; const STORAGE_KEY_LAST_REPO = 'agentSessions.lastPickedRepo'; @@ -271,7 +270,7 @@ export class RepoPicker extends Disposable { } private _setRepo(repo: IRepoItem): void { - this._newSession?.setRepoUri(URI.parse(`${GITHUB_REMOTE_FILE_SCHEME}://github/${repo.id}`)); + this._newSession?.setRepoUri(URI.parse(`vscode-vfs://github/${repo.id}`)); } } diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts index 85f4a6cbd63d7..d0055f1ae8444 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts @@ -23,7 +23,6 @@ import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browse import { INewSession, LocalNewSession, RemoteNewSession } from '../../chat/browser/newSession.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { ILanguageModelsService } from '../../../../workbench/contrib/chat/common/languageModels.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../../fileTreeView/browser/githubFileSystemProvider.js'; export const IsNewChatSessionContext = new RawContextKey('isNewChatSession', true); @@ -194,7 +193,7 @@ export class SessionsManagementService extends Disposable implements ISessionsMa } if (session.providerType === AgentSessionProviders.Cloud) { - return [URI.parse(`${GITHUB_REMOTE_FILE_SCHEME}://github/${metadata.owner}/${metadata.name}`), undefined]; + return [URI.parse(`vscode-vfs://github/${metadata.owner}/${metadata.name}`), undefined]; } const workingDirectoryPath = metadata?.workingDirectoryPath as string | undefined; From a2dfaa0864147d867ef9486874e9e80ee1f3d5c7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 2 Mar 2026 13:03:33 +0100 Subject: [PATCH 37/50] make it more stable (#298691) --- .../contrib/chat/browser/folderPicker.ts | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/vs/sessions/contrib/chat/browser/folderPicker.ts b/src/vs/sessions/contrib/chat/browser/folderPicker.ts index 22e53aa45654f..dbc253e1cbfa2 100644 --- a/src/vs/sessions/contrib/chat/browser/folderPicker.ts +++ b/src/vs/sessions/contrib/chat/browser/folderPicker.ts @@ -7,7 +7,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { basename, isEqual } from '../../../../base/common/resources.js'; +import { basename, extUriBiasedIgnorePathCase, isEqual } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js'; @@ -27,6 +27,7 @@ const FILTER_THRESHOLD = 10; interface IFolderItem { readonly uri: URI; readonly label: string; + readonly checked?: boolean; } /** @@ -218,40 +219,32 @@ export class FolderPicker extends Disposable { private _buildItems(currentFolderUri: URI | undefined): IActionListItem[] { const seenUris = new Set(); - if (currentFolderUri) { - seenUris.add(currentFolderUri.toString()); - } const items: IActionListItem[] = []; - // Currently selected folder (shown first, checked) + // Collect all folders (current + recently picked), deduplicated and sorted by name + const allFolders: { uri: URI; label: string }[] = []; if (currentFolderUri) { - items.push({ - kind: ActionListItemKind.Action, - label: basename(currentFolderUri), - group: { title: '', icon: Codicon.folder }, - item: { uri: currentFolderUri, label: basename(currentFolderUri) }, - }); + seenUris.add(currentFolderUri.toString()); + allFolders.push({ uri: currentFolderUri, label: basename(currentFolderUri) }); } - - // Recently picked folders (sorted by name) - const dedupedFolders: { uri: URI; label: string }[] = []; for (const folderUri of this._recentlyPickedFolders) { const key = folderUri.toString(); if (seenUris.has(key)) { continue; } seenUris.add(key); - dedupedFolders.push({ uri: folderUri, label: basename(folderUri) }); + allFolders.push({ uri: folderUri, label: basename(folderUri) }); } - dedupedFolders.sort((a, b) => a.label.localeCompare(b.label)); - for (const folder of dedupedFolders) { + allFolders.sort((a, b) => extUriBiasedIgnorePathCase.compare(a.uri, b.uri)); + for (const folder of allFolders) { + const isCurrent = currentFolderUri && isEqual(folder.uri, currentFolderUri); items.push({ kind: ActionListItemKind.Action, label: folder.label, group: { title: '', icon: Codicon.folder }, - item: { uri: folder.uri, label: folder.label }, - onRemove: () => this._removeFolder(folder.uri), + item: { uri: folder.uri, label: folder.label, checked: isCurrent || false }, + ...(!isCurrent ? { onRemove: () => this._removeFolder(folder.uri) } : {}), }); } From 5e4c0d559a044e9a8d61b3ef79b5270c9a7e4982 Mon Sep 17 00:00:00 2001 From: Kunal Bhujbal <156365067+kbhujbal@users.noreply.github.com> Date: Mon, 2 Mar 2026 04:18:35 -0800 Subject: [PATCH 38/50] Fix typos in user-facing localized strings (#297892) - Fix "Overriden" -> "Overridden" in terminal cwd picker description - Fix "requried" -> "required" in notebook renderer messaging description Co-authored-by: Claude Opus 4.6 --- .../contrib/notebook/browser/notebookExtensionPoint.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts index 6de1d23298e3f..e0c53ce067c5b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts @@ -158,7 +158,7 @@ const notebookRendererContribution: IJSONSchema = { ], enumDescriptions: [ nls.localize('contributes.notebook.renderer.requiresMessaging.always', 'Messaging is required. The renderer will only be used when it\'s part of an extension that can be run in an extension host.'), - nls.localize('contributes.notebook.renderer.requiresMessaging.optional', 'The renderer is better with messaging available, but it\'s not requried.'), + nls.localize('contributes.notebook.renderer.requiresMessaging.optional', 'The renderer is better with messaging available, but it\'s not required.'), nls.localize('contributes.notebook.renderer.requiresMessaging.never', 'The renderer does not require messaging.'), ], description: nls.localize('contributes.notebook.renderer.requiresMessaging', 'Defines how and if the renderer needs to communicate with an extension host, via `createRendererMessaging`. Renderers with stronger messaging requirements may not work in all environments.'), diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 79023c406f44a..4d881d9cb1b23 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1671,7 +1671,7 @@ async function pickTerminalCwd(accessor: ServicesAccessor, cancel?: Cancellation const folderPicks: Item[] = shrinkedPairs.map(pair => { const label = pair.folder.name; const description = pair.isOverridden - ? localize('workbench.action.terminal.overriddenCwdDescription', "(Overriden) {0}", labelService.getUriLabel(pair.cwd, { relative: !pair.isAbsolute })) + ? localize('workbench.action.terminal.overriddenCwdDescription', "(Overridden) {0}", labelService.getUriLabel(pair.cwd, { relative: !pair.isAbsolute })) : labelService.getUriLabel(dirname(pair.cwd), { relative: true }); return { From 543e5208ea98757de955511862c086df631a17ca Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:22:47 +0100 Subject: [PATCH 39/50] Sessions - resize body when collapse/expand the tree (#298698) --- src/vs/sessions/contrib/changesView/browser/changesView.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/sessions/contrib/changesView/browser/changesView.ts b/src/vs/sessions/contrib/changesView/browser/changesView.ts index 040941a0465b5..47eca5b0933e9 100644 --- a/src/vs/sessions/contrib/changesView/browser/changesView.ts +++ b/src/vs/sessions/contrib/changesView/browser/changesView.ts @@ -673,6 +673,9 @@ export class ChangesViewPane extends ViewPane { if (this.tree) { const tree = this.tree; + // Re-layout when collapse state changes so the card height adjusts + this.renderDisposables.add(tree.onDidChangeContentHeight(() => this.layoutTree())); + const openFileItem = (item: IChangesFileItem, items: IChangesFileItem[], sideBySide: boolean) => { const { uri: modifiedFileUri, originalUri, isDeletion } = item; const currentIndex = items.indexOf(item); From 53f6daeba0f4df410ff76447f6c5287606de2023 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 12:29:04 +0000 Subject: [PATCH 40/50] update hover and background colors in quick input for dark and light themes --- extensions/theme-2026/themes/2026-dark.json | 2 +- extensions/theme-2026/themes/2026-light.json | 2 +- extensions/theme-2026/themes/styles.css | 271 ------------------- 3 files changed, 2 insertions(+), 273 deletions(-) diff --git a/extensions/theme-2026/themes/2026-dark.json b/extensions/theme-2026/themes/2026-dark.json index 8d3f082600d84..f1b217f5cca39 100644 --- a/extensions/theme-2026/themes/2026-dark.json +++ b/extensions/theme-2026/themes/2026-dark.json @@ -227,7 +227,7 @@ "quickInputList.focusBackground": "#3994BC26", "quickInputList.focusForeground": "#bfbfbf", "quickInputList.focusIconForeground": "#bfbfbf", - "quickInputList.hoverBackground": "#515253", + "quickInputList.hoverBackground": "#262728", "terminal.selectionBackground": "#3994BC33", "terminal.background": "#191A1B", "terminal.border": "#2A2B2CFF", diff --git a/extensions/theme-2026/themes/2026-light.json b/extensions/theme-2026/themes/2026-light.json index c743617242634..3a17e4284b414 100644 --- a/extensions/theme-2026/themes/2026-light.json +++ b/extensions/theme-2026/themes/2026-light.json @@ -229,7 +229,7 @@ "extensionButton.prominentHoverBackground": "#0064CC", "pickerGroup.border": "#EEEEF1", "pickerGroup.foreground": "#202020", - "quickInput.background": "#F0F0F3", + "quickInput.background": "#FAFAFD", "quickInput.foreground": "#202020", "quickInputList.focusBackground": "#0069CC1A", "quickInputList.focusForeground": "#202020", diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index e4008b89a0a6e..c19b276e67eba 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -5,7 +5,6 @@ :root { --radius-sm: 4px; - --radius-md: 6px; --radius-lg: 8px; --shadow-sm: 0 0 4px rgba(0, 0, 0, 0.08); @@ -19,16 +18,10 @@ /* Panel depth shadows cast onto the editor surface */ --shadow-depth-x: 5px 0 10px -4px rgba(0, 0, 0, 0.05); --shadow-depth-y: 0 5px 10px -4px rgba(0, 0, 0, 0.04); - - --backdrop-blur-md: blur(20px) saturate(180%); - --backdrop-blur-lg: blur(40px) saturate(180%); } /* Dark theme: add brightness reduction for contrast-safe luminosity blending over bright backgrounds */ .monaco-workbench.vs-dark { - --backdrop-blur-md: blur(20px) saturate(180%) brightness(0.55); - --backdrop-blur-lg: blur(40px) saturate(180%) brightness(0.55); - --shadow-depth-x: 5px 0 12px -4px rgba(0, 0, 0, 0.14); --shadow-depth-y: 0 5px 12px -4px rgba(0, 0, 0, 0.10); } @@ -125,17 +118,6 @@ /* Quick Input (Command Palette) */ .monaco-workbench .quick-input-widget { box-shadow: var(--shadow-xl) !important; - background-color: color-mix(in srgb, var(--vscode-quickInput-background) 60%, transparent) !important; - backdrop-filter: var(--backdrop-blur-lg); - -webkit-backdrop-filter: var(--backdrop-blur-lg); -} - -/* Remove backdrop-filter when quick chat is active, because it creates a new - containing block that shifts position:fixed suggest widgets to the right. */ -.monaco-workbench .quick-input-widget:has(.interactive-session) { - backdrop-filter: none; - -webkit-backdrop-filter: none; - background-color: var(--vscode-quickInput-background) !important; } .monaco-workbench.vs-dark .quick-input-widget { @@ -160,10 +142,6 @@ background: transparent !important; } -.monaco-workbench.vs .quick-input-widget .quick-input-list .monaco-list-row:hover:not(.selected):not(.focused) { - background-color: color-mix(in srgb, var(--vscode-list-hoverBackground) 95%, black) !important; -} - .monaco-workbench .quick-input-list .quick-input-list-entry .quick-input-list-separator { height: 16px; margin-top: 2px; @@ -222,8 +200,6 @@ overflow: visible; } -/* .monaco-workbench .notifications-list-container, -.monaco-workbench > .notifications-center > .notifications-center-header, */ .monaco-workbench .notifications-list-container .monaco-list-rows { background: transparent !important; } @@ -233,10 +209,6 @@ .monaco-workbench .context-view .monaco-menu { box-shadow: var(--shadow-lg); border: none; - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--vscode-menu-background) 60%, transparent) !important; - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench .monaco-select-box-dropdown-container { @@ -245,15 +217,6 @@ .monaco-workbench .monaco-menu-container > .monaco-scrollable-element { box-shadow: var(--shadow-lg) !important; - background: color-mix(in srgb, var(--vscode-menu-background) 60%, transparent) !important; - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); -} - -.monaco-workbench .action-widget { - background: color-mix(in srgb, var(--vscode-menu-background) 60%, transparent) !important; - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench .action-widget .action-widget-action-bar { @@ -263,9 +226,6 @@ /* Suggest Widget */ .monaco-workbench .monaco-editor .suggest-widget { box-shadow: var(--shadow-lg); - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); - background: color-mix(in srgb, var(--vscode-editorSuggestWidget-background) 60%, transparent) !important; } .monaco-workbench.vs-dark .monaco-editor .suggest-widget { @@ -276,35 +236,21 @@ /* Find Widget */ .monaco-workbench .monaco-editor .find-widget { box-shadow: var(--shadow-lg); - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench .inline-chat-gutter-menu { box-shadow: var(--shadow-lg); - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); } /* Dialog */ .monaco-workbench .monaco-dialog-box { border: 1px solid var(--vscode-dialog-border); box-shadow: var(--shadow-xl); - backdrop-filter: var(--backdrop-blur-lg); - -webkit-backdrop-filter: var(--backdrop-blur-lg); - background: color-mix(in srgb, var(--vscode-editor-background) 60%, transparent) !important; } /* Peek View */ .monaco-workbench .monaco-editor .peekview-widget { box-shadow: var(--shadow-hover); - background: color-mix(in srgb, var(--vscode-peekViewEditor-background) 60%, transparent) !important; - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); -} - -.monaco-workbench.vs-dark .monaco-editor .peekview-widget { - background: color-mix(in srgb, var(--vscode-peekViewEditor-background) 60%, transparent) !important; } .monaco-workbench .monaco-editor .peekview-widget .head, @@ -313,33 +259,22 @@ } .monaco-editor .monaco-hover { - background-color: color-mix(in srgb, var(--vscode-editorHoverWidget-background) 60%, transparent) !important; box-shadow: var(--shadow-sm-strong); - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench .monaco-hover.workbench-hover, .monaco-hover.workbench-hover { - background-color: color-mix(in srgb, var(--vscode-editorHoverWidget-background) 60%, transparent) !important; - backdrop-filter: var(--backdrop-blur-lg) !important; - -webkit-backdrop-filter: var(--backdrop-blur-lg) !important; box-shadow: var(--shadow-sm-strong); } .monaco-workbench .defineKeybindingWidget { border: 1px solid var(--vscode-editorWidget-border); box-shadow: var(--shadow-lg) !important; - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); - background-color: color-mix(in srgb, var(--vscode-editorHoverWidget-background) 60%, transparent) !important; } .monaco-workbench .chat-editor-overlay-widget, .monaco-workbench .chat-diff-change-content-widget { box-shadow: var(--shadow-md); - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench.vs-dark .chat-editor-overlay-widget, @@ -381,15 +316,6 @@ } /* Breadcrumbs */ -.monaco-workbench .breadcrumbs-picker-widget { - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); - background: color-mix(in srgb, var(--vscode-breadcrumbPicker-background) 60%, transparent) !important; -} - -.monaco-workbench.vs-dark .breadcrumbs-picker-widget { - background: color-mix(in srgb, var(--vscode-breadcrumbPicker-background) 60%, transparent) !important; -} .monaco-workbench.vs .breadcrumbs-control { border-bottom: 1px solid var(--vscode-editorWidget-border); @@ -441,20 +367,15 @@ /* SCM */ .monaco-workbench .scm-view .scm-provider { box-shadow: var(--shadow-sm); - border-radius: var(--radius-md); } /* Debug Toolbar */ .monaco-workbench .debug-toolbar { box-shadow: var(--shadow-lg); - backdrop-filter: var(--backdrop-blur-lg) !important; - -webkit-backdrop-filter: var(--backdrop-blur-lg) !important; } .monaco-workbench .debug-hover-widget { box-shadow: var(--shadow-lg); - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); color: var(--vscode-editor-foreground) !important; } @@ -470,13 +391,6 @@ /* Parameter Hints */ .monaco-workbench .monaco-editor .parameter-hints-widget { box-shadow: var(--shadow-lg); - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); -} - -.monaco-workbench.vs-dark .monaco-editor .parameter-hints-widget, -.monaco-workbench.vs .monaco-editor .parameter-hints-widget { - background: color-mix(in srgb, var(--vscode-editorWidget-background) 60%, transparent) !important; } /* Minimap */ @@ -534,8 +448,6 @@ } .monaco-editor .rename-box.preview { - backdrop-filter: var(--backdrop-blur-lg) !important; - -webkit-backdrop-filter: var(--backdrop-blur-lg) !important; box-shadow: var(--shadow-hover) !important; border: 1px solid var(--vscode-editorWidget-border); } @@ -548,8 +460,6 @@ .notebookOverlay .monaco-list-row .cell-title-toolbar { background-color: var(--vscode-editorWidget-background) !important; - backdrop-filter: var(--backdrop-blur-md); - -webkit-backdrop-filter: var(--backdrop-blur-md); box-shadow: var(--shadow-sm); } @@ -562,9 +472,6 @@ /* Command Center */ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { box-shadow: inset var(--shadow-sm) !important; - background: color-mix(in srgb, var(--vscode-commandCenter-background) 60%, transparent) !important; - -webkit-backdrop-filter: var(--backdrop-blur-md); - backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:hover { @@ -598,181 +505,3 @@ opacity: 1; color: var(--vscode-descriptionForeground); } - -/* ============================================================================================ - * Reduced Transparency - disable backdrop-filter blur and color-mix transparency effects - * for improved rendering performance. Controlled by workbench.reduceTransparency setting. - * ============================================================================================ */ - -/* Reset blur variables to none */ -.monaco-workbench.monaco-reduce-transparency { - --backdrop-blur-sm: none; - --backdrop-blur-md: none; - --backdrop-blur-lg: none; -} - -/* Quick Input (Command Palette) */ -.monaco-workbench.monaco-reduce-transparency .quick-input-widget { - background-color: var(--vscode-quickInput-background) !important; - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -/* Notifications */ -.monaco-workbench.monaco-reduce-transparency .notification-toast-container { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-notifications-background) !important; -} - -.monaco-workbench.monaco-reduce-transparency .notifications-list-container { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-notifications-background) !important; -} - -.monaco-workbench.monaco-reduce-transparency .notification-list-item, -.monaco-workbench.monaco-reduce-transparency .notifications-center-header { - background: var(--vscode-notifications-background) !important; -} - -.monaco-workbench.monaco-reduce-transparency .notifications-center { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-notifications-background) !important; -} - -/* Context Menu / Action Widget */ -.monaco-workbench.monaco-reduce-transparency .action-widget { - background: var(--vscode-menu-background) !important; - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -/* Suggest Widget */ -.monaco-workbench.monaco-reduce-transparency .monaco-editor .suggest-widget { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-editorSuggestWidget-background) !important; -} - -/* Find Widget */ -.monaco-workbench.monaco-reduce-transparency .monaco-editor .find-widget { - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -.monaco-workbench.monaco-reduce-transparency .inline-chat-gutter-menu { - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -/* Dialog */ -.monaco-workbench.monaco-reduce-transparency .monaco-dialog-box { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-editor-background) !important; -} - -/* Peek View */ -.monaco-workbench.monaco-reduce-transparency .monaco-editor .peekview-widget { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-peekViewEditor-background) !important; -} - -/* Hover */ -.monaco-reduce-transparency .monaco-hover { - background-color: var(--vscode-editorHoverWidget-background) !important; - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -.monaco-reduce-transparency .monaco-hover.workbench-hover, -.monaco-reduce-transparency .workbench-hover { - background-color: var(--vscode-editorHoverWidget-background) !important; - -webkit-backdrop-filter: none !important; - backdrop-filter: none !important; -} - -/* Keybinding Widget */ -.monaco-workbench.monaco-reduce-transparency .defineKeybindingWidget { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-editorHoverWidget-background) !important; -} - -/* Chat Editor Overlay */ -.monaco-workbench.monaco-reduce-transparency .chat-editor-overlay-widget, -.monaco-workbench.monaco-reduce-transparency .chat-diff-change-content-widget { - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -/* Debug Toolbar */ -.monaco-workbench.monaco-reduce-transparency .debug-toolbar { - -webkit-backdrop-filter: none !important; - backdrop-filter: none !important; -} - -.monaco-workbench.monaco-reduce-transparency .debug-hover-widget { - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -/* Parameter Hints */ -.monaco-workbench.monaco-reduce-transparency .monaco-editor .parameter-hints-widget { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-editorWidget-background) !important; -} - -/* Sticky Scroll */ -.monaco-workbench.monaco-reduce-transparency .monaco-editor .sticky-widget { - -webkit-backdrop-filter: none !important; - backdrop-filter: none !important; - background: var(--vscode-editor-background) !important; -} - -.monaco-workbench.monaco-reduce-transparency .monaco-editor .sticky-widget .sticky-widget-line-numbers, -.monaco-workbench.monaco-reduce-transparency .monaco-editor .sticky-widget .sticky-widget-lines, -.monaco-workbench.monaco-reduce-transparency .monaco-editor .sticky-widget .sticky-line-content { - -webkit-backdrop-filter: none !important; - backdrop-filter: none !important; - background: var(--vscode-editor-background) !important; -} - -/* Rename Box */ -.monaco-reduce-transparency .monaco-editor .rename-box.preview { - -webkit-backdrop-filter: none !important; - backdrop-filter: none !important; -} - -/* Notebook */ -.monaco-workbench.monaco-reduce-transparency .notebookOverlay .monaco-list-row .cell-title-toolbar { - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -/* Command Center */ -.monaco-workbench.monaco-reduce-transparency .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { - background: var(--vscode-commandCenter-background) !important; - -webkit-backdrop-filter: none; - backdrop-filter: none; -} - -.monaco-workbench.monaco-reduce-transparency .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:hover { - background: var(--vscode-commandCenter-activeBackground) !important; -} - -/* Breadcrumbs */ -.monaco-workbench.monaco-reduce-transparency .breadcrumbs-picker-widget { - -webkit-backdrop-filter: none; - backdrop-filter: none; - background: var(--vscode-breadcrumbPicker-background) !important; -} - -/* Quick Input filter input */ -.monaco-workbench.monaco-reduce-transparency .quick-input-widget .quick-input-filter .monaco-inputbox { - background: var(--vscode-input-background) !important; -} From 76944e5e3598f5432b6be9e0f685797171f008b2 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 12:32:23 +0000 Subject: [PATCH 41/50] update breadcrumbPicker and notificationCenterHeader background colors for improved theme consistency --- extensions/theme-2026/themes/2026-light.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/theme-2026/themes/2026-light.json b/extensions/theme-2026/themes/2026-light.json index 3a17e4284b414..44fec6d16a7d0 100644 --- a/extensions/theme-2026/themes/2026-light.json +++ b/extensions/theme-2026/themes/2026-light.json @@ -208,7 +208,7 @@ "breadcrumb.background": "#FFFFFF", "breadcrumb.focusForeground": "#202020", "breadcrumb.activeSelectionForeground": "#202020", - "breadcrumbPicker.background": "#F0F0F3", + "breadcrumbPicker.background": "#FAFAFD", "notificationCenter.border": "#F0F1F2FF", "notificationCenterHeader.foreground": "#202020", "notificationCenterHeader.background": "#FAFAFD", From cfc50baa1f3f14a7d516cc98bcb8d31bb4cbabe7 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 12:47:18 +0000 Subject: [PATCH 42/50] update quick input title background color for improved theme aesthetics --- extensions/theme-2026/themes/2026-light.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/theme-2026/themes/2026-light.json b/extensions/theme-2026/themes/2026-light.json index 44fec6d16a7d0..9a3a44fb872c3 100644 --- a/extensions/theme-2026/themes/2026-light.json +++ b/extensions/theme-2026/themes/2026-light.json @@ -256,7 +256,7 @@ "gauge.errorForeground": "#ad0707", "gauge.errorBackground": "#ad070740", "statusBarItem.prominentHoverForeground": "#FFFFFF", - "quickInputTitle.background": "#F0F0F3", + "quickInputTitle.background": "#FAFAFD", "chat.requestBubbleBackground": "#EEF4FB", "chat.requestBubbleHoverBackground": "#E6EDFA", "chat.thinkingShimmer": "#999999", From 16869c2e449dcedb54d8be6d4e1bfa041bea99c4 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 2 Mar 2026 12:50:50 +0000 Subject: [PATCH 43/50] remove background color mixing for suggest widget and title bar hover effect --- extensions/theme-2026/themes/styles.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index c19b276e67eba..87790e5e7eba9 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -229,7 +229,6 @@ } .monaco-workbench.vs-dark .monaco-editor .suggest-widget { - background: color-mix(in srgb, var(--vscode-editorSuggestWidget-background) 60%, transparent) !important; border: 1px solid var(--vscode-editorWidget-border); } @@ -474,10 +473,6 @@ box-shadow: inset var(--shadow-sm) !important; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:hover { - background: color-mix(in srgb, var(--vscode-commandCenter-activeBackground) 60%, transparent) !important; -} - .monaco-workbench .part.titlebar .command-center .agent-status-pill { border-color: var(--vscode-input-border); } From 967655a586ad9707c1621d4a30241a5e7ae313f8 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 2 Mar 2026 14:01:41 +0100 Subject: [PATCH 44/50] :lipstick: --- src/vs/sessions/contrib/chat/browser/chat.contribution.ts | 2 +- .../contrib/sessions/browser/sessionsManagementService.ts | 3 ++- .../terminal/browser/sessionsTerminalContribution.ts | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts index 37fe6dc58844e..74b536c694779 100644 --- a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts @@ -104,7 +104,7 @@ class OpenSessionWorktreeInVSCodeNotAvailableAction extends Action2 { menu: [{ id: Menus.TitleBarRight, group: 'navigation', - order: 9, + order: 10, when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated(), IsActiveSessionBackgroundProviderContext.toNegated()) }] }); diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts index 546c0b73faf39..d5c50a1a17d82 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts @@ -488,7 +488,8 @@ export class SessionsManagementService extends Disposable implements ISessionsMa a.label === b.label && a.resource.toString() === b.resource.toString() && a.repository?.toString() === b.repository?.toString() && - a.worktree?.toString() === b.worktree?.toString() + a.worktree?.toString() === b.worktree?.toString() && + a.providerType === b.providerType ); } diff --git a/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts b/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts index 809716d687b28..269d3a63a23bd 100644 --- a/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts +++ b/src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts @@ -113,7 +113,9 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben return; } - const targetPath = getSessionCwd(session) ?? await this._pathService.userHome(); + const sessionCwd = getSessionCwd(session); + + const targetPath = sessionCwd ?? await this._pathService.userHome(); const targetFsPath = targetPath.fsPath; if (this._lastTargetFsPath?.toLowerCase() === targetFsPath.toLowerCase()) { return; @@ -149,7 +151,7 @@ class OpenSessionInTerminalAction extends Action2 { menu: [{ id: Menus.TitleBarRight, group: 'navigation', - order: 10, + order: 11, when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SessionsWelcomeVisibleContext.toNegated()) }] }); From cb9e1165f2bf74663457c12a1b9ec1c596207d61 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 2 Mar 2026 14:31:16 +0100 Subject: [PATCH 45/50] chat - confirm from toast should not force reveal session (#298693) * chat - confirm from toast should not force reveal session * ccr --- .../chat/browser/chatWindowNotifier.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts b/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts index d9fe036dac880..3dce99f35720d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts @@ -11,17 +11,14 @@ import { autorunDelta, autorunIterableDelta } from '../../../../base/common/obse import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { FocusMode } from '../../../../platform/native/common/native.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IChatModel, IChatRequestNeedsInputInfo } from '../common/model/chatModel.js'; -import { IChatService } from '../common/chatService/chatService.js'; +import { IChatService, IChatToolInvocation, ToolConfirmKind } from '../common/chatService/chatService.js'; import { migrateLegacyTerminalToolSpecificData } from '../common/chat.js'; import { ChatConfiguration, ChatNotificationMode } from '../common/constants.js'; import { IChatWidgetService } from './chat.js'; -import { AcceptToolConfirmationActionId, IToolConfirmationActionContext } from './actions/chatToolActions.js'; -import { isMacintosh } from '../../../../base/common/platform.js'; /** * Observes all live chat models and triggers OS notifications when any model @@ -38,7 +35,6 @@ export class ChatWindowNotifier extends Disposable implements IWorkbenchContribu @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IHostService private readonly _hostService: IHostService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ICommandService private readonly _commandService: ICommandService, ) { super(); @@ -116,28 +112,44 @@ export class ChatWindowNotifier extends Disposable implements IWorkbenchContribu actions: [actionLabel], }, cts.token); + if (result.actionIndex === 0 && !isQuestionCarousel && this._confirmAllow(sessionResource)) { + return; // skip focusing/opening chat if we successfully confirmed the tool invocation from the toast action + } + if (result.clicked || typeof result.actionIndex === 'number') { await this._hostService.focus(targetWindow, { mode: FocusMode.Force }); const widget = await this._chatWidgetService.openSession(sessionResource); widget?.focusInput(); - - if (result.actionIndex === 0 && !isQuestionCarousel) { - await this._commandService.executeCommand(AcceptToolConfirmationActionId, { sessionResource } satisfies IToolConfirmationActionContext); - } } } finally { this._clearNotification(sessionResource); } } + private _confirmAllow(sessionResource: URI): boolean { + const model = this._chatService.getSession(sessionResource); + const lastResponse = model?.lastRequest?.response; + if (!lastResponse) { + return false; + } + for (const part of lastResponse.response.value) { + const state = part.kind === 'toolInvocation' ? part.state.get() : undefined; + if (state?.type === IChatToolInvocation.StateKind.WaitingForConfirmation || state?.type === IChatToolInvocation.StateKind.WaitingForPostApproval) { + state.confirm({ type: ToolConfirmKind.UserAction }); + return true; + } + } + return false; + } + private _getNotificationBody(sessionResource: URI, info: IChatRequestNeedsInputInfo, isQuestionCarousel: boolean): string { - const terminalCommand = this._getPendingTerminalCommand(sessionResource); if (isQuestionCarousel) { return localize('questionCarouselDetail', "Questions need your input."); } - if (isMacintosh && terminalCommand) { - return this._sanitizeOSToastText(terminalCommand); // prefer full command on macOS where you can approve from the toast + const terminalCommand = this._getPendingTerminalCommand(sessionResource); + if (terminalCommand) { + return this._sanitizeOSToastText(terminalCommand); } if (info.detail) { return this._sanitizeOSToastText(info.detail); From 610ebdc1c9e579ab2a0a4b8e73668c0e93f08c94 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 2 Mar 2026 15:37:40 +0100 Subject: [PATCH 46/50] sessions - when applying changes, offer action to open (#298718) * feat - add open folder action to applyToParentRepo * feat - add query parameters to `open` action --- .../browser/applyToParentRepo.contribution.ts | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/vs/sessions/contrib/applyToParentRepo/browser/applyToParentRepo.contribution.ts b/src/vs/sessions/contrib/applyToParentRepo/browser/applyToParentRepo.contribution.ts index 96a23763e803c..3b5ce530a7b12 100644 --- a/src/vs/sessions/contrib/applyToParentRepo/browser/applyToParentRepo.contribution.ts +++ b/src/vs/sessions/contrib/applyToParentRepo/browser/applyToParentRepo.contribution.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { toAction } from '../../../../base/common/actions.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; import { autorun } from '../../../../base/common/observable.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { localize, localize2 } from '../../../../nls.js'; @@ -11,7 +13,9 @@ import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/c import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IFileService } from '../../../../platform/files/common/files.js'; -import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; import { CHAT_CATEGORY } from '../../../../workbench/contrib/chat/browser/actions/chatActions.js'; import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js'; @@ -83,6 +87,8 @@ class ApplyToParentRepoAction extends Action2 { const fileService = accessor.get(IFileService); const notificationService = accessor.get(INotificationService); const logService = accessor.get(ILogService); + const openerService = accessor.get(IOpenerService); + const productService = accessor.get(IProductService); const activeSession = sessionManagementService.getActiveSession(); if (!activeSession?.worktree || !activeSession?.repository) { @@ -139,19 +145,46 @@ class ApplyToParentRepoAction extends Action2 { } } + const openFolderAction = toAction({ + id: 'applyToParentRepo.openFolder', + label: localize('openInVSCode', "Open in VS Code"), + run: () => { + const scheme = productService.quality === 'stable' + ? 'vscode' + : productService.quality === 'exploration' + ? 'vscode-exploration' + : 'vscode-insiders'; + + const params = new URLSearchParams(); + params.set('windowId', '_blank'); + params.set('session', activeSession.resource.toString()); + + openerService.open(URI.from({ + scheme, + authority: Schemas.file, + path: repoRoot.path, + query: params.toString(), + }), { openExternal: true }); + } + }); + const totalApplied = copiedCount + deletedCount; if (errorCount > 0) { - notificationService.warn( - totalApplied === 1 + notificationService.notify({ + severity: Severity.Warning, + message: totalApplied === 1 ? localize('applyToParentRepoPartial1', "Applied 1 file to parent repo with {0} error(s).", errorCount) - : localize('applyToParentRepoPartialN', "Applied {0} files to parent repo with {1} error(s).", totalApplied, errorCount) - ); + : localize('applyToParentRepoPartialN', "Applied {0} files to parent repo with {1} error(s).", totalApplied, errorCount), + actions: { primary: [openFolderAction] } + }); } else if (totalApplied > 0) { - notificationService.info( - totalApplied === 1 + notificationService.notify({ + severity: Severity.Info, + message: totalApplied === 1 ? localize('applyToParentRepoSuccess1', "Applied 1 file to parent repo.") - : localize('applyToParentRepoSuccessN', "Applied {0} files to parent repo.", totalApplied) - ); + : localize('applyToParentRepoSuccessN', "Applied {0} files to parent repo.", totalApplied), + actions: { primary: [openFolderAction] } + }); } } } From 61e42ed8379fd3f6ddbae5a9bbc7fad9a743f306 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 2 Mar 2026 15:50:29 +0100 Subject: [PATCH 47/50] remove test --- .../test/browser/sessionsTerminalContribution.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts b/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts index ebc29e7e13f65..5cb061bb85e5c 100644 --- a/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts +++ b/src/vs/sessions/contrib/terminal/test/browser/sessionsTerminalContribution.test.ts @@ -149,14 +149,6 @@ suite('SessionsTerminalContribution', () => { assert.strictEqual(createdTerminals[0].cwd.fsPath, repoUri.fsPath); }); - test('does not create a terminal when background session has no paths', async () => { - const session = makeAgentSession({ providerType: AgentSessionProviders.Background }); - activeSessionObs.set(session, undefined); - await tick(); - - assert.strictEqual(createdTerminals.length, 0); - }); - // --- Non-background providers: use home directory --- test('uses home directory for a cloud agent session', async () => { From 41d62661eef4f51c22db49cd3298215447c8a226 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:55:23 +0100 Subject: [PATCH 48/50] Enhance sync functionality with loading state and improved UI feedback (#298699) --- .../contrib/chat/browser/syncIndicator.ts | 48 +++++++++++++------ .../gitSync/browser/gitSync.contribution.ts | 22 +++++++-- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/vs/sessions/contrib/chat/browser/syncIndicator.ts b/src/vs/sessions/contrib/chat/browser/syncIndicator.ts index e07089daee537..b63411982720a 100644 --- a/src/vs/sessions/contrib/chat/browser/syncIndicator.ts +++ b/src/vs/sessions/contrib/chat/browser/syncIndicator.ts @@ -7,6 +7,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { autorun } from '../../../../base/common/observable.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { localize } from '../../../../nls.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -24,6 +25,7 @@ export class SyncIndicator extends Disposable { private _repository: IGitRepository | undefined; private _selectedBranch: string | undefined; private _visible = true; + private _syncing = false; private readonly _renderDisposables = this._register(new DisposableStore()); private readonly _stateDisposables = this._register(new DisposableStore()); @@ -81,13 +83,13 @@ export class SyncIndicator extends Disposable { this._renderDisposables.add(dom.addDisposableListener(button, dom.EventType.CLICK, (e) => { dom.EventHelper.stop(e, true); - this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri); + this._executeSyncCommand(); })); this._renderDisposables.add(dom.addDisposableListener(button, dom.EventType.KEY_DOWN, (e) => { if (e.key === 'Enter' || e.key === ' ') { dom.EventHelper.stop(e, true); - this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri); + this._executeSyncCommand(); } })); @@ -102,6 +104,20 @@ export class SyncIndicator extends Disposable { this._update(); } + private async _executeSyncCommand(): Promise { + if (this._syncing) { + return; + } + this._syncing = true; + this._update(); + try { + await this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri); + } finally { + this._syncing = false; + this._update(); + } + } + private _getAheadBehind(): { ahead: number; behind: number } | undefined { if (!this._repository) { return undefined; @@ -132,7 +148,7 @@ export class SyncIndicator extends Disposable { } const counts = this._getAheadBehind(); - if (!counts || !this._visible) { + if ((!counts && !this._syncing) || !this._visible) { this._slotElement.style.display = 'none'; return; } @@ -140,24 +156,26 @@ export class SyncIndicator extends Disposable { this._slotElement.style.display = ''; dom.clearNode(this._buttonElement); - dom.append(this._buttonElement, renderIcon(Codicon.sync)); + dom.append(this._buttonElement, renderIcon(this._syncing ? ThemeIcon.modify(Codicon.sync, 'spin') : Codicon.sync)); - const parts: string[] = []; - if (counts.behind > 0) { - parts.push(`${counts.behind}↓`); - } - if (counts.ahead > 0) { - parts.push(`${counts.ahead}↑`); - } + if (counts) { + const parts: string[] = []; + if (counts.behind > 0) { + parts.push(`${counts.behind}↓`); + } + if (counts.ahead > 0) { + parts.push(`${counts.ahead}↑`); + } - const label = dom.append(this._buttonElement, dom.$('span.sessions-chat-dropdown-label')); - label.textContent = parts.join('\u00a0'); + const label = dom.append(this._buttonElement, dom.$('span.sessions-chat-dropdown-label')); + label.textContent = parts.join('\u00a0'); + } this._buttonElement.title = localize( 'syncIndicator.tooltip', "Synchronize Changes ({0} to pull, {1} to push)", - counts.behind, - counts.ahead, + counts?.behind ?? 0, + counts?.ahead ?? 0, ); } } diff --git a/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts b/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts index 65869c6cb8b5b..2aed42bfd4cf4 100644 --- a/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts +++ b/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun } from '../../../../base/common/observable.js'; +import { autorun, observableValue } from '../../../../base/common/observable.js'; import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -27,6 +28,7 @@ class GitSyncContribution extends Disposable implements IWorkbenchContribution { private readonly _syncActionDisposable = this._register(new MutableDisposable()); private readonly _gitRepoDisposables = this._register(new DisposableStore()); + private readonly _isSyncing = observableValue(this, false); constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -60,6 +62,7 @@ class GitSyncContribution extends Disposable implements IWorkbenchContribution { } repoDisposables.add(autorun(innerReader => { const state = repository.state.read(innerReader); + const isSyncing = this._isSyncing.read(innerReader); const head = state.HEAD; if (!head?.upstream) { this._syncActionDisposable.clear(); @@ -70,14 +73,16 @@ class GitSyncContribution extends Disposable implements IWorkbenchContribution { const behind = head.behind ?? 0; const hasSyncChanges = ahead > 0 || behind > 0; contextKey.set(hasSyncChanges); - this._syncActionDisposable.value = registerSyncAction(behind, ahead); + this._syncActionDisposable.value = registerSyncAction(behind, ahead, isSyncing, (syncing) => { + this._isSyncing.set(syncing, undefined); + }); })); }); })); } } -function registerSyncAction(behind: number, ahead: number): IDisposable { +function registerSyncAction(behind: number, ahead: number, isSyncing: boolean, setSyncing: (syncing: boolean) => void): IDisposable { if (behind === 0 && ahead === 0) { return Disposable.None; } @@ -89,6 +94,8 @@ function registerSyncAction(behind: number, ahead: number): IDisposable { title += `${ahead}↑`; } + const icon = isSyncing ? ThemeIcon.modify(Codicon.sync, 'spin') : Codicon.sync; + class SynchronizeChangesAction extends Action2 { static readonly ID = 'chatEditing.synchronizeChanges'; @@ -97,7 +104,7 @@ function registerSyncAction(behind: number, ahead: number): IDisposable { id: SynchronizeChangesAction.ID, title, tooltip: localize('synchronizeChanges', "Synchronize Changes with Git (Behind {0}, Ahead {1})", behind, ahead), - icon: Codicon.sync, + icon, category: CHAT_CATEGORY, menu: [ { @@ -114,7 +121,12 @@ function registerSyncAction(behind: number, ahead: number): IDisposable { const commandService = accessor.get(ICommandService); const sessionManagementService = accessor.get(ISessionsManagementService); const worktreeUri = sessionManagementService.getActiveSession()?.worktree; - await commandService.executeCommand('git.sync', worktreeUri); + setSyncing(true); + try { + await commandService.executeCommand('git.sync', worktreeUri); + } finally { + setSyncing(false); + } } } return registerAction2(SynchronizeChangesAction); From b9b33d2877b3137dcf80dd7aedcbfcfdfa251b7d Mon Sep 17 00:00:00 2001 From: Elie Richa Date: Mon, 2 Mar 2026 16:04:43 +0100 Subject: [PATCH 49/50] include debug extension host env in shell env (#241078) (#298276) * include debug extension host env in shell env (#241078) * Use terminalEnvironment.mergeEnvironments for platform-specific considerations Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Clone the shell env to avoid mutation of a shared object --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../terminal/electron-browser/localTerminalBackend.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts index 4435552b283f7..e30e2c69b465e 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts @@ -37,6 +37,7 @@ import { IStatusbarService } from '../../../services/statusbar/browser/statusbar import { memoize } from '../../../../base/common/decorators.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; +import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js'; import { shouldUseEnvironmentVariableCollection } from '../../../../platform/terminal/common/terminalEnvironment.js'; import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; @@ -95,6 +96,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke @INativeHostService private readonly _nativeHostService: INativeHostService, @IStatusbarService statusBarService: IStatusbarService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, ) { super(_localPtyService, logService, historyService, _configurationResolverService, statusBarService, workspaceContextService); @@ -294,7 +296,14 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke @memoize async getShellEnvironment(): Promise { - return this._shellEnvironmentService.getShellEnv(); + const env = { ... await this._shellEnvironmentService.getShellEnv() }; + + // If running in the context of an extension development host, include the environment derived from the launch configuration + if (this._environmentService.debugExtensionHost.env) { + terminalEnvironment.mergeEnvironments(env, this._environmentService.debugExtensionHost.env); + } + + return env; } async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix'): Promise { From 952b80928b01dbc5672b3a90917a76335043960b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:58:25 +0000 Subject: [PATCH 50/50] Add chat tip for /fork conversation feature (#298084) --- .../chat/browser/actions/chatActions.ts | 23 +++++++++++++++ .../contrib/chat/browser/chatTipCatalog.ts | 21 ++++++++++++++ .../contrib/chat/browser/chatTipService.ts | 20 +++++++------ .../chat/browser/chatTipStorageKeys.ts | 2 ++ .../chat/test/browser/chatTipService.test.ts | 28 ++++++++++++++++++- 5 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 7516c88f8edf3..98fb8e824f961 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -85,6 +85,7 @@ export const GENERATE_PROMPT_COMMAND_ID = 'workbench.action.chat.generatePrompt' export const GENERATE_SKILL_COMMAND_ID = 'workbench.action.chat.generateSkill'; export const GENERATE_AGENT_COMMAND_ID = 'workbench.action.chat.generateAgent'; export const GENERATE_HOOK_COMMAND_ID = 'workbench.action.chat.generateHook'; +export const INSERT_FORK_CONVERSATION_COMMAND_ID = 'workbench.action.chat.insertForkConversationCommand'; const defaultChat = { manageSettingsUrl: product.defaultChatAgent?.manageSettingsUrl ?? '', @@ -1370,6 +1371,28 @@ export function registerChatActions() { } }); + registerAction2(class InsertForkConversationSlashCommandAction extends Action2 { + constructor() { + super({ + id: INSERT_FORK_CONVERSATION_COMMAND_ID, + title: localize2('insertForkConversationSlashCommand', "Insert Fork Command"), + shortTitle: localize2('insertForkConversationSlashCommand.short', "Insert /fork"), + category: CHAT_CATEGORY, + icon: Codicon.repoForked, + f1: true, + precondition: ChatContextKeys.enabled + }); + } + + async run(accessor: ServicesAccessor): Promise { + const commandService = accessor.get(ICommandService); + await commandService.executeCommand('workbench.action.chat.open', { + query: '/fork ', + isPartialQuery: true, + }); + } + }); + registerAction2(class OpenChatFeatureSettingsAction extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/chat/browser/chatTipCatalog.ts b/src/vs/workbench/contrib/chat/browser/chatTipCatalog.ts index d130efd72758d..b29cc69ac81da 100644 --- a/src/vs/workbench/contrib/chat/browser/chatTipCatalog.ts +++ b/src/vs/workbench/contrib/chat/browser/chatTipCatalog.ts @@ -18,6 +18,7 @@ import { GENERATE_ON_DEMAND_INSTRUCTIONS_COMMAND_ID, GENERATE_PROMPT_COMMAND_ID, GENERATE_SKILL_COMMAND_ID, + INSERT_FORK_CONVERSATION_COMMAND_ID, } from './actions/chatActions.js'; /** @@ -287,6 +288,26 @@ export const TIP_CATALOG: readonly ITipDefinition[] = [ when: ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Agent), excludeWhenCommandsExecuted: ['workbench.action.chat.queueMessage', 'workbench.action.chat.steerWithMessage'], }, + { + id: 'tip.forkConversation', + buildMessage(ctx) { + const kb = formatKeybinding(ctx, INSERT_FORK_CONVERSATION_COMMAND_ID); + return new MarkdownString( + localize( + 'tip.forkConversation', + "Use [{0}](command:{1}){2} to branch the conversation. Explore a different approach without losing the original context.", + '/fork', + INSERT_FORK_CONVERSATION_COMMAND_ID, + kb + ) + ); + }, + excludeWhenCommandsExecuted: [ + INSERT_FORK_CONVERSATION_COMMAND_ID, + 'workbench.action.chat.forkConversation', + TipTrackingCommands.ForkConversationUsed, + ], + }, { id: 'tip.yoloMode', buildMessage() { diff --git a/src/vs/workbench/contrib/chat/browser/chatTipService.ts b/src/vs/workbench/contrib/chat/browser/chatTipService.ts index 3bc000338552c..57ee53a820f0e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatTipService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatTipService.ts @@ -54,6 +54,8 @@ export const CREATE_PROMPT_TRACKING_COMMAND = TipTrackingCommands.CreatePromptUs export const CREATE_AGENT_TRACKING_COMMAND = TipTrackingCommands.CreateAgentUsed; /** @deprecated Use TipTrackingCommands.CreateSkillUsed */ export const CREATE_SKILL_TRACKING_COMMAND = TipTrackingCommands.CreateSkillUsed; +/** @deprecated Use TipTrackingCommands.ForkConversationUsed */ +export const FORK_CONVERSATION_TRACKING_COMMAND = TipTrackingCommands.ForkConversationUsed; export const IChatTipService = createDecorator('chatTipService'); @@ -216,9 +218,9 @@ export class ChatTipService extends Disposable implements IChatTipService { this._tracker.recordCommandExecuted(TipTrackingCommands.AttachFilesReferenceUsed); } - const createCommandTrackingId = this._getCreateSlashCommandTrackingId(message); - if (createCommandTrackingId) { - this._tracker.recordCommandExecuted(createCommandTrackingId); + const slashCommandTrackingId = this._getSlashCommandTrackingId(message); + if (slashCommandTrackingId) { + this._tracker.recordCommandExecuted(slashCommandTrackingId); } })); @@ -267,20 +269,20 @@ export class ChatTipService extends Disposable implements IChatTipService { }); } - private _getCreateSlashCommandTrackingId(message: IParsedChatRequest): string | undefined { + private _getSlashCommandTrackingId(message: IParsedChatRequest): string | undefined { for (const part of message.parts) { if (part.kind === ChatRequestSlashCommandPart.Kind) { const slashCommand = (part as ChatRequestSlashCommandPart).slashCommand.command; - return this._toCreateSlashCommandTrackingId(slashCommand); + return this._toSlashCommandTrackingId(slashCommand); } } const trimmed = message.text.trimStart(); - const match = /^\/(create-(?:instructions|prompt|agent|skill))(?:\s|$)/.exec(trimmed); - return match ? this._toCreateSlashCommandTrackingId(match[1]) : undefined; + const match = /^\/(create-(?:instructions|prompt|agent|skill)|fork)(?:\s|$)/.exec(trimmed); + return match ? this._toSlashCommandTrackingId(match[1]) : undefined; } - private _toCreateSlashCommandTrackingId(command: string): string | undefined { + private _toSlashCommandTrackingId(command: string): string | undefined { switch (command) { case 'create-instructions': return CREATE_AGENT_INSTRUCTIONS_TRACKING_COMMAND; @@ -290,6 +292,8 @@ export class ChatTipService extends Disposable implements IChatTipService { return CREATE_AGENT_TRACKING_COMMAND; case 'create-skill': return CREATE_SKILL_TRACKING_COMMAND; + case 'fork': + return FORK_CONVERSATION_TRACKING_COMMAND; default: return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/chatTipStorageKeys.ts b/src/vs/workbench/contrib/chat/browser/chatTipStorageKeys.ts index 1b7a995f8c86b..f7ccacdd94ccc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatTipStorageKeys.ts +++ b/src/vs/workbench/contrib/chat/browser/chatTipStorageKeys.ts @@ -45,4 +45,6 @@ export const TipTrackingCommands = { CreateAgentUsed: 'chat.tips.createAgent.commandUsed', /** Tracked when user executes /create-skill. */ CreateSkillUsed: 'chat.tips.createSkill.commandUsed', + /** Tracked when user executes /fork. */ + ForkConversationUsed: 'chat.tips.forkConversation.commandUsed', } as const; diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts index ba43aa820ee2c..f6e5316b0bb0b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts @@ -17,7 +17,7 @@ import { MockContextKeyService } from '../../../../../platform/keybinding/test/c import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js'; import { IProductService } from '../../../../../platform/product/common/productService.js'; import { IStorageService, InMemoryStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; -import { ChatTipService, CREATE_AGENT_INSTRUCTIONS_TRACKING_COMMAND, CREATE_AGENT_TRACKING_COMMAND, CREATE_PROMPT_TRACKING_COMMAND, CREATE_SKILL_TRACKING_COMMAND, IChatTip, ITipDefinition, TipEligibilityTracker } from '../../browser/chatTipService.js'; +import { ChatTipService, CREATE_AGENT_INSTRUCTIONS_TRACKING_COMMAND, CREATE_AGENT_TRACKING_COMMAND, CREATE_PROMPT_TRACKING_COMMAND, CREATE_SKILL_TRACKING_COMMAND, FORK_CONVERSATION_TRACKING_COMMAND, IChatTip, ITipDefinition, TipEligibilityTracker } from '../../browser/chatTipService.js'; import { AgentFileType, IPromptPath, IPromptsService, IResolvedAgentFile, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; import { URI } from '../../../../../base/common/uri.js'; import { ChatContextKeys } from '../../common/actions/chatContextKeys.js'; @@ -189,6 +189,32 @@ suite('ChatTipService', () => { assert.ok(!executedCommands.includes(CREATE_AGENT_INSTRUCTIONS_TRACKING_COMMAND)); assert.ok(!executedCommands.includes(CREATE_AGENT_TRACKING_COMMAND)); assert.ok(!executedCommands.includes(CREATE_SKILL_TRACKING_COMMAND)); + assert.ok(!executedCommands.includes(FORK_CONVERSATION_TRACKING_COMMAND)); + }); + + test('records fork tip usage for submitted /fork command', () => { + const submitRequestEmitter = testDisposables.add(new Emitter<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest }>()); + instantiationService.stub(IChatService, { + onDidSubmitRequest: submitRequestEmitter.event, + getSession: () => undefined, + } as Partial as IChatService); + + createService(); + + submitRequestEmitter.fire({ + chatSessionResource: URI.parse('chat:session-fork'), + message: { + text: '/fork', + parts: [], + }, + }); + + const executedCommands = JSON.parse(storageService.get('chat.tips.executedCommands', StorageScope.APPLICATION) ?? '[]') as string[]; + assert.ok(executedCommands.includes(FORK_CONVERSATION_TRACKING_COMMAND)); + assert.ok(!executedCommands.includes(CREATE_AGENT_INSTRUCTIONS_TRACKING_COMMAND)); + assert.ok(!executedCommands.includes(CREATE_PROMPT_TRACKING_COMMAND)); + assert.ok(!executedCommands.includes(CREATE_AGENT_TRACKING_COMMAND)); + assert.ok(!executedCommands.includes(CREATE_SKILL_TRACKING_COMMAND)); }); test('returns Auto switch tip when current model is gpt-4.1', () => {