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 dcc387ba7e)

Co-authored-by: Sandeep Somavarapu <sasomava@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Vijay Upadya
2026-08-18 19:12:28 +00:00
committed by GitHub
co-authored by Sandeep Somavarapu Copilot
parent 0d07085cd0
commit c89822681a
2 changed files with 20 additions and 10 deletions
@@ -1306,12 +1306,18 @@ export class AgentService extends Disposable implements IAgentService {
private async _awaitInitialProviderMigration(): Promise<void> {
const providers = [...this._providers.values()];
const results = await Promise.allSettled(providers.map(provider => this._initialProviderMigrations.get(provider.id) ?? Promise.resolve()));
const retries: Promise<void>[] = [];
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<IRegisteredSession | undefined>(4);
@@ -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<string, URI> })._sessions.set(AgentSession.id(legacy), legacy);
(agent as unknown as { listChatsToMigrate: () => Promise<readonly IAgentChatMetadata[] | undefined> }).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()],
});
});