Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
00e3c89
sessions: connect to agent hosts through browser tunnels
connor4312 Aug 19, 2026
f1130cc
Merge remote-tracking branch 'origin/main' into connor4312/browser-de…
connor4312 Aug 19, 2026
41435f6
sessions: initialize tunnel storage after services
connor4312 Aug 19, 2026
20fdc25
sessions: harden browser tunnel lifecycle
connor4312 Aug 19, 2026
fff2914
chat: back terminal chat with the Agent Host (#331591)
connor4312 Aug 19, 2026
cb0b17e
agentHost: enable built-in GitHub MCP servers (#331561)
connor4312 Aug 19, 2026
57ca46f
Render evidence chapters from the automation harness (#331574)
bryanchen-d Aug 19, 2026
e145e08
Fix file URL to path conversion in html-language-features esbuild scr…
jadefr Aug 19, 2026
ed09c51
Agent Host changes for agents/log-analysis-error-fix-prioritization-3…
vritant24 Aug 19, 2026
e0e045b
Polish external session banner picker (#331625)
Copilot Aug 19, 2026
589012a
byok: omit store for custom Responses endpoints
vritant24 Aug 19, 2026
dbcdbc9
Coalesce pane composite bar layout updates (#331670)
dmitrivMS Aug 19, 2026
bb208d7
agentHost: keep Agent Merge monitoring sessions without a client (#33…
benibenj Aug 19, 2026
b592199
Merge pull request #331680 from microsoft/agents/log-analysis-error-f…
vritant24 Aug 19, 2026
0e18182
byok: test explicit custom endpoint retention
vritant24 Aug 19, 2026
3c803dc
agentHost: Harden session catalog migration (#331679)
sandy081 Aug 19, 2026
928efb2
sessions: enable single-pane layout by default (#331681)
sandy081 Aug 19, 2026
0927290
Agents - create session from pull request should not leverage the `IG…
lszomoru Aug 19, 2026
e7a7392
Remove Agents window startup A/A experiment trigger (#331559)
rfeltis Aug 19, 2026
16cd612
cli: recover stale tunnel port protocols (#331351)
connor4312 Aug 19, 2026
1cd9dbd
Merge pull request #331683 from microsoft/agents/byok-responses-api-s…
vritant24 Aug 19, 2026
17c1bfa
add mcp server key as description in tool picker (#325003)
dzsquared Aug 19, 2026
96ace44
Merge pull request #331602 from microsoft/connor4312/browser-dev-tunn…
connor4312 Aug 19, 2026
8041c5c
nes: feat: add eagerness option for diffpatch prompt (#327544)
bstee615 Aug 19, 2026
8729180
Apply multi-diff options before attaching models (#331692)
roblourens Aug 19, 2026
a5ff91e
agents window: fix sessions list progress (#331394)
justschen Aug 19, 2026
8cbcdac
agentHost: log shadowed Claude customizations in multi-root workspace…
DonJayamanne Aug 19, 2026
bcb1c85
chat: Prevent duplicate remote Agent Host queue sends (#331698)
roblourens Aug 19, 2026
7e0ab9d
Revert chat quota trajectory nudge (#331401)
rfeltis Aug 19, 2026
f30670f
Add Sandbox checkbox for Cloud Sessions (#330576)
osortega Aug 19, 2026
6cb98a0
agentHost: fix invalid GitHub GraphQL fields breaking Agent Merge (#3…
benibenj Aug 19, 2026
a61659b
agentHost: Resolve state files through host (#331697)
roblourens Aug 19, 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
7 changes: 7 additions & 0 deletions .eslint-allowed-javascript-files
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ build/builtin/browser-main.js
build/builtin/main.js
build/npm/stubs/sharp/index.js
build/codex/generate-protocol.mjs
build/next/devTunnelsWebEntry.js
build/next/devTunnelsShims/buffer.js
build/next/devTunnelsShims/bufferutil.cjs
build/next/devTunnelsShims/empty.cjs
build/next/devTunnelsShims/process.js
build/next/devTunnelsShims/utf8Validate.cjs
build/next/devTunnelsShims/vscodeJsonrpc.cjs
eslint.config.js
extensions/copilot/.mocha-multi-reporters.js
extensions/copilot/.mocharc.js
Expand Down
35 changes: 32 additions & 3 deletions .github/skills/ui-scenario-validation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,27 @@ npm --prefix test/mcp run compile
```

The automation MCP server is `test/mcp` (`out/stdio.js`). Add it to your MCP configuration so the
`vscode_automation_*` tools are available; append `--web --headless` to the args to record the web
build instead of Electron.
`vscode_automation_*` tools are available.

| Target | Args | Use for |
|--------|------|---------|
| Dev build from this checkout | *(none)* | Verifying a local change |
| Installed Insiders | `--build <app-root>` | Reproducing a report against shipped behavior |
| Web | `--web --headless` | Browser-only behavior |

`--build` takes the application root — the install directory on Windows and Linux, or the `.app`
bundle on macOS. For example:

```bash
# Windows
--build "C:/Users/<you>/AppData/Local/Programs/Microsoft VS Code Insiders"
# macOS
--build "/Applications/Visual Studio Code - Insiders.app"
```

An installed build runs with an isolated profile, so your own extensions and settings do not leak
into the recording. Note that Insiders only reproduces **shipped** behavior — to validate an
unmerged change you must run the dev build from a checkout that contains it.

## Record a clean capture

Expand All @@ -33,6 +52,16 @@ DOM of the product under test, so it can shift layout and affect focus and selec
capture enabled the recording shows unmodified UI, and step boundaries are still recorded in
`manifest.json` with timestamps and screenshots.

Add the step titles back afterwards, once the recording is finished:

```bash
node test/mcp/out/renderEvidenceChapters.js .build/vscode-playwright-mcp/evidence/<run-id>
```

This writes `videos/annotated.mp4` with a full-screen card before each step, inserted between
segments so no recorded frame is hidden. It needs `ffmpeg` and `ffprobe` on `PATH`; without them it
prints a warning and leaves the raw recording untouched.

## Run a scenario

1. Choose a **disposable** workspace folder. Never point a scenario at real work: the run types,
Expand Down Expand Up @@ -71,7 +100,7 @@ Evidence is written to `.build/vscode-playwright-mcp/evidence/<run-id>/`:
|------|----------|
| `report.html` | Step table, outcome, embedded video |
| `manifest.json` | Step timestamps, statuses, artifact paths, environment |
| `videos/` | Screen recording of the run |
| `videos/` | Screen recording, plus `annotated.mp4` once chapters are rendered |
| `*.png` | Per-step screenshots |
| `logs/` | Playwright trace, window and server logs |

Expand Down
6 changes: 6 additions & 0 deletions build/next/devTunnelsShims/buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

export { Buffer } from 'buffer';
16 changes: 16 additions & 0 deletions build/next/devTunnelsShims/bufferutil.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

exports.mask = function mask(source, mask, output, offset, length) {
for (let i = 0; i < length; i++) {
output[offset + i] = source[i] ^ mask[i & 3];
}
};

exports.unmask = function unmask(buffer, mask) {
for (let i = 0; i < buffer.length; i++) {
buffer[i] ^= mask[i & 3];
}
};
6 changes: 6 additions & 0 deletions build/next/devTunnelsShims/empty.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

module.exports = {};
14 changes: 14 additions & 0 deletions build/next/devTunnelsShims/process.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

/**
* Minimal browser process shim for the Dev Tunnels dependency graph.
*/
export const process = {
env: Object.create(null),
nextTick(callback, ...args) {
queueMicrotask(() => callback(...args));
},
};
15 changes: 15 additions & 0 deletions build/next/devTunnelsShims/utf8Validate.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

const utf8Decoder = new TextDecoder('utf-8', { fatal: true });

module.exports = function isValidUTF8(buffer) {
try {
utf8Decoder.decode(buffer);
return true;
} catch {
return false;
}
};
13 changes: 13 additions & 0 deletions build/next/devTunnelsShims/vscodeJsonrpc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

const events = require('vscode-jsonrpc/lib/events');
const cancellation = require('vscode-jsonrpc/lib/cancellation');

exports.Disposable = events.Disposable;
exports.Event = events.Event;
exports.Emitter = events.Emitter;
exports.CancellationToken = cancellation.CancellationToken;
exports.CancellationTokenSource = cancellation.CancellationTokenSource;
199 changes: 199 additions & 0 deletions build/next/devTunnelsWeb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as esbuild from 'esbuild';
import * as fs from 'fs';
import { createRequire } from 'module';
import * as path from 'path';

const REPO_ROOT = path.dirname(path.dirname(import.meta.dirname));
const ENTRY_POINT = path.join(REPO_ROOT, 'build', 'next', 'devTunnelsWebEntry.js');
const SHIMS_ROOT = path.join(REPO_ROOT, 'build', 'next', 'devTunnelsShims');
const NODE_MODULES_ROOT = path.join(REPO_ROOT, 'node_modules');
const BUFFER_GLOBAL_SHIM = path.join(SHIMS_ROOT, 'buffer.js');
const VSCODE_JSONRPC_SHIM = path.join(SHIMS_ROOT, 'vscodeJsonrpc.cjs');
const SSH_ALGORITHMS_ROOT = path.join(REPO_ROOT, 'node_modules', '@microsoft', 'dev-tunnels-ssh', 'algorithms');
const WEB_MODULE_OUT_DIR = path.join('vs', 'sessions', 'contrib', 'providers', 'remoteAgentHost', 'browser');
const WEB_DEV_OUT_DIR = path.join('out', WEB_MODULE_OUT_DIR);
const nodeRequire = createRequire(import.meta.url);
const allowedImporterRoots = [
path.join(NODE_MODULES_ROOT, '@microsoft', 'dev-tunnels-ssh'),
path.join(NODE_MODULES_ROOT, '@microsoft', 'dev-tunnels-ssh-tcp'),
path.join(NODE_MODULES_ROOT, '@microsoft', 'dev-tunnels-connections'),
path.join(NODE_MODULES_ROOT, '@microsoft', 'dev-tunnels-management'),
path.join(NODE_MODULES_ROOT, '@microsoft', 'dev-tunnels-contracts'),
path.join(NODE_MODULES_ROOT, 'websocket'),
];
const nodeBuiltinNames = ['net', 'os', 'path', 'crypto', 'child_process', 'fs', 'http', 'https', 'tls', 'dns', 'zlib'];
const nodeBuiltinFilter = new RegExp(`^(?:node:)?(?:${nodeBuiltinNames.join('|')}|stream|buffer)$`);
const vscodeJsonrpcEventsPath = resolveDevTunnelsJsonRpcModule('events');
const vscodeJsonrpcCancellationPath = resolveDevTunnelsJsonRpcModule('cancellation');

/**
* Builds the browser-only Dev Tunnels SDK bundle loaded through a dynamic ESM import.
*/
export async function bundleDevTunnelsWeb(options: { minify?: boolean; outDir: string }): Promise<void> {
const outDir = path.resolve(REPO_ROOT, options.outDir);
const t1 = Date.now();
await fs.promises.mkdir(outDir, { recursive: true });

console.log(`[dev-tunnels-web] ${path.relative(REPO_ROOT, ENTRY_POINT)} → ${path.relative(REPO_ROOT, outDir) || '.'}${options.minify ? ' (minify)' : ''}`);
await esbuild.build({
entryPoints: [ENTRY_POINT],
bundle: true,
format: 'esm',
platform: 'browser',
target: ['es2024'],
define: {
// Some browser entry points in the SDK's dependency graph still use
// the Browserify-era `global` alias (for example randombytes).
global: 'globalThis',
},
inject: [
BUFFER_GLOBAL_SHIM,
path.join(SHIMS_ROOT, 'process.js'),
],
minify: options.minify,
sourcemap: 'linked',
outfile: path.join(outDir, 'devTunnelsModule.js'),
plugins: [devTunnelsBrowserShimPlugin()],
logLevel: 'warning',
});
console.log(`[dev-tunnels-web] Done in ${Date.now() - t1}ms`);
}

/**
* Resolves Dev Tunnels' browser dependencies without changing unrelated bundle resolution.
*/
export function devTunnelsBrowserShimPlugin(): esbuild.Plugin {
return {
name: 'dev-tunnels-browser-shims',
setup(build) {
build.onResolve({ filter: nodeBuiltinFilter }, args => {
const specifier = args.path.startsWith('node:') ? args.path.slice('node:'.length) : args.path;
if (specifier === 'stream') {
// Stream changes the SDK transport; other Node modules only receive standard browser polyfills.
if (isAllowedImporter(args.importer)) {
return { path: path.join(NODE_MODULES_ROOT, 'readable-stream', 'readable-browser.js') };
}
if (isNodeModulesImporter(args.importer)) {
return;
}
return nodeBuiltinError(args);
}

if (!isNodeModulesImporter(args.importer) && args.importer !== BUFFER_GLOBAL_SHIM) {
return nodeBuiltinError(args);
}

return specifier === 'buffer'
? { path: path.join(NODE_MODULES_ROOT, 'buffer', 'index.js') }
: { path: path.join(SHIMS_ROOT, 'empty.cjs') };
});

build.onResolve({ filter: /^node-rsa$/ }, args => {
if (!isAllowedImporter(args.importer)) {
return;
}
return { path: path.join(SHIMS_ROOT, 'empty.cjs') };
});

build.onResolve({ filter: /^\.[\\/]node[\\/]/ }, args => {
if (!isSshNodeAlgorithmImport(args)) {
return;
}
return { path: path.join(SHIMS_ROOT, 'empty.cjs') };
});

build.onResolve({ filter: /^bufferutil$/ }, args => {
if (!isAllowedImporter(args.importer)) {
return;
}
return { path: path.join(SHIMS_ROOT, 'bufferutil.cjs') };
});

build.onResolve({ filter: /^utf-8-validate$/ }, args => {
if (!isAllowedImporter(args.importer)) {
return;
}
return { path: path.join(SHIMS_ROOT, 'utf8Validate.cjs') };
});

build.onResolve({ filter: /^vscode-jsonrpc$/ }, args => {
if (!isAllowedImporter(args.importer)) {
return;
}
return { path: path.join(SHIMS_ROOT, 'vscodeJsonrpc.cjs') };
});

build.onResolve({ filter: /^vscode-jsonrpc\/lib\/(?:events|cancellation)$/ }, args => {
if (args.importer !== VSCODE_JSONRPC_SHIM) {
return;
}
return {
path: args.path.endsWith('/events') ? vscodeJsonrpcEventsPath : vscodeJsonrpcCancellationPath
};
});
}
};
}

function isAllowedImporter(importer: string): boolean {
if (importer === ENTRY_POINT) {
return true;
}

return allowedImporterRoots.some(root => {
return isPathWithin(root, importer);
});
}

function isNodeModulesImporter(importer: string): boolean {
return isPathWithin(NODE_MODULES_ROOT, importer);
}

function isSshNodeAlgorithmImport(args: esbuild.OnResolveArgs): boolean {
return isPathWithin(SSH_ALGORITHMS_ROOT, args.importer);
}

function isPathWithin(root: string, filePath: string): boolean {
const relative = path.relative(root, filePath);
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}

function nodeBuiltinError(args: esbuild.OnResolveArgs): esbuild.OnResolveResult {
return {
errors: [{
text: `Refusing to shim Node builtin '${args.path}' requested outside node_modules by '${args.importer || '<entry point>'}'.`
}]
};
}

function resolveDevTunnelsJsonRpcModule(moduleName: 'events' | 'cancellation'): string {
return nodeRequire.resolve(`vscode-jsonrpc/lib/${moduleName}`, {
paths: [path.join(NODE_MODULES_ROOT, '@microsoft', 'dev-tunnels-ssh')]
});
}

function getArgValue(name: string): string | undefined {
const index = process.argv.indexOf(name);
return index !== -1 && index + 1 < process.argv.length ? process.argv[index + 1] : undefined;
}

async function main(): Promise<void> {
try {
await bundleDevTunnelsWeb({
minify: process.argv.includes('--minify'),
outDir: getArgValue('--out') ?? WEB_DEV_OUT_DIR,
});
} catch (error) {
console.error('Dev Tunnels web bundle failed:', error);
process.exit(1);
}
}

if (import.meta.main) {
main();
}
19 changes: 19 additions & 0 deletions build/next/devTunnelsWebEntry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TunnelManagementHttpClient, ManagementApiVersions } from '@microsoft/dev-tunnels-management';
import { TunnelRelayTunnelClient } from '@microsoft/dev-tunnels-connections';
import { TunnelAccessScopes } from '@microsoft/dev-tunnels-contracts';
// The package root resolves to lib/browser.js, a native-WebSocket wrapper. This deep import provides
// RFC 6455 framing for an existing duplex stream and must not be replaced with the package root.
import WebSocketConnection from 'websocket/lib/WebSocketConnection';

export {
TunnelManagementHttpClient,
ManagementApiVersions,
TunnelRelayTunnelClient,
TunnelAccessScopes,
WebSocketConnection,
};
11 changes: 10 additions & 1 deletion build/next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import packageJson from '../../package.json' with { type: 'json' };
import { useEsbuildTranspile } from '../buildConfig.ts';
import { isWebExtension, type IScannedBuiltinExtension } from '../lib/extensions.ts';
import { runBuildFast } from './build-fast.ts';
import { bundleDevTunnelsWeb } from './devTunnelsWeb.ts';
import { copyFile, mapWithConcurrency, MAX_CONCURRENT_FILE_OPERATIONS, transpileFile } from './transpile.ts';

const globAsync = promisify(glob);
Expand Down Expand Up @@ -121,8 +122,9 @@ const webEntryPoints = [
];

// Additional web-only entry points (CDN build only, not in server-web)
const sessionsWebEntryPoint = 'vs/sessions/sessions.web.main.internal';
const webOnlyEntryPoints = [
'vs/sessions/sessions.web.main.internal',
sessionsWebEntryPoint,
];

const keyboardMapEntryPoints = [
Expand Down Expand Up @@ -1016,6 +1018,13 @@ ${tslib}`,
// Compile standalone TypeScript files (like Electron preload scripts) that cannot be bundled
await compileStandaloneFiles(outDir, doMinify, target);

if (allEntryPoints.includes(sessionsWebEntryPoint)) {
await bundleDevTunnelsWeb({
minify: doMinify,
outDir: path.join(outDir, 'vs', 'sessions', 'contrib', 'providers', 'remoteAgentHost', 'browser'),
});
}

console.log(`[bundle] Done in ${Date.now() - t1}ms (${bundled} bundles)`);
}

Expand Down
Loading
Loading