diff --git a/.github/workflows/screenshot-test.yml b/.github/workflows/screenshot-test.yml index 0b082e81a24d9..decfcf2a6f84d 100644 --- a/.github/workflows/screenshot-test.yml +++ b/.github/workflows/screenshot-test.yml @@ -10,8 +10,6 @@ on: permissions: contents: read - pull-requests: write - checks: write statuses: write concurrency: @@ -96,41 +94,18 @@ jobs: REPORT="test/componentFixtures/.screenshots/report/report.json" if [ -f "$REPORT" ]; then CHANGED=$(node -e "const r = require('./$REPORT'); console.log(r.summary.added + r.summary.removed + r.summary.changed)") - TITLE="📸 ${CHANGED} screenshots changed" - CONCLUSION="neutral" + TITLE="⚠ ${CHANGED} screenshots changed" else - TITLE="📸 Screenshots match" - CONCLUSION="success" + TITLE="✅ Screenshots match" fi SHA="${{ github.event.pull_request.head.sha || github.sha }}" DETAILS_URL="https://hediet-ghartifactpreview.azurewebsites.net/${{ github.repository }}/run/${{ github.run_id }}/component-explorer/___explorer.html?report=./screenshot-report/report.json" - EXPLORER_PUSHED=false - if gh api "repos/${{ github.repository }}/check-runs" \ - --input - < disposables.forEach(d => d.dispose())); + + this._output = vscode.window.createOutputChannel('VS Code Extras', { log: true }); + disposables.push(this._output); + + this._updateNpmFeature(); + + disposables.push( + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('vscode-extras.npmUpToDateFeature.enabled')) { + this._updateNpmFeature(); + } + }) + ); + } + + private _updateNpmFeature(): void { + const enabled = vscode.workspace.getConfiguration('vscode-extras').get('npmUpToDateFeature.enabled', true); + if (enabled && !this._npmFeature) { + this._npmFeature = new NpmUpToDateFeature(this._output); + } else if (!enabled && this._npmFeature) { + this._npmFeature.dispose(); + this._npmFeature = undefined; + } + } +} + +let extension: Extension | undefined; + +export function activate(context: vscode.ExtensionContext) { + extension = new Extension(context); + context.subscriptions.push(extension); +} + +export function deactivate() { + extension = undefined; +} diff --git a/.vscode/extensions/vscode-extras/src/npmUpToDateFeature.ts b/.vscode/extensions/vscode-extras/src/npmUpToDateFeature.ts new file mode 100644 index 0000000000000..b3f172eca1dc6 --- /dev/null +++ b/.vscode/extensions/vscode-extras/src/npmUpToDateFeature.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +interface FileHashes { + readonly [relativePath: string]: string; +} + +interface PostinstallState { + readonly nodeVersion: string; + readonly fileHashes: FileHashes; +} + +interface InstallState { + readonly root: string; + readonly current: PostinstallState; + readonly saved: PostinstallState | undefined; + readonly files: readonly string[]; +} + +export class NpmUpToDateFeature extends vscode.Disposable { + private readonly _statusBarItem: vscode.StatusBarItem; + private readonly _disposables: vscode.Disposable[] = []; + private _watchers: fs.FSWatcher[] = []; + private _terminal: vscode.Terminal | undefined; + + constructor(private readonly _output: vscode.LogOutputChannel) { + const disposables: vscode.Disposable[] = []; + super(() => { + disposables.forEach(d => d.dispose()); + for (const w of this._watchers) { + w.close(); + } + }); + this._disposables = disposables; + + this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10000); + this._statusBarItem.name = 'npm Install State'; + this._statusBarItem.text = '$(warning) node_modules is stale - run npm i'; + this._statusBarItem.tooltip = 'Dependencies are out of date. Click to run npm install.'; + this._statusBarItem.command = 'vscode-extras.runNpmInstall'; + this._statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); + this._disposables.push(this._statusBarItem); + + this._disposables.push( + vscode.commands.registerCommand('vscode-extras.runNpmInstall', () => this._runNpmInstall()) + ); + + this._disposables.push( + vscode.window.onDidCloseTerminal(t => { + if (t === this._terminal) { + this._terminal = undefined; + this._check(); + } + }) + ); + + this._check(); + } + + private _runNpmInstall(): void { + if (this._terminal) { + this._terminal.show(); + return; + } + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri; + if (!workspaceRoot) { + return; + } + this._terminal = vscode.window.createTerminal({ name: 'npm install', cwd: workspaceRoot }); + this._terminal.sendText('node build/npm/fast-install.ts --force'); + this._terminal.show(); + + this._statusBarItem.text = '$(loading~spin) npm i'; + this._statusBarItem.tooltip = 'npm install is running...'; + this._statusBarItem.backgroundColor = undefined; + this._statusBarItem.command = 'vscode-extras.runNpmInstall'; + } + + private _queryState(): InstallState | undefined { + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if (!workspaceRoot) { + return undefined; + } + try { + const script = path.join(workspaceRoot, 'build', 'npm', 'installStateHash.ts'); + const output = cp.execFileSync(process.execPath, [script], { + cwd: workspaceRoot, + timeout: 10_000, + encoding: 'utf8', + }); + const parsed = JSON.parse(output.trim()); + this._output.trace('raw output:', output.trim()); + return parsed; + } catch (e) { + this._output.error('_queryState error:', e as any); + return undefined; + } + } + + private _check(): void { + const state = this._queryState(); + this._output.trace('state:', JSON.stringify(state, null, 2)); + if (!state) { + this._output.trace('no state, hiding'); + this._statusBarItem.hide(); + return; + } + + this._setupWatcher(state); + + const changedFiles = this._getChangedFiles(state); + this._output.trace('changedFiles:', JSON.stringify(changedFiles)); + + if (changedFiles.length === 0) { + this._statusBarItem.hide(); + } else { + this._statusBarItem.text = '$(warning) node_modules is stale - run npm i'; + const tooltip = new vscode.MarkdownString(); + tooltip.appendText('Dependencies are out of date. Click to run npm install.\n\nChanged files:\n'); + for (const file of changedFiles) { + tooltip.appendText(` • ${file}\n`); + } + this._statusBarItem.tooltip = tooltip; + this._statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); + this._statusBarItem.show(); + } + } + + private _getChangedFiles(state: InstallState): string[] { + if (!state.saved) { + return ['(no postinstall state found)']; + } + const changed: string[] = []; + if (state.saved.nodeVersion !== state.current.nodeVersion) { + changed.push(`Node.js version (${state.saved.nodeVersion} → ${state.current.nodeVersion})`); + } + const allKeys = new Set([...Object.keys(state.current.fileHashes), ...Object.keys(state.saved.fileHashes)]); + for (const key of allKeys) { + if (state.current.fileHashes[key] !== state.saved.fileHashes[key]) { + changed.push(key); + } + } + return changed; + } + + private _setupWatcher(state: InstallState): void { + for (const w of this._watchers) { + w.close(); + } + this._watchers = []; + + let debounceTimer: ReturnType | undefined; + const scheduleCheck = () => { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + debounceTimer = setTimeout(() => this._check(), 500); + }; + + for (const file of state.files) { + try { + const watcher = fs.watch(file, scheduleCheck); + this._watchers.push(watcher); + } catch { + // file may not exist yet + } + } + } +} diff --git a/.vscode/extensions/vscode-extras/tsconfig.json b/.vscode/extensions/vscode-extras/tsconfig.json new file mode 100644 index 0000000000000..9133c3bbf4b87 --- /dev/null +++ b/.vscode/extensions/vscode-extras/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../extensions/tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./out", + "types": [ + "node" + ] + }, + "include": [ + "src/**/*", + "../../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/.vscode/extensions/vscode-selfhost-test-provider/package.json b/.vscode/extensions/vscode-selfhost-test-provider/package.json index 6f0db218fb254..1a894a5405532 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/package.json +++ b/.vscode/extensions/vscode-selfhost-test-provider/package.json @@ -6,6 +6,9 @@ "testObserver", "testRelatedCode" ], + "extensionDependencies": [ + "ms-vscode.vscode-extras" + ], "engines": { "vscode": "^1.88.0" }, diff --git a/build/gulpfile.extensions.ts b/build/gulpfile.extensions.ts index a2eb47535f4dd..e8ee8fa80f477 100644 --- a/build/gulpfile.extensions.ts +++ b/build/gulpfile.extensions.ts @@ -95,6 +95,7 @@ const compilations = [ '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', '.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json', + '.vscode/extensions/vscode-extras/tsconfig.json', ]; const getBaseUrl = (out: string) => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`; diff --git a/build/npm/dirs.ts b/build/npm/dirs.ts index 48d76e2731a6e..b56884af25c52 100644 --- a/build/npm/dirs.ts +++ b/build/npm/dirs.ts @@ -60,6 +60,7 @@ export const dirs = [ 'test/mcp', '.vscode/extensions/vscode-selfhost-import-aid', '.vscode/extensions/vscode-selfhost-test-provider', + '.vscode/extensions/vscode-extras', ]; if (existsSync(`${import.meta.dirname}/../../.build/distro/npm`)) { diff --git a/build/npm/fast-install.ts b/build/npm/fast-install.ts new file mode 100644 index 0000000000000..ff9a7d2097cf2 --- /dev/null +++ b/build/npm/fast-install.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as child_process from 'child_process'; +import { root, isUpToDate, forceInstallMessage } from './installStateHash.ts'; + +if (!process.argv.includes('--force') && isUpToDate()) { + console.log(`\x1b[32mAll dependencies up to date.\x1b[0m ${forceInstallMessage}`); + process.exit(0); +} + +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +const result = child_process.spawnSync(npm, ['install'], { + cwd: root, + stdio: 'inherit', + shell: true, + env: { ...process.env, VSCODE_FORCE_INSTALL: '1' }, +}); + +process.exit(result.status ?? 1); diff --git a/build/npm/installStateHash.ts b/build/npm/installStateHash.ts new file mode 100644 index 0000000000000..79c0c130f5e28 --- /dev/null +++ b/build/npm/installStateHash.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import path from 'path'; +import { dirs } from './dirs.ts'; + +export const root = fs.realpathSync.native(path.dirname(path.dirname(import.meta.dirname))); +export const stateFile = path.join(root, 'node_modules', '.postinstall-state'); +export const forceInstallMessage = 'Run \x1b[36mnode build/npm/fast-install.ts --force\x1b[0m to force a full install.'; + +export function collectInputFiles(): string[] { + const files: string[] = []; + + for (const dir of dirs) { + const base = dir === '' ? root : path.join(root, dir); + for (const file of ['package.json', '.npmrc']) { + const filePath = path.join(base, file); + if (fs.existsSync(filePath)) { + files.push(filePath); + } + } + } + + files.push(path.join(root, '.nvmrc')); + + return files; +} + +export interface PostinstallState { + readonly nodeVersion: string; + readonly fileHashes: Record; +} + +function hashFileContent(filePath: string): string { + const hash = crypto.createHash('sha256'); + hash.update(fs.readFileSync(filePath)); + return hash.digest('hex'); +} + +export function computeState(): PostinstallState { + const fileHashes: Record = {}; + for (const filePath of collectInputFiles()) { + fileHashes[path.relative(root, filePath)] = hashFileContent(filePath); + } + return { nodeVersion: process.versions.node, fileHashes }; +} + +export function readSavedState(): PostinstallState | undefined { + try { + return JSON.parse(fs.readFileSync(stateFile, 'utf8')); + } catch { + return undefined; + } +} + +export function isUpToDate(): boolean { + const saved = readSavedState(); + if (!saved) { + return false; + } + const current = computeState(); + return saved.nodeVersion === current.nodeVersion + && JSON.stringify(saved.fileHashes) === JSON.stringify(current.fileHashes); +} + +// When run directly, output state as JSON for tooling (e.g. the vscode-extras extension). +if (import.meta.filename === process.argv[1]) { + console.log(JSON.stringify({ + root, + current: computeState(), + saved: readSavedState(), + files: [...collectInputFiles(), stateFile], + })); +} diff --git a/build/npm/postinstall.ts b/build/npm/postinstall.ts index b6a934f74b3eb..6b61bbded5c84 100644 --- a/build/npm/postinstall.ts +++ b/build/npm/postinstall.ts @@ -8,9 +8,9 @@ import path from 'path'; import * as os from 'os'; import * as child_process from 'child_process'; import { dirs } from './dirs.ts'; +import { root, stateFile, computeState, isUpToDate } from './installStateHash.ts'; const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; -const root = path.dirname(path.dirname(import.meta.dirname)); const rootNpmrcConfigKeys = getNpmrcConfigKeys(path.join(root, '.npmrc')); function log(dir: string, message: string) { @@ -35,24 +35,45 @@ function run(command: string, args: string[], opts: child_process.SpawnSyncOptio } } -function npmInstall(dir: string, opts?: child_process.SpawnSyncOptions) { - opts = { +function spawnAsync(command: string, args: string[], opts: child_process.SpawnOptions): Promise { + return new Promise((resolve, reject) => { + const child = child_process.spawn(command, args, { ...opts, stdio: ['ignore', 'pipe', 'pipe'] }); + let output = ''; + child.stdout?.on('data', (data: Buffer) => { output += data.toString(); }); + child.stderr?.on('data', (data: Buffer) => { output += data.toString(); }); + child.on('error', reject); + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`Process exited with code: ${code}\n${output}`)); + } else { + resolve(output); + } + }); + }); +} + +async function npmInstallAsync(dir: string, opts?: child_process.SpawnOptions): Promise { + const finalOpts: child_process.SpawnOptions = { env: { ...process.env }, ...(opts ?? {}), - cwd: dir, - stdio: 'inherit', - shell: true + cwd: path.join(root, dir), + shell: true, }; const command = process.env['npm_command'] || 'install'; if (process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'] && /^(.build\/distro\/npm\/)?remote$/.test(dir)) { + const syncOpts: child_process.SpawnSyncOptions = { + env: finalOpts.env, + cwd: root, + stdio: 'inherit', + shell: true, + }; const userinfo = os.userInfo(); log(dir, `Installing dependencies inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); - opts.cwd = root; if (process.env['npm_config_arch'] === 'arm64') { - run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], opts); + run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], syncOpts); } run('sudo', [ 'docker', 'run', @@ -63,11 +84,16 @@ function npmInstall(dir: string, opts?: child_process.SpawnSyncOptions) { '-w', path.resolve('/root/vscode', dir), process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'sh', '-c', `\"chown -R root:root ${path.resolve('/root/vscode', dir)} && export PATH="/root/vscode/.build/nodejs-musl/usr/local/bin:$PATH" && npm i -g node-gyp-build && npm ci\"` - ], opts); - run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${path.resolve(root, dir)}`], opts); + ], syncOpts); + run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${path.resolve(root, dir)}`], syncOpts); } else { log(dir, 'Installing dependencies...'); - run(npm, command.split(' '), opts); + const output = await spawnAsync(npm, command.split(' '), finalOpts); + if (output.trim()) { + for (const line of output.trim().split('\n')) { + log(dir, line); + } + } } removeParcelWatcherPrebuild(dir); } @@ -156,65 +182,114 @@ function clearInheritedNpmrcConfig(dir: string, env: NodeJS.ProcessEnv): void { } } -for (const dir of dirs) { +async function runWithConcurrency(tasks: (() => Promise)[], concurrency: number): Promise { + const errors: Error[] = []; + let index = 0; - if (dir === '') { - removeParcelWatcherPrebuild(dir); - continue; // already executed in root + async function worker() { + while (index < tasks.length) { + const i = index++; + try { + await tasks[i](); + } catch (err) { + errors.push(err as Error); + } + } } - let opts: child_process.SpawnSyncOptions | undefined; + await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker())); - if (dir === 'build') { - opts = { - env: { - ...process.env - }, - }; - if (process.env['CC']) { opts.env!['CC'] = 'gcc'; } - if (process.env['CXX']) { opts.env!['CXX'] = 'g++'; } - if (process.env['CXXFLAGS']) { opts.env!['CXXFLAGS'] = ''; } - if (process.env['LDFLAGS']) { opts.env!['LDFLAGS'] = ''; } - - setNpmrcConfig('build', opts.env!); - npmInstall('build', opts); - continue; - } - - if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { - // node modules used by vscode server - opts = { - env: { - ...process.env - }, - }; - if (process.env['VSCODE_REMOTE_CC']) { - opts.env!['CC'] = process.env['VSCODE_REMOTE_CC']; - } else { - delete opts.env!['CC']; + if (errors.length > 0) { + for (const err of errors) { + console.error(err.message); } - if (process.env['VSCODE_REMOTE_CXX']) { - opts.env!['CXX'] = process.env['VSCODE_REMOTE_CXX']; - } else { - delete opts.env!['CXX']; + process.exit(1); + } +} + +async function main() { + if (!process.env['VSCODE_FORCE_INSTALL'] && isUpToDate()) { + log('.', 'All dependencies up to date, skipping postinstall.'); + child_process.execSync('git config pull.rebase merges'); + child_process.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); + return; + } + + const _state = computeState(); + + const nativeTasks: (() => Promise)[] = []; + const parallelTasks: (() => Promise)[] = []; + + for (const dir of dirs) { + if (dir === '') { + removeParcelWatcherPrebuild(dir); + continue; // already executed in root + } + + if (dir === 'build') { + nativeTasks.push(() => { + const env: NodeJS.ProcessEnv = { ...process.env }; + if (process.env['CC']) { env['CC'] = 'gcc'; } + if (process.env['CXX']) { env['CXX'] = 'g++'; } + if (process.env['CXXFLAGS']) { env['CXXFLAGS'] = ''; } + if (process.env['LDFLAGS']) { env['LDFLAGS'] = ''; } + setNpmrcConfig('build', env); + return npmInstallAsync('build', { env }); + }); + continue; + } + + if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { + const remoteDir = dir; + nativeTasks.push(() => { + const env: NodeJS.ProcessEnv = { ...process.env }; + if (process.env['VSCODE_REMOTE_CC']) { + env['CC'] = process.env['VSCODE_REMOTE_CC']; + } else { + delete env['CC']; + } + if (process.env['VSCODE_REMOTE_CXX']) { + env['CXX'] = process.env['VSCODE_REMOTE_CXX']; + } else { + delete env['CXX']; + } + if (process.env['CXXFLAGS']) { delete env['CXXFLAGS']; } + if (process.env['CFLAGS']) { delete env['CFLAGS']; } + if (process.env['LDFLAGS']) { delete env['LDFLAGS']; } + if (process.env['VSCODE_REMOTE_CXXFLAGS']) { env['CXXFLAGS'] = process.env['VSCODE_REMOTE_CXXFLAGS']; } + if (process.env['VSCODE_REMOTE_LDFLAGS']) { env['LDFLAGS'] = process.env['VSCODE_REMOTE_LDFLAGS']; } + if (process.env['VSCODE_REMOTE_NODE_GYP']) { env['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; } + setNpmrcConfig('remote', env); + return npmInstallAsync(remoteDir, { env }); + }); + continue; } - if (process.env['CXXFLAGS']) { delete opts.env!['CXXFLAGS']; } - if (process.env['CFLAGS']) { delete opts.env!['CFLAGS']; } - if (process.env['LDFLAGS']) { delete opts.env!['LDFLAGS']; } - if (process.env['VSCODE_REMOTE_CXXFLAGS']) { opts.env!['CXXFLAGS'] = process.env['VSCODE_REMOTE_CXXFLAGS']; } - if (process.env['VSCODE_REMOTE_LDFLAGS']) { opts.env!['LDFLAGS'] = process.env['VSCODE_REMOTE_LDFLAGS']; } - if (process.env['VSCODE_REMOTE_NODE_GYP']) { opts.env!['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; } - - setNpmrcConfig('remote', opts.env!); - npmInstall(dir, opts); - continue; - } - - // For directories that don't define their own .npmrc, clear inherited config - const env = { ...process.env }; - clearInheritedNpmrcConfig(dir, env); - npmInstall(dir, { env }); + + const taskDir = dir; + parallelTasks.push(() => { + const env = { ...process.env }; + clearInheritedNpmrcConfig(taskDir, env); + return npmInstallAsync(taskDir, { env }); + }); + } + + // Native dirs (build, remote) run sequentially to avoid node-gyp conflicts + for (const task of nativeTasks) { + await task(); + } + + // JS-only dirs run in parallel + const concurrency = Math.min(os.cpus().length, 8); + log('.', `Running ${parallelTasks.length} npm installs with concurrency ${concurrency}...`); + await runWithConcurrency(parallelTasks, concurrency); + + child_process.execSync('git config pull.rebase merges'); + child_process.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); + + fs.writeFileSync(stateFile, JSON.stringify(_state)); } -child_process.execSync('git config pull.rebase merges'); -child_process.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/build/npm/preinstall.ts b/build/npm/preinstall.ts index 3476fcabb5009..dd53ff4467123 100644 --- a/build/npm/preinstall.ts +++ b/build/npm/preinstall.ts @@ -6,6 +6,7 @@ import path from 'path'; import * as fs from 'fs'; import * as child_process from 'child_process'; import * as os from 'os'; +import { isUpToDate, forceInstallMessage } from './installStateHash.ts'; if (!process.env['VSCODE_SKIP_NODE_VERSION_CHECK']) { // Get the running Node.js version @@ -41,6 +42,13 @@ if (process.env.npm_execpath?.includes('yarn')) { throw new Error(); } +// Fast path: if nothing changed since last successful install, skip everything. +// This makes `npm i` near-instant when dependencies haven't changed. +if (!process.env['VSCODE_FORCE_INSTALL'] && isUpToDate()) { + console.log(`\x1b[32mAll dependencies up to date.\x1b[0m ${forceInstallMessage}`); + process.exit(0); +} + if (process.platform === 'win32') { if (!hasSupportedVisualStudioVersion()) { console.error('\x1b[1;31m*** Invalid C/C++ Compiler Toolchain. Please check https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites.\x1b[0;0m'); diff --git a/build/package-lock.json b/build/package-lock.json index b78c4c8389ac5..b7890ceb3d420 100644 --- a/build/package-lock.json +++ b/build/package-lock.json @@ -3493,10 +3493,23 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", "dev": true, "funding": [ { @@ -3506,6 +3519,7 @@ ], "license": "MIT", "dependencies": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { diff --git a/extensions/css-language-features/package-lock.json b/extensions/css-language-features/package-lock.json index 231eda54dba77..e5cf7c26d199e 100644 --- a/extensions/css-language-features/package-lock.json +++ b/extensions/css-language-features/package-lock.json @@ -51,9 +51,9 @@ } }, "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" diff --git a/extensions/json-language-features/package-lock.json b/extensions/json-language-features/package-lock.json index e5e1b9c133204..9ceade066818b 100644 --- a/extensions/json-language-features/package-lock.json +++ b/extensions/json-language-features/package-lock.json @@ -190,9 +190,9 @@ } }, "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index b7180c307306d..fbde1da49b92d 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -513,9 +513,10 @@ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, diff --git a/extensions/npm/package-lock.json b/extensions/npm/package-lock.json index 694e98b5e1213..d3bf11daf09c5 100644 --- a/extensions/npm/package-lock.json +++ b/extensions/npm/package-lock.json @@ -12,7 +12,7 @@ "find-up": "^5.0.0", "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^3.2.0", - "minimatch": "^5.1.6", + "minimatch": "^5.1.8", "request-light": "^0.7.0", "vscode-uri": "^3.0.8", "which": "^4.0.0", @@ -209,9 +209,10 @@ } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, diff --git a/extensions/npm/package.json b/extensions/npm/package.json index bba6a23b8ac99..7db2ec0e2d547 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -30,7 +30,7 @@ "find-up": "^5.0.0", "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^3.2.0", - "minimatch": "^5.1.6", + "minimatch": "^5.1.8", "request-light": "^0.7.0", "which": "^4.0.0", "which-pm": "^2.1.1", diff --git a/package-lock.json b/package-lock.json index b10be95329b35..ef5b2106cd61e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5901,39 +5901,19 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -7657,20 +7637,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/express/node_modules/fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", @@ -10647,9 +10613,9 @@ } }, "node_modules/hono": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", - "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.3.tgz", + "integrity": "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==", "dev": true, "license": "MIT", "engines": { @@ -11928,14 +11894,14 @@ } }, "node_modules/koa": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/koa/-/koa-3.1.1.tgz", - "integrity": "sha512-KDDuvpfqSK0ZKEO2gCPedNjl5wYpfj+HNiuVRlbhd1A88S3M0ySkdf2V/EJ4NWt5dwh5PXCdcenrKK2IQJAxsg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.1.2.tgz", + "integrity": "sha512-2LOQnFKu3m0VxpE+5sb5+BRTSKrXmNxGgxVRiKwD9s5KQB1zID/FRXhtzeV7RT1L2GVpdEEAfVuclFOMGl1ikA==", "dev": true, "license": "MIT", "dependencies": { "accepts": "^1.3.8", - "content-disposition": "~0.5.4", + "content-disposition": "~1.0.1", "content-type": "^1.0.5", "cookies": "~0.9.1", "delegates": "^1.0.0", diff --git a/package.json b/package.json index 92aade626d160..d7f05095ed7c5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.110.0", - "distro": "8445dd0629deae101785f6b8e406ab3784a42fa6", + "distro": "31b6675a65c3b72a5a20fdc648050340585879c3", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts index 93ec32ae65e28..89c480c64c4e1 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts @@ -371,6 +371,7 @@ suite('Snippet Variables Resolver', function () { getCompleteWorkspace = this._throw; getWorkspace(): IWorkspace { return workspace; } getWorkbenchState = this._throw; + hasWorkspaceData = this._throw; getWorkspaceFolder = this._throw; isCurrentWorkspace = this._throw; isInsideWorkspace = this._throw; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index fb5b0122e9181..e12a15076e4f9 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -852,6 +852,10 @@ class StandaloneWorkspaceContextService implements IWorkspaceContextService { return WorkbenchState.EMPTY; } + public hasWorkspaceData(): boolean { + return this.getWorkbenchState() !== WorkbenchState.EMPTY; + } + public getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return resource && resource.scheme === StandaloneWorkspaceContextService.SCHEME ? this.workspace.folders[0] : null; } diff --git a/src/vs/platform/browserView/common/browserView.ts b/src/vs/platform/browserView/common/browserView.ts index 28161016f83a3..43bcb59d6c66e 100644 --- a/src/vs/platform/browserView/common/browserView.ts +++ b/src/vs/platform/browserView/common/browserView.ts @@ -34,6 +34,7 @@ export interface IBrowserViewState { lastFavicon: string | undefined; lastError: IBrowserViewLoadError | undefined; storageScope: BrowserViewStorageScope; + zoomFactor: number; } export interface IBrowserViewNavigationEvent { @@ -153,6 +154,14 @@ export interface IBrowserViewService { */ destroyBrowserView(id: string): Promise; + /** + * Get the state of an existing browser view by ID, or throw if it doesn't exist + * @param id The browser view identifier + * @return The state of the browser view for the given ID + * @throws If no browser view exists for the given ID + */ + getState(id: string): Promise; + /** * Update the bounds of a browser view * @param id The browser view identifier diff --git a/src/vs/platform/browserView/common/playwrightService.ts b/src/vs/platform/browserView/common/playwrightService.ts index b49c50b5fb388..1615924a5cfe6 100644 --- a/src/vs/platform/browserView/common/playwrightService.ts +++ b/src/vs/platform/browserView/common/playwrightService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from '../../../base/common/event.js'; -import { VSBuffer } from '../../../base/common/buffer.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; export const IPlaywrightService = createDecorator('playwrightService'); @@ -63,23 +62,24 @@ export interface IPlaywrightService { getSummary(pageId: string): Promise; /** - * Run a function with access to a Playwright page. + * Run a function with access to a Playwright page and return its raw result, or throw an error. * The first function argument is always the Playwright `page` object, and additional arguments can be passed after. * @param pageId The browser view ID identifying the page to operate on. * @param fnDef The function code to execute. Should contain the function definition but not its invocation, e.g. `async (page, arg1, arg2) => { ... }`. * @param args Additional arguments to pass to the function after the `page` object. - * @returns The result of the function execution, including a page summary. + * @returns The result of the function execution. */ - invokeFunction(pageId: string, fnDef: string, ...args: unknown[]): Promise<{ result: unknown; summary: string }>; + invokeFunctionRaw(pageId: string, fnDef: string, ...args: unknown[]): Promise; /** - * Takes a screenshot of the current page viewport and returns it as a VSBuffer. - * @param pageId The browser view ID identifying the page to capture. - * @param selector Optional Playwright selector to capture a specific element instead of the viewport. - * @param fullPage Whether to capture the full scrollable page instead of just the viewport. - * @returns The screenshot image data. + * Run a function with access to a Playwright page and return a result for tool output, including error handling. + * The first function argument is always the Playwright `page` object, and additional arguments can be passed after. + * @param pageId The browser view ID identifying the page to operate on. + * @param fnDef The function code to execute. Should contain the function definition but not its invocation, e.g. `async (page, arg1, arg2) => { ... }`. + * @param args Additional arguments to pass to the function after the `page` object. + * @returns The result of the function execution, including a page summary. */ - captureScreenshot(pageId: string, selector?: string, fullPage?: boolean): Promise; + invokeFunction(pageId: string, fnDef: string, ...args: unknown[]): Promise<{ result: unknown; summary: string }>; /** * Responds to a file chooser dialog on the given page. diff --git a/src/vs/platform/browserView/electron-main/browserView.ts b/src/vs/platform/browserView/electron-main/browserView.ts index 45e5d838d277c..ec0c2fb6b895b 100644 --- a/src/vs/platform/browserView/electron-main/browserView.ts +++ b/src/vs/platform/browserView/electron-main/browserView.ts @@ -359,7 +359,8 @@ export class BrowserView extends Disposable implements ICDPTarget { lastScreenshot: this._lastScreenshot, lastFavicon: this._lastFavicon, lastError: this._lastError, - storageScope: this.session.storageScope + storageScope: this.session.storageScope, + zoomFactor: webContents.getZoomFactor() }; } @@ -509,13 +510,6 @@ export class BrowserView extends Disposable implements ICDPTarget { } } - /** - * Set the zoom factor of this view - */ - async setZoomFactor(zoomFactor: number): Promise { - await this._view.webContents.setZoomFactor(zoomFactor); - } - /** * Focus this view */ diff --git a/src/vs/platform/browserView/electron-main/browserViewMainService.ts b/src/vs/platform/browserView/electron-main/browserViewMainService.ts index 7db0af2ac3451..7d95fe8d3d3f9 100644 --- a/src/vs/platform/browserView/electron-main/browserViewMainService.ts +++ b/src/vs/platform/browserView/electron-main/browserViewMainService.ts @@ -278,6 +278,10 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa return this._getBrowserView(id).onDidClose; } + async getState(id: string): Promise { + return this._getBrowserView(id).getState(); + } + async destroyBrowserView(id: string): Promise { return this.browserViews.deleteAndDispose(id); } @@ -330,10 +334,6 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa return this._getBrowserView(id).dispatchKeyEvent(keyEvent); } - async setZoomFactor(id: string, zoomFactor: number): Promise { - return this._getBrowserView(id).setZoomFactor(zoomFactor); - } - async focus(id: string): Promise { return this._getBrowserView(id).focus(); } diff --git a/src/vs/platform/browserView/node/playwrightService.ts b/src/vs/platform/browserView/node/playwrightService.ts index 3016e0a3659e2..0a55710ec61f2 100644 --- a/src/vs/platform/browserView/node/playwrightService.ts +++ b/src/vs/platform/browserView/node/playwrightService.ts @@ -10,7 +10,6 @@ import { ILogService } from '../../log/common/log.js'; import { IPlaywrightService } from '../common/playwrightService.js'; import { IBrowserViewGroupRemoteService } from '../node/browserViewGroupRemoteService.js'; import { IBrowserViewGroup } from '../common/browserViewGroup.js'; -import { VSBuffer } from '../../../base/common/buffer.js'; import { PlaywrightTab } from './playwrightTab.js'; // eslint-disable-next-line local/code-import-patterns @@ -125,18 +124,22 @@ export class PlaywrightService extends Disposable implements IPlaywrightService return this._pages.getSummary(pageId, true); } + async invokeFunctionRaw(pageId: string, fnDef: string, ...args: unknown[]): Promise { + await this.initialize(); + + const vm = await import('vm'); + const fn = vm.compileFunction(`return (${fnDef})(page, ...args)`, ['page', 'args'], { parsingContext: vm.createContext() }); + + return this._pages.runAgainstPage(pageId, (page) => fn(page, args)); + } + async invokeFunction(pageId: string, fnDef: string, ...args: unknown[]): Promise<{ result: unknown; summary: string }> { this.logService.info(`[PlaywrightService] Invoking function on view ${pageId}`); try { - await this.initialize(); - - const vm = await import('vm'); - const fn = vm.compileFunction(`return (${fnDef})(page, ...args)`, ['page', 'args'], { parsingContext: vm.createContext() }); - let result; try { - result = await this._pages.runAgainstPage(pageId, (page) => fn(page, args)); + result = await this.invokeFunctionRaw(pageId, fnDef, ...args); } catch (err: unknown) { result = err instanceof Error ? err.message : String(err); } @@ -155,16 +158,6 @@ export class PlaywrightService extends Disposable implements IPlaywrightService } } - async captureScreenshot(pageId: string, selector?: string, fullPage?: boolean): Promise { - await this.initialize(); - return this._pages.runAgainstPage(pageId, async page => { - const screenshotBuffer = selector - ? await page.locator(selector).screenshot({ type: 'jpeg', quality: 80 }) - : await page.screenshot({ type: 'jpeg', quality: 80, fullPage: fullPage ?? false }); - return VSBuffer.wrap(screenshotBuffer); - }); - } - async replyToFileChooser(pageId: string, files: string[]): Promise<{ summary: string }> { await this.initialize(); const summary = await this._pages.replyToFileChooser(pageId, files); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index a0459d077e6ea..691e1a3bf2e89 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -74,6 +74,11 @@ export interface IWorkspaceContextService { * Returns if the provided resource is inside the workspace or not. */ isInsideWorkspace(resource: URI): boolean; + + /** + * Return `true` if the current workspace has data (e.g. folders or a workspace configuration) that can be sent to the extension host, otherwise `false`. + */ + hasWorkspaceData(): boolean; } export interface IResolvedWorkspace extends IWorkspaceIdentifier, IBaseWorkspace { diff --git a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css index 6df45c10a07d0..57410a89fc0f3 100644 --- a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css +++ b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css @@ -35,7 +35,7 @@ width: 100%; max-width: 200px; aspect-ratio: 1/1; - background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-dark.svg'); + background-image: url('./letterpress-sessions-dark.svg'); background-size: contain; background-position: center; background-repeat: no-repeat; @@ -43,16 +43,9 @@ margin-bottom: 20px; } -.vs .chat-full-welcome-letterpress { - background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-light.svg'); -} - +.vs .chat-full-welcome-letterpress, .hc-light .chat-full-welcome-letterpress { - background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-hcLight.svg'); -} - -.hc-black .chat-full-welcome-letterpress { - background-image: url('../../../../../workbench/browser/parts/editor/media/letterpress-hcDark.svg'); + background-image: url('./letterpress-sessions-light.svg'); } @container (max-height: 350px) { diff --git a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-exploration.svg b/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-exploration.svg deleted file mode 100644 index 81991ee80fa80..0000000000000 --- a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-exploration.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-insider.svg b/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-insider.svg deleted file mode 100644 index 55db4d45e46fb..0000000000000 --- a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-insider.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-stable.svg b/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-stable.svg deleted file mode 100644 index e26c10e038aa0..0000000000000 --- a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions-stable.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions.svg b/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions.svg deleted file mode 100644 index e26c10e038aa0..0000000000000 --- a/src/vs/sessions/contrib/chat/browser/media/code-icon-agent-sessions.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/sessions/contrib/chat/browser/media/letterpress-sessions-dark.svg b/src/vs/sessions/contrib/chat/browser/media/letterpress-sessions-dark.svg new file mode 100644 index 0000000000000..623629695fc17 --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/media/letterpress-sessions-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/vs/sessions/contrib/chat/browser/media/letterpress-sessions-light.svg b/src/vs/sessions/contrib/chat/browser/media/letterpress-sessions-light.svg new file mode 100644 index 0000000000000..29dfd5459d13c --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/media/letterpress-sessions-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/vs/sessions/contrib/chat/browser/syncIndicator.ts b/src/vs/sessions/contrib/chat/browser/syncIndicator.ts index 3480f68166b2a..e07089daee537 100644 --- a/src/vs/sessions/contrib/chat/browser/syncIndicator.ts +++ b/src/vs/sessions/contrib/chat/browser/syncIndicator.ts @@ -81,13 +81,13 @@ export class SyncIndicator extends Disposable { this._renderDisposables.add(dom.addDisposableListener(button, dom.EventType.CLICK, (e) => { dom.EventHelper.stop(e, true); - this.commandService.executeCommand(GIT_SYNC_COMMAND); + this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri); })); this._renderDisposables.add(dom.addDisposableListener(button, dom.EventType.KEY_DOWN, (e) => { if (e.key === 'Enter' || e.key === ' ') { dom.EventHelper.stop(e, true); - this.commandService.executeCommand(GIT_SYNC_COMMAND); + this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri); } })); diff --git a/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts b/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts index 57f7e8a23ccbe..65869c6cb8b5b 100644 --- a/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts +++ b/src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts @@ -112,7 +112,9 @@ function registerSyncAction(behind: number, ahead: number): IDisposable { override async run(accessor: ServicesAccessor): Promise { const commandService = accessor.get(ICommandService); - await commandService.executeCommand('git.sync'); + const sessionManagementService = accessor.get(ISessionsManagementService); + const worktreeUri = sessionManagementService.getActiveSession()?.worktree; + await commandService.executeCommand('git.sync', worktreeUri); } } return registerAction2(SynchronizeChangesAction); diff --git a/src/vs/sessions/contrib/workspace/browser/workspaceFolderManagement.ts b/src/vs/sessions/contrib/workspace/browser/workspaceFolderManagement.ts index 020c7861d46bf..09b03f5a160f9 100644 --- a/src/vs/sessions/contrib/workspace/browser/workspaceFolderManagement.ts +++ b/src/vs/sessions/contrib/workspace/browser/workspaceFolderManagement.ts @@ -85,10 +85,6 @@ export class WorkspaceFolderManagementContribution extends Disposable implements return; } - if (!this.isUriTrusted(session.repository)) { - return; - } - if (!this.isUriTrusted(session.worktree)) { await this.workspaceTrustManagementService.setUrisTrust([session.worktree], true); } diff --git a/src/vs/sessions/services/workspace/browser/workspaceContextService.ts b/src/vs/sessions/services/workspace/browser/workspaceContextService.ts index b9ec9f571f9c5..58b4dfb588923 100644 --- a/src/vs/sessions/services/workspace/browser/workspaceContextService.ts +++ b/src/vs/sessions/services/workspace/browser/workspaceContextService.ts @@ -49,7 +49,11 @@ export class SessionsWorkspaceContextService extends Disposable implements IWork } getWorkbenchState(): WorkbenchState { - return WorkbenchState.EMPTY; + return WorkbenchState.WORKSPACE; + } + + hasWorkspaceData(): boolean { + return false; } getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts index 5059c608dae22..69a3859726970 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts @@ -66,7 +66,8 @@ export class MainThreadLanguageModelTools extends Disposable implements MainThre // Only return content and metadata to EH const out: Dto = { content: result.content, - toolMetadata: result.toolMetadata + toolMetadata: result.toolMetadata, + toolResultError: result.toolResultError, }; return toolResultHasBuffers(result) ? new SerializableObjectWithBuffers(out) : out; } diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 02a2e749a1cf0..b756baf6d6aef 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -16,7 +16,7 @@ import { ILabelService } from '../../../platform/label/common/label.js'; import { INotificationService } from '../../../platform/notification/common/notification.js'; import { AuthInfo, Credentials, IRequestService } from '../../../platform/request/common/request.js'; import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, ResourceTrustRequestOptions } from '../../../platform/workspace/common/workspaceTrust.js'; -import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from '../../../platform/workspace/common/workspace.js'; +import { IWorkspace, IWorkspaceContextService, isUntitledWorkspace, WorkspaceFolder } from '../../../platform/workspace/common/workspace.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { checkGlobFileExists } from '../../services/extensions/common/workspaceContains.js'; import { IFileQueryBuilderOptions, ITextQueryBuilderOptions, QueryBuilder } from '../../services/search/common/queryBuilder.js'; @@ -130,7 +130,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } private getWorkspaceData(workspace: IWorkspace): IWorkspaceData | null { - if (this._contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (!this._contextService.hasWorkspaceData()) { return null; } return { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index d565aa7634c67..476ccdf8443db 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3873,6 +3873,9 @@ export namespace LanguageModelToolResult { if (result.toolMetadata !== undefined) { toolResult.toolMetadata = result.toolMetadata; } + if (result.toolResultError) { + toolResult.hasError = !!result.toolResultError; + } return toolResult; } @@ -3938,6 +3941,7 @@ export namespace LanguageModelToolResult { toolResultMessage: MarkdownString.fromStrict(result.toolResultMessage), toolResultDetails: detailsDto, toolMetadata: result.toolMetadata, + toolResultError: result.hasError, }; return hasBuffers ? new SerializableObjectWithBuffers(dto) : dto; diff --git a/src/vs/workbench/contrib/browserView/common/browserView.ts b/src/vs/workbench/contrib/browserView/common/browserView.ts index 76f04a67f829d..7654a648a753e 100644 --- a/src/vs/workbench/contrib/browserView/common/browserView.ts +++ b/src/vs/workbench/contrib/browserView/common/browserView.ts @@ -76,6 +76,14 @@ export interface IBrowserViewWorkbenchService { */ getOrCreateBrowserViewModel(id: string): Promise; + /** + * Get an existing browser view model for the given ID + * @param id The browser view identifier + * @returns A browser view model that proxies to the main process + * @throws If no browser view exists for the given ID + */ + getBrowserViewModel(id: string): Promise; + /** * Clear all storage data for the global browser session */ @@ -105,9 +113,9 @@ export interface IBrowserViewModel extends IDisposable { readonly isDevToolsOpen: boolean; readonly canGoForward: boolean; readonly error: IBrowserViewLoadError | undefined; - readonly storageScope: BrowserViewStorageScope; readonly sharedWithAgent: boolean; + readonly zoomFactor: number; readonly onDidChangeSharedWithAgent: Event; readonly onDidNavigate: Event; @@ -123,7 +131,7 @@ export interface IBrowserViewModel extends IDisposable { readonly onDidClose: Event; readonly onWillDispose: Event; - initialize(): Promise; + initialize(create: boolean): Promise; layout(bounds: IBrowserViewBounds): Promise; setVisible(visible: boolean): Promise; @@ -156,6 +164,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel { private _error: IBrowserViewLoadError | undefined = undefined; private _storageScope: BrowserViewStorageScope = BrowserViewStorageScope.Ephemeral; private _sharedWithAgent: boolean = false; + private _zoomFactor: number = 1; private readonly _onDidChangeSharedWithAgent = this._register(new Emitter()); readonly onDidChangeSharedWithAgent: Event = this._onDidChangeSharedWithAgent.event; @@ -190,6 +199,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel { get error(): IBrowserViewLoadError | undefined { return this._error; } get storageScope(): BrowserViewStorageScope { return this._storageScope; } get sharedWithAgent(): boolean { return this._sharedWithAgent; } + get zoomFactor(): number { return this._zoomFactor; } get onDidNavigate(): Event { return this.browserViewService.onDynamicDidNavigate(this.id); @@ -236,9 +246,11 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel { } /** - * Initialize the model with the current state from the main process + * Initialize the model with the current state from the main process. + * @param create Whether to create the browser view if it doesn't already exist. + * @throws If the browser view doesn't exist and `create` is false, or if initialization fails */ - async initialize(): Promise { + async initialize(create: boolean): Promise { const dataStorageSetting = this.configurationService.getValue( 'workbench.browser.dataStorage' ) ?? BrowserViewStorageScope.Global; @@ -253,7 +265,9 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel { const dataStorage = isWorkspaceUntrusted ? BrowserViewStorageScope.Ephemeral : dataStorageSetting; const workspaceId = this.workspaceContextService.getWorkspace().id; - const state = await this.browserViewService.getOrCreateBrowserView(this.id, dataStorage, workspaceId); + const state = create + ? await this.browserViewService.getOrCreateBrowserView(this.id, dataStorage, workspaceId) + : await this.browserViewService.getState(this.id); this._url = state.url; this._title = state.title; @@ -268,6 +282,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel { this._error = state.lastError; this._storageScope = state.storageScope; this._sharedWithAgent = await this.playwrightService.isPageTracked(this.id); + this._zoomFactor = state.zoomFactor; // Set up state synchronization @@ -314,6 +329,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel { } async layout(bounds: IBrowserViewBounds): Promise { + this._zoomFactor = bounds.zoomFactor; return this.browserViewService.layout(this.id, bounds); } diff --git a/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts b/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts index 68d2376c587d1..c60a295cd5a55 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts +++ b/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts @@ -27,6 +27,23 @@ export class BrowserViewWorkbenchService implements IBrowserViewWorkbenchService } async getOrCreateBrowserViewModel(id: string): Promise { + return this._getBrowserViewModel(id, true); + } + + async getBrowserViewModel(id: string): Promise { + return this._getBrowserViewModel(id, false); + } + + async clearGlobalStorage(): Promise { + return this._browserViewService.clearGlobalStorage(); + } + + async clearWorkspaceStorage(): Promise { + const workspaceId = this.workspaceContextService.getWorkspace().id; + return this._browserViewService.clearWorkspaceStorage(workspaceId); + } + + private async _getBrowserViewModel(id: string, create: boolean): Promise { let model = this._models.get(id); if (model) { return model; @@ -36,7 +53,12 @@ export class BrowserViewWorkbenchService implements IBrowserViewWorkbenchService this._models.set(id, model); // Initialize the model with current state - await model.initialize(); + try { + await model.initialize(create); + } catch (e) { + this._models.delete(id); + throw e; + } // Clean up model when disposed Event.once(model.onWillDispose)(() => { @@ -45,13 +67,4 @@ export class BrowserViewWorkbenchService implements IBrowserViewWorkbenchService return model; } - - async clearGlobalStorage(): Promise { - return this._browserViewService.clearGlobalStorage(); - } - - async clearWorkspaceStorage(): Promise { - const workspaceId = this.workspaceContextService.getWorkspace().id; - return this._browserViewService.clearWorkspaceStorage(workspaceId); - } } diff --git a/src/vs/workbench/contrib/browserView/electron-browser/tools/browserToolHelpers.ts b/src/vs/workbench/contrib/browserView/electron-browser/tools/browserToolHelpers.ts index e6981287fe2f1..04e48f2bab5c1 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/tools/browserToolHelpers.ts +++ b/src/vs/workbench/contrib/browserView/electron-browser/tools/browserToolHelpers.ts @@ -9,6 +9,18 @@ import { IToolResult } from '../../../chat/common/tools/languageModelToolsServic // eslint-disable-next-line local/code-import-patterns import type { Page } from 'playwright-core'; +/** + * Shared helper for running a Playwright function against a page and returning its result. + */ +export async function playwrightInvokeRaw( + playwrightService: IPlaywrightService, + pageId: string, + fn: (page: Page, ...args: TArgs) => Promise, + ...args: TArgs +): Promise { + return playwrightService.invokeFunctionRaw(pageId, fn.toString(), ...args); +} + /** * Shared helper for running a Playwright function against a page and returning * a tool result. Handles success/error formatting. diff --git a/src/vs/workbench/contrib/browserView/electron-browser/tools/screenshotBrowserTool.ts b/src/vs/workbench/contrib/browserView/electron-browser/tools/screenshotBrowserTool.ts index 831ca2bf8426f..b2bb1f9a3fed7 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/tools/screenshotBrowserTool.ts +++ b/src/vs/workbench/contrib/browserView/electron-browser/tools/screenshotBrowserTool.ts @@ -8,7 +8,8 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { localize } from '../../../../../nls.js'; import { IPlaywrightService } from '../../../../../platform/browserView/common/playwrightService.js'; import { ToolDataSource, type CountTokensCallback, type IPreparedToolInvocation, type IToolData, type IToolImpl, type IToolInvocation, type IToolInvocationPreparationContext, type IToolResult, type ToolProgress } from '../../../chat/common/tools/languageModelToolsService.js'; -import { errorResult } from './browserToolHelpers.js'; +import { IBrowserViewWorkbenchService } from '../../common/browserView.js'; +import { errorResult, playwrightInvokeRaw } from './browserToolHelpers.js'; import { OpenPageToolId } from './openBrowserTool.js'; import { ReadBrowserToolData } from './readBrowserTool.js'; @@ -29,16 +30,16 @@ export const ScreenshotBrowserToolData: IToolData = { }, selector: { type: 'string', - description: 'Playwright selector of an element to capture. If omitted, captures the whole page.' + description: 'Playwright selector of an element to capture. If omitted, captures the whole viewport.' }, ref: { type: 'string', - description: 'Element reference to capture. If omitted, captures the whole page.' + description: 'Element reference to capture. If omitted, captures the whole viewport.' }, - fullPage: { + scrollIntoViewIfNeeded: { type: 'boolean', - description: 'Set to true to capture the full scrollable page instead of just the viewport. Incompatible with selector/ref.' - }, + description: 'Whether to scroll the element into view before capturing. Defaults to false.', + } }, required: ['pageId'], }, @@ -48,11 +49,12 @@ interface IScreenshotBrowserToolParams { pageId: string; selector?: string; ref?: string; - fullPage?: boolean; + scrollIntoViewIfNeeded?: boolean; } export class ScreenshotBrowserTool implements IToolImpl { constructor( + @IBrowserViewWorkbenchService private readonly browserViewWorkbenchService: IBrowserViewWorkbenchService, @IPlaywrightService private readonly playwrightService: IPlaywrightService, ) { } @@ -75,7 +77,24 @@ export class ScreenshotBrowserTool implements IToolImpl { selector = `aria-ref=${params.ref}`; } - const screenshot = await this.playwrightService.captureScreenshot(params.pageId, selector, params.fullPage); + // Note that we don't use Playwright's screenshot methods because they cause brief flashing on the page, + // and also doesn't handle zooming well. + const browserViewModel = await this.browserViewWorkbenchService.getBrowserViewModel(params.pageId); // Throws if the given pageId doesn't exist + const bounds = selector && await playwrightInvokeRaw(this.playwrightService, params.pageId, async (page, selector, scrollIntoViewIfNeeded) => { + const locator = page.locator(selector); + if (scrollIntoViewIfNeeded) { + await locator.scrollIntoViewIfNeeded(); + } + return locator.boundingBox(); + }, selector, params.scrollIntoViewIfNeeded) || undefined; + const zoomFactor = browserViewModel.zoomFactor; + if (bounds) { + bounds.x *= zoomFactor; + bounds.y *= zoomFactor; + bounds.width *= zoomFactor; + bounds.height *= zoomFactor; + } + const screenshot = await browserViewModel.captureScreenshot({ rect: bounds }); return { content: [ diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d1232f91828ff..b6ea731238562 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -55,7 +55,7 @@ import { ElicitationState, IChatService, IChatToolInvocation } from '../../commo import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/attachments/chatVariableEntries.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/model/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/widget/chatWidgetHistoryService.js'; -import { AgentsControlClickBehavior, ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js'; import { ILanguageModelToolsConfirmationService } from '../../common/tools/languageModelToolsConfirmationService.js'; @@ -600,36 +600,14 @@ export function registerChatActions() { const viewsService = accessor.get(IViewsService); const viewDescriptorService = accessor.get(IViewDescriptorService); const widgetService = accessor.get(IChatWidgetService); - const configurationService = accessor.get(IConfigurationService); const chatLocation = viewDescriptorService.getViewLocationById(ChatViewId); const chatVisible = viewsService.isViewVisible(ChatViewId); - const clickBehavior = configurationService.getValue(ChatConfiguration.AgentsControlClickBehavior); - switch (clickBehavior) { - case AgentsControlClickBehavior.Cycle: - if (chatVisible) { - if ( - chatLocation === ViewContainerLocation.AuxiliaryBar && - !layoutService.isAuxiliaryBarMaximized() - ) { - layoutService.setAuxiliaryBarMaximized(true); - (await widgetService.revealWidget())?.focusInput(); - } else { - this.updatePartVisibility(layoutService, chatLocation, false); - } - } else { - this.updatePartVisibility(layoutService, chatLocation, true); - (await widgetService.revealWidget())?.focusInput(); - } - break; - default: - if (chatVisible) { - this.updatePartVisibility(layoutService, chatLocation, false); - } else { - this.updatePartVisibility(layoutService, chatLocation, true); - (await widgetService.revealWidget())?.focusInput(); - } - break; + if (chatVisible) { + this.updatePartVisibility(layoutService, chatLocation, false); + } else { + this.updatePartVisibility(layoutService, chatLocation, true); + (await widgetService.revealWidget())?.focusInput(); } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 9701a79f06436..292cf32d80d7a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -185,6 +185,14 @@ const requestInProgressOrPendingToolCall = ContextKeyExpr.or( ChatContextKeys.Editing.hasToolConfirmation, ChatContextKeys.Editing.hasQuestionCarousel, ); +const requestInProgressWithoutInput = ContextKeyExpr.and( + ChatContextKeys.requestInProgress, + ChatContextKeys.inputHasText.negate(), +); +const pendingToolCall = ContextKeyExpr.or( + ChatContextKeys.Editing.hasToolConfirmation, + ChatContextKeys.Editing.hasQuestionCarousel, +); const whenNotInProgress = ChatContextKeys.requestInProgress.negate(); export class ChatSubmitAction extends SubmitAction { @@ -838,7 +846,7 @@ export class CancelAction extends Action2 { menu: [{ id: MenuId.ChatExecute, when: ContextKeyExpr.and( - requestInProgressOrPendingToolCall, + ContextKeyExpr.or(requestInProgressWithoutInput, pendingToolCall), ChatContextKeys.remoteJobCreating.negate(), ChatContextKeys.currentlyEditing.negate(), ), diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatPluginActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatPluginActions.ts index 3a5f9993b0f71..c4186a2adca5f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatPluginActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatPluginActions.ts @@ -10,7 +10,7 @@ import { ChatContextKeys } from '../../common/actions/chatContextKeys.js'; import { CHAT_CATEGORY, CHAT_CONFIG_MENU_ID } from './chatActions.js'; import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; -class ManagePluginsAction extends Action2 { +export class ManagePluginsAction extends Action2 { static readonly ID = 'workbench.action.chat.managePlugins'; constructor() { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQueueActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQueueActions.ts index 2db5e67e5d3c3..6606748d653f4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQueueActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQueueActions.ts @@ -280,7 +280,10 @@ export function registerChatQueueActions(): void { submenu: MenuId.ChatExecuteQueue, title: localize2('chat.queueSubmenu', "Queue"), icon: Codicon.listOrdered, - when: queuingActionsPresent, + when: ContextKeyExpr.and( + queuingActionsPresent, + ChatContextKeys.inputHasText, + ), group: 'navigation', order: 4, }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index d50fc45f037c1..aad03e3583ae4 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -44,7 +44,7 @@ import { ChatTodoListService, IChatTodoListService } from '../common/tools/chatT import { ChatTransferService, IChatTransferService } from '../common/model/chatTransferService.js'; import { IChatVariablesService } from '../common/attachments/chatVariables.js'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from '../common/widget/chatWidgetHistoryService.js'; -import { AgentsControlClickBehavior, ChatConfiguration, ChatNotificationMode } from '../common/constants.js'; +import { ChatConfiguration, ChatNotificationMode } from '../common/constants.js'; import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } from '../common/ignoredFiles.js'; import { ILanguageModelsService, LanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelStatsService, LanguageModelStatsService } from '../common/languageModelStats.js'; @@ -218,20 +218,6 @@ configurationRegistry.registerConfiguration({ description: nls.localize('interactiveSession.editor.lineHeight', "Controls the line height in pixels in chat codeblocks. Use 0 to compute the line height from the font size."), default: 0 }, - [ChatConfiguration.AgentsControlClickBehavior]: { - type: 'string', - enum: [AgentsControlClickBehavior.Default, AgentsControlClickBehavior.Cycle], - enumDescriptions: [ - nls.localize('chat.agentsControl.clickBehavior.default', "Clicking chat icon toggles chat visibility."), - nls.localize('chat.agentsControl.clickBehavior.cycle', "Clicking chat icon cycles through: show chat, maximize chat, hide chat. This requires chat to be contained in the secondary sidebar."), - ], - markdownDescription: nls.localize('chat.agentsControl.clickBehavior', "Controls the behavior when clicking on the chat icon in the command center."), - default: AgentsControlClickBehavior.Default, // TODO@bpasero figure out the default - tags: ['experimental'], - experiment: { - mode: 'auto' - } - }, [ChatConfiguration.AgentStatusEnabled]: { type: 'boolean', markdownDescription: nls.localize('chat.agentsControl.enabled', "Controls whether the 'Agent Status' indicator is shown in the title bar command center. Enabling this setting will automatically enable {0}. The unread/in-progress session indicators require {1} to be enabled.", '`#window.commandCenter#`', '`#chat.viewSessions.enabled#`'), @@ -265,6 +251,10 @@ configurationRegistry.registerConfiguration({ }, default: { 'panel': 'always', + }, + tags: ['experimental'], + experiment: { + mode: 'startup' } }, 'chat.implicitContext.suggestedContext': { diff --git a/src/vs/workbench/contrib/chat/browser/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/browser/chatSlashCommands.ts index 9acdbe8df1642..16c998243669a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSlashCommands.ts @@ -19,6 +19,7 @@ import { IChatService } from '../common/chatService/chatService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../common/constants.js'; import { ACTION_ID_NEW_CHAT } from './actions/chatActions.js'; import { ChatSubmitAction, OpenModePickerAction, OpenModelPickerAction } from './actions/chatExecuteActions.js'; +import { ManagePluginsAction } from './actions/chatPluginActions.js'; import { ConfigureToolsAction } from './actions/chatToolActions.js'; import { IAgentSessionsService } from './agentSessions/agentSessionsService.js'; import { CONFIGURE_INSTRUCTIONS_ACTION_ID } from './promptSyntax/attachInstructionsAction.js'; @@ -87,6 +88,16 @@ export class ChatSlashCommandsContribution extends Disposable { }, async () => { await commandService.executeCommand(ConfigureToolsAction.ID); })); + this._store.add(slashCommandService.registerSlashCommand({ + command: 'plugins', + detail: nls.localize('plugins', "Manage plugins"), + sortText: 'z3_plugins', + executeImmediately: true, + silent: true, + locations: [ChatAgentLocation.Chat] + }, async () => { + await commandService.executeCommand(ManagePluginsAction.ID); + })); this._store.add(slashCommandService.registerSlashCommand({ command: 'debug', detail: nls.localize('debug', "Show Chat Debug View"), diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts index 615ae0c63dcdf..4a3ec940760e8 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { $, append } from '../../../../../../base/browser/dom.js'; +import { IRenderedMarkdown, renderAsPlaintext } from '../../../../../../base/browser/markdownRenderer.js'; import { alert } from '../../../../../../base/browser/ui/aria/aria.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js'; +import { stripIcons } from '../../../../../../base/common/iconLabels.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; -import { IRenderedMarkdown } from '../../../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../../nls.js'; import { IChatProgressMessage, IChatTask, IChatTaskSerialized, IChatToolInvocation, IChatToolInvocationSerialized } from '../../../common/chatService/chatService.js'; @@ -61,10 +62,9 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP return; } - if (this.showSpinner && !this.configurationService.getValue(AccessibilityWorkbenchSettingId.VerboseChatProgressUpdates)) { - // TODO@roblourens is this the right place for this? + if (this.showSpinner && this.configurationService.getValue(AccessibilityWorkbenchSettingId.VerboseChatProgressUpdates)) { // this step is in progress, communicate it to SR users - alert(progress.content.value); + alert(stripIcons(renderAsPlaintext(progress.content))); } const isLoadingIcon = icon && ThemeIcon.isEqual(icon, ThemeIcon.modify(Codicon.loading, 'spin')); // Even if callers request shimmer, only the active (spinner-visible) progress row should animate. diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatSubagentContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatSubagentContentPart.ts index 969d7b506a0d0..7a4f6d0a12919 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatSubagentContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatSubagentContentPart.ts @@ -379,6 +379,29 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen const labelElement = this._collapseButton.labelElement; + if (!this.isActive) { + labelElement.textContent = ''; + this.titleShimmerSpan = undefined; + + if (this.titleDetailRendered) { + this.titleDetailRendered.dispose(); + this.titleDetailRendered = undefined; + } + this.titleDetailContainer = undefined; + + const prefixSpan = $('span'); + prefixSpan.textContent = `${prefix}:`; + labelElement.appendChild(prefixSpan); + + const descSpan = $('span.chat-thinking-title-detail-text'); + descSpan.textContent = ` ${this.description}`; + labelElement.appendChild(descSpan); + + this._collapseButton.element.ariaLabel = shimmerText; + this._collapseButton.element.ariaExpanded = String(this.isExpanded()); + return; + } + // Ensure the persistent shimmer span exists if (!this.titleShimmerSpan || !this.titleShimmerSpan.parentElement) { labelElement.textContent = ''; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts index b47f7312a4c76..473db6ff51a02 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts @@ -14,6 +14,7 @@ import { ChatConfiguration, ThinkingDisplayMode } from '../../../common/constant import { ChatTreeItem } from '../../chat.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { AccessibilityWorkbenchSettingId } from '../../../../accessibility/browser/accessibilityConfiguration.js'; import { MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { IRenderedMarkdown } from '../../../../../../base/browser/markdownRenderer.js'; import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; @@ -261,7 +262,9 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen } // Alert screen reader users that thinking has started - alert(localize('chat.thinking.started', 'Thinking')); + if (this.configurationService.getValue(AccessibilityWorkbenchSettingId.VerboseChatProgressUpdates)) { + alert(localize('chat.thinking.started', 'Thinking')); + } if (configuredMode === ThinkingDisplayMode.Collapsed) { this.setExpanded(false); diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatConfirmationWidget.css b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatConfirmationWidget.css index c96e09ae84995..cd5343e662e84 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatConfirmationWidget.css +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatConfirmationWidget.css @@ -104,6 +104,7 @@ .chat-confirmation-widget-title small { font-size: 1em; + opacity: 0.7; &::before { content: ' \2013 '; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css index 31acb4c321b58..f6732a06bb70e 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css @@ -142,7 +142,8 @@ } /* todo: ideally not !important, but the competing css has 14 specificity */ - .codicon:not(.chat-thinking-icon){ + .codicon.codicon-check, + .codicon.codicon-loading { display: none !important; } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatTipContent.css b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatTipContent.css index dba68be9b33e4..66dbbe1f82865 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatTipContent.css +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatTipContent.css @@ -17,7 +17,6 @@ background-color: var(--vscode-editorWidget-background); border: 1px solid var(--vscode-chat-requestBorder); border-radius: var(--vscode-cornerRadius-medium); - z-index: 100; transition: opacity 0.1s ease-in-out; } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts index 1cf8b39f364e6..5130b606b9832 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts @@ -110,6 +110,7 @@ import { ChatPendingDragController } from './chatPendingDragAndDrop.js'; import { HookType } from '../../common/promptSyntax/hookSchema.js'; import { ChatQuestionCarouselAutoReply } from './chatQuestionCarouselAutoReply.js'; import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js'; +import { AccessibilityWorkbenchSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; const $ = dom.$; @@ -912,6 +913,14 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer templateData.detail.classList.toggle('show-checkmarks', !!this.configService.getValue(AccessibilityWorkbenchSettingId.ShowChatCheckmarks)); + updateCheckmarks(); + templateData.elementDisposables.add(this.configService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AccessibilityWorkbenchSettingId.ShowChatCheckmarks)) { + updateCheckmarks(); + } + })); } } diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index 57a0106718a43..1502fd802342e 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -613,17 +613,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.initSelectedModel(); - this._register(this.languageModelsService.onDidChangeLanguageModels((vendor) => { - // Remove vendor from cache since the models changed and what is stored is no longer valid - // TODO @lramos15 - The cache should be less confusing since we have the LM Service cache + the view cache interacting weirdly - this.storageService.store( - CachedLanguageModelsKey, - this.storageService.getObject(CachedLanguageModelsKey, StorageScope.APPLICATION, []).filter(m => !m.identifier.startsWith(vendor)), - StorageScope.APPLICATION, - StorageTarget.MACHINE - ); - - // We've changed models and the current one is no longer available. Select a new one + this._register(this.languageModelsService.onDidChangeLanguageModels(() => { const selectedModel = this._currentLanguageModel ? this.getModels().find(m => m.identifier === this._currentLanguageModel.get()?.identifier) : undefined; if (!this.currentLanguageModel || !selectedModel) { this.setCurrentLanguageModelToDefault(); @@ -1061,12 +1051,25 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private getModels(): ILanguageModelChatMetadataAndIdentifier[] { const cachedModels = this.storageService.getObject(CachedLanguageModelsKey, StorageScope.APPLICATION, []); - let models = this.languageModelsService.getLanguageModelIds() + const liveModels = this.languageModelsService.getLanguageModelIds() .map(modelId => ({ identifier: modelId, metadata: this.languageModelsService.lookupLanguageModel(modelId)! })); - if (models.length === 0 || models.some(m => m.metadata.isDefaultForLocation[this.location]) === false) { - models = cachedModels; - } else { + + // Merge live models with cached models per-vendor. For vendors whose + // models have resolved, use the live data. For vendors that are still + // contributed but haven't resolved yet (startup race), keep their + // cached models. Vendors that are no longer contributed at all (e.g. + // extension uninstalled) are evicted from the cache. + let models: ILanguageModelChatMetadataAndIdentifier[]; + if (liveModels.length > 0) { + const liveVendors = new Set(liveModels.map(m => m.metadata.vendor)); + const contributedVendors = new Set(this.languageModelsService.getVendors().map(v => v.vendor)); + models = [ + ...liveModels, + ...cachedModels.filter(m => !liveVendors.has(m.metadata.vendor) && contributedVendors.has(m.metadata.vendor)), + ]; this.storageService.store(CachedLanguageModelsKey, models, StorageScope.APPLICATION, StorageTarget.MACHINE); + } else { + models = cachedModels; } models.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts index d6a3689597bac..e5c907aed38d1 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts @@ -397,7 +397,7 @@ function createUnavailableModelItem( let description: string | MarkdownString | undefined; if (reason === 'upgrade') { - description = new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade your plan](command:workbench.action.chat.upgradePlan \" \")"), { isTrusted: true }); + description = new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade](command:workbench.action.chat.upgradePlan \" \")"), { isTrusted: true }); } else if (reason === 'update') { description = localize('chat.modelPicker.updateDescription', "Update VS Code"); } else { @@ -409,7 +409,7 @@ function createUnavailableModelItem( let hoverContent: MarkdownString; if (reason === 'upgrade') { hoverContent = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); - hoverContent.appendMarkdown(localize('chat.modelPicker.upgradeHover', "[Upgrade your plan](command:workbench.action.chat.upgradePlan \" \") to use this model.")); + hoverContent.appendMarkdown(localize('chat.modelPicker.upgradeHover', "[Upgrade to GitHub Copilot Pro](command:workbench.action.chat.upgradePlan \" \") with a free 30-day trial to use the best models.")); } else if (reason === 'update') { hoverContent = getUpdateHoverContent(updateStateType); } else { @@ -596,6 +596,11 @@ export class ModelPickerWidget extends Disposable { getModelPickerAccessibilityProvider(), listOptions ); + + const activeElement = dom.getActiveElement(); + if (dom.isHTMLInputElement(activeElement) && activeElement.classList.contains('action-list-filter-input')) { + activeElement.classList.add('chat-model-picker-filter-input'); + } } private _updateBadge(): void { diff --git a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css index b714445a27378..47b58886cdebd 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -1436,6 +1436,13 @@ have to be updated for changes to the rules above, or to support more deeply nes color: var(--vscode-textLink-activeForeground); } +.action-widget .action-list-filter-input.chat-model-picker-filter-input, +.action-widget .action-list-filter-input.chat-model-picker-filter-input:focus { + outline: none; + box-shadow: none; + border-color: transparent; +} + .interactive-session .chat-input-toolbars .codicon-debug-stop { color: var(--vscode-icon-foreground) !important; } @@ -2726,15 +2733,25 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-request .header.partially-disabled .detail-container { - margin-left: 4px; + margin-left: 0px; } .interactive-item-container .header .detail .codicon-check { margin-right: 7px; vertical-align: middle; font-size: 11px; + display: none; } + .interactive-item-container .header.partially-disabled .detail.show-checkmarks { + margin-left: 4px; + + .codicon-check { + display: inline; + } + } + + .request-hover { position: absolute; overflow: hidden; diff --git a/src/vs/workbench/contrib/chat/browser/widget/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/widget/media/chatViewWelcome.css index 9a5cff033fa59..8a6e91f1861b9 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chatViewWelcome.css @@ -157,7 +157,7 @@ div.chat-welcome-view { } & > .chat-welcome-view-disclaimer { - color: var(--vscode-input-placeholderForeground); + color: var(--vscode-descriptionForeground); text-align: center; margin: 0; max-width: 256px; diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index d7ce75b61bd31..57b1391e68f21 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -52,7 +52,6 @@ export enum ChatConfiguration { ShowCodeBlockProgressAnimation = 'chat.agent.codeBlockProgress', RestoreLastPanelSession = 'chat.restoreLastPanelSession', ExitAfterDelegation = 'chat.exitAfterDelegation', - AgentsControlClickBehavior = 'chat.agentsControl.clickBehavior', ExplainChangesEnabled = 'chat.editing.explainChanges.enabled', GrowthNotificationEnabled = 'chat.growthNotification.enabled', ChatCustomizationMenuEnabled = 'chat.customizationsMenu.enabled', @@ -101,11 +100,6 @@ export enum ChatNotificationMode { Always = 'always', } -export enum AgentsControlClickBehavior { - Default = 'default', - Cycle = 'cycle', -} - export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook' | 'editing-session'; export enum ChatAgentLocation { diff --git a/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts b/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts index cecb075bd83f8..28253d0586ebb 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts @@ -13,9 +13,11 @@ import { joinPath } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js'; import { FileOperationResult, IFileService, toFileOperationResult } from '../../../../../platform/files/common/files.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { IUserDataProfilesService } from '../../../../../platform/userDataProfile/common/userDataProfile.js'; @@ -57,6 +59,8 @@ export class ChatSessionStore extends Disposable { @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, + @IDialogService private readonly dialogService: IDialogService, + @IOpenerService private readonly openerService: IOpenerService, ) { super(); @@ -339,6 +343,8 @@ export class ChatSessionStore extends Disposable { } } + private _didReportIssue = false; + private async writeSession(session: ChatModel | ISerializableChatData): Promise { try { const index = this.internalGetIndex(); @@ -349,7 +355,32 @@ export class ChatSessionStore extends Disposable { session.dataSerializer = new ChatSessionOperationLog(); } - const { op, data } = session.dataSerializer.write(session); + let op: 'append' | 'replace'; + let data: VSBuffer; + try { + ({ op, data } = session.dataSerializer.write(session)); + } catch (e) { + // This is a big of an ugly prompt, but there is _something_ going on with + // missing sessions. Unfortunately it's hard to root cause because users would + // not notice an error until they reload the window, at which point any error + // is gone. Throw a very verbose dialog here so we can get some quality + // bug reports, if the issue is indeed in the serialized. + // todo@connor4312: remove after a little bit + if (!this._didReportIssue) { + this._didReportIssue = true; + this.dialogService.prompt({ + custom: true, // so text is copyable + title: localize('chatSessionStore.serializationError', 'Error saving chat session'), + message: localize('chatSessionStore.writeError', 'Error serializing chat session for storage. The session will be lost if the window is closed. Please report this issue to the VS Code team:\n\n{0}', e.stack || toErrorMessage(e)), + buttons: [ + { label: localize('reportIssue', 'Report Issue'), run: () => this.openerService.open('https://github.com/microsoft/vscode/issues/new?template=bug_report.md') } + ] + }); + } + + throw e; + } + if (data.byteLength > 0) { await this.fileService.writeFile(storageLocation.log, data, { append: op === 'append' }); } diff --git a/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts b/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts index 01ce16ae67a9b..1e134a69a6d54 100644 --- a/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts +++ b/src/vs/workbench/contrib/chat/common/model/objectMutationLog.ts @@ -7,6 +7,34 @@ import { assertNever } from '../../../../../base/common/assert.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { isUndefinedOrNull } from '../../../../../base/common/types.js'; +/** + * Updates an error's message and stack trace with a prefix. In V8 the stack + * string starts with "ErrorName: message\n at …", so we rebuild the header + * after mutating the message. + */ +function prefixError(e: Error, prefix: string): void { + e.message = prefix + e.message; + if (e.stack) { + const nlIdx = e.stack.indexOf('\n'); + e.stack = nlIdx !== -1 + ? `${e.name}: ${e.message}${e.stack.slice(nlIdx)}` + : `${e.name}: ${e.message}`; + } +} + +/** + * Prepends a path segment to an error as it unwinds through nested extract + * calls. Each level adds its segment so the final message reads e.g. + * `.responses[2].content: Cannot read property 'x' of undefined`. + */ +function rethrowWithPathSegment(e: unknown, segment: string | number): never { + if (e instanceof Error) { + const part = typeof segment === 'number' ? `[${segment}]` : `.${segment}`; + const needsSep = !e.message.startsWith('[') && !e.message.startsWith('.'); + prefixError(e, part + (needsSep ? ': ' : '')); + } + throw e; +} /** IMPORTANT: `Key` comes first. Then we should sort in order of least->most expensive to diff */ const enum TransformKind { @@ -96,7 +124,13 @@ export function array(schema: TransformObject | TransformValue return { kind: TransformKind.Array, itemSchema: schema, - extract: from => from?.map(item => schema.extract(item)), + extract: from => from?.map((item, i) => { + try { + return schema.extract(item); + } catch (e) { + rethrowWithPathSegment(e, i); + } + }), }; } @@ -124,7 +158,11 @@ export function object(schema: Schema, options?: Obje const result: Record = Object.create(null); for (const [key, transform] of entries) { - result[key] = transform.extract(from); + try { + result[key] = transform.extract(from); + } catch (e) { + rethrowWithPathSegment(e, key); + } } return result as R; }, @@ -278,7 +316,15 @@ export class ObjectMutationLog { // Generate diff entries const entries: Entry[] = []; const path: ObjectPath = []; - this._diff(this._transform, path, this._previous, currentValue, entries); + try { + this._diff(this._transform, path, this._previous, currentValue, entries); + } catch (e) { + if (e instanceof Error) { + const pathStr = path.map(s => typeof s === 'number' ? `[${s}]` : `.${s}`).join('') || ''; + prefixError(e, `error diffing at ${pathStr}: `); + } + throw e; + } if (entries.length === 0) { // No changes 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 7394858cd2ab6..6c566c8bbdc06 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -711,7 +711,7 @@ export class PromptsService extends Disposable implements IPromptsService { } const visibility = { userInvocable: ast.header.userInvocable !== false, - agentInvocable: ast.header.infer === true || ast.header.disableModelInvocation !== true, + agentInvocable: ast.header.infer !== undefined ? ast.header.infer === true : ast.header.disableModelInvocation !== true, } satisfies ICustomAgentVisibility; let model = ast.header.model; diff --git a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsService.ts index d14a93696e08e..fe83345a4a2af 100644 --- a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsService.ts @@ -265,7 +265,7 @@ export interface IToolResult { content: (IToolResultPromptTsxPart | IToolResultTextPart | IToolResultDataPart)[]; toolResultMessage?: string | IMarkdownString; toolResultDetails?: Array | IToolResultInputOutputDetails | IToolResultOutputDetails; - toolResultError?: string; + toolResultError?: string | boolean; toolMetadata?: unknown; /** Whether to ask the user to confirm these tool results. Overrides {@link IToolConfirmationMessages.confirmResults}. */ confirmResults?: boolean; diff --git a/src/vs/workbench/contrib/chat/test/browser/tools/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/tools/languageModelToolsService.test.ts index f642745892e92..f8ee29ce85fbf 100644 --- a/src/vs/workbench/contrib/chat/test/browser/tools/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/tools/languageModelToolsService.test.ts @@ -3905,7 +3905,7 @@ suite('LanguageModelToolsService', () => { // Verify error result returned assert.ok(result.toolResultError); - assert.ok(result.toolResultError.includes('Destructive operations require approval')); + assert.ok((result.toolResultError as string).includes('Destructive operations require approval')); assert.strictEqual(result.content[0].kind, 'text'); assert.ok((result.content[0] as IToolResultTextPart).value.includes('Tool execution denied')); 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 dc2fb6d67b9c7..59673c29689fd 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 @@ -1338,6 +1338,60 @@ suite('PromptsService', () => { ); }); + test('header with infer: false sets agentInvocable to false', async () => { + const rootFolderName = 'custom-agents-infer-false'; + const rootFolder = `/${rootFolderName}`; + const rootFolderUri = URI.file(rootFolder); + + workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); + + await mockFiles(fileService, [ + { + path: `${rootFolder}/.github/agents/agent-infer-false.agent.md`, + contents: [ + '---', + 'description: \'Agent with infer: false.\'', + 'infer: false', + '---', + 'I should not be invocable by the model.', + ] + }, + { + path: `${rootFolder}/.github/agents/agent-infer-true.agent.md`, + contents: [ + '---', + 'description: \'Agent with infer: true.\'', + 'infer: true', + '---', + 'I should be invocable by the model.', + ] + }, + { + path: `${rootFolder}/.github/agents/agent-no-infer.agent.md`, + contents: [ + '---', + 'description: \'Agent without infer.\'', + '---', + 'I should default to being invocable by the model.', + ] + } + ]); + + const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) })); + + const inferFalseAgent = result.find(a => a.name === 'agent-infer-false'); + assert.ok(inferFalseAgent, 'Should find agent with infer: false'); + assert.strictEqual(inferFalseAgent.visibility.agentInvocable, false, 'infer: false should set agentInvocable to false'); + + const inferTrueAgent = result.find(a => a.name === 'agent-infer-true'); + assert.ok(inferTrueAgent, 'Should find agent with infer: true'); + assert.strictEqual(inferTrueAgent.visibility.agentInvocable, true, 'infer: true should set agentInvocable to true'); + + const noInferAgent = result.find(a => a.name === 'agent-no-infer'); + assert.ok(noInferAgent, 'Should find agent without infer'); + assert.strictEqual(noInferAgent.visibility.agentInvocable, true, 'missing infer should default agentInvocable to true'); + }); + test('agents from user data folder', async () => { const rootFolderName = 'custom-agents-user-data'; const rootFolder = `/${rootFolderName}`; diff --git a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index 12f8e73bc273c..e22f45b9dfb6a 100644 --- a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts @@ -30,7 +30,14 @@ const MetaModulesToLookFor = [ ]; const ModulesToLookFor = [ - // Packages that suggest a node server + // Platform-related type definition packages + '@types/vscode', + '@vscode/dts', + '@types/node', + '@types/bun', + 'bun-types', + '@types/web', + // Packages that suggest a JS-based backend server 'express', 'sails', 'koa', @@ -42,18 +49,36 @@ const ModulesToLookFor = [ '@nestjs/core', 'strapi', 'gatsby', + 'fastify', + 'hono', // JS frameworks 'react', 'react-native', 'react-native-macos', 'react-native-windows', 'rnpm-plugin-windows', + '@types/react', '@angular/core', '@ionic', 'vue', 'tns-core-modules', '@nativescript/core', 'electron', + '@remix-run/react', + '@remix-run/express', + '@remix-run/node', + '@remix-run/dev', + '@sveltejs/kit', + 'preact', + '@preact/signals', + '@builder.io/qwik', + 'alpinejs', + 'astro', + 'htmx.org', + 'solid-js', + 'svelte', + 'lit-html', + 'vuepress', // Other interesting packages 'aws-sdk', 'aws-amplify', @@ -61,6 +86,7 @@ const ModulesToLookFor = [ 'azure-storage', 'chroma', 'deepseek-js', + 'docusaurus', 'faiss', 'firebase', '@google-cloud/common', @@ -71,6 +97,43 @@ const ModulesToLookFor = [ 'pinecone', 'praisonai', 'qdrant', + 'typescript', + '@typescript/native-preview', + 'tslib', + 'eslint', + 'oxlint', + 'oxfmt', + 'typescript-eslint', + 'prettier', + 'dprint', + '@dprint/formatter', + '@babel/cli', + '@babel/core', + '@swc/cli', + '@swc/core', + 'vite', + 'esbuild', + 'rollup', + 'rolldown', + 'nx', + 'turbo', + 'webpack', + 'parcel', + '@biomejs/biome', + '@rspack/core', + '@rspack/cli', + '@trpc/client', + '@trpc/server', + 'zod', + 'pyright', + 'ts-morph', + 'oxc-minify', + 'oxc-transform', + 'oxc-resolver', + 'vue-tsc', + 'svelte-check', + 'tsup', + 'tsdown', // Office and Sharepoint packages '@microsoft/teams-js', '@microsoft/office-js', @@ -96,13 +159,17 @@ const ModulesToLookFor = [ 'playwright-firefox', 'playwright-webkit', // Other interesting browser testing packages + 'chai', 'cypress', + 'gherkin', + 'jest', + 'mocha', 'nightwatch', 'protractor', 'puppeteer', 'selenium-webdriver', + 'vitest', 'webdriverio', - 'gherkin', // AzureSDK packages '@azure/app-configuration', '@azure/cosmos-sign', @@ -183,6 +250,7 @@ const ModulesToLookFor = [ '@azure/arm-hybridkubernetes', '@azure/arm-kubernetesconfiguration', //AI and vector db dev packages + 'ai', '@anthropic-ai/sdk', '@anthropic-ai/tokenizer', '@arizeai/openinference-instrumentation-langchain', @@ -615,6 +683,73 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.tika" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.webdriverio" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.gherkin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@types/vscode" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@vscode/dts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@types/node" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@types/bun" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.bun-types" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@types/web" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.fastify" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.hono" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@types/react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@remix-run/react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@remix-run/express" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@remix-run/node" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@remix-run/dev" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@sveltejs/kit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.preact" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@preact/signals" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@builder.io/qwik" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.alpinejs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.astro" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.htmx.org" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.solid-js" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.svelte" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.lit-html" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.vuepress" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.docusaurus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.typescript" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@typescript/native-preview" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.tslib" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.eslint" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.oxlint" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.oxfmt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.typescript-eslint" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.prettier" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.dprint" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@dprint/formatter" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@babel/cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@babel/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@swc/cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@swc/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.vite" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.esbuild" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.rollup" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.rolldown" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.nx" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.turbo" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.webpack" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.parcel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@biomejs/biome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@rspack/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@rspack/cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@trpc/client" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@trpc/server" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.zod" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.pyright" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.ts-morph" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.oxc-minify" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.oxc-transform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.oxc-resolver" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.vue-tsc" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.svelte-check" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.tsup" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.tsdown" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.chai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.jest" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.mocha" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.vitest" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/app-configuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/cosmos-sign" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/cosmos-language-service" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index b6b3e48dcd3e1..d9491186439c9 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -194,6 +194,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return WorkbenchState.EMPTY; } + public hasWorkspaceData(): boolean { + return this.getWorkbenchState() !== WorkbenchState.EMPTY; + } + public getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return this.workspace.getFolder(resource); } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index a214818b29ce2..8fefa7297ca33 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -11,6 +11,7 @@ import { ConfigurationTarget } from '../../../../platform/configuration/common/c import { isBoolean, isString } from '../../../../base/common/types.js'; import { IconContribution, IconDefinition } from '../../../../platform/theme/common/iconRegistry.js'; import { ColorScheme, ThemeTypeSelector } from '../../../../platform/theme/common/theme.js'; +import product from '../../../../platform/product/common/product.js'; export const IWorkbenchThemeService = refineServiceDecorator(IThemeService); @@ -38,17 +39,19 @@ export enum ThemeSettings { SYSTEM_COLOR_THEME = 'window.systemColorTheme' } -export enum ThemeSettingDefaults { - COLOR_THEME_DARK = 'Default Dark Modern', - COLOR_THEME_LIGHT = 'Default Light Modern', - COLOR_THEME_HC_DARK = 'Default High Contrast', - COLOR_THEME_HC_LIGHT = 'Default High Contrast Light', +const isOSS = !product.quality; - COLOR_THEME_DARK_OLD = 'Default Dark+', - COLOR_THEME_LIGHT_OLD = 'Default Light+', +export namespace ThemeSettingDefaults { + export const COLOR_THEME_DARK = isOSS ? 'Experimental Dark' : 'Default Dark Modern'; + export const COLOR_THEME_LIGHT = isOSS ? 'Experimental Light' : 'Default Light Modern'; + export const COLOR_THEME_HC_DARK = 'Default High Contrast'; + export const COLOR_THEME_HC_LIGHT = 'Default High Contrast Light'; - FILE_ICON_THEME = 'vs-seti', - PRODUCT_ICON_THEME = 'Default', + export const COLOR_THEME_DARK_OLD = 'Default Dark+'; + export const COLOR_THEME_LIGHT_OLD = 'Default Light+'; + + export const FILE_ICON_THEME = 'vs-seti'; + export const PRODUCT_ICON_THEME = 'Default'; } export const COLOR_THEME_DARK_INITIAL_COLORS = { diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index e625aa79e5bbd..3a60b99ae9161 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -120,6 +120,10 @@ export class TestContextService implements IWorkspaceContextService { return WorkbenchState.EMPTY; } + hasWorkspaceData(): boolean { + return this.getWorkbenchState() !== WorkbenchState.EMPTY; + } + getCompleteWorkspace(): Promise { return Promise.resolve(this.getWorkspace()); } diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png index bfe8d842cf7fe..89032243eae7f 100644 --- a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png +++ b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0f41bf819fed7de2b99f15776cdb0d353f9bfaa4526dbb857f12be7c1343881 -size 15185 +oid sha256:cea3d365efe7e033cfd1fb8bc408fa0853d769af9cf7fcd8c1217d7c1e7982ba +size 15184 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultiSelectQuestion/Dark.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultiSelectQuestion/Dark.png new file mode 100644 index 0000000000000..b5f1d62d15e21 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultiSelectQuestion/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05f0f518e03f8b91e2178761c977215bd2561b10b04ad69685aa054231cd81be +size 15058 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultiSelectQuestion/Light.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultiSelectQuestion/Light.png new file mode 100644 index 0000000000000..8d17c778055ca --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultiSelectQuestion/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c5de42c54bcd1d35f59711f2c8c9c24f747df926017f1c3a52e3703c9d69da7 +size 15326 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultipleQuestions/Dark.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultipleQuestions/Dark.png new file mode 100644 index 0000000000000..597e7cbbc672e --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultipleQuestions/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7510376219beff4c9bb17bccb3de3631a633bda27fe53d6418309de484167688 +size 7506 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultipleQuestions/Light.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultipleQuestions/Light.png new file mode 100644 index 0000000000000..93470614a41e7 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/MultipleQuestions/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8918a56e08760211087b9cd0c798758cb7a55c002fcce1b343d06fd9f078197f +size 7434 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/NoSkip/Dark.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/NoSkip/Dark.png new file mode 100644 index 0000000000000..356200049e46c --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/NoSkip/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a324c6cde2596228ddc260d04688bf5153860b7e7de5b41f9211b456285ee581 +size 25804 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/NoSkip/Light.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/NoSkip/Light.png new file mode 100644 index 0000000000000..2b1bfefce81b7 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/NoSkip/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0eb74e618241b7d863bf8bac7132204b332c47e44e15a9c0b12270fa31a4fb71 +size 25874 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleSelectQuestion/Dark.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleSelectQuestion/Dark.png new file mode 100644 index 0000000000000..1ecda11f8e889 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleSelectQuestion/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d5ede2eec1393f07f015cd724e9bb0a84492d85b175577705e195ee948e80ab +size 26210 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleSelectQuestion/Light.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleSelectQuestion/Light.png new file mode 100644 index 0000000000000..5025b7c37cdcf --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleSelectQuestion/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:695e11e18a05b7f9d0b73612edc7f4b0288408bedbf7fc259fccb2c7fe5f7dd0 +size 26288 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleTextQuestion/Dark.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleTextQuestion/Dark.png new file mode 100644 index 0000000000000..2bc00d0ba8436 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleTextQuestion/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d03152872902d21cea55c0d8ba464894c9a6ec77e7a58f68360c3be26a9a62a4 +size 7302 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleTextQuestion/Light.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleTextQuestion/Light.png new file mode 100644 index 0000000000000..26a90f10d7af8 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SingleTextQuestion/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d727ac8876b301c7bf81d799197b6a8b0962a95bc9eb9b269e28f6c1eb4acd88 +size 7246 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SkippedSummary/Dark.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SkippedSummary/Dark.png new file mode 100644 index 0000000000000..ccf46b5de69d9 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SkippedSummary/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3e31fb939e811c76b4fb03a7211cc84b86794dbdce2ecf26d1d42324dc86fd4 +size 2099 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SkippedSummary/Light.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SkippedSummary/Light.png new file mode 100644 index 0000000000000..29bea3c013db4 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SkippedSummary/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d7fd7e2f20d9a7a8e2ea867e0383e88291547b5e9d2c04642a84b5087491f64 +size 2064 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SubmittedSummary/Dark.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SubmittedSummary/Dark.png new file mode 100644 index 0000000000000..7b0b53c68a447 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SubmittedSummary/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c84efc6a1c010c303da05377ae980ead28d0f7412d0ce0e49bfc66ce23c766d3 +size 20333 diff --git a/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SubmittedSummary/Light.png b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SubmittedSummary/Light.png new file mode 100644 index 0000000000000..858f4e5ccd34d --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/chatQuestionCarousel/SubmittedSummary/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cf2233bec9a1ab64995e6168081da43e485d6b2388f868648613d63034160b8 +size 18278 diff --git a/test/componentFixtures/.screenshots/baseline/codeActionList/GroupedCodeActions/Dark.png b/test/componentFixtures/.screenshots/baseline/codeActionList/GroupedCodeActions/Dark.png new file mode 100644 index 0000000000000..b48c12e655ed1 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/codeActionList/GroupedCodeActions/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b99678e6d41e30c874e0517eb6d97ccb3eaece31c489482fae81dd92904051c9 +size 14963 diff --git a/test/componentFixtures/.screenshots/baseline/codeActionList/GroupedCodeActions/Light.png b/test/componentFixtures/.screenshots/baseline/codeActionList/GroupedCodeActions/Light.png new file mode 100644 index 0000000000000..8b2121d3a7c79 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/codeActionList/GroupedCodeActions/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfaa85a662524e06f3d8323bf9a4655822d36567ffa438db85719a79addb6cd3 +size 14439 diff --git a/test/componentFixtures/.screenshots/baseline/codeActionList/SimpleQuickFixes/Dark.png b/test/componentFixtures/.screenshots/baseline/codeActionList/SimpleQuickFixes/Dark.png new file mode 100644 index 0000000000000..f82d6f544e6cc --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/codeActionList/SimpleQuickFixes/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38e0cc34e642669cd200ac977f5e8948c100c100457f202b20a642ca211ab959 +size 6540 diff --git a/test/componentFixtures/.screenshots/baseline/codeActionList/SimpleQuickFixes/Light.png b/test/componentFixtures/.screenshots/baseline/codeActionList/SimpleQuickFixes/Light.png new file mode 100644 index 0000000000000..ecbb1c1dc430c --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/codeActionList/SimpleQuickFixes/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfd9431a64258c6939f06c59d9163d33b65278cfd3ace4ae3c269f1e7e12d40 +size 6111 diff --git a/test/componentFixtures/.screenshots/baseline/findWidget/Find/Dark.png b/test/componentFixtures/.screenshots/baseline/findWidget/Find/Dark.png new file mode 100644 index 0000000000000..55c823622c787 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/findWidget/Find/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed115cf70c25bc400d265a08db2e1d6a4d7596674d7a6ddc932ca1ad33ccaa25 +size 33367 diff --git a/test/componentFixtures/.screenshots/baseline/findWidget/Find/Light.png b/test/componentFixtures/.screenshots/baseline/findWidget/Find/Light.png new file mode 100644 index 0000000000000..9e80b63bb4819 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/findWidget/Find/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cfebc3c9b7caa2d40cfb8db5b47ae6f5a446bb72883bdce5198ed5d296ae82f +size 32889 diff --git a/test/componentFixtures/.screenshots/baseline/findWidget/FindAndReplace/Dark.png b/test/componentFixtures/.screenshots/baseline/findWidget/FindAndReplace/Dark.png new file mode 100644 index 0000000000000..cc9b2aa43769a --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/findWidget/FindAndReplace/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d3a4d62c5226a9ca16b2327d95337a68286107a4fb12d1d5bc43530f17f6b1d +size 33379 diff --git a/test/componentFixtures/.screenshots/baseline/findWidget/FindAndReplace/Light.png b/test/componentFixtures/.screenshots/baseline/findWidget/FindAndReplace/Light.png new file mode 100644 index 0000000000000..c8a7325251209 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/findWidget/FindAndReplace/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:522ba17bdd1d4378c0be262631d8e6bb1fd867f2999c07c06c4991cd9971a4cb +size 33114 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbar/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbar/Dark.png new file mode 100644 index 0000000000000..201fc1a7bf5ae --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbar/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a88fb3139a351b8c46df4309f64ed71210c456d4a89aad6b087ee512e26d9e1b +size 9762 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbar/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbar/Light.png new file mode 100644 index 0000000000000..be9dca0e21be0 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbar/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0679cbdb00bacb421b6c8c65b632211a5dd153ae4c698c7a49903e53632310f +size 9569 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbarHovered/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbarHovered/Dark.png new file mode 100644 index 0000000000000..201fc1a7bf5ae --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbarHovered/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a88fb3139a351b8c46df4309f64ed71210c456d4a89aad6b087ee512e26d9e1b +size 9762 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbarHovered/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbarHovered/Light.png new file mode 100644 index 0000000000000..be9dca0e21be0 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/HintsToolbarHovered/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0679cbdb00bacb421b6c8c65b632211a5dd153ae4c698c7a49903e53632310f +size 9569 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/JumpToHint/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/JumpToHint/Dark.png new file mode 100644 index 0000000000000..37bbb987300a6 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/JumpToHint/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c3e7be1da3065615074aa5678fd7ca38dd7b3906573fcd555ee9e249d9d645f +size 15252 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/JumpToHint/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/JumpToHint/Light.png new file mode 100644 index 0000000000000..ea8fe1b7068b2 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/JumpToHint/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:054c0dcab2a00526f7fe63440a7e782618f03b9396f40e6a31b8e28e7ea59145 +size 14846 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/LongDistanceHint/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/LongDistanceHint/Dark.png new file mode 100644 index 0000000000000..83d5938ca6c74 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/LongDistanceHint/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05deae07ae1a6a5f738d6ebd4d475ed2e7d14630977b8b64cdae9030da2885ff +size 55645 diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/LongDistanceHint/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/LongDistanceHint/Light.png new file mode 100644 index 0000000000000..23db3df30fb8c --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/inlineCompletionsExtras/LongDistanceHint/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9d81860fc8c296f9cc718ed3e6690669eb0f7301c17ae2f15639c984d499178 +size 54993 diff --git a/test/componentFixtures/.screenshots/baseline/promptFilePickers/InstructionFilesWithAgentInstructions/Dark.png b/test/componentFixtures/.screenshots/baseline/promptFilePickers/InstructionFilesWithAgentInstructions/Dark.png new file mode 100644 index 0000000000000..dba2c92968eda --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/promptFilePickers/InstructionFilesWithAgentInstructions/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c54a17b190dde0caa1f175b1ef025850a6ede44f03d8d124859355c309aa28a +size 27557 diff --git a/test/componentFixtures/.screenshots/baseline/promptFilePickers/InstructionFilesWithAgentInstructions/Light.png b/test/componentFixtures/.screenshots/baseline/promptFilePickers/InstructionFilesWithAgentInstructions/Light.png new file mode 100644 index 0000000000000..63742c15bcda1 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/promptFilePickers/InstructionFilesWithAgentInstructions/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eac6ff34521293e8f1f7e70f3f1e4227cc371861763fe954cce62d49b181955b +size 26846 diff --git a/test/componentFixtures/.screenshots/baseline/promptFilePickers/PromptFiles/Dark.png b/test/componentFixtures/.screenshots/baseline/promptFilePickers/PromptFiles/Dark.png new file mode 100644 index 0000000000000..ffc64ba9aac75 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/promptFilePickers/PromptFiles/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:099e8f6e3b7c1c33fae6a75f47e387ab7f87536953607ec0f3a7b5160e0d4d59 +size 23377 diff --git a/test/componentFixtures/.screenshots/baseline/promptFilePickers/PromptFiles/Light.png b/test/componentFixtures/.screenshots/baseline/promptFilePickers/PromptFiles/Light.png new file mode 100644 index 0000000000000..c9ced02aeb75c --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/promptFilePickers/PromptFiles/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7def912d49d13cd496386ef22a39df862a558e5585b01bfc46fd6f5f3276d4 +size 22969 diff --git a/test/componentFixtures/.screenshots/baseline/renameWidget/RenameClass/Dark.png b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameClass/Dark.png new file mode 100644 index 0000000000000..26eeb8a87511d --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameClass/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1191cc4ad1b12b31b28e94fa6305b2c91a3ea399580f04cd93b5387c8b58b89d +size 20408 diff --git a/test/componentFixtures/.screenshots/baseline/renameWidget/RenameClass/Light.png b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameClass/Light.png new file mode 100644 index 0000000000000..cd7a92aa849df --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameClass/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e18bb8e20cf85b06825dea19140a88a9fd0d9ec844db939d8550956e9049a2f2 +size 19982 diff --git a/test/componentFixtures/.screenshots/baseline/renameWidget/RenameVariable/Dark.png b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameVariable/Dark.png new file mode 100644 index 0000000000000..2b11099af61cb --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameVariable/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc8f9f9295ae0e9b7f2c9215e788c0a0516d7c667f30f61fbadcfafdb13bb2e6 +size 21470 diff --git a/test/componentFixtures/.screenshots/baseline/renameWidget/RenameVariable/Light.png b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameVariable/Light.png new file mode 100644 index 0000000000000..909659b1b5fe1 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/renameWidget/RenameVariable/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d8a254da836fa81f53516bbc992394aeb91f7297cba4d13a8c5485ab3d46d7e +size 20862 diff --git a/test/componentFixtures/.screenshots/baseline/suggestWidget/MethodCompletions/Dark.png b/test/componentFixtures/.screenshots/baseline/suggestWidget/MethodCompletions/Dark.png new file mode 100644 index 0000000000000..20494f462540f --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/suggestWidget/MethodCompletions/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:869d6db0575e1e0dce468df0227cd68c1725d7341c5efb5b47ab929d3717761d +size 22978 diff --git a/test/componentFixtures/.screenshots/baseline/suggestWidget/MethodCompletions/Light.png b/test/componentFixtures/.screenshots/baseline/suggestWidget/MethodCompletions/Light.png new file mode 100644 index 0000000000000..aefc052d100ea --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/suggestWidget/MethodCompletions/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cff0734d5bbb48f8900e8cff6892aab851f58206db69a4c0a49425c0981f3967 +size 22208 diff --git a/test/componentFixtures/.screenshots/baseline/suggestWidget/MixedKinds/Dark.png b/test/componentFixtures/.screenshots/baseline/suggestWidget/MixedKinds/Dark.png new file mode 100644 index 0000000000000..f44709ad5bfd2 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/suggestWidget/MixedKinds/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b84d2c246a9594012baff0a70212b6768e6b0d784936c4f2f9d37378cf813248 +size 13541 diff --git a/test/componentFixtures/.screenshots/baseline/suggestWidget/MixedKinds/Light.png b/test/componentFixtures/.screenshots/baseline/suggestWidget/MixedKinds/Light.png new file mode 100644 index 0000000000000..2f008a94f3e4b --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/suggestWidget/MixedKinds/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c3aba099609c37eb2e58de791958665aec9fd8d2931b2208ec91515cac41b96 +size 13386 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/AvailableForDownload/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/AvailableForDownload/Dark.png new file mode 100644 index 0000000000000..4024eff5bc975 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/AvailableForDownload/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63aa8f046ab23ac6bd53722db86fd254c7cd88a487f45feb68cc9a9bbd18300f +size 1209 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/AvailableForDownload/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/AvailableForDownload/Light.png new file mode 100644 index 0000000000000..5baee83328743 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/AvailableForDownload/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d69bf362154015616fbb8c95052466061b7982f54203faea3ae89ad5afeaec3 +size 1221 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/CheckingForUpdates/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/CheckingForUpdates/Dark.png new file mode 100644 index 0000000000000..1197206909f45 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/CheckingForUpdates/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43c9b8702922e8892a0cf20afc053e8ac56cdff65c02a9d47cb0183262da7bd4 +size 1908 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/CheckingForUpdates/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/CheckingForUpdates/Light.png new file mode 100644 index 0000000000000..11d36a2938778 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/CheckingForUpdates/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0a17ec5b2f0e18be88383e491a09260654ce828236e37c11d5028e6ac326daa +size 1924 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloaded/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloaded/Dark.png new file mode 100644 index 0000000000000..0e6b503cc23c4 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloaded/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6897095d4c9d17a31bce693d2ce0f800d13ec56f2e27f4c5ec303ee64c7d19c3 +size 2189 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloaded/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloaded/Light.png new file mode 100644 index 0000000000000..91af710d8796d --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloaded/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3491ca579cf94b7148f4a943dc1e1a3eb44dd444d5268963c6a15a949d13bf88 +size 2056 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading0Percent/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading0Percent/Dark.png new file mode 100644 index 0000000000000..db4f67e3b5d69 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading0Percent/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3345ce1dc95c59d627fd618ddbf2f4028f0e7b13528bd3325fe6882cfb73891 +size 1781 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading0Percent/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading0Percent/Light.png new file mode 100644 index 0000000000000..a59cf06f88803 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading0Percent/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d4921395a33a571db185f7015160a23ee6e8ba3f82281a8ab4a935e43950b44 +size 1811 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading100Percent/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading100Percent/Dark.png new file mode 100644 index 0000000000000..28c4dcb54780c --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading100Percent/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7bc3e108e03debccd5881474ff30f42bf5c56a43a7b4525d3d2fc16b075b56d +size 2482 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading100Percent/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading100Percent/Light.png new file mode 100644 index 0000000000000..118bab91cc1da --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading100Percent/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8df766b159576a0c12683af17f7d9ed391a2bd765743494f3760d8b415380c22 +size 2252 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading30Percent/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading30Percent/Dark.png new file mode 100644 index 0000000000000..9bb317db8757f --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading30Percent/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c1f6f054881033c6162405b99e99ce8f049b9b20d320a74abc07793987474d9 +size 2249 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading30Percent/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading30Percent/Light.png new file mode 100644 index 0000000000000..c97505c005c0f --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading30Percent/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b6101c92d7073116c81eba73ddc08c721c2e947b4f7714887c54ffb91284d4e +size 2138 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading65Percent/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading65Percent/Dark.png new file mode 100644 index 0000000000000..c1853513bec44 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading65Percent/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3179d62298aff75dfc01442a4589a61d83b6ba7f14c4b8441c9a688ca655738d +size 2434 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading65Percent/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading65Percent/Light.png new file mode 100644 index 0000000000000..8060149471e0d --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Downloading65Percent/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e9dbcbf22233e5b48617067c8993a51c6b10afd940cd499270183e8eb4430f4 +size 2217 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/DownloadingIndeterminate/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/DownloadingIndeterminate/Dark.png new file mode 100644 index 0000000000000..4b0fc104ee8c5 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/DownloadingIndeterminate/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13295a45a513349c0c8ae6129301d5f2fb89549cae3b4453c6962a770b6290ec +size 3716 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/DownloadingIndeterminate/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/DownloadingIndeterminate/Light.png new file mode 100644 index 0000000000000..d7a897ebda5d6 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/DownloadingIndeterminate/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21dbc34d1ec2776453bc176631f2a0e9a03f7fcd528369efcb40fd5c49536c92 +size 3837 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Overwriting/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Overwriting/Dark.png new file mode 100644 index 0000000000000..db4f67e3b5d69 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Overwriting/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3345ce1dc95c59d627fd618ddbf2f4028f0e7b13528bd3325fe6882cfb73891 +size 1781 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Overwriting/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Overwriting/Light.png new file mode 100644 index 0000000000000..a59cf06f88803 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Overwriting/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d4921395a33a571db185f7015160a23ee6e8ba3f82281a8ab4a935e43950b44 +size 1811 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Ready/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Ready/Dark.png new file mode 100644 index 0000000000000..017a1ebcebe3a --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Ready/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f97568f25aeafee2be621a345675fb29afe6c17a6655eeb2b053e09a671f2fe7 +size 2116 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Ready/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Ready/Light.png new file mode 100644 index 0000000000000..230402c3327fb --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Ready/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faf0132e6298ffe9f285c4a2d6b7a0ef13c440175a22fa8febaff6b881cb4907 +size 1932 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Updating/Dark.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Updating/Dark.png new file mode 100644 index 0000000000000..4024eff5bc975 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Updating/Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63aa8f046ab23ac6bd53722db86fd254c7cd88a487f45feb68cc9a9bbd18300f +size 1209 diff --git a/test/componentFixtures/.screenshots/baseline/updateWidget/Updating/Light.png b/test/componentFixtures/.screenshots/baseline/updateWidget/Updating/Light.png new file mode 100644 index 0000000000000..5baee83328743 --- /dev/null +++ b/test/componentFixtures/.screenshots/baseline/updateWidget/Updating/Light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d69bf362154015616fbb8c95052466061b7982f54203faea3ae89ad5afeaec3 +size 1221 diff --git a/test/sanity/package-lock.json b/test/sanity/package-lock.json index 6113958376736..79d178a5f5cbf 100644 --- a/test/sanity/package-lock.json +++ b/test/sanity/package-lock.json @@ -107,24 +107,18 @@ "license": "Python-2.0" }, "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/browser-stdout": { @@ -654,12 +648,12 @@ } }, "node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17"