From 6ca01668a6cf469a71493364e1de54c15c7f35ea Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 8 Sep 2026 16:48:50 +0200 Subject: [PATCH 1/4] sessions: add explicit worktree option to create_session Honor user-requested worktree overrides while preserving isolation inheritance when omitted. Reject overrides for current-session chats and resolve known worktree folders to their project root when worktree is false. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vs/platform/agentHost/AGENTS.md | 15 +- .../node/shared/sessionServerTools.ts | 27 ++- .../test/node/sessionServerTools.test.ts | 160 +++++++++++++++++- 3 files changed, 188 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/agentHost/AGENTS.md b/src/vs/platform/agentHost/AGENTS.md index b7c44861ec506..d9673ba232099 100644 --- a/src/vs/platform/agentHost/AGENTS.md +++ b/src/vs/platform/agentHost/AGENTS.md @@ -244,7 +244,8 @@ Treat a session as the user-visible unit of work. `create_session` requires a relationship: `currentSession` creates a peer chat for tasks in the current plan or deliverable, sharing its workspace, lifecycle, and aggregate diff; `independent` creates a top-level session for a separate deliverable that needs -its own workspace, provider, or lifecycle. A title is required for both +its own workspace, provider, or lifecycle, or for an explicitly requested +worktree. A title is required for both relationships and is applied before the initial prompt starts. Sessions created by the `create_session` server tool record only the creating @@ -261,9 +262,15 @@ configured project root over a transient worktree. Ambiguous names require an explicit project URI. An independent session inherits the creating session's host-owned isolation -selection independently of provider-owned configuration. The target workspace -still constrains the effective selection, so a folder that cannot support Git -worktrees resolves to folder isolation. +selection independently of provider-owned configuration; +otherwise it uses worktree isolation. The optional `worktree` argument overrides +that selection. Agents must set it only when the user explicitly asks to create a +worktree (`true`) or work without one (`false`); otherwise they must omit it. +With `false`, a supplied worktree folder resolves to its known project root +before session creation; folders without a known project are used directly. +The option is invalid with `currentSession`, whose chats share the existing +workspace. The target workspace still constrains the effective selection, so a +folder that cannot support Git worktrees resolves to folder isolation. --- diff --git a/src/vs/platform/agentHost/node/shared/sessionServerTools.ts b/src/vs/platform/agentHost/node/shared/sessionServerTools.ts index dad63d53cdf6d..922078123f4ec 100644 --- a/src/vs/platform/agentHost/node/shared/sessionServerTools.ts +++ b/src/vs/platform/agentHost/node/shared/sessionServerTools.ts @@ -73,10 +73,11 @@ const createSessionInputSchema: ToolDefinition['inputSchema'] = { relationship: { type: 'string', enum: [...createSessionRelationshipValues], - description: 'Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle.', + description: 'Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree.', }, prompt: { type: 'string', description: 'Initial prompt to send to the new session.' }, workspace: { type: 'string', description: 'For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`.' }, + worktree: { type: 'boolean', description: 'Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session\'s isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`.' }, title: { type: 'string', maxLength: 200, description: 'Short title for the new chat or independent session.' }, model: { type: 'string', description: 'Optional model ID or display name. Defaults to the current chat\'s model. For `currentSession`, the model must belong to the current session\'s provider; for `independent`, the model selects the new session\'s provider.' }, }, @@ -173,7 +174,7 @@ export const sessionServerToolDefinitions: IAgentServerToolDefinition[] = [ { name: SessionServerToolName.CreateSession, title: 'Create Session', - description: 'Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session\'s workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.', + description: 'Create delegated work and start it with an initial prompt, either in a new chat sharing the current session\'s workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.', inputSchema: createSessionInputSchema, annotations: { readOnlyHint: false }, }, @@ -216,6 +217,7 @@ export function currentSessionUri(toolCallChannel: ProtocolURI): URI { interface ICreateSessionArgs { readonly relationship?: unknown; readonly workspace?: unknown; + readonly worktree?: unknown; readonly prompt?: unknown; readonly title?: unknown; readonly model?: unknown; @@ -229,6 +231,7 @@ export type IResolvedCreateSessionArgs = { } | { readonly relationship: 'independent'; readonly workspace: URI; + readonly worktree?: boolean; readonly prompt: string; readonly title: string; readonly model?: IAgentModelInfo; @@ -429,12 +432,12 @@ export function getSetWorkspaceArgs(rawArgs: unknown): { readonly workspaceFolde }; } -function resolveWorkspace(workspace: string, sessions: readonly IAgentSessionMetadata[]): URI { +function resolveWorkspace(workspace: string, sessions: readonly IAgentSessionMetadata[], preferProject = false): URI { const parsed = parseWorkspaceUri(workspace); for (const session of sessions) { for (const candidate of [session.project?.uri, ...(session.workingDirectories ?? [])]) { if (candidate && parsed && isEqual(candidate, parsed)) { - return candidate; + return preferProject ? session.project?.uri ?? candidate : candidate; } } } @@ -515,11 +518,15 @@ export function getCreateSessionArgs(rawArgs: unknown, sessions: readonly IAgent validateRenameTitle(title, SessionServerToolName.CreateSession); const workspace = getOptionalString(args.workspace, 'workspace', SessionServerToolName.CreateSession); const modelName = getOptionalString(args.model, 'model', SessionServerToolName.CreateSession); + const worktree = getOptionalBoolean(args.worktree, 'worktree', SessionServerToolName.CreateSession); const model = resolveModel(modelName, models, relationship === 'currentSession' ? currentProvider : undefined); if (relationship === 'currentSession') { if (workspace !== undefined) { throw new Error(`Invalid ${SessionServerToolName.CreateSession} input: workspace is only valid when relationship is "independent".`); } + if (worktree !== undefined) { + throw new Error(`Invalid ${SessionServerToolName.CreateSession} input: worktree is only valid when relationship is "independent"; chats in the current session share its workspace.`); + } return { relationship, prompt, @@ -529,7 +536,8 @@ export function getCreateSessionArgs(rawArgs: unknown, sessions: readonly IAgent } return { relationship, - workspace: resolveWorkspace(getRequiredString(workspace, 'workspace', SessionServerToolName.CreateSession), sessions), + workspace: resolveWorkspace(getRequiredString(workspace, 'workspace', SessionServerToolName.CreateSession), sessions, worktree === false), + ...(worktree !== undefined ? { worktree } : {}), prompt, title, ...(model !== undefined ? { model } : {}), @@ -821,9 +829,12 @@ export async function applyCreateSessionTool(accessor: ISessionServerToolAccesso const provider = args.model?.provider ?? defaults?.provider; const inheritsSourceProvider = provider !== undefined && provider === defaults?.provider; const inheritedProviderConfig = inheritsSourceProvider ? defaults?.config : undefined; - const isolation = defaults?.project !== undefined && isEqual(defaults.project, args.workspace) - ? defaults.isolation - : 'worktree'; + let isolation: 'folder' | 'worktree' | undefined = 'worktree'; + if (args.worktree !== undefined) { + isolation = args.worktree ? 'worktree' : 'folder'; + } else if (defaults?.project !== undefined && isEqual(defaults.project, args.workspace)) { + isolation = defaults.isolation; + } const configValues = inheritedProviderConfig === undefined && isolation === undefined ? undefined : { diff --git a/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts b/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts index 327df814b4126..6ef778e0ef635 100644 --- a/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts +++ b/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts @@ -115,10 +115,11 @@ suite('SessionServerTools', () => { relationship: { type: 'string', enum: ['currentSession', 'independent'], - description: 'Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle.', + description: 'Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree.', }, prompt: { type: 'string', description: 'Initial prompt to send to the new session.' }, workspace: { type: 'string', description: 'For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`.' }, + worktree: { type: 'boolean', description: 'Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session\'s isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`.' }, title: { type: 'string', maxLength: 200, description: 'Short title for the new chat or independent session.' }, model: { type: 'string', description: 'Optional model ID or display name. Defaults to the current chat\'s model. For `currentSession`, the model must belong to the current session\'s provider; for `independent`, the model selects the new session\'s provider.' }, }, @@ -644,6 +645,12 @@ suite('SessionServerTools', () => { }); }); + test('create_session guidance requires an explicit isolation choice and excludes currentSession', () => { + const description = sessionServerToolDefinitions.find(definition => definition.name === SessionServerToolName.CreateSession)?.description ?? ''; + assert.match(description, /Only supply `worktree` when the user explicitly requests working with or without a new worktree/); + assert.match(description, /never combine it with `currentSession`/); + }); + test('getCreateSessionArgs resolves workspace by working directory and model by id/name', () => { const sessions = [sessionMeta('s1', SessionStatus.Idle, workspace)]; const byId = getCreateSessionArgs({ relationship: 'independent', workspace: workspace.toString(), prompt: 'hi', title: 'Task', model: 'gpt-4o' }, sessions, [model]); @@ -698,6 +705,63 @@ suite('SessionServerTools', () => { }); }); + for (const scheme of ['file', 'vscode-remote']) { + for (const worktree of [false, true, undefined]) { + test(`create_session resolves ${scheme} worktree folders to the project only with worktree=false (value=${worktree})`, async () => { + const project = URI.from({ scheme, authority: scheme === 'file' ? '' : 'ssh-remote+example', path: '/workspace/repo' }); + const existingWorktree = project.with({ path: '/worktrees/existing' }); + let created: IAgentCreateSessionConfig | undefined; + const accessor = createAccessor({ + listSessions: async () => [{ + ...sessionMeta('source', SessionStatus.Idle, existingWorktree), + project: { uri: project, displayName: 'Repo' }, + }], + getCreationDefaults: () => ({ provider: 'copilot', project, isolation: 'worktree' }), + onCreate: config => { created = config; }, + }); + + await applyCreateSessionTool(accessor, { + relationship: 'independent', + workspace: scheme === 'file' ? existingWorktree.fsPath : existingWorktree.toString(), + ...(worktree !== undefined ? { worktree } : {}), + prompt: 'do it', + title: 'Task', + }, URI.parse('copilot:/source')); + + assert.deepStrictEqual({ + workingDirectories: created?.workingDirectories?.map(directory => directory.toString()), + isolation: created?.config?.[SessionConfigKey.Isolation], + }, { + workingDirectories: [(worktree === false ? project : existingWorktree).toString()], + isolation: worktree === false ? 'folder' : 'worktree', + }); + }); + } + } + + test('getCreateSessionArgs preserves folders without a known project when worktree=false', () => { + for (const sessions of [[], [sessionMeta('folder', SessionStatus.Idle, workspace)]]) { + const args = getCreateSessionArgs({ + relationship: 'independent', + workspace: workspace.toString(), + worktree: false, + prompt: 'do it', + title: 'Task', + }, sessions, []); + + assert.deepStrictEqual({ + ...args, + workspace: args.relationship === 'independent' ? args.workspace.toString() : undefined, + }, { + relationship: 'independent', + workspace: workspace.toString(), + worktree: false, + prompt: 'do it', + title: 'Task', + }); + } + }); + test('getCreateSessionArgs reports ambiguous project names', () => { const sessions = [ { ...sessionMeta('one', SessionStatus.Idle, URI.parse('file:///worktrees/one')), project: { uri: URI.parse('file:///projects/one'), displayName: 'App' } }, @@ -981,7 +1045,7 @@ suite('SessionServerTools', () => { const gitWorkspace = URI.file('/workspace/git-repository'); let created: IAgentCreateSessionConfig | undefined; const accessor = createAccessor({ - getCreationDefaults: () => ({ provider: 'copilot', isolation: 'worktree', project: gitWorkspace }), + getCreationDefaults: () => ({ provider: 'copilot', config: { [SessionConfigKey.Isolation]: 'worktree' }, isolation: 'worktree', project: gitWorkspace }), onCreate: config => { created = config; }, }); @@ -1003,6 +1067,98 @@ suite('SessionServerTools', () => { }); }); + for (const worktree of [false, true]) { + test(`create_session honors explicit worktree=${worktree} over inherited isolation`, async () => { + let created: IAgentCreateSessionConfig | undefined; + const accessor = createAccessor({ + getCreationDefaults: () => ({ + provider: 'copilot', + project: workspace, + isolation: worktree ? 'folder' : 'worktree', + config: { [SessionConfigKey.Isolation]: worktree ? 'folder' : 'worktree', autoApprove: 'autoApprove' }, + }), + onCreate: config => { created = config; }, + }); + + await applyCreateSessionTool(accessor, { + relationship: 'independent', + workspace: workspace.toString(), + worktree, + prompt: 'do it', + title: 'Task', + }, URI.parse('copilot:/source')); + + assert.deepStrictEqual(created?.config, { + [SessionConfigKey.Isolation]: worktree ? 'worktree' : 'folder', + autoApprove: 'autoApprove', + }); + }); + + test(`create_session rejects currentSession with worktree=${worktree} before creating work`, async () => { + const operations: string[] = []; + const accessor = createAccessor({ + onCreate: () => operations.push('session'), + onCreateChat: () => operations.push('chat'), + onPrompt: () => operations.push('prompt'), + }); + + await assert.rejects(applyCreateSessionTool(accessor, { + relationship: 'currentSession', + worktree, + prompt: 'do it', + title: 'Task', + }, URI.parse('copilot:/source')), /worktree is only valid when relationship is "independent"/); + assert.deepStrictEqual(operations, []); + }); + } + + test('create_session preserves unspecified isolation for the same project', async () => { + let created: IAgentCreateSessionConfig | undefined; + const accessor = createAccessor({ + getCreationDefaults: () => ({ provider: 'copilot', project: workspace }), + onCreate: config => { created = config; }, + }); + + await applyCreateSessionTool(accessor, { + relationship: 'independent', + workspace: workspace.toString(), + prompt: 'do it', + title: 'Task', + }, URI.parse('copilot:/source')); + + assert.deepStrictEqual(created?.config, undefined); + }); + + test('create_session honors worktree=false for a different project', async () => { + let created: IAgentCreateSessionConfig | undefined; + const accessor = createAccessor({ + getCreationDefaults: () => ({ provider: 'copilot', project: URI.file('/workspace/source'), isolation: 'worktree' }), + onCreate: config => { created = config; }, + }); + + await applyCreateSessionTool(accessor, { + relationship: 'independent', + workspace: workspace.toString(), + worktree: false, + prompt: 'do it', + title: 'Task', + }, URI.parse('copilot:/source')); + + assert.deepStrictEqual(created?.config, { [SessionConfigKey.Isolation]: 'folder' }); + }); + + test('getCreateSessionArgs rejects non-boolean worktree values', () => { + for (const worktree of ['true', 'false', 1, null, {}]) { + assert.throws(() => getCreateSessionArgs({ + relationship: 'independent', + workspace: workspace.toString(), + worktree, + prompt: 'do it', + title: 'Task', + }, [], []), /worktree must be a boolean/); + } + }); + test('create_session uses a remote project root with a model from another provider', async () => { const remoteProject = URI.parse('vscode-remote://ssh-remote+example/home/me/app'); const remoteWorktree = URI.parse('vscode-remote://ssh-remote+example/home/me/app-worktree'); From 4725615df1c253de959c04dca3586846dbdc7457 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 8 Sep 2026 17:11:04 +0200 Subject: [PATCH 2/4] sessions: preserve folders when disabling worktree isolation Resolve only exact linked-worktree roots reported by Git to the primary checkout. Preserve nested and additional workspace folders instead of inferring worktree identity from session project metadata. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vs/platform/agentHost/AGENTS.md | 5 +- .../platform/agentHost/node/agentService.ts | 1 + .../agentHost/node/agentServiceFoundation.ts | 1 + .../node/shared/sessionServerTools.ts | 18 ++-- .../test/node/sessionServerTools.test.ts | 86 ++++++++++++++++++- 5 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/agentHost/AGENTS.md b/src/vs/platform/agentHost/AGENTS.md index d9673ba232099..2f4bc50e06b8b 100644 --- a/src/vs/platform/agentHost/AGENTS.md +++ b/src/vs/platform/agentHost/AGENTS.md @@ -266,8 +266,9 @@ selection independently of provider-owned configuration; otherwise it uses worktree isolation. The optional `worktree` argument overrides that selection. Agents must set it only when the user explicitly asks to create a worktree (`true`) or work without one (`false`); otherwise they must omit it. -With `false`, a supplied worktree folder resolves to its known project root -before session creation; folders without a known project are used directly. +With `false`, an exact linked-worktree root reported by Git resolves to its +primary checkout before session creation. Nested and ordinary additional +workspace folders are preserved. The option is invalid with `currentSession`, whose chats share the existing workspace. The target workspace still constrains the effective selection, so a folder that cannot support Git worktrees resolves to folder isolation. diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index 36c53366234f1..853ce5afbff52 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -1178,6 +1178,7 @@ export class AgentService extends Disposable implements IAgentService { && readSessionWorkspaceless(this._stateManager.getSessionState(session.toString())?._meta), listSessions: () => this.listSessions(), getSession: session => this._getSessionMetadata(session), + getWorktreeRoots: workspace => this._gitService.getWorktreeRoots(workspace), createSession: config => this.createSession(config), getModels: () => { const models: IAgentModelInfo[] = []; diff --git a/src/vs/platform/agentHost/node/agentServiceFoundation.ts b/src/vs/platform/agentHost/node/agentServiceFoundation.ts index 8506160d570ad..a4461a63d4883 100644 --- a/src/vs/platform/agentHost/node/agentServiceFoundation.ts +++ b/src/vs/platform/agentHost/node/agentServiceFoundation.ts @@ -39,6 +39,7 @@ export class AgentServiceCallbackAdapter implements IAgentServiceCallbackBinder canConvertWorkspace: session => this.value.sessionServerToolAccessor.canConvertWorkspace(session), listSessions: () => this.value.sessionServerToolAccessor.listSessions(), getSession: session => this.value.sessionServerToolAccessor.getSession(session), + getWorktreeRoots: workspace => this.value.sessionServerToolAccessor.getWorktreeRoots(workspace), createSession: config => this.value.sessionServerToolAccessor.createSession(config), getModels: () => this.value.sessionServerToolAccessor.getModels(), getCreationDefaults: source => this.value.sessionServerToolAccessor.getCreationDefaults(source), diff --git a/src/vs/platform/agentHost/node/shared/sessionServerTools.ts b/src/vs/platform/agentHost/node/shared/sessionServerTools.ts index 922078123f4ec..ce925bb291cf0 100644 --- a/src/vs/platform/agentHost/node/shared/sessionServerTools.ts +++ b/src/vs/platform/agentHost/node/shared/sessionServerTools.ts @@ -243,6 +243,7 @@ export interface IAgentServiceSessionServerToolAccessor { readonly canConvertWorkspace: (session: URI) => boolean; readonly listSessions: () => Promise; readonly getSession: (session: URI) => Promise; + readonly getWorktreeRoots: (workspace: URI) => Promise; readonly createSession: (config: IAgentCreateSessionConfig) => Promise; readonly getModels: () => readonly IAgentModelInfo[]; readonly getCreationDefaults: (source: URI) => ISessionCreationDefaults | undefined; @@ -432,12 +433,12 @@ export function getSetWorkspaceArgs(rawArgs: unknown): { readonly workspaceFolde }; } -function resolveWorkspace(workspace: string, sessions: readonly IAgentSessionMetadata[], preferProject = false): URI { +function resolveWorkspace(workspace: string, sessions: readonly IAgentSessionMetadata[]): URI { const parsed = parseWorkspaceUri(workspace); for (const session of sessions) { for (const candidate of [session.project?.uri, ...(session.workingDirectories ?? [])]) { if (candidate && parsed && isEqual(candidate, parsed)) { - return preferProject ? session.project?.uri ?? candidate : candidate; + return candidate; } } } @@ -536,7 +537,7 @@ export function getCreateSessionArgs(rawArgs: unknown, sessions: readonly IAgent } return { relationship, - workspace: resolveWorkspace(getRequiredString(workspace, 'workspace', SessionServerToolName.CreateSession), sessions, worktree === false), + workspace: resolveWorkspace(getRequiredString(workspace, 'workspace', SessionServerToolName.CreateSession), sessions), ...(worktree !== undefined ? { worktree } : {}), prompt, title, @@ -825,6 +826,13 @@ export async function applyCreateSessionTool(accessor: ISessionServerToolAccesso if (parentDepth >= maxSessionSpawnDepth) { throw new Error(`Refusing to create a session: recursion limit reached (max spawn depth ${maxSessionSpawnDepth}). This session was itself created ${parentDepth} level(s) deep.`); } + let workspace = args.workspace; + if (args.worktree === false) { + const [primaryRoot, ...linkedRoots] = await accessor.getWorktreeRoots(workspace); + if (primaryRoot && linkedRoots.some(root => isEqual(root, workspace))) { + workspace = primaryRoot; + } + } const defaults = source ? accessor.getCreationDefaults(source) : undefined; const provider = args.model?.provider ?? defaults?.provider; const inheritsSourceProvider = provider !== undefined && provider === defaults?.provider; @@ -832,7 +840,7 @@ export async function applyCreateSessionTool(accessor: ISessionServerToolAccesso let isolation: 'folder' | 'worktree' | undefined = 'worktree'; if (args.worktree !== undefined) { isolation = args.worktree ? 'worktree' : 'folder'; - } else if (defaults?.project !== undefined && isEqual(defaults.project, args.workspace)) { + } else if (defaults?.project !== undefined && isEqual(defaults.project, workspace)) { isolation = defaults.isolation; } const configValues = inheritedProviderConfig === undefined && isolation === undefined @@ -842,7 +850,7 @@ export async function applyCreateSessionTool(accessor: ISessionServerToolAccesso ...(isolation !== undefined ? { [SessionConfigKey.Isolation]: isolation } : {}), }; const config: IAgentCreateSessionConfig = { - workingDirectories: args.workspace ? [args.workspace] : undefined, + workingDirectories: [workspace], ...(provider !== undefined ? { provider } : {}), ...(args.model !== undefined ? { model: { id: args.model.id } } : defaults?.model !== undefined ? { model: defaults.model } : {}), ...(configValues !== undefined ? { config: configValues } : {}), diff --git a/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts b/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts index 6ef778e0ef635..8e295cdd5b476 100644 --- a/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts +++ b/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts @@ -68,6 +68,7 @@ suite('SessionServerTools', () => { canConvertWorkspace: overrides?.canConvertWorkspace ?? (() => true), listSessions: overrides?.listSessions ?? (async () => [sessionMeta('s1', SessionStatus.InProgress, workspace)]), getSession: overrides?.getSession ?? (async session => session.toString() === 'copilot:/s1' ? sessionMeta('s1', SessionStatus.InProgress, workspace) : undefined), + getWorktreeRoots: overrides?.getWorktreeRoots ?? (async () => []), createSession: overrides?.createSession ?? (async config => { overrides?.onCreate?.(config); return URI.parse('copilot:/new'); }), getModels: overrides?.getModels ?? (() => [model]), getCreationDefaults: overrides?.getCreationDefaults ?? (() => undefined), @@ -707,7 +708,7 @@ suite('SessionServerTools', () => { for (const scheme of ['file', 'vscode-remote']) { for (const worktree of [false, true, undefined]) { - test(`create_session resolves ${scheme} worktree folders to the project only with worktree=false (value=${worktree})`, async () => { + test(`create_session resolves ${scheme} linked worktree roots only with worktree=false (value=${worktree})`, async () => { const project = URI.from({ scheme, authority: scheme === 'file' ? '' : 'ssh-remote+example', path: '/workspace/repo' }); const existingWorktree = project.with({ path: '/worktrees/existing' }); let created: IAgentCreateSessionConfig | undefined; @@ -717,6 +718,11 @@ suite('SessionServerTools', () => { project: { uri: project, displayName: 'Repo' }, }], getCreationDefaults: () => ({ provider: 'copilot', project, isolation: 'worktree' }), + getWorktreeRoots: async directory => { + assert.strictEqual(directory.toString(), existingWorktree.toString()); + assert.strictEqual(worktree, false); + return [project, existingWorktree]; + }, onCreate: config => { created = config; }, }); @@ -739,6 +745,84 @@ suite('SessionServerTools', () => { } } + test('create_session with worktree=false preserves nested and additional workspace folders', async () => { + const project = URI.file('/repo'); + const linkedRoot = URI.file('/worktrees/linked'); + const nestedFolder = URI.file('/repo/packages/foo'); + const linkedNestedFolder = URI.file('/worktrees/linked/packages/foo'); + const additionalRoot = URI.file('/other'); + const plainFolder = URI.file('/plain'); + const created: (string[] | undefined)[] = []; + const accessor = createAccessor({ + listSessions: async () => [{ + ...sessionMeta('source', SessionStatus.Idle, nestedFolder), + workingDirectories: [nestedFolder, additionalRoot, linkedNestedFolder, plainFolder], + project: { uri: project, displayName: 'Repo' }, + }], + getWorktreeRoots: async directory => { + if (directory.toString() === additionalRoot.toString()) { + return [additionalRoot]; + } + if (directory.toString() === plainFolder.toString()) { + return []; + } + return [project, linkedRoot]; + }, + onCreate: config => created.push(config.workingDirectories?.map(directory => directory.toString())), + }); + + for (const directory of [project, nestedFolder, linkedNestedFolder, additionalRoot, plainFolder]) { + await applyCreateSessionTool(accessor, { + relationship: 'independent', + workspace: directory.toString(), + worktree: false, + prompt: 'do it', + title: 'Task', + }); + } + + assert.deepStrictEqual(created, [project, nestedFolder, linkedNestedFolder, additionalRoot, plainFolder].map(directory => [directory.toString()])); + }); + + test('create_session with worktree=false resolves linked roots without session metadata', async () => { + const project = URI.file('/repo'); + const linkedRoot = URI.file('/worktrees/linked'); + let created: IAgentCreateSessionConfig | undefined; + const accessor = createAccessor({ + listSessions: async () => [], + getWorktreeRoots: async () => [project, linkedRoot], + onCreate: config => { created = config; }, + }); + + await applyCreateSessionTool(accessor, { + relationship: 'independent', + workspace: linkedRoot.toString(), + worktree: false, + prompt: 'do it', + title: 'Task', + }); + + assert.deepStrictEqual(created?.workingDirectories?.map(directory => directory.toString()), [project.toString()]); + }); + + test('create_session propagates worktree lookup failures before creating a session', async () => { + const operations: string[] = []; + const accessor = createAccessor({ + getWorktreeRoots: async () => { throw new Error('Worktree lookup failed'); }, + onCreate: () => operations.push('create'), + onPrompt: () => operations.push('prompt'), + }); + + await assert.rejects(applyCreateSessionTool(accessor, { + relationship: 'independent', + workspace: workspace.toString(), + worktree: false, + prompt: 'do it', + title: 'Task', + }), /Worktree lookup failed/); + assert.deepStrictEqual(operations, []); + }); + test('getCreateSessionArgs preserves folders without a known project when worktree=false', () => { for (const sessions of [[], [sessionMeta('folder', SessionStatus.Idle, workspace)]]) { const args = getCreateSessionArgs({ From 9a8d2b79c53df0bbe367ef9ab82523a4465de736 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 8 Sep 2026 18:32:39 +0200 Subject: [PATCH 3/4] sessions: fix worktree tests and refresh tool prompt snapshots Use drive-qualified paths for Windows filesystem test inputs and regenerate Copilot prompt baselines for the explicit worktree tool option. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ..._Host_E2E___Copilot_prompts_claude-haiku-4_5.prompt.md | 8 ++++++-- ...t_Host_E2E___Copilot_prompts_claude-opus-4_5.prompt.md | 8 ++++++-- ...t_Host_E2E___Copilot_prompts_claude-opus-4_6.prompt.md | 8 ++++++-- ...t_Host_E2E___Copilot_prompts_claude-opus-4_7.prompt.md | 8 ++++++-- ...t_Host_E2E___Copilot_prompts_claude-opus-4_8.prompt.md | 8 ++++++-- ...ent_Host_E2E___Copilot_prompts_claude-opus-5.prompt.md | 8 ++++++-- ...Host_E2E___Copilot_prompts_claude-sonnet-4_5.prompt.md | 8 ++++++-- ...Host_E2E___Copilot_prompts_claude-sonnet-4_6.prompt.md | 8 ++++++-- ...t_Host_E2E___Copilot_prompts_claude-sonnet-5.prompt.md | 8 ++++++-- ..._Host_E2E___Copilot_prompts_gemini-2_0-flash.prompt.md | 8 ++++++-- ...Agent_Host_E2E___Copilot_prompts_gpt-5-codex.prompt.md | 8 ++++++-- .../Agent_Host_E2E___Copilot_prompts_gpt-5-mini.prompt.md | 8 ++++++-- .../Agent_Host_E2E___Copilot_prompts_gpt-5.prompt.md | 8 ++++++-- ...ost_E2E___Copilot_prompts_gpt-5_1-codex-mini.prompt.md | 8 ++++++-- ...ent_Host_E2E___Copilot_prompts_gpt-5_1-codex.prompt.md | 8 ++++++-- .../Agent_Host_E2E___Copilot_prompts_gpt-5_1.prompt.md | 8 ++++++-- ...gent_Host_E2E___Copilot_prompts_gpt-5_6-luna.prompt.md | 8 ++++++-- ...Agent_Host_E2E___Copilot_prompts_gpt-5_6-sol.prompt.md | 8 ++++++-- ...ent_Host_E2E___Copilot_prompts_gpt-5_6-terra.prompt.md | 8 ++++++-- .../agentHost/test/node/sessionServerTools.test.ts | 6 ++++-- 20 files changed, 118 insertions(+), 40 deletions(-) diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-haiku-4_5.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-haiku-4_5.prompt.md index 224bd3aa3ca00..e42c352951bf9 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-haiku-4_5.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-haiku-4_5.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_5.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_5.prompt.md index 88d3faa90ed74..b69997595dfc2 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_5.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_5.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_6.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_6.prompt.md index fc7650db5b919..6ddab93b58574 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_6.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_6.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_7.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_7.prompt.md index a8f698a3b51f4..f377b1c2fadc0 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_7.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_7.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_8.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_8.prompt.md index 4f6e9c308c01e..b8631a4a9992b 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_8.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-4_8.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-5.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-5.prompt.md index 38fc3b56132e6..f822d904c6581 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-5.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-opus-5.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_5.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_5.prompt.md index 3bde585137ef7..424a08bd1098d 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_5.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_5.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_6.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_6.prompt.md index 2d306031176b4..a4354931e8198 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_6.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-4_6.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-5.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-5.prompt.md index 969abbe8fb4f5..8d997ba4df01e 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-5.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_claude-sonnet-5.prompt.md @@ -733,7 +733,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "input_schema": { "type": "object", "properties": { @@ -743,7 +743,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -753,6 +753,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gemini-2_0-flash.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gemini-2_0-flash.prompt.md index 7bec1bc62d023..40fa1b9b2d167 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gemini-2_0-flash.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gemini-2_0-flash.prompt.md @@ -765,7 +765,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -775,7 +775,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -785,6 +785,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-codex.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-codex.prompt.md index 4589a2396b3f1..ae4606be3e29d 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-codex.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-codex.prompt.md @@ -726,7 +726,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -736,7 +736,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -746,6 +746,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-mini.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-mini.prompt.md index 063a87373067e..96ec9caa3908d 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-mini.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5-mini.prompt.md @@ -765,7 +765,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -775,7 +775,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -785,6 +785,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5.prompt.md index 180e0a9031009..044dcfda39962 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5.prompt.md @@ -765,7 +765,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -775,7 +775,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -785,6 +785,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex-mini.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex-mini.prompt.md index 3cf34126e2f44..a5d40e1516560 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex-mini.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex-mini.prompt.md @@ -726,7 +726,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -736,7 +736,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -746,6 +746,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex.prompt.md index b5fb7809f3f48..e4737cac501e1 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1-codex.prompt.md @@ -726,7 +726,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -736,7 +736,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -746,6 +746,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1.prompt.md index 2c3011c9f5e57..6c2bf215f1499 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_1.prompt.md @@ -765,7 +765,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -775,7 +775,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -785,6 +785,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-luna.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-luna.prompt.md index 75162a227fa56..dc1cf943c2f06 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-luna.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-luna.prompt.md @@ -726,7 +726,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -736,7 +736,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -746,6 +746,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-sol.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-sol.prompt.md index d09c2dd4d3f13..6b6d836fb329f 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-sol.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-sol.prompt.md @@ -726,7 +726,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -736,7 +736,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -746,6 +746,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-terra.prompt.md b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-terra.prompt.md index 30f1d3b7ad491..bc2f1cf41f39a 100644 --- a/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-terra.prompt.md +++ b/src/vs/platform/agentHost/test/node/e2e/providers/__snapshots__/Agent_Host_E2E___Copilot_prompts_gpt-5_6-terra.prompt.md @@ -726,7 +726,7 @@ }, { "name": "create_session", - "description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.", + "description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.", "parameters": { "type": "object", "properties": { @@ -736,7 +736,7 @@ "currentSession", "independent" ], - "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle." + "description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree." }, "prompt": { "type": "string", @@ -746,6 +746,10 @@ "type": "string", "description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`." }, + "worktree": { + "type": "boolean", + "description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`." + }, "title": { "type": "string", "description": "Short title for the new chat or independent session.\n\n{maxLength: 200}" diff --git a/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts b/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts index 8e295cdd5b476..20b8d5c1fd0ab 100644 --- a/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts +++ b/src/vs/platform/agentHost/test/node/sessionServerTools.test.ts @@ -6,6 +6,7 @@ import assert from 'assert'; import { DeferredPromise } from '../../../../base/common/async.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { isWindows } from '../../../../base/common/platform.js'; import { URI } from '../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { NullLogService } from '../../../log/common/log.js'; @@ -709,8 +710,9 @@ suite('SessionServerTools', () => { for (const scheme of ['file', 'vscode-remote']) { for (const worktree of [false, true, undefined]) { test(`create_session resolves ${scheme} linked worktree roots only with worktree=false (value=${worktree})`, async () => { - const project = URI.from({ scheme, authority: scheme === 'file' ? '' : 'ssh-remote+example', path: '/workspace/repo' }); - const existingWorktree = project.with({ path: '/worktrees/existing' }); + const drive = scheme === 'file' && isWindows ? '/c:' : ''; + const project = URI.from({ scheme, authority: scheme === 'file' ? '' : 'ssh-remote+example', path: `${drive}/workspace/repo` }); + const existingWorktree = project.with({ path: `${drive}/worktrees/existing` }); let created: IAgentCreateSessionConfig | undefined; const accessor = createAccessor({ listSessions: async () => [{ From 9c936d3cd8f73fb47970cef9e3e6fe93e5cd7a2f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 9 Sep 2026 13:56:46 +0200 Subject: [PATCH 4/4] diagnostics: compare cancellation replay on base and PR Temporary macOS A/B probe for #335076; do not merge. Alternate fixed revisions on one runner and retain host, runtime and protocol diagnostics before teardown. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/cancellation-ab.sh | 38 ++ .github/workflows/pr.yml | 478 ++---------------- .../e2e/harness/agentHostE2ETestHarness.ts | 20 +- 3 files changed, 97 insertions(+), 439 deletions(-) create mode 100644 .github/cancellation-ab.sh diff --git a/.github/cancellation-ab.sh b/.github/cancellation-ab.sh new file mode 100644 index 0000000000000..355f589fb987d --- /dev/null +++ b/.github/cancellation-ab.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Temporary investigation only; never merge this script into main. +base=e341a3c1515af84f5f679761d76a05a02af182cd +feature=6acb3aafa40acf016d00efd5230a97e446ecfa7e +evidence="$RUNNER_TEMP/cancellation-ab" +mkdir -p "$evidence" +printf 'phase\titeration\tcommit\texit_code\n' > "$evidence/results.tsv" +node -p 'JSON.stringify({node:process.version,platform:process.platform,arch:process.arch,copilot:require("./node_modules/@github/copilot/package.json").version})' > "$evidence/environment.json" +sw_vers >> "$evidence/environment.json" +failed=0 +for phase in base-1 feature-1 feature-2 base-2; do + if [[ "$phase" == base-* ]]; then + revision="$base" + else + revision="$feature" + fi + git switch --detach "$revision" + git apply "$RUNNER_TEMP/cancellation.patch" + npm run transpile-client > "$evidence/$phase-transpile.log" 2>&1 + for iteration in 1 2 3; do + destination="$evidence/$phase-$iteration" + mkdir -p "$destination" + export VSCODE_CANCELLATION_DIAGNOSTICS="$destination" + result=0 + ./scripts/test-integration.sh \ + --run src/vs/platform/agentHost/test/node/e2e/providers/copilotAgentHostE2E.integrationTest.ts \ + --grep 'cancelling a turn paused' > "$destination/test.log" 2>&1 || result=$? + printf '%s\t%s\t%s\t%s\n' "$phase" "$iteration" "$revision" "$result" | tee -a "$evidence/results.tsv" + if [[ "$result" != 0 ]]; then + failed=1 + fi + done + git apply --reverse "$RUNNER_TEMP/cancellation.patch" +done +cat "$evidence/results.tsv" >> "$GITHUB_STEP_SUMMARY" +exit "$failed" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f196afce554fb..c8aa1cb85280c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,461 +1,63 @@ -name: Code OSS +name: Cancellation A-B diagnostics (DO NOT MERGE) on: pull_request: - branches: - - main - - 'release/*' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + branches: [main] permissions: contents: read - pull-requests: read - -env: - VSCODE_QUALITY: 'oss' jobs: - compile: - name: Compile & Hygiene - runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-ubuntu-22.04-x64, "JobId=compile-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" ] + compare: + if: github.head_ref == 'diagnostics/335076-cancellation-ab' + runs-on: macos-26-xlarge + timeout-minutes: 60 + env: + VSCODE_QUALITY: oss + VSCODE_ARCH: arm64 + NPM_ARCH: arm64 + VSCODE_SKIP_PRELAUNCH: '1' steps: - - name: Checkout microsoft/vscode - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 lfs: true - - - name: Setup Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version-file: .nvmrc - - - name: Restore node_modules cache - id: cache-node-modules + - name: Save identical diagnostic instrumentation + run: | + git diff 6acb3aafa40acf016d00efd5230a97e446ecfa7e HEAD -- src/vs/platform/agentHost/test/node/e2e/harness/agentHostE2ETestHarness.ts > "$RUNNER_TEMP/cancellation.patch" + git show HEAD:.github/cancellation-ab.sh > "$RUNNER_TEMP/cancellation-ab.sh" + git diff --exit-code e341a3c1515af84f5f679761d76a05a02af182cd 6acb3aafa40acf016d00efd5230a97e446ecfa7e -- package.json package-lock.json .npmrc .nvmrc + - name: Restore dependencies + id: cache uses: ./.github/actions/restore-node-modules with: - key-prefix: node_modules-compile - key-args: "compile $(node -p process.arch)" - - - name: Install build tools - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libnotify-bin libkrb5-dev - - - name: Install dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' + key-prefix: node_modules-macos + key-args: "darwin arm64 $(node -p process.arch)" + - name: Install locked dependencies + if: steps.cache.outputs.cache-hit != 'true' run: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done + python3 -m pip install --break-system-packages setuptools + npm ci env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + npm_config_arch: arm64 + ELECTRON_SKIP_BINARY_DOWNLOAD: '1' + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Prepare Electron types - run: node build/npm/electronTypes.ts - - - name: Type check /build/ scripts - run: npm run typecheck - working-directory: build - - - name: Prepare built-in extensions cache key - shell: pwsh - run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash - - - name: Restore built-in extensions cache - id: cache-builtin-extensions - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - enableCrossOsArchive: true - path: .build/builtInExtensions - key: "builtin-extensions-${{ hashFiles('.build/builtindepshash') }}" - - - name: Download built-in extensions - if: steps.cache-builtin-extensions.outputs.cache-hit != 'true' - run: node build/lib/builtInExtensions.ts + GYP_DEFINES: kerberos_use_rtld=false + - name: Prepare Electron + run: npm run electron -- arm64 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check Markdown editor manifests - run: | - npm run markdown-editor-package-json-check - npm --prefix extensions/markdown-language-features run test-markdown-editor-package-json - - - name: Compile & Hygiene - run: npm exec -- npm-run-all2 -lp core-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check Codex protocol client is in sync - run: | - git fetch --no-tags --depth=1 origin "$CODEX_SYNC_BASE" || true - node build/codex/check-protocol-sync.ts --if-changed --base "$CODEX_SYNC_BASE" - env: - CODEX_SYNC_BASE: ${{ github.event.pull_request.base.sha }} - - - name: Check cyclic dependencies - run: node build/lib/checkCyclicDependencies.ts out-build - - linux-cli-tests: - name: Linux - uses: ./.github/workflows/pr-linux-cli-test.yml - with: - job_name: CLI - rustup_toolchain: 1.88 - - linux-electron-unit-tests: - name: Linux - uses: ./.github/workflows/pr-linux-test.yml - with: - job_name: Electron-Unit - electron_tests: true - integration_tests: false - smoke_tests: false - - linux-electron-tests: - name: Linux - uses: ./.github/workflows/pr-linux-test.yml - with: - job_name: Electron - electron_tests: true - unit_tests: false - smoke_tests: false - - linux-electron-smoke-tests: - name: Linux - uses: ./.github/workflows/pr-linux-test.yml - with: - job_name: Electron-Smoke - electron_tests: true - unit_tests: false - integration_tests: false - - linux-browser-tests: - name: Linux - uses: ./.github/workflows/pr-linux-test.yml - with: - job_name: Browser - browser_tests: true - - linux-remote-tests: - name: Linux - uses: ./.github/workflows/pr-linux-test.yml - with: - job_name: Remote - remote_tests: true - - macos-electron-unit-tests: - name: macOS - uses: ./.github/workflows/pr-darwin-test.yml - with: - job_name: Electron-Unit - electron_tests: true - integration_tests: false - smoke_tests: false - - macos-electron-tests: - name: macOS - uses: ./.github/workflows/pr-darwin-test.yml - with: - job_name: Electron - electron_tests: true - unit_tests: false - smoke_tests: false - - macos-electron-smoke-tests: - name: macOS - uses: ./.github/workflows/pr-darwin-test.yml - with: - job_name: Electron-Smoke - electron_tests: true - unit_tests: false - integration_tests: false - - macos-browser-tests: - name: macOS - uses: ./.github/workflows/pr-darwin-test.yml - with: - job_name: Browser - browser_tests: true - - macos-remote-tests: - name: macOS - uses: ./.github/workflows/pr-darwin-test.yml - with: - job_name: Remote - remote_tests: true - - windows-electron-unit-tests: - name: Windows - uses: ./.github/workflows/pr-win32-test.yml - with: - job_name: Electron-Unit - electron_tests: true - integration_tests: false - smoke_tests: false - - windows-electron-tests: - name: Windows - uses: ./.github/workflows/pr-win32-test.yml - with: - job_name: Electron - electron_tests: true - unit_tests: false - smoke_tests: false - - windows-electron-smoke-tests: - name: Windows - uses: ./.github/workflows/pr-win32-test.yml - with: - job_name: Electron-Smoke - electron_tests: true - unit_tests: false - integration_tests: false - - windows-browser-tests: - name: Windows - uses: ./.github/workflows/pr-win32-test.yml - with: - job_name: Browser - browser_tests: true - - windows-remote-tests: - name: Windows - uses: ./.github/workflows/pr-win32-test.yml - with: - job_name: Remote - remote_tests: true - - copilot-check-test-cache: - name: Copilot - Check Test Cache - runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-ubuntu-22.04-x64, "JobId=copilot-check-test-cache-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" ] - permissions: - contents: read - pull-requests: read - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - lfs: true - - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version-file: extensions/copilot/.nvmrc - - - name: Restore node_modules cache - id: cache-node-modules - uses: ./.github/actions/restore-node-modules - with: - key-prefix: copilot-node_modules-linux - key-args: "$(node -p process.platform) $(node -p process.arch)" - - - name: Install root dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci --ignore-scripts --no-workspaces - - - name: Install copilot dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - working-directory: extensions/copilot - run: npm ci - - - name: Ensure no duplicate cache keys - working-directory: extensions/copilot - run: npx tsx test/base/cache-cli check - - - name: Ensure no untrusted cache changes - if: github.event_name == 'pull_request' - working-directory: extensions/copilot - run: npx tsx build/pr-check-cache-files.ts - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPOSITORY: ${{ github.repository }} - PULL_REQUEST: ${{ github.event.pull_request.number }} - - copilot-check-telemetry: - name: Copilot - Check Telemetry - runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-ubuntu-22.04-x64, "JobId=copilot-check-telemetry-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" ] - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - lfs: true - - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version-file: extensions/copilot/.nvmrc - - - name: Validate telemetry events - working-directory: extensions/copilot - run: npx --package=@vscode/telemetry-extractor@1.20.4 --yes vscode-telemetry-extractor -s . > /dev/null - - copilot-linux-tests: - name: Copilot - Test (Linux) - runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-ubuntu-22.04-x64, "JobId=copilot-linux-tests-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" ] - permissions: - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - lfs: true - - - name: Setup Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version-file: extensions/copilot/.nvmrc - - - name: Setup Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.12' - architecture: 'x64' - - - name: Setup .NET - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 - with: - dotnet-version: '10.0' - - - name: Install setuptools - run: pip install setuptools - - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y xvfb libgtk-3-0 libgbm1 - - - name: Restore node_modules cache - id: cache-node-modules - uses: ./.github/actions/restore-node-modules - with: - key-prefix: copilot-node_modules-linux - key-args: "$(node -p process.platform) $(node -p process.arch)" - - - name: Install root dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci --ignore-scripts --no-workspaces - - - name: Install copilot dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - working-directory: extensions/copilot - run: npm ci - - - name: TypeScript type checking - working-directory: extensions/copilot - run: npm run typecheck - - - name: Lint - working-directory: extensions/copilot - run: npm run lint - - - name: Compile - working-directory: extensions/copilot - run: npm run compile - - - name: Run vitest unit tests - working-directory: extensions/copilot - run: npm run test:unit - - - name: Run simulation tests with cache - working-directory: extensions/copilot - run: npm run simulate-ci - - - name: Run Completions Core prompt tests - working-directory: extensions/copilot - run: npm run test:prompt - - - name: Run Completions Core lib tests using VS Code - working-directory: extensions/copilot - run: xvfb-run -a npm run test:completions-core - - - name: Archive simulation output - if: always() - working-directory: extensions/copilot - run: | - set -e - mkdir -p .simulation-archive - tar -czf .simulation-archive/simulation.tgz -C .simulation . - - - name: Upload simulation output + - name: Alternate base and PR on the same runner + run: bash "$RUNNER_TEMP/cancellation-ab.sh" + - name: Upload comparison evidence if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: copilot-simulation-output-linux-${{ github.run_attempt }} - path: extensions/copilot/.simulation-archive/simulation.tgz - - copilot-windows-tests: - name: Copilot - Test (Windows) - runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-windows-2022-x64, "JobId=copilot-windows-tests-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" ] - permissions: - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - lfs: true - - - name: Setup Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version-file: extensions/copilot/.nvmrc - - - name: Setup Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.12' - architecture: 'x64' - - - name: Setup .NET - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 - with: - dotnet-version: '10.0' - - - name: Install setuptools - run: pip install setuptools - - - name: Restore node_modules cache - id: cache-node-modules - uses: ./.github/actions/restore-node-modules - with: - key-prefix: copilot-node_modules-windows - key-args: "$(node -p process.platform) $(node -p process.arch)" - - - name: Install root dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: npm ci --ignore-scripts --no-workspaces - - - name: Install copilot dependencies - if: steps.cache-node-modules.outputs.cache-hit != 'true' - working-directory: extensions/copilot - run: npm ci - - - name: Compile - working-directory: extensions/copilot - run: npm run compile - - - name: Run vitest unit tests - working-directory: extensions/copilot - run: npm run test:unit - - - name: Run simulation tests with cache - working-directory: extensions/copilot - run: npm run simulate-ci - - - name: Run Completions Core prompt tests - working-directory: extensions/copilot - run: npm run test:prompt - - - name: Run Completions Core lib tests using VS Code - working-directory: extensions/copilot - run: npm run test:completions-core + name: cancellation-ab-evidence + path: ${{ runner.temp }}/cancellation-ab + retention-days: 7 diff --git a/src/vs/platform/agentHost/test/node/e2e/harness/agentHostE2ETestHarness.ts b/src/vs/platform/agentHost/test/node/e2e/harness/agentHostE2ETestHarness.ts index ad12e5ba70d38..03066db11298a 100644 --- a/src/vs/platform/agentHost/test/node/e2e/harness/agentHostE2ETestHarness.ts +++ b/src/vs/platform/agentHost/test/node/e2e/harness/agentHostE2ETestHarness.ts @@ -9,7 +9,7 @@ import assert from 'assert'; import { execSync } from 'child_process'; -import { chmodSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'fs'; +import { chmodSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'fs'; import { homedir, tmpdir, userInfo } from 'os'; import { fileURLToPath } from 'url'; import { timeout } from '../../../../../../base/common/async.js'; @@ -1120,6 +1120,24 @@ export class AgentHostE2EServerLease { * would only obscure it. */ async release(createdSessions: string[], forceRestart = false): Promise { + const diagnosticRoot = process.env['VSCODE_CANCELLATION_DIAGNOSTICS']; + if (diagnosticRoot) { + const destination = mkdtempSync(join(diagnosticRoot, 'test-')); + writeFileSync(join(destination, 'state.json'), JSON.stringify({ + createdSessions, + forceRestart, + notifications: this._client?.receivedNotifications(() => true), + capturedAt: new Date().toISOString(), + }, null, 2)); + for (const [name, source] of [ + ['host', join(this._startOptions.userDataDir, 'logs')], + ['runtime', join(this._startOptions.homeDir, '.copilot', 'logs')], + ]) { + if (existsSync(source)) { + cpSync(source, join(destination, name), { recursive: true }); + } + } + } const client = this._client; const cleanupErrors: Error[] = []; if (client) {