fix(cliproxy): preserve manual quota pauses

This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-30 15:20:52 -04:00
committed by Tam Nhu Tran
parent 62a4d44b58
commit 6ecca9b16f
3 changed files with 70 additions and 1 deletions
@@ -21,6 +21,7 @@ import {
restoreExpiredQuotaPauses,
} from '../../accounts/account-safety';
import { sanitizeEmail } from '../../auth/auth-utils';
import { pauseAccount } from '../registry';
// Setup test isolation
let tmpDir: string;
@@ -1228,6 +1229,65 @@ describe('Quota Exhaustion Handlers', () => {
expect(fs.existsSync(path.join(tmpDir, '.ccs', 'cliproxy', 'quota-paused.json'))).toBe(false);
});
it('does not auto-resume when a user manually re-pauses a quota-paused account', async () => {
writeRegistry({
agy: {
default: 'cooldown@gmail.com',
accounts: {
'cooldown@gmail.com': {
email: 'cooldown@gmail.com',
tokenFile: 'agy-cooldown.json',
},
},
},
});
writeAuthToken('agy-cooldown.json', {
type: 'agy',
email: 'cooldown@gmail.com',
access_token: 'token',
});
const now = Date.now();
expect(pauseAccountForQuotaCooldown('agy', 'cooldown@gmail.com', 5, now)).toBe(true);
const registryPath = path.join(tmpDir, '.ccs', 'cliproxy', 'accounts.json');
const quotaPausedPath = path.join(tmpDir, '.ccs', 'cliproxy', 'quota-paused.json');
const originalPausedAt = '2026-01-01T00:00:00.000Z';
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8')) as {
providers: {
agy: {
accounts: Record<string, { paused?: boolean; pausedAt?: string }>;
};
};
};
const quotaPaused = JSON.parse(fs.readFileSync(quotaPausedPath, 'utf8')) as {
entries: Array<{ pausedAt: string }>;
};
registry.providers.agy.accounts['cooldown@gmail.com'].pausedAt = originalPausedAt;
quotaPaused.entries[0].pausedAt = originalPausedAt;
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2));
fs.writeFileSync(quotaPausedPath, JSON.stringify(quotaPaused, null, 2));
expect(pauseAccount('agy', 'cooldown@gmail.com')).toBe(true);
const refreshedRegistry = JSON.parse(
fs.readFileSync(registryPath, 'utf8')
) as typeof registry;
expect(refreshedRegistry.providers.agy.accounts['cooldown@gmail.com'].pausedAt).not.toBe(
originalPausedAt
);
const resumed = restoreExpiredQuotaPauses(now + 6 * 60 * 1000);
const { getAccount } = await import('../account-manager');
expect(resumed).toBe(0);
expect(getAccount('agy', 'cooldown@gmail.com')?.paused).toBe(true);
expect(
fs.existsSync(path.join(tmpDir, '.ccs', 'cliproxy', 'auth-paused', 'agy-cooldown.json'))
).toBe(true);
expect(fs.existsSync(quotaPausedPath)).toBe(false);
});
it('does not auto-resume quota-paused accounts when pausedAt metadata is missing', async () => {
writeRegistry({
agy: {
+4
View File
@@ -660,6 +660,10 @@ export function pauseAccount(provider: CLIProxyProvider, accountId: string): boo
const accountMeta = providerAccounts.accounts[accountId];
if (accountMeta.paused) {
// Treat an explicit pause request for an already paused account as a fresh
// manual decision. This changes the pause metadata so quota cooldown
// restore cannot later mistake the pause for its original auto-pause.
accountMeta.pausedAt = new Date().toISOString();
return true;
}
+6 -1
View File
@@ -1056,8 +1056,13 @@ export async function handlePauseAccount(args: string[]): Promise<void> {
}
if (account.paused) {
const refreshed = pauseAccount(provider, account.id);
const refreshedAccount = refreshed ? findAccountByQuery(provider, account.id) : account;
console.log(warn(`Account already paused: ${formatCliAccountLabel(account)}`));
console.log(info(`Paused at: ${account.pausedAt || 'unknown'}`));
if (refreshed) {
console.log(info('Manual pause refreshed; account will stay out of quota rotation'));
}
console.log(info(`Paused at: ${refreshedAccount?.pausedAt || account.pausedAt || 'unknown'}`));
return;
}