Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8f8b99b
Add copy button to chat message toolbar
pierceboggan Feb 24, 2026
bf38bf2
Add copy button to chat response footer toolbar
pierceboggan Feb 25, 2026
6e417b3
MCP Gateway: avoid blocking list calls on startup
RajeshKumar11 Feb 26, 2026
86ec1ce
Merge branch 'main' into fix/mcp-gateway-nonblocking-297780-v2
RajeshKumar11 Feb 27, 2026
3235b1e
Merge branch 'main' into fix/mcp-gateway-nonblocking-297780-v2
RajeshKumar11 Feb 27, 2026
72e7228
Merge branch 'main' into fix/mcp-gateway-nonblocking-297780-v2
RajeshKumar11 Feb 27, 2026
820760f
MCP Gateway: add 5 s per-server startup grace period
RajeshKumar11 Feb 27, 2026
df5dd11
Merge branch 'main' into fix/mcp-gateway-nonblocking-297780-v2
RajeshKumar11 Feb 27, 2026
5cb3cc3
Merge branch 'main' into fix/mcp-gateway-nonblocking-297780-v2
RajeshKumar11 Mar 1, 2026
c30f111
MCP gateway: apply grace period to Outdated servers same as Unknown
RajeshKumar11 Mar 1, 2026
517f7a3
Merge branch 'main' into fix/mcp-gateway-nonblocking-297780-v2
RajeshKumar11 Mar 1, 2026
4e5f31b
Merge branch 'main' into copilot-worktree-2026-02-24T23-48-15
pierceboggan Mar 3, 2026
dd8539f
Session window: apply patch to local
osortega Mar 3, 2026
ffc4f9d
Reapply 8e445caeffff66b8920466770121e4b53343ebea
mjbvz Mar 3, 2026
96ab8cd
Make sure we specify a tsconfig.browser.json for browser ext
mjbvz Mar 3, 2026
e8e300f
fix compile error in mcpGatewayToolBrokerChannel test
connor4312 Mar 3, 2026
6c608bf
fix: invalidate _startupGrace when cacheState regresses
connor4312 Mar 3, 2026
fa59b65
fix: use per-entry resolved tracking instead of autorun for _startupG…
connor4312 Mar 3, 2026
2910902
Use settings icon instead of tools icon in chat input (#299068)
daviddossett Mar 3, 2026
f0113db
tests
connor4312 Mar 3, 2026
c3d549c
fix chat tip issue (#299070)
meganrogge Mar 3, 2026
e848b20
constrain and make chat questions scrollable (#298786)
meganrogge Mar 3, 2026
876317a
Merge remote-tracking branch 'origin/main' into connor4312/fix-mcp-ga…
connor4312 Mar 3, 2026
1f3ddd8
add play button in prompt config dialog (#297715)
aeschli Mar 3, 2026
5113d68
Merge pull request #299071 from mjbvz/dev/mjbvz/blank-anteater
mjbvz Mar 3, 2026
c4672f2
fix merge error
connor4312 Mar 3, 2026
a31ef0b
Merge pull request #299033 from mjbvz/dev/mjbvz/pleasant-catshark
mjbvz Mar 3, 2026
26b6024
Using merge strategy
osortega Mar 3, 2026
bfd958e
Rename
osortega Mar 3, 2026
3dfa3a4
fix: passing js-flags to utility process (#299069)
deepak1556 Mar 3, 2026
5e0dc2f
Merge pull request #299072 from microsoft/connor4312/fix-mcp-gateway-…
connor4312 Mar 4, 2026
c7d548a
Fix /hooks slash command from blocking chat (#299084)
pwang347 Mar 4, 2026
eb8db02
Merge branch 'main' into copilot-worktree-2026-02-24T23-48-15
pierceboggan Mar 4, 2026
fd97f69
Disable slash commands for background (#299085)
DonJayamanne Mar 4, 2026
f7d450f
refactor: update getCliUserSubfolder to clarify prompts exclusion (#2…
joshspicer Mar 4, 2026
0b270f1
Don't show Used n references when opening an old session (#299092)
roblourens Mar 4, 2026
56ca891
api: fix memory leaks in MainThreadManagedSockets (#299093)
connor4312 Mar 4, 2026
55dc53f
Review comments
osortega Mar 4, 2026
68da193
Clean up
osortega Mar 4, 2026
76fa920
Merge pull request #299090 from microsoft/osortega/interior-flea
osortega Mar 4, 2026
7588065
Merge branch 'main' into copilot-worktree-2026-02-24T23-48-15
pierceboggan Mar 4, 2026
d4ab06f
Commit customization files to main repo for worktree persistence (#29…
joshspicer Mar 4, 2026
dea9178
Merge pull request #297550 from microsoft/copilot-worktree-2026-02-24…
pierceboggan Mar 4, 2026
ea82cfa
sessions: suggest slash commands on new session page (#299098)
joshspicer Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .eslint-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
**/extensions/terminal-suggest/src/shell/fishBuiltinsCache.ts
**/extensions/terminal-suggest/third_party/**
**/extensions/typescript-language-features/test-workspace/**
**/extensions/typescript-language-features/extension.webpack.config.js
**/extensions/typescript-language-features/extension-browser.webpack.config.js
**/extensions/typescript-language-features/package-manager/node-maintainer/**
**/extensions/vscode-api-tests/testWorkspace/**
**/extensions/vscode-api-tests/testWorkspace2/**
Expand Down
12 changes: 0 additions & 12 deletions build/gulpfile.extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,6 @@ async function buildWebExtensions(isWatch: boolean): Promise<void> {
{ ignore: ['**/node_modules'] }
);

// Find all webpack configs, excluding those that will be esbuilt
const esbuildExtensionDirs = new Set(esbuildConfigLocations.map(p => path.dirname(p)));
const webpackConfigLocations = (await nodeUtil.promisify(glob)(
path.join(extensionsPath, '**', 'extension-browser.webpack.config.js'),
{ ignore: ['**/node_modules'] }
)).filter(configPath => !esbuildExtensionDirs.has(path.dirname(configPath)));

const promises: Promise<unknown>[] = [];

// Esbuild for extensions
Expand All @@ -330,10 +323,5 @@ async function buildWebExtensions(isWatch: boolean): Promise<void> {
);
}

// Run webpack for remaining extensions
if (webpackConfigLocations.length > 0) {
promises.push(ext.webpackExtensions('packaging web extension', isWatch, webpackConfigLocations.map(configPath => ({ configPath }))));
}

await Promise.all(promises);
}
208 changes: 4 additions & 204 deletions build/lib/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import fancyLog from 'fancy-log';
import ansiColors from 'ansi-colors';
import buffer from 'gulp-buffer';
import * as jsoncParser from 'jsonc-parser';
import webpack from 'webpack';
import { getProductionDependencies } from './dependencies.ts';
import { type IExtensionDefinition, getExtensionStream } from './builtInExtensions.ts';
import { getVersion } from './getVersion.ts';
import { fetchUrls, fetchGithub } from './fetch.ts';
import { createTsgoStream, spawnTsgo } from './tsgo.ts';
import vzip from 'gulp-vinyl-zip';
Expand All @@ -32,8 +30,8 @@ import { createRequire } from 'module';
const require = createRequire(import.meta.url);

const root = path.dirname(path.dirname(import.meta.dirname));
const commit = getVersion(root);
const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`;
// const commit = getVersion(root);
// const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`;

function minifyExtensionResources(input: Stream): Stream {
const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true });
Expand Down Expand Up @@ -65,32 +63,24 @@ function updateExtensionPackageJSON(input: Stream, update: (data: any) => any):
.pipe(packageJsonFilter.restore);
}

function fromLocal(extensionPath: string, forWeb: boolean, disableMangle: boolean): Stream {
function fromLocal(extensionPath: string, forWeb: boolean, _disableMangle: boolean): Stream {

const esbuildConfigFileName = forWeb
? 'esbuild.browser.mts'
: 'esbuild.mts';

const webpackConfigFileName = forWeb
? `extension-browser.webpack.config.js`
: `extension.webpack.config.js`;

const hasEsbuild = fs.existsSync(path.join(extensionPath, esbuildConfigFileName));
const hasWebpack = fs.existsSync(path.join(extensionPath, webpackConfigFileName));

let input: Stream;
let isBundled = false;

if (hasEsbuild) {
// Unlike webpack, esbuild only does bundling so we still want to run a separate type check step
// Esbuild only does bundling so we still want to run a separate type check step
input = es.merge(
fromLocalEsbuild(extensionPath, esbuildConfigFileName),
...getBuildRootsForExtension(extensionPath).map(root => typeCheckExtensionStream(root, forWeb)),
);
isBundled = true;
} else if (hasWebpack) {
input = fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle);
isBundled = true;
} else {
input = fromLocalNormal(extensionPath);
}
Expand Down Expand Up @@ -122,132 +112,6 @@ export function typeCheckExtensionStream(extensionPath: string, forWeb: boolean)
return createTsgoStream(tsconfigPath, { taskName: 'typechecking extension (tsgo)', noEmit: true });
}

function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, disableMangle: boolean): Stream {
const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce');
const webpack = require('webpack');
const webpackGulp = require('webpack-stream');
const result = es.through();

const packagedDependencies: string[] = [];
const stripOutSourceMaps: string[] = [];
const packageJsonConfig = require(path.join(extensionPath, 'package.json'));
if (packageJsonConfig.dependencies) {
const webpackConfig = require(path.join(extensionPath, webpackConfigFileName));
const webpackRootConfig = webpackConfig.default;
for (const key in webpackRootConfig.externals) {
if (key in packageJsonConfig.dependencies) {
packagedDependencies.push(key);
}
}

if (webpackConfig.StripOutSourceMaps) {
for (const filePath of webpackConfig.StripOutSourceMaps) {
stripOutSourceMaps.push(filePath);
}
}
}

// TODO: add prune support based on packagedDependencies to vsce.PackageManager.Npm similar
// to vsce.PackageManager.Yarn.
// A static analysis showed there are no webpack externals that are dependencies of the current
// local extensions so we can use the vsce.PackageManager.None config to ignore dependencies list
// as a temporary workaround.
vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.None, packagedDependencies }).then(fileNames => {
const files = fileNames
.map(fileName => path.join(extensionPath, fileName))
.map(filePath => new File({
path: filePath,
stat: fs.statSync(filePath),
base: extensionPath,
contents: fs.createReadStream(filePath)
}));

// check for a webpack configuration files, then invoke webpack
// and merge its output with the files stream.
const webpackConfigLocations = (glob.sync(
path.join(extensionPath, '**', webpackConfigFileName),
{ ignore: ['**/node_modules'] }
) as string[]);
const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => {

const webpackDone = (err: Error | undefined, stats: any) => {
fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`);
if (err) {
result.emit('error', err);
}
const { compilation } = stats;
if (compilation.errors.length > 0) {
result.emit('error', compilation.errors.join('\n'));
}
if (compilation.warnings.length > 0) {
result.emit('error', compilation.warnings.join('\n'));
}
};

const exportedConfig = require(webpackConfigPath).default;
return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => {
const webpackConfig = {
...config,
...{ mode: 'production' }
};
if (disableMangle) {
if (Array.isArray(config.module.rules)) {
for (const rule of config.module.rules) {
if (Array.isArray(rule.use)) {
for (const use of rule.use) {
if (String(use.loader).endsWith('mangle-loader.js')) {
use.options.disabled = true;
}
}
}
}
}
}
const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path);

return webpackGulp(webpackConfig, webpack, webpackDone)
.pipe(es.through(function (data) {
data.stat = data.stat || {};
data.base = extensionPath;
this.emit('data', data);
}))
.pipe(es.through(function (data: File) {
// source map handling:
// * rewrite sourceMappingURL
// * save to disk so that upload-task picks this up
if (path.extname(data.basename) === '.js') {
if (stripOutSourceMaps.indexOf(data.relative) >= 0) { // remove source map
const contents = (data.contents as Buffer).toString('utf8');
data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, ''), 'utf8');
} else {
const contents = (data.contents as Buffer).toString('utf8');
data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) {
return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`;
}), 'utf8');
}
}

this.emit('data', data);
}));
});
});

