diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index f0fad8b953..85f68a5569 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -126,6 +126,11 @@ declare module 'vscode' { */ readonly hasHooksEnabled: boolean; + /** + * Whether this request was submitted through Agents Voice Mode. + */ + readonly isVoiceModeInput?: boolean; + /** * When true, this request was initiated by the system (e.g. a terminal * command completion notification) rather than by the user typing a @@ -135,6 +140,41 @@ declare module 'vscode' { readonly isSystemInitiated?: boolean; } + /** + * A transient progress update intended for Voice Mode narration. + */ + export type ChatResponseVoiceProgressStage = 'investigating' | 'planning' | 'editing' | 'validating' | 'recovering'; + + export class ChatResponseVoiceProgressPart { + /** + * A stable identifier used to de-duplicate the progress update. + */ + readonly id: ChatResponseVoiceProgressStage; + /** + * The concise text to narrate. + */ + readonly value: string; + /** + * Creates a Voice Mode progress update. + * @param id A stable identifier used to de-duplicate the update. + * @param value The concise text to narrate. + */ + constructor(id: ChatResponseVoiceProgressStage, value: string); + } + + export interface ExtendedChatResponseParts { + ChatResponseVoiceProgressPart: ChatResponseVoiceProgressPart; + } + + export interface ChatResponseStream { + /** + * Reports transient progress for Voice Mode narration. + * @param id A stable identifier used to de-duplicate the update. + * @param value The concise text to narrate. + */ + voiceProgress(id: ChatResponseVoiceProgressStage, value: string): void; + } + export enum ChatRequestEditedFileEventKind { Keep = 1, Undo = 2, diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index 1003937f26..844de8f23d 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -717,7 +717,7 @@ declare module 'vscode' { readonly promo?: { readonly id: string; readonly discountPercent: number; - readonly endsAt: string; + readonly endsAt?: string; readonly message: string; }; readonly maxInputTokens?: number; diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 9807c8a53e..85fbd392c2 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -495,7 +495,7 @@ export class FolderRepositoryManager extends Disposable { } const activeRemotes = await this.getActiveRemotes(); - const isAuthenticated = this.checkForAuthMatch(activeRemotes); + let isAuthenticated = this.checkForAuthMatch(activeRemotes); if (this.credentialStore.isAnyAuthenticated() && (activeRemotes.length === 0)) { const allUnknownRemotes = await this.computeAllUnknownRemotes(); const areAllNeverGitHub = allUnknownRemotes.every(remote => GitHubManager.isNeverGitHub(vscode.Uri.parse(remote.normalizedHost).authority)); @@ -504,6 +504,9 @@ export class FolderRepositoryManager extends Disposable { this.state = ReposManagerState.RepositoriesLoaded; return true; } + if (allUnknownRemotes.length > 0) { + isAuthenticated = false; + } } const repositories: GitHubRepository[] = []; const resolveRemotePromises: Promise[] = []; diff --git a/src/test/github/folderRepositoryManager.test.ts b/src/test/github/folderRepositoryManager.test.ts index 6e4b1b9ef4..e77be9e2a8 100644 --- a/src/test/github/folderRepositoryManager.test.ts +++ b/src/test/github/folderRepositoryManager.test.ts @@ -6,7 +6,7 @@ import { default as assert } from 'assert'; import { createSandbox, SinonSandbox } from 'sinon'; -import { FolderRepositoryManager, titleAndBodyFrom } from '../../github/folderRepositoryManager'; +import { FolderRepositoryManager, ReposManagerState, titleAndBodyFrom } from '../../github/folderRepositoryManager'; import { MockRepository } from '../mocks/mockRepository'; import { MockTelemetry } from '../mocks/mockTelemetry'; import { MockCommandRegistry } from '../mocks/mockCommandRegistry'; @@ -20,7 +20,7 @@ import { GitApiImpl } from '../../api/api1'; import { CredentialStore } from '../../github/credentials'; import { MockExtensionContext } from '../mocks/mockExtensionContext'; import { Uri } from 'vscode'; -import { GitHubServerType } from '../../common/authentication'; +import { AuthProvider, GitHubServerType } from '../../common/authentication'; import { CreatePullRequestHelper } from '../../view/createPullRequestHelper'; import { RepositoriesManager } from '../../github/repositoriesManager'; import { MockThemeWatcher } from '../mocks/mockThemeWatcher'; @@ -53,6 +53,20 @@ describe('PullRequestManager', function () { sinon.restore(); }); + describe('updateRepositories', function () { + it('requires authentication for an unknown remote with an existing GitHub.com session', async function () { + const url = 'ssh://git@ghe.example.com/org/repo.git'; + const remote = new Remote('origin', url, new Protocol(url)); + sinon.stub(manager, 'computeAllGitHubRemotes').resolves([]); + sinon.stub(manager, 'computeAllUnknownRemotes').resolves([remote]); + sinon.stub(manager.credentialStore, 'isAuthenticated').callsFake(provider => provider === AuthProvider.github); + + await manager.updateRepositories(); + + assert.strictEqual(manager.state, ReposManagerState.NeedsAuthentication); + }); + }); + describe('activePullRequest', function () { it('gets and sets the active pull request', function () { assert.strictEqual(manager.activePullRequest, undefined);