diff --git a/src/plugin.ts b/src/plugin.ts index cad8209..09257a3 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1699,8 +1699,6 @@ export const createAntigravityPlugin = (providerId: string) => async ( accountManager.markToastShown(account.index); } - accountManager.requestSaveToDisk(); - let authRecord = accountManager.toAuthDetails(account); if (accessTokenExpired(authRecord)) { @@ -2330,7 +2328,8 @@ export const createAntigravityPlugin = (providerId: string) => async ( account.consecutiveFailures = 0; getHealthTracker().recordSuccess(account.index); accountManager.markAccountUsed(account.index); - + accountManager.requestSaveToDisk() + void triggerAsyncQuotaRefreshForAccount( accountManager, account.index, diff --git a/src/plugin/accounts.test.ts b/src/plugin/accounts.test.ts index 24a02a5..2b86a7d 100644 --- a/src/plugin/accounts.test.ts +++ b/src/plugin/accounts.test.ts @@ -1087,7 +1087,7 @@ describe("AccountManager", () => { expect(saveSpy).not.toHaveBeenCalled(); - await vi.advanceTimersByTimeAsync(1500); + await vi.advanceTimersByTimeAsync(5500); expect(saveSpy).toHaveBeenCalledTimes(1); @@ -1112,7 +1112,7 @@ describe("AccountManager", () => { const flushPromise = manager.flushSaveToDisk(); - await vi.advanceTimersByTimeAsync(1500); + await vi.advanceTimersByTimeAsync(5500); await flushPromise; expect(saveSpy).toHaveBeenCalledTimes(1); @@ -1135,16 +1135,43 @@ describe("AccountManager", () => { const saveSpy = vi.spyOn(manager, "saveToDisk").mockResolvedValue(); manager.requestSaveToDisk(); - await vi.advanceTimersByTimeAsync(1500); + await vi.advanceTimersByTimeAsync(5500); expect(saveSpy).toHaveBeenCalledTimes(1); - await vi.advanceTimersByTimeAsync(3000); + await vi.advanceTimersByTimeAsync(6000); expect(saveSpy).toHaveBeenCalledTimes(1); saveSpy.mockRestore(); }); + + it("skips write when account state has not changed", async () => { + vi.useFakeTimers(); + + const stored: AccountStorageV4 = { + version: 4, + accounts: [ + { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 }, + ], + activeIndex: 0, + }; + + const manager = new AccountManager(undefined, stored); + const saveSpy = vi.spyOn(manager, "saveToDisk").mockResolvedValue(); + + // First save — should write + manager.requestSaveToDisk(); + await vi.advanceTimersByTimeAsync(5500); + expect(saveSpy).toHaveBeenCalledTimes(1); + + // Second save with no state change — should skip + manager.requestSaveToDisk(); + await vi.advanceTimersByTimeAsync(5500); + expect(saveSpy).toHaveBeenCalledTimes(1); + + saveSpy.mockRestore(); + }); }); describe("Rate Limit Reason Classification", () => { diff --git a/src/plugin/accounts.ts b/src/plugin/accounts.ts index e86ed5f..303a118 100644 --- a/src/plugin/accounts.ts +++ b/src/plugin/accounts.ts @@ -312,6 +312,7 @@ export class AccountManager { private savePending = false; private saveTimeout: ReturnType | null = null; private savePromiseResolvers: Array<() => void> = []; + private lastSavedSnapshot: string | null = null; static async loadFromDisk(authFallback?: OAuthAuthDetails): Promise { const stored = await loadAccounts(); @@ -973,11 +974,11 @@ export class AccountManager { return [...this.accounts]; } - async saveToDisk(): Promise { + private buildStorageState(): AccountStorageV4 { const claudeIndex = Math.max(0, this.currentAccountIndexByFamily.claude); const geminiIndex = Math.max(0, this.currentAccountIndexByFamily.gemini); - - const storage: AccountStorageV4 = { + + return { version: 4, accounts: this.accounts.map((a) => ({ email: a.email, @@ -1005,9 +1006,11 @@ export class AccountManager { claude: claudeIndex, gemini: geminiIndex, }, - }; + } + } - await saveAccounts(storage); + async saveToDisk(): Promise { + await saveAccounts(this.buildStorageState()); } requestSaveToDisk(): void { @@ -1017,7 +1020,7 @@ export class AccountManager { this.savePending = true; this.saveTimeout = setTimeout(() => { void this.executeSave(); - }, 1000); + }, 5000); } async flushSaveToDisk(): Promise { @@ -1032,9 +1035,13 @@ export class AccountManager { private async executeSave(): Promise { this.savePending = false; this.saveTimeout = null; - + try { - await this.saveToDisk(); + const snapshot = JSON.stringify(this.buildStorageState()); + if (snapshot !== this.lastSavedSnapshot) { + await this.saveToDisk(); + this.lastSavedSnapshot = snapshot; + } } catch { // best-effort persistence; avoid unhandled rejection from timer-driven saves } finally {