Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
93b485f
Refactor chat UI styles for improved alignment and consistency
mrleemurray Apr 20, 2026
4c9b954
Add spacing between action items in chat controls and repo-config too…
mrleemurray Apr 20, 2026
184d892
Adjust spacing and padding in chat widget for improved layout
mrleemurray Apr 20, 2026
9c4313d
Enhance chat widget and config toolbar styles for better label ellips…
mrleemurray Apr 20, 2026
bbbde53
Refactor chat widget and input styles for improved alignment and spacing
mrleemurray Apr 20, 2026
2163b5f
Center align chat widget elements for improved layout consistency
mrleemurray Apr 20, 2026
dcfb24c
Update src/vs/sessions/contrib/chat/browser/media/chatWidget.css
mrleemurray Apr 20, 2026
664f0e4
Refactor chat input and widget styles for improved alignment and spacing
mrleemurray Apr 20, 2026
918ed8d
Adjust min-width for action items and prevent mode picker from shrink…
mrleemurray Apr 20, 2026
e8a7c6d
Stop copying node-pty into Copilot CLI SDK (#310925)
anthonykim1 Apr 21, 2026
1a39bed
show animation around chat input while working (#311125)
justschen Apr 21, 2026
2f92697
fix(chat): enable scrollbar for height-capped code blocks in tool con…
maruthang Apr 21, 2026
938f78a
add better padding for thinking + persistant progress (#311603)
justschen Apr 21, 2026
8574f4f
Merge pull request #311357 from microsoft/mrleemurray/new-session-polish
mrleemurray Apr 21, 2026
d844c09
handle tap and click properly (#311604)
rebornix Apr 21, 2026
018a47f
feat(copilotcli): add WorktreeSessionIndex for managing chat session …
DonJayamanne Apr 21, 2026
083af3b
[cherry-pick] Remove ReviewPlanTool from BuiltinToolsContribution (#3…
vs-code-engineering[bot] Apr 21, 2026
1f3b864
Agents - fix resource order in the multi-file diff editor (#311639)
lszomoru Apr 21, 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
9 changes: 4 additions & 5 deletions build/gulpfile.reh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import * as cp from 'child_process';
import log from 'fancy-log';
import buildfile from './buildfile.ts';
import { fetchUrls, fetchGithub } from './lib/fetch.ts';
import { getCopilotExcludeFilter, copyCopilotNativeDeps, prepareBuiltInCopilotExtensionShims } from './lib/copilot.ts';
import { getCopilotExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
import jsonEditor from 'gulp-json-editor';


Expand Down Expand Up @@ -463,14 +463,13 @@ function patchWin32DependenciesTask(destinationFolderName: string) {
};
}

function copyCopilotNativeDepsTaskREH(platform: string, arch: string, destinationFolderName: string) {
function prepareCopilotRipgrepShimTaskREH(platform: string, arch: string, destinationFolderName: string) {
return async () => {
const outputDir = path.join(BUILD_ROOT, destinationFolderName);
const nodeModulesDir = path.join(outputDir, 'node_modules');
copyCopilotNativeDeps(platform, arch, nodeModulesDir);

const builtInCopilotExtensionDir = path.join(outputDir, 'extensions', 'copilot');
prepareBuiltInCopilotExtensionShims(platform, arch, builtInCopilotExtensionDir, nodeModulesDir);
prepareBuiltInCopilotRipgrepShim(platform, arch, builtInCopilotExtensionDir, nodeModulesDir);
};
}

Expand Down Expand Up @@ -523,7 +522,7 @@ function tweakProductForServerWeb(product: typeof import('../product.json')) {
gulp.task(`node-${platform}-${arch}`) as task.Task,
util.rimraf(path.join(BUILD_ROOT, destinationFolderName)),
packageTask(type, platform, arch, sourceFolderName, destinationFolderName),
copyCopilotNativeDepsTaskREH(platform, arch, destinationFolderName)
prepareCopilotRipgrepShimTaskREH(platform, arch, destinationFolderName)
];

if (platform === 'win32') {
Expand Down
9 changes: 4 additions & 5 deletions build/gulpfile.vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import minimist from 'minimist';
import { compileBuildWithoutManglingTask, compileBuildWithManglingTask } from './gulpfile.compile.ts';
import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask, compileCopilotExtensionBuildTask } from './gulpfile.extensions.ts';
import { copyCodiconsTask } from './lib/compilation.ts';
import { getCopilotExcludeFilter, copyCopilotNativeDeps, prepareBuiltInCopilotExtensionShims } from './lib/copilot.ts';
import { getCopilotExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts';
import type { EmbeddedProductInfo } from './lib/embeddedType.ts';
import { useEsbuildTranspile } from './buildConfig.ts';
import { promisify } from 'util';
Expand Down Expand Up @@ -700,7 +700,7 @@ function patchWin32DependenciesTask(destinationFolderName: string) {
};
}

function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFolderName: string) {
function prepareCopilotRipgrepShimTask(platform: string, arch: string, destinationFolderName: string) {
const outputDir = path.join(path.dirname(root), destinationFolderName);

return async () => {
Expand All @@ -711,10 +711,9 @@ function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFo
? path.join(outputDir, `${product.nameLong}.app`, 'Contents', 'Resources', 'app')
: path.join(outputDir, versionedResourcesFolder, 'resources', 'app');
const appNodeModulesDir = path.join(appBase, 'node_modules');
copyCopilotNativeDeps(platform, arch, appNodeModulesDir);

const builtInCopilotExtensionDir = path.join(appBase, 'extensions', 'copilot');
prepareBuiltInCopilotExtensionShims(platform, arch, builtInCopilotExtensionDir, appNodeModulesDir);
prepareBuiltInCopilotRipgrepShim(platform, arch, builtInCopilotExtensionDir, appNodeModulesDir);
};
}

Expand Down Expand Up @@ -743,7 +742,7 @@ BUILD_TARGETS.forEach(buildTarget => {
compileNativeExtensionsBuildTask,
util.rimraf(path.join(buildRoot, destinationFolderName)),
packageTask(platform, arch, sourceFolderName, destinationFolderName, opts),
copyCopilotNativeDepsTask(platform, arch, destinationFolderName)
prepareCopilotRipgrepShimTask(platform, arch, destinationFolderName)
];

if (platform === 'win32') {
Expand Down
86 changes: 17 additions & 69 deletions build/lib/copilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,117 +46,65 @@ function toNodePlatformArch(platform: string, arch: string): { nodePlatform: str
* for architectures other than the build target.
*
* For platforms the copilot SDK doesn't natively support (e.g. alpine, armhf),
* ALL platform packages are stripped - that's fine because the SDK doesn't ship
* binaries for those platforms anyway, and we replace them with VS Code's own.
* ALL platform packages are stripped - that's fine because the copilot CLI SDK
* resolves `node-pty` from the embedder (VS Code) first via `hostRequire`,
* falling back to its bundled copy only if the embedder can't provide it.
*/
export function getCopilotExcludeFilter(platform: string, arch: string): string[] {
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
const targetPlatformArch = `${nodePlatform}-${nodeArch}`;
const nonTargetPlatforms = copilotPlatforms.filter(p => p !== targetPlatformArch);

// Strip wrong-architecture @github/copilot-{platform} packages.
// All copilot prebuilds are stripped by .moduleignore; VS Code's own
// node-pty is copied into the prebuilds location by a post-packaging task.
// All copilot prebuilds are stripped by .moduleignore; the copilot CLI SDK
// resolves `node-pty` from VS Code's own node_modules via `hostRequire`.
const excludes = nonTargetPlatforms.map(p => `!**/node_modules/@github/copilot-${p}/**`);

return ['**', ...excludes];
}

/**
* Copies VS Code's own node-pty binaries into the copilot SDK's
* expected locations so the copilot CLI subprocess can find them at runtime.
* The copilot-bundled prebuilds are stripped by .moduleignore;
* this replaces them with the same binaries VS Code already ships, avoiding
* new system dependency requirements.
*
* This works even for platforms the copilot SDK doesn't natively support
* (e.g. alpine, armhf) because the SDK's native module loader simply
* looks for `prebuilds/{process.platform}-{process.arch}/pty.node` - it
* doesn't validate the platform against a supported list.
*
* Failures are logged but do not throw, to avoid breaking the build on
* platforms where something unexpected happens.
*
* @param nodeModulesDir Absolute path to the node_modules directory that
* contains both the source binaries (node-pty) and the copilot SDK
* target directories.
*/
export function copyCopilotNativeDeps(platform: string, arch: string, nodeModulesDir: string): void {
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
const platformArch = `${nodePlatform}-${nodeArch}`;

const copilotBase = path.join(nodeModulesDir, '@github', 'copilot');
if (!fs.existsSync(copilotBase)) {
console.warn(`[copyCopilotNativeDeps] @github/copilot not found at ${copilotBase}, skipping`);
return;
}

const nodePtySource = path.join(nodeModulesDir, 'node-pty', 'build', 'Release');
if (!fs.existsSync(nodePtySource)) {
console.warn(`[copyCopilotNativeDeps] node-pty source not found at ${nodePtySource}, skipping`);
return;
}

try {
// Copy node-pty (pty.node + spawn-helper on Unix, conpty.node + conpty/ on Windows)
// into copilot prebuilds so the SDK finds them via loadNativeModule.
const copilotPrebuildsDir = path.join(copilotBase, 'prebuilds', platformArch);
fs.mkdirSync(copilotPrebuildsDir, { recursive: true });
fs.cpSync(nodePtySource, copilotPrebuildsDir, { recursive: true });
console.log(`[copyCopilotNativeDeps] Copied node-pty from ${nodePtySource} to ${copilotPrebuildsDir}`);
} catch (err) {
console.warn(`[copyCopilotNativeDeps] Failed to copy node-pty for ${platformArch}: ${err}`);
}
}

/**
* Materializes copilot CLI shims directly inside the built-in copilot extension.
* Materializes the copilot CLI ripgrep shim directly inside the built-in copilot extension.
*
* This is used when copilot is shipped as a built-in extension so startup does
* not need to create shims at runtime. The destination layout matches the
* not need to create the shim at runtime. The destination layout matches the
* runtime shim logic in the copilot extension:
* - node-pty: node_modules/@github/copilot/sdk/prebuilds/{platform-arch}
* - ripgrep: node_modules/@github/copilot/sdk/ripgrep/bin/{platform-arch}
* - marker: node_modules/@github/copilot/shims.txt
*
* Note: `node-pty` is no longer shimmed. The copilot CLI SDK resolves
* `node-pty` from the embedder (VS Code) via `hostRequire` and falls back to
* its bundled copy only if that fails.
*
* Failures throw to fail the build because built-in packaging must guarantee
* these artifacts are present.
* this artifact is present.
*/
export function prepareBuiltInCopilotExtensionShims(platform: string, arch: string, builtInCopilotExtensionDir: string, appNodeModulesDir: string): void {
export function prepareBuiltInCopilotRipgrepShim(platform: string, arch: string, builtInCopilotExtensionDir: string, appNodeModulesDir: string): void {
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
const platformArch = `${nodePlatform}-${nodeArch}`;

const extensionNodeModules = path.join(builtInCopilotExtensionDir, 'node_modules');
const copilotBase = path.join(extensionNodeModules, '@github', 'copilot');
const copilotSdkBase = path.join(copilotBase, 'sdk');
if (!fs.existsSync(copilotSdkBase)) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] Copilot SDK directory not found at ${copilotSdkBase}`);
}

const nodePtySource = path.join(appNodeModulesDir, 'node-pty', 'build', 'Release');
if (!fs.existsSync(nodePtySource)) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] node-pty source not found at ${nodePtySource}`);
throw new Error(`[prepareBuiltInCopilotRipgrepShim] Copilot SDK directory not found at ${copilotSdkBase}`);
}

const ripgrepSource = path.join(appNodeModulesDir, '@vscode', 'ripgrep', 'bin');
if (!fs.existsSync(ripgrepSource)) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] ripgrep source not found at ${ripgrepSource}`);
throw new Error(`[prepareBuiltInCopilotRipgrepShim] ripgrep source not found at ${ripgrepSource}`);
}

const nodePtyDest = path.join(copilotSdkBase, 'prebuilds', platformArch);
const ripgrepDest = path.join(copilotSdkBase, 'ripgrep', 'bin', platformArch);
const shimMarkerPath = path.join(copilotBase, 'shims.txt');

try {
fs.mkdirSync(nodePtyDest, { recursive: true });
fs.cpSync(nodePtySource, nodePtyDest, { recursive: true });

fs.mkdirSync(ripgrepDest, { recursive: true });
fs.cpSync(ripgrepSource, ripgrepDest, { recursive: true });

fs.writeFileSync(shimMarkerPath, 'Shims created successfully');
console.log(`[prepareBuiltInCopilotExtensionShims] Materialized shims for ${platformArch} in ${builtInCopilotExtensionDir}`);
console.log(`[prepareBuiltInCopilotRipgrepShim] Materialized ripgrep shim for ${platformArch} in ${builtInCopilotExtensionDir}`);
} catch (err) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] Failed to materialize shims for ${platformArch}: ${err}`);
throw new Error(`[prepareBuiltInCopilotRipgrepShim] Failed to materialize ripgrep shim for ${platformArch}: ${err}`);
}
}
5 changes: 5 additions & 0 deletions build/lib/stylelint/vscode-known-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
"--vscode-chat-avatarForeground",
"--vscode-chat-checkpointSeparator",
"--vscode-chat-editedFileForeground",
"--vscode-chat-inputWorkingBorderColor1",
"--vscode-chat-inputWorkingBorderColor2",
"--vscode-chat-inputWorkingBorderColor3",
"--vscode-chat-linesAddedForeground",
"--vscode-chat-linesRemovedForeground",
"--vscode-chat-requestBackground",
Expand Down Expand Up @@ -1037,6 +1040,8 @@
"--monaco-editor-warning-decoration",
"--animation-angle",
"--animation-opacity",
"--chat-input-anim-angle",
"--chat-input-working-fill",
"--chat-setup-dialog-glow-angle",
"--vscode-chat-font-family",
"--vscode-chat-font-size-body-l",
Expand Down
1 change: 0 additions & 1 deletion extensions/copilot/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ export default tseslint.config(
ignores: [
'src/util/vs/**/*.ts', // vendored code
'src/**/*.spec.ts', // allow in tests
'./src/extension/agents/copilotcli/node/nodePtyShim.ts',
'./src/extension/byok/common/anthropicMessageConverter.ts',
'./src/extension/byok/common/geminiFunctionDeclarationConverter.ts',
'./src/extension/byok/common/geminiMessageConverter.ts',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ export interface ChatSessionMetadataFile {
* session or if the session is a child session created from the Agents app.
*/
parentSessionId?: string;
/** Milliseconds since epoch when this metadata was first written. */
created?: number;
/** Milliseconds since epoch of the last write. Used for top-N trim sort and cross-process merge. */
modified?: number;
}

/**
* One line in `~/.copilot/vscode.session.worktree.jsonl`. Maps a session id
* to the path of its worktree so folder → session lookups work even when the
* session has been evicted from the bulk metadata cache.
*/
export interface WorktreeSessionEntry {
readonly id: string;
readonly path: string;
readonly created: number;
}

export const IChatSessionMetadataStore = createServiceIdentifier<IChatSessionMetadataStore>('IChatSessionMetadataStore');
Expand Down Expand Up @@ -126,4 +141,10 @@ export interface IChatSessionMetadataStore {
getSessionOrigin(sessionId: string): Promise<'vscode' | 'other'>;
setSessionParentId(sessionId: string, parentSessionId: string): Promise<void>;
getSessionParentId(sessionId: string): Promise<string | undefined>;
/**
* Re-read the shared bulk metadata file from disk and merge into the in-memory cache.
* Wired to the chat-sessions UI refresh action so cross-process writes become visible
* on demand. Concurrent calls collapse: at most one in-flight + one pending.
*/
refresh(): Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class MockChatSessionMetadataStore implements IChatSessionMetadataStore {
this._requestDetails.delete(sessionId);
}

async refresh(): Promise<void> {
// no-op in mock — there is no on-disk state to reload.
}

async storeWorktreeInfo(sessionId: string, properties: ChatSessionWorktreeProperties): Promise<void> {
this._worktreeProperties.set(sessionId, properties);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ copilotcli/
│ ├── copilotCLISkills.ts # Skills location resolution
│ ├── copilotCLIImageSupport.ts # Image attachment handling
│ ├── mcpHandler.ts # MCP server configuration for SDK sessions
│ ├── nodePtyShim.ts # Copies VS Code's node-pty for SDK use
│ ├── userInputHelpers.ts # User question/input handling interface
│ ├── exitPlanModeHandler.ts # Plan mode exit flow with user choice
│ ├── ripgrepShim.ts # Copies VS Code's ripgrep for SDK use
Expand Down Expand Up @@ -313,7 +312,7 @@ Orchestrates the start and end of each chat request turn, coordinating worktree

## Critical Pitfalls

- **Shims before SDK import**: `ensureNodePtyShim()` and `ensureRipgrepShim()` in `node/nodePtyShim.ts` / `node/ripgrepShim.ts` MUST be called before any `import('@github/copilot/sdk')`. They copy VS Code's bundled native binaries to the SDK's expected locations. See `node/copilotCli.ts` for the initialization order.
- **Shims before SDK import**: `ensureRipgrepShim()` in `node/ripgrepShim.ts` MUST be called before any `import('@github/copilot/sdk')`. It copies VS Code's bundled ripgrep binary to the SDK's expected location. `node-pty` is no longer shimmed: the copilot CLI SDK resolves it from VS Code's own `node_modules` via `hostRequire`, falling back to its bundled copy only if that fails. See `node/copilotCli.ts` for the initialization order.

- **Delayed permission UI**: Tool invocation messages are held in `toolCallWaitingForPermissions` until permission resolves. `flushPendingInvocationMessageForToolCallId()` flushes only the specific approved tool, not all pending tools. This is intentional — don't bypass it.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,21 @@ export function getCopilotCLISessionEventsFile(sessionId: string) {
export function getCopilotCLIWorkspaceFile(sessionId: string) {
return join(getCopilotCLISessionDir(sessionId), 'workspace.yaml');
}

/**
* Path of the shared bulk metadata cache file. This file is shared by all VS Code
* installs (Stable, Insiders, OSS, Exploration) and the Agents application.
*/
export function getCopilotBulkMetadataFile(): string {
return join(getCopilotHome(), 'vscode.session.metadata.cache.json');
}

/**
* Path of the shared worktree-sessions JSONL index. Append-only, one
* {@link WorktreeSessionEntry} per line.
* Used as a worktree folder → session-id fallback
* when an entry has been evicted from the bulk cache.
*/
export function getCopilotWorktreeSessionsFile(): string {
return join(getCopilotHome(), 'vscode.session.worktree.jsonl');
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { basename } from '../../../../util/vs/base/common/resources';
import { URI } from '../../../../util/vs/base/common/uri';
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
import { getCopilotLogger } from './logger';
import { ensureNodePtyShim } from './nodePtyShim';
import { ensureRipgrepShim } from './ripgrepShim';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';

Expand Down Expand Up @@ -460,7 +459,7 @@ export class CopilotCLISDK implements ICopilotCLISDK {

public async getPackage(): Promise<typeof import('@github/copilot/sdk')> {
try {
// Ensure the node-pty shim exists before importing the SDK (required for CLI sessions)
// Ensure the ripgrep shim exists before importing the SDK (required for CLI sessions)
await this._ensureShimsPromise;
return await import('@github/copilot/sdk');
} catch (error) {
Expand Down Expand Up @@ -497,10 +496,7 @@ export class CopilotCLISDK implements ICopilotCLISDK {
if (await checkFileExists(successfulPlaceholder)) {
return;
}
await Promise.all([
ensureNodePtyShim(this.extensionContext.extensionPath, this.envService.appRoot, this.logService),
ensureRipgrepShim(this.extensionContext.extensionPath, this.envService.appRoot, this.logService)
]);
await ensureRipgrepShim(this.extensionContext.extensionPath, this.envService.appRoot, this.logService);
await fs.writeFile(successfulPlaceholder, 'Shims created successfully');
}

Expand Down
Loading
Loading