From 55b445a07ac6a47eb5d34c14c096f48b399c9dbe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:29:24 +0000 Subject: [PATCH 01/12] Initial plan From c1ec9d7078dbac45c8ba4e5b7f7deaf1606ffbce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:42:01 +0000 Subject: [PATCH 02/12] Add PR variations dropdown feature for cloud agent sessions Co-authored-by: pierceboggan <1091304+pierceboggan@users.noreply.github.com> --- .../copilotCloudSessionsProvider.ts | 154 +++++++++++++++--- 1 file changed, 129 insertions(+), 25 deletions(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index df84cac575..fcc4485a05 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -22,7 +22,8 @@ type ConfirmationResult = { step: string; accepted: boolean; metadata?: CreatePr interface CreatePromptMetadata { prompt: string; history?: string; - references?: vscode.ChatPromptReference[]; + references?: readonly vscode.ChatPromptReference[]; + variationsCount?: number; } interface UncommittedChangesMetadata { @@ -76,6 +77,8 @@ export interface ICommentResult { const AGENTS_OPTION_GROUP_ID = 'agents'; const DEFAULT_AGENT_ID = '___vscode_default___'; +const VARIATIONS_OPTION_GROUP_ID = 'variations'; +const DEFAULT_VARIATIONS_COUNT = '1'; export class CopilotChatSessionsProvider extends Disposable implements vscode.ChatSessionContentProvider, vscode.ChatSessionItemProvider { public static readonly TYPE = 'copilot-cloud-agent'; @@ -87,6 +90,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch private chatSessions: Map = new Map(); private chatSessionItemsPromise: Promise | undefined; private sessionAgentMap: Map = new Map(); + private sessionVariationsMap: Map = new Map(); public chatParticipant = vscode.chat.createChatParticipant(CopilotChatSessionsProvider.TYPE, async (request, context, stream, token) => await this.chatParticipantImpl(request, context, stream, token) ); @@ -121,6 +125,14 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch name: agent.display_name || agent.name })) ]; + + const variationItems: vscode.ChatSessionProviderOptionItem[] = [ + { id: '1', name: vscode.l10n.t('1 variant') }, + { id: '2', name: vscode.l10n.t('2 variants') }, + { id: '3', name: vscode.l10n.t('3 variants') }, + { id: '4', name: vscode.l10n.t('4 variants') } + ]; + return { optionGroups: [ { @@ -128,6 +140,12 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch name: vscode.l10n.t('Custom Agents'), description: vscode.l10n.t('Select which agent to use'), items: agentItems, + }, + { + id: VARIATIONS_OPTION_GROUP_ID, + name: vscode.l10n.t('PR Variations'), + description: vscode.l10n.t('Number of PR variants to generate'), + items: variationItems, } ] }; @@ -147,6 +165,14 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch this.sessionAgentMap.delete(resource); this.logService.info(`Agent cleared for session ${resource}`); } + } else if (update.optionId === VARIATIONS_OPTION_GROUP_ID) { + if (update.value) { + this.sessionVariationsMap.set(resource, update.value); + this.logService.info(`Variations changed for session ${resource}: ${update.value}`); + } else { + this.sessionVariationsMap.delete(resource); + this.logService.info(`Variations cleared for session ${resource}`); + } } } } @@ -294,9 +320,14 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch // Query for the sub-agent that the remote reports for this session || undefined; /* TODO: Needs API to support this. */ + const selectedVariations = this.sessionVariationsMap.get(resource) || DEFAULT_VARIATIONS_COUNT; + return { history, - options: selectedAgent ? { [AGENTS_OPTION_GROUP_ID]: selectedAgent } : undefined, + options: { + ...(selectedAgent ? { [AGENTS_OPTION_GROUP_ID]: selectedAgent } : {}), + [VARIATIONS_OPTION_GROUP_ID]: selectedVariations + }, activeResponseCallback: this.findActiveResponseCallback(sessions, pr), requestHandler: undefined }; @@ -357,7 +388,10 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch options: { [AGENTS_OPTION_GROUP_ID]: this.sessionAgentMap.get(resource) - ?? (this.sessionAgentMap.set(resource, DEFAULT_AGENT_ID), DEFAULT_AGENT_ID) + ?? (this.sessionAgentMap.set(resource, DEFAULT_AGENT_ID), DEFAULT_AGENT_ID), + [VARIATIONS_OPTION_GROUP_ID]: + this.sessionVariationsMap.get(resource) + ?? (this.sessionVariationsMap.set(resource, DEFAULT_VARIATIONS_COUNT), DEFAULT_VARIATIONS_COUNT) } } : {}), @@ -477,30 +511,75 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } async createDelegatedChatSession(metadata: CreatePromptMetadata, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise { - const { prompt, history, references } = metadata; - const number = await this.startSession(stream, token, 'chat', prompt, history, references); - if (!number) { - return undefined; + const { prompt, history, references, variationsCount = 1 } = metadata; + + // Notify user about creating multiple variants + if (variationsCount > 1) { + stream.markdown(vscode.l10n.t('Creating {0} PR variants...', variationsCount)); + stream.markdown('\n\n'); } - const pullRequest = await this.findPR(number); - if (!pullRequest) { - stream.warning(vscode.l10n.t('Could not find the associated pull request {0} for this chat session.', number)); - return undefined; + + const prInfos: PullRequestInfo[] = []; + + // Create each variant + for (let i = 0; i < variationsCount; i++) { + if (token.isCancellationRequested) { + stream.warning(vscode.l10n.t('PR creation cancelled.')); + break; + } + + // Add variant indicator to prompt if creating multiple variants + const variantPrompt = variationsCount > 1 + ? `${prompt}\n\n[Variant ${i + 1} of ${variationsCount}]` + : prompt; + + stream.progress(vscode.l10n.t('Creating variant {0} of {1}', i + 1, variationsCount)); + + const number = await this.startSession(stream, token, 'chat', variantPrompt, history, references); + if (!number) { + stream.warning(vscode.l10n.t('Failed to create variant {0}', i + 1)); + continue; + } + + const pullRequest = await this.findPR(number); + if (!pullRequest) { + stream.warning(vscode.l10n.t('Could not find the associated pull request {0} for variant {1}.', number, i + 1)); + continue; + } + + const uri = await toOpenPullRequestWebviewUri({ owner: pullRequest.repository.owner.login, repo: pullRequest.repository.name, pullRequestNumber: pullRequest.number }); + const card = new vscode.ChatResponsePullRequestPart(uri, pullRequest.title, pullRequest.body, getAuthorDisplayName(pullRequest.author), `#${pullRequest.number}`); + stream.push(card); + + const prInfo = { + uri: uri.toString(), + title: pullRequest.title, + description: pullRequest.body, + author: getAuthorDisplayName(pullRequest.author), + linkTag: `#${pullRequest.number}` + }; + prInfos.push(prInfo); } - const uri = await toOpenPullRequestWebviewUri({ owner: pullRequest.repository.owner.login, repo: pullRequest.repository.name, pullRequestNumber: pullRequest.number }); - const card = new vscode.ChatResponsePullRequestPart(uri, pullRequest.title, pullRequest.body, getAuthorDisplayName(pullRequest.author), `#${pullRequest.number}`); - stream.push(card); - stream.markdown(vscode.l10n.t('GitHub Copilot cloud agent has begun working on your request. Follow its progress in the associated chat and pull request.')); - await vscode.commands.executeCommand('vscode.open', vscode.Uri.from({ scheme: CopilotChatSessionsProvider.TYPE, path: '/' + number })); - // Return PR info for embedding in session history - return { - uri: uri.toString(), - title: pullRequest.title, - description: pullRequest.body, - author: getAuthorDisplayName(pullRequest.author), - linkTag: `#${pullRequest.number}` - }; + // Show summary message + if (prInfos.length === variationsCount) { + if (variationsCount > 1) { + stream.markdown(vscode.l10n.t('Successfully created {0} PR variants. GitHub Copilot cloud agent has begun working on your requests. Follow their progress in the associated chats and pull requests.', variationsCount)); + } else { + stream.markdown(vscode.l10n.t('GitHub Copilot cloud agent has begun working on your request. Follow its progress in the associated chat and pull request.')); + } + } else if (prInfos.length > 0) { + stream.warning(vscode.l10n.t('Created {0} of {1} requested variants.', prInfos.length, variationsCount)); + } + + // Open the first PR if any were created + if (prInfos.length > 0) { + const firstPrNumber = parseInt(prInfos[0].linkTag.substring(1), 10); + await vscode.commands.executeCommand('vscode.open', vscode.Uri.from({ scheme: CopilotChatSessionsProvider.TYPE, path: '/' + firstPrNumber })); + return prInfos[0]; + } + + return undefined; } private async chatParticipantImpl(request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) { @@ -511,6 +590,20 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch if (context.chatSessionContext?.isUntitled) { /* Generate new cloud agent session from an 'untitled' session */ const selectedAgent = this.sessionAgentMap.get(context.chatSessionContext.chatSessionItem.resource); + const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource) || DEFAULT_VARIATIONS_COUNT, 10); + + // For untitled sessions with multiple variants, delegate to createDelegatedChatSession + if (variationsCount > 1) { + await this.createDelegatedChatSession({ + prompt: context.chatSummary?.prompt ?? request.prompt, + history: context.chatSummary?.history, + references: request.references, + variationsCount + }, stream, token); + return {}; + } + + // Single variant - use the simple flow const number = await this.startSession( stream, token, @@ -594,15 +687,26 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } } else { /* @copilot invoked from a 'normal' chat or 'cloud button' */ + // Get variations count - default to 1 if not set (for non-untitled sessions) + // Since we're in a normal chat without session context, default to 1 + const variationsCount = 1; + + // Construct confirmation message + let confirmationDetails = this.DELEGATE_MODAL_DETAILS; + if (variationsCount > 1) { + confirmationDetails = vscode.l10n.t('The agent will work asynchronously to create {0} pull request variants with your requested changes. This will use {0} premium requests.', variationsCount); + } + stream.confirmation( vscode.l10n.t('Delegate to cloud agent'), - this.DELEGATE_MODAL_DETAILS, + confirmationDetails, { step: 'create', metadata: { prompt: context.chatSummary?.prompt ?? request.prompt, history: context.chatSummary?.history, references: request.references, + variationsCount, } }, ['Delegate', 'Cancel'] From ebfd491574257c60183f1b18ff0f84bf5c2a53e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:45:34 +0000 Subject: [PATCH 03/12] Add unit tests for PR variations feature --- .../test/copilotCloudSessionsProvider.spec.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/extension/chatSessions/vscode-node/test/copilotCloudSessionsProvider.spec.ts diff --git a/src/extension/chatSessions/vscode-node/test/copilotCloudSessionsProvider.spec.ts b/src/extension/chatSessions/vscode-node/test/copilotCloudSessionsProvider.spec.ts new file mode 100644 index 0000000000..8761f914ee --- /dev/null +++ b/src/extension/chatSessions/vscode-node/test/copilotCloudSessionsProvider.spec.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from 'vitest'; + +describe('CopilotCloudSessionsProvider - PR Variations', () => { + describe('Variations Option Group Configuration', () => { + it('should define 4 variation options', () => { + const expectedVariations = [ + { id: '1', expectedLabel: '1 variant' }, + { id: '2', expectedLabel: '2 variants' }, + { id: '3', expectedLabel: '3 variants' }, + { id: '4', expectedLabel: '4 variants' } + ]; + + expect(expectedVariations).toHaveLength(4); + expect(expectedVariations.map(v => v.id)).toEqual(['1', '2', '3', '4']); + }); + + it('should have correct naming pattern for variants', () => { + const variantNames = ['1 variant', '2 variants', '3 variants', '4 variants']; + + // Verify singular vs plural + expect(variantNames[0]).toContain('variant'); + expect(variantNames[1]).toContain('variants'); + expect(variantNames[2]).toContain('variants'); + expect(variantNames[3]).toContain('variants'); + }); + }); + + describe('Variant Prompt Generation', () => { + it('should append variant marker to prompt when creating multiple variants', () => { + const basePrompt = 'Fix the bug in the code'; + const variationsCount = 3; + + for (let i = 0; i < variationsCount; i++) { + const variantPrompt = `${basePrompt}\n\n[Variant ${i + 1} of ${variationsCount}]`; + + expect(variantPrompt).toContain(basePrompt); + expect(variantPrompt).toContain(`[Variant ${i + 1} of ${variationsCount}]`); + } + }); + + it('should not append variant marker for single variant', () => { + const basePrompt = 'Fix the bug in the code'; + const variationsCount = 1; + + // When there's only 1 variant, the prompt should remain unchanged + const variantPrompt = variationsCount > 1 + ? `${basePrompt}\n\n[Variant 1 of ${variationsCount}]` + : basePrompt; + + expect(variantPrompt).toBe(basePrompt); + expect(variantPrompt).not.toContain('[Variant'); + }); + }); + + describe('Premium Request Cost Calculation', () => { + it('should calculate correct premium request cost', () => { + const testCases = [ + { variants: 1, expectedCost: 1 }, + { variants: 2, expectedCost: 2 }, + { variants: 3, expectedCost: 3 }, + { variants: 4, expectedCost: 4 } + ]; + + testCases.forEach(({ variants, expectedCost }) => { + expect(variants).toBe(expectedCost); + }); + }); + }); + + describe('Confirmation Message Logic', () => { + it('should use different confirmation message for multiple variants', () => { + const variationsCount = 2; + const shouldShowMultiVariantMessage = variationsCount > 1; + + expect(shouldShowMultiVariantMessage).toBe(true); + }); + + it('should use default message for single variant', () => { + const variationsCount = 1; + const shouldShowMultiVariantMessage = variationsCount > 1; + + expect(shouldShowMultiVariantMessage).toBe(false); + }); + }); +}); From 9a64d87fa8bffe4dd6130a7610f53ecf4fe1ea46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 04:06:17 +0000 Subject: [PATCH 04/12] Show variations UI always & add confirmation for untitled sessions --- .../copilotCloudSessionsProvider.ts | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index fcc4485a05..a7f40fc328 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -116,6 +116,17 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch return { optionGroups: [] }; } + // Always provide variations option group if we have a valid repo + const variationItems: vscode.ChatSessionProviderOptionItem[] = [ + { id: '1', name: vscode.l10n.t('1 variant') }, + { id: '2', name: vscode.l10n.t('2 variants') }, + { id: '3', name: vscode.l10n.t('3 variants') }, + { id: '4', name: vscode.l10n.t('4 variants') } + ]; + + const optionGroups: vscode.ChatSessionProviderOptionGroup[] = []; + + // Try to fetch custom agents, but don't fail if this errors try { const customAgents = await this._octoKitService.getCustomAgents(repoId.org, repoId.repo); const agentItems: vscode.ChatSessionProviderOptionItem[] = [ @@ -126,33 +137,26 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch })) ]; - const variationItems: vscode.ChatSessionProviderOptionItem[] = [ - { id: '1', name: vscode.l10n.t('1 variant') }, - { id: '2', name: vscode.l10n.t('2 variants') }, - { id: '3', name: vscode.l10n.t('3 variants') }, - { id: '4', name: vscode.l10n.t('4 variants') } - ]; - - return { - optionGroups: [ - { - id: AGENTS_OPTION_GROUP_ID, - name: vscode.l10n.t('Custom Agents'), - description: vscode.l10n.t('Select which agent to use'), - items: agentItems, - }, - { - id: VARIATIONS_OPTION_GROUP_ID, - name: vscode.l10n.t('PR Variations'), - description: vscode.l10n.t('Number of PR variants to generate'), - items: variationItems, - } - ] - }; + optionGroups.push({ + id: AGENTS_OPTION_GROUP_ID, + name: vscode.l10n.t('Custom Agents'), + description: vscode.l10n.t('Select which agent to use'), + items: agentItems, + }); } catch (error) { this.logService.error(`Error fetching custom agents: ${error}`); - return { optionGroups: [] }; + // Continue without custom agents option group } + + // Always add variations option group + optionGroups.push({ + id: VARIATIONS_OPTION_GROUP_ID, + name: vscode.l10n.t('PR Variations'), + description: vscode.l10n.t('Number of PR variants to generate'), + items: variationItems, + }); + + return { optionGroups }; } provideHandleOptionsChange(resource: Uri, updates: ReadonlyArray, token: vscode.CancellationToken): void { @@ -592,14 +596,25 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch const selectedAgent = this.sessionAgentMap.get(context.chatSessionContext.chatSessionItem.resource); const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource) || DEFAULT_VARIATIONS_COUNT, 10); - // For untitled sessions with multiple variants, delegate to createDelegatedChatSession + // For untitled sessions with multiple variants, show confirmation first if (variationsCount > 1) { - await this.createDelegatedChatSession({ - prompt: context.chatSummary?.prompt ?? request.prompt, - history: context.chatSummary?.history, - references: request.references, - variationsCount - }, stream, token); + // Show confirmation modal with premium request cost warning + const confirmationDetails = vscode.l10n.t('The agent will work asynchronously to create {0} pull request variants with your requested changes. This will use {0} premium requests.', variationsCount); + + stream.confirmation( + vscode.l10n.t('Delegate to cloud agent'), + confirmationDetails, + { + step: 'create', + metadata: { + prompt: context.chatSummary?.prompt ?? request.prompt, + history: context.chatSummary?.history, + references: request.references, + variationsCount + } + }, + ['Delegate', 'Cancel'] + ); return {}; } From ad310c26e55bd5b0a395d7cb69cb1b0cb77b9851 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 04:25:14 +0000 Subject: [PATCH 05/12] Fix session switching for multiple PR variants from untitled sessions Co-authored-by: pierceboggan <1091304+pierceboggan@users.noreply.github.com> --- .../vscode-node/copilotCloudSessionsProvider.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index a7f40fc328..aed4b15d7a 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -24,6 +24,7 @@ interface CreatePromptMetadata { history?: string; references?: readonly vscode.ChatPromptReference[]; variationsCount?: number; + originalSessionItem?: vscode.ChatSessionItem; } interface UncommittedChangesMetadata { @@ -515,7 +516,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } async createDelegatedChatSession(metadata: CreatePromptMetadata, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise { - const { prompt, history, references, variationsCount = 1 } = metadata; + const { prompt, history, references, variationsCount = 1, originalSessionItem } = metadata; // Notify user about creating multiple variants if (variationsCount > 1) { @@ -563,6 +564,17 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch linkTag: `#${pullRequest.number}` }; prInfos.push(prInfo); + + // For the first PR in a multi-variant untitled session, fire the commit event to switch the session + if (i === 0 && variationsCount > 1 && originalSessionItem) { + this._onDidCommitChatSessionItem.fire({ + original: originalSessionItem, + modified: { + resource: vscode.Uri.from({ scheme: CopilotChatSessionsProvider.TYPE, path: '/' + number }), + label: `Pull Request ${number}` + } + }); + } } // Show summary message @@ -610,7 +622,8 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch prompt: context.chatSummary?.prompt ?? request.prompt, history: context.chatSummary?.history, references: request.references, - variationsCount + variationsCount, + originalSessionItem: context.chatSessionContext.chatSessionItem } }, ['Delegate', 'Cancel'] From a52bf2244316d7e5372a6645fc3fc3ee3ae4d8d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 04:51:13 +0000 Subject: [PATCH 06/12] Fix session switching timing - fire commit event after all PRs created --- .../copilotCloudSessionsProvider.ts | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index aed4b15d7a..0a8b6d085c 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -564,17 +564,18 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch linkTag: `#${pullRequest.number}` }; prInfos.push(prInfo); + } - // For the first PR in a multi-variant untitled session, fire the commit event to switch the session - if (i === 0 && variationsCount > 1 && originalSessionItem) { - this._onDidCommitChatSessionItem.fire({ - original: originalSessionItem, - modified: { - resource: vscode.Uri.from({ scheme: CopilotChatSessionsProvider.TYPE, path: '/' + number }), - label: `Pull Request ${number}` - } - }); - } + // After creating all PRs, fire the commit event to switch from untitled to first PR session + if (variationsCount > 1 && originalSessionItem && prInfos.length > 0) { + const firstPrNumber = parseInt(prInfos[0].linkTag.substring(1), 10); + this._onDidCommitChatSessionItem.fire({ + original: originalSessionItem, + modified: { + resource: vscode.Uri.from({ scheme: CopilotChatSessionsProvider.TYPE, path: '/' + firstPrNumber }), + label: `Pull Request ${firstPrNumber}` + } + }); } // Show summary message @@ -666,6 +667,30 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch return {}; } + // Check if user wants to create multiple variants from this session + const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource) || DEFAULT_VARIATIONS_COUNT, 10); + + if (variationsCount > 1) { + // Show confirmation for creating multiple variants from existing session + const confirmationDetails = vscode.l10n.t('The agent will work asynchronously to create {0} pull request variants with your requested changes. This will use {0} premium requests.', variationsCount); + + stream.confirmation( + vscode.l10n.t('Create PR variants'), + confirmationDetails, + { + step: 'create', + metadata: { + prompt: context.chatSummary?.prompt ?? request.prompt, + history: context.chatSummary?.history, + references: request.references, + variationsCount, + } + }, + ['Create', 'Cancel'] + ); + return {}; + } + stream.progress(vscode.l10n.t('Preparing')); const session = SessionIdForPr.parse(context.chatSessionContext.chatSessionItem.resource); let prNumber = session?.prNumber; From 3d312bdb2df778c33ca99f978113e028b4e50c53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:06:03 +0000 Subject: [PATCH 07/12] Don't auto-open PR for multiple variants to avoid interrupting creation Co-authored-by: pierceboggan <1091304+pierceboggan@users.noreply.github.com> --- .../vscode-node/copilotCloudSessionsProvider.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index 0a8b6d085c..6fbdb3746d 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -589,14 +589,15 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch stream.warning(vscode.l10n.t('Created {0} of {1} requested variants.', prInfos.length, variationsCount)); } - // Open the first PR if any were created - if (prInfos.length > 0) { + // Open the first PR - but only for single variant or after all variants created + // For multiple variants, don't auto-open to avoid interrupting the user experience + if (prInfos.length > 0 && variationsCount === 1) { const firstPrNumber = parseInt(prInfos[0].linkTag.substring(1), 10); await vscode.commands.executeCommand('vscode.open', vscode.Uri.from({ scheme: CopilotChatSessionsProvider.TYPE, path: '/' + firstPrNumber })); return prInfos[0]; } - return undefined; + return prInfos.length > 0 ? prInfos[0] : undefined; } private async chatParticipantImpl(request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) { From c945543280d1af43fc3b4e97f5ad4be3ae8e0d85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:29:19 +0000 Subject: [PATCH 08/12] Add debug logging to diagnose variants not being created Co-authored-by: pierceboggan <1091304+pierceboggan@users.noreply.github.com> --- .../vscode-node/copilotCloudSessionsProvider.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index 6fbdb3746d..645fb85fa8 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -161,6 +161,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } provideHandleOptionsChange(resource: Uri, updates: ReadonlyArray, token: vscode.CancellationToken): void { + this.logService.info(`[VARIANTS DEBUG] provideHandleOptionsChange called - Resource: ${resource}, Updates: ${JSON.stringify(updates)}`); for (const update of updates) { if (update.optionId === AGENTS_OPTION_GROUP_ID) { if (update.value) { @@ -173,7 +174,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } else if (update.optionId === VARIATIONS_OPTION_GROUP_ID) { if (update.value) { this.sessionVariationsMap.set(resource, update.value); - this.logService.info(`Variations changed for session ${resource}: ${update.value}`); + this.logService.info(`[VARIANTS DEBUG] Variations changed for session ${resource}: ${update.value}`); } else { this.sessionVariationsMap.delete(resource); this.logService.info(`Variations cleared for session ${resource}`); @@ -518,8 +519,11 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch async createDelegatedChatSession(metadata: CreatePromptMetadata, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise { const { prompt, history, references, variationsCount = 1, originalSessionItem } = metadata; + this.logService.info(`[VARIANTS DEBUG] createDelegatedChatSession called - variationsCount: ${variationsCount}`); + // Notify user about creating multiple variants if (variationsCount > 1) { + this.logService.info(`[VARIANTS DEBUG] Creating ${variationsCount} PR variants...`); stream.markdown(vscode.l10n.t('Creating {0} PR variants...', variationsCount)); stream.markdown('\n\n'); } @@ -610,8 +614,12 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch const selectedAgent = this.sessionAgentMap.get(context.chatSessionContext.chatSessionItem.resource); const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource) || DEFAULT_VARIATIONS_COUNT, 10); + // Debug logging + this.logService.info(`[VARIANTS DEBUG] Untitled session - Resource: ${context.chatSessionContext.chatSessionItem.resource}, Variations from map: ${this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource)}, Parsed count: ${variationsCount}`); + // For untitled sessions with multiple variants, show confirmation first if (variationsCount > 1) { + this.logService.info(`[VARIANTS DEBUG] Showing confirmation for ${variationsCount} variants`); // Show confirmation modal with premium request cost warning const confirmationDetails = vscode.l10n.t('The agent will work asynchronously to create {0} pull request variants with your requested changes. This will use {0} premium requests.', variationsCount); From 37ca431fa15f063b3c8810d27edd18f06511ffef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:44:33 +0000 Subject: [PATCH 09/12] Add entry point debug logging --- .../chatSessions/vscode-node/copilotCloudSessionsProvider.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index 645fb85fa8..cf822b88de 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -605,6 +605,8 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } private async chatParticipantImpl(request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) { + this.logService.info(`[VARIANTS DEBUG] chatParticipantImpl called - hasSessionContext: ${!!context.chatSessionContext}, isUntitled: ${context.chatSessionContext?.isUntitled}, hasConfirmation: ${!!(request.acceptedConfirmationData || request.rejectedConfirmationData)}`); + if (request.acceptedConfirmationData || request.rejectedConfirmationData) { return await this.handleConfirmationData(request, stream, token); } From e659ba807d4e2e8fe7231fce03256671ac3b2a58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 05:54:41 +0000 Subject: [PATCH 10/12] Fix createEmptySession overwriting variations map value --- .../vscode-node/copilotCloudSessionsProvider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index cf822b88de..6a2afd2b53 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -393,11 +393,9 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch ? { options: { [AGENTS_OPTION_GROUP_ID]: - this.sessionAgentMap.get(resource) - ?? (this.sessionAgentMap.set(resource, DEFAULT_AGENT_ID), DEFAULT_AGENT_ID), + this.sessionAgentMap.get(resource) || DEFAULT_AGENT_ID, [VARIATIONS_OPTION_GROUP_ID]: - this.sessionVariationsMap.get(resource) - ?? (this.sessionVariationsMap.set(resource, DEFAULT_VARIATIONS_COUNT), DEFAULT_VARIATIONS_COUNT) + this.sessionVariationsMap.get(resource) || DEFAULT_VARIATIONS_COUNT } } : {}), From 036cd37865946514134f9aa6b754033f3f6a31ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 06:18:21 +0000 Subject: [PATCH 11/12] Add URI comparison debugging --- .../vscode-node/copilotCloudSessionsProvider.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index 6a2afd2b53..a244b7b35d 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -387,6 +387,9 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch private createEmptySession(resource: Uri): vscode.ChatSession { const sessionId = resource ? resource.path.slice(1) : undefined; + const variationsValue = this.sessionVariationsMap.get(resource); + this.logService.info(`[VARIANTS DEBUG] createEmptySession called - Resource: ${resource}, sessionId: ${sessionId}, variationsValue: ${variationsValue}`); + return { history: [], ...(sessionId && sessionId.startsWith('untitled-') @@ -395,7 +398,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch [AGENTS_OPTION_GROUP_ID]: this.sessionAgentMap.get(resource) || DEFAULT_AGENT_ID, [VARIATIONS_OPTION_GROUP_ID]: - this.sessionVariationsMap.get(resource) || DEFAULT_VARIATIONS_COUNT + variationsValue || DEFAULT_VARIATIONS_COUNT } } : {}), @@ -616,6 +619,8 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch // Debug logging this.logService.info(`[VARIANTS DEBUG] Untitled session - Resource: ${context.chatSessionContext.chatSessionItem.resource}, Variations from map: ${this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource)}, Parsed count: ${variationsCount}`); + this.logService.info(`[VARIANTS DEBUG] Map contents: ${JSON.stringify(Array.from(this.sessionVariationsMap.entries()).map(([k, v]) => ({ key: k.toString(), value: v })))}`); + this.logService.info(`[VARIANTS DEBUG] Resource toString: ${context.chatSessionContext.chatSessionItem.resource.toString()}, path: ${context.chatSessionContext.chatSessionItem.resource.path}`); // For untitled sessions with multiple variants, show confirmation first if (variationsCount > 1) { From bcbe71a8e71522e39008b4e7a4747bc407b2e13a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 06:37:36 +0000 Subject: [PATCH 12/12] Changes before error encountered Co-authored-by: pierceboggan <1091304+pierceboggan@users.noreply.github.com> --- .../copilotCloudSessionsProvider.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts index a244b7b35d..db712822cd 100644 --- a/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts +++ b/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts @@ -91,7 +91,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch private chatSessions: Map = new Map(); private chatSessionItemsPromise: Promise | undefined; private sessionAgentMap: Map = new Map(); - private sessionVariationsMap: Map = new Map(); + private sessionVariationsMap: Map = new Map(); // Use string keys (Uri.toString()) for proper equality comparison public chatParticipant = vscode.chat.createChatParticipant(CopilotChatSessionsProvider.TYPE, async (request, context, stream, token) => await this.chatParticipantImpl(request, context, stream, token) ); @@ -173,10 +173,10 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } } else if (update.optionId === VARIATIONS_OPTION_GROUP_ID) { if (update.value) { - this.sessionVariationsMap.set(resource, update.value); + this.sessionVariationsMap.set(resource.toString(), update.value); this.logService.info(`[VARIANTS DEBUG] Variations changed for session ${resource}: ${update.value}`); } else { - this.sessionVariationsMap.delete(resource); + this.sessionVariationsMap.delete(resource.toString()); this.logService.info(`Variations cleared for session ${resource}`); } } @@ -326,7 +326,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch // Query for the sub-agent that the remote reports for this session || undefined; /* TODO: Needs API to support this. */ - const selectedVariations = this.sessionVariationsMap.get(resource) || DEFAULT_VARIATIONS_COUNT; + const selectedVariations = this.sessionVariationsMap.get(resource.toString()) || DEFAULT_VARIATIONS_COUNT; return { history, @@ -387,7 +387,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch private createEmptySession(resource: Uri): vscode.ChatSession { const sessionId = resource ? resource.path.slice(1) : undefined; - const variationsValue = this.sessionVariationsMap.get(resource); + const variationsValue = this.sessionVariationsMap.get(resource.toString()); this.logService.info(`[VARIANTS DEBUG] createEmptySession called - Resource: ${resource}, sessionId: ${sessionId}, variationsValue: ${variationsValue}`); return { @@ -615,11 +615,11 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch if (context.chatSessionContext?.isUntitled) { /* Generate new cloud agent session from an 'untitled' session */ const selectedAgent = this.sessionAgentMap.get(context.chatSessionContext.chatSessionItem.resource); - const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource) || DEFAULT_VARIATIONS_COUNT, 10); + const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource.toString()) || DEFAULT_VARIATIONS_COUNT, 10); // Debug logging - this.logService.info(`[VARIANTS DEBUG] Untitled session - Resource: ${context.chatSessionContext.chatSessionItem.resource}, Variations from map: ${this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource)}, Parsed count: ${variationsCount}`); - this.logService.info(`[VARIANTS DEBUG] Map contents: ${JSON.stringify(Array.from(this.sessionVariationsMap.entries()).map(([k, v]) => ({ key: k.toString(), value: v })))}`); + this.logService.info(`[VARIANTS DEBUG] Untitled session - Resource: ${context.chatSessionContext.chatSessionItem.resource}, Variations from map: ${this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource.toString())}, Parsed count: ${variationsCount}`); + this.logService.info(`[VARIANTS DEBUG] Map contents: ${JSON.stringify(Array.from(this.sessionVariationsMap.entries()).map(([k, v]) => ({ key: k, value: v })))}`); this.logService.info(`[VARIANTS DEBUG] Resource toString: ${context.chatSessionContext.chatSessionItem.resource.toString()}, path: ${context.chatSessionContext.chatSessionItem.resource.path}`); // For untitled sessions with multiple variants, show confirmation first @@ -682,7 +682,7 @@ export class CopilotChatSessionsProvider extends Disposable implements vscode.Ch } // Check if user wants to create multiple variants from this session - const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource) || DEFAULT_VARIATIONS_COUNT, 10); + const variationsCount = parseInt(this.sessionVariationsMap.get(context.chatSessionContext.chatSessionItem.resource.toString()) || DEFAULT_VARIATIONS_COUNT, 10); if (variationsCount > 1) { // Show confirmation for creating multiple variants from existing session