es.merge(...webpackStreams, es.readArray(files))
// .pipe(es.through(function (data) {
// // debug
// console.log('out', data.path, data.contents.length);
// this.emit('data', data);
// }))
.pipe(result);

}).catch(err => {
console.error(extensionPath);
console.error(packagedDependencies);
result.emit('error', err);
});

return result.pipe(createStatsStream(path.basename(extensionPath)));
}

function fromLocalNormal(extensionPath: string): Stream {
const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce');
Expand Down Expand Up @@ -649,70 +513,6 @@ export function translatePackageJSON(packageJSON: string, packageNLSPath: string

const extensionsPath = path.join(root, 'extensions');

export async function webpackExtensions(taskName: string, isWatch: boolean, webpackConfigLocations: { configPath: string; outputRoot?: string }[]) {
const webpack = require('webpack') as typeof import('webpack');

const webpackConfigs: webpack.Configuration[] = [];

for (const { configPath, outputRoot } of webpackConfigLocations) {
const configOrFnOrArray = require(configPath).default;
function addConfig(configOrFnOrArray: webpack.Configuration | ((env: unknown, args: unknown) => webpack.Configuration) | webpack.Configuration[]) {
for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) {
const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn;
if (outputRoot) {
config.output!.path = path.join(outputRoot, path.relative(path.dirname(configPath), config.output!.path!));
}
webpackConfigs.push(config);
}
}
addConfig(configOrFnOrArray);
}

function reporter(fullStats: any) {
if (Array.isArray(fullStats.children)) {
for (const stats of fullStats.children) {
const outputPath = stats.outputPath;
if (outputPath) {
const relativePath = path.relative(extensionsPath, outputPath).replace(/\\/g, '/');
const match = relativePath.match(/[^\/]+(\/server|\/client)?/);
fancyLog(`Finished ${ansiColors.green(taskName)} ${ansiColors.cyan(match![0])} with ${stats.errors.length} errors.`);
}
if (Array.isArray(stats.errors)) {
stats.errors.forEach((error: any) => {
fancyLog.error(error);
});
}
if (Array.isArray(stats.warnings)) {
stats.warnings.forEach((warning: any) => {
fancyLog.warn(warning);
});
}
}
}
}
return new Promise<void>((resolve, reject) => {
if (isWatch) {
webpack(webpackConfigs).watch({}, (err, stats) => {
if (err) {
reject();
} else {
reporter(stats?.toJson());
}
});
} else {
webpack(webpackConfigs).run((err, stats) => {
if (err) {
fancyLog.error(err);
reject();
} else {
reporter(stats?.toJson());
resolve();
}
});
}
});
}

export async function esbuildExtensions(taskName: string, isWatch: boolean, scripts: { script: string; outputRoot?: string }[]): Promise<void> {
function reporter(stdError: string, script: string) {
const matches = (stdError || '').match(/\> (.+): error: (.+)?/g);
Expand Down
3 changes: 3 additions & 0 deletions extensions/extension-editing/esbuild.browser.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ run({
},
srcDir,
outdir: outDir,
additionalOptions: {
tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'),
},
}, process.argv);
25 changes: 20 additions & 5 deletions extensions/git/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,22 @@ export class CommandCenter {
await repo.pull();
}

@command('_git.revParseAbbrevRef')
async revParseAbbrevRef(repositoryPath: string): Promise<string> {
const dotGit = await this.git.getRepositoryDotGit(repositoryPath);
const repo = new GitRepository(this.git, repositoryPath, undefined, dotGit, this.logger);
const result = await repo.exec(['rev-parse', '--abbrev-ref', 'HEAD']);
return result.stdout.trim();
}

@command('_git.mergeBranch')
async mergeBranch(repositoryPath: string, branch: string): Promise<string> {
const dotGit = await this.git.getRepositoryDotGit(repositoryPath);
const repo = new GitRepository(this.git, repositoryPath, undefined, dotGit, this.logger);
const result = await repo.exec(['merge', branch, '--no-edit']);
return result.stdout.trim();
}

@command('git.init')
async init(skipFolderPrompt = false): Promise<void> {
let repositoryPath: string | undefined = undefined;
Expand Down Expand Up @@ -5595,15 +5611,14 @@ export class CommandCenter {
options.modal = false;
break;
default: {
const hint = (err.stderr || err.message || String(err))
const hintLines = (err.stderr || err.stdout || err.message || String(err))
.replace(/^error: /mi, '')
.replace(/^> husky.*$/mi, '')
.split(/[\r\n]/)
.filter((line: string) => !!line)
[0];
.filter((line: string) => !!line);

message = hint
? l10n.t('Git: {0}', hint)
message = hintLines.length > 0
? l10n.t('Git: {0}', err.stdout ? hintLines[hintLines.length - 1] : hintLines[0])
: l10n.t('Git error');

break;
Expand Down
2 changes: 1 addition & 1 deletion extensions/github/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
"when": "github.hasGitHubRepo && timelineItem =~ /git:file:commit\\b/"
}
],
"chat/input/editing/sessionToolbar": [
"chat/input/editing/sessionApplyActions": [
{
"command": "github.createPullRequest",
"group": "navigation",
Expand Down
Loading
Loading