Skip to content

Commit 87d3c01

Browse files
benibenjCopilot
andcommitted
Avoid invalidation for idle provisional sessions
Keep expensive session-list computations coalesced when invisible provisional composers are created or disposed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7eeca34 commit 87d3c01

2 files changed

Lines changed: 55 additions & 4 deletions

File tree

src/vs/platform/agentHost/node/agentService.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,6 +2765,7 @@ export class AgentService extends Disposable implements IAgentService {
27652765
this._createProviderSession(provider, config, deferWorktreeCreation),
27662766
]);
27672767
const session = created.session;
2768+
const isIdleProvisional = created.provisional === true && !config?.importConversation;
27682769
this._logService.trace(`[AgentService] createSession: initialization complete`);
27692770
const creationReference = readSessionCreationReference(config?._meta);
27702771
if (creationReference && !isEphemeral) {
@@ -2783,7 +2784,9 @@ export class AgentService extends Disposable implements IAgentService {
27832784
() => this._sessionRegistry.tombstone(session),
27842785
`tombstoning ephemeral session ${session.toString()}`,
27852786
);
2786-
this._invalidateSessionList();
2787+
if (!isIdleProvisional) {
2788+
this._invalidateSessionList();
2789+
}
27872790
} catch (err) {
27882791
await this._rollbackProviderSession(provider, session);
27892792
throw err;
@@ -2795,7 +2798,9 @@ export class AgentService extends Disposable implements IAgentService {
27952798
() => this._sessionRegistry.register(session, { provider: provider.id, startTime: registeredAt, modifiedTime: registeredAt, source: 'explicit' }, { checkTombstone: false }),
27962799
`registration for ${session.toString()}`,
27972800
);
2798-
this._invalidateSessionList();
2801+
if (!isIdleProvisional) {
2802+
this._invalidateSessionList();
2803+
}
27992804
} catch (err) {
28002805
await this._rollbackProviderSession(provider, session);
28012806
throw err;
@@ -2835,7 +2840,7 @@ export class AgentService extends Disposable implements IAgentService {
28352840
// updates while resolving that snapshot; without a state entry those
28362841
// actions are rejected as targeting an unknown session and custom agents
28372842
// can disappear from the picker permanently.
2838-
const provisionalState = created.provisional && !config?.importConversation
2843+
const provisionalState = isIdleProvisional
28392844
? (() => {
28402845
const summary = this._buildInitialSummary(provider, session, config, created, '');
28412846
const state = this._stateManager.createSession(summary, { emitNotification: false });
@@ -3931,6 +3936,7 @@ export class AgentService extends Disposable implements IAgentService {
39313936
const sessionKey = session.toString();
39323937
this._cancelPendingSessionGc(session);
39333938
const isEphemeral = this._stateManager.isEphemeralSession(sessionKey);
3939+
const isIdleProvisional = this._stateManager.isIdleProvisionalSession(sessionKey);
39343940
this._stateManager.invalidateSessionChatResolutions(session.toString());
39353941
const sessionChats = this._stateManager.getSessionState(session.toString())?.chats ?? [];
39363942
for (const chat of sessionChats) {
@@ -3955,7 +3961,9 @@ export class AgentService extends Disposable implements IAgentService {
39553961
`unregistration for ${session.toString()}`,
39563962
);
39573963
}
3958-
this._invalidateSessionList();
3964+
if (!isIdleProvisional) {
3965+
this._invalidateSessionList();
3966+
}
39593967
if (provider) {
39603968
this._providerService.releaseSession(session.toString());
39613969
this._clearDownloadProgressInterest(session.toString());

src/vs/platform/agentHost/test/node/agentService.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6073,6 +6073,48 @@ suite('AgentService (node dispatcher)', () => {
60736073
);
60746074
});
60756075

6076+
test('idle provisional create and dispose do not invalidate the session list', async () => {
6077+
class ConfigurableProvisionalAgent extends MockAgent {
6078+
provisional = true;
6079+
override readonly chats: IAgentChats = withChatOverrides(getChatSurface(this), base => ({
6080+
createChat: async (chat, context, options) => {
6081+
const created = await base.createChat(chat, context, options);
6082+
return created && this.provisional ? { ...created, provisional: true } : created;
6083+
},
6084+
}));
6085+
}
6086+
6087+
const localService = disposables.add(createTestAgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService()));
6088+
const agent = disposables.add(new ConfigurableProvisionalAgent('copilot'));
6089+
registerTestAgentProvider(localService, agent);
6090+
const registryEpoch = () => (localService as unknown as { _registryEpoch: number })._registryEpoch;
6091+
const initialEpoch = registryEpoch();
6092+
6093+
const provisional = await localService.createSession({ provider: agent.id });
6094+
const afterProvisionalCreate = registryEpoch();
6095+
await localService.disposeSession(provisional);
6096+
const afterProvisionalDispose = registryEpoch();
6097+
6098+
agent.provisional = false;
6099+
const materialized = await localService.createSession({ provider: agent.id });
6100+
const afterMaterializedCreate = registryEpoch();
6101+
await localService.disposeSession(materialized);
6102+
6103+
assert.deepStrictEqual({
6104+
initialEpoch,
6105+
afterProvisionalCreate,
6106+
afterProvisionalDispose,
6107+
afterMaterializedCreate,
6108+
afterMaterializedDispose: registryEpoch(),
6109+
}, {
6110+
initialEpoch,
6111+
afterProvisionalCreate: initialEpoch,
6112+
afterProvisionalDispose: initialEpoch,
6113+
afterMaterializedCreate: initialEpoch + 1,
6114+
afterMaterializedDispose: initialEpoch + 2,
6115+
});
6116+
});
6117+
60766118
test('listSessions overlays live workspace metadata over a stale provider snapshot', async () => {
60776119
class DelayedListAgent extends MockAgent {
60786120
readonly listStarted = new DeferredPromise<void>();
@@ -6769,6 +6811,7 @@ suite('AgentService (node dispatcher)', () => {
67696811
{ git: gitState },
67706812
);
67716813
});
6814+
67726815
});
67736816

67746817
test('subscribe to a registered session changeset URI returns a changeset snapshot', async () => {

0 commit comments

Comments
 (0)