From a8101c12b5bc7e3f6ef323f5fc59f58d448acdbe Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:15:39 -0700 Subject: [PATCH 01/16] Fix any cases in md extension For #269213 --- .../notebook/index.ts | 12 ++++----- .../package-lock.json | 17 ++++++------ .../markdown-language-features/package.json | 6 ++--- .../preview-src/index.ts | 14 +++++----- .../src/client/protocol.ts | 3 +-- .../src/languageFeatures/diagnostics.ts | 3 +-- .../src/markdownEngine.ts | 26 +++++++++---------- 7 files changed, 39 insertions(+), 42 deletions(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 1c6e68f4de828..3d11a7574bd01 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -5,7 +5,6 @@ import DOMPurify from 'dompurify'; import MarkdownIt from 'markdown-it'; -import type * as MarkdownItToken from 'markdown-it/lib/token'; import type { ActivationFunction } from 'vscode-notebook-renderer'; const allowedHtmlTags = Object.freeze(['a', @@ -352,11 +351,11 @@ export const activate: ActivationFunction = (ctx) => { }; -function addNamedHeaderRendering(md: InstanceType): void { +function addNamedHeaderRendering(md: MarkdownIt): void { const slugCounter = new Map(); const originalHeaderOpen = md.renderer.rules.heading_open; - md.renderer.rules.heading_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => { + md.renderer.rules.heading_open = (tokens: MarkdownIt.Token[], idx: number, options, env, self) => { const title = tokens[idx + 1].children!.reduce((acc, t) => acc + t.content, ''); let slug = slugify(title); @@ -378,17 +377,16 @@ function addNamedHeaderRendering(md: InstanceType): void { }; const originalRender = md.render; - md.render = function () { + md.render = function (str: string, env?: unknown) { slugCounter.clear(); - // eslint-disable-next-line local/code-no-any-casts - return originalRender.apply(this, arguments as any); + return originalRender.call(this, str, env); }; } function addLinkRenderer(md: MarkdownIt): void { const original = md.renderer.rules.link_open; - md.renderer.rules.link_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => { + md.renderer.rules.link_open = (tokens: MarkdownIt.Token[], idx: number, options, env, self) => { const token = tokens[idx]; const href = token.attrGet('href'); if (typeof href === 'string' && href.startsWith('#')) { diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index d8169c35e676e..44fee9edb8414 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -14,7 +14,7 @@ "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.4", - "morphdom": "^2.7.4", + "morphdom": "^2.7.7", "picomatch": "^2.3.1", "punycode": "^2.3.1", "vscode-languageclient": "^8.0.2", @@ -25,7 +25,7 @@ "devDependencies": { "@types/dompurify": "^3.0.5", "@types/lodash.throttle": "^4.1.3", - "@types/markdown-it": "12.2.3", + "@types/markdown-it": "14.1.0", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", @@ -193,10 +193,11 @@ } }, "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-B/o5iMXxpIAl1+792pO4ryhrNNiAus9HhrTs+Gt2m+6SIdEUEvyOIs0iqDNjTSRgbelWvyeek3UxfYYOKDaWfA==", "dev": true, + "license": "MIT", "dependencies": { "@types/linkify-it": "*", "@types/mdurl": "*" @@ -511,9 +512,9 @@ } }, "node_modules/morphdom": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.4.tgz", - "integrity": "sha512-ATTbWMgGa+FaMU3FhnFYB6WgulCqwf6opOll4CBzmVDTLvPMmUPrEv8CudmLPK0MESa64+6B89fWOxP3+YIlxQ==", + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.7.tgz", + "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==", "license": "MIT" }, "node_modules/node-html-parser": { diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 922fe087af543..d4b197e94967c 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -756,7 +756,7 @@ ] }, "scripts": { - "compile": "gulp compile-extension:markdown-language-features-languageService && gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", + "compile": "gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", "watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService", "vscode:prepublish": "npm run build-ext && npm run build-preview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", @@ -771,7 +771,7 @@ "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.4", - "morphdom": "^2.7.4", + "morphdom": "^2.7.7", "picomatch": "^2.3.1", "punycode": "^2.3.1", "vscode-languageclient": "^8.0.2", @@ -782,7 +782,7 @@ "devDependencies": { "@types/dompurify": "^3.0.5", "@types/lodash.throttle": "^4.1.3", - "@types/markdown-it": "12.2.3", + "@types/markdown-it": "14.1.0", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index b6200b8ceb9ff..08475ad7fa4be 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -23,8 +23,12 @@ let documentResource = settings.settings.source; const vscode = acquireVsCodeApi(); -// eslint-disable-next-line local/code-no-any-casts -const originalState = vscode.getState() ?? {} as any; +interface State { + scrollProgress?: number; + resource?: string; +} + +const originalState: State = vscode.getState() ?? {}; const state = { ...originalState, ...getData('data-state') @@ -250,7 +254,6 @@ window.addEventListener('message', async event => { } newRoot.prepend(...styles); - // eslint-disable-next-line local/code-no-any-casts morphdom(root, newRoot, { childrenOnly: true, onBeforeElUpdated: (fromEl: Element, toEl: Element) => { @@ -287,7 +290,7 @@ window.addEventListener('message', async event => { domEval(childNode); } } - } as any); + }); } ++documentVersion; @@ -441,8 +444,7 @@ function domEval(el: Element): void { for (const key of preservedScriptAttributes) { const val = node.getAttribute?.(key); if (val) { - // eslint-disable-next-line local/code-no-any-casts - scriptTag.setAttribute(key, val as any); + scriptTag.setAttribute(key, val); } } diff --git a/extensions/markdown-language-features/src/client/protocol.ts b/extensions/markdown-language-features/src/client/protocol.ts index 69d162f8262f3..a822327f9ffb4 100644 --- a/extensions/markdown-language-features/src/client/protocol.ts +++ b/extensions/markdown-language-features/src/client/protocol.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; import { FileRename, RequestType } from 'vscode-languageclient'; import type * as lsp from 'vscode-languageserver-types'; @@ -16,7 +15,7 @@ export type ResolvedDocumentLinkTarget = | { readonly kind: 'external'; readonly uri: vscode.Uri }; //#region From server -export const parse = new RequestType<{ uri: string; text?: string }, Token[], any>('markdown/parse'); +export const parse = new RequestType<{ uri: string; text?: string }, md.Token[], any>('markdown/parse'); export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile'); export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory'); diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index 793ea7f64ecbd..0ffab0b01fb0d 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -50,8 +50,7 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { case DiagnosticCode.link_noSuchHeaderInOwnFile: case DiagnosticCode.link_noSuchFile: case DiagnosticCode.link_noSuchHeaderInFile: { - // eslint-disable-next-line local/code-no-any-casts - const hrefText = (diagnostic as any).data?.hrefText; + const hrefText = (diagnostic as unknown as Record).data?.hrefText; if (hrefText) { const fix = new vscode.CodeAction( vscode.l10n.t("Exclude '{0}' from link validation.", hrefText), diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 733fcf9e43785..6a655da5500a4 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import type MarkdownIt = require('markdown-it'); -import type Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; import { ILogger } from './logging'; import { MarkdownContributionProvider } from './markdownExtensions'; @@ -50,9 +49,9 @@ class TokenCache { readonly version: number; readonly config: MarkdownItConfig; }; - private _tokens?: Token[]; + private _tokens?: MarkdownIt.Token[]; - public tryGetCached(document: ITextDocument, config: MarkdownItConfig): Token[] | undefined { + public tryGetCached(document: ITextDocument, config: MarkdownItConfig): MarkdownIt.Token[] | undefined { if (this._cachedDocument && this._cachedDocument.uri.toString() === document.uri.toString() && document.version >= 0 && this._cachedDocument.version === document.version @@ -64,7 +63,7 @@ class TokenCache { return undefined; } - public update(document: ITextDocument, config: MarkdownItConfig, tokens: Token[]) { + public update(document: ITextDocument, config: MarkdownItConfig, tokens: MarkdownIt.Token[]) { this._cachedDocument = { uri: document.uri, version: document.version, @@ -93,7 +92,7 @@ interface RenderEnv { export interface IMdParser { readonly slugifier: Slugifier; - tokenize(document: ITextDocument): Promise; + tokenize(document: ITextDocument): Promise; } export class MarkdownItEngine implements IMdParser { @@ -143,8 +142,7 @@ export class MarkdownItEngine implements IMdParser { const frontMatterPlugin = await import('markdown-it-front-matter'); // Extract rules from front matter plugin and apply at a lower precedence let fontMatterRule: any; - // eslint-disable-next-line local/code-no-any-casts - frontMatterPlugin.default({ + frontMatterPlugin.default({ block: { ruler: { before: (_id: any, _id2: any, rule: any) => { fontMatterRule = rule; } @@ -180,7 +178,7 @@ export class MarkdownItEngine implements IMdParser { document: ITextDocument, config: MarkdownItConfig, engine: MarkdownIt - ): Token[] { + ): MarkdownIt.Token[] { const cached = this._tokenCache.tryGetCached(document, config); if (cached) { this._resetSlugCount(); @@ -228,7 +226,7 @@ export class MarkdownItEngine implements IMdParser { }; } - public async tokenize(document: ITextDocument): Promise { + public async tokenize(document: ITextDocument): Promise { const config = this._getConfig(document.uri); const engine = await this._getEngine(config); return this._tokenizeDocument(document, config, engine); @@ -249,7 +247,7 @@ export class MarkdownItEngine implements IMdParser { private _addImageRenderer(md: MarkdownIt): void { const original = md.renderer.rules.image; - md.renderer.rules.image = (tokens: Token[], idx: number, options, env: RenderEnv, self) => { + md.renderer.rules.image = (tokens: MarkdownIt.Token[], idx: number, options, env: RenderEnv, self) => { const token = tokens[idx]; const src = token.attrGet('src'); if (src) { @@ -271,7 +269,7 @@ export class MarkdownItEngine implements IMdParser { private _addFencedRenderer(md: MarkdownIt): void { const original = md.renderer.rules['fenced']; - md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options, env, self) => { + md.renderer.rules['fenced'] = (tokens: MarkdownIt.Token[], idx: number, options, env, self) => { const token = tokens[idx]; if (token.map?.length) { token.attrJoin('class', 'hljs'); @@ -313,7 +311,7 @@ export class MarkdownItEngine implements IMdParser { private _addNamedHeaders(md: MarkdownIt): void { const original = md.renderer.rules.heading_open; - md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env, self) => { + md.renderer.rules.heading_open = (tokens: MarkdownIt.Token[], idx: number, options, env, self) => { const title = this._tokenToPlainText(tokens[idx + 1]); let slug = this.slugifier.fromHeading(title); @@ -335,7 +333,7 @@ export class MarkdownItEngine implements IMdParser { }; } - private _tokenToPlainText(token: Token): string { + private _tokenToPlainText(token: MarkdownIt.Token): string { if (token.children) { return token.children.map(x => this._tokenToPlainText(x)).join(''); } @@ -353,7 +351,7 @@ export class MarkdownItEngine implements IMdParser { private _addLinkRenderer(md: MarkdownIt): void { const original = md.renderer.rules.link_open; - md.renderer.rules.link_open = (tokens: Token[], idx: number, options, env, self) => { + md.renderer.rules.link_open = (tokens: MarkdownIt.Token[], idx: number, options, env, self) => { const token = tokens[idx]; const href = token.attrGet('href'); // A string, including empty string, may be `href`. From 2de481e6cc45e3f036f360a34bbd00e52090f076 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:18:29 -0700 Subject: [PATCH 02/16] Also update watch script --- extensions/markdown-language-features/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index d4b197e94967c..0210681bd1418 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -757,7 +757,7 @@ }, "scripts": { "compile": "gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService", + "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", "vscode:prepublish": "npm run build-ext && npm run build-preview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", "build-notebook": "node ./esbuild-notebook.mjs", From 2e81391ad362a5b26212d99e25e632a7700024ff Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:31:13 -0800 Subject: [PATCH 03/16] Adopt esbuild instead of webpack for a few more extensions Adopting for configuration-editing, emmet, grunt, jake, and npm --- .../configuration-editing/.vscodeignore | 5 +- .../configuration-editing/esbuild.browser.mts | 35 +++++++++++ extensions/configuration-editing/esbuild.mts | 18 ++++++ .../extension-browser.webpack.config.js | 23 ------- extensions/configuration-editing/package.json | 8 ++- .../tsconfig.browser.json | 10 +++ extensions/emmet/.vscodeignore | 5 +- extensions/emmet/esbuild.browser.mts | 21 +++++++ .../esbuild.mts} | 29 ++++----- extensions/emmet/package.json | 8 ++- extensions/emmet/tsconfig.browser.json | 10 +++ extensions/esbuild-extension-common.mts | 63 ++++++++++++------- extensions/grunt/.vscodeignore | 4 +- .../esbuild.mts} | 23 +++---- extensions/gulp/.vscodeignore | 4 +- .../esbuild.mts} | 23 ++++--- extensions/gulp/extension.webpack.config.js | 16 ----- extensions/jake/.vscodeignore | 4 +- .../esbuild.mts} | 22 ++++--- extensions/jake/extension.webpack.config.js | 16 ----- .../esbuild.browser.mts | 11 +--- .../markdown-language-features/esbuild.mts | 1 + extensions/markdown-math/esbuild.browser.mts | 9 +-- extensions/markdown-math/esbuild.mts | 1 + extensions/media-preview/esbuild.browser.mts | 9 +-- extensions/media-preview/esbuild.mts | 1 + .../mermaid-chat-features/esbuild.browser.mts | 9 +-- extensions/mermaid-chat-features/esbuild.mts | 1 + extensions/npm/.vscodeignore | 5 +- extensions/npm/esbuild.browser.mts | 25 ++++++++ .../esbuild.mts} | 25 ++++---- extensions/npm/extension.webpack.config.js | 20 ------ extensions/npm/package.json | 8 ++- .../src/features/packageJSONContribution.ts | 5 +- extensions/npm/tsconfig.browser.json | 10 +++ 35 files changed, 275 insertions(+), 212 deletions(-) create mode 100644 extensions/configuration-editing/esbuild.browser.mts create mode 100644 extensions/configuration-editing/esbuild.mts delete mode 100644 extensions/configuration-editing/extension-browser.webpack.config.js create mode 100644 extensions/configuration-editing/tsconfig.browser.json create mode 100644 extensions/emmet/esbuild.browser.mts rename extensions/{npm/extension-browser.webpack.config.js => emmet/esbuild.mts} (50%) create mode 100644 extensions/emmet/tsconfig.browser.json rename extensions/{emmet/extension-browser.webpack.config.js => grunt/esbuild.mts} (52%) rename extensions/{emmet/extension.webpack.config.js => gulp/esbuild.mts} (52%) delete mode 100644 extensions/gulp/extension.webpack.config.js rename extensions/{grunt/extension.webpack.config.js => jake/esbuild.mts} (52%) delete mode 100644 extensions/jake/extension.webpack.config.js create mode 100644 extensions/npm/esbuild.browser.mts rename extensions/{configuration-editing/extension.webpack.config.js => npm/esbuild.mts} (51%) delete mode 100644 extensions/npm/extension.webpack.config.js create mode 100644 extensions/npm/tsconfig.browser.json diff --git a/extensions/configuration-editing/.vscodeignore b/extensions/configuration-editing/.vscodeignore index 679a6d6859f0a..7c246a3d95f20 100644 --- a/extensions/configuration-editing/.vscodeignore +++ b/extensions/configuration-editing/.vscodeignore @@ -1,9 +1,8 @@ test/** src/** -tsconfig.json +tsconfig*.json out/** -extension.webpack.config.js -extension-browser.webpack.config.js +esbuild* package-lock.json build/** schemas/devContainer.codespaces.schema.json diff --git a/extensions/configuration-editing/esbuild.browser.mts b/extensions/configuration-editing/esbuild.browser.mts new file mode 100644 index 0000000000000..347aadc7c9229 --- /dev/null +++ b/extensions/configuration-editing/esbuild.browser.mts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import type { Plugin } from 'esbuild'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +/** + * Plugin to redirect `./node/net` imports to `./browser/net` for the browser build. + */ +const browserNetPlugin: Plugin = { + name: 'browser-net-redirect', + setup(build) { + build.onResolve({ filter: /\/node\/net$/ }, args => { + return { path: path.join(path.dirname(args.resolveDir), 'src', 'browser', 'net.ts') }; + }); + }, +}; + +run({ + platform: 'browser', + entryPoints: { + 'configurationEditingMain': path.join(srcDir, 'configurationEditingMain.ts'), + }, + srcDir, + outdir: outDir, + additionalOptions: { + plugins: [browserNetPlugin], + tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), + }, +}, process.argv); diff --git a/extensions/configuration-editing/esbuild.mts b/extensions/configuration-editing/esbuild.mts new file mode 100644 index 0000000000000..a0d03289442b4 --- /dev/null +++ b/extensions/configuration-editing/esbuild.mts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'configurationEditingMain': path.join(srcDir, 'configurationEditingMain.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/configuration-editing/extension-browser.webpack.config.js b/extensions/configuration-editing/extension-browser.webpack.config.js deleted file mode 100644 index 1136c92520837..0000000000000 --- a/extensions/configuration-editing/extension-browser.webpack.config.js +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import path from 'path'; -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; - -export default withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/configurationEditingMain.ts' - }, - output: { - filename: 'configurationEditingMain.js' - }, - resolve: { - alias: { - './node/net': path.resolve(import.meta.dirname, 'src', 'browser', 'net'), - } - } -}); - diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 84af30cb45b01..60569fb201fbe 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -22,7 +22,13 @@ "browser": "./dist/browser/configurationEditingMain", "scripts": { "compile": "gulp compile-extension:configuration-editing", - "watch": "gulp watch-extension:configuration-editing" + "watch": "gulp watch-extension:configuration-editing", + "compile-web": "npm-run-all2 -lp bundle-web typecheck-web", + "bundle-web": "node ./esbuild.browser.mts", + "typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit", + "watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web", + "watch-bundle-web": "node ./esbuild.browser.mts --watch", + "watch-typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit --watch" }, "dependencies": { "@octokit/rest": "^21.1.1", diff --git a/extensions/configuration-editing/tsconfig.browser.json b/extensions/configuration-editing/tsconfig.browser.json new file mode 100644 index 0000000000000..f609992ab5893 --- /dev/null +++ b/extensions/configuration-editing/tsconfig.browser.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ], + "files": [ + "./src/configurationEditingMain.ts" + ] +} diff --git a/extensions/emmet/.vscodeignore b/extensions/emmet/.vscodeignore index ccf478fb97f7b..c63ff6f6805d2 100644 --- a/extensions/emmet/.vscodeignore +++ b/extensions/emmet/.vscodeignore @@ -2,9 +2,8 @@ test/** test-workspace/** src/** out/** -tsconfig.json -extension.webpack.config.js -extension-browser.webpack.config.js +tsconfig*.json +esbuild* CONTRIBUTING.md cgmanifest.json package-lock.json diff --git a/extensions/emmet/esbuild.browser.mts b/extensions/emmet/esbuild.browser.mts new file mode 100644 index 0000000000000..5436791dde64d --- /dev/null +++ b/extensions/emmet/esbuild.browser.mts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'emmetBrowserMain': path.join(srcDir, 'browser', 'emmetBrowserMain.ts'), + }, + srcDir, + outdir: outDir, + additionalOptions: { + tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), + }, +}, process.argv); diff --git a/extensions/npm/extension-browser.webpack.config.js b/extensions/emmet/esbuild.mts similarity index 50% rename from extensions/npm/extension-browser.webpack.config.js rename to extensions/emmet/esbuild.mts index 6ec242a87a221..1bc372fa8c6d0 100644 --- a/extensions/npm/extension-browser.webpack.config.js +++ b/extensions/emmet/esbuild.mts @@ -2,22 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -const config = withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/npmBrowserMain.ts' - }, - output: { - filename: 'npmBrowserMain.js' - }, - resolve: { - fallback: { - 'child_process': false - } - } -}); +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'node'); -export default config; +run({ + platform: 'node', + entryPoints: { + 'emmetNodeMain': path.join(srcDir, 'node', 'emmetNodeMain.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 5b5d3748f87f6..c4e020a8d161e 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -474,8 +474,14 @@ } }, "scripts": { - "watch": "gulp watch-extension:emmet", "compile": "gulp compile-extension:emmet", + "watch": "gulp watch-extension:emmet", + "compile-web": "npm-run-all2 -lp bundle-web typecheck-web", + "bundle-web": "node ./esbuild.browser.mts", + "typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit", + "watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web", + "watch-bundle-web": "node ./esbuild.browser.mts --watch", + "watch-typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit --watch", "deps": "npm install @vscode/emmet-helper" }, "devDependencies": { diff --git a/extensions/emmet/tsconfig.browser.json b/extensions/emmet/tsconfig.browser.json new file mode 100644 index 0000000000000..781e4ba631c54 --- /dev/null +++ b/extensions/emmet/tsconfig.browser.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ], + "files": [ + "./src/browser/emmetBrowserMain.ts" + ] +} diff --git a/extensions/esbuild-extension-common.mts b/extensions/esbuild-extension-common.mts index 513656ae89fb3..e429554a639e0 100644 --- a/extensions/esbuild-extension-common.mts +++ b/extensions/esbuild-extension-common.mts @@ -16,17 +16,7 @@ type BuildOptions = Partial & { * Build the source code once using esbuild. */ async function build(options: BuildOptions, didBuild?: (outDir: string) => unknown): Promise { - await esbuild.build({ - bundle: true, - minify: true, - sourcemap: false, - format: 'cjs', - platform: 'node', - target: ['es2024'], - external: ['vscode'], - ...options, - }); - + await esbuild.build(options); await didBuild?.(options.outdir); } @@ -42,10 +32,44 @@ async function tryBuild(options: BuildOptions, didBuild?: (outDir: string) => un } interface RunConfig { - srcDir: string; - outdir: string; - entryPoints: string[] | Record | { in: string; out: string }[]; - additionalOptions?: Partial; + readonly platform: 'node' | 'browser'; + readonly srcDir: string; + readonly outdir: string; + readonly entryPoints: string[] | Record | { in: string; out: string }[]; + readonly additionalOptions?: Partial; +} + +function resolveOptions(config: RunConfig, outdir: string): BuildOptions { + const options: BuildOptions = { + platform: config.platform, + bundle: true, + minify: true, + sourcemap: true, + target: ['es2024'], + external: ['vscode'], + entryPoints: config.entryPoints, + outdir, + logOverride: { + 'import-is-undefined': 'error', + }, + ...(config.additionalOptions || {}), + }; + + if (config.platform === 'node') { + options.format = 'cjs'; + } else if (config.platform === 'browser') { + options.format = 'iife'; + options.alias = { + 'path': 'path-browserify', + }; + options.define = { + 'process.platform': JSON.stringify('web'), + 'process.env': JSON.stringify({}), + 'process.env.BROWSER_ENV': JSON.stringify('true'), + }; + } + + return options; } export async function run(config: RunConfig, args: string[], didBuild?: (outDir: string) => unknown): Promise { @@ -57,14 +81,7 @@ export async function run(config: RunConfig, args: string[], didBuild?: (outDir: outdir = path.join(outputRoot, outputDirName); } - const resolvedOptions: BuildOptions = { - entryPoints: config.entryPoints, - outdir, - logOverride: { - 'import-is-undefined': 'error', - }, - ...(config.additionalOptions || {}), - }; + const resolvedOptions = resolveOptions(config, outdir); const isWatch = args.indexOf('--watch') >= 0; if (isWatch) { diff --git a/extensions/grunt/.vscodeignore b/extensions/grunt/.vscodeignore index 698898bf9dfb1..e6cd7f0ed82e6 100644 --- a/extensions/grunt/.vscodeignore +++ b/extensions/grunt/.vscodeignore @@ -1,6 +1,6 @@ test/** src/** -tsconfig.json +tsconfig*.json out/** -extension.webpack.config.js +esbuild* package-lock.json diff --git a/extensions/emmet/extension-browser.webpack.config.js b/extensions/grunt/esbuild.mts similarity index 52% rename from extensions/emmet/extension-browser.webpack.config.js rename to extensions/grunt/esbuild.mts index ce7ea8d197b90..9be4332b6d832 100644 --- a/extensions/emmet/extension-browser.webpack.config.js +++ b/extensions/grunt/esbuild.mts @@ -2,16 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/browser/emmetBrowserMain.ts' - }, - output: { - filename: 'emmetBrowserMain.js' - } -}); +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); +run({ + platform: 'node', + entryPoints: { + 'main': path.join(srcDir, 'main.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/gulp/.vscodeignore b/extensions/gulp/.vscodeignore index 360fcfd1c990b..58e98fc51822d 100644 --- a/extensions/gulp/.vscodeignore +++ b/extensions/gulp/.vscodeignore @@ -1,5 +1,5 @@ src/** -tsconfig.json +tsconfig*.json out/** -extension.webpack.config.js +esbuild* package-lock.json diff --git a/extensions/emmet/extension.webpack.config.js b/extensions/gulp/esbuild.mts similarity index 52% rename from extensions/emmet/extension.webpack.config.js rename to extensions/gulp/esbuild.mts index 2c6094112e17b..9be4332b6d832 100644 --- a/extensions/emmet/extension.webpack.config.js +++ b/extensions/gulp/esbuild.mts @@ -2,18 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import path from 'path'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -import withDefaults from '../shared.webpack.config.mjs'; +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/node/emmetNodeMain.ts', +run({ + platform: 'node', + entryPoints: { + 'main': path.join(srcDir, 'main.ts'), }, - output: { - path: path.join(import.meta.dirname, 'dist', 'node'), - filename: 'emmetNodeMain.js' - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/gulp/extension.webpack.config.js b/extensions/gulp/extension.webpack.config.js deleted file mode 100644 index 1e221c2fa8500..0000000000000 --- a/extensions/gulp/extension.webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; - -export default withDefaults({ - context: import.meta.dirname, - entry: { - main: './src/main.ts', - }, - resolve: { - mainFields: ['module', 'main'] - } -}); diff --git a/extensions/jake/.vscodeignore b/extensions/jake/.vscodeignore index 360fcfd1c990b..58e98fc51822d 100644 --- a/extensions/jake/.vscodeignore +++ b/extensions/jake/.vscodeignore @@ -1,5 +1,5 @@ src/** -tsconfig.json +tsconfig*.json out/** -extension.webpack.config.js +esbuild* package-lock.json diff --git a/extensions/grunt/extension.webpack.config.js b/extensions/jake/esbuild.mts similarity index 52% rename from extensions/grunt/extension.webpack.config.js rename to extensions/jake/esbuild.mts index 1e221c2fa8500..9be4332b6d832 100644 --- a/extensions/grunt/extension.webpack.config.js +++ b/extensions/jake/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - main: './src/main.ts', +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'main': path.join(srcDir, 'main.ts'), }, - resolve: { - mainFields: ['module', 'main'] - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/jake/extension.webpack.config.js b/extensions/jake/extension.webpack.config.js deleted file mode 100644 index 1e221c2fa8500..0000000000000 --- a/extensions/jake/extension.webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; - -export default withDefaults({ - context: import.meta.dirname, - entry: { - main: './src/main.ts', - }, - resolve: { - mainFields: ['module', 'main'] - } -}); diff --git a/extensions/markdown-language-features/esbuild.browser.mts b/extensions/markdown-language-features/esbuild.browser.mts index ddf0c5a99dc4e..ece1769bcdc25 100644 --- a/extensions/markdown-language-features/esbuild.browser.mts +++ b/extensions/markdown-language-features/esbuild.browser.mts @@ -19,22 +19,13 @@ async function copyServerWorkerMain(outDir: string): Promise { } run({ + platform: 'browser', entryPoints: { 'extension': path.join(srcDir, 'extension.browser.ts'), }, srcDir, outdir: outDir, additionalOptions: { - platform: 'browser', - format: 'cjs', - alias: { - 'path': 'path-browserify', - }, - define: { - 'process.platform': JSON.stringify('web'), - 'process.env': JSON.stringify({}), - 'process.env.BROWSER_ENV': JSON.stringify('true'), - }, tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), }, }, process.argv, copyServerWorkerMain); diff --git a/extensions/markdown-language-features/esbuild.mts b/extensions/markdown-language-features/esbuild.mts index a1cf6eb5fa8de..2a7eda8c18328 100644 --- a/extensions/markdown-language-features/esbuild.mts +++ b/extensions/markdown-language-features/esbuild.mts @@ -19,6 +19,7 @@ async function copyServerWorkerMain(outDir: string): Promise { } run({ + platform: 'node', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, diff --git a/extensions/markdown-math/esbuild.browser.mts b/extensions/markdown-math/esbuild.browser.mts index e3fa7792d056d..9ea4d5f68401e 100644 --- a/extensions/markdown-math/esbuild.browser.mts +++ b/extensions/markdown-math/esbuild.browser.mts @@ -9,18 +9,13 @@ const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist', 'browser'); run({ + platform: 'browser', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, srcDir, outdir: outDir, additionalOptions: { - platform: 'browser', - format: 'cjs', - define: { - 'process.platform': JSON.stringify('web'), - 'process.env': JSON.stringify({}), - 'process.env.BROWSER_ENV': JSON.stringify('true'), - }, + tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), }, }, process.argv); diff --git a/extensions/markdown-math/esbuild.mts b/extensions/markdown-math/esbuild.mts index 5fafb57ab75a8..2b75ca703da06 100644 --- a/extensions/markdown-math/esbuild.mts +++ b/extensions/markdown-math/esbuild.mts @@ -9,6 +9,7 @@ const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist'); run({ + platform: 'node', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, diff --git a/extensions/media-preview/esbuild.browser.mts b/extensions/media-preview/esbuild.browser.mts index e3fa7792d056d..9ea4d5f68401e 100644 --- a/extensions/media-preview/esbuild.browser.mts +++ b/extensions/media-preview/esbuild.browser.mts @@ -9,18 +9,13 @@ const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist', 'browser'); run({ + platform: 'browser', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, srcDir, outdir: outDir, additionalOptions: { - platform: 'browser', - format: 'cjs', - define: { - 'process.platform': JSON.stringify('web'), - 'process.env': JSON.stringify({}), - 'process.env.BROWSER_ENV': JSON.stringify('true'), - }, + tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), }, }, process.argv); diff --git a/extensions/media-preview/esbuild.mts b/extensions/media-preview/esbuild.mts index 5fafb57ab75a8..2b75ca703da06 100644 --- a/extensions/media-preview/esbuild.mts +++ b/extensions/media-preview/esbuild.mts @@ -9,6 +9,7 @@ const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist'); run({ + platform: 'node', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, diff --git a/extensions/mermaid-chat-features/esbuild.browser.mts b/extensions/mermaid-chat-features/esbuild.browser.mts index e3fa7792d056d..9ea4d5f68401e 100644 --- a/extensions/mermaid-chat-features/esbuild.browser.mts +++ b/extensions/mermaid-chat-features/esbuild.browser.mts @@ -9,18 +9,13 @@ const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist', 'browser'); run({ + platform: 'browser', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, srcDir, outdir: outDir, additionalOptions: { - platform: 'browser', - format: 'cjs', - define: { - 'process.platform': JSON.stringify('web'), - 'process.env': JSON.stringify({}), - 'process.env.BROWSER_ENV': JSON.stringify('true'), - }, + tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), }, }, process.argv); diff --git a/extensions/mermaid-chat-features/esbuild.mts b/extensions/mermaid-chat-features/esbuild.mts index 5fafb57ab75a8..2b75ca703da06 100644 --- a/extensions/mermaid-chat-features/esbuild.mts +++ b/extensions/mermaid-chat-features/esbuild.mts @@ -9,6 +9,7 @@ const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist'); run({ + platform: 'node', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, diff --git a/extensions/npm/.vscodeignore b/extensions/npm/.vscodeignore index f05a79416be02..7e9dd51ede2aa 100644 --- a/extensions/npm/.vscodeignore +++ b/extensions/npm/.vscodeignore @@ -1,7 +1,6 @@ src/** out/** -tsconfig.json +tsconfig*.json .vscode/** -extension.webpack.config.js -extension-browser.webpack.config.js +esbuild* package-lock.json diff --git a/extensions/npm/esbuild.browser.mts b/extensions/npm/esbuild.browser.mts new file mode 100644 index 0000000000000..852700edd07a5 --- /dev/null +++ b/extensions/npm/esbuild.browser.mts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'npmBrowserMain': path.join(srcDir, 'npmBrowserMain.ts'), + }, + srcDir, + outdir: outDir, + additionalOptions: { + tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), + external: [ + 'vscode', + 'child_process', + ] + }, +}, process.argv); diff --git a/extensions/configuration-editing/extension.webpack.config.js b/extensions/npm/esbuild.mts similarity index 51% rename from extensions/configuration-editing/extension.webpack.config.js rename to extensions/npm/esbuild.mts index 519fc2e359f44..be92f45b26cc7 100644 --- a/extensions/configuration-editing/extension.webpack.config.js +++ b/extensions/npm/esbuild.mts @@ -2,18 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/configurationEditingMain.ts', - }, - output: { - filename: 'configurationEditingMain.js' +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'npmMain': path.join(srcDir, 'npmMain.ts'), }, - resolve: { - mainFields: ['module', 'main'] - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/npm/extension.webpack.config.js b/extensions/npm/extension.webpack.config.js deleted file mode 100644 index 0dcad6f8b143f..0000000000000 --- a/extensions/npm/extension.webpack.config.js +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; - -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/npmMain.ts', - }, - output: { - filename: 'npmMain.js', - }, - resolve: { - mainFields: ['module', 'main'], - extensions: ['.ts', '.js'] // support ts-files and js-files - } -}); diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 9a67c3a83ecfa..bba6a23b8ac99 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -18,7 +18,13 @@ ], "scripts": { "compile": "npx gulp compile-extension:npm", - "watch": "npx gulp watch-extension:npm" + "watch": "npx gulp watch-extension:npm", + "compile-web": "npm-run-all2 -lp bundle-web typecheck-web", + "bundle-web": "node ./esbuild.browser.mts", + "typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit", + "watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web", + "watch-bundle-web": "node ./esbuild.browser.mts --watch", + "watch-typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit --watch" }, "dependencies": { "find-up": "^5.0.0", diff --git a/extensions/npm/src/features/packageJSONContribution.ts b/extensions/npm/src/features/packageJSONContribution.ts index 999f39664f1c2..d90a6b189f533 100644 --- a/extensions/npm/src/features/packageJSONContribution.ts +++ b/extensions/npm/src/features/packageJSONContribution.ts @@ -8,7 +8,7 @@ import { IJSONContribution, ISuggestionsCollector } from './jsonContributions'; import { XHRRequest } from 'request-light'; import { Location } from 'jsonc-parser'; -import * as cp from 'child_process'; +import type * as cp from 'child_process'; import { dirname } from 'path'; import { fromNow } from './date'; @@ -283,7 +283,8 @@ export class PackageJSONContribution implements IJSONContribution { return info; } - private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise { + private async npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise { + const cp = await import('child_process'); return new Promise((resolve, _reject) => { const args = ['view', '--json', '--', pack, 'description', 'dist-tags.latest', 'homepage', 'version', 'time']; const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined; diff --git a/extensions/npm/tsconfig.browser.json b/extensions/npm/tsconfig.browser.json new file mode 100644 index 0000000000000..4e9b89120f863 --- /dev/null +++ b/extensions/npm/tsconfig.browser.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ], + "files": [ + "./src/npmBrowserMain.ts" + ] +} From 37cdd0eb438e9c6b6ce7c2132af856a9eaffa81f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:43:36 -0800 Subject: [PATCH 04/16] Fixing errors --- .../markdown-language-features/package-lock.json | 12 ++++++------ extensions/markdown-language-features/package.json | 2 +- .../markdown-language-features/src/markdownEngine.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index 0ba6fbad3914f..7d8c47eabc944 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -25,7 +25,7 @@ "devDependencies": { "@types/dompurify": "^3.0.5", "@types/lodash.throttle": "^4.1.9", - "@types/markdown-it": "14.1.0", + "@types/markdown-it": "^14.1.2", "@types/node": "22.x", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", @@ -195,14 +195,14 @@ } }, "node_modules/@types/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-B/o5iMXxpIAl1+792pO4ryhrNNiAus9HhrTs+Gt2m+6SIdEUEvyOIs0iqDNjTSRgbelWvyeek3UxfYYOKDaWfA==", + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "node_modules/@types/mdurl": { diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 9f89c72836e23..c242dceffd85d 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -789,7 +789,7 @@ "devDependencies": { "@types/dompurify": "^3.0.5", "@types/lodash.throttle": "^4.1.9", - "@types/markdown-it": "14.1.0", + "@types/markdown-it": "^14.1.2", "@types/node": "22.x", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 8e10d72c7303d..0f4c7eb671792 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type MarkdownIt = require('markdown-it'); +import type MarkdownIt from 'markdown-it'; import * as vscode from 'vscode'; import { ILogger } from './logging'; import { MarkdownContributionProvider } from './markdownExtensions'; From 97384371fadaf8e26499071ce0db9ee4a7d05b6f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:14:56 -0800 Subject: [PATCH 05/16] Fixing error reporting and resolution of jsonc --- build/lib/tsgo.ts | 45 +++++++------------------ extensions/esbuild-extension-common.mts | 1 + 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/build/lib/tsgo.ts b/build/lib/tsgo.ts index 421f4c1cc1b17..7f00c1816b56f 100644 --- a/build/lib/tsgo.ts +++ b/build/lib/tsgo.ts @@ -14,8 +14,8 @@ const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'; const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; export function spawnTsgo(projectPath: string, config: { taskName: string; noEmit?: boolean }, onComplete?: () => Promise | void): Promise { - function reporter(stdError: string) { - const matches = (stdError || '').match(/^error \w+: (.+)?/g); + function runReporter(stdError: string) { + const matches = (stdError || '').match(/error \w+: (.+)?/g); fancyLog(`Finished ${ansiColors.green(config.taskName)} ${projectPath} with ${matches ? matches.length : 0} errors.`); for (const match of matches || []) { fancyLog.error(match); @@ -34,29 +34,14 @@ export function spawnTsgo(projectPath: string, config: { taskName: string; noEmi shell: true }); - let buffer = ''; - const handleLine = (line: string) => { - const trimmed = line.replace(ansiRegex, '').trim(); - if (!trimmed) { - return; - } - if (/Starting compilation|File change detected/i.test(trimmed)) { - return; - } - if (/Compilation complete/i.test(trimmed)) { - return; - } - - reporter(trimmed); - }; - const handleData = (data: Buffer) => { - buffer += data.toString('utf8'); - const lines = buffer.split(/\r?\n/); - buffer = lines.pop() ?? ''; - for (const line of lines) { - handleLine(line); - } + const lines = data.toString() + .split(/\r?\n/) + .map(line => line.replace(ansiRegex, '').trim()) + .filter(line => line.length > 0) + .filter(line => !/Starting compilation|File change detected|Compilation complete/i.test(line)); + + runReporter(lines.join('\n')); }; child.stdout?.on('data', handleData); @@ -64,11 +49,6 @@ export function spawnTsgo(projectPath: string, config: { taskName: string; noEmi return new Promise((resolve, reject) => { child.on('exit', code => { - if (buffer.trim()) { - handleLine(buffer); - buffer = ''; - } - if (code === 0) { Promise.resolve(onComplete?.()).then(() => resolve(), reject); } else { @@ -84,12 +64,11 @@ export function spawnTsgo(projectPath: string, config: { taskName: string; noEmi export function createTsgoStream(projectPath: string, config: { taskName: string; noEmit?: boolean }, onComplete?: () => Promise | void): NodeJS.ReadWriteStream { const stream = es.through(); + spawnTsgo(projectPath, config, onComplete).then(() => { stream.emit('end'); - }).catch(() => { - // Errors are already reported by spawnTsgo via the reporter. - // Don't emit 'error' on the stream as that would exit the watch process. - stream.emit('end'); + }).catch(err => { + stream.emit('error', err); }); return stream; diff --git a/extensions/esbuild-extension-common.mts b/extensions/esbuild-extension-common.mts index e429554a639e0..84eb4d02ec734 100644 --- a/extensions/esbuild-extension-common.mts +++ b/extensions/esbuild-extension-common.mts @@ -47,6 +47,7 @@ function resolveOptions(config: RunConfig, outdir: string): BuildOptions { sourcemap: true, target: ['es2024'], external: ['vscode'], + mainFields: ['module', 'main'], entryPoints: config.entryPoints, outdir, logOverride: { From e371f136a11126e19ddf0b384765652a1a148b9a Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Thu, 12 Feb 2026 15:01:33 -0800 Subject: [PATCH 06/16] add support for fetching active chat session (#294992) * add support for fetching active chat session * refactor: rename activeChatSession to activeChatPanelSession and update related events --- .../common/extensionsApiProposals.ts | 2 +- .../api/browser/mainThreadChatAgents2.ts | 3 +++ .../workbench/api/common/extHost.api.impl.ts | 8 ++++++++ .../workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostChatAgents2.ts | 19 +++++++++++++++++++ src/vs/workbench/contrib/chat/browser/chat.ts | 5 +++++ .../chat/browser/widget/chatWidgetService.ts | 4 ++++ .../test/browser/widget/mockChatWidget.ts | 1 + .../test/browser/workbenchTestServices.ts | 1 + ...scode.proposed.chatParticipantPrivate.d.ts | 15 ++++++++++++++- 10 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index b6a71de582b39..c4cc744d7e430 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -56,7 +56,7 @@ const _allApiProposals = { }, chatParticipantPrivate: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts', - version: 13 + version: 14 }, chatPromptFiles: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts', diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 9aeea40f6d52b..06341d03c02be 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -145,6 +145,9 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._register(this._chatService.onDidReceiveQuestionCarouselAnswer(e => { this._proxy.$handleQuestionCarouselAnswer(e.requestId, e.resolveId, e.answers); })); + this._register(this._chatWidgetService.onDidChangeFocusedWidget(widget => { + this._proxy.$acceptActiveChatSession(widget?.viewModel?.sessionResource); + })); } $unregisterAgent(handle: number): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d41aab9b5d692..b8e9775191ade 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1044,6 +1044,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatStatusItem'); return extHostChatStatus.createChatStatusItem(extension, id); }, + get activeChatPanelSessionResource() { + checkProposedApiEnabled(extension, 'chatParticipantPrivate'); + return extHostChatAgents2.activeChatPanelSessionResource; + }, + onDidChangeActiveChatPanelSessionResource: (listeners, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'chatParticipantPrivate'); + return _asExtensionEvent(extHostChatAgents2.onDidChangeActiveChatPanelSessionResource)(listeners, thisArgs, disposables); + }, }; // namespace: workspace diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 566fcf1c24031..c596dbbf2a61f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1476,6 +1476,7 @@ export interface ExtHostChatAgentsShape2 { $providePromptFiles(handle: number, type: PromptsType, context: IPromptFileContext, token: CancellationToken): Promise[] | undefined>; $setRequestTools(requestId: string, tools: UserSelectedTools): void; $setYieldRequested(requestId: string): void; + $acceptActiveChatSession(sessionResource: UriComponents | undefined): void; } export interface IChatParticipantMetadata { participant: string; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 4df866f871df4..4cc0bc3cf550d 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -487,6 +487,15 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS private readonly _onDidDisposeChatSession = this._register(new Emitter()); readonly onDidDisposeChatSession = this._onDidDisposeChatSession.event; + private _activeChatPanelSessionResource: URI | undefined; + + private readonly _onDidChangeActiveChatPanelSessionResource = this._register(new Emitter()); + readonly onDidChangeActiveChatPanelSessionResource = this._onDidChangeActiveChatPanelSessionResource.event; + + get activeChatPanelSessionResource(): URI | undefined { + return this._activeChatPanelSessionResource; + } + constructor( mainContext: IMainContext, private readonly _logService: ILogService, @@ -877,6 +886,16 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS } } + $acceptActiveChatSession(sessionResourceDto: UriComponents | undefined): void { + const sessionResource = sessionResourceDto ? URI.revive(sessionResourceDto) : undefined; + if (this._activeChatPanelSessionResource?.toString() === sessionResource?.toString()) { + return; + } + + this._activeChatPanelSessionResource = sessionResource; + this._onDidChangeActiveChatPanelSessionResource.fire(sessionResource); + } + async $provideFollowups(requestDto: Dto, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index fc4423e96d12e..1cace4dc13431 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -121,6 +121,11 @@ export interface IChatWidgetService { */ readonly onDidBackgroundSession: Event; + /** + * Fires when the focused chat widget changes. + */ + readonly onDidChangeFocusedWidget: Event; + /** * Reveals the widget, focusing its input unless `preserveFocus` is true. */ diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidgetService.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidgetService.ts index aeee92322a3d1..9e5ce02f368c8 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidgetService.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidgetService.ts @@ -33,6 +33,9 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService private readonly _onDidBackgroundSession = this._register(new Emitter()); readonly onDidBackgroundSession = this._onDidBackgroundSession.event; + private readonly _onDidChangeFocusedWidget = this._register(new Emitter()); + readonly onDidChangeFocusedWidget = this._onDidChangeFocusedWidget.event; + constructor( @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, @IViewsService private readonly viewsService: IViewsService, @@ -218,6 +221,7 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService } this._lastFocusedWidget = widget; + this._onDidChangeFocusedWidget.fire(widget); } register(newWidget: IChatWidget): IDisposable { diff --git a/src/vs/workbench/contrib/chat/test/browser/widget/mockChatWidget.ts b/src/vs/workbench/contrib/chat/test/browser/widget/mockChatWidget.ts index 2014bd9b9d442..7e7d3336a17a1 100644 --- a/src/vs/workbench/contrib/chat/test/browser/widget/mockChatWidget.ts +++ b/src/vs/workbench/contrib/chat/test/browser/widget/mockChatWidget.ts @@ -12,6 +12,7 @@ import { ChatAgentLocation } from '../../../common/constants.js'; export class MockChatWidgetService implements IChatWidgetService { readonly onDidAddWidget: Event = Event.None; readonly onDidBackgroundSession: Event = Event.None; + readonly onDidChangeFocusedWidget: Event = Event.None; readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 5f9844e71e8eb..0684952209a18 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -2140,6 +2140,7 @@ export class TestChatWidgetService implements IChatWidgetService { onDidAddWidget = Event.None; onDidBackgroundSession = Event.None; + onDidChangeFocusedWidget = Event.None; async reveal(widget: IChatWidget, preserveFocus?: boolean): Promise { return false; } async revealWidget(preserveFocus?: boolean): Promise { return undefined; } diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index ad37a1404ceea..63f9ec248841a 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 13 +// version: 14 declare module 'vscode' { @@ -335,6 +335,19 @@ declare module 'vscode' { export const onDidDisposeChatSession: Event; } + export namespace window { + /** + * The resource URI of the currently active chat panel session, + * or `undefined` if there is no active chat panel session. + */ + export const activeChatPanelSessionResource: Uri | undefined; + + /** + * An event that fires when the active chat panel session resource changes. + */ + export const onDidChangeActiveChatPanelSessionResource: Event; + } + // #endregion // #region ChatErrorDetailsWithConfirmation From dfc43c73a6522f7305f1882c14531481d4b1bc67 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:07:36 -0800 Subject: [PATCH 07/16] Adopt unified js/ts setting for suggest Fixes #292934 --- .../typescript-language-features/package.json | 140 ++++++++++++++++++ .../package.nls.json | 13 ++ .../src/languageFeatures/completions.ts | 11 +- .../fileConfigurationManager.ts | 16 +- .../src/languageFeatures/jsDocCompletions.ts | 3 +- .../src/languageProvider.ts | 3 +- .../src/test/smoke/completions.test.ts | 10 +- .../src/test/testUtils.ts | 2 +- 8 files changed, 178 insertions(+), 20 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 03d98f3efe77a..e5d4826e8e8c3 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -418,16 +418,28 @@ "markdownDescription": "%typescript.locale%", "scope": "window" }, + "js/ts.suggestionActions.enabled": { + "type": "boolean", + "default": true, + "description": "%configuration.suggestionActions.enabled%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggestionActions.enabled": { "type": "boolean", "default": true, "description": "%javascript.suggestionActions.enabled%", + "markdownDeprecationMessage": "%configuration.suggestionActions.enabled.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.suggestionActions.enabled": { "type": "boolean", "default": true, "description": "%typescript.suggestionActions.enabled%", + "markdownDeprecationMessage": "%configuration.suggestionActions.enabled.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.updateImportsOnFileMove.enabled": { @@ -531,124 +543,252 @@ "title": "%configuration.suggest%", "order": 21, "properties": { + "js/ts.suggest.enabled": { + "type": "boolean", + "default": true, + "description": "%typescript.suggest.enabled%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.enabled": { "type": "boolean", "default": true, "description": "%typescript.suggest.enabled%", + "markdownDeprecationMessage": "%configuration.suggest.enabled.unifiedDeprecationMessage%", "scope": "language-overridable" }, "typescript.suggest.enabled": { "type": "boolean", "default": true, "description": "%typescript.suggest.enabled%", + "markdownDeprecationMessage": "%configuration.suggest.enabled.unifiedDeprecationMessage%", "scope": "language-overridable" }, + "js/ts.suggest.autoImports": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.autoImports%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.autoImports": { "type": "boolean", "default": true, "description": "%configuration.suggest.autoImports%", + "markdownDeprecationMessage": "%configuration.suggest.autoImports.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.suggest.autoImports": { "type": "boolean", "default": true, "description": "%configuration.suggest.autoImports%", + "markdownDeprecationMessage": "%configuration.suggest.autoImports.unifiedDeprecationMessage%", "scope": "resource" }, + "js/ts.suggest.names": { + "type": "boolean", + "default": true, + "markdownDescription": "%configuration.suggest.names%", + "scope": "language-overridable", + "tags": [ + "JavaScript" + ] + }, "javascript.suggest.names": { "type": "boolean", "default": true, "markdownDescription": "%configuration.suggest.names%", + "markdownDeprecationMessage": "%configuration.suggest.names.unifiedDeprecationMessage%", "scope": "resource" }, + "js/ts.suggest.completeFunctionCalls": { + "type": "boolean", + "default": false, + "description": "%configuration.suggest.completeFunctionCalls%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.completeFunctionCalls": { "type": "boolean", "default": false, "description": "%configuration.suggest.completeFunctionCalls%", + "markdownDeprecationMessage": "%configuration.suggest.completeFunctionCalls.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.suggest.completeFunctionCalls": { "type": "boolean", "default": false, "description": "%configuration.suggest.completeFunctionCalls%", + "markdownDeprecationMessage": "%configuration.suggest.completeFunctionCalls.unifiedDeprecationMessage%", "scope": "resource" }, + "js/ts.suggest.paths": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.paths%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.paths": { "type": "boolean", "default": true, "description": "%configuration.suggest.paths%", + "markdownDeprecationMessage": "%configuration.suggest.paths.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.suggest.paths": { "type": "boolean", "default": true, "description": "%configuration.suggest.paths%", + "markdownDeprecationMessage": "%configuration.suggest.paths.unifiedDeprecationMessage%", "scope": "resource" }, + "js/ts.suggest.completeJSDocs": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.completeJSDocs%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.completeJSDocs": { "type": "boolean", "default": true, "description": "%configuration.suggest.completeJSDocs%", + "markdownDeprecationMessage": "%configuration.suggest.completeJSDocs.unifiedDeprecationMessage%", "scope": "language-overridable" }, "typescript.suggest.completeJSDocs": { "type": "boolean", "default": true, "description": "%configuration.suggest.completeJSDocs%", + "markdownDeprecationMessage": "%configuration.suggest.completeJSDocs.unifiedDeprecationMessage%", "scope": "language-overridable" }, + "js/ts.suggest.jsdoc.generateReturns": { + "type": "boolean", + "default": true, + "markdownDescription": "%configuration.suggest.jsdoc.generateReturns%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.jsdoc.generateReturns": { "type": "boolean", "default": true, "markdownDescription": "%configuration.suggest.jsdoc.generateReturns%", + "markdownDeprecationMessage": "%configuration.suggest.jsdoc.generateReturns.unifiedDeprecationMessage%", "scope": "language-overridable" }, "typescript.suggest.jsdoc.generateReturns": { "type": "boolean", "default": true, "markdownDescription": "%configuration.suggest.jsdoc.generateReturns%", + "markdownDeprecationMessage": "%configuration.suggest.jsdoc.generateReturns.unifiedDeprecationMessage%", "scope": "language-overridable" }, + "js/ts.suggest.includeAutomaticOptionalChainCompletions": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.includeAutomaticOptionalChainCompletions": { "type": "boolean", "default": true, "description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%", + "markdownDeprecationMessage": "%configuration.suggest.includeAutomaticOptionalChainCompletions.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.suggest.includeAutomaticOptionalChainCompletions": { "type": "boolean", "default": true, "description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%", + "markdownDeprecationMessage": "%configuration.suggest.includeAutomaticOptionalChainCompletions.unifiedDeprecationMessage%", "scope": "resource" }, + "js/ts.suggest.includeCompletionsForImportStatements": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.includeCompletionsForImportStatements%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.includeCompletionsForImportStatements": { "type": "boolean", "default": true, "description": "%configuration.suggest.includeCompletionsForImportStatements%", + "markdownDeprecationMessage": "%configuration.suggest.includeCompletionsForImportStatements.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.suggest.includeCompletionsForImportStatements": { "type": "boolean", "default": true, "description": "%configuration.suggest.includeCompletionsForImportStatements%", + "markdownDeprecationMessage": "%configuration.suggest.includeCompletionsForImportStatements.unifiedDeprecationMessage%", "scope": "resource" }, + "js/ts.suggest.classMemberSnippets.enabled": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.classMemberSnippets.enabled%", + "scope": "language-overridable", + "tags": [ + "JavaScript", + "TypeScript" + ] + }, "javascript.suggest.classMemberSnippets.enabled": { "type": "boolean", "default": true, "description": "%configuration.suggest.classMemberSnippets.enabled%", + "markdownDeprecationMessage": "%configuration.suggest.classMemberSnippets.enabled.unifiedDeprecationMessage%", "scope": "resource" }, "typescript.suggest.classMemberSnippets.enabled": { "type": "boolean", "default": true, "description": "%configuration.suggest.classMemberSnippets.enabled%", + "markdownDeprecationMessage": "%configuration.suggest.classMemberSnippets.enabled.unifiedDeprecationMessage%", "scope": "resource" }, + "js/ts.suggest.objectLiteralMethodSnippets.enabled": { + "type": "boolean", + "default": true, + "description": "%configuration.suggest.objectLiteralMethodSnippets.enabled%", + "scope": "language-overridable", + "tags": [ + "TypeScript" + ] + }, "typescript.suggest.objectLiteralMethodSnippets.enabled": { "type": "boolean", "default": true, "description": "%configuration.suggest.objectLiteralMethodSnippets.enabled%", + "markdownDeprecationMessage": "%configuration.suggest.objectLiteralMethodSnippets.enabled.unifiedDeprecationMessage%", "scope": "resource" } } diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index cdbce28c5a970..cb65d0c94177d 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -11,8 +11,11 @@ "configuration.inlayHints": "Inlay Hints", "configuration.server": "TS Server", "configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.", + "configuration.suggest.completeFunctionCalls.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.completeFunctionCalls#` instead.", "configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires strict null checks to be enabled.", + "configuration.suggest.includeAutomaticOptionalChainCompletions.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.includeAutomaticOptionalChainCompletions#` instead.", "configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements.", + "configuration.suggest.includeCompletionsForImportStatements.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.includeCompletionsForImportStatements#` instead.", "typescript.useTsgo": "Disables TypeScript and JavaScript language features to allow usage of the TypeScript Go experimental extension. Requires TypeScript Go to be installed and configured. Requires reloading extensions after changing this setting.", "typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.", "typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.", @@ -66,6 +69,7 @@ "typescript.npm": "Specifies the path to the npm executable used for [Automatic Type Acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition).", "typescript.check.npmIsInstalled": "Check if npm is installed for [Automatic Type Acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition).", "configuration.suggest.names": "Enable/disable including unique names from the file in JavaScript suggestions. Note that name suggestions are always disabled in JavaScript code that is semantically checked using `@ts-check` or `checkJs`.", + "configuration.suggest.names.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.names#` instead.", "typescript.tsc.autoDetect": "Controls auto detection of tsc tasks.", "typescript.tsc.autoDetect.off": "Disable this feature.", "typescript.tsc.autoDetect.on": "Create both build and watch tasks.", @@ -75,6 +79,7 @@ "typescript.problemMatchers.tsgo-watch.label": "TypeScript problems (watch mode)", "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)", "configuration.suggest.paths": "Enable/disable suggestions for paths in import statements and require calls.", + "configuration.suggest.paths.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.paths#` instead.", "configuration.tsserver.useSeparateSyntaxServer": "Enable/disable spawning a separate TypeScript server that can more quickly respond to syntax related operations, such as calculating folding or computing document symbols.", "configuration.tsserver.useSyntaxServer": "Controls if TypeScript launches a dedicated server to more quickly handle syntax related operations, such as computing code folding.", "configuration.tsserver.useSyntaxServer.always": "Use a lighter weight syntax server to handle all IntelliSense operations. This disables project-wide features including auto-imports, cross-file completions, and go to definition for symbols in other files. Only use this for very large projects where performance is critical.", @@ -92,7 +97,9 @@ "configuration.implicitProjectConfig.strictFunctionTypes": "Enable/disable [strict function types](https://www.typescriptlang.org/tsconfig#strictFunctionTypes) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", "configuration.implicitProjectConfig.strict": "Enable/disable [strict mode](https://www.typescriptlang.org/tsconfig#strict) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", "configuration.suggest.jsdoc.generateReturns": "Enable/disable generating `@returns` annotations for JSDoc templates.", + "configuration.suggest.jsdoc.generateReturns.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.jsdoc.generateReturns#` instead.", "configuration.suggest.autoImports": "Enable/disable auto import suggestions.", + "configuration.suggest.autoImports.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.autoImports#` instead.", "configuration.preferGoToSourceDefinition": "Makes `Go to Definition` avoid type declaration files when possible by triggering `Go to Source Definition` instead. This allows `Go to Source Definition` to be triggered with the mouse gesture.", "inlayHints.parameterNames.none": "Disable parameter name hints.", "inlayHints.parameterNames.literals": "Enable parameter name hints only for literal arguments.", @@ -146,6 +153,8 @@ "taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.", "javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor.", "typescript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for TypeScript files in the editor.", + "configuration.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript and TypeScript files in the editor.", + "configuration.suggestionActions.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggestionActions.enabled#` instead.", "typescript.preferences.quoteStyle": "Preferred quote style to use for Quick Fixes.", "typescript.preferences.quoteStyle.single": "Always use single quotes: `'`", "typescript.preferences.quoteStyle.double": "Always use double quotes: `\"`", @@ -180,7 +189,9 @@ "typescript.updateImportsOnFileMove.enabled.never": "Never rename paths and don't prompt.", "typescript.autoClosingTags": "Enable/disable automatic closing of JSX tags.", "typescript.suggest.enabled": "Enable/disable autocomplete suggestions.", + "configuration.suggest.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.enabled#` instead.", "configuration.suggest.completeJSDocs": "Enable/disable suggestion to complete JSDoc comments.", + "configuration.suggest.completeJSDocs.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.completeJSDocs#` instead.", "configuration.tsserver.useVsCodeWatcher": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", "configuration.tsserver.watchOptions": "Configure which watching strategies should be used to keep track of files and directories.", "configuration.tsserver.watchOptions.vscode": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", @@ -232,7 +243,9 @@ "typescript.findAllFileReferences": "Find File References", "typescript.goToSourceDefinition": "Go to Source Definition", "configuration.suggest.classMemberSnippets.enabled": "Enable/disable snippet completions for class members.", + "configuration.suggest.classMemberSnippets.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.classMemberSnippets.enabled#` instead.", "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals.", + "configuration.suggest.objectLiteralMethodSnippets.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.objectLiteralMethodSnippets.enabled#` instead.", "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that VS Code is running in a trusted context.", "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors on web even when project wide IntelliSense is enabled. This is always on when project wide IntelliSense is not enabled or available. See `#typescript.tsserver.web.projectWideIntellisense.enabled#`", "configuration.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web. This enables IntelliSense for imported packages. Requires `#typescript.tsserver.web.projectWideIntellisense.enabled#`. Currently not supported for Safari.", diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 7ac732d3f867f..a6378deaece6d 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -16,6 +16,7 @@ import * as typeConverters from '../typeConverters'; import { ClientCapability, ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; import TypingsStatus from '../ui/typingsStatus'; import { nulToken } from '../utils/cancellation'; +import { readUnifiedConfig } from '../utils/configuration'; import FileConfigurationManager from './fileConfigurationManager'; import { applyCodeAction } from './util/codeAction'; import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; @@ -670,10 +671,10 @@ namespace CompletionConfiguration { ): CompletionConfiguration { const config = vscode.workspace.getConfiguration(modeId, resource); return { - completeFunctionCalls: config.get(CompletionConfiguration.completeFunctionCalls, false), - pathSuggestions: config.get(CompletionConfiguration.pathSuggestions, true), - autoImportSuggestions: config.get(CompletionConfiguration.autoImportSuggestions, true), - nameSuggestions: config.get(CompletionConfiguration.nameSuggestions, true), + completeFunctionCalls: readUnifiedConfig(CompletionConfiguration.completeFunctionCalls, false, { scope: resource, fallbackSection: modeId }), + pathSuggestions: readUnifiedConfig(CompletionConfiguration.pathSuggestions, true, { scope: resource, fallbackSection: modeId }), + autoImportSuggestions: readUnifiedConfig(CompletionConfiguration.autoImportSuggestions, true, { scope: resource, fallbackSection: modeId }), + nameSuggestions: readUnifiedConfig(CompletionConfiguration.nameSuggestions, true, { scope: resource, fallbackSection: modeId }), importStatementSuggestions: config.get(CompletionConfiguration.importStatementSuggestions, true), }; } @@ -703,7 +704,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< token: vscode.CancellationToken, context: vscode.CompletionContext ): Promise | undefined> { - if (!vscode.workspace.getConfiguration(this.language.id, document).get('suggest.enabled')) { + if (!readUnifiedConfig('suggest.enabled', true, { scope: document, fallbackSection: this.language.id })) { return undefined; } diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index f6ede823fcdde..a79d086344ab3 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -182,6 +182,8 @@ export default class FileConfigurationManager extends Disposable { isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences', document); + const fallbackSection = isTypeScriptDocument(document) ? 'typescript' : 'javascript'; + const preferences: Proto.UserPreferences = { ...config.get('unstable'), quotePreference: this.getQuoteStylePreference(preferencesConfig), @@ -191,13 +193,13 @@ export default class FileConfigurationManager extends Disposable { allowTextChangesInNewFiles: document.uri.scheme === fileSchemes.file, providePrefixAndSuffixTextForRename: preferencesConfig.get('useAliasesForRenames', true), allowRenameOfImportPath: true, - includeAutomaticOptionalChainCompletions: config.get('suggest.includeAutomaticOptionalChainCompletions', true), + includeAutomaticOptionalChainCompletions: readUnifiedConfig('suggest.includeAutomaticOptionalChainCompletions', true, { scope: document, fallbackSection }), provideRefactorNotApplicableReason: true, - generateReturnInDocTemplate: config.get('suggest.jsdoc.generateReturns', true), - includeCompletionsForImportStatements: config.get('suggest.includeCompletionsForImportStatements', true), + generateReturnInDocTemplate: readUnifiedConfig('suggest.jsdoc.generateReturns', true, { scope: document, fallbackSection }), + includeCompletionsForImportStatements: readUnifiedConfig('suggest.includeCompletionsForImportStatements', true, { scope: document, fallbackSection }), includeCompletionsWithSnippetText: true, - includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true), - includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), + includeCompletionsWithClassMemberSnippets: readUnifiedConfig('suggest.classMemberSnippets.enabled', true, { scope: document, fallbackSection }), + includeCompletionsWithObjectLiteralMethodSnippets: readUnifiedConfig('suggest.objectLiteralMethodSnippets.enabled', true, { scope: document, fallbackSection }), autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri), autoImportSpecifierExcludeRegexes: preferencesConfig.get('autoImportSpecifierExcludeRegexes'), preferTypeOnlyAutoImports: preferencesConfig.get('preferTypeOnlyAutoImports', false), @@ -206,8 +208,8 @@ export default class FileConfigurationManager extends Disposable { displayPartsForJSDoc: true, disableLineTextInReferences: true, interactiveInlayHints: true, - includeCompletionsForModuleExports: config.get('suggest.autoImports'), - ...getInlayHintsPreferences(document, isTypeScriptDocument(document) ? 'typescript' : 'javascript'), + includeCompletionsForModuleExports: readUnifiedConfig('suggest.autoImports', true, { scope: document, fallbackSection }), + ...getInlayHintsPreferences(document, fallbackSection), ...this.getOrganizeImportsPreferences(preferencesConfig), maximumHoverLength: this.getMaximumHoverLength(document), }; diff --git a/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts b/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts index f2c1b49c67f2e..9e1c3c7cf9dcb 100644 --- a/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts @@ -8,6 +8,7 @@ import { DocumentSelector } from '../configuration/documentSelector'; import { LanguageDescription } from '../configuration/languageDescription'; import * as typeConverters from '../typeConverters'; import { ITypeScriptServiceClient } from '../typescriptService'; +import { readUnifiedConfig } from '../utils/configuration'; import FileConfigurationManager from './fileConfigurationManager'; @@ -45,7 +46,7 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider { position: vscode.Position, token: vscode.CancellationToken ): Promise { - if (!vscode.workspace.getConfiguration(this.language.id, document).get('suggest.completeJSDocs')) { + if (!readUnifiedConfig('suggest.completeJSDocs', true, { scope: document, fallbackSection: this.language.id })) { return undefined; } diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index f2d52f21fa73a..cd383a46ec545 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -18,6 +18,7 @@ import { ClientCapability } from './typescriptService'; import TypeScriptServiceClient from './typescriptServiceClient'; import TypingsStatus from './ui/typingsStatus'; import { Disposable } from './utils/dispose'; +import { readUnifiedConfig } from './utils/configuration'; import { isWeb, isWebAndHasSharedArrayBuffers, supportsReadableByteStreams } from './utils/platform'; @@ -96,7 +97,7 @@ export default class LanguageProvider extends Disposable { private configurationChanged(): void { const config = vscode.workspace.getConfiguration(this.id, null); this.updateValidate(config.get(validateSetting, true)); - this.updateSuggestionDiagnostics(config.get(suggestionSetting, true)); + this.updateSuggestionDiagnostics(readUnifiedConfig(suggestionSetting, true, { scope: null, fallbackSection: this.id })); } public handlesUri(resource: vscode.Uri): boolean { diff --git a/extensions/typescript-language-features/src/test/smoke/completions.test.ts b/extensions/typescript-language-features/src/test/smoke/completions.test.ts index bdaeb858e25ad..deea8f9bdacd3 100644 --- a/extensions/typescript-language-features/src/test/smoke/completions.test.ts +++ b/extensions/typescript-language-features/src/test/smoke/completions.test.ts @@ -16,7 +16,7 @@ const insertModes = Object.freeze(['insert', 'replace']); suite.skip('TypeScript Completions', () => { const configDefaults = Object.freeze({ [Config.autoClosingBrackets]: 'always', - [Config.typescriptCompleteFunctionCalls]: false, + [Config.completeFunctionCalls]: false, [Config.insertMode]: 'insert', [Config.snippetSuggestions]: 'none', [Config.suggestSelection]: 'first', @@ -181,7 +181,7 @@ suite.skip('TypeScript Completions', () => { }); test('completeFunctionCalls should complete function parameters when at end of word', async () => { - await updateConfig(testDocumentUri, { [Config.typescriptCompleteFunctionCalls]: true }); + await updateConfig(testDocumentUri, { [Config.completeFunctionCalls]: true }); // Complete with-in word const editor = await createTestEditor(testDocumentUri, @@ -199,7 +199,7 @@ suite.skip('TypeScript Completions', () => { }); test.skip('completeFunctionCalls should complete function parameters when within word', async () => { - await updateConfig(testDocumentUri, { [Config.typescriptCompleteFunctionCalls]: true }); + await updateConfig(testDocumentUri, { [Config.completeFunctionCalls]: true }); const editor = await createTestEditor(testDocumentUri, `function abcdef(x, y, z) { }`, @@ -216,7 +216,7 @@ suite.skip('TypeScript Completions', () => { }); test('completeFunctionCalls should not complete function parameters at end of word if we are already in something that looks like a function call, #18131', async () => { - await updateConfig(testDocumentUri, { [Config.typescriptCompleteFunctionCalls]: true }); + await updateConfig(testDocumentUri, { [Config.completeFunctionCalls]: true }); const editor = await createTestEditor(testDocumentUri, `function abcdef(x, y, z) { }`, @@ -233,7 +233,7 @@ suite.skip('TypeScript Completions', () => { }); test.skip('completeFunctionCalls should not complete function parameters within word if we are already in something that looks like a function call, #18131', async () => { - await updateConfig(testDocumentUri, { [Config.typescriptCompleteFunctionCalls]: true }); + await updateConfig(testDocumentUri, { [Config.completeFunctionCalls]: true }); const editor = await createTestEditor(testDocumentUri, `function abcdef(x, y, z) { }`, diff --git a/extensions/typescript-language-features/src/test/testUtils.ts b/extensions/typescript-language-features/src/test/testUtils.ts index 106df7f61186d..fc65d68944f03 100644 --- a/extensions/typescript-language-features/src/test/testUtils.ts +++ b/extensions/typescript-language-features/src/test/testUtils.ts @@ -119,7 +119,7 @@ export async function updateConfig(documentUri: vscode.Uri, newConfig: VsCodeCon export const Config = Object.freeze({ autoClosingBrackets: 'editor.autoClosingBrackets', - typescriptCompleteFunctionCalls: 'typescript.suggest.completeFunctionCalls', + completeFunctionCalls: 'js/ts.suggest.completeFunctionCalls', insertMode: 'editor.suggest.insertMode', snippetSuggestions: 'editor.snippetSuggestions', suggestSelection: 'editor.suggestSelection', From e987c5242eb3078429d3a6bb05f4d485c1bf79e5 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:15:03 -0800 Subject: [PATCH 08/16] chore: bump extension versions (#295040) --- extensions/bat/package.json | 2 +- extensions/clojure/package.json | 2 +- extensions/coffeescript/package.json | 2 +- extensions/configuration-editing/package-lock.json | 4 ++-- extensions/configuration-editing/package.json | 2 +- extensions/cpp/package.json | 2 +- extensions/csharp/package.json | 2 +- extensions/css-language-features/package-lock.json | 4 ++-- extensions/css-language-features/package.json | 2 +- extensions/css-language-features/server/package-lock.json | 4 ++-- extensions/css-language-features/server/package.json | 2 +- extensions/css/package.json | 2 +- extensions/dart/package.json | 2 +- extensions/debug-auto-launch/package-lock.json | 4 ++-- extensions/debug-auto-launch/package.json | 2 +- extensions/debug-server-ready/package-lock.json | 4 ++-- extensions/debug-server-ready/package.json | 2 +- extensions/diff/package.json | 2 +- extensions/docker/package.json | 2 +- extensions/dotenv/package.json | 2 +- extensions/emmet/package-lock.json | 4 ++-- extensions/emmet/package.json | 2 +- extensions/extension-editing/package-lock.json | 4 ++-- extensions/extension-editing/package.json | 2 +- extensions/fsharp/package.json | 2 +- extensions/git-base/package-lock.json | 4 ++-- extensions/git-base/package.json | 2 +- extensions/git/package-lock.json | 4 ++-- extensions/git/package.json | 2 +- extensions/go/package.json | 2 +- extensions/groovy/package.json | 2 +- extensions/grunt/package-lock.json | 4 ++-- extensions/grunt/package.json | 2 +- extensions/gulp/package-lock.json | 4 ++-- extensions/gulp/package.json | 2 +- extensions/handlebars/package.json | 2 +- extensions/hlsl/package.json | 2 +- extensions/html-language-features/package-lock.json | 4 ++-- extensions/html-language-features/package.json | 2 +- extensions/html-language-features/server/package-lock.json | 4 ++-- extensions/html-language-features/server/package.json | 2 +- extensions/html/package.json | 2 +- extensions/ini/package.json | 2 +- extensions/ipynb/package-lock.json | 4 ++-- extensions/ipynb/package.json | 2 +- extensions/jake/package-lock.json | 4 ++-- extensions/jake/package.json | 2 +- extensions/java/package.json | 2 +- extensions/javascript/package.json | 2 +- extensions/json-language-features/package-lock.json | 4 ++-- extensions/json-language-features/package.json | 2 +- extensions/json/package.json | 2 +- extensions/julia/package.json | 2 +- extensions/latex/package.json | 2 +- extensions/less/package.json | 2 +- extensions/log/package.json | 2 +- extensions/lua/package.json | 2 +- extensions/make/package.json | 2 +- extensions/markdown-basics/package.json | 2 +- extensions/markdown-language-features/package-lock.json | 4 ++-- extensions/markdown-language-features/package.json | 2 +- extensions/markdown-math/package-lock.json | 4 ++-- extensions/markdown-math/package.json | 2 +- extensions/media-preview/package-lock.json | 4 ++-- extensions/media-preview/package.json | 2 +- extensions/merge-conflict/package-lock.json | 4 ++-- extensions/merge-conflict/package.json | 2 +- extensions/mermaid-chat-features/package-lock.json | 4 ++-- extensions/mermaid-chat-features/package.json | 2 +- extensions/notebook-renderers/package-lock.json | 4 ++-- extensions/notebook-renderers/package.json | 2 +- extensions/objective-c/package.json | 2 +- extensions/perl/package.json | 2 +- extensions/php-language-features/package-lock.json | 4 ++-- extensions/php-language-features/package.json | 2 +- extensions/php/package.json | 2 +- extensions/powershell/package.json | 2 +- extensions/prompt-basics/package.json | 2 +- extensions/pug/package.json | 2 +- extensions/python/package.json | 2 +- extensions/r/package.json | 2 +- extensions/razor/package.json | 2 +- extensions/references-view/package-lock.json | 4 ++-- extensions/references-view/package.json | 2 +- extensions/restructuredtext/package.json | 2 +- extensions/ruby/package.json | 2 +- extensions/rust/package.json | 2 +- extensions/scss/package.json | 2 +- extensions/search-result/package-lock.json | 4 ++-- extensions/search-result/package.json | 2 +- extensions/shaderlab/package.json | 2 +- extensions/shellscript/package.json | 2 +- extensions/simple-browser/package-lock.json | 4 ++-- extensions/simple-browser/package.json | 2 +- extensions/sql/package.json | 2 +- extensions/swift/package.json | 2 +- extensions/theme-abyss/package.json | 2 +- extensions/theme-defaults/package.json | 2 +- extensions/theme-kimbie-dark/package.json | 2 +- extensions/theme-monokai-dimmed/package.json | 2 +- extensions/theme-monokai/package.json | 2 +- extensions/theme-quietlight/package.json | 2 +- extensions/theme-red/package.json | 2 +- extensions/theme-seti/package.json | 2 +- extensions/theme-solarized-dark/package.json | 2 +- extensions/theme-solarized-light/package.json | 2 +- extensions/theme-tomorrow-night-blue/package.json | 2 +- extensions/tunnel-forwarding/package-lock.json | 4 ++-- extensions/tunnel-forwarding/package.json | 2 +- extensions/typescript-basics/package.json | 2 +- extensions/typescript-language-features/package-lock.json | 4 ++-- extensions/typescript-language-features/package.json | 2 +- extensions/vb/package.json | 2 +- extensions/xml/package.json | 2 +- extensions/yaml/package.json | 4 ++-- 115 files changed, 144 insertions(+), 144 deletions(-) diff --git a/extensions/bat/package.json b/extensions/bat/package.json index 2e654fe10a5c8..aab7ccab51d47 100644 --- a/extensions/bat/package.json +++ b/extensions/bat/package.json @@ -2,7 +2,7 @@ "name": "bat", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/clojure/package.json b/extensions/clojure/package.json index 730594c2ac5b7..41e641f986124 100644 --- a/extensions/clojure/package.json +++ b/extensions/clojure/package.json @@ -2,7 +2,7 @@ "name": "clojure", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/coffeescript/package.json b/extensions/coffeescript/package.json index 615138a280a94..f42a9d1e38c01 100644 --- a/extensions/coffeescript/package.json +++ b/extensions/coffeescript/package.json @@ -2,7 +2,7 @@ "name": "coffeescript", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/configuration-editing/package-lock.json b/extensions/configuration-editing/package-lock.json index 831c339ad94a5..1e7abfdd8d499 100644 --- a/extensions/configuration-editing/package-lock.json +++ b/extensions/configuration-editing/package-lock.json @@ -1,12 +1,12 @@ { "name": "configuration-editing", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "configuration-editing", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@octokit/rest": "^21.1.1", diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 84af30cb45b01..33156c201dc0c 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -2,7 +2,7 @@ "name": "configuration-editing", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/cpp/package.json b/extensions/cpp/package.json index 9f3c890a48bbd..7df28eed69389 100644 --- a/extensions/cpp/package.json +++ b/extensions/cpp/package.json @@ -2,7 +2,7 @@ "name": "cpp", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/csharp/package.json b/extensions/csharp/package.json index c45029ddb13d9..cc7cf45406aa8 100644 --- a/extensions/csharp/package.json +++ b/extensions/csharp/package.json @@ -2,7 +2,7 @@ "name": "csharp", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/css-language-features/package-lock.json b/extensions/css-language-features/package-lock.json index abb2975f7de83..af7a789d5a2db 100644 --- a/extensions/css-language-features/package-lock.json +++ b/extensions/css-language-features/package-lock.json @@ -1,12 +1,12 @@ { "name": "css-language-features", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "css-language-features", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "vscode-languageclient": "^10.0.0-next.18", diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index ab57bc0c9b2c4..5d25c2ca43a3f 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -2,7 +2,7 @@ "name": "css-language-features", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/css-language-features/server/package-lock.json b/extensions/css-language-features/server/package-lock.json index 60165842552ee..72e2e858443b4 100644 --- a/extensions/css-language-features/server/package-lock.json +++ b/extensions/css-language-features/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-css-languageserver", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-css-languageserver", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 1d6d0cd2cbc87..17aaf4052137c 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-css-languageserver", "description": "CSS/LESS/SCSS language server", - "version": "1.0.0", + "version": "10.0.0", "author": "Microsoft Corporation", "license": "MIT", "engines": { diff --git a/extensions/css/package.json b/extensions/css/package.json index 711c6f4a41ae1..7e48b60836a61 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -2,7 +2,7 @@ "name": "css", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/dart/package.json b/extensions/dart/package.json index 4aea7f98f50ba..e77181d25d036 100644 --- a/extensions/dart/package.json +++ b/extensions/dart/package.json @@ -2,7 +2,7 @@ "name": "dart", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/debug-auto-launch/package-lock.json b/extensions/debug-auto-launch/package-lock.json index a25a1d9a1b4a4..3a31b9208b060 100644 --- a/extensions/debug-auto-launch/package-lock.json +++ b/extensions/debug-auto-launch/package-lock.json @@ -1,12 +1,12 @@ { "name": "debug-auto-launch", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "debug-auto-launch", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/debug-auto-launch/package.json b/extensions/debug-auto-launch/package.json index e17486724711e..dd2bd2ce2dcf6 100644 --- a/extensions/debug-auto-launch/package.json +++ b/extensions/debug-auto-launch/package.json @@ -2,7 +2,7 @@ "name": "debug-auto-launch", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/debug-server-ready/package-lock.json b/extensions/debug-server-ready/package-lock.json index fa4d002246640..63931e9fe0e80 100644 --- a/extensions/debug-server-ready/package-lock.json +++ b/extensions/debug-server-ready/package-lock.json @@ -1,12 +1,12 @@ { "name": "debug-server-ready", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "debug-server-ready", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/debug-server-ready/package.json b/extensions/debug-server-ready/package.json index adc91350ddcaf..1f5bbef20246e 100644 --- a/extensions/debug-server-ready/package.json +++ b/extensions/debug-server-ready/package.json @@ -2,7 +2,7 @@ "name": "debug-server-ready", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/diff/package.json b/extensions/diff/package.json index 23047e3a534f9..cc75acaefb4c0 100644 --- a/extensions/diff/package.json +++ b/extensions/diff/package.json @@ -2,7 +2,7 @@ "name": "diff", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/docker/package.json b/extensions/docker/package.json index a9a3dfdd9bf3f..643e37d2ae119 100644 --- a/extensions/docker/package.json +++ b/extensions/docker/package.json @@ -2,7 +2,7 @@ "name": "docker", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/dotenv/package.json b/extensions/dotenv/package.json index 2adbc86ff36d9..6a9521be1a843 100644 --- a/extensions/dotenv/package.json +++ b/extensions/dotenv/package.json @@ -2,7 +2,7 @@ "name": "dotenv", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/emmet/package-lock.json b/extensions/emmet/package-lock.json index e225308e6d7e9..92930a25c2793 100644 --- a/extensions/emmet/package-lock.json +++ b/extensions/emmet/package-lock.json @@ -1,12 +1,12 @@ { "name": "emmet", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "emmet", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@emmetio/css-parser": "ramya-rao-a/css-parser#vscode", diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 5b5d3748f87f6..dce1af57a1b93 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -2,7 +2,7 @@ "name": "emmet", "displayName": "Emmet", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/extension-editing/package-lock.json b/extensions/extension-editing/package-lock.json index 4328e1ce81ec1..be1aa96eea6cb 100644 --- a/extensions/extension-editing/package-lock.json +++ b/extensions/extension-editing/package-lock.json @@ -1,12 +1,12 @@ { "name": "extension-editing", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "extension-editing", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "jsonc-parser": "^3.2.0", diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index c13a3386f5f27..3e277dbbfd385 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -2,7 +2,7 @@ "name": "extension-editing", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/fsharp/package.json b/extensions/fsharp/package.json index 1d05eb83de3a6..8688218b57ab2 100644 --- a/extensions/fsharp/package.json +++ b/extensions/fsharp/package.json @@ -2,7 +2,7 @@ "name": "fsharp", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/git-base/package-lock.json b/extensions/git-base/package-lock.json index 8ae6f0c2f7a59..bd2b70f2f98ea 100644 --- a/extensions/git-base/package-lock.json +++ b/extensions/git-base/package-lock.json @@ -1,12 +1,12 @@ { "name": "git-base", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "git-base", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/git-base/package.json b/extensions/git-base/package.json index f61e5f8df5628..62f32d03c4591 100644 --- a/extensions/git-base/package.json +++ b/extensions/git-base/package.json @@ -2,7 +2,7 @@ "name": "git-base", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/git/package-lock.json b/extensions/git/package-lock.json index 45e3989e801f0..b552ce9fa5b3f 100644 --- a/extensions/git/package-lock.json +++ b/extensions/git/package-lock.json @@ -1,12 +1,12 @@ { "name": "git", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "git", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@joaomoreno/unique-names-generator": "^5.2.0", diff --git a/extensions/git/package.json b/extensions/git/package.json index bf5d003593bb2..d3012834ba572 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -4,7 +4,7 @@ "description": "%description%", "publisher": "vscode", "license": "MIT", - "version": "1.0.0", + "version": "10.0.0", "engines": { "vscode": "^1.5.0" }, diff --git a/extensions/go/package.json b/extensions/go/package.json index ea0ca7921487c..ecae5521341cf 100644 --- a/extensions/go/package.json +++ b/extensions/go/package.json @@ -2,7 +2,7 @@ "name": "go", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/groovy/package.json b/extensions/groovy/package.json index 02e1138256fde..eb76fd2377354 100644 --- a/extensions/groovy/package.json +++ b/extensions/groovy/package.json @@ -2,7 +2,7 @@ "name": "groovy", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/grunt/package-lock.json b/extensions/grunt/package-lock.json index ca37ada887478..1c9f231958929 100644 --- a/extensions/grunt/package-lock.json +++ b/extensions/grunt/package-lock.json @@ -1,12 +1,12 @@ { "name": "grunt", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "grunt", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index b0932676ec0d2..4e0ee15a9cf04 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -3,7 +3,7 @@ "publisher": "vscode", "description": "Extension to add Grunt capabilities to VS Code.", "displayName": "Grunt support for VS Code", - "version": "1.0.0", + "version": "10.0.0", "private": true, "icon": "images/grunt.png", "license": "MIT", diff --git a/extensions/gulp/package-lock.json b/extensions/gulp/package-lock.json index 2ded5fe7db1f0..ee8ef27bbbaa4 100644 --- a/extensions/gulp/package-lock.json +++ b/extensions/gulp/package-lock.json @@ -1,12 +1,12 @@ { "name": "gulp", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gulp", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/gulp/package.json b/extensions/gulp/package.json index 88c402468cf42..a0a522323257b 100644 --- a/extensions/gulp/package.json +++ b/extensions/gulp/package.json @@ -3,7 +3,7 @@ "publisher": "vscode", "description": "%description%", "displayName": "%displayName%", - "version": "1.0.0", + "version": "10.0.0", "icon": "images/gulp.png", "license": "MIT", "engines": { diff --git a/extensions/handlebars/package.json b/extensions/handlebars/package.json index 7e6ecc1b11358..fe4c58f2df898 100644 --- a/extensions/handlebars/package.json +++ b/extensions/handlebars/package.json @@ -2,7 +2,7 @@ "name": "handlebars", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/hlsl/package.json b/extensions/hlsl/package.json index ea60ab164832a..f353e03c172e3 100644 --- a/extensions/hlsl/package.json +++ b/extensions/hlsl/package.json @@ -2,7 +2,7 @@ "name": "hlsl", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/html-language-features/package-lock.json b/extensions/html-language-features/package-lock.json index ab0ef3f9510e2..46be23d97bee0 100644 --- a/extensions/html-language-features/package-lock.json +++ b/extensions/html-language-features/package-lock.json @@ -1,12 +1,12 @@ { "name": "html-language-features", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "html-language-features", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 4e630b7273623..0e3d2a046ffa1 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -2,7 +2,7 @@ "name": "html-language-features", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", diff --git a/extensions/html-language-features/server/package-lock.json b/extensions/html-language-features/server/package-lock.json index dc257f8b9d78f..1237a0d47fce6 100644 --- a/extensions/html-language-features/server/package-lock.json +++ b/extensions/html-language-features/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-html-languageserver", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-html-languageserver", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 8208d3f22e67e..bdec7b48344d5 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-html-languageserver", "description": "HTML language server", - "version": "1.0.0", + "version": "10.0.0", "author": "Microsoft Corporation", "license": "MIT", "engines": { diff --git a/extensions/html/package.json b/extensions/html/package.json index 098f9eae994c8..495ff1a0ad937 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -2,7 +2,7 @@ "name": "html", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/ini/package.json b/extensions/ini/package.json index f4837eb881f22..edc2086e5c578 100644 --- a/extensions/ini/package.json +++ b/extensions/ini/package.json @@ -2,7 +2,7 @@ "name": "ini", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "private": true, "publisher": "vscode", "license": "MIT", diff --git a/extensions/ipynb/package-lock.json b/extensions/ipynb/package-lock.json index 8dc9c5a8a42db..f833928307212 100644 --- a/extensions/ipynb/package-lock.json +++ b/extensions/ipynb/package-lock.json @@ -1,12 +1,12 @@ { "name": "ipynb", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ipynb", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@enonic/fnv-plus": "^1.3.0", diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 7396e270a47e3..b80b2dff98666 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -3,7 +3,7 @@ "displayName": "%displayName%", "description": "%description%", "publisher": "vscode", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "icon": "media/icon.png", "engines": { diff --git a/extensions/jake/package-lock.json b/extensions/jake/package-lock.json index c7d37035062d0..89f3a36d6a652 100644 --- a/extensions/jake/package-lock.json +++ b/extensions/jake/package-lock.json @@ -1,12 +1,12 @@ { "name": "jake", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jake", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/jake/package.json b/extensions/jake/package.json index e68c335d49561..c15a4c597713f 100644 --- a/extensions/jake/package.json +++ b/extensions/jake/package.json @@ -4,7 +4,7 @@ "description": "%description%", "displayName": "%displayName%", "icon": "images/cowboy_hat.png", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "engines": { "vscode": "*" diff --git a/extensions/java/package.json b/extensions/java/package.json index 2403b88bf9ac3..ccbbf268f1841 100644 --- a/extensions/java/package.json +++ b/extensions/java/package.json @@ -2,7 +2,7 @@ "name": "java", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/javascript/package.json b/extensions/javascript/package.json index 9a64d1585f0d2..ae2b946716b95 100644 --- a/extensions/javascript/package.json +++ b/extensions/javascript/package.json @@ -2,7 +2,7 @@ "name": "javascript", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/json-language-features/package-lock.json b/extensions/json-language-features/package-lock.json index 57df1eea33719..213518c3d0964 100644 --- a/extensions/json-language-features/package-lock.json +++ b/extensions/json-language-features/package-lock.json @@ -1,12 +1,12 @@ { "name": "json-language-features", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "json-language-features", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 9529d6572866b..5a90b8b7e1b0d 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -2,7 +2,7 @@ "name": "json-language-features", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", diff --git a/extensions/json/package.json b/extensions/json/package.json index 1bc6fa85e53cf..df25f535da607 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -2,7 +2,7 @@ "name": "json", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/julia/package.json b/extensions/julia/package.json index 741271fb2318d..00de050b8ac45 100644 --- a/extensions/julia/package.json +++ b/extensions/julia/package.json @@ -2,7 +2,7 @@ "name": "julia", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/latex/package.json b/extensions/latex/package.json index 197343cd6a1b7..55201dd59c9dc 100644 --- a/extensions/latex/package.json +++ b/extensions/latex/package.json @@ -2,7 +2,7 @@ "name": "latex", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/less/package.json b/extensions/less/package.json index 5bd376312fa0d..65c416ceae904 100644 --- a/extensions/less/package.json +++ b/extensions/less/package.json @@ -2,7 +2,7 @@ "name": "less", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/log/package.json b/extensions/log/package.json index d52c43afea735..78983dcd5c267 100644 --- a/extensions/log/package.json +++ b/extensions/log/package.json @@ -2,7 +2,7 @@ "name": "log", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/lua/package.json b/extensions/lua/package.json index 72c0840a9ea3f..349bcbc42b55e 100644 --- a/extensions/lua/package.json +++ b/extensions/lua/package.json @@ -2,7 +2,7 @@ "name": "lua", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/make/package.json b/extensions/make/package.json index 49e96cab1eb4b..a7b6b9ef0acaf 100644 --- a/extensions/make/package.json +++ b/extensions/make/package.json @@ -2,7 +2,7 @@ "name": "make", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index cb1351a71cfa5..c77aad6a30149 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -2,7 +2,7 @@ "name": "markdown", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index 7d8c47eabc944..b7180c307306d 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -1,12 +1,12 @@ { "name": "markdown-language-features", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "markdown-language-features", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index c242dceffd85d..745ba66b56c5e 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -2,7 +2,7 @@ "name": "markdown-language-features", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "icon": "icon.png", "publisher": "vscode", "license": "MIT", diff --git a/extensions/markdown-math/package-lock.json b/extensions/markdown-math/package-lock.json index 5cf113f940933..4f7e1494bdf2e 100644 --- a/extensions/markdown-math/package-lock.json +++ b/extensions/markdown-math/package-lock.json @@ -1,12 +1,12 @@ { "name": "markdown-math", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "markdown-math", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/markdown-it-katex": "^1.1.2" diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index 19f20fcd04ab8..9fdbf63f45ff7 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -2,7 +2,7 @@ "name": "markdown-math", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "icon": "icon.png", "publisher": "vscode", "license": "MIT", diff --git a/extensions/media-preview/package-lock.json b/extensions/media-preview/package-lock.json index fcd827cb0c3fa..bcd3f56ab2ad9 100644 --- a/extensions/media-preview/package-lock.json +++ b/extensions/media-preview/package-lock.json @@ -1,12 +1,12 @@ { "name": "media-preview", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "media-preview", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/extensions/media-preview/package.json b/extensions/media-preview/package.json index 3f7e1c0165353..f1ee36118f2eb 100644 --- a/extensions/media-preview/package.json +++ b/extensions/media-preview/package.json @@ -6,7 +6,7 @@ "ui", "workspace" ], - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "icon": "icon.png", "license": "MIT", diff --git a/extensions/merge-conflict/package-lock.json b/extensions/merge-conflict/package-lock.json index ee1581e78de18..ac5c4de8274b3 100644 --- a/extensions/merge-conflict/package-lock.json +++ b/extensions/merge-conflict/package-lock.json @@ -1,12 +1,12 @@ { "name": "merge-conflict", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "merge-conflict", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8" diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index c699832992ce8..5b0eaa1b29c8f 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -4,7 +4,7 @@ "displayName": "%displayName%", "description": "%description%", "icon": "media/icon.png", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { diff --git a/extensions/mermaid-chat-features/package-lock.json b/extensions/mermaid-chat-features/package-lock.json index 2ec780d5c5925..3d502443d2a2d 100644 --- a/extensions/mermaid-chat-features/package-lock.json +++ b/extensions/mermaid-chat-features/package-lock.json @@ -1,12 +1,12 @@ { "name": "mermaid-chat-features", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mermaid-chat-features", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "dompurify": "^3.2.7", diff --git a/extensions/mermaid-chat-features/package.json b/extensions/mermaid-chat-features/package.json index 64c31782461fe..ba67e03fdcee3 100644 --- a/extensions/mermaid-chat-features/package.json +++ b/extensions/mermaid-chat-features/package.json @@ -2,7 +2,7 @@ "name": "mermaid-chat-features", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "repository": { diff --git a/extensions/notebook-renderers/package-lock.json b/extensions/notebook-renderers/package-lock.json index 85357e3c85538..beacde5ae213c 100644 --- a/extensions/notebook-renderers/package-lock.json +++ b/extensions/notebook-renderers/package-lock.json @@ -1,12 +1,12 @@ { "name": "builtin-notebook-renderers", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "builtin-notebook-renderers", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/jsdom": "^21.1.0", diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json index 715cfc03e85c1..e9890cad8991d 100644 --- a/extensions/notebook-renderers/package.json +++ b/extensions/notebook-renderers/package.json @@ -3,7 +3,7 @@ "displayName": "%displayName%", "description": "%description%", "publisher": "vscode", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "icon": "media/icon.png", "engines": { diff --git a/extensions/objective-c/package.json b/extensions/objective-c/package.json index 5339f7c42f9b5..b53f4030e3a16 100644 --- a/extensions/objective-c/package.json +++ b/extensions/objective-c/package.json @@ -2,7 +2,7 @@ "name": "objective-c", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/perl/package.json b/extensions/perl/package.json index 1569e55614638..e0a798dbb0250 100644 --- a/extensions/perl/package.json +++ b/extensions/perl/package.json @@ -2,7 +2,7 @@ "name": "perl", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/php-language-features/package-lock.json b/extensions/php-language-features/package-lock.json index b27fe3a21919b..21368d375ead0 100644 --- a/extensions/php-language-features/package-lock.json +++ b/extensions/php-language-features/package-lock.json @@ -1,12 +1,12 @@ { "name": "php-language-features", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "php-language-features", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "which": "^2.0.2" diff --git a/extensions/php-language-features/package.json b/extensions/php-language-features/package.json index 8bb61e5b626d7..bb46b7c01ac76 100644 --- a/extensions/php-language-features/package.json +++ b/extensions/php-language-features/package.json @@ -2,7 +2,7 @@ "name": "php-language-features", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "icon": "icons/logo.png", diff --git a/extensions/php/package.json b/extensions/php/package.json index e38b844be653a..b89ab4b84050e 100644 --- a/extensions/php/package.json +++ b/extensions/php/package.json @@ -2,7 +2,7 @@ "name": "php", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/powershell/package.json b/extensions/powershell/package.json index d73e5e72faddd..2161d60221172 100644 --- a/extensions/powershell/package.json +++ b/extensions/powershell/package.json @@ -2,7 +2,7 @@ "name": "powershell", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/prompt-basics/package.json b/extensions/prompt-basics/package.json index a2c950a34431f..f0ab2b321627d 100644 --- a/extensions/prompt-basics/package.json +++ b/extensions/prompt-basics/package.json @@ -2,7 +2,7 @@ "name": "prompt", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/pug/package.json b/extensions/pug/package.json index cb77f60ba8875..62aea0dd7c8cb 100644 --- a/extensions/pug/package.json +++ b/extensions/pug/package.json @@ -2,7 +2,7 @@ "name": "pug", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/python/package.json b/extensions/python/package.json index 9ffffb90e067c..a7a21d1b5e4a4 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/r/package.json b/extensions/r/package.json index f4edd6a4f5ced..c5e767f508e61 100644 --- a/extensions/r/package.json +++ b/extensions/r/package.json @@ -2,7 +2,7 @@ "name": "r", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/razor/package.json b/extensions/razor/package.json index 06551edc8b946..088d812af6121 100644 --- a/extensions/razor/package.json +++ b/extensions/razor/package.json @@ -2,7 +2,7 @@ "name": "razor", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/references-view/package-lock.json b/extensions/references-view/package-lock.json index fe0d8aad7de02..c98b458dd75d9 100644 --- a/extensions/references-view/package-lock.json +++ b/extensions/references-view/package-lock.json @@ -1,12 +1,12 @@ { "name": "references-view", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "references-view", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/references-view/package.json b/extensions/references-view/package.json index 5f2714589c77a..b5ac88950703d 100644 --- a/extensions/references-view/package.json +++ b/extensions/references-view/package.json @@ -3,7 +3,7 @@ "displayName": "%displayName%", "description": "%description%", "icon": "media/icon.png", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/restructuredtext/package.json b/extensions/restructuredtext/package.json index 4e74e6611ed8b..426c6ed2a1ff0 100644 --- a/extensions/restructuredtext/package.json +++ b/extensions/restructuredtext/package.json @@ -2,7 +2,7 @@ "name": "restructuredtext", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/ruby/package.json b/extensions/ruby/package.json index 355e0cd58a9cd..105a5de69e45c 100644 --- a/extensions/ruby/package.json +++ b/extensions/ruby/package.json @@ -2,7 +2,7 @@ "name": "ruby", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/rust/package.json b/extensions/rust/package.json index 34132cd15df67..94adda84ae40f 100644 --- a/extensions/rust/package.json +++ b/extensions/rust/package.json @@ -2,7 +2,7 @@ "name": "rust", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/scss/package.json b/extensions/scss/package.json index 19eed5e43c619..c0e8ddc789d73 100644 --- a/extensions/scss/package.json +++ b/extensions/scss/package.json @@ -2,7 +2,7 @@ "name": "scss", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/search-result/package-lock.json b/extensions/search-result/package-lock.json index f85d9e265e03e..e7e72d3a81f76 100644 --- a/extensions/search-result/package-lock.json +++ b/extensions/search-result/package-lock.json @@ -1,12 +1,12 @@ { "name": "search-result", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "search-result", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "^22.18.10" diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 1119636313f67..bcd0656eb6147 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -2,7 +2,7 @@ "name": "search-result", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "icon": "images/icon.png", diff --git a/extensions/shaderlab/package.json b/extensions/shaderlab/package.json index df6282b6fa96d..6083dafa0cee2 100644 --- a/extensions/shaderlab/package.json +++ b/extensions/shaderlab/package.json @@ -2,7 +2,7 @@ "name": "shaderlab", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index 9cad54150bbff..c5058f1e3c2a4 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -2,7 +2,7 @@ "name": "shellscript", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/simple-browser/package-lock.json b/extensions/simple-browser/package-lock.json index 8aa3894ba1e97..df8bdd20adc29 100644 --- a/extensions/simple-browser/package-lock.json +++ b/extensions/simple-browser/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-browser", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-browser", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8" diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index d372992c8972e..691e72f248c41 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -5,7 +5,7 @@ "enabledApiProposals": [ "externalUriOpener" ], - "version": "1.0.0", + "version": "10.0.0", "icon": "media/icon.png", "publisher": "vscode", "license": "MIT", diff --git a/extensions/sql/package.json b/extensions/sql/package.json index 5c467297f8188..ec5cccb9946ee 100644 --- a/extensions/sql/package.json +++ b/extensions/sql/package.json @@ -2,7 +2,7 @@ "name": "sql", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/swift/package.json b/extensions/swift/package.json index 5effcf0c6fdf0..c805b19b623dc 100644 --- a/extensions/swift/package.json +++ b/extensions/swift/package.json @@ -2,7 +2,7 @@ "name": "swift", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-abyss/package.json b/extensions/theme-abyss/package.json index 3d3cf45c82a5c..f373199587bf8 100644 --- a/extensions/theme-abyss/package.json +++ b/extensions/theme-abyss/package.json @@ -2,7 +2,7 @@ "name": "theme-abyss", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-defaults/package.json b/extensions/theme-defaults/package.json index 2458ef317da70..edff700e9a854 100644 --- a/extensions/theme-defaults/package.json +++ b/extensions/theme-defaults/package.json @@ -5,7 +5,7 @@ "categories": [ "Themes" ], - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-kimbie-dark/package.json b/extensions/theme-kimbie-dark/package.json index 5c3ec5f7ad0f8..fea6ce4f3dab0 100644 --- a/extensions/theme-kimbie-dark/package.json +++ b/extensions/theme-kimbie-dark/package.json @@ -2,7 +2,7 @@ "name": "theme-kimbie-dark", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-monokai-dimmed/package.json b/extensions/theme-monokai-dimmed/package.json index bd6055e6b2835..88f0a9d8e3ceb 100644 --- a/extensions/theme-monokai-dimmed/package.json +++ b/extensions/theme-monokai-dimmed/package.json @@ -2,7 +2,7 @@ "name": "theme-monokai-dimmed", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-monokai/package.json b/extensions/theme-monokai/package.json index f2e63cd349493..17e3f937274d9 100644 --- a/extensions/theme-monokai/package.json +++ b/extensions/theme-monokai/package.json @@ -2,7 +2,7 @@ "name": "theme-monokai", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-quietlight/package.json b/extensions/theme-quietlight/package.json index e27a8d30d65fa..50f5818d8cd28 100644 --- a/extensions/theme-quietlight/package.json +++ b/extensions/theme-quietlight/package.json @@ -2,7 +2,7 @@ "name": "theme-quietlight", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-red/package.json b/extensions/theme-red/package.json index c2273a0046d8d..4721ddd37af93 100644 --- a/extensions/theme-red/package.json +++ b/extensions/theme-red/package.json @@ -2,7 +2,7 @@ "name": "theme-red", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-seti/package.json b/extensions/theme-seti/package.json index fbc23ec6ee408..4c0755674d528 100644 --- a/extensions/theme-seti/package.json +++ b/extensions/theme-seti/package.json @@ -1,7 +1,7 @@ { "name": "vscode-theme-seti", "private": true, - "version": "1.0.0", + "version": "10.0.0", "displayName": "%displayName%", "description": "%description%", "publisher": "vscode", diff --git a/extensions/theme-solarized-dark/package.json b/extensions/theme-solarized-dark/package.json index c3eed2573d8cf..400daf9e3266c 100644 --- a/extensions/theme-solarized-dark/package.json +++ b/extensions/theme-solarized-dark/package.json @@ -2,7 +2,7 @@ "name": "theme-solarized-dark", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-solarized-light/package.json b/extensions/theme-solarized-light/package.json index 0d016de93dc90..0fca2653095f6 100644 --- a/extensions/theme-solarized-light/package.json +++ b/extensions/theme-solarized-light/package.json @@ -2,7 +2,7 @@ "name": "theme-solarized-light", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/theme-tomorrow-night-blue/package.json b/extensions/theme-tomorrow-night-blue/package.json index 8739e70c13f96..35adcf9db9202 100644 --- a/extensions/theme-tomorrow-night-blue/package.json +++ b/extensions/theme-tomorrow-night-blue/package.json @@ -2,7 +2,7 @@ "name": "theme-tomorrow-night-blue", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/tunnel-forwarding/package-lock.json b/extensions/tunnel-forwarding/package-lock.json index b62fef9300ede..727281a12e934 100644 --- a/extensions/tunnel-forwarding/package-lock.json +++ b/extensions/tunnel-forwarding/package-lock.json @@ -1,12 +1,12 @@ { "name": "tunnel-forwarding", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tunnel-forwarding", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "devDependencies": { "@types/node": "22.x" diff --git a/extensions/tunnel-forwarding/package.json b/extensions/tunnel-forwarding/package.json index 6928b1a08155a..af53863b2b38f 100644 --- a/extensions/tunnel-forwarding/package.json +++ b/extensions/tunnel-forwarding/package.json @@ -2,7 +2,7 @@ "name": "tunnel-forwarding", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/typescript-basics/package.json b/extensions/typescript-basics/package.json index 830b32762e797..2cbd3e29c6614 100644 --- a/extensions/typescript-basics/package.json +++ b/extensions/typescript-basics/package.json @@ -2,7 +2,7 @@ "name": "typescript", "description": "%description%", "displayName": "%displayName%", - "version": "1.0.0", + "version": "10.0.0", "author": "vscode", "publisher": "vscode", "license": "MIT", diff --git a/extensions/typescript-language-features/package-lock.json b/extensions/typescript-language-features/package-lock.json index f0f86a349f7ea..21a1849269409 100644 --- a/extensions/typescript-language-features/package-lock.json +++ b/extensions/typescript-language-features/package-lock.json @@ -1,12 +1,12 @@ { "name": "typescript-language-features", - "version": "1.0.0", + "version": "10.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "typescript-language-features", - "version": "1.0.0", + "version": "10.0.0", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 03d98f3efe77a..18ffbca5e4a09 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -2,7 +2,7 @@ "name": "typescript-language-features", "description": "%description%", "displayName": "%displayName%", - "version": "1.0.0", + "version": "10.0.0", "author": "vscode", "publisher": "vscode", "license": "MIT", diff --git a/extensions/vb/package.json b/extensions/vb/package.json index 00665b071c39f..9f107a49e7b47 100644 --- a/extensions/vb/package.json +++ b/extensions/vb/package.json @@ -2,7 +2,7 @@ "name": "vb", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/xml/package.json b/extensions/xml/package.json index 557205cae965e..4758ebb01cee6 100644 --- a/extensions/xml/package.json +++ b/extensions/xml/package.json @@ -2,7 +2,7 @@ "name": "xml", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index 66f0a200d084f..87575303730d4 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -2,7 +2,7 @@ "name": "yaml", "displayName": "%displayName%", "description": "%description%", - "version": "1.0.0", + "version": "10.0.0", "publisher": "vscode", "license": "MIT", "engines": { @@ -96,7 +96,7 @@ "editor.autoIndent": "advanced", "diffEditor.ignoreTrimWhitespace": false, "editor.defaultColorDecorators": "never", - "editor.quickSuggestions": { + "editor.quickSuggestions": { "strings": "on" } }, From 313730f8367531641728c12373fccc166534f693 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:15:08 -0800 Subject: [PATCH 09/16] Use `browser` if it exists --- extensions/esbuild-extension-common.mts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/esbuild-extension-common.mts b/extensions/esbuild-extension-common.mts index 84eb4d02ec734..b5ce1aceb0c78 100644 --- a/extensions/esbuild-extension-common.mts +++ b/extensions/esbuild-extension-common.mts @@ -47,7 +47,6 @@ function resolveOptions(config: RunConfig, outdir: string): BuildOptions { sourcemap: true, target: ['es2024'], external: ['vscode'], - mainFields: ['module', 'main'], entryPoints: config.entryPoints, outdir, logOverride: { @@ -58,8 +57,10 @@ function resolveOptions(config: RunConfig, outdir: string): BuildOptions { if (config.platform === 'node') { options.format = 'cjs'; + options.mainFields = ['module', 'main']; } else if (config.platform === 'browser') { options.format = 'iife'; + options.mainFields = ['browser', 'module', 'main']; options.alias = { 'path': 'path-browserify', }; From 0370d00302b2aec2a6f7e6a535056ba7c38a9c60 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:16:38 -0800 Subject: [PATCH 10/16] chat: make prompt discovery overrideable for scoped workspaces (#294763) - add protected workspace folder hooks in PromptFilesLocator to allow scoped roots - normalize workspace folder lookup to avoid null/undefined mismatch - allow PromptsService subclasses to supply a custom prompt files locator --- .../service/promptsServiceImpl.ts | 8 +++-- .../promptSyntax/utils/promptFilesLocator.ts | 32 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) 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 e32abc667f020..257cb5fdc57b6 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -137,7 +137,7 @@ export class PromptsService extends Disposable implements IPromptsService { @ILogService public readonly logger: ILogService, @ILabelService private readonly labelService: ILabelService, @IModelService private readonly modelService: IModelService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, @IUserDataProfileService private readonly userDataService: IUserDataProfileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, @@ -150,7 +150,7 @@ export class PromptsService extends Disposable implements IPromptsService { ) { super(); - this.fileLocator = this.instantiationService.createInstance(PromptFilesLocator); + this.fileLocator = this.createPromptFilesLocator(); this._register(this.modelService.onModelRemoved((model) => { this.cachedParsedPromptFromModels.delete(model.uri); })); @@ -188,6 +188,10 @@ export class PromptsService extends Disposable implements IPromptsService { this._register(this.cachedHooks.onDidChange(() => { })); } + protected createPromptFilesLocator(): PromptFilesLocator { + return this.instantiationService.createInstance(PromptFilesLocator); + } + private getFileLocatorEvent(type: PromptsType): Event { let event = this.fileLocatorEvents[type]; if (!event) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts index 106a6fe1f82ac..50ca5a8dc0dbd 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts @@ -10,7 +10,7 @@ import * as nls from '../../../../../../nls.js'; import { FileOperationError, FileOperationResult, IFileService } from '../../../../../../platform/files/common/files.js'; import { getPromptFileLocationsConfigKey, isTildePath, PromptsConfig } from '../config/config.js'; import { basename, dirname, isEqualOrParent, joinPath } from '../../../../../../base/common/resources.js'; -import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; +import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType, LEGACY_MODE_FILE_EXTENSION, getCleanPromptName, AGENT_FILE_EXTENSION, getPromptFileDefaultLocations, SKILL_FILENAME, IPromptSourceFolder, DEFAULT_AGENT_SOURCE_FOLDERS, IResolvedPromptFile, IResolvedPromptSourceFolder, PromptFileSource, isInClaudeRulesFolder } from '../config/promptFileLocations.js'; import { PromptsType } from '../promptTypes.js'; @@ -43,6 +43,18 @@ export class PromptFilesLocator { ) { } + protected getWorkspaceFolders(): readonly IWorkspaceFolder[] { + return this.workspaceService.getWorkspace().folders; + } + + protected getWorkspaceFolder(resource: URI): IWorkspaceFolder | undefined { + return this.workspaceService.getWorkspaceFolder(resource) ?? undefined; + } + + protected onDidChangeWorkspaceFolders(): Event { + return Event.map(this.workspaceService.onDidChangeWorkspaceFolders, () => undefined); + } + /** * List all prompt files from the filesystem. * @@ -112,7 +124,7 @@ export class PromptFilesLocator { */ private getSourceFoldersSync(type: PromptsType, userHome: URI): readonly URI[] { const result: URI[] = []; - const { folders } = this.workspaceService.getWorkspace(); + const folders = this.getWorkspaceFolders(); const defaultFileOrFolders = getPromptFileDefaultLocations(type); const getFolderUri = (type: PromptsType, fileOrFolderPath: URI): URI => { @@ -155,7 +167,7 @@ export class PromptFilesLocator { const updateExternalFolderWatchers = () => { externalFolderWatchers.clear(); for (const folder of parentFolders) { - if (!this.workspaceService.getWorkspaceFolder(folder.parent)) { + if (!this.getWorkspaceFolder(folder.parent)) { // if the folder is not part of the workspace, we need to watch it const recursive = folder.filePattern !== undefined; externalFolderWatchers.add(this.fileService.watch(folder.parent, { recursive, excludes: [] })); @@ -163,7 +175,7 @@ export class PromptFilesLocator { } // Watch all source folders (including user home if applicable) for (const folder of allSourceFolders) { - if (!this.workspaceService.getWorkspaceFolder(folder)) { + if (!this.getWorkspaceFolder(folder)) { externalFolderWatchers.add(this.fileService.watch(folder, { recursive: true, excludes: [] })); } } @@ -387,7 +399,7 @@ export class PromptFilesLocator { private toAbsoluteLocations(type: PromptsType, configuredLocations: readonly IPromptSourceFolder[], userHome: URI | undefined, defaultLocations?: readonly IPromptSourceFolder[]): readonly IResolvedPromptSourceFolder[] { const result: IResolvedPromptSourceFolder[] = []; const seen = new ResourceSet(); - const { folders } = this.workspaceService.getWorkspace(); + const folders = this.getWorkspaceFolders(); // Create a set of default paths for quick lookup const defaultPaths = new Set(defaultLocations?.map(loc => loc.path)); @@ -513,7 +525,7 @@ export class PromptFilesLocator { const disregardIgnoreFiles = this.configService.getValue('explorer.excludeGitIgnore'); - const workspaceRoot = this.workspaceService.getWorkspaceFolder(folder); + const workspaceRoot = this.getWorkspaceFolder(folder); const getExcludePattern = (folder: URI) => getExcludes(this.configService.getValue({ resource: folder })) || {}; const searchOptions: IFileQuery = { @@ -542,7 +554,7 @@ export class PromptFilesLocator { public async findCopilotInstructionsMDsInWorkspace(token: CancellationToken): Promise { const result: IResolvedAgentFile[] = []; - const { folders } = this.workspaceService.getWorkspace(); + const folders = this.getWorkspaceFolders(); for (const folder of folders) { const file = joinPath(folder.uri, `.github/` + COPILOT_CUSTOM_INSTRUCTIONS_FILENAME); try { @@ -562,7 +574,7 @@ export class PromptFilesLocator { * Gets list of `AGENTS.md` files anywhere in the workspace. */ public async findAgentMDsInWorkspace(token: CancellationToken): Promise { - const result = await Promise.all(this.workspaceService.getWorkspace().folders.map(folder => this.findAgentMDsInFolder(folder.uri, token))); + const result = await Promise.all(this.getWorkspaceFolders().map(folder => this.findAgentMDsInFolder(folder.uri, token))); return result.flat(1); } @@ -643,7 +655,7 @@ export class PromptFilesLocator { * Gets list of files at the root workspace folder(s). */ public async findFilesInWorkspaceRoots(fileName: string, folder: string | undefined, type: AgentFileType, token: CancellationToken, result: IResolvedAgentFile[] = []): Promise { - const { folders } = this.workspaceService.getWorkspace(); + const folders = this.getWorkspaceFolders(); if (folder) { return this.findFilesInRoots(folders.map(f => joinPath(f.uri, folder)), fileName, type, token, result); } @@ -671,7 +683,7 @@ export class PromptFilesLocator { public getAgentFileURIFromModeFile(oldURI: URI): URI | undefined { if (oldURI.path.endsWith(LEGACY_MODE_FILE_EXTENSION)) { let newLocation; - const workspaceFolder = this.workspaceService.getWorkspaceFolder(oldURI); + const workspaceFolder = this.getWorkspaceFolder(oldURI); if (workspaceFolder) { newLocation = joinPath(workspaceFolder.uri, AGENTS_SOURCE_FOLDER, getCleanPromptName(oldURI) + AGENT_FILE_EXTENSION); } else if (isEqualOrParent(oldURI, this.userDataService.currentProfile.promptsHome)) { From aad1561e221ca507309e7ee1ca099634c170b535 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:45:10 -0800 Subject: [PATCH 11/16] Merge pull request #294774 from microsoft/josh/upstream-newpromptactions prompt actions: add extension points for folder and editor overrides --- .../chat/browser/promptSyntax/hookActions.ts | 78 +++++++++++++------ .../promptSyntax/newPromptFileActions.ts | 75 +++++++++++++----- 2 files changed, 108 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/hookActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/hookActions.ts index e37b11c22cd3d..00255f745086a 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/hookActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/hookActions.ts @@ -96,7 +96,8 @@ async function addHookToFile( fileService: IFileService, editorService: IEditorService, notificationService: INotificationService, - bulkEditService: IBulkEditService + bulkEditService: IBulkEditService, + openEditorOverride?: (resource: URI, options?: { selection?: ITextEditorSelection }) => Promise, ): Promise { // Parse existing file let hooksContent: { hooks: Record }; @@ -240,13 +241,17 @@ async function addHookToFile( const selection = findHookCommandSelection(jsonContent, keyToUse, newHookIndex, 'command'); // Open editor with selection (or re-focus if already open) - await editorService.openEditor({ - resource: hookFileUri, - options: { - selection, - pinned: false - } - }); + if (openEditorOverride) { + await openEditorOverride(hookFileUri, { selection }); + } else { + await editorService.openEditor({ + resource: hookFileUri, + options: { + selection, + pinned: false + } + }); + } } } @@ -290,12 +295,25 @@ const enum Step { EnterFilename = 5, } +/** + * Optional callbacks for customizing the hook creation and opening behaviour. + * The agentic editor passes these to open hooks in the embedded editor and + * track worktree files for auto-commit. + */ +export interface IHookQuickPickCallbacks { + /** Override how the hook file is opened. If not provided, uses editorService.openEditor. */ + readonly openEditor?: (resource: URI, options?: { selection?: ITextEditorSelection }) => Promise; + /** Called after a new hook file is created on disk. */ + readonly onHookFileCreated?: (uri: URI) => void; +} + /** * Shows the Configure Hooks quick pick UI, allowing the user to view, * open, or create hooks. Can be called from the action or slash command. */ export async function showConfigureHooksQuickPick( accessor: ServicesAccessor, + callbacks?: IHookQuickPickCallbacks, ): Promise { const promptsService = accessor.get(IPromptsService); const quickInputService = accessor.get(IQuickInputService); @@ -470,13 +488,17 @@ export async function showConfigureHooksQuickPick( } picker.hide(); - await editorService.openEditor({ - resource: entry.fileUri, - options: { - selection, - pinned: false - } - }); + if (callbacks?.openEditor) { + await callbacks.openEditor(entry.fileUri, { selection }); + } else { + await editorService.openEditor({ + resource: entry.fileUri, + options: { + selection, + pinned: false + } + }); + } return; } @@ -548,7 +570,8 @@ export async function showConfigureHooksQuickPick( fileService, editorService, notificationService, - bulkEditService + bulkEditService, + callbacks?.openEditor, ); return; } @@ -679,7 +702,8 @@ export async function showConfigureHooksQuickPick( fileService, editorService, notificationService, - bulkEditService + bulkEditService, + callbacks?.openEditor, ); return; } @@ -704,18 +728,24 @@ export async function showConfigureHooksQuickPick( const jsonContent = JSON.stringify(hooksContent, null, '\t'); await fileService.writeFile(hookFileUri, VSBuffer.fromString(jsonContent)); + callbacks?.onHookFileCreated?.(hookFileUri); + // Find the selection for the new hook's command field const selection = findHookCommandSelection(jsonContent, hookTypeKey, 0, 'command'); // Open editor with selection store.dispose(); - await editorService.openEditor({ - resource: hookFileUri, - options: { - selection, - pinned: false - } - }); + if (callbacks?.openEditor) { + await callbacks.openEditor(hookFileUri, { selection }); + } else { + await editorService.openEditor({ + resource: hookFileUri, + options: { + selection, + pinned: false + } + }); + } return; } } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts index 5928158fa19b4..5b3393e59d23c 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts @@ -5,7 +5,7 @@ import { isEqual } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; -import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { getCodeEditor, ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { SnippetController2 } from '../../../../../editor/contrib/snippet/browser/snippetController2.js'; import { localize, localize2 } from '../../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; @@ -26,9 +26,19 @@ import { askForPromptFileName } from './pickers/askForPromptName.js'; import { askForPromptSourceFolder } from './pickers/askForPromptSourceFolder.js'; import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; import { getCleanPromptName, SKILL_FILENAME } from '../../common/promptSyntax/config/promptFileLocations.js'; -import { Target } from '../../common/promptSyntax/service/promptsService.js'; +import { Target, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; import { getTarget } from '../../common/promptSyntax/languageProviders/promptValidator.js'; +/** + * Options to override the default folder-picker and editor-open behaviour + * of the new-prompt-file actions. The agentic editor passes these to open + * files in the embedded editor and pre-resolve the target folder. + */ +export interface INewPromptOptions { + readonly targetFolder?: URI; + readonly targetStorage?: PromptsStorage; + readonly openFile?: (uri: URI) => Promise; +} class AbstractNewPromptFileAction extends Action2 { @@ -49,7 +59,7 @@ class AbstractNewPromptFileAction extends Action2 { }); } - public override async run(accessor: ServicesAccessor) { + public override async run(accessor: ServicesAccessor, options?: INewPromptOptions) { const logService = accessor.get(ILogService); const openerService = accessor.get(IOpenerService); const commandService = accessor.get(ICommandService); @@ -59,27 +69,40 @@ class AbstractNewPromptFileAction extends Action2 { const fileService = accessor.get(IFileService); const instaService = accessor.get(IInstantiationService); - const selectedFolder = await instaService.invokeFunction(askForPromptSourceFolder, this.type); - if (!selectedFolder) { - return; + let folderUri: URI; + let storage: string; + if (options?.targetFolder) { + folderUri = options.targetFolder; + storage = options.targetStorage ?? PromptsStorage.local; + } else { + const selectedFolder = await instaService.invokeFunction(askForPromptSourceFolder, this.type); + if (!selectedFolder) { + return; + } + folderUri = selectedFolder.uri; + storage = selectedFolder.storage; } - const fileName = await instaService.invokeFunction(askForPromptFileName, this.type, selectedFolder.uri); + const fileName = await instaService.invokeFunction(askForPromptFileName, this.type, folderUri); if (!fileName) { return; } // create the prompt file - await fileService.createFolder(selectedFolder.uri); + await fileService.createFolder(folderUri); - const promptUri = URI.joinPath(selectedFolder.uri, fileName); + const promptUri = URI.joinPath(folderUri, fileName); await fileService.createFile(promptUri); - await openerService.open(promptUri); - const cleanName = getCleanPromptName(promptUri); - const editor = getCodeEditor(editorService.activeTextEditorControl); + let editor: ICodeEditor | null | undefined; + if (options?.openFile) { + editor = await options.openFile(promptUri); + } else { + await openerService.open(promptUri); + editor = getCodeEditor(editorService.activeTextEditorControl); + } if (editor && editor.hasModel() && isEqual(editor.getModel().uri, promptUri)) { SnippetController2.get(editor)?.apply([{ range: editor.getModel().getFullModelRange(), @@ -87,7 +110,7 @@ class AbstractNewPromptFileAction extends Action2 { }]); } - if (selectedFolder.storage !== 'user') { + if (storage !== 'user') { return; } @@ -247,16 +270,22 @@ class NewSkillFileAction extends Action2 { }); } - public override async run(accessor: ServicesAccessor) { + public override async run(accessor: ServicesAccessor, options?: INewPromptOptions) { const openerService = accessor.get(IOpenerService); const editorService = accessor.get(IEditorService); const fileService = accessor.get(IFileService); const instaService = accessor.get(IInstantiationService); const quickInputService = accessor.get(IQuickInputService); - const selectedFolder = await instaService.invokeFunction(askForPromptSourceFolder, PromptsType.skill); - if (!selectedFolder) { - return; + let folderUri: URI; + if (options?.targetFolder) { + folderUri = options.targetFolder; + } else { + const selectedFolder = await instaService.invokeFunction(askForPromptSourceFolder, PromptsType.skill); + if (!selectedFolder) { + return; + } + folderUri = selectedFolder.uri; } // Ask for skill name (will be the folder name) @@ -294,15 +323,19 @@ class NewSkillFileAction extends Action2 { const trimmedName = skillName.trim(); // Create the skill folder and SKILL.md file - const skillFolder = URI.joinPath(selectedFolder.uri, trimmedName); + const skillFolder = URI.joinPath(folderUri, trimmedName); await fileService.createFolder(skillFolder); const skillFileUri = URI.joinPath(skillFolder, SKILL_FILENAME); await fileService.createFile(skillFileUri); - await openerService.open(skillFileUri); - - const editor = getCodeEditor(editorService.activeTextEditorControl); + let editor: ICodeEditor | null | undefined; + if (options?.openFile) { + editor = await options.openFile(skillFileUri); + } else { + await openerService.open(skillFileUri); + editor = getCodeEditor(editorService.activeTextEditorControl); + } if (editor && editor.hasModel() && isEqual(editor.getModel().uri, skillFileUri)) { SnippetController2.get(editor)?.apply([{ range: editor.getModel().getFullModelRange(), From 73a9d49dea46f46484e84964b2d3fafa88fc257d Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Thu, 12 Feb 2026 16:03:46 -0800 Subject: [PATCH 12/16] Merge pull request #294779 from microsoft/digitarald/explore-agent-default-model Add Explore agent default model picker and refactor DefaultModelContribution --- .../contrib/chat/browser/chat.contribution.ts | 9 ++ .../chat/browser/defaultModelContribution.ts | 137 ++++++++++++++++++ .../chat/browser/exploreAgentDefaultModel.ts | 34 +++++ .../chat/browser/planAgentDefaultModel.ts | 103 ++----------- .../contrib/chat/common/constants.ts | 1 + .../browser/inlineChatDefaultModel.ts | 108 +++----------- 6 files changed, 214 insertions(+), 178 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/defaultModelContribution.ts create mode 100644 src/vs/workbench/contrib/chat/browser/exploreAgentDefaultModel.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index ff1604d72b9bf..035ee4ffe0801 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -143,6 +143,7 @@ import { ChatRepoInfoContribution } from './chatRepoInfo.js'; import { VALID_PROMPT_FOLDER_PATTERN } from '../common/promptSyntax/utils/promptFilesLocator.js'; import { ChatTipService, IChatTipService } from './chatTipService.js'; import { ChatQueuePickerRendering } from './widget/input/chatQueuePickerActionItem.js'; +import { ExploreAgentDefaultModel } from './exploreAgentDefaultModel.js'; import { PlanAgentDefaultModel } from './planAgentDefaultModel.js'; const toolReferenceNameEnumValues: string[] = []; @@ -626,6 +627,14 @@ configurationRegistry.registerConfiguration({ enumItemLabels: PlanAgentDefaultModel.modelLabels, markdownEnumDescriptions: PlanAgentDefaultModel.modelDescriptions }, + [ChatConfiguration.ExploreAgentDefaultModel]: { + type: 'string', + description: nls.localize('chat.exploreAgent.defaultModel.description', "Select the default language model to use for the Explore subagent from the available providers."), + default: '', + enum: ExploreAgentDefaultModel.modelIds, + enumItemLabels: ExploreAgentDefaultModel.modelLabels, + markdownEnumDescriptions: ExploreAgentDefaultModel.modelDescriptions + }, [ChatConfiguration.RequestQueueingEnabled]: { type: 'boolean', description: nls.localize('chat.requestQueuing.enabled.description', "When enabled, allows queuing additional messages while a request is in progress and steering the current request with a new message."), diff --git a/src/vs/workbench/contrib/chat/browser/defaultModelContribution.ts b/src/vs/workbench/contrib/chat/browser/defaultModelContribution.ts new file mode 100644 index 0000000000000..8af7a3622b94e --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/defaultModelContribution.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { localize } from '../../../../nls.js'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { ILanguageModelChatMetadata, ILanguageModelsService } from '../common/languageModels.js'; +import { DEFAULT_MODEL_PICKER_CATEGORY } from '../common/widget/input/modelPickerWidget.js'; + +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + +export interface DefaultModelArrays { + readonly modelIds: string[]; + readonly modelLabels: string[]; + readonly modelDescriptions: string[]; +} + +export interface DefaultModelContributionOptions { + /** Configuration key for the setting (used in schema notification). */ + readonly configKey: string; + /** Configuration section id for `notifyConfigurationSchemaUpdated`, or `undefined` to skip notification. */ + readonly configSectionId: string | undefined; + /** Log prefix, e.g. `'[PlanAgentDefaultModel]'`. */ + readonly logPrefix: string; + /** Additional filter beyond `isUserSelectable`. Return `true` to include the model. */ + readonly filter?: (metadata: ILanguageModelChatMetadata) => boolean; +} + +/** + * Creates the initial static arrays used by configuration registration code. + * The returned arrays are mutated in-place by {@link DefaultModelContribution}. + */ +export function createDefaultModelArrays(): DefaultModelArrays { + return { + modelIds: [''], + modelLabels: [localize('defaultModel', 'Auto (Vendor Default)')], + modelDescriptions: [localize('defaultModelDescription', "Use the vendor's default model")], + }; +} + +/** + * Shared base class for workbench contributions that populate a dynamic enum + * of language models for a settings picker. + */ +export abstract class DefaultModelContribution extends Disposable { + + constructor( + private readonly _arrays: DefaultModelArrays, + private readonly _options: DefaultModelContributionOptions, + @ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService, + @ILogService private readonly _logService: ILogService, + ) { + super(); + this._register(_languageModelsService.onDidChangeLanguageModels(() => this._updateModelValues())); + this._updateModelValues(); + } + + private _updateModelValues(): void { + const { modelIds, modelLabels, modelDescriptions } = this._arrays; + const { configKey, configSectionId, logPrefix, filter } = this._options; + + try { + // Clear arrays + modelIds.length = 0; + modelLabels.length = 0; + modelDescriptions.length = 0; + + // Add default/empty option + modelIds.push(''); + modelLabels.push(localize('defaultModel', 'Auto (Vendor Default)')); + modelDescriptions.push(localize('defaultModelDescription', "Use the vendor's default model")); + + const models: { identifier: string; metadata: ILanguageModelChatMetadata }[] = []; + const allModelIds = this._languageModelsService.getLanguageModelIds(); + + for (const modelId of allModelIds) { + try { + const metadata = this._languageModelsService.lookupLanguageModel(modelId); + if (metadata) { + models.push({ identifier: modelId, metadata }); + } else { + this._logService.warn(`${logPrefix} No metadata found for model ID: ${modelId}`); + } + } catch (e) { + this._logService.error(`${logPrefix} Error looking up model ${modelId}:`, e); + } + } + + const supportedModels = models.filter(model => { + if (!model.metadata?.isUserSelectable) { + return false; + } + if (filter && !filter(model.metadata)) { + return false; + } + return true; + }); + + supportedModels.sort((a, b) => { + const aCategory = a.metadata.modelPickerCategory ?? DEFAULT_MODEL_PICKER_CATEGORY; + const bCategory = b.metadata.modelPickerCategory ?? DEFAULT_MODEL_PICKER_CATEGORY; + + if (aCategory.order !== bCategory.order) { + return aCategory.order - bCategory.order; + } + + return a.metadata.name.localeCompare(b.metadata.name); + }); + + for (const model of supportedModels) { + try { + const qualifiedName = ILanguageModelChatMetadata.asQualifiedName(model.metadata); + modelIds.push(qualifiedName); + modelLabels.push(model.metadata.name); + modelDescriptions.push(model.metadata.tooltip ?? model.metadata.detail ?? ''); + } catch (e) { + this._logService.error(`${logPrefix} Error adding model ${model.metadata.name}:`, e); + } + } + + if (configSectionId) { + configurationRegistry.notifyConfigurationSchemaUpdated({ + id: configSectionId, + properties: { + [configKey]: {} + } + }); + } + } catch (e) { + this._logService.error(`${logPrefix} Error updating model values:`, e); + } + } +} diff --git a/src/vs/workbench/contrib/chat/browser/exploreAgentDefaultModel.ts b/src/vs/workbench/contrib/chat/browser/exploreAgentDefaultModel.ts new file mode 100644 index 0000000000000..b20d98e421564 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/exploreAgentDefaultModel.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogService } from '../../../../platform/log/common/log.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { ChatConfiguration } from '../common/constants.js'; +import { ILanguageModelsService } from '../common/languageModels.js'; +import { createDefaultModelArrays, DefaultModelContribution } from './defaultModelContribution.js'; + +const arrays = createDefaultModelArrays(); + +export class ExploreAgentDefaultModel extends DefaultModelContribution { + static readonly ID = 'workbench.contrib.exploreAgentDefaultModel'; + + static readonly modelIds = arrays.modelIds; + static readonly modelLabels = arrays.modelLabels; + static readonly modelDescriptions = arrays.modelDescriptions; + + constructor( + @ILanguageModelsService languageModelsService: ILanguageModelsService, + @ILogService logService: ILogService, + ) { + super(arrays, { + configKey: ChatConfiguration.ExploreAgentDefaultModel, + configSectionId: 'chatSidebar', + logPrefix: '[ExploreAgentDefaultModel]', + filter: metadata => !!metadata.capabilities?.toolCalling, + }, languageModelsService, logService); + } +} + +registerWorkbenchContribution2(ExploreAgentDefaultModel.ID, ExploreAgentDefaultModel, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/chat/browser/planAgentDefaultModel.ts b/src/vs/workbench/contrib/chat/browser/planAgentDefaultModel.ts index 37c49f0774fd8..e6a8cd319438b 100644 --- a/src/vs/workbench/contrib/chat/browser/planAgentDefaultModel.ts +++ b/src/vs/workbench/contrib/chat/browser/planAgentDefaultModel.ts @@ -3,104 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { localize } from '../../../../nls.js'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { ChatConfiguration } from '../common/constants.js'; -import { ILanguageModelChatMetadata, ILanguageModelsService } from '../common/languageModels.js'; -import { DEFAULT_MODEL_PICKER_CATEGORY } from '../common/widget/input/modelPickerWidget.js'; +import { ILanguageModelsService } from '../common/languageModels.js'; +import { createDefaultModelArrays, DefaultModelContribution } from './defaultModelContribution.js'; -const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); +const arrays = createDefaultModelArrays(); -export class PlanAgentDefaultModel extends Disposable { +export class PlanAgentDefaultModel extends DefaultModelContribution { static readonly ID = 'workbench.contrib.planAgentDefaultModel'; - static readonly configName = ChatConfiguration.PlanAgentDefaultModel; - static modelIds: string[] = ['']; - static modelLabels: string[] = [localize('defaultModel', 'Auto (Vendor Default)')]; - static modelDescriptions: string[] = [localize('defaultModelDescription', "Use the vendor's default model")]; + static readonly modelIds = arrays.modelIds; + static readonly modelLabels = arrays.modelLabels; + static readonly modelDescriptions = arrays.modelDescriptions; constructor( - @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, - @ILogService private readonly logService: ILogService, + @ILanguageModelsService languageModelsService: ILanguageModelsService, + @ILogService logService: ILogService, ) { - super(); - this._register(languageModelsService.onDidChangeLanguageModels(() => this._updateModelValues())); - this._updateModelValues(); - } - - private _updateModelValues(): void { - try { - // Clear arrays - PlanAgentDefaultModel.modelIds.length = 0; - PlanAgentDefaultModel.modelLabels.length = 0; - PlanAgentDefaultModel.modelDescriptions.length = 0; - - // Add default/empty option - PlanAgentDefaultModel.modelIds.push(''); - PlanAgentDefaultModel.modelLabels.push(localize('defaultModel', 'Auto (Vendor Default)')); - PlanAgentDefaultModel.modelDescriptions.push(localize('defaultModelDescription', "Use the vendor's default model")); - - const models: { identifier: string; metadata: ILanguageModelChatMetadata }[] = []; - const modelIds = this.languageModelsService.getLanguageModelIds(); - - for (const modelId of modelIds) { - try { - const metadata = this.languageModelsService.lookupLanguageModel(modelId); - if (metadata) { - models.push({ identifier: modelId, metadata }); - } else { - this.logService.warn(`[PlanAgentDefaultModel] No metadata found for model ID: ${modelId}`); - } - } catch (e) { - this.logService.error(`[PlanAgentDefaultModel] Error looking up model ${modelId}:`, e); - } - } - - const supportedModels = models.filter(model => { - if (!model.metadata?.isUserSelectable) { - return false; - } - if (!model.metadata.capabilities?.toolCalling) { - return false; - } - return true; - }); - - supportedModels.sort((a, b) => { - const aCategory = a.metadata.modelPickerCategory ?? DEFAULT_MODEL_PICKER_CATEGORY; - const bCategory = b.metadata.modelPickerCategory ?? DEFAULT_MODEL_PICKER_CATEGORY; - - if (aCategory.order !== bCategory.order) { - return aCategory.order - bCategory.order; - } - - return a.metadata.name.localeCompare(b.metadata.name); - }); - - for (const model of supportedModels) { - try { - const qualifiedName = `${model.metadata.name} (${model.metadata.vendor})`; - PlanAgentDefaultModel.modelIds.push(qualifiedName); - PlanAgentDefaultModel.modelLabels.push(model.metadata.name); - PlanAgentDefaultModel.modelDescriptions.push(model.metadata.tooltip ?? model.metadata.detail ?? ''); - } catch (e) { - this.logService.error(`[PlanAgentDefaultModel] Error adding model ${model.metadata.name}:`, e); - } - } - - configurationRegistry.notifyConfigurationSchemaUpdated({ - id: 'chatSidebar', - properties: { - [ChatConfiguration.PlanAgentDefaultModel]: {} - } - }); - } catch (e) { - this.logService.error('[PlanAgentDefaultModel] Error updating model values:', e); - } + super(arrays, { + configKey: ChatConfiguration.PlanAgentDefaultModel, + configSectionId: 'chatSidebar', + logPrefix: '[PlanAgentDefaultModel]', + filter: metadata => !!metadata.capabilities?.toolCalling, + }, languageModelsService, logService); } } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 2ac63ee13eabe..2440311f09bba 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -12,6 +12,7 @@ export enum ChatConfiguration { AIDisabled = 'chat.disableAIFeatures', AgentEnabled = 'chat.agent.enabled', PlanAgentDefaultModel = 'chat.planAgent.defaultModel', + ExploreAgentDefaultModel = 'chat.exploreAgent.defaultModel', RequestQueueingEnabled = 'chat.requestQueuing.enabled', RequestQueueingDefaultAction = 'chat.requestQueuing.defaultAction', AgentStatusEnabled = 'chat.agentsControl.enabled', diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatDefaultModel.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatDefaultModel.ts index 20286d86cd720..5fd53270237b5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatDefaultModel.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatDefaultModel.ts @@ -5,104 +5,32 @@ import { localize } from '../../../../nls.js'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { ILanguageModelsService, ILanguageModelChatMetadata } from '../../chat/common/languageModels.js'; +import { ILanguageModelsService } from '../../chat/common/languageModels.js'; import { InlineChatConfigKeys } from '../common/inlineChat.js'; -import { ILogService } from '../../../../platform/log/common/log.js'; -import { DEFAULT_MODEL_PICKER_CATEGORY } from '../../chat/common/widget/input/modelPickerWidget.js'; +import { createDefaultModelArrays, DefaultModelContribution } from '../../chat/browser/defaultModelContribution.js'; -export class InlineChatDefaultModel extends Disposable { +const arrays = createDefaultModelArrays(); + +export class InlineChatDefaultModel extends DefaultModelContribution { static readonly ID = 'workbench.contrib.inlineChatDefaultModel'; - static readonly configName = InlineChatConfigKeys.DefaultModel; - static modelIds: string[] = ['']; - static modelLabels: string[] = [localize('defaultModel', 'Auto (Vendor Default)')]; - static modelDescriptions: string[] = [localize('defaultModelDescription', 'Use the vendor\'s default model')]; + static readonly modelIds = arrays.modelIds; + static readonly modelLabels = arrays.modelLabels; + static readonly modelDescriptions = arrays.modelDescriptions; constructor( - @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, - @ILogService private readonly logService: ILogService, + @ILanguageModelsService languageModelsService: ILanguageModelsService, + @ILogService logService: ILogService, ) { - super(); - this._register(languageModelsService.onDidChangeLanguageModels(() => this._updateModelValues())); - this._updateModelValues(); - } - - private _updateModelValues(): void { - try { - // Clear arrays - InlineChatDefaultModel.modelIds.length = 0; - InlineChatDefaultModel.modelLabels.length = 0; - InlineChatDefaultModel.modelDescriptions.length = 0; - - // Add default/empty option - InlineChatDefaultModel.modelIds.push(''); - InlineChatDefaultModel.modelLabels.push(localize('defaultModel', 'Auto (Vendor Default)')); - InlineChatDefaultModel.modelDescriptions.push(localize('defaultModelDescription', 'Use the vendor\'s default model')); - - // Get all available models - const modelIds = this.languageModelsService.getLanguageModelIds(); - - const models: { identifier: string; metadata: ILanguageModelChatMetadata }[] = []; - - // Look up each model's metadata - for (const modelId of modelIds) { - try { - const metadata = this.languageModelsService.lookupLanguageModel(modelId); - if (metadata) { - models.push({ identifier: modelId, metadata }); - } else { - this.logService.warn(`[InlineChatDefaultModel] No metadata found for model ID: ${modelId}`); - } - } catch (e) { - this.logService.error(`[InlineChatDefaultModel] Error looking up model ${modelId}:`, e); - } - } - - // Filter models that are: - // 1. User selectable - // 2. Support tool calling (required for inline chat v2) - const supportedModels = models.filter(model => { - if (!model.metadata?.isUserSelectable) { - return false; - } - // Check if model supports inline chat - needs tool calling capability - if (!model.metadata.capabilities?.toolCalling) { - return false; - } - return true; - }); - - // Sort by category order, then alphabetically by name within each category - supportedModels.sort((a, b) => { - const aCategory = a.metadata.modelPickerCategory ?? DEFAULT_MODEL_PICKER_CATEGORY; - const bCategory = b.metadata.modelPickerCategory ?? DEFAULT_MODEL_PICKER_CATEGORY; - - // First sort by category order - if (aCategory.order !== bCategory.order) { - return aCategory.order - bCategory.order; - } - - // Then sort by name within the same category - return a.metadata.name.localeCompare(b.metadata.name); - }); - - // Populate arrays with filtered models - for (const model of supportedModels) { - try { - const qualifiedName = `${model.metadata.name} (${model.metadata.vendor})`; - InlineChatDefaultModel.modelIds.push(qualifiedName); - InlineChatDefaultModel.modelLabels.push(model.metadata.name); - InlineChatDefaultModel.modelDescriptions.push(model.metadata.tooltip ?? model.metadata.detail ?? ''); - } catch (e) { - this.logService.error(`[InlineChatDefaultModel] Error adding model ${model.metadata.name}:`, e); - } - } - } catch (e) { - this.logService.error('[InlineChatDefaultModel] Error updating model values:', e); - } + super(arrays, { + configKey: InlineChatConfigKeys.DefaultModel, + configSectionId: 'inlineChat', + logPrefix: '[InlineChatDefaultModel]', + filter: metadata => !!metadata.capabilities?.toolCalling, + }, languageModelsService, logService); } } @@ -111,7 +39,7 @@ registerWorkbenchContribution2(InlineChatDefaultModel.ID, InlineChatDefaultModel Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ ...{ id: 'inlineChat', title: localize('inlineChatConfigurationTitle', 'Inline Chat'), order: 30, type: 'object' }, properties: { - [InlineChatDefaultModel.configName]: { + [InlineChatConfigKeys.DefaultModel]: { description: localize('inlineChatDefaultModelDescription', "Select the default language model to use for inline chat from the available providers. Model names may include the provider in parentheses, for example 'Claude Haiku 4.5 (copilot)'."), type: 'string', default: '', From 104123aaee7dc537b3f725c93112e96c714589f7 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 12 Feb 2026 16:56:59 -0800 Subject: [PATCH 13/16] Merge pull request #295063 from microsoft/dev/dmitriv/flaky-download-fix Add retries for target download logic in sanity tests --- test/sanity/src/context.ts | 44 +++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/test/sanity/src/context.ts b/test/sanity/src/context.ts index 95273f9f7fc18..8ee3cd5bb0d76 100644 --- a/test/sanity/src/context.ts +++ b/test/sanity/src/context.ts @@ -298,21 +298,39 @@ export class TestContext { const { url, sha256hash } = await this.fetchMetadata(target); const filePath = path.join(this.createTempDir(), path.basename(url)); - this.log(`Downloading ${url} to ${filePath}`); - const { body } = await this.fetchNoErrors(url); - - const stream = fs.createWriteStream(filePath); - await new Promise((resolve, reject) => { - body.on('error', reject); - stream.on('error', reject); - stream.on('finish', resolve); - body.pipe(stream); - }); + const maxRetries = 5; + let lastError: Error | undefined; - this.log(`Downloaded ${url} to ${filePath}`); - this.validateSha256Hash(filePath, sha256hash); + for (let attempt = 0; attempt < maxRetries; attempt++) { + if (attempt > 0) { + const delay = Math.pow(2, attempt - 1) * 1000; + this.log(`Retrying download (attempt ${attempt + 1}/${maxRetries}) after ${delay}ms`); + await new Promise(resolve => setTimeout(resolve, delay)); + } - return filePath; + try { + this.log(`Downloading ${url} to ${filePath}`); + const { body } = await this.fetchNoErrors(url); + + const stream = fs.createWriteStream(filePath); + await new Promise((resolve, reject) => { + body.on('error', reject); + stream.on('error', reject); + stream.on('finish', resolve); + body.pipe(stream); + }); + + this.log(`Downloaded ${url} to ${filePath}`); + this.validateSha256Hash(filePath, sha256hash); + + return filePath; + } catch (error) { + lastError = error instanceof Error ? error : new Error(String(error)); + this.log(`Download attempt ${attempt + 1} failed: ${lastError.message}`); + } + } + + this.error(`Failed to download ${url} after ${maxRetries} attempts: ${lastError?.message}`); } /** From 1531635f6adb34162e60803ecd9f7ce5a32e6ee7 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 12 Feb 2026 17:39:07 -0800 Subject: [PATCH 14/16] Merge pull request #295059 from microsoft/dev/dmitriv/fetch-prevent-ad-redirects Prevent tracking redirects in fetch tool --- .../electron-main/webPageLoader.ts | 14 ++- .../test/electron-main/webPageLoader.test.ts | 96 ++++++++++++++++++- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/webContentExtractor/electron-main/webPageLoader.ts b/src/vs/platform/webContentExtractor/electron-main/webPageLoader.ts index c176090f9d84a..14157f0fd1a28 100644 --- a/src/vs/platform/webContentExtractor/electron-main/webPageLoader.ts +++ b/src/vs/platform/webContentExtractor/electron-main/webPageLoader.ts @@ -84,8 +84,8 @@ export class WebPageLoader extends Disposable { .once('did-start-loading', this.onStartLoading.bind(this)) .once('did-finish-load', this.onFinishLoad.bind(this)) .once('did-fail-load', this.onFailLoad.bind(this)) - .once('will-navigate', this.onRedirect.bind(this)) - .once('will-redirect', this.onRedirect.bind(this)) + .on('will-navigate', this.onRedirect.bind(this)) + .on('will-redirect', this.onRedirect.bind(this)) .on('select-client-certificate', (event) => event.preventDefault()); this._window.webContents.session.webRequest.onBeforeSendHeaders( @@ -262,6 +262,9 @@ export class WebPageLoader extends Disposable { if (statusCode === -3) { this.trace(`Ignoring ERR_ABORTED (-3) as it may be caused by CSP or other measures`); void this._queue.queue(() => this.extractContent()); + } else if (statusCode === -27) { + this.trace(`Ignoring ERR_BLOCKED_BY_CLIENT (-27) as it may be caused by ad-blockers or similar extensions`); + void this._queue.queue(() => this.extractContent()); } else { void this._queue.queue(() => this.extractContent({ status: 'error', statusCode, error })); } @@ -289,6 +292,13 @@ export class WebPageLoader extends Disposable { return; } + // Ignore script-initiated navigation (ads/trackers etc) + if (this._didFinishLoad) { + this.trace(`Blocking post-load navigation to ${url} (likely ad/tracker script)`); + event.preventDefault(); + return; + } + // Otherwise, prevent redirect and report it event.preventDefault(); this._onResult({ status: 'redirect', toURI }); diff --git a/src/vs/platform/webContentExtractor/test/electron-main/webPageLoader.test.ts b/src/vs/platform/webContentExtractor/test/electron-main/webPageLoader.test.ts index 6c86c592226e8..5de4f73f0d310 100644 --- a/src/vs/platform/webContentExtractor/test/electron-main/webPageLoader.test.ts +++ b/src/vs/platform/webContentExtractor/test/electron-main/webPageLoader.test.ts @@ -19,6 +19,7 @@ interface MockElectronEvent { class MockWebContents { private readonly _listeners = new Map void)[]>(); + private readonly _onceListeners = new Set<(...args: unknown[]) => void>(); public readonly debugger: MockDebugger; public loadURL = sinon.stub().resolves(); public getTitle = sinon.stub().returns('Test Page Title'); @@ -41,6 +42,7 @@ class MockWebContents { this._listeners.set(event, []); } this._listeners.get(event)!.push(listener); + this._onceListeners.add(listener); return this; } @@ -57,7 +59,16 @@ class MockWebContents { for (const listener of listeners) { listener(...args); } - this._listeners.delete(event); + // Remove once listeners, keep on listeners + const remaining = listeners.filter(l => !this._onceListeners.has(l)); + for (const listener of listeners) { + this._onceListeners.delete(listener); + } + if (remaining.length > 0) { + this._listeners.set(event, remaining); + } else { + this._listeners.delete(event); + } } beginFrameSubscription(_onlyDirty: boolean, callback: () => void): void { @@ -232,6 +243,27 @@ suite('WebPageLoader', () => { } })); + test('ERR_BLOCKED_BY_CLIENT is ignored and content extraction continues', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + const uri = URI.parse('https://example.com/page'); + + const loader = createWebPageLoader(uri); + setupDebuggerMock(); + + const loadPromise = loader.load(); + + // Simulate ERR_BLOCKED_BY_CLIENT (-27) which should be ignored + const mockEvent: MockElectronEvent = {}; + window.webContents.emit('did-fail-load', mockEvent, -27, 'ERR_BLOCKED_BY_CLIENT'); + + const result = await loadPromise; + + // ERR_BLOCKED_BY_CLIENT should not cause an error status, content should be extracted + assert.strictEqual(result.status, 'ok'); + if (result.status === 'ok') { + assert.ok(result.result.includes('Test content from page')); + } + })); + //#endregion //#region Redirect Tests @@ -394,6 +426,68 @@ suite('WebPageLoader', () => { assert.strictEqual(result.status, 'ok'); })); + test('post-load navigation to different domain is blocked silently and content is extracted', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + const uri = URI.parse('https://example.com/page'); + const adRedirectUrl = 'https://eus.rubiconproject.com/usync.html?p=12776'; + + const loader = createWebPageLoader(uri, { followRedirects: false }); + setupDebuggerMock(); + + const loadPromise = loader.load(); + + // Simulate successful page load + window.webContents.emit('did-start-loading'); + window.webContents.emit('did-finish-load'); + + // Simulate ad/tracker script redirecting after page load + const mockEvent: MockElectronEvent = { + preventDefault: sinon.stub() + }; + window.webContents.emit('will-navigate', mockEvent, adRedirectUrl); + + const result = await loadPromise; + + // Navigation should be prevented + assert.ok((mockEvent.preventDefault!).called); + // But result should be ok (content extracted), NOT redirect + assert.strictEqual(result.status, 'ok'); + assert.ok(result.result.includes('Test content from page')); + })); + + test('initial same-domain navigation is allowed but later cross-domain navigation is blocked', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + const uri = URI.parse('https://example.com/page'); + const sameDomainUrl = 'https://example.com/otherpage'; + const crossDomainUrl = 'https://eus.rubiconproject.com/usync.html?p=12776'; + + const loader = createWebPageLoader(uri, { followRedirects: false }); + setupDebuggerMock(); + + const loadPromise = loader.load(); + + // First navigation: same-authority, should be allowed + const initialEvent: MockElectronEvent = { + preventDefault: sinon.stub() + }; + window.webContents.emit('will-navigate', initialEvent, sameDomainUrl); + assert.ok(!(initialEvent.preventDefault!).called); + + // Simulate successful page load + window.webContents.emit('did-start-loading'); + window.webContents.emit('did-finish-load'); + + // Second navigation: cross-domain after load, should be blocked + const crossDomainEvent: MockElectronEvent = { + preventDefault: sinon.stub() + }; + window.webContents.emit('will-navigate', crossDomainEvent, crossDomainUrl); + + const result = await loadPromise; + + assert.ok((crossDomainEvent.preventDefault!).called); + assert.strictEqual(result.status, 'ok'); + assert.ok(result.result.includes('Test content from page')); + })); + test('redirect to non-trusted domain is blocked', async () => { const uri = URI.parse('https://example.com/page'); const redirectUrl = 'https://untrusted-domain.com/redirected'; From 92d3b378a08369596fb5ef9993727a6b34c1c2ae Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 12 Feb 2026 18:09:06 -0800 Subject: [PATCH 15/16] Rename `user-invokable` to `user-invocable` (#295058) * rename * tests * fixes * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix test * Update src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../chatCustomizationDiagnosticsAction.ts | 10 +- .../input/editor/chatInputCompletions.ts | 8 +- .../contrib/chat/common/chatModes.ts | 4 +- .../computeAutomaticInstructions.ts | 8 +- .../promptHeaderAutocompletion.ts | 2 +- .../languageProviders/promptHovers.ts | 2 +- .../languageProviders/promptValidator.ts | 32 +++-- .../common/promptSyntax/promptFileParser.ts | 6 +- .../promptSyntax/service/promptsService.ts | 16 +-- .../service/promptsServiceImpl.ts | 16 +-- .../promptHeaderAutocompletion.test.ts | 12 +- .../languageProviders/promptHovers.test.ts | 8 +- .../languageProviders/promptValidator.test.ts | 73 ++++++----- .../chat/test/common/chatModeService.test.ts | 12 +- .../computeAutomaticInstructions.test.ts | 8 +- .../service/promptFileParser.test.ts | 44 +++++++ .../service/promptsService.test.ts | 116 +++++++++--------- .../builtinTools/runSubagentTool.test.ts | 4 +- 18 files changed, 226 insertions(+), 155 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts index bb72913ae98e3..fe17d4b9cd535 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts @@ -87,8 +87,8 @@ export interface IFileStatusInfo { overwrittenBy?: string; /** Extension ID if this file comes from an extension */ extensionId?: string; - /** If true, hidden from / menu (user-invokable: false) */ - userInvokable?: boolean; + /** If false, hidden from / menu (user-invocable: false) */ + userInvocable?: boolean; /** If true, won't be auto-loaded by agent (disable-model-invocation: true) */ disableModelInvocation?: boolean; } @@ -466,7 +466,7 @@ function convertDiscoveryResultToFileStatus(result: IPromptFileDiscoveryResult): name: result.name, storage: result.storage, extensionId: result.extensionId, - userInvokable: result.userInvokable, + userInvocable: result.userInvocable, disableModelInvocation: result.disableModelInvocation }; } @@ -789,8 +789,8 @@ function getSkillFlags(file: IFileStatusInfo, type: PromptsType): string { flags.push(`${ICON_MANUAL} *${nls.localize('status.skill.manualOnly', 'manual only')}*`); } - // userInvokable: false means hidden from / menu - if (file.userInvokable === false) { + // userInvocable: false means hidden from / menu + if (file.userInvocable === false) { flags.push(`${ICON_HIDDEN} *${nls.localize('status.skill.hiddenFromMenu', 'hidden from menu')}*`); } diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts index fdadd6673579c..65afc29143b0f 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts @@ -201,14 +201,14 @@ class SlashCommandCompletions extends Disposable { return null; } - // Filter out commands that are not user-invokable (hidden from / menu) - const userInvokableCommands = promptCommands.filter(c => c.parsedPromptFile?.header?.userInvokable !== false); - if (userInvokableCommands.length === 0) { + // Filter out commands that are not user-invocable (hidden from / menu) + const userInvocableCommands = promptCommands.filter(c => c.parsedPromptFile?.header?.userInvocable !== false); + if (userInvocableCommands.length === 0) { return null; } return { - suggestions: userInvokableCommands.map((c, i): CompletionItem => { + suggestions: userInvocableCommands.map((c, i): CompletionItem => { const label = `/${c.name}`; const description = c.description; return { diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index f4d5712527aca..a099229a2702a 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -122,7 +122,7 @@ export class ChatModeService extends Disposable implements IChatModeService { agentInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, handOffs: cachedMode.handOffs, target: cachedMode.target ?? Target.Undefined, - visibility: cachedMode.visibility ?? { userInvokable: true, agentInvokable: cachedMode.infer !== false }, + visibility: cachedMode.visibility ?? { userInvocable: true, agentInvocable: cachedMode.infer !== false }, agents: cachedMode.agents, source: reviveChatModeSource(cachedMode.source) ?? { storage: PromptsStorage.local } }; @@ -154,7 +154,7 @@ export class ChatModeService extends Disposable implements IChatModeService { const seenUris = new Set(); for (const customMode of customModes) { - if (!customMode.visibility.userInvokable) { + if (!customMode.visibility.userInvocable) { continue; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index 2ca88074c2675..50c007fc1168d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -349,8 +349,8 @@ export class ComputeAutomaticInstructions { const agentSkills = await this._promptsService.findAgentSkills(token); // Filter out skills with disableModelInvocation=true (they can only be triggered manually via /name) - const modelInvokableSkills = agentSkills?.filter(skill => !skill.disableModelInvocation); - if (modelInvokableSkills && modelInvokableSkills.length > 0) { + const modelInvocableSkills = agentSkills?.filter(skill => !skill.disableModelInvocation); + if (modelInvocableSkills && modelInvocableSkills.length > 0) { const useSkillAdherencePrompt = this._configurationService.getValue(PromptsConfig.USE_SKILL_ADHERENCE_PROMPT); entries.push(''); if (useSkillAdherencePrompt) { @@ -372,7 +372,7 @@ export class ComputeAutomaticInstructions { entries.push('Each skill comes with a description of the topic and a file path that contains the detailed instructions.'); entries.push(`When a user asks you to perform a task that falls within the domain of a skill, use the ${readTool.variable} tool to acquire the full instructions from the file URI.`); } - for (const skill of modelInvokableSkills) { + for (const skill of modelInvocableSkills) { entries.push(''); entries.push(`${skill.name}`); if (skill.description) { @@ -387,7 +387,7 @@ export class ComputeAutomaticInstructions { if (runSubagentTool && this._configurationService.getValue(ChatConfiguration.SubagentToolCustomAgents)) { const canUseAgent = (() => { if (!this._enabledSubagents || this._enabledSubagents.includes('*')) { - return (agent: ICustomAgent) => agent.visibility.agentInvokable; + return (agent: ICustomAgent) => agent.visibility.agentInvocable; } else { const subagents = this._enabledSubagents; return (agent: ICustomAgent) => subagents.includes(agent.name); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index 9035e6bf40400..3ca82e90d52ca 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -299,7 +299,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { return [{ name: '["*"]' }]; } break; - case PromptHeaderAttributes.userInvokable: + case PromptHeaderAttributes.userInvocable: if (promptType === PromptsType.agent || promptType === PromptsType.skill) { return [{ name: 'true' }, { name: 'false' }]; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index c1df4ed4ab0c6..662ce1962657e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -87,7 +87,7 @@ export class PromptHoverProvider implements HoverProvider { case PromptHeaderAttributes.handOffs: return this.getHandsOffHover(attribute, position, target); case PromptHeaderAttributes.infer: - return this.createHover(description + '\n\n' + localize('promptHeader.attribute.infer.hover', 'Deprecated: Use `user-invokable` and `disable-model-invocation` instead.'), attribute.range); + return this.createHover(description + '\n\n' + localize('promptHeader.attribute.infer.hover', 'Deprecated: Use `user-invocable` and `disable-model-invocation` instead.'), attribute.range); default: return this.createHover(description, attribute.range); } 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 802e88203dd31..55559b5b04fea 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -186,6 +186,7 @@ export class PromptValidator { case PromptsType.agent: { this.validateTarget(attributes, report); this.validateInfer(attributes, report); + this.validateUserInvocable(attributes, report); this.validateUserInvokable(attributes, report); this.validateDisableModelInvocation(attributes, report); this.validateTools(attributes, ChatModeKind.Agent, target, report); @@ -200,6 +201,7 @@ export class PromptValidator { } case PromptsType.skill: + this.validateUserInvocable(attributes, report); this.validateUserInvokable(attributes, report); this.validateDisableModelInvocation(attributes, report); break; @@ -604,7 +606,7 @@ export class PromptValidator { if (!attribute) { return; } - report(toMarker(localize('promptValidator.inferDeprecated', "The 'infer' attribute is deprecated in favour of 'user-invokable' and 'disable-model-invocation'."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.inferDeprecated', "The 'infer' attribute is deprecated in favour of 'user-invocable' and 'disable-model-invocation'."), attribute.value.range, MarkerSeverity.Error)); } private validateTarget(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { @@ -627,15 +629,23 @@ export class PromptValidator { } } - private validateUserInvokable(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { - const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.userInvokable); + private validateUserInvocable(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.userInvocable); if (!attribute) { return; } if (attribute.value.type !== 'boolean') { - report(toMarker(localize('promptValidator.userInvokableMustBeBoolean', "The 'user-invokable' attribute must be a boolean."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.userInvocableMustBeBoolean', "The 'user-invocable' attribute must be a boolean."), attribute.value.range, MarkerSeverity.Error)); + return; + } + } + + private validateUserInvokable(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.userInvokable); + if (!attribute) { return; } + report(toMarker(localize('promptValidator.userInvokableDeprecated', "The 'user-invokable' attribute is deprecated. Use 'user-invocable' instead."), attribute.range, MarkerSeverity.Warning)); } private validateDisableModelInvocation(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { @@ -690,8 +700,8 @@ export class PromptValidator { const allAttributeNames: Record = { [PromptsType.prompt]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.mode, PromptHeaderAttributes.agent, PromptHeaderAttributes.argumentHint], [PromptsType.instructions]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo, PromptHeaderAttributes.excludeAgent], - [PromptsType.agent]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target, PromptHeaderAttributes.infer, PromptHeaderAttributes.agents, PromptHeaderAttributes.userInvokable, PromptHeaderAttributes.disableModelInvocation], - [PromptsType.skill]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.license, PromptHeaderAttributes.compatibility, PromptHeaderAttributes.metadata, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.userInvokable, PromptHeaderAttributes.disableModelInvocation], + [PromptsType.agent]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target, PromptHeaderAttributes.infer, PromptHeaderAttributes.agents, PromptHeaderAttributes.userInvocable, PromptHeaderAttributes.userInvokable, PromptHeaderAttributes.disableModelInvocation], + [PromptsType.skill]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.license, PromptHeaderAttributes.compatibility, PromptHeaderAttributes.metadata, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.userInvocable, PromptHeaderAttributes.userInvokable, PromptHeaderAttributes.disableModelInvocation], [PromptsType.hook]: [], // hooks are JSON files, not markdown with YAML frontmatter }; const githubCopilotAgentAttributeNames = [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.tools, PromptHeaderAttributes.target, GithubPromptHeaderAttributes.mcpServers, PromptHeaderAttributes.infer]; @@ -718,7 +728,7 @@ export function getValidAttributeNames(promptType: PromptsType, includeNonRecomm } export function isNonRecommendedAttribute(attributeName: string): boolean { - return attributeName === PromptHeaderAttributes.advancedOptions || attributeName === PromptHeaderAttributes.excludeAgent || attributeName === PromptHeaderAttributes.mode || attributeName === PromptHeaderAttributes.infer; + return attributeName === PromptHeaderAttributes.advancedOptions || attributeName === PromptHeaderAttributes.excludeAgent || attributeName === PromptHeaderAttributes.mode || attributeName === PromptHeaderAttributes.infer || attributeName === PromptHeaderAttributes.userInvokable; } export function getAttributeDescription(attributeName: string, promptType: PromptsType, target: Target): string | undefined { @@ -749,8 +759,8 @@ export function getAttributeDescription(attributeName: string, promptType: Promp return localize('promptHeader.skill.description', 'The description of the skill. The description is added to every request and will be used by the agent to decide when to load the skill.'); case PromptHeaderAttributes.argumentHint: return localize('promptHeader.skill.argumentHint', 'Hint shown during autocomplete to indicate expected arguments. Example: [issue-number] or [filename] [format]'); - case PromptHeaderAttributes.userInvokable: - return localize('promptHeader.skill.userInvokable', 'Set to false to hide from the / menu. Use for background knowledge users should not invoke directly. Default: true.'); + case PromptHeaderAttributes.userInvocable: + return localize('promptHeader.skill.userInvocable', 'Set to false to hide from the / menu. Use for background knowledge users should not invoke directly. Default: true.'); case PromptHeaderAttributes.disableModelInvocation: return localize('promptHeader.skill.disableModelInvocation', 'Set to true to prevent the agent from automatically loading this skill. Use for workflows you want to trigger manually with /name. Default: false.'); } @@ -775,8 +785,8 @@ export function getAttributeDescription(attributeName: string, promptType: Promp return localize('promptHeader.agent.infer', 'Controls visibility of the agent.'); case PromptHeaderAttributes.agents: return localize('promptHeader.agent.agents', 'One or more agents that this agent can use as subagents. Use \'*\' to specify all available agents.'); - case PromptHeaderAttributes.userInvokable: - return localize('promptHeader.agent.userInvokable', 'Whether the agent can be selected and invoked by users in the UI.'); + case PromptHeaderAttributes.userInvocable: + return localize('promptHeader.agent.userInvocable', 'Whether the agent can be selected and invoked by users in the UI.'); case PromptHeaderAttributes.disableModelInvocation: return localize('promptHeader.agent.disableModelInvocation', 'If true, prevents the agent from being invoked as a subagent.'); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index 778cf3a82454b..bb0bc23424442 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -82,6 +82,7 @@ export namespace PromptHeaderAttributes { export const metadata = 'metadata'; export const agents = 'agents'; export const userInvokable = 'user-invokable'; + export const userInvocable = 'user-invocable'; export const disableModelInvocation = 'disable-model-invocation'; } @@ -327,8 +328,9 @@ export class PromptHeader { return this.getStringArrayAttribute(PromptHeaderAttributes.agents); } - public get userInvokable(): boolean | undefined { - return this.getBooleanAttribute(PromptHeaderAttributes.userInvokable); + public get userInvocable(): boolean | undefined { + // TODO: user-invokable is deprecated, remove later and only keep user-invocable + return this.getBooleanAttribute(PromptHeaderAttributes.userInvocable) ?? this.getBooleanAttribute(PromptHeaderAttributes.userInvokable); } public get disableModelInvocation(): boolean | undefined { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index f59d436d4fd2b..8ee90218f2b49 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -125,16 +125,16 @@ export type IAgentSource = { * - 'hidden': neither in picker nor usable as subagent */ export type ICustomAgentVisibility = { - readonly userInvokable: boolean; - readonly agentInvokable: boolean; + readonly userInvocable: boolean; + readonly agentInvocable: boolean; }; export function isCustomAgentVisibility(obj: unknown): obj is ICustomAgentVisibility { if (typeof obj !== 'object' || obj === null) { return false; } - const v = obj as { userInvokable?: unknown; agentInvokable?: unknown }; - return typeof v.userInvokable === 'boolean' && typeof v.agentInvokable === 'boolean'; + const v = obj as { userInvocable?: unknown; agentInvocable?: unknown }; + return typeof v.userInvocable === 'boolean' && typeof v.agentInvocable === 'boolean'; } export enum Target { @@ -181,7 +181,7 @@ export interface ICustomAgent { readonly target: Target; /** - * What visibility the agent has (user invokable, subagent invokable). + * What visibility the agent has (user invocable, subagent invocable). */ readonly visibility: ICustomAgentVisibility; @@ -235,7 +235,7 @@ export interface IAgentSkill { * If false, the skill is hidden from the / menu. * Use for background knowledge users shouldn't invoke directly. */ - readonly userInvokable: boolean; + readonly userInvocable: boolean; } /** @@ -291,8 +291,8 @@ export interface IPromptFileDiscoveryResult { readonly duplicateOf?: URI; /** Extension ID if from extension */ readonly extensionId?: string; - /** If true, the skill is hidden from the / menu (user-invokable: false) */ - readonly userInvokable?: boolean; + /** Whether the skill is user-invocable in the / menu (set user-invocable: false to hide it) */ + readonly userInvocable?: boolean; /** If true, the skill won't be automatically loaded by the agent (disable-model-invocation: true) */ readonly disableModelInvocation?: boolean; } 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 257cb5fdc57b6..25ba3b58bd7fa 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -579,11 +579,11 @@ export class PromptsService extends Disposable implements IPromptsService { const source: IAgentSource = IAgentSource.fromPromptPath(promptPath); if (!ast.header) { - return { uri, name, agentInstructions, source, target, visibility: { userInvokable: true, agentInvokable: true } }; + return { uri, name, agentInstructions, source, target, visibility: { userInvocable: true, agentInvocable: true } }; } const visibility = { - userInvokable: ast.header.userInvokable !== false, - agentInvokable: ast.header.infer === true || ast.header.disableModelInvocation !== true, + userInvocable: ast.header.userInvocable !== false, + agentInvocable: ast.header.infer === true || ast.header.disableModelInvocation !== true, } satisfies ICustomAgentVisibility; let model = ast.header.model; @@ -916,7 +916,7 @@ export class PromptsService extends Disposable implements IPromptsService { name: file.name, description: sanitizedDescription, disableModelInvocation: file.disableModelInvocation ?? false, - userInvokable: file.userInvokable ?? true + userInvocable: file.userInvocable ?? true }); } } @@ -1124,10 +1124,10 @@ export class PromptsService extends Disposable implements IPromptsService { * Returns the discovery results and a map of skill counts by source type for telemetry. */ private async computeSkillDiscoveryInfo(token: CancellationToken): Promise<{ - files: (IPromptFileDiscoveryResult & { description?: string; source?: PromptFileSource; disableModelInvocation?: boolean; userInvokable?: boolean })[]; + files: (IPromptFileDiscoveryResult & { description?: string; source?: PromptFileSource; disableModelInvocation?: boolean; userInvocable?: boolean })[]; skillsBySource: Map; }> { - const files: (IPromptFileDiscoveryResult & { description?: string; source?: PromptFileSource; disableModelInvocation?: boolean; userInvokable?: boolean })[] = []; + const files: (IPromptFileDiscoveryResult & { description?: string; source?: PromptFileSource; disableModelInvocation?: boolean; userInvocable?: boolean })[] = []; const skillsBySource = new Map(); const seenNames = new Set(); const nameToUri = new Map(); @@ -1206,8 +1206,8 @@ export class PromptsService extends Disposable implements IPromptsService { seenNames.add(sanitizedName); nameToUri.set(sanitizedName, uri); const disableModelInvocation = parsedFile.header?.disableModelInvocation === true; - const userInvokable = parsedFile.header?.userInvokable !== false; - files.push({ uri, storage, status: 'loaded', name: sanitizedName, description, extensionId, source, disableModelInvocation, userInvokable }); + const userInvocable = parsedFile.header?.userInvocable !== false; + files.push({ uri, storage, status: 'loaded', name: sanitizedName, description, extensionId, source, disableModelInvocation, userInvocable }); // Track skill type skillsBySource.set(source, (skillsBySource.get(source) || 0) + 1); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHeaderAutocompletion.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHeaderAutocompletion.test.ts index 36a6634e5642e..db6a9d11f99ae 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHeaderAutocompletion.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHeaderAutocompletion.test.ts @@ -76,7 +76,7 @@ suite('PromptHeaderAutocompletion', () => { uri: URI.parse('myFs://.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local }, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; const parser = new PromptFileParser(); @@ -145,7 +145,7 @@ suite('PromptHeaderAutocompletion', () => { { label: 'name', result: 'name: $0' }, { label: 'target', result: 'target: ${0:vscode}' }, { label: 'tools', result: 'tools: ${0:[]}' }, - { label: 'user-invokable', result: 'user-invokable: ${0:true}' }, + { label: 'user-invocable', result: 'user-invocable: ${0:true}' }, ].sort(sortByLabel)); }); @@ -332,18 +332,18 @@ suite('PromptHeaderAutocompletion', () => { ].sort(sortByLabel)); }); - test('complete user-invokable attribute value', async () => { + test('complete user-invocable attribute value', async () => { const content = [ '---', 'description: "Test"', - 'user-invokable: |', + 'user-invocable: |', '---', ].join('\n'); const actual = await getCompletions(content, PromptsType.agent); assert.deepStrictEqual(actual.sort(sortByLabel), [ - { label: 'false', result: 'user-invokable: false' }, - { label: 'true', result: 'user-invokable: true' }, + { label: 'false', result: 'user-invocable: false' }, + { label: 'true', result: 'user-invocable: true' }, ].sort(sortByLabel)); }); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts index 476b8011ad05a..f8042fe069e53 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts @@ -79,7 +79,7 @@ suite('PromptHoverProvider', () => { agentInstructions: { content: 'Beast mode instructions', toolReferences: [] }, source: { storage: PromptsStorage.local }, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }); instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); @@ -323,7 +323,7 @@ suite('PromptHoverProvider', () => { '---', ].join('\n'); const hover = await getHover(content, 4, 1, PromptsType.agent); - assert.strictEqual(hover, 'Controls visibility of the agent.\n\nDeprecated: Use `user-invokable` and `disable-model-invocation` instead.'); + assert.strictEqual(hover, 'Controls visibility of the agent.\n\nDeprecated: Use `user-invocable` and `disable-model-invocation` instead.'); }); test('hover on agents attribute shows description', async () => { @@ -338,12 +338,12 @@ suite('PromptHoverProvider', () => { assert.strictEqual(hover, 'One or more agents that this agent can use as subagents. Use \'*\' to specify all available agents.'); }); - test('hover on user-invokable attribute shows description', async () => { + test('hover on user-invocable attribute shows description', async () => { const content = [ '---', 'name: "Test Agent"', 'description: "Test agent"', - 'user-invokable: true', + 'user-invocable: true', '---', ].join('\n'); const hover = await getHover(content, 4, 1, PromptsType.agent); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptValidator.test.ts index 071f767b5d142..5fe0c45d88f71 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptValidator.test.ts @@ -136,7 +136,7 @@ suite('PromptValidator', () => { agentInstructions: { content: 'Beast mode instructions', toolReferences: [] }, source: { storage: PromptsStorage.local }, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }); instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); @@ -156,7 +156,7 @@ suite('PromptValidator', () => { agentInstructions: { content: 'Custom mode body', toolReferences: [] }, source: { storage: PromptsStorage.local }, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; promptsService.setCustomModes([customMode]); instaService.stub(IPromptsService, promptsService); @@ -511,7 +511,7 @@ suite('PromptValidator', () => { assert.deepStrictEqual( markers.map(m => ({ severity: m.severity, message: m.message })), [ - { severity: MarkerSeverity.Warning, message: `Attribute 'applyTo' is not supported in VS Code agent files. Supported: agents, argument-hint, description, disable-model-invocation, handoffs, model, name, target, tools, user-invokable.` }, + { severity: MarkerSeverity.Warning, message: `Attribute 'applyTo' is not supported in VS Code agent files. Supported: agents, argument-hint, description, disable-model-invocation, handoffs, model, name, target, tools, user-invocable.` }, ] ); }); @@ -846,7 +846,7 @@ suite('PromptValidator', () => { }); test('infer attribute validation', async () => { - const deprecationMessage = `The 'infer' attribute is deprecated in favour of 'user-invokable' and 'disable-model-invocation'.`; + const deprecationMessage = `The 'infer' attribute is deprecated in favour of 'user-invocable' and 'disable-model-invocation'.`; // Valid infer: true (maps to 'all') - shows deprecation warning { @@ -1085,68 +1085,83 @@ suite('PromptValidator', () => { assert.deepStrictEqual(markers, [], 'Empty array should not require agent tool'); }); - test('user-invokable attribute validation', async () => { - // Valid user-invokable: true + test('user-invocable attribute validation', async () => { + // Valid user-invocable: true { const content = [ '---', 'name: "TestAgent"', 'description: "Test agent"', - 'user-invokable: true', + 'user-invocable: true', '---', 'Body', ].join('\n'); const markers = await validate(content, PromptsType.agent); - assert.deepStrictEqual(markers, [], 'Valid user-invokable: true should not produce errors'); + assert.deepStrictEqual(markers, [], 'Valid user-invocable: true should not produce errors'); } - // Valid user-invokable: false + // Valid user-invocable: false { const content = [ '---', 'name: "TestAgent"', 'description: "Test agent"', - 'user-invokable: false', + 'user-invocable: false', '---', 'Body', ].join('\n'); const markers = await validate(content, PromptsType.agent); - assert.deepStrictEqual(markers, [], 'Valid user-invokable: false should not produce errors'); + assert.deepStrictEqual(markers, [], 'Valid user-invocable: false should not produce errors'); } - // Invalid user-invokable: string value + // Invalid user-invocable: string value { const content = [ '---', 'name: "TestAgent"', 'description: "Test agent"', - 'user-invokable: "yes"', + 'user-invocable: "yes"', '---', '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 'user-invokable' attribute must be a boolean.`); + assert.strictEqual(markers[0].message, `The 'user-invocable' attribute must be a boolean.`); } - // Invalid user-invokable: number value + // Invalid user-invocable: number value { const content = [ '---', 'name: "TestAgent"', 'description: "Test agent"', - 'user-invokable: 1', + 'user-invocable: 1', '---', '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 'user-invokable' attribute must be a boolean.`); + assert.strictEqual(markers[0].message, `The 'user-invocable' attribute must be a boolean.`); } }); + test('deprecated user-invokable attribute shows warning', async () => { + const content = [ + '---', + 'name: "TestAgent"', + 'description: "Test agent"', + 'user-invokable: true', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.strictEqual(markers[0].message, `The 'user-invokable' attribute is deprecated. Use 'user-invocable' instead.`); + }); + test('disable-model-invocation attribute validation', async () => { // Valid disable-model-invocation: true { @@ -1674,47 +1689,47 @@ suite('PromptValidator', () => { assert.ok(markers.every(m => m.message.includes('Supported: '))); }); - test('skill with user-invokable: false is valid', async () => { + test('skill with user-invocable: false is valid', async () => { const content = [ '---', 'name: my-skill', 'description: Background knowledge skill', - 'user-invokable: false', + 'user-invocable: false', '---', 'This skill provides background context.' ].join('\n'); const markers = await validate(content, PromptsType.skill, URI.parse('file:///.github/skills/my-skill/SKILL.md')); - assert.deepStrictEqual(markers, [], 'user-invokable: false should be valid for skills'); + assert.deepStrictEqual(markers, [], 'user-invocable: false should be valid for skills'); }); - test('skill with user-invokable: true is valid', async () => { + test('skill with user-invocable: true is valid', async () => { const content = [ '---', 'name: my-skill', 'description: User-accessible skill', - 'user-invokable: true', + 'user-invocable: true', '---', 'This skill can be invoked by users.' ].join('\n'); const markers = await validate(content, PromptsType.skill, URI.parse('file:///.github/skills/my-skill/SKILL.md')); - assert.deepStrictEqual(markers, [], 'user-invokable: true should be valid for skills'); + assert.deepStrictEqual(markers, [], 'user-invocable: true should be valid for skills'); }); - test('skill with invalid user-invokable value shows error', async () => { + test('skill with invalid user-invocable value shows error', async () => { // String value instead of boolean { const content = [ '---', 'name: my-skill', 'description: Test Skill', - 'user-invokable: "false"', + 'user-invocable: "false"', '---', 'Body' ].join('\n'); const markers = await validate(content, PromptsType.skill, URI.parse('file:///.github/skills/my-skill/SKILL.md')); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].severity, MarkerSeverity.Error); - assert.strictEqual(markers[0].message, `The 'user-invokable' attribute must be a boolean.`); + assert.strictEqual(markers[0].message, `The 'user-invocable' attribute must be a boolean.`); } // Number value instead of boolean @@ -1723,14 +1738,14 @@ suite('PromptValidator', () => { '---', 'name: my-skill', 'description: Test Skill', - 'user-invokable: 0', + 'user-invocable: 0', '---', 'Body' ].join('\n'); const markers = await validate(content, PromptsType.skill, URI.parse('file:///.github/skills/my-skill/SKILL.md')); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].severity, MarkerSeverity.Error); - assert.strictEqual(markers[0].message, `The 'user-invokable' attribute must be a boolean.`); + assert.strictEqual(markers[0].message, `The 'user-invocable' attribute must be a boolean.`); } }); @@ -1842,7 +1857,7 @@ suite('PromptValidator', () => { '---', 'name: my-skill', 'description: Complex visibility skill', - 'user-invokable: false', + 'user-invocable: false', 'disable-model-invocation: true', 'argument-hint: "[optional-arg]"', '---', diff --git a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts index 75f22e0c69f2b..f7745a0b6fa9f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts @@ -120,7 +120,7 @@ suite('ChatModeService', () => { agentInstructions: { content: 'Custom mode body', toolReferences: [] }, source: workspaceSource, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; promptsService.setCustomModes([customMode]); @@ -158,7 +158,7 @@ suite('ChatModeService', () => { agentInstructions: { content: 'Custom mode body', toolReferences: [] }, source: workspaceSource, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; promptsService.setCustomModes([customMode]); @@ -178,7 +178,7 @@ suite('ChatModeService', () => { agentInstructions: { content: 'Findable mode body', toolReferences: [] }, source: workspaceSource, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; promptsService.setCustomModes([customMode]); @@ -204,7 +204,7 @@ suite('ChatModeService', () => { model: ['gpt-4'], source: workspaceSource, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; promptsService.setCustomModes([initialMode]); @@ -249,7 +249,7 @@ suite('ChatModeService', () => { agentInstructions: { content: 'Mode 1 body', toolReferences: [] }, source: workspaceSource, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; const mode2: ICustomAgent = { @@ -260,7 +260,7 @@ suite('ChatModeService', () => { agentInstructions: { content: 'Mode 2 body', toolReferences: [] }, source: workspaceSource, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; // Add both modes diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts index 5733ddff8275a..3cb6fe2ad58fe 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts @@ -1150,7 +1150,7 @@ suite('ComputeAutomaticInstructions', () => { contents: [ '---', 'description: \'Test agent 1\'', - 'user-invokable: true', + 'user-invocable: true', 'disable-model-invocation: false', '---', 'Test agent content', @@ -1161,7 +1161,7 @@ suite('ComputeAutomaticInstructions', () => { contents: [ '---', 'description: \'Test agent 2\'', - 'user-invokable: true', + 'user-invocable: true', 'disable-model-invocation: true', '---', 'Test agent content', @@ -1172,7 +1172,7 @@ suite('ComputeAutomaticInstructions', () => { contents: [ '---', 'description: \'Test agent 3\'', - 'user-invokable: false', + 'user-invocable: false', 'disable-model-invocation: false', '---', 'Test agent content', @@ -1183,7 +1183,7 @@ suite('ComputeAutomaticInstructions', () => { contents: [ '---', 'description: \'Test agent 4\'', - 'user-invokable: false', + 'user-invocable: false', 'disable-model-invocation: true', '---', 'Test agent content', diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptFileParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptFileParser.test.ts index 156e28227d776..d4a3d7d430a27 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptFileParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptFileParser.test.ts @@ -407,4 +407,48 @@ suite('PromptFileParser', () => { }); + test('userInvocable getter falls back to deprecated user-invokable', async () => { + const uri = URI.parse('file:///test/test.agent.md'); + + // user-invocable (new spelling) takes precedence + const content1 = [ + '---', + 'description: "Test"', + 'user-invocable: true', + '---', + ].join('\n'); + const result1 = new PromptFileParser().parse(uri, content1); + assert.strictEqual(result1.header?.userInvocable, true); + + // deprecated user-invokable still works as fallback + const content2 = [ + '---', + 'description: "Test"', + 'user-invokable: false', + '---', + ].join('\n'); + const result2 = new PromptFileParser().parse(uri, content2); + assert.strictEqual(result2.header?.userInvocable, false); + + // user-invocable takes precedence over deprecated user-invokable + const content3 = [ + '---', + 'description: "Test"', + 'user-invocable: true', + 'user-invokable: false', + '---', + ].join('\n'); + const result3 = new PromptFileParser().parse(uri, content3); + assert.strictEqual(result3.header?.userInvocable, true); + + // neither set returns undefined + const content4 = [ + '---', + 'description: "Test"', + '---', + ].join('\n'); + const result4 = new PromptFileParser().parse(uri, content4); + assert.strictEqual(result4.header?.userInvocable, undefined); + }); + }); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index f4e54cac7f152..dbcc9c0b2facd 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -769,7 +769,7 @@ suite('PromptsService', () => { argumentHint: undefined, tools: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local } @@ -825,7 +825,7 @@ suite('PromptsService', () => { model: undefined, argumentHint: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local }, @@ -843,7 +843,7 @@ suite('PromptsService', () => { uri: URI.joinPath(rootFolderUri, '.github/agents/agent2.agent.md'), source: { storage: PromptsStorage.local }, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } } ]; @@ -900,7 +900,7 @@ suite('PromptsService', () => { handOffs: undefined, model: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local } @@ -918,7 +918,7 @@ suite('PromptsService', () => { model: undefined, tools: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent2.agent.md'), source: { storage: PromptsStorage.local } @@ -988,7 +988,7 @@ suite('PromptsService', () => { handOffs: undefined, model: undefined, argumentHint: undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/github-agent.agent.md'), source: { storage: PromptsStorage.local } @@ -1006,7 +1006,7 @@ suite('PromptsService', () => { handOffs: undefined, argumentHint: undefined, tools: undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/vscode-agent.agent.md'), source: { storage: PromptsStorage.local } @@ -1024,7 +1024,7 @@ suite('PromptsService', () => { argumentHint: undefined, tools: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/generic-agent.agent.md'), source: { storage: PromptsStorage.local } @@ -1101,7 +1101,7 @@ suite('PromptsService', () => { }, handOffs: undefined, argumentHint: undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/copilot-agent.agent.md'), source: { storage: PromptsStorage.local } @@ -1121,7 +1121,7 @@ suite('PromptsService', () => { }, handOffs: undefined, argumentHint: undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.claude/agents/claude-agent.md'), source: { storage: PromptsStorage.local } @@ -1140,7 +1140,7 @@ suite('PromptsService', () => { }, handOffs: undefined, argumentHint: undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.claude/agents/claude-agent2.md'), source: { storage: PromptsStorage.local } @@ -1195,7 +1195,7 @@ suite('PromptsService', () => { model: undefined, argumentHint: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, agents: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/demonstrate.md'), source: { storage: PromptsStorage.local } @@ -1266,7 +1266,7 @@ suite('PromptsService', () => { model: undefined, argumentHint: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, uri: URI.joinPath(rootFolderUri, '.github/agents/restricted-agent.agent.md'), source: { storage: PromptsStorage.local } }, @@ -1284,7 +1284,7 @@ suite('PromptsService', () => { argumentHint: undefined, tools: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, uri: URI.joinPath(rootFolderUri, '.github/agents/no-access-agent.agent.md'), source: { storage: PromptsStorage.local } }, @@ -1302,7 +1302,7 @@ suite('PromptsService', () => { argumentHint: undefined, tools: undefined, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true }, + visibility: { userInvocable: true, agentInvocable: true }, uri: URI.joinPath(rootFolderUri, '.github/agents/full-access-agent.agent.md'), source: { storage: PromptsStorage.local } }, @@ -3101,22 +3101,22 @@ suite('PromptsService', () => { }); }); - suite('getPromptSlashCommands - userInvokable filtering', () => { + suite('getPromptSlashCommands - userInvocable filtering', () => { teardown(() => { sinon.restore(); }); - test('should return correct userInvokable value for skills with user-invokable: false', async () => { + test('should return correct userInvocable value for skills with user-invocable: false', async () => { testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true); testConfigService.setUserConfiguration(PromptsConfig.SKILLS_LOCATION_KEY, {}); - const rootFolderName = 'user-invokable-false'; + const rootFolderName = 'user-invocable-false'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - // Create a skill with user-invokable: false (should be hidden from / menu) + // Create a skill with user-invocable: false (should be hidden from / menu) await mockFiles(fileService, [ { path: `${rootFolder}/.github/skills/hidden-skill/SKILL.md`, @@ -3124,7 +3124,7 @@ suite('PromptsService', () => { '---', 'name: "hidden-skill"', 'description: "A skill hidden from the / menu"', - 'user-invokable: false', + 'user-invocable: false', '---', 'Hidden skill content', ], @@ -3135,27 +3135,27 @@ suite('PromptsService', () => { const hiddenSkillCommand = slashCommands.find(cmd => cmd.name === 'hidden-skill'); assert.ok(hiddenSkillCommand, 'Should find hidden skill in slash commands'); - assert.strictEqual(hiddenSkillCommand.parsedPromptFile?.header?.userInvokable, false, - 'Should have userInvokable=false in parsed header'); + assert.strictEqual(hiddenSkillCommand.parsedPromptFile?.header?.userInvocable, false, + 'Should have userInvocable=false in parsed header'); // Verify the filtering logic would correctly exclude this skill - const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvokable !== false); + const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvocable !== false); const hiddenSkillInFiltered = filteredCommands.find(cmd => cmd.name === 'hidden-skill'); assert.strictEqual(hiddenSkillInFiltered, undefined, - 'Hidden skill should be filtered out when applying userInvokable filter'); + 'Hidden skill should be filtered out when applying userInvocable filter'); }); - test('should return correct userInvokable value for skills with user-invokable: true', async () => { + test('should return correct userInvocable value for skills with user-invocable: true', async () => { testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true); testConfigService.setUserConfiguration(PromptsConfig.SKILLS_LOCATION_KEY, {}); - const rootFolderName = 'user-invokable-true'; + const rootFolderName = 'user-invocable-true'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - // Create a skill with explicit user-invokable: true + // Create a skill with explicit user-invocable: true await mockFiles(fileService, [ { path: `${rootFolder}/.github/skills/visible-skill/SKILL.md`, @@ -3163,7 +3163,7 @@ suite('PromptsService', () => { '---', 'name: "visible-skill"', 'description: "A skill visible in the / menu"', - 'user-invokable: true', + 'user-invocable: true', '---', 'Visible skill content', ], @@ -3174,34 +3174,34 @@ suite('PromptsService', () => { const visibleSkillCommand = slashCommands.find(cmd => cmd.name === 'visible-skill'); assert.ok(visibleSkillCommand, 'Should find visible skill in slash commands'); - assert.strictEqual(visibleSkillCommand.parsedPromptFile?.header?.userInvokable, true, - 'Should have userInvokable=true in parsed header'); + assert.strictEqual(visibleSkillCommand.parsedPromptFile?.header?.userInvocable, true, + 'Should have userInvocable=true in parsed header'); // Verify the filtering logic would correctly include this skill - const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvokable !== false); + const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvocable !== false); const visibleSkillInFiltered = filteredCommands.find(cmd => cmd.name === 'visible-skill'); assert.ok(visibleSkillInFiltered, - 'Visible skill should be included when applying userInvokable filter'); + 'Visible skill should be included when applying userInvocable filter'); }); - test('should default to true for skills without user-invokable attribute', async () => { + test('should default to true for skills without user-invocable attribute', async () => { testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true); testConfigService.setUserConfiguration(PromptsConfig.SKILLS_LOCATION_KEY, {}); - const rootFolderName = 'user-invokable-undefined'; + const rootFolderName = 'user-invocable-undefined'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - // Create a skill without user-invokable attribute (should default to true) + // Create a skill without user-invocable attribute (should default to true) await mockFiles(fileService, [ { path: `${rootFolder}/.github/skills/default-skill/SKILL.md`, contents: [ '---', 'name: "default-skill"', - 'description: "A skill without explicit user-invokable"', + 'description: "A skill without explicit user-invocable"', '---', 'Default skill content', ], @@ -3212,24 +3212,24 @@ suite('PromptsService', () => { const defaultSkillCommand = slashCommands.find(cmd => cmd.name === 'default-skill'); assert.ok(defaultSkillCommand, 'Should find default skill in slash commands'); - assert.strictEqual(defaultSkillCommand.parsedPromptFile?.header?.userInvokable, undefined, - 'Should have userInvokable=undefined when attribute is not specified'); + assert.strictEqual(defaultSkillCommand.parsedPromptFile?.header?.userInvocable, undefined, + 'Should have userInvocable=undefined when attribute is not specified'); // Verify the filtering logic would correctly include this skill (undefined !== false is true) - const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvokable !== false); + const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvocable !== false); const defaultSkillInFiltered = filteredCommands.find(cmd => cmd.name === 'default-skill'); assert.ok(defaultSkillInFiltered, - 'Skill without user-invokable attribute should be included when applying userInvokable filter'); + 'Skill without user-invocable attribute should be included when applying userInvocable filter'); }); - test('should handle prompts with user-invokable: false', async () => { - const rootFolderName = 'prompt-user-invokable-false'; + test('should handle prompts with user-invocable: false', async () => { + const rootFolderName = 'prompt-user-invocable-false'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - // Create a prompt with user-invokable: false + // Create a prompt with user-invocable: false await mockFiles(fileService, [ { path: `${rootFolder}/.github/prompts/hidden-prompt.prompt.md`, @@ -3237,7 +3237,7 @@ suite('PromptsService', () => { '---', 'name: "hidden-prompt"', 'description: "A prompt hidden from the / menu"', - 'user-invokable: false', + 'user-invocable: false', '---', 'Hidden prompt content', ], @@ -3248,27 +3248,27 @@ suite('PromptsService', () => { const hiddenPromptCommand = slashCommands.find(cmd => cmd.name === 'hidden-prompt'); assert.ok(hiddenPromptCommand, 'Should find hidden prompt in slash commands'); - assert.strictEqual(hiddenPromptCommand.parsedPromptFile?.header?.userInvokable, false, - 'Should have userInvokable=false in parsed header'); + assert.strictEqual(hiddenPromptCommand.parsedPromptFile?.header?.userInvocable, false, + 'Should have userInvocable=false in parsed header'); // Verify the filtering logic would correctly exclude this prompt - const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvokable !== false); + const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvocable !== false); const hiddenPromptInFiltered = filteredCommands.find(cmd => cmd.name === 'hidden-prompt'); assert.strictEqual(hiddenPromptInFiltered, undefined, - 'Hidden prompt should be filtered out when applying userInvokable filter'); + 'Hidden prompt should be filtered out when applying userInvocable filter'); }); - test('should correctly filter mixed user-invokable values', async () => { + test('should correctly filter mixed user-invocable values', async () => { testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_SKILLS, true); testConfigService.setUserConfiguration(PromptsConfig.SKILLS_LOCATION_KEY, {}); - const rootFolderName = 'mixed-user-invokable'; + const rootFolderName = 'mixed-user-invocable'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - // Create a mix of skills and prompts with different user-invokable values + // Create a mix of skills and prompts with different user-invocable values await mockFiles(fileService, [ { path: `${rootFolder}/.github/prompts/visible-prompt.prompt.md`, @@ -3286,7 +3286,7 @@ suite('PromptsService', () => { '---', 'name: "hidden-prompt"', 'description: "A hidden prompt"', - 'user-invokable: false', + 'user-invocable: false', '---', 'Hidden prompt content', ], @@ -3297,7 +3297,7 @@ suite('PromptsService', () => { '---', 'name: "visible-skill"', 'description: "A visible skill"', - 'user-invokable: true', + 'user-invocable: true', '---', 'Visible skill content', ], @@ -3308,7 +3308,7 @@ suite('PromptsService', () => { '---', 'name: "hidden-skill"', 'description: "A hidden skill"', - 'user-invokable: false', + 'user-invocable: false', '---', 'Hidden skill content', ], @@ -3321,7 +3321,7 @@ suite('PromptsService', () => { assert.strictEqual(slashCommands.length, 4, 'Should find all 4 commands'); // Apply the same filtering logic as chatInputCompletions.ts - const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvokable !== false); + const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvocable !== false); assert.strictEqual(filteredCommands.length, 2, 'Should have 2 commands after filtering'); assert.ok(filteredCommands.find(c => c.name === 'visible-prompt'), 'visible-prompt should be included'); @@ -3361,13 +3361,13 @@ suite('PromptsService', () => { 'Should have undefined header'); // Verify the filtering logic handles missing header correctly - // parsedPromptFile?.header?.userInvokable !== false + // parsedPromptFile?.header?.userInvocable !== false // When header is undefined: undefined !== false is true, so skill is included - const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvokable !== false); + const filteredCommands = slashCommands.filter(c => c.parsedPromptFile?.header?.userInvocable !== false); const noHeaderSkillInFiltered = filteredCommands.find(cmd => cmd.promptPath.uri.path.includes('no-header-skill')); assert.ok(noHeaderSkillInFiltered, - 'Skill without header should be included when applying userInvokable filter (defaults to true)'); + 'Skill without header should be included when applying userInvocable filter (defaults to true)'); }); }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts index 32809f2ebdd94..df306a3ef3ad0 100644 --- a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts @@ -56,7 +56,7 @@ suite('RunSubagentTool', () => { agentInstructions: { content: 'Custom agent body', toolReferences: [] }, source: { storage: PromptsStorage.local }, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; promptsService.setCustomModes([customMode]); @@ -276,7 +276,7 @@ suite('RunSubagentTool', () => { agentInstructions: { content: 'test', toolReferences: [] }, source: { storage: PromptsStorage.local }, target: Target.Undefined, - visibility: { userInvokable: true, agentInvokable: true } + visibility: { userInvocable: true, agentInvocable: true } }; } From 16e49a8b88457e6bd43b048933707e77bdad65ff Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 13 Feb 2026 13:40:49 +1100 Subject: [PATCH 16/16] Support prompt file slash commands in background agents (#295070) * Support prompt file slash commands in backgrround agents * Update tests * Update comments --- .../chatSessions/chatSessions.contribution.ts | 4 + .../contrib/chat/browser/widget/chatWidget.ts | 2 + .../input/editor/chatInputCompletions.ts | 2 +- .../chat/common/participants/chatAgents.ts | 1 + .../common/requestParser/chatRequestParser.ts | 9 +- ...rity_with_supportsPromptAttachments.0.snap | 85 +++++++++++++++++ ...agent_and_supportsPromptAttachments.0.snap | 82 ++++++++++++++++ ...nt_but_no_supportsPromptAttachments.0.snap | 54 +++++++++++ ...agent_and_supportsPromptAttachments.0.snap | 82 ++++++++++++++++ .../requestParser/chatRequestParser.test.ts | 93 +++++++++++++++++++ 10 files changed, 410 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_agent_subcommand_still_takes_priority_with_supportsPromptAttachments.0.snap create mode 100644 src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_and_supportsPromptAttachments.0.snap create mode 100644 src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_but_no_supportsPromptAttachments.0.snap create mode 100644 src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_slash_command_with_agent_and_supportsPromptAttachments.0.snap diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts index 8a6372af31d1a..a5da0877ddac1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts @@ -168,6 +168,10 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint = { supportsProblemAttachments: true, supportsSymbolAttachments: true, supportsTerminalAttachments: true, + supportsPromptAttachments: true, }; const DISCLAIMER = localize('chatDisclaimer', "AI responses may be inaccurate."); @@ -315,6 +316,7 @@ export class ChatWidget extends Disposable implements IChatWidget { .parseChatRequest(this.viewModel.sessionResource, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind, + attachmentCapabilities: this.attachmentCapabilities, forcedAgent: this._lockedAgent?.id ? this.chatAgentService.getAgent(this._lockedAgent.id) : undefined }); this._onDidChangeParsedInput.fire(); diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts index 65afc29143b0f..4188fb18fd0be 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts @@ -197,7 +197,7 @@ class SlashCommandCompletions extends Disposable { return null; } - if (widget.lockedAgentId) { + if (widget.lockedAgentId && !widget.attachmentCapabilities.supportsPromptAttachments) { return null; } diff --git a/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts b/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts index 94097d98c96e5..55f5e4c319cc8 100644 --- a/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts @@ -50,6 +50,7 @@ export interface IChatAgentAttachmentCapabilities { supportsProblemAttachments?: boolean; supportsSymbolAttachments?: boolean; supportsTerminalAttachments?: boolean; + supportsPromptAttachments?: boolean; } export interface IChatAgentData { diff --git a/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts index fa07524a29e1a..9994bcffbcb8a 100644 --- a/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts @@ -9,7 +9,7 @@ import { Range } from '../../../../../editor/common/core/range.js'; import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js'; import { IChatVariablesService, IDynamicVariable } from '../attachments/chatVariables.js'; import { ChatAgentLocation, ChatModeKind } from '../constants.js'; -import { IChatAgentData, IChatAgentService } from '../participants/chatAgents.js'; +import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentService } from '../participants/chatAgents.js'; import { IChatSlashCommandService } from '../participants/chatSlashCommands.js'; import { IPromptsService } from '../promptSyntax/service/promptsService.js'; import { IToolData, IToolSet, isToolSet } from '../tools/languageModelToolsService.js'; @@ -25,6 +25,7 @@ export interface IChatParserContext { mode?: ChatModeKind; /** Parse as this agent, even when it does not appear in the query text */ forcedAgent?: IChatAgentData; + attachmentCapabilities?: IChatAgentAttachmentCapabilities; } export class ChatRequestParser { @@ -215,7 +216,9 @@ export class ChatRequestParser { // Valid agent subcommand return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); } - } else { + } + + if (!usedAgent || context?.attachmentCapabilities?.supportsPromptAttachments) { const slashCommands = this.slashCommandService.getCommands(location, context?.mode ?? ChatModeKind.Ask); const slashCommand = slashCommands.find(c => c.command === command); if (slashCommand) { @@ -231,7 +234,7 @@ export class ChatRequestParser { } } - // if there's no agent, asume it is a prompt slash command + // if there's no agent or attachments are supported, asume it is a prompt slash command const isPromptCommand = this.promptsService.isValidSlashCommandName(command); if (isPromptCommand) { return new ChatRequestSlashPromptPart(slashRange, slashEditorRange, command); diff --git a/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_agent_subcommand_still_takes_priority_with_supportsPromptAttachments.0.snap b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_agent_subcommand_still_takes_priority_with_supportsPromptAttachments.0.snap new file mode 100644 index 0000000000000..932d8aaea2dc3 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_agent_subcommand_still_takes_priority_with_supportsPromptAttachments.0.snap @@ -0,0 +1,85 @@ +{ + parts: [ + { + range: { + start: 0, + endExclusive: 6 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 7 + }, + agent: { + id: "agent", + name: "agent", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionVersion: undefined, + publisherDisplayName: "", + extensionDisplayName: "", + extensionPublisherId: "", + locations: [ "panel" ], + modes: [ "ask" ], + metadata: { }, + slashCommands: [ + { + name: "subCommand", + description: "" + } + ], + disambiguation: [ ] + }, + kind: "agent" + }, + { + range: { + start: 6, + endExclusive: 7 + }, + editorRange: { + startLineNumber: 1, + startColumn: 7, + endLineNumber: 1, + endColumn: 8 + }, + text: " ", + kind: "text" + }, + { + range: { + start: 7, + endExclusive: 18 + }, + editorRange: { + startLineNumber: 1, + startColumn: 8, + endLineNumber: 1, + endColumn: 19 + }, + command: { + name: "subCommand", + description: "" + }, + kind: "subcommand" + }, + { + range: { + start: 18, + endExclusive: 31 + }, + editorRange: { + startLineNumber: 1, + startColumn: 19, + endLineNumber: 1, + endColumn: 32 + }, + text: " do something", + kind: "text" + } + ], + text: "@agent /subCommand do something" +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_and_supportsPromptAttachments.0.snap b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_and_supportsPromptAttachments.0.snap new file mode 100644 index 0000000000000..2ae3f526313cb --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_and_supportsPromptAttachments.0.snap @@ -0,0 +1,82 @@ +{ + parts: [ + { + range: { + start: 0, + endExclusive: 6 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 7 + }, + agent: { + id: "agent", + name: "agent", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionVersion: undefined, + publisherDisplayName: "", + extensionDisplayName: "", + extensionPublisherId: "", + locations: [ "panel" ], + modes: [ "ask" ], + metadata: { }, + slashCommands: [ + { + name: "subCommand", + description: "" + } + ], + disambiguation: [ ] + }, + kind: "agent" + }, + { + range: { + start: 6, + endExclusive: 7 + }, + editorRange: { + startLineNumber: 1, + startColumn: 7, + endLineNumber: 1, + endColumn: 8 + }, + text: " ", + kind: "text" + }, + { + range: { + start: 7, + endExclusive: 16 + }, + editorRange: { + startLineNumber: 1, + startColumn: 8, + endLineNumber: 1, + endColumn: 17 + }, + name: "myPrompt", + kind: "prompt" + }, + { + range: { + start: 16, + endExclusive: 29 + }, + editorRange: { + startLineNumber: 1, + startColumn: 17, + endLineNumber: 1, + endColumn: 30 + }, + text: " do something", + kind: "text" + } + ], + text: "@agent /myPrompt do something" +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_but_no_supportsPromptAttachments.0.snap b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_but_no_supportsPromptAttachments.0.snap new file mode 100644 index 0000000000000..736584ac2f41f --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_prompt_slash_command_with_agent_but_no_supportsPromptAttachments.0.snap @@ -0,0 +1,54 @@ +{ + parts: [ + { + range: { + start: 0, + endExclusive: 6 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 7 + }, + agent: { + id: "agent", + name: "agent", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionVersion: undefined, + publisherDisplayName: "", + extensionDisplayName: "", + extensionPublisherId: "", + locations: [ "panel" ], + modes: [ "ask" ], + metadata: { }, + slashCommands: [ + { + name: "subCommand", + description: "" + } + ], + disambiguation: [ ] + }, + kind: "agent" + }, + { + range: { + start: 6, + endExclusive: 29 + }, + editorRange: { + startLineNumber: 1, + startColumn: 7, + endLineNumber: 1, + endColumn: 30 + }, + text: " /myPrompt do something", + kind: "text" + } + ], + text: "@agent /myPrompt do something" +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_slash_command_with_agent_and_supportsPromptAttachments.0.snap b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_slash_command_with_agent_and_supportsPromptAttachments.0.snap new file mode 100644 index 0000000000000..b7e770cf3f7c9 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/requestParser/__snapshots__/ChatRequestParser_slash_command_with_agent_and_supportsPromptAttachments.0.snap @@ -0,0 +1,82 @@ +{ + parts: [ + { + range: { + start: 0, + endExclusive: 6 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 7 + }, + agent: { + id: "agent", + name: "agent", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionVersion: undefined, + publisherDisplayName: "", + extensionDisplayName: "", + extensionPublisherId: "", + locations: [ "panel" ], + modes: [ "ask" ], + metadata: { }, + slashCommands: [ + { + name: "subCommand", + description: "" + } + ], + disambiguation: [ ] + }, + kind: "agent" + }, + { + range: { + start: 6, + endExclusive: 7 + }, + editorRange: { + startLineNumber: 1, + startColumn: 7, + endLineNumber: 1, + endColumn: 8 + }, + text: " ", + kind: "text" + }, + { + range: { + start: 7, + endExclusive: 11 + }, + editorRange: { + startLineNumber: 1, + startColumn: 8, + endLineNumber: 1, + endColumn: 12 + }, + slashCommand: { command: "fix" }, + kind: "slash" + }, + { + range: { + start: 11, + endExclusive: 16 + }, + editorRange: { + startLineNumber: 1, + startColumn: 12, + endLineNumber: 1, + endColumn: 17 + }, + text: " this", + kind: "text" + } + ], + text: "@agent /fix this" +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/requestParser/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/requestParser/chatRequestParser.test.ts index 1ec95eed15f7d..96e754d368887 100644 --- a/src/vs/workbench/contrib/chat/test/common/requestParser/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/requestParser/chatRequestParser.test.ts @@ -347,4 +347,97 @@ suite('ChatRequestParser', () => { const result = parser.parseChatRequest(testSessionUri, '@agent Please \ndo /subCommand with #selection\nand #debugConsole'); await assertSnapshot(result); }); + + test('prompt slash command with agent and supportsPromptAttachments', async () => { + const agentsService = mockObject()({}); + agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatAgentService, agentsService as any); + + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); + + const promptSlashCommandService = mockObject()({}); + promptSlashCommandService.isValidSlashCommandName.callsFake((command: string) => { + return !!command.match(/^[\w_\-\.]+$/); + }); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IPromptsService, promptSlashCommandService as any); + + parser = instantiationService.createInstance(ChatRequestParser); + const result = parser.parseChatRequest(testSessionUri, '@agent /myPrompt do something', undefined, { + attachmentCapabilities: { supportsPromptAttachments: true } + }); + await assertSnapshot(result); + }); + + test('prompt slash command with agent but no supportsPromptAttachments', async () => { + const agentsService = mockObject()({}); + agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatAgentService, agentsService as any); + + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); + + const promptSlashCommandService = mockObject()({}); + promptSlashCommandService.isValidSlashCommandName.callsFake((command: string) => { + return !!command.match(/^[\w_\-\.]+$/); + }); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IPromptsService, promptSlashCommandService as any); + + parser = instantiationService.createInstance(ChatRequestParser); + const result = parser.parseChatRequest(testSessionUri, '@agent /myPrompt do something', undefined, { + attachmentCapabilities: { supportsPromptAttachments: false } + }); + await assertSnapshot(result); + }); + + test('agent subcommand still takes priority with supportsPromptAttachments', async () => { + const agentsService = mockObject()({}); + agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatAgentService, agentsService as any); + + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); + + const promptSlashCommandService = mockObject()({}); + promptSlashCommandService.isValidSlashCommandName.callsFake((command: string) => { + return !!command.match(/^[\w_\-\.]+$/); + }); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IPromptsService, promptSlashCommandService as any); + + parser = instantiationService.createInstance(ChatRequestParser); + const result = parser.parseChatRequest(testSessionUri, '@agent /subCommand do something', undefined, { + attachmentCapabilities: { supportsPromptAttachments: true } + }); + await assertSnapshot(result); + }); + + test('slash command with agent and supportsPromptAttachments', async () => { + const agentsService = mockObject()({}); + agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatAgentService, agentsService as any); + + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); + + parser = instantiationService.createInstance(ChatRequestParser); + const result = parser.parseChatRequest(testSessionUri, '@agent /fix this', undefined, { + attachmentCapabilities: { supportsPromptAttachments: true } + }); + await assertSnapshot(result); + }); });