From ea87c5d5ae4f97474121296f068b72c0f8bc21b1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:58:50 -0800 Subject: [PATCH 01/34] Add input latency tracking to nativeEditContext Fixes #278180 --- .../controller/editContext/native/nativeEditContext.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index b417161930f..53c4692efa6 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -33,6 +33,7 @@ import { IME } from '../../../../../base/common/ime.js'; import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js'; import { ILogService, LogLevel } from '../../../../../platform/log/common/log.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; +import { inputLatency } from '../../../../../base/browser/performance.js'; // Corresponds to classes in nativeEditContext.css enum CompositionClassName { @@ -125,12 +126,16 @@ export class NativeEditContext extends AbstractEditContext { this.logService.trace('NativeEditContext#cut (before viewController.cut)'); this._viewController.cut(); })); + this._register(addDisposableListener(this.domNode.domNode, 'selectionchange', () => { + inputLatency.onSelectionChange(); + })); this._register(addDisposableListener(this.domNode.domNode, 'keyup', (e) => this._onKeyUp(e))); this._register(addDisposableListener(this.domNode.domNode, 'keydown', async (e) => this._onKeyDown(e))); this._register(addDisposableListener(this._imeTextArea.domNode, 'keyup', (e) => this._onKeyUp(e))); this._register(addDisposableListener(this._imeTextArea.domNode, 'keydown', async (e) => this._onKeyDown(e))); this._register(addDisposableListener(this.domNode.domNode, 'beforeinput', async (e) => { + inputLatency.onBeforeInput(); if (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak') { this._onType(this._viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); } @@ -166,6 +171,7 @@ export class NativeEditContext extends AbstractEditContext { this._register(editContextAddDisposableListener(this._editContext, 'characterboundsupdate', (e) => this._updateCharacterBounds(e))); let highSurrogateCharacter: string | undefined; this._register(editContextAddDisposableListener(this._editContext, 'textupdate', (e) => { + inputLatency.onInput(); const text = e.text; if (text.length === 1) { const charCode = text.charCodeAt(0); @@ -355,10 +361,12 @@ export class NativeEditContext extends AbstractEditContext { // --- Private methods --- private _onKeyUp(e: KeyboardEvent) { + inputLatency.onKeyUp(); this._viewController.emitKeyUp(new StandardKeyboardEvent(e)); } private _onKeyDown(e: KeyboardEvent) { + inputLatency.onKeyDown(); const standardKeyboardEvent = new StandardKeyboardEvent(e); // When the IME is visible, the keys, like arrow-left and arrow-right, should be used to navigate in the IME, and should not be propagated further if (standardKeyboardEvent.keyCode === KeyCode.KEY_IN_COMPOSITION) { From 7d3b6734362d60e2a1a0a43fbf90d058ce253c8c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:01:13 -0800 Subject: [PATCH 02/34] Add GPU acceleration tracking to input latency logs Fixes #278175 --- .../contrib/performance/browser/inputLatencyContrib.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts b/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts index 80676ac2b56..9c750e37600 100644 --- a/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts +++ b/src/vs/workbench/contrib/performance/browser/inputLatencyContrib.ts @@ -7,6 +7,7 @@ import { inputLatency } from '../../../../base/browser/performance.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Event } from '../../../../base/common/event.js'; import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; @@ -16,6 +17,7 @@ export class InputLatencyContrib extends Disposable implements IWorkbenchContrib private readonly _scheduler: RunOnceScheduler; constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, @ITelemetryService private readonly _telemetryService: ITelemetryService ) { @@ -64,16 +66,20 @@ export class InputLatencyContrib extends Disposable implements IWorkbenchContrib render: InputLatencyStatisticFragment; total: InputLatencyStatisticFragment; sampleCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The number of samples measured.' }; + gpuAcceleration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether GPU acceleration was enabled at the time the event was reported.' }; }; - type PerformanceInputLatencyEvent = inputLatency.IInputLatencyMeasurements; + type PerformanceInputLatencyEvent = inputLatency.IInputLatencyMeasurements & { + gpuAcceleration: boolean; + }; this._telemetryService.publicLog2('performance.inputLatency', { keydown: measurements.keydown, input: measurements.input, render: measurements.render, total: measurements.total, - sampleCount: measurements.sampleCount + sampleCount: measurements.sampleCount, + gpuAcceleration: this._configurationService.getValue('editor.experimentalGpuAcceleration') === 'on' }); } } From 2718f9051d1245a9fdfb7442b1912e8f4c04b582 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 18 Nov 2025 15:02:19 -0800 Subject: [PATCH 03/34] String utilities perf improvements and rtrim bug fix --- src/vs/base/common/strings.ts | 72 ++++++++++++++++++++----- src/vs/base/test/common/strings.test.ts | 12 +++++ 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index c341d98e26a..9e5a4b32c9f 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -132,12 +132,7 @@ export function trim(haystack: string, needle: string = ' '): string { return rtrim(trimmed, needle); } -/** - * Removes all occurrences of needle from the beginning of haystack. - * @param haystack string to trim - * @param needle the thing to trim - */ -export function ltrim(haystack: string, needle: string): string { +export function ltrim_old(haystack: string, needle: string): string { if (!haystack || !needle) { return haystack; } @@ -156,11 +151,31 @@ export function ltrim(haystack: string, needle: string): string { } /** - * Removes all occurrences of needle from the end of haystack. + * Removes all occurrences of needle from the beginning of haystack. * @param haystack string to trim * @param needle the thing to trim */ -export function rtrim(haystack: string, needle: string): string { +export function ltrim(haystack: string, needle: string): string { + if (!haystack || !needle) { + return haystack; + } + + const needleLen = needle.length; + let offset = 0; + if (needleLen === 1) { + const ch = needle.charCodeAt(0); + while (offset < haystack.length && haystack.charCodeAt(offset) === ch) { + offset++; + } + } else { + while (haystack.startsWith(needle, offset)) { + offset += needleLen; + } + } + return haystack.substring(offset); +} + +export function rtrim_old(haystack: string, needle: string): string { if (!haystack || !needle) { return haystack; } @@ -189,6 +204,36 @@ export function rtrim(haystack: string, needle: string): string { return haystack.substring(0, offset); } +/** + * Removes all occurrences of needle from the end of haystack. + * @param haystack string to trim + * @param needle the thing to trim + */ +export function rtrim(haystack: string, needle: string): string { + if (!haystack || !needle) { + return haystack; + } + + const needleLen = needle.length, + haystackLen = haystack.length; + + if (needleLen === 1) { + let end = haystackLen; + const ch = needle.charCodeAt(0); + while (end > 0 && haystack.charCodeAt(end - 1) === ch) { + end--; + } + return haystack.substring(0, end); + } + + let offset = haystackLen; + while (offset > 0 && haystack.endsWith(needle, offset)) { + offset -= needleLen; + } + + return haystack.substring(0, offset); +} + export function convertSimple2RegExpPattern(pattern: string): string { return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); } @@ -1197,10 +1242,9 @@ export class AmbiguousCharacters { ); }); - private static readonly cache = new LRUCachedFunction< - string[], - AmbiguousCharacters - >({ getCacheKey: JSON.stringify }, (locales) => { + private static readonly cache = new LRUCachedFunction((localesStr) => { + const locales = localesStr.split(','); + function arrayToMap(arr: number[]): Map { const result = new Map(); for (let i = 0; i < arr.length; i += 2) { @@ -1257,8 +1301,8 @@ export class AmbiguousCharacters { return new AmbiguousCharacters(map); }); - public static getInstance(locales: Set): AmbiguousCharacters { - return AmbiguousCharacters.cache.get(Array.from(locales)); + public static getInstance(locales: Iterable): AmbiguousCharacters { + return AmbiguousCharacters.cache.get(Array.from(locales).join(',')); } private static _locales = new Lazy(() => diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index cfdf7836392..cfde5423e06 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -215,6 +215,11 @@ suite('Strings', () => { assert.strictEqual(strings.ltrim('///', '/'), ''); assert.strictEqual(strings.ltrim('', ''), ''); assert.strictEqual(strings.ltrim('', '/'), ''); + // Multi-character needle with consecutive repetitions + assert.strictEqual(strings.ltrim('---hello', '---'), 'hello'); + assert.strictEqual(strings.ltrim('------hello', '---'), 'hello'); + assert.strictEqual(strings.ltrim('---------hello', '---'), 'hello'); + assert.strictEqual(strings.ltrim('hello---', '---'), 'hello---'); }); test('rtrim', () => { @@ -228,6 +233,13 @@ suite('Strings', () => { assert.strictEqual(strings.rtrim('///', '/'), ''); assert.strictEqual(strings.rtrim('', ''), ''); assert.strictEqual(strings.rtrim('', '/'), ''); + // Multi-character needle with consecutive repetitions (bug fix) + assert.strictEqual(strings.rtrim('hello---', '---'), 'hello'); + assert.strictEqual(strings.rtrim('hello------', '---'), 'hello'); + assert.strictEqual(strings.rtrim('hello---------', '---'), 'hello'); + assert.strictEqual(strings.rtrim('---hello', '---'), '---hello'); + assert.strictEqual(strings.rtrim('hello world' + '---'.repeat(10), '---'), 'hello world'); + assert.strictEqual(strings.rtrim('path/to/file///', '//'), 'path/to/file/'); }); test('trim', () => { From 9cbd1b3a017178ef8c54c0151c6f08c0d4a8d0e7 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 18 Nov 2025 15:05:12 -0800 Subject: [PATCH 04/34] Remove old versions. --- src/vs/base/common/strings.ts | 47 ----------------------------------- 1 file changed, 47 deletions(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 9e5a4b32c9f..6299a251b89 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -132,24 +132,6 @@ export function trim(haystack: string, needle: string = ' '): string { return rtrim(trimmed, needle); } -export function ltrim_old(haystack: string, needle: string): string { - if (!haystack || !needle) { - return haystack; - } - - const needleLen = needle.length; - if (needleLen === 0 || haystack.length === 0) { - return haystack; - } - - let offset = 0; - - while (haystack.indexOf(needle, offset) === offset) { - offset = offset + needleLen; - } - return haystack.substring(offset); -} - /** * Removes all occurrences of needle from the beginning of haystack. * @param haystack string to trim @@ -175,35 +157,6 @@ export function ltrim(haystack: string, needle: string): string { return haystack.substring(offset); } -export function rtrim_old(haystack: string, needle: string): string { - if (!haystack || !needle) { - return haystack; - } - - const needleLen = needle.length, - haystackLen = haystack.length; - - if (needleLen === 0 || haystackLen === 0) { - return haystack; - } - - let offset = haystackLen, - idx = -1; - - while (true) { - idx = haystack.lastIndexOf(needle, offset - 1); - if (idx === -1 || idx + needleLen !== offset) { - break; - } - if (idx === 0) { - return ''; - } - offset = idx; - } - - return haystack.substring(0, offset); -} - /** * Removes all occurrences of needle from the end of haystack. * @param haystack string to trim From f6e1df2daafd3d5a943ae614beb5fd75fb575c1f Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 18 Nov 2025 21:41:00 -0800 Subject: [PATCH 05/34] fix inline chat css (#278268) * fix inline chat css * extra header --- src/vs/workbench/contrib/chat/browser/media/chat.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 05476177d90..a5a3692b3fe 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -78,7 +78,6 @@ font-size: var(--vscode-chat-font-size-body-s); color: var(--vscode-descriptionForeground); overflow: hidden; - margin-left: 4px; } .interactive-item-container .detail-container .detail .agentOrSlashCommandDetected A { @@ -2494,6 +2493,10 @@ have to be updated for changes to the rules above, or to support more deeply nes display: none; } + .interactive-request .header.partially-disabled .detail-container { + margin-left: 4px; + } + .interactive-item-container .header .detail .codicon-check { margin-right: 7px; vertical-align: middle; From 3d1980dfa63c9427628fe9dac3f130044c0debcd Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:03:10 -0800 Subject: [PATCH 06/34] Revert "Pick up latest TS for building VS Code" This reverts commit 3560855cffddc7cdd6ccc6e36a1ff8d03195d3f7. Reverting since this has resulted in false positive unreachable code reports while editing. Doesn't seem to effect tsc. Collecting logs --- package-lock.json | 72 +++++++++++++++++++++++------------------------ package.json | 6 ++-- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff7180b2fe8..72e6541080c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,7 +80,7 @@ "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", "@typescript-eslint/utils": "^8.45.0", - "@typescript/native-preview": "^7.0.0-dev.20251117", + "@typescript/native-preview": "^7.0.0-dev.20250812.1", "@vscode/gulp-electron": "^1.38.2", "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", @@ -152,7 +152,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20251117", + "typescript": "^6.0.0-dev.20251110", "typescript-eslint": "^8.45.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -2509,28 +2509,28 @@ } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-JgKY4Q6jRCszCJ46c8tVrGVnmdiRPSKTW0UQvcyxdI7LG9NYMchJ/W7iUyFZVjG8BV1iUTl3DYml1xErPHLKeg==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-yzCDN6wUV1kibefOTwxw1MdeIgaJOgN5/a06cMyUlEDcXBriV4O2v+yeXY8c3yzUaVVVO8CKtHPbCMwro4j1Dw==", "dev": true, "license": "Apache-2.0", "bin": { "tsgo": "bin/tsgo.js" }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20251117.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20251117.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20251110.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20251110.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-O7Hhb9m8AZJCAUSBbGmZs7Vm890Kh5Z3xAAASs+L4thtPM0oRckeaoXLvHeE9Qy1p8qG//EmZ3+uSdtUTV4wqg==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-x3DskzZCgk5qA7BCcCC/8XuZiycvZk5reeqkNTuDYeWyF1ZCKa8WWZRbW5LaunaOtXV6UsAPRCqRC8Wx34mMCg==", "cpu": [ "arm64" ], @@ -2542,9 +2542,9 @@ ] }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-/I/iWWvUvuy8BK0bXn5Kz6z2QwknwD2kl2estQxgsz9VgHHyLSyjAg7c18pX/re0Z9ISPz7wutEKabzdtRW8Uw==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-tuS4akGtsPs+RTiVXEXOT41+as23DXCOhzeOEtYYVdhWVuMBYLHksdTx5PGoQrCc4SfETp5jDwhyqUaVYLDGcA==", "cpu": [ "x64" ], @@ -2556,9 +2556,9 @@ ] }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-Mfnc8CytGICsYJCMbu3FwE/KDcVg4/QTFix6O31oUkj9ERp3zbSePVMQulkJTH2vuhDvJnVISHzIYawtq5QPTQ==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-I9zOzHXFqIQIcTcf2Sx9EF6gLOKXUCMo5gsjoQm4/R22+19+TMLeAs7Q1aTvd8CX8kFCtpI1eeyNzIf76rxELA==", "cpu": [ "arm" ], @@ -2570,9 +2570,9 @@ ] }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-YSkmJb4/WrS6ZMEJSDbv5o2Garms3+3yKsH+Y3JLUab0namf1Br7T53ydW7ijV2rE7j9DgJs9P+GNu8753St3Q==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-IvSeQ1iw4uvBZ8+XrO9z80J9KfbkbTzfXliPHUsjZqEtpOJTf/Mv7xzMbv4mN4xOEGVUyBG47p846oW2HknogA==", "cpu": [ "arm64" ], @@ -2584,9 +2584,9 @@ ] }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-R5KvnKuGsbozjHbmA+zPa4xVkQSutvtU9/PQJ7vjJL0xsvSsRUgOE2V2jlT+KnfjAhYVoIg2njtHdf0uv5k9Ow==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-OWy32tgpP70rSRvmQZ6OgJpuv1pi4mQdng00eF3tfHheHluX3mvqqe86H0FOv5B9PuxlGwOZSUot1XHWadhAWg==", "cpu": [ "x64" ], @@ -2598,9 +2598,9 @@ ] }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-xfEwDD9BwCm2gFf0AePfvXxjgQ/EDBDLRbSejtShTSFwrgdnRJ7iW63/ns/i31qLesTzGZaLxeAV8zgh6C2Ibg==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-u/Bo0gIcQCv/4MDnV5f2FZR1dEdN2jk3MfkmJLKGG1zwbak4MY7sWNzvSRJHihwK2SxtcJEHus4tKb2ra2Rhig==", "cpu": [ "arm64" ], @@ -2612,9 +2612,9 @@ ] }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-GhJ4GIygHSU86gZw6NkOnJKi/XW0Yw+1quanZ6BaOAZ+HY6aftuESy+NlbC6nUSGE2xmbvxqJgqchCIlC6YPoA==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-1CysgwFRuNjR0bBYv6RI3fbXtAwzD5OlbxqOQFhf2lUulMZRIkP1w4eCChSndLVCTfnUEt5Bnmn1JEUauIE+kQ==", "cpu": [ "x64" ], @@ -17308,9 +17308,9 @@ "dev": true }, "node_modules/typescript": { - "version": "6.0.0-dev.20251117", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20251117.tgz", - "integrity": "sha512-BJkVdQDGWE8KxtuSLvWLQ/ju+n6FdSM8rq/2B9myrmKXeKa9HRG36MOTMgfZQUWDmPd2f5+U8fhU7xO2+WNa3g==", + "version": "6.0.0-dev.20251110", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20251110.tgz", + "integrity": "sha512-tHG+EJXTSaUCMbTNApOuVE3WmgOmEqUwQiAXnmwsF/sVKhPFHQA0+S1hml0Ro8kpayvD0d9AX5iC2S2s+TIQxQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index efb504f4ffd..932b3cd5697 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "extensions-ci": "node ./node_modules/gulp/bin/gulp.js extensions-ci", "extensions-ci-pr": "node ./node_modules/gulp/bin/gulp.js extensions-ci-pr", "perf": "node scripts/code-perf.js", - "update-build-ts-version": "npm install -D typescript@next @typescript/native-preview && (cd build && npm run compile)" + "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run compile)" }, "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", @@ -142,7 +142,7 @@ "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", "@typescript-eslint/utils": "^8.45.0", - "@typescript/native-preview": "^7.0.0-dev.20251117", + "@typescript/native-preview": "^7.0.0-dev.20250812.1", "@vscode/gulp-electron": "^1.38.2", "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", @@ -214,7 +214,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20251117", + "typescript": "^6.0.0-dev.20251110", "typescript-eslint": "^8.45.0", "util": "^0.12.4", "webpack": "^5.94.0", From 3df30807a118d250572e42ac53a05cf52a885557 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Nov 2025 07:54:09 +0100 Subject: [PATCH 07/34] agent sessions - fix sorting of sessions (#278274) --- .../chat/browser/agentSessions/agentSessionsViewer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 1417702b724..137bd445003 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -336,13 +336,13 @@ export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegat export class AgentSessionsSorter implements ITreeSorter { compare(sessionA: IAgentSessionViewModel, sessionB: IAgentSessionViewModel): number { - const aHasEndTime = !!sessionA.timing.endTime; - const bHasEndTime = !!sessionB.timing.endTime; + const aInProgress = sessionA.status === ChatSessionStatus.InProgress; + const bInProgress = sessionB.status === ChatSessionStatus.InProgress; - if (!aHasEndTime && bHasEndTime) { + if (aInProgress && !bInProgress) { return -1; // a (in-progress) comes before b (finished) } - if (aHasEndTime && !bHasEndTime) { + if (!aInProgress && bInProgress) { return 1; // a (finished) comes after b (in-progress) } From c8f4e041f70da6bb0bc37088f8d1ce795ee113ee Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 18 Nov 2025 23:08:38 -0800 Subject: [PATCH 08/34] Fix not properly reading observable (#278275) Tried to simplify, dropped the reader --- src/vs/workbench/contrib/chat/browser/chatViewPane.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 5ea583316e3..70682b5bfc8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -7,7 +7,7 @@ import { $, getWindow } from '../../../../base/browser/dom.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { MarshalledId } from '../../../../base/common/marshallingIds.js'; -import { autorun } from '../../../../base/common/observable.js'; +import { autorun, IReader } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -229,14 +229,14 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { })); this._widget.render(parent); - const updateWidgetVisibility = () => { - this._widget.setVisible(this.isBodyVisible() && !welcomeController.isShowingWelcome.get()); + const updateWidgetVisibility = (r?: IReader) => { + this._widget.setVisible(this.isBodyVisible() && !welcomeController.isShowingWelcome.read(r)); }; this._register(this.onDidChangeBodyVisibility(() => { updateWidgetVisibility(); })); this._register(autorun(r => { - updateWidgetVisibility(); + updateWidgetVisibility(r); })); const info = this.getTransferredOrPersistedSessionInfo(); From dc72558ec7a12e0d2784a37d83d3ce7a0f08db5c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Nov 2025 08:31:03 +0100 Subject: [PATCH 09/34] debt - reduce `in` operator (#278280) --- eslint.config.js | 1 - .../chat/browser/chatSessions/localChatSessionsProvider.ts | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index f35fadddf8a..9d83f9269e3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -274,7 +274,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts', 'src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts', 'src/vs/workbench/contrib/chat/browser/chatSessions/common.ts', - 'src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts', 'src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts', 'src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts', 'src/vs/workbench/contrib/chat/common/annotations.ts', diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index c58a714d68b..612d4d8b5bc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -16,7 +16,7 @@ import { IChatModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { ChatAgentLocation } from '../../common/constants.js'; -import { IChatWidget, IChatWidgetService } from '../chat.js'; +import { IChatWidget, IChatWidgetService, isIChatViewViewContext } from '../chat.js'; import { ChatSessionItemWithProvider } from './common.js'; export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider, IWorkbenchContribution { @@ -59,8 +59,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio this._register(this.chatWidgetService.onDidAddWidget(widget => { // Only fire for chat view instance if (widget.location === ChatAgentLocation.Chat && - typeof widget.viewContext === 'object' && - 'viewId' in widget.viewContext && + isIChatViewViewContext(widget.viewContext) && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { this._onDidChange.fire(); this._registerWidgetModelListeners(widget); @@ -69,7 +68,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio // Check for existing chat widgets and register listeners const existingWidgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) - .filter(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); + .filter(widget => isIChatViewViewContext(widget.viewContext) && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); existingWidgets.forEach(widget => { this._registerWidgetModelListeners(widget); From d83b77ec2a3506469fcdf3bf390e2152cf722c69 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:03:41 +0000 Subject: [PATCH 10/34] Workbench - fix floating toolbar separator color (#278282) --- src/vs/editor/contrib/floatingMenu/browser/floatingMenu.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/floatingMenu/browser/floatingMenu.css b/src/vs/editor/contrib/floatingMenu/browser/floatingMenu.css index ea9e47d9e2a..425b87c5258 100644 --- a/src/vs/editor/contrib/floatingMenu/browser/floatingMenu.css +++ b/src/vs/editor/contrib/floatingMenu/browser/floatingMenu.css @@ -39,6 +39,6 @@ } .action-item .action-label.separator { - background-color: var(--vscode-menu-separatorBackground); + background-color: var(--vscode-button-separator); } } From f3910c1215e95676c27eb3f26d4d5c8464e16842 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Nov 2025 10:10:18 +0100 Subject: [PATCH 11/34] agent sessions - implement local cache (#278288) * agent sessions - implement local cache * await provider activation --- .../agentSessions/agentSessionViewModel.ts | 140 ++++++++++++++++-- .../agentSessions/agentSessionsActions.ts | 2 +- .../agentSessions/agentSessionsView.ts | 8 +- .../agentSessions/agentSessionsViewFilter.ts | 2 +- .../chat/browser/chatSessions.contribution.ts | 12 +- .../chatSessions/view/chatSessionsView.ts | 2 +- .../chat/common/chatSessionsService.ts | 2 +- .../browser/agentSessionViewModel.test.ts | 45 ++---- .../test/common/mockChatSessionsService.ts | 4 +- 9 files changed, 159 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index 118dd173191..6bc1ea8599f 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -11,11 +11,12 @@ import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { URI } from '../../../../../base/common/uri.js'; +import { URI, UriComponents } from '../../../../../base/common/uri.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; -import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { AgentSessionProviders, getAgentSessionProviderIcon, getAgentSessionProviderName } from './agentSessions.js'; import { AgentSessionsViewFilter } from './agentSessionsViewFilter.js'; @@ -35,7 +36,7 @@ export interface IAgentSessionsViewModel { export interface IAgentSessionViewModel { - readonly provider: IChatSessionItemProvider; + readonly providerType: string; readonly providerLabel: string; readonly resource: URI; @@ -65,7 +66,7 @@ export interface IAgentSessionViewModel { } export function isLocalAgentSessionItem(session: IAgentSessionViewModel): boolean { - return session.provider.chatSessionType === localChatSessionType; + return session.providerType === localChatSessionType; } export function isAgentSession(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionViewModel { @@ -114,20 +115,25 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions }>(); private readonly filter: AgentSessionsViewFilter; + private readonly cache: AgentSessionsCache; constructor( options: IAgentSessionsViewModelOptions, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStorageService private readonly storageService: IStorageService, ) { super(); this.filter = this._register(this.instantiationService.createInstance(AgentSessionsViewFilter, { filterMenuId: options.filterMenuId })); - this.registerListeners(); + this.cache = this.instantiationService.createInstance(AgentSessionsCache); + this._sessions = this.cache.loadCachedSessions(); this.resolve(undefined); + + this.registerListeners(); } private registerListeners(): void { @@ -135,6 +141,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions this._register(this.chatSessionsService.onDidChangeAvailability(() => this.resolve(undefined))); this._register(this.chatSessionsService.onDidChangeSessionItems(provider => this.resolve(provider))); this._register(this.filter.onDidChange(() => this._onDidChangeSessions.fire())); + this._register(this.storageService.onWillSaveState(() => this.cache.saveCachedSessions(this._sessions))); } async resolve(provider: string | string[] | undefined): Promise { @@ -169,19 +176,16 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions mapSessionContributionToType.set(contribution.type, contribution); } + const resolvedProviders = new Set(); const sessions = new ResourceMap(); for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) { if (!providersToResolve.includes(undefined) && !providersToResolve.includes(provider.chatSessionType)) { - for (const session of this._sessions) { - if (session.provider.chatSessionType === provider.chatSessionType) { - sessions.set(session.resource, session); - } - } - - continue; // skipped for resolving, preserve existing ones + continue; // skip: not considered for resolving } const providerSessions = await provider.provideChatSessionItems(token); + resolvedProviders.add(provider.chatSessionType); + if (token.isCancellationRequested) { return; } @@ -240,7 +244,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions } sessions.set(session.resource, { - provider, + providerType: provider.chatSessionType, providerLabel, resource: session.resource, label: session.label, @@ -260,6 +264,12 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions } } + for (const session of this._sessions) { + if (!resolvedProviders.has(session.providerType)) { + sessions.set(session.resource, session); // fill in existing sessions for providers that did not resolve + } + } + this._sessions.length = 0; this._sessions.push(...sessions.values()); @@ -272,3 +282,107 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions this._onDidChangeSessions.fire(); } } + +//#region Sessions Cache + +interface ISerializedAgentSessionViewModel { + + readonly providerType: string; + readonly providerLabel: string; + + readonly resource: UriComponents; + + readonly icon: string; + + readonly label: string; + + readonly description?: string | IMarkdownString; + readonly tooltip?: string | IMarkdownString; + + readonly status: ChatSessionStatus; + readonly archived: boolean; + + readonly timing: { + readonly startTime: number; + readonly endTime?: number; + }; + + readonly statistics?: { + readonly files: number; + readonly insertions: number; + readonly deletions: number; + }; +} + +class AgentSessionsCache { + + private static readonly STORAGE_KEY = 'agentSessions.cache'; + + constructor(@IStorageService private readonly storageService: IStorageService) { } + + saveCachedSessions(sessions: IAgentSessionViewModel[]): void { + const serialized: ISerializedAgentSessionViewModel[] = sessions + .filter(session => + // Only consider providers that we own where we know that + // we can also invalidate the data after startup + // Other providers are bound to a different lifecycle (extensions) + session.providerType === AgentSessionProviders.Local || + session.providerType === AgentSessionProviders.Background || + session.providerType === AgentSessionProviders.Cloud + ) + .map(session => ({ + providerType: session.providerType, + providerLabel: session.providerLabel, + + resource: session.resource.toJSON(), + + icon: session.icon.id, + label: session.label, + description: session.description, + tooltip: session.tooltip, + + status: session.status, + archived: session.archived, + + timing: { + startTime: session.timing.startTime, + endTime: session.timing.endTime, + }, + + statistics: session.statistics, + })); + this.storageService.store(AgentSessionsCache.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + + loadCachedSessions(): IAgentSessionViewModel[] { + const sessionsCache = this.storageService.get(AgentSessionsCache.STORAGE_KEY, StorageScope.WORKSPACE); + if (!sessionsCache) { + return []; + } + + const cached = JSON.parse(sessionsCache) as ISerializedAgentSessionViewModel[]; + return cached.map(session => ({ + providerType: session.providerType, + providerLabel: session.providerLabel, + + resource: URI.revive(session.resource), + + icon: ThemeIcon.fromId(session.icon), + label: session.label, + description: session.description, + tooltip: session.tooltip, + + status: session.status, + archived: session.archived, + + timing: { + startTime: session.timing.startTime, + endTime: session.timing.endTime, + }, + + statistics: session.statistics, + })); + } +} + +//#endregion diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts index 2393f73fc9c..e4df4d24272 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts @@ -103,7 +103,7 @@ export class AgentSessionDiffActionViewItem extends ActionViewItem { const session = this.action.getSession(); - this.commandService.executeCommand(`agentSession.${session.provider.chatSessionType}.openChanges`, this.action.getSession().resource); + this.commandService.executeCommand(`agentSession.${session.providerType}.openChanges`, this.action.getSession().resource); } } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 8194158f99a..bf9cb3321ff 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -144,18 +144,22 @@ export class AgentSessionsView extends ViewPane { ...e.editorOptions, }; + await this.chatSessionsService.activateChatSessionItemProvider(session.providerType); // ensure provider is activated before trying to open + const group = e.sideBySide ? SIDE_GROUP : undefined; await this.chatWidgetService.openSession(session.resource, group, options); } - private showContextMenu({ element: session, anchor }: ITreeContextMenuEvent): void { + private async showContextMenu({ element: session, anchor }: ITreeContextMenuEvent): Promise { if (!session) { return; } + const provider = await this.chatSessionsService.activateChatSessionItemProvider(session.providerType); + const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, this.contextKeyService.createOverlay(getSessionItemContextOverlay( session, - session.provider, + provider, this.chatWidgetService, this.chatService, this.editorGroupsService diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewFilter.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewFilter.ts index 49722b0e60d..f87407f61fc 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewFilter.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewFilter.ts @@ -232,7 +232,7 @@ export class AgentSessionsViewFilter extends Disposable { return true; } - if (this.excludes.providers.includes(session.provider.chatSessionType)) { + if (this.excludes.providers.includes(session.providerType)) { return true; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index f40f4aee4da..fceb1312c0b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -662,7 +662,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ }); } - async hasChatSessionItemProvider(chatViewType: string): Promise { + async activateChatSessionItemProvider(chatViewType: string): Promise { await this._extensionService.whenInstalledExtensionsRegistered(); const resolvedType = this._resolveToPrimaryType(chatViewType); if (resolvedType) { @@ -671,16 +671,16 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const contribution = this._contributions.get(chatViewType)?.contribution; if (contribution && !this._isContributionAvailable(contribution)) { - return false; + return undefined; } if (this._itemsProviders.has(chatViewType)) { - return true; + return this._itemsProviders.get(chatViewType); } await this._extensionService.activateByEvent(`onChatSession:${chatViewType}`); - return this._itemsProviders.has(chatViewType); + return this._itemsProviders.get(chatViewType); } async canResolveChatSession(chatSessionResource: URI) { @@ -709,7 +709,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } private async getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { - if (!(await this.hasChatSessionItemProvider(chatSessionType))) { + if (!(await this.activateChatSessionItemProvider(chatSessionType))) { return []; } @@ -790,7 +790,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ request: IChatAgentRequest; metadata?: any; }, token: CancellationToken): Promise { - if (!(await this.hasChatSessionItemProvider(chatSessionType))) { + if (!(await this.activateChatSessionItemProvider(chatSessionType))) { throw Error(`Cannot find provider for ${chatSessionType}`); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index 20633da2090..d584f92babe 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -86,7 +86,7 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon private async updateViewRegistration(): Promise { // prepare all chat session providers const contributions = this.chatSessionsService.getAllChatSessionContributions(); - await Promise.all(contributions.map(contrib => this.chatSessionsService.hasChatSessionItemProvider(contrib.type))); + await Promise.all(contributions.map(contrib => this.chatSessionsService.activateChatSessionItemProvider(contrib.type))); const currentProviders = this.getAllChatSessionItemProviders(); const currentProviderIds = new Set(currentProviders.map(p => p.chatSessionType)); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index d2bd31b0229..5a13c99a58e 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -155,7 +155,7 @@ export interface IChatSessionsService { readonly onDidChangeInProgress: Event; registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable; - hasChatSessionItemProvider(chatSessionType: string): Promise; + activateChatSessionItemProvider(chatSessionType: string): Promise; getAllChatSessionItemProviders(): IChatSessionItemProvider[]; getAllChatSessionContributions(): IChatSessionsExtensionPoint[]; diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts index 546036f8209..f89cc801097 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts @@ -419,8 +419,7 @@ suite('AgentSessionsViewModel', () => { await viewModel.resolve(undefined); assert.strictEqual(viewModel.sessions.length, 1); - assert.strictEqual(viewModel.sessions[0].provider, provider); - assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, 'test-type'); + assert.strictEqual(viewModel.sessions[0].providerType, 'test-type'); }); }); @@ -536,7 +535,7 @@ suite('AgentSessionsViewModel', () => { await viewModel.resolve(undefined); assert.strictEqual(viewModel.sessions.length, 1); - assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, localChatSessionType); + assert.strictEqual(viewModel.sessions[0].providerType, localChatSessionType); }); }); @@ -725,11 +724,7 @@ suite('AgentSessionsViewModel - Helper Functions', () => { test('isLocalAgentSessionItem should identify local sessions', () => { const localSession: IAgentSessionViewModel = { - provider: { - chatSessionType: localChatSessionType, - onDidChangeChatSessionItems: Event.None, - provideChatSessionItems: async () => [] - }, + providerType: localChatSessionType, providerLabel: 'Local', icon: Codicon.chatSparkle, resource: URI.parse('test://local-1'), @@ -741,12 +736,8 @@ suite('AgentSessionsViewModel - Helper Functions', () => { }; const remoteSession: IAgentSessionViewModel = { - provider: { - chatSessionType: 'remote', - onDidChangeChatSessionItems: Event.None, - provideChatSessionItems: async () => [] - }, - providerLabel: 'Local', + providerType: 'remote', + providerLabel: 'Remote', icon: Codicon.chatSparkle, resource: URI.parse('test://remote-1'), label: 'Remote', @@ -762,11 +753,7 @@ suite('AgentSessionsViewModel - Helper Functions', () => { test('isAgentSession should identify session view models', () => { const session: IAgentSessionViewModel = { - provider: { - chatSessionType: 'test', - onDidChangeChatSessionItems: Event.None, - provideChatSessionItems: async () => [] - }, + providerType: 'test', providerLabel: 'Local', icon: Codicon.chatSparkle, resource: URI.parse('test://test-1'), @@ -787,11 +774,7 @@ suite('AgentSessionsViewModel - Helper Functions', () => { test('isAgentSessionsViewModel should identify sessions view models', () => { const session: IAgentSessionViewModel = { - provider: { - chatSessionType: 'test', - onDidChangeChatSessionItems: Event.None, - provideChatSessionItems: async () => [] - }, + providerType: 'test', providerLabel: 'Local', icon: Codicon.chatSparkle, resource: URI.parse('test://test-1'), @@ -855,7 +838,7 @@ suite('AgentSessionsViewFilter', () => { }; const session1: IAgentSessionViewModel = { - provider: provider1, + providerType: provider1.chatSessionType, providerLabel: 'Provider 1', icon: Codicon.chatSparkle, resource: URI.parse('test://session-1'), @@ -866,7 +849,7 @@ suite('AgentSessionsViewFilter', () => { }; const session2: IAgentSessionViewModel = { - provider: provider2, + providerType: provider2.chatSessionType, providerLabel: 'Provider 2', icon: Codicon.chatSparkle, resource: URI.parse('test://session-2'), @@ -907,7 +890,7 @@ suite('AgentSessionsViewFilter', () => { }; const archivedSession: IAgentSessionViewModel = { - provider, + providerType: provider.chatSessionType, providerLabel: 'Test Provider', icon: Codicon.chatSparkle, resource: URI.parse('test://archived-session'), @@ -918,7 +901,7 @@ suite('AgentSessionsViewFilter', () => { }; const activeSession: IAgentSessionViewModel = { - provider, + providerType: provider.chatSessionType, providerLabel: 'Test Provider', icon: Codicon.chatSparkle, resource: URI.parse('test://active-session'), @@ -959,7 +942,7 @@ suite('AgentSessionsViewFilter', () => { }; const failedSession: IAgentSessionViewModel = { - provider, + providerType: provider.chatSessionType, providerLabel: 'Test Provider', icon: Codicon.chatSparkle, resource: URI.parse('test://failed-session'), @@ -970,7 +953,7 @@ suite('AgentSessionsViewFilter', () => { }; const completedSession: IAgentSessionViewModel = { - provider, + providerType: provider.chatSessionType, providerLabel: 'Test Provider', icon: Codicon.chatSparkle, resource: URI.parse('test://completed-session'), @@ -981,7 +964,7 @@ suite('AgentSessionsViewFilter', () => { }; const inProgressSession: IAgentSessionViewModel = { - provider, + providerType: provider.chatSessionType, providerLabel: 'Test Provider', icon: Codicon.chatSparkle, resource: URI.parse('test://inprogress-session'), diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index fa3ad78161a..d63564825b1 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -73,8 +73,8 @@ export class MockChatSessionsService implements IChatSessionsService { this.contributions = contributions; } - async hasChatSessionItemProvider(chatSessionType: string): Promise { - return this.sessionItemProviders.has(chatSessionType); + async activateChatSessionItemProvider(chatSessionType: string): Promise { + return this.sessionItemProviders.get(chatSessionType); } getAllChatSessionItemProviders(): IChatSessionItemProvider[] { From f62fcfde011cc0db50abfa2b09c6056851936793 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 19 Nov 2025 10:31:50 +0100 Subject: [PATCH 12/34] prompts service: fix clearing the cachedFileLocations after a contribution change (#278296) prompt service: fix clearing the cachedFileLocations after a contribution change --- .../chat/common/promptSyntax/service/promptsServiceImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index dfef81722c3..e518cbcd80b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -359,7 +359,7 @@ export class PromptsService extends Disposable implements IPromptsService { bucket.set(uri, entryPromise); const flushCachesIfRequired = () => { - this.cachedFileLocations[PromptsType.agent] = undefined; + this.cachedFileLocations[type] = undefined; switch (type) { case PromptsType.agent: this.cachedCustomAgents.refresh(); From 1df4ff4a554b0813648d792b3756ed3e970fe938 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 19 Nov 2025 09:38:56 +0000 Subject: [PATCH 13/34] SCM - remove the `force-twistie` from the repository renderer in the Repositories and Changes views (#278291) --- .../workbench/contrib/scm/browser/scmRepositoriesViewPane.ts | 3 --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 7fead618873..d168d958d50 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -531,9 +531,6 @@ export class SCMRepositoriesViewPane extends ViewPane { getWidgetAriaLabel() { return localize('scm', "Source Control Repositories"); } - }, - twistieAdditionalCssClass: (e: unknown) => { - return isSCMRepository(e) ? 'force-twistie' : undefined; } } ) as WorkbenchCompressibleAsyncDataTree; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 9c755c94fb2..f78b971edad 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2405,9 +2405,7 @@ export class SCMViewPane extends ViewPane { }, accessibilityProvider: this.instantiationService.createInstance(SCMAccessibilityProvider), twistieAdditionalCssClass: (e: unknown) => { - if (isSCMRepository(e)) { - return 'force-twistie'; - } else if (isSCMActionButton(e) || isSCMInput(e)) { + if (isSCMActionButton(e) || isSCMInput(e)) { return 'force-no-twistie'; } From b51ac0c6bc7ef7c88aed072514175e4293385480 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 19 Nov 2025 10:24:57 +0000 Subject: [PATCH 14/34] feat: add new 'forward' codicon to the codicons library --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 121972 -> 122116 bytes src/vs/base/common/codiconsLibrary.ts | 1 + 2 files changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 28f3db1cc81ff69f72c52f4da2d0d71e668a9163..7048151d08af41aa5fa9d4b9297243c7334ef35b 100644 GIT binary patch delta 6182 zcmXBY3w+Jx9|!R7=j>{R(QGVc7e-p9VX3L)GR$0ZS;JZz+gx|StYLPBxg<1xLSoI_ zN0P-Z3Arapl63t`DxGtZv?Q(kKTlt;*ZcQ5&)M02&+qwtpU3Yx^)+54bzY@i?V)*d zPl))4NN~!;X|ppcvmZDk5;;w<>cXUqwDhFaJ5D=3a5i!db zKF36y#VGj@v8a^ia1XDd9B)Xa#3DsDNj)BtIGm9c(h7gb0eJ&?NI)O-#sB0Nd0+mA z%czzuIE8)EfcNk=j^HHTN1a^5K^zyGc!;Mo7GG&1O{E$8)ZA+D*=0W=C*_o!mUHrv zT#&EiYYy*qxgkG`OK!@qa!Y=byYiRZlfR`w{*ixSLlA<|4xwm|4v0WUJd8*@if-tR zC(sK|;wkh-f5c%B24g6O;W=51aY)AVcmb&xj|s>?CZ=K){6A~p3%U+KLlh@rd-62PH@n%dV6Zw*XHn3xxtdli(3!CIixguZSF8-8wyoALv zQNBeKy2$q!A^!56ye@<7l8!V7kAl!LTpBioRPEg0jJk$ ze1d=Fn4HJ&oZ9f*ve{g)aANRZs~U3@u{E{!H4C)26>*GLn8%!;I9JxaM9&_Q*xDQQ(TC}>Ba#u z2d>HDbXQ!R#p$8APK(o1am5y=m*UzjPH)9kT%3&**K=_;R$SS|>8rTLi?fO1YA?>F zitD~zoXyxEGx|y+Rd}5_LSZ9wq(T{UltKk_w8B=yu(aUIKVW;L8!5YKNx7QWy_QW;a#RF3&LS$y24SWDG|akrYRA^3FbtF zbIeH!A2BB@e9oMr@CDP96yYl-OUiBz_=XKrQ-p7srlbg0nWm%&-!V-|5w0<(D}2v1 z9m?a7!%tZ=e%*6`fR{ORi zQD+tK*uy&4HoS?s*7XWMTMcca?5)|_q+m+9OkoN04TaOpa>b`rac)*(?o5TkYs@VQ z<_udEOyzG=c$2wZ!BpuE5l?f#WNTk=VC&!bR;3a%3r#T-9<#0n2M+pyt-T7JnQtkW zxw}vCQCXaCD~V#7swX5dP1O^ITfN&wrG3Db8A8G@%vyz?m~{%~jHc>|Put==q~s#= zJq0uDW;T%7CpwQP46rV=3$WXS&11^OhV!_xfq6pVW9CVP3(Qjr<}_yH37<00C|qKi z?h`&^o>hG07w3nH5C7skCssyCi`FCg()@^|nrTi)_{%B{3G8r(EpsA*2h*I0U`oxL zh~Q?O3yE$0CtF`C?jFSXm6A(VNN6~gS^YwzTANY&UWpmW>k5h1a(?8OxP4y6(F>fpQF@INhfXP`o)Ep4N z#vclSOfw9GMoe>|1aIbD1v9RHDKUTip28$+P1t}zEk#_sqXwEXd8ZAtMR2((?vBL8 zK{d?tn~Q^OpevJ?Yv2*9eR!;$_teD;GUPIo7j1AaB`)*VNk%hyp$675d7%b(TH>JIhiC9XInpE3t1L|a`W0$Vp`%Ul-0l)zvGe=9X27M8U!A{=Y2qY<%9%=yPC zT(P`64jBD0Td4}^%ru4ROw%*s{#aZSl$aix&k^=A&D|vKp2d}^J^ zGp8!ahENwEXDn{xUv;@-r~wpW(VxbRanh5JtKU=oT0cw7uQV1y}JCy0rNKp zInRbonGJkZLZ=+quk{V;o7i_j-&_46`X%>U-Cz1I?teY5Q(SV~xdD3ywi;M6uwhV5 z{HXZ5gVP7EPY6mFpKyK1s3BK}77lAVtax~f;YGvmJ)8IJl@U*j*f{cuk$Xqp9JO+^ z@95IecM?+)mnAkl*YmlpN$r#J$Fv!fFlPOj`myn25048TH)q_QajxX<$rbkG`sXjF z^iC;AsYto|LgovrQ*EgOQyGgd6EW=>yXX)Ayub z&IrjEl~I^+D&t;e`^<#QQxg*=UYOKrvisz*llM;Xo02rO^VIaIWmE4>%b!*@tv;(y z*4nJw+0ohA*=w@vvTx@^<`n08=Z?zVpKHH4J#2c?^o`SR&4`;(H?!5utut@t^~zg6 ztMjb&`AzeC=a=Pw`{Ln($b!m(hS|w;WX|F_cjl(dT|M`5VOU{#VZ*${dE4hXin#k%wBN4@6Qu=4fn*Dq}xzOiPLZBxRg z<(qDn6_wS$QCJ>VKBs)|=CzxzRQOa3uPCi>Y)RO%c1!)%)U6HM>_fNJZI9o+Z2P?( zy>^`4IdJENU0rsq+;z2bO6B?8U3VAkzPKlBPtBXX-mKg!dk5|*wfj2n+rNK* zRm3|N55yc8d*IZ;;DedfZK{*1ORI0yMAR&<4Xa&NyR-I0?bX_U>q6=Z>#n>T|L(a% zMTaiEmwtHY;af*09qoCv?ESF!i{HN%b}ar_>G4*_laH63h&fSm;?Bt~C$F69dFs~b z_|vD(^gdH|=0;UgWY{7*zl4kAH?QIR4l!2bv2IQI)oEB4eq-7_=CPLk0p$5Gv>l7+ zb_nq5=pPV^j)6VG!y=wUc$jBfm^Y$hQi?p9`=qoC^Y>{KdNjaqp*?LNvG+pn#t$^|@elW3>(ksL{9s^c)m*;-wDddFEWkV7+qZ?g=YX@* z{d}Kk?ENl3&iPg4zkh}2L^IROf7dPvVL!b*{e$te&8tTgBEp*TtKV=Qz1ZRCm>%r; zkI`WfFU|&KL{rZA{{!Pb`oRDI delta 6032 zcmXZg2V7NkAII_E7cYXKNQk0|h#+W2YGyVZnG)hwT)2?!Vu@%@t|HQKdt8|V_bwku z0V4;Jnw6E6l`S(L(|b>5YUWCx@4w&U>-D~$d*pJt=bZofbN&<_^14;-wY05sN^HN~ zBDEffgbWy)F+F{C|HJo1qT&V1633+_rN%EWjuVj!)L|U|!((WTHhf-5&BtZtO&j^_ zqst<&MZEnpvQv`kcG~;6NXtGV4xh}VX*o5T;B|hzAzvSwm6Vxg-LL2`_@JEscPA%% za-F>XS)bJt9N8}tT)nI^ypBV_*HY(YS9>nApN69}1YQnnv}bK=QO%a6qdgBgnr!{| zMdH{XHU51T#rlYNZWV_k*J|q%gj_4u=LIJ}iqt?bX5m#ihje^|L2?=|VwViVU97-H zY>?g35u;_j+(b)>!AV&p4RK2j$VN;?Z@i3d_(^OzCO_e89F#5i5bw%;RAN64qZ&t1 zA-~`Ns>DM)rKb2wZK)%EQdj)tvE{zIoX4eFK9m#kv3w$*$$9x+ewJV5H@PBi`CYEb zA97v(mOFA+?#X?5ApZbpgeC|4@I57uvQGj_^fQ49u#aIFh zZ(%Lo#(HeR7Hr23?8I*D!GBPKQoM&UIe>B;!Us5lV>phF@d?i1Tb#!a_z^$j0)D|o z{EFXj6)w1OjUUwc2mZuO+{1l5kO(~V@bIW1C&tQV@e*&TB|Zp59XuvY#DzA9lCIKN9+#o=m3)n*G6buzO>W_Ne24F)FE&dR z{*n$Dj4pT%QzQ+Ik&msiT2|m4tdsBL8~GN0zFTN@EVjA`;v9?4leEj!=?f7FxFGD?!9HI~adNyJJSBu`0{^hZ~G zBP}FO#>*xtl`}{|4&IdM*oU5SLmaXeGf|9Z<)nNhr+9TO!{_)%KE(}Q#WkgLo1ZtF zdwCu5pGO@LSEAw-E3PDkU^ZD{7Mr4Y)ru=s$vHMnA)Wm{e8lHt6|Zh_jZ<=(9j|)f zMLwCJc=3yCqLN{3hQeJoQ(*<0rLd9BR@h)o^$(92&8^9b6Nk9+l-y*eD73T=`v>8q zbRAOE;U*T)ENO7VP z*K10)u$+UP<^dma<4whROk4{Tryg-FRN&<9TB5*t*|k*RFuP2lnq97Nl(iHuV{w^- zNAe51QsDq+M%OBC5UNTe1138-HHym|bmAl_u6v5prMS%JAx@m)dZ0Lkit8W6$y8hq z6{l5kJyM)tIj<^Cwc>{2q$_R@#pzevHF!YG17~7!I~3<+aeFHBF7BF&^R&3V6lZL4 zdn?Y};;yAQi;LS=aXuG!ZN-^g+#L1>=Xi1ZDbDszao6PrnWOLaSNs(q?#GmHZ`}b3 zW>4xVG-3l4ny~d1Lf8fhp=^*sILlwb2AZ?x`x|lmA%ai%OVU6j+gPC$8?5joYkml! zEo*)Vp&i>);ThKaK*9@bGlh<9m_jGkd=Nrs)@eQo;U(655JEI-J_z9zwuM40+ftzy z`-DPY)_hn(Kh}I$;;%$;M=2>{pHdjXwpLDzJ19(GpHrB~KCh6$zF^Kj4;3=G@uET&YYrJ9n>B}wFo`vXjF8KAQOIM> z(IZS{yDCg)UsjmGc2oS#FYfM2Ry+B`9BzVnZVoqL4jZE|m+hfYz?#!Qn8)^1SiqW7 zLRiT5R#?RLQCQ5HQ$<+9np5TE11vrnpzszuP+=__r|>pANMSuYSYZ=8L}3eS&N*Q_ z%Zc5<4mMt4Cp%nWx4Hh!U?A*a%~&A(hc#n?P{NK@C}qvKAiT$pQM@Sw z7~u!jj4#5EtQlW~pV@44&N;|%fz46)g*AhXaFI2GjqocwS>ZR<3_QYBc8Y?FHG`1g zW?xmf#!gfJUiW5j5^oLpWQM|@>`aB5>@0}-YmY`)^nBnOa^2-XZ*!b5hh^6+2_ z6mL4kJx_@_HHBR8m`5k)vC!LR9N|ZY;T=y5?;sb>l%eCmPbe%XG3nS zS1==egMxXFjS45&V#T{xac@#$PR(Wo^R8PIR=5(7n_zL9 zkxgRG?=FSw)|rr?qz>FN-%7km7WY0Sm)Lg{o?zcq=)vw+yqOlaxg1E^u;y|g@VauF z%Yo3}ifP)$d5T-*$}9!l6$+PGGx!PSC%&(EKQ8V{C10@Sq9E{uxDPA5#vW0;Zx{Db zC3Dzg3Xj+-k#zIm0d5>u9{j}aYK1fGhYFvuClt&NKB;h)H9JB0lKn{GEB3U)r`Cwj zaKu^pp+TMR@c%wnTr!B;ycfw|ta&ejgEj9(FvHKh7s10i8``naZEl@cTwsX%dnI35 zk1KAxfTiCS#vECf>?8M2%fBY z9q}ez+}9LpvwtYmVy`RsvVSW0u{X>?=L70;rX4LQ^jtl zuss!gShJr5GZ=a+)U(nfJ7T%DIWin8trL+Q>vZK;hAVty`L*aVmgFpJm5xYV#{nef9l?#dtCSY?sjxobbR#U z=m)P9ymBF?dCZC)c|9(~_KB_TIj2`}ucN)AdMEb2*r!#Wy?ulG?(7%c?_B@b{^tk8 z45%8|X<%`jM_lS4k3mxh9UMG#@K;0Pha4UnIW%r)^)MNhG0cwd5`SrU;PAxZl_LU1 z6pgq%vfIeKk?)OsFlxf6O6TYe3El}^6DA}a9n*Tuh%u)V{SyZ#u1czzRFrfrIW{>r zd2RBUl&F;Sl>I3eQ~gt;Q}a?cq+Ur2PK!=koK}@)rw67l9ve7z&DiVXJ{cc2e(Lyd zCxlM8F)?al`oyY?E*a?=m6<-737Lm7?`3t!8kJR$btvmrc5L?NIl(zIat`J=U6Vp5 zrA{i(^~){Dy)Zdr^1(buUSi&jDT!0gP4%9dK6U%6MX%nPmOHI_dh_Y0X7rnJcxL3x z5i>W;bj=z!t9o|G?5x=(vmfMl$)A;9<$T<^&UxDTa8B1b_vR+g-CxkI;Nra8LZ8A; zg)<6E=I6{WFA6MLQ*@^2@@rjRAMpCcHv-=%cD@<<=86T51^EkpSor+H6$>vcdVW#m z;=(14B{@sZFAZKgVd=$XU6!p~c5C^l%xlY6(uV}Ru-(RT$Q)#>}sFY6IQQU z6Z}@m+M#cEczfNthU@06JH0+|{owTnH^gkH+!$T#SKO_5>ZXKE#hcD?Qz?yb~tt{-|4?|&Cc7qhVI(AyWQ?pyYKAjx@YYk*WNk% z9Q$(joqEUropJBD-c5M-#{L;4GfJ+#xAwh<2f_|4K5+S9>w}ldLd)XI%FC{m_bYc* z#8u=}EUegBaiZeNq3A=!?+3iUqB5p(-3L*J8yqe_()OqvO+V^77JF=WRX|u(eAR{H z&5x%ae^}k8`f&B_4~tL8iSm;HCl{acK9zoIzes6DRM`A*dC1#3e+iTJ@}kIdfekwH zFLX~LtTq2Q*}lU|Xx9{0$lb=H!owmvBglNYMeC;Zr+E6;im%r+pq6*8uXlIvz`E1@ z>eu)4^7Zwa?&Z_Tx9($}ju*NOY!KpIE1+q;1wQ_c7GBZbKDl)R1MBYd3w+GSYoB@T txVpZbIypSMm9{NeRC8WLMOj1z+IqoJHuNaG9c78tI9IEz%NbPr{STBP;e-GH diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index 233ea2a4dfd..869b78e4eca 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -639,4 +639,5 @@ export const codiconsLibrary = { searchLarge: register('search-large', 0xec70), terminalGitBash: register('terminal-git-bash', 0xec71), windowActive: register('window-active', 0xec72), + forward: register('forward', 0xec73), } as const; From 0e86ec3cb238c11fb5b059f7343053d91806d804 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Nov 2025 11:25:50 +0100 Subject: [PATCH 15/34] use the possible matching scopes for default account (#278286) * use the possible matching scopes for default account * update distro --- package.json | 4 ++-- src/vs/base/common/product.ts | 2 +- .../accounts/common/defaultAccount.ts | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 932b3cd5697..c273e1f685b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.107.20251119", - "distro": "3ee33b7862b5e018538b730ae631f35747f57a2c", + "distro": "70b452e51d85528e38164c11d783791ead374f43", "author": { "name": "Microsoft Corporation" }, @@ -240,4 +240,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 9529eb95910..f21630823a0 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -213,7 +213,7 @@ export interface IProductConfiguration { readonly enterpriseProviderId: string; readonly enterpriseProviderConfig: string; readonly enterpriseProviderUriSetting: string; - readonly scopes: string[]; + readonly scopes: string[][]; }; readonly tokenEntitlementUrl: string; readonly chatEntitlementUrl: string; diff --git a/src/vs/workbench/services/accounts/common/defaultAccount.ts b/src/vs/workbench/services/accounts/common/defaultAccount.ts index 91f17367c07..d5c6f16f5c1 100644 --- a/src/vs/workbench/services/accounts/common/defaultAccount.ts +++ b/src/vs/workbench/services/accounts/common/defaultAccount.ts @@ -163,7 +163,7 @@ export class DefaultAccountManagementContribution extends Disposable implements return; } - this.registerSignInAction(defaultAccountProviderId, this.productService.defaultAccount.authenticationProvider.scopes); + this.registerSignInAction(defaultAccountProviderId, this.productService.defaultAccount.authenticationProvider.scopes[0]); this.setDefaultAccount(await this.getDefaultAccountFromAuthenticatedSessions(defaultAccountProviderId, this.productService.defaultAccount.authenticationProvider.scopes)); this._register(this.authenticationService.onDidChangeSessions(async e => { @@ -205,11 +205,10 @@ export class DefaultAccountManagementContribution extends Disposable implements return result; } - private async getDefaultAccountFromAuthenticatedSessions(authProviderId: string, scopes: string[]): Promise { + private async getDefaultAccountFromAuthenticatedSessions(authProviderId: string, scopes: string[][]): Promise { try { this.logService.debug('[DefaultAccount] Getting Default Account from authenticated sessions for provider:', authProviderId); - const sessions = await this.authenticationService.getSessions(authProviderId, undefined, undefined, true); - const session = sessions.find(s => this.scopesMatch(s.scopes, scopes)); + const session = await this.findMatchingProviderSession(authProviderId, scopes); if (!session) { this.logService.debug('[DefaultAccount] No matching session found for provider:', authProviderId); @@ -239,6 +238,18 @@ export class DefaultAccountManagementContribution extends Disposable implements } } + private async findMatchingProviderSession(authProviderId: string, allScopes: string[][]): Promise { + const sessions = await this.authenticationService.getSessions(authProviderId, undefined, undefined, true); + for (const session of sessions) { + for (const scopes of allScopes) { + if (this.scopesMatch(session.scopes, scopes)) { + return session; + } + } + } + return undefined; + } + private scopesMatch(scopes: ReadonlyArray, expectedScopes: string[]): boolean { return scopes.length === expectedScopes.length && expectedScopes.every(scope => scopes.includes(scope)); } From d20f143de3ce6c6996aa1a894614d7f8823b6cdb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:42:39 +0000 Subject: [PATCH 16/34] Initial plan From a85314a4fbfa2a962419bfbff193ec825d5d158b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 19 Nov 2025 11:43:39 +0100 Subject: [PATCH 17/34] disable intent detection for editor inline chat (#278305) https://github.com/microsoft/vscode/issues/278057 --- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index feb807fed99..181608b00ff 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -937,6 +937,7 @@ export class ChatService extends Disposable implements IChatService { !commandPart && !agentSlashCommandPart && enableCommandDetection && + location !== ChatAgentLocation.EditorInline && options?.modeInfo?.kind !== ChatModeKind.Agent && options?.modeInfo?.kind !== ChatModeKind.Edit && !options?.agentIdSilent From 99afdff7f500ebb96e3ccac036938757e8d08282 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:54:42 +0000 Subject: [PATCH 18/34] Update inline chat dialog text per @ntrogh's suggestions Co-authored-by: jrieken <1794099+jrieken@users.noreply.github.com> --- .../inlineChat/browser/inlineChatSessionServiceImpl.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 3d69c2043b7..43dc08a7a52 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -543,15 +543,15 @@ export class InlineChatEscapeToolContribution extends Disposable { let result: { confirmed: boolean; checkboxChecked?: boolean }; if (dontAskAgain !== undefined) { - // Use previously stored user preference: true = 'Continue in Chat', false = 'Rephrase' (Cancel) + // Use previously stored user preference: true = 'Continue in Chat view', false = 'Rephrase' (Cancel) result = { confirmed: dontAskAgain, checkboxChecked: false }; } else { result = await dialogService.confirm({ type: 'question', - title: localize('confirm.title', "Continue in Panel Chat?"), - message: localize('confirm', "Do you want to continue in panel chat or rephrase your prompt?"), - detail: localize('confirm.detail', "Inline Chat is designed for single file code changes. This task is either too complex or requires a text response. You can rephrase your prompt or continue in panel chat."), - primaryButton: localize('confirm.yes', "Continue in Chat"), + title: localize('confirm.title', "Do you want to continue in Chat view?"), + message: localize('confirm', "Do you want to continue in Chat view?"), + detail: localize('confirm.detail', "Inline chat is designed for making single-file code changes. Continue your request in the Chat view or rephrase it for inline chat."), + primaryButton: localize('confirm.yes', "Continue in Chat view"), cancelButton: localize('confirm.cancel', "Cancel"), checkbox: { label: localize('chat.remove.confirmation.checkbox', "Don't ask again"), checked: false }, }); From 1910826164135c7abf82e1b03ab13ed40c55c4e9 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 19 Nov 2025 10:56:22 +0000 Subject: [PATCH 19/34] style: adjust padding for select box dropdown and remove border radius styles --- .../base/browser/ui/selectBox/selectBoxCustom.css | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css index dcb66d31c5a..4d2fb516f20 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -42,21 +42,12 @@ } .monaco-select-box-dropdown-container > .select-box-details-pane { - padding: 5px; + padding: 5px 6px; } .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row { cursor: pointer; -} - -.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:first-child { - border-top-left-radius: 5px; - border-top-right-radius: 5px; -} - -.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:last-child { - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; + padding-left: 2px; } .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-text { From 614b50dc15a6ec4dd1cf79e2c4a5343aae13239a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Nov 2025 12:51:09 +0100 Subject: [PATCH 20/34] agent sessions - adopt new icon to forward (#278330) --- .../browser/actions/chatContinueInAction.ts | 2 +- .../agentSessions/agentSessionViewModel.ts | 38 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts index 09bbab020b0..4b02e2e1f00 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts @@ -130,7 +130,7 @@ export class ChatContinueInSessionActionItem extends ActionWidgetDropdownActionV } protected override renderLabel(element: HTMLElement): IDisposable | null { - const icon = this.contextKeyService.contextMatchesRules(ChatContextKeys.remoteJobCreating) ? Codicon.sync : Codicon.indent; + const icon = this.contextKeyService.contextMatchesRules(ChatContextKeys.remoteJobCreating) ? Codicon.sync : Codicon.forward; element.classList.add(...ThemeIcon.asClassNameArray(icon)); return super.renderLabel(element); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index 6bc1ea8599f..33531fda4c9 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -360,28 +360,32 @@ class AgentSessionsCache { return []; } - const cached = JSON.parse(sessionsCache) as ISerializedAgentSessionViewModel[]; - return cached.map(session => ({ - providerType: session.providerType, - providerLabel: session.providerLabel, + try { + const cached = JSON.parse(sessionsCache) as ISerializedAgentSessionViewModel[]; + return cached.map(session => ({ + providerType: session.providerType, + providerLabel: session.providerLabel, - resource: URI.revive(session.resource), + resource: URI.revive(session.resource), - icon: ThemeIcon.fromId(session.icon), - label: session.label, - description: session.description, - tooltip: session.tooltip, + icon: ThemeIcon.fromId(session.icon), + label: session.label, + description: session.description, + tooltip: session.tooltip, - status: session.status, - archived: session.archived, + status: session.status, + archived: session.archived, - timing: { - startTime: session.timing.startTime, - endTime: session.timing.endTime, - }, + timing: { + startTime: session.timing.startTime, + endTime: session.timing.endTime, + }, - statistics: session.statistics, - })); + statistics: session.statistics, + })); + } catch { + return []; // invalid data in storage, fallback to empty sessions list + } } } From 8a34fcfb95ba58a65f728705c9b11fe2ca75ee9c Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 19 Nov 2025 21:38:09 +0900 Subject: [PATCH 21/34] chore: disallow crashpad forwarding crashes to system crash handler (#278334) --- src/main.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index e19dde49541..ec188d02721 100644 --- a/src/main.ts +++ b/src/main.ts @@ -528,7 +528,8 @@ function configureCrashReporter(): void { productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, submitURL, uploadToServer, - compress: true + compress: true, + ignoreSystemCrashHandler: true }); } From db6ff2b3b90c083b435de57064c870f0bbc9eed4 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:44:18 +0100 Subject: [PATCH 22/34] Chat context provider API changes (#278135) * Chat context provider API changes Part of #271104 * Implement workspace chat context Part of #271104 * Fix some tests * actually fix the test --- .../api/browser/mainThreadChatContext.ts | 12 +++- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 3 +- .../api/common/extHostChatContext.ts | 71 ++++++++++++++----- .../chatAttachmentsContentPart.ts | 5 +- .../chat/browser/chatContextService.ts | 32 +++++++-- .../contrib/chat/browser/chatInputPart.ts | 4 +- .../chat/common/chatVariableEntries.ts | 13 +++- .../test/browser/inlineChatController.test.ts | 3 + .../vscode.proposed.chatContextProvider.d.ts | 52 +++++++++++--- 10 files changed, 161 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatContext.ts b/src/vs/workbench/api/browser/mainThreadChatContext.ts index 38cab5806a0..c17babbcc46 100644 --- a/src/vs/workbench/api/browser/mainThreadChatContext.ts +++ b/src/vs/workbench/api/browser/mainThreadChatContext.ts @@ -15,7 +15,7 @@ import { URI } from '../../../base/common/uri.js'; @extHostNamedCustomer(MainContext.MainThreadChatContext) export class MainThreadChatContext extends Disposable implements MainThreadChatContextShape { private readonly _proxy: ExtHostChatContextShape; - private readonly _providers = new Map(); + private readonly _providers = new Map(); constructor( extHostContext: IExtHostContext, @@ -25,7 +25,7 @@ export class MainThreadChatContext extends Disposable implements MainThreadChatC this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatContext); } - $registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[], _options: { icon: ThemeIcon }, support: IChatContextSupport): void { + $registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[] | undefined, _options: { icon: ThemeIcon }, support: IChatContextSupport): void { this._providers.set(handle, { selector, support, id }); this._chatContextService.registerChatContextProvider(id, selector, { provideChatContext: (token: CancellationToken) => { @@ -48,4 +48,12 @@ export class MainThreadChatContext extends Disposable implements MainThreadChatC this._chatContextService.unregisterChatContextProvider(provider.id); this._providers.delete(handle); } + + $updateWorkspaceContextItems(handle: number, items: IChatContextItem[]): void { + const provider = this._providers.get(handle); + if (!provider) { + return; + } + this._chatContextService.updateWorkspaceContextItems(provider.id, items); + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index da16fe2d2e4..a976c20189f 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1537,9 +1537,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatOutputRenderer'); return extHostChatOutputRenderer.registerChatOutputRenderer(extension, viewType, renderer); }, - registerChatContextProvider(selector: vscode.DocumentSelector, id: string, provider: vscode.ChatContextProvider): vscode.Disposable { + registerChatContextProvider(selector: vscode.DocumentSelector | undefined, id: string, provider: vscode.ChatContextProvider): vscode.Disposable { checkProposedApiEnabled(extension, 'chatContextProvider'); - return extHostChatContext.registerChatContextProvider(checkSelector(selector), `${extension.id}-${id}`, provider); + return extHostChatContext.registerChatContextProvider(selector ? checkSelector(selector) : undefined, `${extension.id}-${id}`, provider); }, }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5b8ca6ea90f..ea42783cd42 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1329,8 +1329,9 @@ export interface ExtHostChatContextShape { } export interface MainThreadChatContextShape extends IDisposable { - $registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[], options: {}, support: IChatContextSupport): void; + $registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[] | undefined, options: {}, support: IChatContextSupport): void; $unregisterChatContextProvider(handle: number): void; + $updateWorkspaceContextItems(handle: number, items: IChatContextItem[]): void; } export interface MainThreadEmbeddingsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostChatContext.ts b/src/vs/workbench/api/common/extHostChatContext.ts index 8ad7bbd595e..e2ee3c42e36 100644 --- a/src/vs/workbench/api/common/extHostChatContext.ts +++ b/src/vs/workbench/api/common/extHostChatContext.ts @@ -10,18 +10,20 @@ import { ExtHostChatContextShape, MainContext, MainThreadChatContextShape } from import { DocumentSelector } from './extHostTypeConverters.js'; import { IExtHostRpcService } from './extHostRpcService.js'; import { IChatContextItem } from '../../contrib/chat/common/chatContext.js'; +import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; -export class ExtHostChatContext implements ExtHostChatContextShape { +export class ExtHostChatContext extends Disposable implements ExtHostChatContextShape { declare _serviceBrand: undefined; private _proxy: MainThreadChatContextShape; private _handlePool: number = 0; - private _providers: Map = new Map(); + private _providers: Map = new Map(); private _itemPool: number = 0; private _items: Map> = new Map(); // handle -> itemHandle -> item constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService, ) { + super(); this._proxy = extHostRpc.getProxy(MainContext.MainThreadChatContext); } @@ -83,16 +85,7 @@ export class ExtHostChatContext implements ExtHostChatContextShape { return item; } - async $resolveChatContext(handle: number, context: IChatContextItem, token: CancellationToken): Promise { - const provider = this._getProvider(handle); - - if (!provider.resolveChatContext) { - throw new Error('resolveChatContext not implemented'); - } - const extItem = this._items.get(handle)?.get(context.handle); - if (!extItem) { - throw new Error('Chat context item not found'); - } + private async _doResolve(provider: vscode.ChatContextProvider, context: IChatContextItem, extItem: vscode.ChatContextItem, token: CancellationToken): Promise { const extResult = await provider.resolveChatContext(extItem, token); const result = extResult ?? context; return { @@ -104,23 +97,69 @@ export class ExtHostChatContext implements ExtHostChatContextShape { }; } - registerChatContextProvider(selector: vscode.DocumentSelector, id: string, provider: vscode.ChatContextProvider): vscode.Disposable { + async $resolveChatContext(handle: number, context: IChatContextItem, token: CancellationToken): Promise { + const provider = this._getProvider(handle); + + if (!provider.resolveChatContext) { + throw new Error('resolveChatContext not implemented'); + } + const extItem = this._items.get(handle)?.get(context.handle); + if (!extItem) { + throw new Error('Chat context item not found'); + } + return this._doResolve(provider, context, extItem, token); + } + + registerChatContextProvider(selector: vscode.DocumentSelector | undefined, id: string, provider: vscode.ChatContextProvider): vscode.Disposable { const handle = this._handlePool++; - this._providers.set(handle, provider); - this._proxy.$registerChatContextProvider(handle, `${id}`, DocumentSelector.from(selector), {}, { supportsResource: !!provider.provideChatContextForResource, supportsResolve: !!provider.resolveChatContext }); + const disposables = new DisposableStore(); + this._listenForWorkspaceContextChanges(handle, provider, disposables); + this._providers.set(handle, { provider, disposables }); + this._proxy.$registerChatContextProvider(handle, `${id}`, selector ? DocumentSelector.from(selector) : undefined, {}, { supportsResource: !!provider.provideChatContextForResource, supportsResolve: !!provider.resolveChatContext }); return { dispose: () => { this._providers.delete(handle); this._proxy.$unregisterChatContextProvider(handle); + disposables.dispose(); } }; } + private _listenForWorkspaceContextChanges(handle: number, provider: vscode.ChatContextProvider, disposables: DisposableStore): void { + if (!provider.onDidChangeWorkspaceChatContext || !provider.provideWorkspaceChatContext) { + return; + } + disposables.add(provider.onDidChangeWorkspaceChatContext(async () => { + const workspaceContexts = await provider.provideWorkspaceChatContext!(CancellationToken.None); + const resolvedContexts: IChatContextItem[] = []; + for (const item of workspaceContexts ?? []) { + const contextItem: IChatContextItem = { + icon: item.icon, + label: item.label, + modelDescription: item.modelDescription, + value: item.value, + handle: this._itemPool++ + }; + const resolved = await this._doResolve(provider, contextItem, item, CancellationToken.None); + resolvedContexts.push(resolved); + } + + this._proxy.$updateWorkspaceContextItems(handle, resolvedContexts); + })); + } + private _getProvider(handle: number): vscode.ChatContextProvider { if (!this._providers.has(handle)) { throw new Error('Chat context provider not found'); } - return this._providers.get(handle)!; + return this._providers.get(handle)!.provider; + } + + public override dispose(): void { + super.dispose(); + for (const { disposables } of this._providers.values()) { + disposables.dispose(); + } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index b7972a34222..56f5b0c247f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -11,7 +11,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ResourceLabels } from '../../../../browser/labels.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isTerminalVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; +import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isTerminalVariableEntry, isWorkspaceVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; @@ -153,6 +153,9 @@ export class ChatAttachmentsContentPart extends Disposable { widget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isSCMHistoryItemChangeRangeVariableEntry(attachment)) { widget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); + } else if (isWorkspaceVariableEntry(attachment)) { + // skip workspace attachments + return; } else { widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContextService.ts b/src/vs/workbench/contrib/chat/browser/chatContextService.ts index 7222696e1d8..0d858bf3180 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContextService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContextService.ts @@ -9,7 +9,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IChatContextPicker, IChatContextPickerItem, IChatContextPickService } from './chatContextPickService.js'; import { IChatContextItem, IChatContextProvider } from '../common/chatContext.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { IGenericChatRequestVariableEntry, StringChatContextValue } from '../common/chatVariableEntries.js'; +import { IChatRequestWorkspaceVariableEntry, IGenericChatRequestVariableEntry, StringChatContextValue } from '../common/chatVariableEntries.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { Disposable, DisposableMap, IDisposable } from '../../../../base/common/lifecycle.js'; @@ -22,7 +22,7 @@ export interface IChatContextService extends ChatContextService { } interface IChatContextProviderEntry { picker?: { title: string; icon: ThemeIcon }; chatContextProvider?: { - selector: LanguageSelector; + selector: LanguageSelector | undefined; provider: IChatContextProvider; }; } @@ -31,6 +31,7 @@ export class ChatContextService extends Disposable { _serviceBrand: undefined; private readonly _providers = new Map(); + private readonly _workspaceContext = new Map(); private readonly _registeredPickers = this._register(new DisposableMap()); private _lastResourceContext: Map = new Map(); @@ -56,7 +57,7 @@ export class ChatContextService extends Disposable { this._registeredPickers.set(id, this._contextPickService.registerChatContextItem(this._asPicker(providerEntry.picker.title, providerEntry.picker.icon, id))); } - registerChatContextProvider(id: string, selector: LanguageSelector, provider: IChatContextProvider): void { + registerChatContextProvider(id: string, selector: LanguageSelector | undefined, provider: IChatContextProvider): void { const providerEntry = this._providers.get(id) ?? { picker: undefined }; providerEntry.chatContextProvider = { selector, provider }; this._providers.set(id, providerEntry); @@ -68,6 +69,29 @@ export class ChatContextService extends Disposable { this._registeredPickers.deleteAndDispose(id); } + updateWorkspaceContextItems(id: string, items: IChatContextItem[]): void { + this._workspaceContext.set(id, items); + } + + getWorkspaceContextItems(): IChatRequestWorkspaceVariableEntry[] { + const items: IChatRequestWorkspaceVariableEntry[] = []; + for (const workspaceContexts of this._workspaceContext.values()) { + for (const item of workspaceContexts) { + if (!item.value) { + continue; + } + items.push({ + value: item.value, + name: item.label, + modelDescription: item.modelDescription, + id: item.label, + kind: 'workspace' + }); + } + } + return items; + } + async contextForResource(uri: URI): Promise { return this._contextForResource(uri, false); } @@ -75,7 +99,7 @@ export class ChatContextService extends Disposable { private async _contextForResource(uri: URI, withValue: boolean): Promise { const scoredProviders: Array<{ score: number; provider: IChatContextProvider }> = []; for (const providerEntry of this._providers.values()) { - if (!providerEntry.chatContextProvider?.provider.provideChatContextForResource) { + if (!providerEntry.chatContextProvider?.provider.provideChatContextForResource || (providerEntry.chatContextProvider.selector === undefined)) { continue; } const matchScore = score(providerEntry.chatContextProvider.selector, uri, '', true, undefined, undefined); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 5f7211875e5..2724953d42b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -105,6 +105,7 @@ import { ChatRelatedFiles } from './contrib/chatInputRelatedFilesContrib.js'; import { resizeImage } from './imageUtils.js'; import { IModelPickerDelegate, ModelPickerActionItem } from './modelPicker/modelPickerActionItem.js'; import { IModePickerDelegate, ModePickerActionItem } from './modelPicker/modePickerActionItem.js'; +import { IChatContextService } from './chatContextService.js'; const $ = dom.$; @@ -179,7 +180,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge public getAttachedContext(sessionResource: URI) { const contextArr = new ChatRequestVariableSet(); - contextArr.add(...this.attachmentModel.attachments); + contextArr.add(...this.attachmentModel.attachments, ...this.chatContextService.getWorkspaceContextItems()); return contextArr; } @@ -411,6 +412,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @ILanguageModelToolsService private readonly toolService: ILanguageModelToolsService, @IChatService private readonly chatService: IChatService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + @IChatContextService private readonly chatContextService: IChatContextService, ) { super(); this._contextResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event })); diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index 95170b9f6d7..68f198d1158 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -90,6 +90,13 @@ export interface IChatRequestStringVariableEntry extends IBaseChatRequestVariabl readonly uri: URI; } +export interface IChatRequestWorkspaceVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'workspace'; + readonly value: string; + readonly modelDescription?: string; +} + + export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry { readonly kind: 'paste'; readonly code: string; @@ -260,7 +267,7 @@ export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChat | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry | IPromptFileVariableEntry | IPromptTextVariableEntry | ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry | ISCMHistoryItemChangeRangeVariableEntry | ITerminalVariableEntry - | IChatRequestStringVariableEntry; + | IChatRequestStringVariableEntry | IChatRequestWorkspaceVariableEntry; export namespace IChatRequestVariableEntry { @@ -293,6 +300,10 @@ export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is ICh return obj.kind === 'paste'; } +export function isWorkspaceVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestWorkspaceVariableEntry { + return obj.kind === 'workspace'; +} + export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry { return obj.kind === 'image'; } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 85dc306faf5..af92bb099a8 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -85,6 +85,7 @@ import { CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatConfigKeys, InlineChatResponse import { TestWorkerService } from './testWorkerService.js'; import { URI } from '../../../../../base/common/uri.js'; import { ChatWidgetService } from '../../../chat/browser/chatWidgetService.js'; +import { ChatContextService, IChatContextService } from '../../../chat/browser/chatContextService.js'; suite('InlineChatController', function () { @@ -256,6 +257,8 @@ suite('InlineChatController', function () { model.setEOL(EndOfLineSequence.LF); editor = store.add(instantiateTestCodeEditor(instaService, model)); + instaService.set(IChatContextService, store.add(instaService.createInstance(ChatContextService))); + store.add(chatAgentService.registerDynamicAgent({ id: 'testEditorAgent', ...agentData, }, { async invoke(request, progress, history, token) { progress([{ diff --git a/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts b/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts index 38ea26573a3..173c5dd11f8 100644 --- a/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts @@ -10,28 +10,61 @@ declare module 'vscode' { export namespace chat { - // TODO@alexr00 API: - // selector is confusing - export function registerChatContextProvider(selector: DocumentSelector, id: string, provider: ChatContextProvider): Disposable; + /** + * Register a chat context provider. Chat context can be provided: + * - For a resource. Make sure to pass a selector that matches the resource you want to provide context for. + * Providers registered without a selector will not be called for resource-based context. + * - Explicitly. These context items are shown as options when the user explicitly attaches context. + * + * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. + * + * @param selector Optional document selector to filter which resources the provider is called for. If omitted, the provider will only be called for explicit context requests. + * @param id Unique identifier for the provider. + * @param provider The chat context provider. + */ + export function registerChatContextProvider(selector: DocumentSelector | undefined, id: string, provider: ChatContextProvider): Disposable; } export interface ChatContextItem { + /** + * Icon for the context item. + */ icon: ThemeIcon; + /** + * Human readable label for the context item. + */ label: string; + /** + * An optional description of the context item, e.g. to describe the item to the language model. + */ modelDescription?: string; + /** + * The value of the context item. Can be omitted when returned from one of the `provide` methods if the provider supports `resolveChatContext`. + */ value?: string; } export interface ChatContextProvider { + /** + * An optional event that should be fired when the workspace chat context has changed. + */ + onDidChangeWorkspaceChatContext?: Event; + + /** + * Provide a list of chat context items to be included as workspace context for all chat sessions. + * + * @param token A cancellation token. + */ + provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; + /** * Provide a list of chat context items that a user can choose from. These context items are shown as options when the user explicitly attaches context. * Chat context items can be provided without a `value`, as the `value` can be resolved later using `resolveChatContext`. * `resolveChatContext` is only called for items that do not have a `value`. * - * @param options - * @param token + * @param token A cancellation token. */ provideChatContextExplicit?(token: CancellationToken): ProviderResult; @@ -40,17 +73,16 @@ declare module 'vscode' { * Chat context items can be provided without a `value`, as the `value` can be resolved later using `resolveChatContext`. * `resolveChatContext` is only called for items that do not have a `value`. * - * @param resource - * @param options - * @param token + * @param options Options include the resource for which to provide context. + * @param token A cancellation token. */ provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; /** * If a chat context item is provided without a `value`, from either of the `provide` methods, this method is called to resolve the `value` for the item. * - * @param context - * @param token + * @param context The context item to resolve. + * @param token A cancellation token. */ resolveChatContext(context: T, token: CancellationToken): ProviderResult; } From b1975ec3582252503e58265bdeced9969a598cb6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Nov 2025 14:19:26 +0100 Subject: [PATCH 23/34] agent sessions - allow to open diff from local changes (#278348) --- .../browser/agentSessions/agentSessionsActions.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts index e4df4d24272..95c1e742e25 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts @@ -8,15 +8,17 @@ import { localize, localize2 } from '../../../../../nls.js'; import { IAgentSessionViewModel } from './agentSessionViewModel.js'; import { Action, IAction } from '../../../../../base/common/actions.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js'; -import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js'; import { EventHelper, h, hide, show } from '../../../../../base/browser/dom.js'; import { assertReturnsDefined } from '../../../../../base/common/types.js'; import { ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; import { ViewAction } from '../../../../browser/parts/views/viewPane.js'; -import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js'; +import { AGENT_SESSIONS_VIEW_ID, AgentSessionProviders } from './agentSessions.js'; import { AgentSessionsView } from './agentSessionsView.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { IChatService } from '../../common/chatService.js'; //#region Diff Statistics Action @@ -107,6 +109,13 @@ export class AgentSessionDiffActionViewItem extends ActionViewItem { } } +CommandsRegistry.registerCommand(`agentSession.${AgentSessionProviders.Local}.openChanges`, async (accessor: ServicesAccessor, resource: URI) => { + const chatService = accessor.get(IChatService); + + const session = chatService.getSession(resource); + session?.editingSession?.show(); +}); + //#endregion //#region View Actions From 911a41f96ef36497da3d50925b3b7026061d3056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 19 Nov 2025 15:08:11 +0100 Subject: [PATCH 24/34] Revert "Insider builds should have an auto increasing version number" (#278356) Revert "Insider builds should have an auto increasing version number (#277497)" This reverts commit 222fb55dd51bc729e942969c57f771dabb7c0ee9. --- .../alpine/product-build-alpine-cli.yml | 3 --- .../alpine/product-build-alpine.yml | 5 ---- .../common/bump-insiders-version.yml | 23 ------------------- .../darwin/product-build-darwin-ci.yml | 3 --- .../darwin/product-build-darwin-cli.yml | 3 --- .../darwin/product-build-darwin-universal.yml | 7 ------ .../darwin/product-build-darwin.yml | 3 --- .../steps/product-build-darwin-compile.yml | 5 ---- .../linux/product-build-linux-cli.yml | 3 --- .../steps/product-build-linux-compile.yml | 3 --- build/azure-pipelines/product-build.yml | 13 ----------- build/azure-pipelines/product-compile.yml | 7 ------ build/azure-pipelines/product-publish.yml | 3 --- .../azure-pipelines/web/product-build-web.yml | 7 ------ .../win32/product-build-win32-cli.yml | 3 --- .../steps/product-build-win32-compile.yml | 3 --- build/gulpfile.vscode.mjs | 4 +--- build/gulpfile.vscode.win32.mjs | 9 +------- 18 files changed, 2 insertions(+), 105 deletions(-) delete mode 100644 build/azure-pipelines/common/bump-insiders-version.yml diff --git a/build/azure-pipelines/alpine/product-build-alpine-cli.yml b/build/azure-pipelines/alpine/product-build-alpine-cli.yml index 8b3920b5237..9f3f60a6b24 100644 --- a/build/azure-pipelines/alpine/product-build-alpine-cli.yml +++ b/build/azure-pipelines/alpine/product-build-alpine-cli.yml @@ -34,9 +34,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../common/bump-insiders-version.yml@self - - template: ../cli/cli-apply-patches.yml@self - script: | diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index ddf226b4306..c6d5ba27eda 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -1,6 +1,4 @@ parameters: - - name: VSCODE_QUALITY - type: string - name: VSCODE_ARCH type: string @@ -57,9 +55,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../common/bump-insiders-version.yml@self - - template: ../distro/download-distro.yml@self - task: AzureKeyVault@2 diff --git a/build/azure-pipelines/common/bump-insiders-version.yml b/build/azure-pipelines/common/bump-insiders-version.yml deleted file mode 100644 index 3cb9aa88128..00000000000 --- a/build/azure-pipelines/common/bump-insiders-version.yml +++ /dev/null @@ -1,23 +0,0 @@ -steps: - - script: | - set -e - BUILD_NAME="$(Build.BuildNumber)" # example "20251114.34 (insider)" - VSCODE_PATCH_VERSION="$(echo $BUILD_NAME | cut -d' ' -f1 | awk -F. '{printf "%s%03d", $1, $2}')" - VSCODE_MAJOR_MINOR_VERSION="$(node -p "require('./package.json').version.replace(/\.\d+$/, '')")" - VSCODE_INSIDERS_VERSION="${VSCODE_MAJOR_MINOR_VERSION}.${VSCODE_PATCH_VERSION}" - echo "Setting Insiders version to: $VSCODE_INSIDERS_VERSION" - node -e "require('fs').writeFileSync('package.json', JSON.stringify({...require('./package.json'), version: process.argv[1]}, null, 2))" $VSCODE_INSIDERS_VERSION - displayName: Override Insiders Version - condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows'))) - - - pwsh: | - $ErrorActionPreference = "Stop" - $buildName = "$(Build.BuildNumber)" # example "20251114.34 (insider)" - $buildParts = ($buildName -split ' ')[0] -split '\.' - $patchVersion = "{0}{1:000}" -f $buildParts[0], [int]$buildParts[1] - $majorMinorVersion = node -p "require('./package.json').version.replace(/\.\d+$/, '')" - $insidersVersion = "$majorMinorVersion.$patchVersion" - Write-Host "Setting Insiders version to: $insidersVersion" - node -e "require('fs').writeFileSync('package.json', JSON.stringify({...require('./package.json'), version: process.argv[1]}, null, 2))" $insidersVersion - displayName: Override Insiders Version - condition: and(succeeded(), contains(variables['Agent.OS'], 'windows')) diff --git a/build/azure-pipelines/darwin/product-build-darwin-ci.yml b/build/azure-pipelines/darwin/product-build-darwin-ci.yml index 93ea356295d..3920c4ec799 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-ci.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-ci.yml @@ -1,6 +1,4 @@ parameters: - - name: VSCODE_QUALITY - type: string - name: VSCODE_CIBUILD type: boolean - name: VSCODE_TEST_SUITE @@ -38,7 +36,6 @@ jobs: steps: - template: ./steps/product-build-darwin-compile.yml@self parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_ARCH: arm64 VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}: diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli.yml b/build/azure-pipelines/darwin/product-build-darwin-cli.yml index 667cf016ffd..35a9b3566ce 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli.yml @@ -35,9 +35,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../common/bump-insiders-version.yml@self - - template: ../cli/cli-apply-patches.yml@self - task: Npm@1 diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index a41494beb3d..23c85dc714a 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -1,7 +1,3 @@ -parameters: - - name: VSCODE_QUALITY - type: string - jobs: - job: macOSUniversal displayName: macOS (UNIVERSAL) @@ -26,9 +22,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../common/bump-insiders-version.yml@self - - template: ../distro/download-distro.yml@self - task: AzureKeyVault@2 diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 34d70ac79d1..770a54f7925 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,6 +1,4 @@ parameters: - - name: VSCODE_QUALITY - type: string - name: VSCODE_ARCH type: string - name: VSCODE_CIBUILD @@ -74,7 +72,6 @@ jobs: steps: - template: ./steps/product-build-darwin-compile.yml@self parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index 523548e469e..d1d431505f6 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -1,6 +1,4 @@ parameters: - - name: VSCODE_QUALITY - type: string - name: VSCODE_ARCH type: string - name: VSCODE_CIBUILD @@ -23,9 +21,6 @@ steps: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../../common/bump-insiders-version.yml@self - - template: ../../distro/download-distro.yml@self - task: AzureKeyVault@2 diff --git a/build/azure-pipelines/linux/product-build-linux-cli.yml b/build/azure-pipelines/linux/product-build-linux-cli.yml index 548bc04acb6..9052a29e18e 100644 --- a/build/azure-pipelines/linux/product-build-linux-cli.yml +++ b/build/azure-pipelines/linux/product-build-linux-cli.yml @@ -34,9 +34,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../common/bump-insiders-version.yml@self - - template: ../cli/cli-apply-patches.yml@self - task: Npm@1 diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml index c0d65917d33..9dc3f9e120b 100644 --- a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -26,9 +26,6 @@ steps: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../../common/bump-insiders-version.yml@self - - template: ../../distro/download-distro.yml@self - task: AzureKeyVault@2 diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 02acdef21e8..e9c8f74e659 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -192,8 +192,6 @@ extends: - stage: Compile jobs: - template: build/azure-pipelines/product-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - stage: CompileCLI @@ -411,12 +409,10 @@ extends: - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - template: build/azure-pipelines/alpine/product-build-alpine.yml@self parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_ARCH: x64 - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - template: build/azure-pipelines/alpine/product-build-alpine.yml@self parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_ARCH: arm64 - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: @@ -434,31 +430,26 @@ extends: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_TEST_SUITE: Electron - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_TEST_SUITE: Browser - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_TEST_SUITE: Remote - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_ARCH: arm64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} @@ -467,8 +458,6 @@ extends: - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}: - template: build/azure-pipelines/darwin/product-build-darwin-universal.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true))) }}: - template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self @@ -482,8 +471,6 @@ extends: - Compile jobs: - template: build/azure-pipelines/web/product-build-web.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: - stage: Publish diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index c3d705fc077..e025e84f911 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -1,7 +1,3 @@ -parameters: - - name: VSCODE_QUALITY - type: string - jobs: - job: Compile timeoutInMinutes: 60 @@ -24,9 +20,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ./common/bump-insiders-version.yml@self - - template: ./distro/download-distro.yml@self - task: AzureKeyVault@2 diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index 89cf3fabc0d..aa0727a1988 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -31,9 +31,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ./common/bump-insiders-version.yml@self - - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 61ba3263107..d4f1af2d0e0 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -1,7 +1,3 @@ -parameters: - - name: VSCODE_QUALITY - type: string - jobs: - job: Web displayName: Web @@ -28,9 +24,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../common/bump-insiders-version.yml@self - - template: ../distro/download-distro.yml@self - task: AzureKeyVault@2 diff --git a/build/azure-pipelines/win32/product-build-win32-cli.yml b/build/azure-pipelines/win32/product-build-win32-cli.yml index 26ab6ee247b..5dd69c3b50d 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli.yml @@ -34,9 +34,6 @@ jobs: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../common/bump-insiders-version.yml@self - - template: ../cli/cli-apply-patches.yml@self - task: Npm@1 diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index 44a1f060aaa..bdc807fdae5 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -23,9 +23,6 @@ steps: versionSource: fromFile versionFilePath: .nvmrc - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - template: ../../common/bump-insiders-version.yml@self - - task: UsePythonVersion@0 inputs: versionSpec: "3.x" diff --git a/build/gulpfile.vscode.mjs b/build/gulpfile.vscode.mjs index 89e9ec08dd4..8f5a7b0d516 100644 --- a/build/gulpfile.vscode.mjs +++ b/build/gulpfile.vscode.mjs @@ -417,9 +417,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op if (quality === 'stable' || quality === 'insider') { result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' })); const rawVersion = version.replace(/-\w+$/, '').split('.'); - - // AppX doesn't support versions like `1.0.107.20251114039`, so we bring it back down to zero - const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${quality === 'insider' ? '0' : rawVersion[2]}`; + const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${rawVersion[2]}`; result = es.merge(result, gulp.src('resources/win32/appx/AppxManifest.xml', { base: '.' }) .pipe(replace('@@AppxPackageName@@', product.win32AppUserModelId)) .pipe(replace('@@AppxPackageVersion@@', appxVersion)) diff --git a/build/gulpfile.vscode.win32.mjs b/build/gulpfile.vscode.win32.mjs index cc32aa2564f..c10201dfc10 100644 --- a/build/gulpfile.vscode.win32.mjs +++ b/build/gulpfile.vscode.win32.mjs @@ -83,19 +83,12 @@ function buildWin32Setup(arch, target) { fs.writeFileSync(productJsonPath, JSON.stringify(productJson, undefined, '\t')); const quality = product.quality || 'dev'; - let RawVersion = pkg.version.replace(/-\w+$/, ''); - - // InnoSetup doesn't support versions like `1.0.107.20251114039`, so we bring it back down to zero - if (quality === 'insider') { - RawVersion = RawVersion.replace(/(\d+)$/, '0'); - } - const definitions = { NameLong: product.nameLong, NameShort: product.nameShort, DirName: product.win32DirName, Version: pkg.version, - RawVersion, + RawVersion: pkg.version.replace(/-\w+$/, ''), NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''), ExeBasename: product.nameShort, RegValueName: product.win32RegValueName, From d865a42135520a7e396c2fe63a739de925bcf109 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Nov 2025 15:11:15 +0100 Subject: [PATCH 25/34] agent sessions - allow to hide button to aid selfhost in single view (#278357) --- .../contrib/chat/browser/agentSessions/agentSessionsView.ts | 4 +++- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index bf9cb3321ff..766335bd7f4 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -84,7 +84,9 @@ export class AgentSessionsView extends ViewPane { container.classList.add('agent-sessions-view'); // New Session - this.createNewSessionButton(container); + if (!this.configurationService.getValue('chat.hideNewButtonInAgentSessionsView')) { + this.createNewSessionButton(container); + } // Sessions List this.createList(container); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 10e41c8d803..8f6fea8a22f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -765,6 +765,12 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, + 'chat.hideNewButtonInAgentSessionsView': { // TODO@bpasero remove me eventually + type: 'boolean', + description: nls.localize('chat.hideNewButtonInAgentSessionsView', "Controls whether the new session button is hidden in the Agent Sessions view."), + default: false, + tags: ['preview'] + }, 'chat.signInWithAlternateScopes': { // TODO@bpasero remove me eventually type: 'boolean', description: nls.localize('chat.signInWithAlternateScopes', "Controls whether sign-in with alternate scopes is used."), From e5ba9be2e6c7976e273e4ddc8683716c1a60b830 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:22:35 +0000 Subject: [PATCH 26/34] SCM - fix actions on the graph node right above the incoming changes node (#278365) --- .../contrib/scm/browser/scmHistoryViewPane.ts | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 57949338fff..a67ececff33 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -344,7 +344,18 @@ registerAction2(class extends Action2 { } else { title = getHistoryItemEditorTitle(historyItem); historyItemId = historyItem.id; - historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + + if (historyItem.parentIds.length > 0) { + // History item right above the incoming changes history item + if (historyItem.parentIds[0] === SCMIncomingHistoryItemId && historyItemRemoteRef) { + historyItemParentId = await historyProvider.resolveHistoryItemRefsCommonAncestor([ + historyItemRef.name, + historyItemRemoteRef.name + ]); + } else { + historyItemParentId = historyItem.parentIds[0]; + } + } } if (!title || !historyItemId || !historyItemParentId) { @@ -938,7 +949,24 @@ class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource 0 ? historyItem.parentIds[0] : undefined; + + if (historyItem.parentIds.length > 0) { + // History item right above the incoming changes history item + if (historyItem.parentIds[0] === SCMIncomingHistoryItemId) { + const historyItemRef = historyProvider?.historyItemRef.get(); + const historyItemRemoteRef = historyProvider?.historyItemRemoteRef.get(); + + if (!historyProvider || !historyItemRef || !historyItemRemoteRef) { + return []; + } + + historyItemParentId = await historyProvider.resolveHistoryItemRefsCommonAncestor([ + historyItemRef.name, + historyItemRemoteRef.name]); + } else { + historyItemParentId = historyItem.parentIds[0]; + } + } } const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? []; From 59af9cbe461df446c9d5e86f66c10749ccbcf756 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Nov 2025 17:29:44 +0100 Subject: [PATCH 27/34] agent sessions - add `groupId` for hovers (#278376) --- .../contrib/chat/browser/agentSessions/agentSessionsViewer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 137bd445003..e76068d8825 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -258,7 +258,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer Date: Wed, 19 Nov 2025 11:32:44 -0500 Subject: [PATCH 28/34] Add tests + fix PII redaction (#278377) --- .../telemetry/common/telemetryService.ts | 2 +- .../test/browser/telemetryService.test.ts | 235 ++++++++++++++++++ 2 files changed, 236 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index df5658c8258..ef8841dc25b 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -69,7 +69,7 @@ export class TelemetryService implements ITelemetryService { this._sendErrorTelemetry = !!config.sendErrorTelemetry; // static cleanup pattern for: `vscode-file:///DANGEROUS/PATH/resources/app/Useful/Information` - this._cleanupPatterns = [/(vscode-)?file:\/\/\/.*?\/resources\/app\//gi]; + this._cleanupPatterns = [/(vscode-)?file:\/\/.*?\/resources\/app\//gi]; for (const piiPath of this._piiPaths) { this._cleanupPatterns.push(new RegExp(escapeRegExpCharacters(piiPath), 'gi')); diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index bf9522a7616..d9e00af00b0 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -743,5 +743,240 @@ suite('TelemetryService', () => { service.dispose(); }); + test('Unexpected Error Telemetry removes Windows PII but preserves code path', sinonTestFn(function (this: any) { + const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); + Errors.setUnexpectedErrorHandler(() => { }); + + try { + const testAppender = new TestTelemetryAppender(); + const service = new TestErrorTelemetryService({ appenders: [testAppender] }); + const errorTelemetry = new ErrorTelemetry(service); + + const windowsUserPath = 'c:/Users/bpasero/AppData/Local/Programs/Microsoft%20VS%20Code%20Insiders/resources/app/'; + const codePath = 'out/vs/workbench/workbench.desktop.main.js'; + const stack = [ + ` at cTe.gc (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:81492)`, + ` at async cTe.setInput (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:80650)`, + ` at async qJe.S (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:58520)`, + ` at async qJe.L (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:57080)`, + ` at async qJe.openEditor (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:56162)` + ]; + + const windowsError: any = new Error('The editor could not be opened because the file was not found.'); + windowsError.stack = stack.join('\n'); + + Errors.onUnexpectedError(windowsError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.strictEqual(testAppender.getEventsCount(), 1); + // Verify PII (username and path) is removed + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('bpasero'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Users'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('c:/Users'), -1); + // Verify important code path is preserved + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1); + + errorTelemetry.dispose(); + service.dispose(); + } finally { + Errors.setUnexpectedErrorHandler(origErrorHandler); + } + })); + + test('Uncaught Error Telemetry removes Windows PII but preserves code path', sinonTestFn(function (this: any) { + const errorStub = sinon.stub(); + mainWindow.onerror = errorStub; + + const testAppender = new TestTelemetryAppender(); + const service = new TestErrorTelemetryService({ appenders: [testAppender] }); + const errorTelemetry = new ErrorTelemetry(service); + + const windowsUserPath = 'c:/Users/bpasero/AppData/Local/Programs/Microsoft%20VS%20Code%20Insiders/resources/app/'; + const codePath = 'out/vs/workbench/workbench.desktop.main.js'; + const stack = [ + ` at cTe.gc (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:81492)`, + ` at async cTe.setInput (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:80650)`, + ` at async qJe.S (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:58520)` + ]; + + const windowsError: any = new Error('The editor could not be opened because the file was not found.'); + windowsError.stack = stack.join('\n'); + + mainWindow.onerror('The editor could not be opened because the file was not found.', 'test.js', 2, 42, windowsError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.strictEqual(errorStub.callCount, 1); + // Verify PII (username and path) is removed + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('bpasero'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Users'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('c:/Users'), -1); + // Verify important code path is preserved + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1); + + errorTelemetry.dispose(); + service.dispose(); + sinon.restore(); + })); + + test('Unexpected Error Telemetry removes macOS PII but preserves code path', sinonTestFn(function (this: any) { + const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); + Errors.setUnexpectedErrorHandler(() => { }); + + try { + const testAppender = new TestTelemetryAppender(); + const service = new TestErrorTelemetryService({ appenders: [testAppender] }); + const errorTelemetry = new ErrorTelemetry(service); + + const macUserPath = 'Applications/Visual%20Studio%20Code%20-%20Insiders.app/Contents/Resources/app/'; + const codePath = 'out/vs/workbench/workbench.desktop.main.js'; + const stack = [ + ` at uTe.gc (vscode-file://vscode-app/${macUserPath}${codePath}:2720:81492)`, + ` at async uTe.setInput (vscode-file://vscode-app/${macUserPath}${codePath}:2720:80650)`, + ` at async JJe.S (vscode-file://vscode-app/${macUserPath}${codePath}:698:58520)`, + ` at async JJe.L (vscode-file://vscode-app/${macUserPath}${codePath}:698:57080)`, + ` at async JJe.openEditor (vscode-file://vscode-app/${macUserPath}${codePath}:698:56162)` + ]; + + const macError: any = new Error('The editor could not be opened because the file was not found.'); + macError.stack = stack.join('\n'); + + Errors.onUnexpectedError(macError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.strictEqual(testAppender.getEventsCount(), 1); + // Verify PII (application path) is removed + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Applications/Visual'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Visual%20Studio%20Code'), -1); + // Verify important code path is preserved + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1); + + errorTelemetry.dispose(); + service.dispose(); + } finally { + Errors.setUnexpectedErrorHandler(origErrorHandler); + } + })); + + test('Uncaught Error Telemetry removes macOS PII but preserves code path', sinonTestFn(function (this: any) { + const errorStub = sinon.stub(); + mainWindow.onerror = errorStub; + + const testAppender = new TestTelemetryAppender(); + const service = new TestErrorTelemetryService({ appenders: [testAppender] }); + const errorTelemetry = new ErrorTelemetry(service); + + const macUserPath = 'Applications/Visual%20Studio%20Code%20-%20Insiders.app/Contents/Resources/app/'; + const codePath = 'out/vs/workbench/workbench.desktop.main.js'; + const stack = [ + ` at uTe.gc (vscode-file://vscode-app/${macUserPath}${codePath}:2720:81492)`, + ` at async uTe.setInput (vscode-file://vscode-app/${macUserPath}${codePath}:2720:80650)`, + ` at async JJe.S (vscode-file://vscode-app/${macUserPath}${codePath}:698:58520)` + ]; + + const macError: any = new Error('The editor could not be opened because the file was not found.'); + macError.stack = stack.join('\n'); + + mainWindow.onerror('The editor could not be opened because the file was not found.', 'test.js', 2, 42, macError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.strictEqual(errorStub.callCount, 1); + // Verify PII (application path) is removed + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Applications/Visual'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Visual%20Studio%20Code'), -1); + // Verify important code path is preserved + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1); + + errorTelemetry.dispose(); + service.dispose(); + sinon.restore(); + })); + + test('Unexpected Error Telemetry removes Linux PII but preserves code path', sinonTestFn(function (this: any) { + const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); + Errors.setUnexpectedErrorHandler(() => { }); + + try { + const testAppender = new TestTelemetryAppender(); + const service = new TestErrorTelemetryService({ appenders: [testAppender] }); + const errorTelemetry = new ErrorTelemetry(service); + + const linuxUserPath = '/home/parallels/GitDevelopment/vscode-node-sqlite3-perf/'; + const linuxSystemPath = 'usr/share/code-insiders/resources/app/'; + const codePath = 'out/vs/workbench/workbench.desktop.main.js'; + const stack = [ + ` at _kt.G (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65940)`, + ` at _kt.F (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65765)`, + ` at async axt.L (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3830:9998)`, + ` at async axt.readStream (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3830:9773)`, + ` at async mye.Eb (vscode-file://vscode-app/${linuxSystemPath}${codePath}:1313:12359)` + ]; + + const linuxError: any = new Error(`Invalid fake file 'git:${linuxUserPath}index.js.git?{"path":"${linuxUserPath}index.js","ref":""}' (Canceled: Canceled)`); + linuxError.stack = stack.join('\n'); + + Errors.onUnexpectedError(linuxError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.strictEqual(testAppender.getEventsCount(), 1); + // Verify PII (username and home directory) is removed + assert.strictEqual(testAppender.events[0].data.msg.indexOf('parallels'), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf('/home/parallels'), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf('GitDevelopment'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('parallels'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('/home/parallels'), -1); + // Verify important code path is preserved + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1); + + errorTelemetry.dispose(); + service.dispose(); + } finally { + Errors.setUnexpectedErrorHandler(origErrorHandler); + } + })); + + test('Uncaught Error Telemetry removes Linux PII but preserves code path', sinonTestFn(function (this: any) { + const errorStub = sinon.stub(); + mainWindow.onerror = errorStub; + + const testAppender = new TestTelemetryAppender(); + const service = new TestErrorTelemetryService({ appenders: [testAppender] }); + const errorTelemetry = new ErrorTelemetry(service); + + const linuxUserPath = '/home/parallels/GitDevelopment/vscode-node-sqlite3-perf/'; + const linuxSystemPath = 'usr/share/code-insiders/resources/app/'; + const codePath = 'out/vs/workbench/workbench.desktop.main.js'; + const stack = [ + ` at _kt.G (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65940)`, + ` at _kt.F (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65765)`, + ` at async axt.L (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3830:9998)` + ]; + + const linuxError: any = new Error(`Unable to read file 'git:${linuxUserPath}index.js.git'`); + linuxError.stack = stack.join('\n'); + + mainWindow.onerror(`Unable to read file 'git:${linuxUserPath}index.js.git'`, 'test.js', 2, 42, linuxError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.strictEqual(errorStub.callCount, 1); + // Verify PII (username and home directory) is removed + assert.strictEqual(testAppender.events[0].data.msg.indexOf('parallels'), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf('/home/parallels'), -1); + assert.strictEqual(testAppender.events[0].data.msg.indexOf('GitDevelopment'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('parallels'), -1); + assert.strictEqual(testAppender.events[0].data.callstack.indexOf('/home/parallels'), -1); + // Verify important code path is preserved + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1); + assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1); + + errorTelemetry.dispose(); + service.dispose(); + sinon.restore(); + })); + ensureNoDisposablesAreLeakedInTestSuite(); }); From 6fbfe15474e86dc4457ad7763d8606326d516cd1 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 19 Nov 2025 08:42:45 -0800 Subject: [PATCH 29/34] edits: require approval for .code-workspace by default (#278379) --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 8f6fea8a22f..e3350fb5e24 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -269,7 +269,7 @@ configurationRegistry.registerConfiguration({ '**/.vscode/*.json': false, '**/.git/**': false, '**/{package.json,package-lock.json,server.xml,build.rs,web.config,.gitattributes,.env}': false, - '**/*.{csproj,fsproj,vbproj,vcxproj,proj,targets,props}': false, + '**/*.{code-workspace,csproj,fsproj,vbproj,vcxproj,proj,targets,props}': false, }, markdownDescription: nls.localize('chat.tools.autoApprove.edits', "Controls whether edits made by chat are automatically approved. The default is to approve all edits except those made to certain files which have the potential to cause immediate unintended side-effects, such as `**/.vscode/*.json`.\n\nSet to `true` to automatically approve edits to matching files, `false` to always require explicit approval. The last pattern matching a given file will determine whether the edit is automatically approved."), type: 'object', From 24e43a086a167f76c2658868316f14b0c941b02b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Nov 2025 17:58:44 +0100 Subject: [PATCH 30/34] fix #275126 (#278383) --- .../chatManagement/chatModelsViewModel.ts | 63 ++++++++++---- .../chatManagement/chatModelsWidget.ts | 36 ++------ .../test/browser/chatModelsViewModel.test.ts | 82 +++++++++---------- 3 files changed, 97 insertions(+), 84 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts index 8ed7665793a..6b02ef9c999 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts @@ -69,13 +69,22 @@ export function isVendorEntry(entry: IModelItemEntry | IVendorItemEntry): entry return entry.type === 'vendor'; } +export type IViewModelEntry = IModelItemEntry | IVendorItemEntry; + +export interface IViewModelChangeEvent { + at: number; + removed: number; + added: IViewModelEntry[]; +} + export class ChatModelsViewModel extends EditorModel { - private readonly _onDidChangeModelEntries = this._register(new Emitter()); - readonly onDidChangeModelEntries = this._onDidChangeModelEntries.event; + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; private modelEntries: IModelEntry[]; private readonly collapsedVendors = new Set(); + private searchValue: string = ''; constructor( @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @@ -83,14 +92,21 @@ export class ChatModelsViewModel extends EditorModel { ) { super(); this.modelEntries = []; - - this._register(this.chatEntitlementService.onDidChangeEntitlement(async () => { - await this.resolve(); - this._onDidChangeModelEntries.fire(); - })); + this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.refresh())); } - fetch(searchValue: string): (IModelItemEntry | IVendorItemEntry)[] { + private readonly _viewModelEntries: IViewModelEntry[] = []; + get viewModelEntries(): readonly IViewModelEntry[] { + return this._viewModelEntries; + } + private splice(at: number, removed: number, added: IViewModelEntry[]): void { + this._viewModelEntries.splice(at, removed, ...added); + this._onDidChange.fire({ at, removed, added }); + } + + filter(searchValue: string): readonly IViewModelEntry[] { + this.searchValue = searchValue; + let modelEntries = this.modelEntries; const capabilityMatchesMap = new Map(); @@ -135,11 +151,10 @@ export class ChatModelsViewModel extends EditorModel { } searchValue = searchValue.trim(); - if (!searchValue) { - return this.toEntries(modelEntries, capabilityMatchesMap); - } + const filtered = searchValue ? this.filterByText(modelEntries, searchValue, capabilityMatchesMap) : this.toEntries(modelEntries, capabilityMatchesMap); - return this.filterByText(modelEntries, searchValue, capabilityMatchesMap); + this.splice(0, this._viewModelEntries.length, filtered); + return this.viewModelEntries; } private filterByProviders(modelEntries: IModelEntry[], providers: string[]): IModelEntry[] { @@ -264,8 +279,12 @@ export class ChatModelsViewModel extends EditorModel { } override async resolve(): Promise { - this.modelEntries = []; + await this.refresh(); + return super.resolve(); + } + private async refresh(): Promise { + this.modelEntries = []; for (const vendor of this.getVendors()) { const modelIdentifiers = await this.languageModelsService.selectLanguageModels({ vendor: vendor.vendor }, vendor.vendor === 'copilot'); const models = coalesce(modelIdentifiers.map(identifier => { @@ -288,12 +307,24 @@ export class ChatModelsViewModel extends EditorModel { } this.modelEntries = distinct(this.modelEntries, modelEntry => ChatModelsViewModel.getId(modelEntry)); + this.filter(this.searchValue); + } - return super.resolve(); + toggleVisibility(model: IModelItemEntry): void { + const isVisible = model.modelEntry.metadata.isUserSelectable ?? false; + const newVisibility = !isVisible; + this.languageModelsService.updateModelPickerPreference(model.modelEntry.identifier, newVisibility); + const metadata = this.languageModelsService.lookupLanguageModel(model.modelEntry.identifier); + const index = this.viewModelEntries.indexOf(model); + if (metadata) { + model.id = ChatModelsViewModel.getId(model.modelEntry); + model.modelEntry.metadata = metadata; + this.splice(index, 1, [model]); + } } private static getId(modelEntry: IModelEntry): string { - return modelEntry.identifier + modelEntry.vendor + (modelEntry.metadata.version || ''); + return `${modelEntry.identifier}.${modelEntry.metadata.version}-visible:${modelEntry.metadata.isUserSelectable}`; } toggleVendorCollapsed(vendorId: string): void { @@ -302,7 +333,7 @@ export class ChatModelsViewModel extends EditorModel { } else { this.collapsedVendors.add(vendorId); } - this._onDidChangeModelEntries.fire(); + this.filter(this.searchValue); } getConfiguredVendors(): IVendorItemEntry[] { diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts index 1e5a3d86f36..db6069540f4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts @@ -288,11 +288,8 @@ class GutterColumnRenderer extends ModelsTableColumnRenderer(); readonly onDidToggleCollapse = this._onDidToggleCollapse.event; - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - constructor( - @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService + private readonly viewModel: ChatModelsViewModel, ) { super(); } @@ -348,11 +345,7 @@ class GutterColumnRenderer extends ModelsTableColumnRenderer { - const newVisibility = !isVisible; - this.languageModelsService.updateModelPickerPreference(modelEntry.identifier, newVisibility); - this._onDidChange.fire(); - } + run: async () => this.viewModel.toggleVisibility(entry) }); templateData.actionBar.push(toggleVisibilityAction, { icon: true, label: false }); } @@ -712,20 +705,13 @@ export class ChatModelsWidget extends Disposable { super(); this.searchFocusContextKey = CONTEXT_MODELS_SEARCH_FOCUS.bindTo(contextKeyService); - this.delayedFiltering = new Delayer(300); + this.delayedFiltering = new Delayer(200); this.viewModel = this._register(this.instantiationService.createInstance(ChatModelsViewModel)); this.element = DOM.$('.models-widget'); this.create(this.element); - const loadingPromise = this.extensionService.whenInstalledExtensionsRegistered().then(async () => { - await this.viewModel.resolve(); - this.refreshTable(); - }); - - // Show progress indicator while loading models + const loadingPromise = this.extensionService.whenInstalledExtensionsRegistered().then(async () => this.viewModel.resolve()); this.editorProgressService.showWhile(loadingPromise, 300); - - this._register(this.viewModel.onDidChangeModelEntries(() => this.refreshTable())); } private create(container: HTMLElement): void { @@ -765,7 +751,6 @@ export class ChatModelsWidget extends Disposable { focusContextKey: this.searchFocusContextKey, }, )); - this._register(this.searchWidget.onInputDidChange(() => this.filterModels())); const filterAction = this._register(new ModelsFilterAction()); const clearSearchAction = this._register(new Action( @@ -781,6 +766,7 @@ export class ChatModelsWidget extends Disposable { this._register(this.searchWidget.onInputDidChange(() => { clearSearchAction.enabled = !!this.searchWidget.getValue(); + this.filterModels(); })); this.searchActionsContainer = DOM.append(searchContainer, $('.models-search-actions')); @@ -821,7 +807,7 @@ export class ChatModelsWidget extends Disposable { this.tableContainer = DOM.append(container, $('.models-table-container')); // Create table - const gutterColumnRenderer = this.instantiationService.createInstance(GutterColumnRenderer); + const gutterColumnRenderer = this.instantiationService.createInstance(GutterColumnRenderer, this.viewModel); const modelNameColumnRenderer = this.instantiationService.createInstance(ModelNameColumnRenderer); const costColumnRenderer = this.instantiationService.createInstance(MultiplierColumnRenderer); const tokenLimitsColumnRenderer = this.instantiationService.createInstance(TokenLimitsColumnRenderer); @@ -832,12 +818,6 @@ export class ChatModelsWidget extends Disposable { this.viewModel.toggleVendorCollapsed(vendorId); })); - this._register(gutterColumnRenderer.onDidChange(e => { - this.viewModel.resolve().then(() => { - this.refreshTable(); - }); - })); - this._register(actionsColumnRenderer.onDidChange(e => { this.viewModel.resolve().then(() => { this.refreshTable(); @@ -960,6 +940,8 @@ export class ChatModelsWidget extends Disposable { } })); + this.table.splice(0, this.table.length, this.viewModel.viewModelEntries); + this._register(this.viewModel.onDidChange(({ at, removed, added }) => this.table.splice(at, removed, added))); } private filterModels(): void { @@ -968,7 +950,7 @@ export class ChatModelsWidget extends Disposable { private async refreshTable(): Promise { const searchValue = this.searchWidget.getValue(); - const modelItems = this.viewModel.fetch(searchValue); + const modelItems = this.viewModel.filter(searchValue); const vendors = this.viewModel.getVendors(); const configuredVendors = new Set(this.viewModel.getConfiguredVendors().map(cv => cv.vendorEntry.vendor)); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts index 0cbc89277a0..5fd56458d59 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts @@ -250,7 +250,7 @@ suite('ChatModelsViewModel', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('should fetch all models without filters', () => { - const results = viewModel.fetch(''); + const results = viewModel.filter(''); // Should have 2 vendor entries and 4 model entries (grouped by vendor) assert.strictEqual(results.length, 6); @@ -263,7 +263,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by provider name', () => { - const results = viewModel.fetch('@provider:copilot'); + const results = viewModel.filter('@provider:copilot'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 2); @@ -271,7 +271,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by provider display name', () => { - const results = viewModel.fetch('@provider:OpenAI'); + const results = viewModel.filter('@provider:OpenAI'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 2); @@ -279,14 +279,14 @@ suite('ChatModelsViewModel', () => { }); test('should filter by multiple providers with OR logic', () => { - const results = viewModel.fetch('@provider:copilot @provider:openai'); + const results = viewModel.filter('@provider:copilot @provider:openai'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 4); }); test('should filter by single capability - tools', () => { - const results = viewModel.fetch('@capability:tools'); + const results = viewModel.filter('@capability:tools'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 3); @@ -294,7 +294,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by single capability - vision', () => { - const results = viewModel.fetch('@capability:vision'); + const results = viewModel.filter('@capability:vision'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 3); @@ -302,7 +302,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by single capability - agent', () => { - const results = viewModel.fetch('@capability:agent'); + const results = viewModel.filter('@capability:agent'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 1); @@ -310,7 +310,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by multiple capabilities with AND logic', () => { - const results = viewModel.fetch('@capability:tools @capability:vision'); + const results = viewModel.filter('@capability:tools @capability:vision'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; // Should only return models that have BOTH tools and vision @@ -322,7 +322,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by three capabilities with AND logic', () => { - const results = viewModel.fetch('@capability:tools @capability:vision @capability:agent'); + const results = viewModel.filter('@capability:tools @capability:vision @capability:agent'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; // Should only return gpt-4o which has all three @@ -331,7 +331,7 @@ suite('ChatModelsViewModel', () => { }); test('should return no results when filtering by incompatible capabilities', () => { - const results = viewModel.fetch('@capability:vision @capability:agent'); + const results = viewModel.filter('@capability:vision @capability:agent'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; // Only gpt-4o has both vision and agent, but gpt-4-vision doesn't have agent @@ -340,7 +340,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by visibility - visible:true', () => { - const results = viewModel.fetch('@visible:true'); + const results = viewModel.filter('@visible:true'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 3); @@ -348,7 +348,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by visibility - visible:false', () => { - const results = viewModel.fetch('@visible:false'); + const results = viewModel.filter('@visible:false'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 1); @@ -356,7 +356,7 @@ suite('ChatModelsViewModel', () => { }); test('should combine provider and capability filters', () => { - const results = viewModel.fetch('@provider:copilot @capability:vision'); + const results = viewModel.filter('@provider:copilot @capability:vision'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 2); @@ -367,7 +367,7 @@ suite('ChatModelsViewModel', () => { }); test('should combine provider, capability, and visibility filters', () => { - const results = viewModel.fetch('@provider:openai @capability:vision @visible:false'); + const results = viewModel.filter('@provider:openai @capability:vision @visible:false'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 1); @@ -375,7 +375,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by text matching model name', () => { - const results = viewModel.fetch('GPT-4o'); + const results = viewModel.filter('GPT-4o'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 1); @@ -384,7 +384,7 @@ suite('ChatModelsViewModel', () => { }); test('should filter by text matching vendor name', () => { - const results = viewModel.fetch('GitHub'); + const results = viewModel.filter('GitHub'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.strictEqual(models.length, 2); @@ -392,7 +392,7 @@ suite('ChatModelsViewModel', () => { }); test('should combine text search with capability filter', () => { - const results = viewModel.fetch('@capability:tools GPT'); + const results = viewModel.filter('@capability:tools GPT'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; // Should match all models with tools capability and 'GPT' in name @@ -401,21 +401,21 @@ suite('ChatModelsViewModel', () => { }); test('should handle empty search value', () => { - const results = viewModel.fetch(''); + const results = viewModel.filter(''); // Should return all models grouped by vendor assert.ok(results.length > 0); }); test('should handle search value with only whitespace', () => { - const results = viewModel.fetch(' '); + const results = viewModel.filter(' '); // Should return all models grouped by vendor assert.ok(results.length > 0); }); test('should match capability text in free text search', () => { - const results = viewModel.fetch('vision'); + const results = viewModel.filter('vision'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; // Should match models that have vision capability or "vision" in their name @@ -429,7 +429,7 @@ suite('ChatModelsViewModel', () => { test('should toggle vendor collapsed state', () => { viewModel.toggleVendorCollapsed('copilot'); - const results = viewModel.fetch(''); + const results = viewModel.filter(''); const copilotVendor = results.find(r => isVendorEntry(r) && (r as IVendorItemEntry).vendorEntry.vendor === 'copilot') as IVendorItemEntry; assert.ok(copilotVendor); @@ -443,7 +443,7 @@ suite('ChatModelsViewModel', () => { // Toggle back viewModel.toggleVendorCollapsed('copilot'); - const resultsAfterExpand = viewModel.fetch(''); + const resultsAfterExpand = viewModel.filter(''); const copilotModelsAfterExpand = resultsAfterExpand.filter(r => !isVendorEntry(r) && (r as IModelItemEntry).modelEntry.vendor === 'copilot' ); @@ -452,7 +452,7 @@ suite('ChatModelsViewModel', () => { test('should fire onDidChangeModelEntries when entitlement changes', async () => { let fired = false; - store.add(viewModel.onDidChangeModelEntries(() => { + store.add(viewModel.onDidChange(() => { fired = true; })); @@ -468,7 +468,7 @@ suite('ChatModelsViewModel', () => { // When a search string is fully quoted (starts and ends with quotes), // the completeMatch flag is set to true, which currently skips all matching // This test verifies the quotes are processed without errors - const results = viewModel.fetch('"GPT"'); + const results = viewModel.filter('"GPT"'); // The function should complete without error // Note: complete match logic (both quotes) currently doesn't perform matching @@ -476,7 +476,7 @@ suite('ChatModelsViewModel', () => { }); test('should remove filter keywords from text search', () => { - const results = viewModel.fetch('@provider:copilot @capability:vision GPT'); + const results = viewModel.filter('@provider:copilot @capability:vision GPT'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; // Should only search 'GPT' in model names, not the filter keywords @@ -485,9 +485,9 @@ suite('ChatModelsViewModel', () => { }); test('should handle case-insensitive capability matching', () => { - const results1 = viewModel.fetch('@capability:TOOLS'); - const results2 = viewModel.fetch('@capability:tools'); - const results3 = viewModel.fetch('@capability:Tools'); + const results1 = viewModel.filter('@capability:TOOLS'); + const results2 = viewModel.filter('@capability:tools'); + const results3 = viewModel.filter('@capability:Tools'); const models1 = results1.filter(r => !isVendorEntry(r)); const models2 = results2.filter(r => !isVendorEntry(r)); @@ -498,8 +498,8 @@ suite('ChatModelsViewModel', () => { }); test('should support toolcalling alias for tools capability', () => { - const resultsTools = viewModel.fetch('@capability:tools'); - const resultsToolCalling = viewModel.fetch('@capability:toolcalling'); + const resultsTools = viewModel.filter('@capability:tools'); + const resultsToolCalling = viewModel.filter('@capability:toolcalling'); const modelsTools = resultsTools.filter(r => !isVendorEntry(r)); const modelsToolCalling = resultsToolCalling.filter(r => !isVendorEntry(r)); @@ -508,8 +508,8 @@ suite('ChatModelsViewModel', () => { }); test('should support agentmode alias for agent capability', () => { - const resultsAgent = viewModel.fetch('@capability:agent'); - const resultsAgentMode = viewModel.fetch('@capability:agentmode'); + const resultsAgent = viewModel.filter('@capability:agent'); + const resultsAgentMode = viewModel.filter('@capability:agentmode'); const modelsAgent = resultsAgent.filter(r => !isVendorEntry(r)); const modelsAgentMode = resultsAgentMode.filter(r => !isVendorEntry(r)); @@ -518,7 +518,7 @@ suite('ChatModelsViewModel', () => { }); test('should include matched capabilities in results', () => { - const results = viewModel.fetch('@capability:tools @capability:vision'); + const results = viewModel.filter('@capability:tools @capability:vision'); const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; assert.ok(models.length > 0); @@ -587,7 +587,7 @@ suite('ChatModelsViewModel', () => { const { viewModel: singleVendorViewModel } = createSingleVendorViewModel(store, chatEntitlementService); await singleVendorViewModel.resolve(); - const results = singleVendorViewModel.fetch(''); + const results = singleVendorViewModel.filter(''); // Should have only model entries, no vendor entry const vendors = results.filter(isVendorEntry); @@ -600,7 +600,7 @@ suite('ChatModelsViewModel', () => { test('should show vendor headers when multiple vendors exist', () => { // This is the existing behavior test - const results = viewModel.fetch(''); + const results = viewModel.filter(''); // Should have 2 vendor entries and 4 model entries (grouped by vendor) const vendors = results.filter(isVendorEntry); @@ -617,7 +617,7 @@ suite('ChatModelsViewModel', () => { // Try to collapse the single vendor singleVendorViewModel.toggleVendorCollapsed('copilot'); - const results = singleVendorViewModel.fetch(''); + const results = singleVendorViewModel.filter(''); // Should still show models even though vendor is "collapsed" // because there's no vendor header to collapse @@ -632,7 +632,7 @@ suite('ChatModelsViewModel', () => { const { viewModel: singleVendorViewModel } = createSingleVendorViewModel(store, chatEntitlementService); await singleVendorViewModel.resolve(); - const results = singleVendorViewModel.fetch('@capability:agent'); + const results = singleVendorViewModel.filter('@capability:agent'); // Should not show vendor header const vendors = results.filter(isVendorEntry); @@ -645,7 +645,7 @@ suite('ChatModelsViewModel', () => { }); test('should always place copilot vendor at the top', () => { - const results = viewModel.fetch(''); + const results = viewModel.filter(''); const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; assert.ok(vendors.length >= 2); @@ -708,7 +708,7 @@ suite('ChatModelsViewModel', () => { await viewModel.resolve(); - const results = viewModel.fetch(''); + const results = viewModel.filter(''); const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; // Should have 4 vendors: copilot, openai, anthropic, azure @@ -725,7 +725,7 @@ suite('ChatModelsViewModel', () => { test('should keep copilot at top even with text search', () => { // Even when searching, if results include multiple vendors, copilot should be first - const results = viewModel.fetch('GPT'); + const results = viewModel.filter('GPT'); const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; @@ -739,7 +739,7 @@ suite('ChatModelsViewModel', () => { }); test('should keep copilot at top when filtering by capability', () => { - const results = viewModel.fetch('@capability:tools'); + const results = viewModel.filter('@capability:tools'); const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; From 0f481e070765053cd16d2981fcbf67759c4062f3 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Wed, 19 Nov 2025 09:25:23 -0800 Subject: [PATCH 31/34] remove chat summary (#278388) --- src/vs/workbench/api/browser/mainThreadChatAgents2.ts | 3 +-- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostChatAgents2.ts | 8 ++------ src/vs/workbench/contrib/chat/common/chatAgents.ts | 7 ------- src/vs/workbench/contrib/chat/common/chatService.ts | 7 ------- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 1 - src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts | 4 ---- 7 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index e741ebf88ae..089ce8b729e 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -180,8 +180,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA try { return await this._proxy.$invokeAgent(handle, request, { history, - chatSessionContext: chatSession?.contributedChatSession, - chatSummary: request.chatSummary + chatSessionContext: chatSession?.contributedChatSession }, token) ?? {}; } finally { this._pendingProgress.delete(request.requestId); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ea42783cd42..027f7debe74 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1421,7 +1421,7 @@ export interface IChatSessionContextDto { } export interface ExtHostChatAgentsShape2 { - $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise; + $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto }, token: CancellationToken): Promise; $provideFollowups(request: Dto, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, voteAction: IChatVoteAction): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index b724e66b454..3c0b9afcd65 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -559,7 +559,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS this._onDidChangeChatRequestTools.fire(request.extRequest); } - async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise { + async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); @@ -606,11 +606,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS }; } - const chatContext: vscode.ChatContext = { - history, - chatSessionContext, - chatSummary: context.chatSummary - }; + const chatContext: vscode.ChatContext = { history, chatSessionContext }; const task = agent.invoke( extRequest, chatContext, diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 56d78746776..44d1bca7014 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -153,13 +153,6 @@ export interface IChatAgentRequest { editedFileEvents?: IChatAgentEditedFileEvent[]; isSubagent?: boolean; - /** - * Summary data for chat sessions context - */ - chatSummary?: { - prompt?: string; - history?: string; - }; } export interface IChatQuestion { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 95befa3816b..a567027daca 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -932,13 +932,6 @@ export interface IChatSendRequestOptions { */ confirmation?: string; - /** - * Summary data for chat sessions context - */ - chatSummary?: { - prompt?: string; - history?: string; - }; } export const IChatService = createDecorator('IChatService'); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 181608b00ff..8a8270a3b3e 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -908,7 +908,6 @@ export class ChatService extends Disposable implements IChatService { userSelectedTools: options?.userSelectedTools?.get(), modeInstructions: options?.modeInfo?.modeInstructions, editedFileEvents: request.editedFileEvents, - chatSummary: options?.chatSummary }; let isInitialTools = true; diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index 489e5f952c8..e47cdbb1fe0 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -245,10 +245,6 @@ declare module 'vscode' { export interface ChatContext { readonly chatSessionContext?: ChatSessionContext; - readonly chatSummary?: { - readonly prompt?: string; - readonly history?: string; - }; } export interface ChatSessionContext { From 5dbc14ff7a18ed96f9cd526940934b649bac3dcc Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 19 Nov 2025 19:07:07 +0100 Subject: [PATCH 32/34] prompt files: allow names to contain spaces (#278395) allow names to contain spaces --- .../languageProviders/promptValidator.ts | 5 +- .../common/promptSyntax/promptFileParser.ts | 8 +-- .../service/promptsServiceImpl.ts | 4 +- .../promptSytntax/promptValidator.test.ts | 49 +------------------ 4 files changed, 6 insertions(+), 60 deletions(-) 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 3dcdf132686..361e887a529 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -16,7 +16,7 @@ import { ChatModeKind } from '../../constants.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; -import { GithubPromptHeaderAttributes, IArrayValue, IHeaderAttribute, IStringValue, ParsedPromptFile, PROMPT_NAME_REGEXP, PromptHeaderAttributes, Target } from '../promptFileParser.js'; +import { GithubPromptHeaderAttributes, IArrayValue, IHeaderAttribute, IStringValue, ParsedPromptFile, PromptHeaderAttributes, Target } from '../promptFileParser.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; @@ -197,9 +197,6 @@ export class PromptValidator { report(toMarker(localize('promptValidator.nameShouldNotBeEmpty', "The 'name' attribute must not be empty."), nameAttribute.value.range, MarkerSeverity.Error)); return; } - if (!PROMPT_NAME_REGEXP.test(nameAttribute.value.value)) { - report(toMarker(localize('promptValidator.nameInvalidCharacters', "The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods."), nameAttribute.value.range, MarkerSeverity.Error)); - } } private validateDescription(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index a4ed0889775..d42e2ca1824 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -10,8 +10,6 @@ import { URI } from '../../../../../base/common/uri.js'; import { parse, YamlNode, YamlParseError, Position as YamlPosition } from '../../../../../base/common/yaml.js'; import { Range } from '../../../../../editor/common/core/range.js'; -export const PROMPT_NAME_REGEXP = /^[\p{L}\d_\-\.]+$/u; - export class PromptFileParser { constructor() { } @@ -162,11 +160,7 @@ export class PromptHeader { } public get name(): string | undefined { - const name = this.getStringAttribute(PromptHeaderAttributes.name); - if (name && PROMPT_NAME_REGEXP.test(name)) { - return name; - } - return undefined; + return this.getStringAttribute(PromptHeaderAttributes.name); } public get description(): string | undefined { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index e518cbcd80b..6db1a4593ab 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -246,8 +246,10 @@ export class PromptsService extends Disposable implements IPromptsService { } private asChatPromptSlashCommand(parsedPromptFile: ParsedPromptFile, promptPath: IPromptPath): IChatPromptSlashCommand { + let name = parsedPromptFile?.header?.name ?? promptPath.name ?? getCleanPromptName(promptPath.uri); + name = name.replace(/[^\p{L}\d_\-\.]+/gu, '-'); // replace spaces with dashes return { - name: parsedPromptFile?.header?.name ?? promptPath.name ?? getCleanPromptName(promptPath.uri), + name: name, description: parsedPromptFile?.header?.description ?? promptPath.description, argumentHint: parsedPromptFile?.header?.argumentHint, parsedPromptFile, diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index f26c1634353..6f3d3f3ac1d 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -472,27 +472,11 @@ suite('PromptValidator', () => { assert.strictEqual(markers[0].message, `The 'name' attribute must be a string.`); } - // Invalid characters in name - { - const content = [ - '---', - 'name: "My@Agent!"', - 'description: "Test agent"', - 'target: vscode', - '---', - 'Body', - ].join('\n'); - const markers = await validate(content, PromptsType.agent); - assert.strictEqual(markers.length, 1); - assert.strictEqual(markers[0].severity, MarkerSeverity.Error); - assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`); - } - // Valid name with allowed characters { const content = [ '---', - 'name: "My_Agent-2.0"', + 'name: "My_Agent-2.0 with spaces"', 'description: "Test agent"', 'target: vscode', '---', @@ -632,22 +616,6 @@ suite('PromptValidator', () => { assert.strictEqual(markers[0].severity, MarkerSeverity.Error); assert.strictEqual(markers[0].message, `The 'name' attribute must not be empty.`); } - - // Invalid characters in name - { - const content = [ - '---', - 'name: "My Instructions#"', - 'description: "Test instructions"', - 'applyTo: "**/*.ts"', - '---', - 'Body', - ].join('\n'); - const markers = await validate(content, PromptsType.instructions); - assert.strictEqual(markers.length, 1); - assert.strictEqual(markers[0].severity, MarkerSeverity.Error); - assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`); - } }); }); @@ -786,21 +754,6 @@ suite('PromptValidator', () => { assert.strictEqual(markers[0].severity, MarkerSeverity.Error); assert.strictEqual(markers[0].message, `The 'name' attribute must not be empty.`); } - - // Invalid characters in name - { - const content = [ - '---', - 'name: "My Prompt!"', - 'description: "Test prompt"', - '---', - 'Body', - ].join('\n'); - const markers = await validate(content, PromptsType.prompt); - assert.strictEqual(markers.length, 1); - assert.strictEqual(markers[0].severity, MarkerSeverity.Error); - assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`); - } }); }); From 3efda6331d969402b5e4dfc89760689e3f76ec3a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 19 Nov 2025 10:07:59 -0800 Subject: [PATCH 33/34] Delete CHAT_WIDGET_VIEW_RESOURCE (#278397) --- .../chatSessions/localChatSessionsProvider.ts | 3 --- .../browser/chatSessions/view/sessionsViewPane.ts | 12 +++--------- .../contrib/chat/browser/chatWidgetService.ts | 10 ---------- .../workbench/contrib/chat/common/chatServiceImpl.ts | 2 +- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 612d4d8b5bc..fe2072b0aab 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -7,9 +7,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../../base/common/map.js'; -import { Schemas } from '../../../../../base/common/network.js'; import { IObservable } from '../../../../../base/common/observable.js'; -import { URI } from '../../../../../base/common/uri.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatModel } from '../../common/chatModel.js'; @@ -22,7 +20,6 @@ import { ChatSessionItemWithProvider } from './common.js'; export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider, IWorkbenchContribution { static readonly ID = 'workbench.contrib.localChatSessionsProvider'; static readonly CHAT_WIDGET_VIEW_ID = 'workbench.panel.chat.view.copilot'; - static readonly CHAT_WIDGET_VIEW_RESOURCE = URI.parse(`${Schemas.vscodeLocalChatSession}://widget`); readonly chatSessionType = localChatSessionType; private readonly _onDidChange = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index df76c123334..f4576d6e2e0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -10,11 +10,9 @@ import { IActionViewItem } from '../../../../../../base/browser/ui/actionbar/act import { IBaseActionViewItemOptions } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js'; import { ITreeContextMenuEvent } from '../../../../../../base/browser/ui/tree/tree.js'; import { IAction, toAction } from '../../../../../../base/common/actions.js'; -import { coalesce } from '../../../../../../base/common/arrays.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { FuzzyScore } from '../../../../../../base/common/filters.js'; import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; -import { isEqual } from '../../../../../../base/common/resources.js'; import { truncate } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; import * as nls from '../../../../../../nls.js'; @@ -304,11 +302,7 @@ export class SessionsViewPane extends ViewPane { const renderer = this.instantiationService.createInstance(SessionsRenderer, this.viewDescriptorService.getViewLocationById(this.viewId)); this._register(renderer); - const getResourceForElement = (element: ChatSessionItemWithProvider): URI | null => { - if (isEqual(element.resource, LocalChatSessionsProvider.CHAT_WIDGET_VIEW_RESOURCE)) { - return null; - } - + const getResourceForElement = (element: ChatSessionItemWithProvider): URI => { return element.resource; }; @@ -324,14 +318,14 @@ export class SessionsViewPane extends ViewPane { onDragStart: (data, originalEvent) => { try { const elements = data.getData() as ChatSessionItemWithProvider[]; - const uris = coalesce(elements.map(getResourceForElement)); + const uris = elements.map(getResourceForElement); this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent)); } catch { // noop } }, getDragURI: (element: ChatSessionItemWithProvider) => { - return getResourceForElement(element)?.toString() ?? null; + return getResourceForElement(element).toString(); }, getDragLabel: (elements: ChatSessionItemWithProvider[]) => { if (elements.length === 1) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidgetService.ts b/src/vs/workbench/contrib/chat/browser/chatWidgetService.ts index 45b4a7ee5dc..604c9c1b102 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidgetService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidgetService.ts @@ -17,7 +17,6 @@ import { ChatAgentLocation } from '../common/constants.js'; import { ChatViewId, ChatViewPaneTarget, IChatWidget, IChatWidgetService, IQuickChatService, isIChatViewViewContext } from './chat.js'; import { ChatEditor, IChatEditorOptions } from './chatEditor.js'; import { findExistingChatEditorByUri } from './chatSessions/common.js'; -import { LocalChatSessionsProvider } from './chatSessions/localChatSessionsProvider.js'; import { ChatViewPane } from './chatViewPane.js'; export class ChatWidgetService extends Disposable implements IChatWidgetService { @@ -95,15 +94,6 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget): Promise; openSession(sessionResource: URI, target?: PreferredGroup, options?: IChatEditorOptions): Promise; async openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget | PreferredGroup, options?: IChatEditorOptions): Promise { - // TODO remove this, open the real resource - if (isEqual(sessionResource, LocalChatSessionsProvider.CHAT_WIDGET_VIEW_RESOURCE)) { - const chatViewPane = await this.viewsService.openView(ChatViewId, true); - if (chatViewPane) { - chatViewPane.focusInput(); - } - return chatViewPane?.widget; - } - const alreadyOpenWidget = await this.revealSessionIfAlreadyOpen(sessionResource); if (alreadyOpenWidget) { return alreadyOpenWidget; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 8a8270a3b3e..9adc2c0bb13 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -404,7 +404,7 @@ export class ChatService extends Disposable implements IChatService { }); } - shouldBeInHistory(entry: Partial) { + private shouldBeInHistory(entry: Partial) { if (entry.sessionResource) { return !entry.isImported && LocalChatSessionUri.parseLocalSessionId(entry.sessionResource) && entry.initialLocation !== ChatAgentLocation.EditorInline; } From 8fe04a776279091c795f795ac40a5de5dc7f8596 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:20:39 +0000 Subject: [PATCH 34/34] Add smart auto-suggest for chat.tools.eligibleForAutoApproval setting (#278254) * Initial plan * Add smart auto suggest for chat.tools.eligibleForAutoApproval setting Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * Enhance tool reference name descriptions with source labels and tool descriptions Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * fix * Update src/vs/workbench/contrib/chat/browser/chat.contribution.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../contrib/chat/browser/chat.contribution.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e3350fb5e24..a0d69c265d3 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -132,6 +132,9 @@ import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsCo import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; import { ChatWidgetService } from './chatWidgetService.js'; +const toolReferenceNameEnumValues: string[] = []; +const toolReferenceNameEnumDescriptions: string[] = []; + // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -298,6 +301,10 @@ configurationRegistry.registerConfiguration({ default: {}, markdownDescription: nls.localize('chat.tools.eligibleForAutoApproval', 'Controls which tools are eligible for automatic approval. Tools set to \'false\' will always present a confirmation and will never offer the option to auto-approve. The default behavior (or setting a tool to \'true\') may result in the tool offering auto-approval options.'), type: 'object', + propertyNames: { + enum: toolReferenceNameEnumValues, + enumDescriptions: toolReferenceNameEnumDescriptions, + }, additionalProperties: { type: 'boolean', }, @@ -921,6 +928,43 @@ class ChatAgentSettingContribution extends Disposable implements IWorkbenchContr } } +class ToolReferenceNamesContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.toolReferenceNames'; + + constructor( + @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, + ) { + super(); + this._updateToolReferenceNames(); + this._register(this._languageModelToolsService.onDidChangeTools(() => this._updateToolReferenceNames())); + } + + private _updateToolReferenceNames(): void { + const tools = + Array.from(this._languageModelToolsService.getTools()) + .filter((tool): tool is typeof tool & { toolReferenceName: string } => typeof tool.toolReferenceName === 'string') + .sort((a, b) => a.toolReferenceName.localeCompare(b.toolReferenceName)); + toolReferenceNameEnumValues.length = 0; + toolReferenceNameEnumDescriptions.length = 0; + for (const tool of tools) { + toolReferenceNameEnumValues.push(tool.toolReferenceName); + toolReferenceNameEnumDescriptions.push(nls.localize( + 'chat.toolReferenceName.description', + "{0} - {1}", + tool.toolReferenceName, + tool.userDescription || tool.displayName + )); + } + configurationRegistry.notifyConfigurationSchemaUpdated({ + id: 'chatSidebar', + properties: { + [ChatConfiguration.EligibleForAutoApproval]: {} + } + }); + } +} + AccessibleViewRegistry.register(new ChatTerminalOutputAccessibleView()); AccessibleViewRegistry.register(new ChatResponseAccessibleView()); AccessibleViewRegistry.register(new PanelChatAccessibilityHelp()); @@ -1025,6 +1069,7 @@ registerWorkbenchContribution2(ChatTeardownContribution.ID, ChatTeardownContribu registerWorkbenchContribution2(ChatStatusBarEntry.ID, ChatStatusBarEntry, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(ChatAgentSettingContribution.ID, ChatAgentSettingContribution, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(ToolReferenceNamesContribution.ID, ToolReferenceNamesContribution, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatEditingEditorAccessibility.ID, ChatEditingEditorAccessibility, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatEditingEditorOverlay.ID, ChatEditingEditorOverlay, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(SimpleBrowserOverlay.ID, SimpleBrowserOverlay, WorkbenchPhase.AfterRestored);