From c89822681aa2b6dd95f6ee4e329fed7dcc23d333 Mon Sep 17 00:00:00 2001 From: Vijay Upadya <41652029+vijayupadya@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:12:28 -0700 Subject: [PATCH] agentHost: Reject unavailable session catalogs (#331510) Prevent an unavailable provider catalog from being accepted as an authoritative partial session list. Retry migration before listing and preserve the failed state so the client cache is not reconciled as session deletion. Fixes #331452. (cherry picked from commit dcc387ba7e912c3d25e43cc5b064b9fecc3427c2) Co-authored-by: Sandeep Somavarapu Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../platform/agentHost/node/agentService.ts | 10 ++++++++-- .../agentHost/test/node/agentService.test.ts | 20 +++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index bab9b35df50..11f8d6b7e91 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -1306,12 +1306,18 @@ export class AgentService extends Disposable implements IAgentService { private async _awaitInitialProviderMigration(): Promise { const providers = [...this._providers.values()]; const results = await Promise.allSettled(providers.map(provider => this._initialProviderMigrations.get(provider.id) ?? Promise.resolve())); + const retries: Promise[] = []; for (let index = 0; index < results.length; index++) { const result = results[index]; if (result.status === 'rejected') { - this._logService.warn(`[AgentService] initial provider catalogs: provider ${providers[index].id} failed and will be retried on the next signal`, result.reason); + const provider = providers[index]; + this._logService.warn(`[AgentService] initial provider catalog for ${provider.id} was unavailable; retrying before listing sessions`, result.reason); + const retry = this._ensureLegacyChatsMigrated(provider, true); + this._initialProviderMigrations.set(provider.id, retry); + retries.push(retry); } } + await Promise.all(retries); } /** @@ -1474,7 +1480,7 @@ export class AgentService extends Disposable implements IAgentService { } const sessions = await this._enumerateLegacyProviderSessions(provider); if (sessions === undefined) { - return; + throw new Error(`Provider ${provider.id} cannot enumerate its native session catalog yet`); } const existing = new Map((await this._listRegisteredSessions()).map(session => [session.session.toString(), session.external])); const migrationLimiter = new Limiter(4); diff --git a/src/vs/platform/agentHost/test/node/agentService.test.ts b/src/vs/platform/agentHost/test/node/agentService.test.ts index 996217099ca..c425b3ed923 100644 --- a/src/vs/platform/agentHost/test/node/agentService.test.ts +++ b/src/vs/platform/agentHost/test/node/agentService.test.ts @@ -3965,14 +3965,16 @@ suite('AgentService (node dispatcher)', () => { assert.strictEqual(agent.listExternalChatsCalls, 1); }); - test('a discovery signal does not bypass completed legacy migration semantics', async () => { + test('listSessions rejects an unavailable migration catalog and retries it on the next call', async () => { class NotYetMigratableAgent extends MockAgent { migrationCalls = 0; enumerable = false; } const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService())); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); const agent = disposables.add(new NotYetMigratableAgent('copilot')); const legacy = AgentSession.uri('copilot', 'legacy-migration-not-ready'); + (agent as unknown as { _sessions: Map })._sessions.set(AgentSession.id(legacy), legacy); (agent as unknown as { listChatsToMigrate: () => Promise }).listChatsToMigrate = async () => { agent.migrationCalls++; return agent.enumerable @@ -3980,18 +3982,20 @@ suite('AgentService (node dispatcher)', () => { : undefined; }; svc.registerProvider(agent); - await svc.listSessions(); + await assert.rejects(svc.listSessions(), /cannot enumerate its native session catalog yet/); + const callsAfterFailure = agent.migrationCalls; agent.enumerable = true; - agent.fireDiscoveredChats([]); await timeout(0); - + const listed = await svc.listSessions(); assert.deepStrictEqual({ - migrationCalls: agent.migrationCalls, - registered: (await svc.getRegisteredSessions()).map(session => session.toString()), + retriedBeforeFailure: callsAfterFailure > 1, + retriedAfterFailure: agent.migrationCalls > callsAfterFailure, + listed: listed.map(session => session.session.toString()), }, { - migrationCalls: 1, - registered: [], + retriedBeforeFailure: true, + retriedAfterFailure: true, + listed: [legacy.toString()], }); });