diff --git a/packages/server/src/server/agent/provider-snapshot-manager.test.ts b/packages/server/src/server/agent/provider-snapshot-manager.test.ts index 4ea80bf3b..e47495505 100644 --- a/packages/server/src/server/agent/provider-snapshot-manager.test.ts +++ b/packages/server/src/server/agent/provider-snapshot-manager.test.ts @@ -251,6 +251,51 @@ describe("ProviderSnapshotManager", () => { manager.destroy(); }); + test("refresh during an in-flight refresh is a no-op", async () => { + const fetchModels = deferred(); + const fetchModes = deferred(); + const { registry, handles } = createRegistry([ + createMockProvider({ + provider: "codex", + fetchModels: async () => fetchModels.promise, + fetchModes: async () => fetchModes.promise, + }), + ]); + const manager = new ProviderSnapshotManager(registry, createTestLogger()); + const changes: ProviderSnapshotEntry[][] = []; + manager.on("change", (entries) => changes.push(entries)); + + manager.refresh("/tmp/project"); + + expect(manager.getSnapshot("/tmp/project")).toEqual([ + { provider: "codex", status: "loading" }, + ]); + + manager.refresh("/tmp/project"); + manager.refresh("/tmp/project"); + manager.refresh("/tmp/project"); + + expect(changes).toHaveLength(1); + expect(handles.codex?.isAvailable).toHaveBeenCalledTimes(1); + + fetchModels.resolve([createModel("codex", "gpt-5.2")]); + fetchModes.resolve([createMode("auto")]); + + await vi.waitFor(() => { + expect(getProviderEntry(manager.getSnapshot("/tmp/project"), "codex")).toMatchObject({ + provider: "codex", + status: "ready", + models: [createModel("codex", "gpt-5.2")], + modes: [createMode("auto")], + }); + }); + + expect(handles.codex?.fetchModels).toHaveBeenCalledTimes(1); + expect(handles.codex?.fetchModes).toHaveBeenCalledTimes(1); + + manager.destroy(); + }); + test("multiple getSnapshot calls for same cwd do not trigger multiple warmUps", async () => { const codexModels = deferred(); const { registry, handles } = createRegistry([ diff --git a/packages/server/src/server/agent/provider-snapshot-manager.ts b/packages/server/src/server/agent/provider-snapshot-manager.ts index dded5a2b4..78b56ce70 100644 --- a/packages/server/src/server/agent/provider-snapshot-manager.ts +++ b/packages/server/src/server/agent/provider-snapshot-manager.ts @@ -32,8 +32,7 @@ export class ProviderSnapshotManager { const cwdKey = normalizeCwdKey(cwd); const entries = this.snapshots.get(cwdKey); if (!entries) { - const loadingEntries = this.createLoadingEntries(); - this.snapshots.set(cwdKey, loadingEntries); + const loadingEntries = this.resetSnapshotToLoading(cwdKey); void this.warmUp(cwd); return entriesToArray(loadingEntries); } @@ -42,7 +41,11 @@ export class ProviderSnapshotManager { refresh(cwd?: string): void { const cwdKey = normalizeCwdKey(cwd); - this.snapshots.set(cwdKey, this.createLoadingEntries()); + if (this.warmUps.has(cwdKey)) { + return; + } + this.resetSnapshotToLoading(cwdKey); + this.emitChange(cwdKey); void this.warmUp(cwd); } @@ -170,6 +173,15 @@ export class ProviderSnapshotManager { return created; } + private resetSnapshotToLoading(cwdKey: string): Map { + const snapshot = this.getOrCreateSnapshot(cwdKey); + snapshot.clear(); + for (const [provider, entry] of this.createLoadingEntries()) { + snapshot.set(provider, entry); + } + return snapshot; + } + private getProviderIds(): AgentProvider[] { return AGENT_PROVIDER_IDS.filter((provider) => this.providerRegistry[provider]); }