Fix agent host checkpoint file changes flicker (#324282)

Fix agent host file changes flicker

Avoid recreating per-turn changeset subscriptions when equivalent session state updates produce equivalent URI objects. Add regression coverage for subscription stability. (Written by Copilot)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Rob Lourens
2026-07-03 23:19:57 -07:00
committed by GitHub
parent 64d400d4d7
commit ed64b5f419
2 changed files with 27 additions and 2 deletions
@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../../../base/common/lifecycle.js';
import { constObservable, derived, IObservable, observableFromEvent } from '../../../../../../base/common/observable.js';
import { constObservable, derived, derivedOpts, IObservable, observableFromEvent } from '../../../../../../base/common/observable.js';
import { isEqual } from '../../../../../../base/common/resources.js';
import { isDefined } from '../../../../../../base/common/types.js';
import { URI } from '../../../../../../base/common/uri.js';
import { IAgentConnection } from '../../../../../../platform/agentHost/common/agentService.js';
@@ -64,7 +65,7 @@ export class AgentHostResponseFileChangesProvider extends Disposable implements
// the summary stays empty (and self-hidden) for them.
const sessionStateObs = this._subscribe<SessionState>(StateComponents.Session, constObservable(backendSession));
const turnChangesetUriObs = derived(reader => {
const turnChangesetUriObs = derivedOpts<URI | undefined>({ equalsFn: isEqual }, reader => {
const sessionState = sessionStateObs.read(reader).read(reader);
if (!sessionState || sessionState instanceof Error) {
return undefined;
@@ -22,14 +22,20 @@ class FakeAgentConnection extends mock<IAgentConnection>() {
private readonly _emitters = new Map<string, Emitter<unknown>>();
private readonly _values = new Map<string, unknown>();
private readonly _subscriptionCounts = new Map<string, number>();
setState(resource: string, value: unknown): void {
this._values.set(resource, value);
this._emitters.get(resource)?.fire(value);
}
getSubscriptionCount(resource: string): number {
return this._subscriptionCounts.get(resource) ?? 0;
}
override getSubscription<T extends StateComponents>(_kind: T, resource: URI, _owner: string): IReference<IAgentSubscription<never>> {
const key = resource.toString();
this._subscriptionCounts.set(key, (this._subscriptionCounts.get(key) ?? 0) + 1);
let emitter = this._emitters.get(key);
if (!emitter) {
emitter = new Emitter<unknown>();
@@ -93,6 +99,24 @@ suite('AgentHostResponseFileChangesProvider', () => {
]);
});
test('keeps the changeset subscription when session state updates', () => {
const ds = store.add(new DisposableStore());
const conn = new FakeAgentConnection();
const provider = ds.add(new AgentHostResponseFileChangesProvider(conn, authority, () => backendSession));
conn.setState(backendSession.toString(), sessionStateWithTurnSupport());
conn.setState(turnChangesetUri('t1'), { status: ChangesetStatus.Ready, files: [] } satisfies ChangesetState);
observe(provider, ds);
const subscriptionCountBeforeUpdate = conn.getSubscriptionCount(turnChangesetUri('t1'));
conn.setState(backendSession.toString(), sessionStateWithTurnSupport());
assert.deepStrictEqual([
subscriptionCountBeforeUpdate,
conn.getSubscriptionCount(turnChangesetUri('t1')),
], [1, 1]);
});
test('returns empty when the agent does not advertise a turn changeset', () => {
const ds = store.add(new DisposableStore());
const conn = new FakeAgentConnection();