From 524be16769bb06639979095bccc12c7af47d638a Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:45:30 -0700 Subject: [PATCH 1/2] Restore and polish PR extension install logic - Restore PR extension check and install logic from commit 1cafcacb37 - Renamed installPullRequestExtension to ensurePullRequestExtensionInstalled for clarity - Add install button in chat/multiDiff/context menu when PR extension is not installed - Show reload window prompt after successful installation to activate extension - Add github.copilot.cloud.sessions.installPullRequestExtension command - Improve error handling with user-friendly messages --- package.json | 12 ++++ .../chatSessions/vscode-node/chatSessions.ts | 59 ++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a08187f994..53276d58cb 100644 --- a/package.json +++ b/package.json @@ -2250,6 +2250,11 @@ { "command": "github.copilot.cloud.sessions.proxy.closeChatSessionPullRequest", "title": "%github.copilot.command.closeChatSessionPullRequest.title%" + }, + { + "command": "github.copilot.cloud.sessions.installPullRequestExtension", + "title": "Install GitHub Pull Requests Extension", + "icon": "$(cloud-download)" } ], "configuration": [ @@ -3831,6 +3836,13 @@ "when": "chatSessionType == copilot-cloud-agent", "group": "context" } + ], + "chat/multiDiff/context": [ + { + "command": "github.copilot.cloud.sessions.installPullRequestExtension", + "when": "chatSessionType == copilot-cloud-agent && !github.copilot.prExtensionInstalled", + "group": "inline" + } ] }, "icons": { diff --git a/src/extension/chatSessions/vscode-node/chatSessions.ts b/src/extension/chatSessions/vscode-node/chatSessions.ts index f6341b85d1..0fd00ffe08 100644 --- a/src/extension/chatSessions/vscode-node/chatSessions.ts +++ b/src/extension/chatSessions/vscode-node/chatSessions.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService'; +import { IEnvService } from '../../../platform/env/common/envService'; import { IOctoKitService } from '../../../platform/github/common/githubService'; import { OctoKitService } from '../../../platform/github/common/octoKitServiceImpl'; import { ILogService } from '../../../platform/log/common/logService'; @@ -19,7 +20,9 @@ import { CopilotCLIAgentManager } from '../../agents/copilotcli/node/copilotcliA import { CopilotCLISessionService, ICopilotCLISessionService } from '../../agents/copilotcli/node/copilotcliSessionService'; import { ILanguageModelServer, LanguageModelServer } from '../../agents/node/langModelServer'; import { IExtensionContribution } from '../../common/contributions'; +import { prExtensionInstalledContextKey } from '../../contextKeys/vscode-node/contextKeys.contribution'; import { ChatSummarizerProvider } from '../../prompt/node/summarizer'; +import { GHPR_EXTENSION_ID } from '../vscode/chatSessionsUriHandler'; import { ClaudeChatSessionContentProvider } from './claudeChatSessionContentProvider'; import { ClaudeChatSessionItemProvider } from './claudeChatSessionItemProvider'; import { ClaudeChatSessionParticipant } from './claudeChatSessionParticipant'; @@ -54,6 +57,7 @@ export class ChatSessionsContrib extends Disposable implements IExtensionContrib constructor( @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IEnvService private readonly envService: IEnvService, @ILogService private readonly logService: ILogService, @IOctoKitService private readonly octoKitService: IOctoKitService, ) { @@ -131,6 +135,8 @@ export class ChatSessionsContrib extends Disposable implements IExtensionContrib const enabled = this.configurationService.getConfig(ConfigKey.Internal.CopilotCloudEnabled); if (enabled && !this.copilotCloudRegistrations) { + vscode.commands.executeCommand('setContext', prExtensionInstalledContextKey, this.isPullRequestExtensionInstalled()); + // Register the Copilot Cloud chat participant this.copilotCloudRegistrations = new DisposableStore(); const copilotSessionsProvider = this.copilotCloudRegistrations.add( @@ -159,7 +165,7 @@ export class ChatSessionsContrib extends Disposable implements IExtensionContrib ); this.copilotCloudRegistrations.add( vscode.commands.registerCommand(CLOSE_SESSION_PR_CMD, async (ctx: CrossChatSessionWithPR) => { - // await this.installPullRequestExtension(); + await this.ensurePullRequestExtensionInstalled(); try { const success = await this.octoKitService.closePullRequest( ctx.pullRequestDetails.repository.owner.login, @@ -174,6 +180,16 @@ export class ChatSessionsContrib extends Disposable implements IExtensionContrib } }) ); + this.copilotCloudRegistrations.add( + vscode.commands.registerCommand('github.copilot.cloud.sessions.installPullRequestExtension', async () => { + try { + await this.ensurePullRequestExtensionInstalled(); + } catch (e) { + this.logService.error(`Failed to install GitHub Pull Request extension: ${e}`); + vscode.window.showErrorMessage(vscode.l10n.t('Failed to install GitHub Pull Request extension. Please install it manually from the Extensions view.')); + } + }) + ); return copilotSessionsProvider; } else if (!enabled && this.copilotCloudRegistrations) { @@ -181,4 +197,45 @@ export class ChatSessionsContrib extends Disposable implements IExtensionContrib this.copilotCloudRegistrations = undefined; } } + + private isPullRequestExtensionInstalled(): boolean { + const extension = vscode.extensions.getExtension(GHPR_EXTENSION_ID); + return extension !== undefined; + } + + private async ensurePullRequestExtensionInstalled(): Promise { + if (this.isPullRequestExtensionInstalled()) { + return; + } + + const isInsiders = this.envService.getEditorInfo().version.includes('insider'); + const installOptions = { enable: true, installPreReleaseVersion: isInsiders }; + + await vscode.commands.executeCommand('workbench.extensions.installExtension', GHPR_EXTENSION_ID, installOptions); + + const maxWaitTime = 10_000; // 10 seconds + const pollInterval = 100; // 100ms + let elapsed = 0; + + while (elapsed < maxWaitTime) { + if (this.isPullRequestExtensionInstalled()) { + await vscode.commands.executeCommand('setContext', prExtensionInstalledContextKey, true); + + const reloadAction = vscode.l10n.t('Reload Window'); + const result = await vscode.window.showInformationMessage( + vscode.l10n.t('GitHub Pull Request extension installed successfully. Reload VS Code to activate it.'), + reloadAction + ); + + if (result === reloadAction) { + await vscode.commands.executeCommand('workbench.action.reloadWindow'); + } + return; + } + await new Promise(resolve => setTimeout(resolve, pollInterval)); + elapsed += pollInterval; + } + + throw new Error('GitHub Pull Request extension installation timed out.'); + } } From 1276209c785dae5fd1ef187d5af87e8ceb8b6c81 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:51:41 -0700 Subject: [PATCH 2/2] polish --- package.json | 2 +- src/extension/chatSessions/vscode-node/chatSessions.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 53276d58cb..23e87792f4 100644 --- a/package.json +++ b/package.json @@ -2254,7 +2254,7 @@ { "command": "github.copilot.cloud.sessions.installPullRequestExtension", "title": "Install GitHub Pull Requests Extension", - "icon": "$(cloud-download)" + "icon": "$(extensions)" } ], "configuration": [ diff --git a/src/extension/chatSessions/vscode-node/chatSessions.ts b/src/extension/chatSessions/vscode-node/chatSessions.ts index 0fd00ffe08..1bffb0e12d 100644 --- a/src/extension/chatSessions/vscode-node/chatSessions.ts +++ b/src/extension/chatSessions/vscode-node/chatSessions.ts @@ -165,7 +165,6 @@ export class ChatSessionsContrib extends Disposable implements IExtensionContrib ); this.copilotCloudRegistrations.add( vscode.commands.registerCommand(CLOSE_SESSION_PR_CMD, async (ctx: CrossChatSessionWithPR) => { - await this.ensurePullRequestExtensionInstalled(); try { const success = await this.octoKitService.closePullRequest( ctx.pullRequestDetails.repository.owner.login,