"use strict";
/**
 * Account Safety Guards
 *
 * Prevents Google account bans by:
 * 1. Cross-provider isolation (auto-pause conflicting accounts at launch, restore on exit)
 * 2. Ban/disable detection (auto-pauses affected accounts on error response)
 * 3. Crash recovery (restores stale auto-pauses from dead sessions)
 *
 * Ref: https://github.com/kaitranntt/ccs/issues/509
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleQuotaExhaustion = exports.writeQuotaWarning = exports.maskEmail = exports.handleBanDetection = exports.isBanResponse = exports.restoreAutoPausedAccounts = exports.enforceProviderIsolation = exports.pauseAccountForQuotaCooldown = exports.restoreExpiredQuotaPauses = exports.cleanupStaleAutoPauses = exports.warnPossible403Ban = exports.isPossible403BanSignal = exports.warnOAuthBanRisk = exports.warnNewAccountConflict = exports.warnCrossProviderDuplicates = exports.checkNewAccountConflict = exports.detectCrossProviderDuplicates = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const ui_1 = require("../../utils/ui");
const registry_1 = require("./registry");
const config_loader_facade_1 = require("../../config/config-loader-facade");
const ISSUE_509_URL = 'https://github.com/kaitranntt/ccs/issues/509';
/** Providers that use Google OAuth (ban risk when overlapping) */
const GOOGLE_OAUTH_PROVIDERS = ['gemini', 'agy', 'codex'];
/** Providers that should display direct CLI warnings for #509 */
const BAN_WARNING_PROVIDERS = ['gemini', 'agy'];
const shownBanWarnings = new Set();
function getAutoPausedPath() {
    return path.join((0, config_loader_facade_1.getCcsDir)(), 'cliproxy', 'auto-paused.json');
}
function getQuotaPausedPath() {
    return path.join((0, config_loader_facade_1.getCcsDir)(), 'cliproxy', 'quota-paused.json');
}
function loadAutoPaused() {
    try {
        const filePath = getAutoPausedPath();
        if (fs.existsSync(filePath)) {
            const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
            if (Array.isArray(data.sessions))
                return { sessions: data.sessions };
        }
    }
    catch {
        // Corrupted or malformed file — start fresh
    }
    return { sessions: [] };
}
function saveAutoPaused(data) {
    const filePath = getAutoPausedPath();
    if (data.sessions.length === 0) {
        try {
            fs.unlinkSync(filePath);
        }
        catch {
            /* already gone */
        }
        return;
    }
    const dir = path.dirname(filePath);
    if (!fs.existsSync(dir))
        fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
    fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
}
function loadQuotaPaused() {
    try {
        const filePath = getQuotaPausedPath();
        if (fs.existsSync(filePath)) {
            const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
            if (Array.isArray(data.entries)) {
                return {
                    entries: data.entries.filter((entry) => typeof entry === 'object' &&
                        entry !== null &&
                        typeof entry.provider === 'string' &&
                        typeof entry.accountId === 'string' &&
                        typeof entry.pausedAt === 'string' &&
                        Number.isFinite(entry.until)),
                };
            }
        }
    }
    catch {
        // Corrupted or malformed file — start fresh
    }
    return { entries: [] };
}
function saveQuotaPaused(data) {
    const filePath = getQuotaPausedPath();
    if (data.entries.length === 0) {
        try {
            fs.unlinkSync(filePath);
        }
        catch {
            /* already gone */
        }
        return;
    }
    const dir = path.dirname(filePath);
    if (!fs.existsSync(dir))
        fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
    fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
}
/**
 * Check if a process is alive. NOTE: PIDs can be recycled by the OS.
 * If a stale PID is reused by an unrelated process, cleanup is deferred until that process exits.
 * This is acceptable — next CCS launch will self-heal via cleanupStaleAutoPauses().
 */
function isPidAlive(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Detect same email registered under multiple Google OAuth providers.
 * This is the primary cause of account bans — Google sees concurrent
 * OAuth usage from different client IDs as suspicious activity.
 *
 * Returns map of email -> providers it appears in (only duplicates).
 */
function detectCrossProviderDuplicates() {
    const registry = (0, registry_1.loadAccountsRegistry)();
    // Build email -> providers mapping (only Google OAuth providers)
    const emailProviders = new Map();
    for (const provider of GOOGLE_OAUTH_PROVIDERS) {
        const providerAccounts = registry.providers[provider];
        if (!providerAccounts)
            continue;
        for (const [, account] of Object.entries(providerAccounts.accounts)) {
            const email = account.email;
            if (!email || account.paused)
                continue;
            const normalized = email.toLowerCase();
            const existing = emailProviders.get(normalized) ?? [];
            existing.push(provider);
            emailProviders.set(normalized, existing);
        }
    }
    // Filter to only duplicates (email in 2+ providers)
    const duplicates = new Map();
    for (const [email, providers] of emailProviders) {
        if (providers.length > 1) {
            duplicates.set(email, providers);
        }
    }
    return duplicates;
}
exports.detectCrossProviderDuplicates = detectCrossProviderDuplicates;
/**
 * Check if a newly registered account creates a cross-provider conflict.
 * Returns the conflicting providers, or null if no conflict.
 */
function checkNewAccountConflict(provider, email) {
    if (!email || !GOOGLE_OAUTH_PROVIDERS.includes(provider))
        return null;
    const registry = (0, registry_1.loadAccountsRegistry)();
    const normalized = email.toLowerCase();
    const conflicts = [];
    for (const other of GOOGLE_OAUTH_PROVIDERS) {
        if (other === provider)
            continue;
        const providerAccounts = registry.providers[other];
        if (!providerAccounts)
            continue;
        for (const [, account] of Object.entries(providerAccounts.accounts)) {
            if (account.email?.toLowerCase() === normalized && !account.paused) {
                conflicts.push(other);
                break;
            }
        }
    }
    return conflicts.length > 0 ? conflicts : null;
}
exports.checkNewAccountConflict = checkNewAccountConflict;
/**
 * Display cross-provider duplicate warning at session launch.
 * Returns true if warning was shown.
 */
function warnCrossProviderDuplicates(provider) {
    if (!GOOGLE_OAUTH_PROVIDERS.includes(provider))
        return false;
    const duplicates = detectCrossProviderDuplicates();
    if (duplicates.size === 0)
        return false;
    console.error('');
    console.error((0, ui_1.warn)('Account safety: cross-provider duplicate detected'));
    console.error('    Same Google account across "ccs gemini" + "ccs agy" is a known suspension/ban risk (ref: #509).');
    console.error('    This risk applies to both CLI sessions and accounts added from "ccs config".');
    console.error('    If provider requests start returning 403/Forbidden, treat it as a possible account disable/ban.');
    console.error('    If you want to keep Google AI access on this account, do not continue this shared-account setup.');
    console.error('    CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.');
    console.error(`    Details: ${ISSUE_509_URL}`);
    console.error('');
    for (const [email, providers] of duplicates) {
        console.error(`    ${maskEmail(email)} -> ${providers.join(', ')}`);
    }
    console.error('');
    console.error('    Immediate action: pause duplicate account and use separate Google accounts.');
    console.error('    Fix command: "ccs cliproxy pause <account> --provider <provider>"');
    console.error('');
    return true;
}
exports.warnCrossProviderDuplicates = warnCrossProviderDuplicates;
/**
 * Warn about a specific new account conflict during OAuth registration.
 */
function warnNewAccountConflict(email, conflictingProviders) {
    console.error('');
    console.error((0, ui_1.warn)('Account safety: this email is used by another provider'));
    console.error(`    ${maskEmail(email)} is also registered under: ${conflictingProviders.join(', ')}`);
    console.error('    Reusing one Google account between "ccs gemini" and "ccs agy" can trigger bans.');
    console.error('    This applies to both CLI auth and "ccs config" dashboard auth for these providers.');
    console.error('    403/Forbidden responses can be an early sign of account disablement.');
    console.error('    If you want to keep Google AI access, do not continue with this shared-account setup.');
    console.error('    CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.');
    console.error('    Consider pausing the duplicate or using a different account.');
    console.error(`    Details: ${ISSUE_509_URL}`);
    console.error('');
}
exports.warnNewAccountConflict = warnNewAccountConflict;
function isBanWarningProvider(provider) {
    return BAN_WARNING_PROVIDERS.includes(provider);
}
/**
 * Show one-time warning for known OAuth ban risk providers.
 */
function warnOAuthBanRisk(provider) {
    if (!isBanWarningProvider(provider) || shownBanWarnings.has(provider))
        return;
    shownBanWarnings.add(provider);
    const isAgy = provider === 'agy';
    console.error('');
    console.error((0, ui_1.warn)('Account safety warning (#509 - read before continuing)'));
    console.error('    Known risk: one Google account shared by "ccs gemini" + "ccs agy" can be disabled/banned.');
    if (isAgy) {
        console.error('    Antigravity-specific warning: OAuth usage can still trigger suspension/ban patterns.');
    }
    console.error('    This risk applies whether auth was done from CLI or from "ccs config" dashboard.');
    console.error('    If you want to keep Google AI access, do not continue with this shared-account setup.');
    console.error('    CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.');
    console.error(`    Details: ${ISSUE_509_URL}`);
    console.error('');
}
exports.warnOAuthBanRisk = warnOAuthBanRisk;
/**
 * Detect whether an error message contains a likely 403/Forbidden ban signal.
 */
function isPossible403BanSignal(errorMessage) {
    const lower = errorMessage.toLowerCase();
    return lower.includes('403') || lower.includes('forbidden');
}
exports.isPossible403BanSignal = isPossible403BanSignal;
/**
 * Show targeted warning when OAuth provider errors include 403/Forbidden.
 * Returns true when warning was emitted.
 */
function warnPossible403Ban(provider, errorMessage) {
    if (!isBanWarningProvider(provider) || !isPossible403BanSignal(errorMessage)) {
        return false;
    }
    console.error('');
    console.error((0, ui_1.warn)(`Account safety: ${provider} returned 403/Forbidden (possible disable/ban)`));
    console.error('    For gemini/agy flows this often means Google blocked or disabled the account.');
    console.error('    If you want to keep Google AI access, stop using this account/provider pairing immediately.');
    console.error('    CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.');
    console.error(`    Details: ${ISSUE_509_URL}`);
    console.error(`    Error: "${truncate(errorMessage, 160)}"`);
    console.error('');
    return true;
}
exports.warnPossible403Ban = warnPossible403Ban;
// --- Enforcement: auto-pause/restore ---
/**
 * Restore auto-paused accounts from crashed sessions (dead PIDs).
 * Call at launch BEFORE enforceProviderIsolation().
 */
function cleanupStaleAutoPauses() {
    const data = loadAutoPaused();
    if (data.sessions.length === 0)
        return;
    const alive = [];
    for (const session of data.sessions) {
        if (isPidAlive(session.pid)) {
            alive.push(session);
            continue;
        }
        // Dead PID — restore accounts
        for (const { provider, accountId } of session.accounts) {
            (0, registry_1.resumeAccount)(provider, accountId);
        }
        console.error((0, ui_1.info)(`Restored ${session.accounts.length} auto-paused account(s) from crashed ${session.initiator} session`));
    }
    if (alive.length !== data.sessions.length) {
        saveAutoPaused({ sessions: alive });
    }
}
exports.cleanupStaleAutoPauses = cleanupStaleAutoPauses;
/**
 * Resume quota-paused accounts whose cooldown windows have expired.
 * Auto-resume only applies to pauses created by CCS quota handling.
 */
function restoreExpiredQuotaPauses(now = Date.now()) {
    const data = loadQuotaPaused();
    if (data.entries.length === 0)
        return 0;
    const keep = [];
    let resumed = 0;
    const registry = (0, registry_1.loadAccountsRegistry)();
    for (const entry of data.entries) {
        if (!Number.isFinite(entry.until) || entry.until > now) {
            keep.push(entry);
            continue;
        }
        const account = registry.providers[entry.provider]?.accounts[entry.accountId];
        if (!account?.paused) {
            continue;
        }
        // Only auto-resume the exact pause CCS created for quota cooldown.
        // Missing or changed pausedAt metadata is treated as a mismatch so we do
        // not accidentally resume a manually paused account.
        if (account.pausedAt !== entry.pausedAt) {
            continue;
        }
        if ((0, registry_1.resumeAccount)(entry.provider, entry.accountId)) {
            resumed += 1;
            continue;
        }
        // Resume failures are treated as transient I/O/state issues. Keep the
        // quota-pause record so the next restore pass can retry instead of leaving
        // the account paused forever without any cooldown metadata.
        keep.push(entry);
    }
    saveQuotaPaused({ entries: keep });
    return resumed;
}
exports.restoreExpiredQuotaPauses = restoreExpiredQuotaPauses;
/**
 * Temporarily remove an exhausted account from CLIProxy rotation for the
 * configured cooldown window. Returns false when the account was already paused
 * or could not be paused, so callers can fall back to in-memory cooldown only.
 */
function pauseAccountForQuotaCooldown(provider, accountId, cooldownMinutes, now = Date.now()) {
    const registryBefore = (0, registry_1.loadAccountsRegistry)();
    const accountBefore = registryBefore.providers[provider]?.accounts[accountId];
    if (!accountBefore || accountBefore.paused) {
        return false;
    }
    if (!(0, registry_1.pauseAccount)(provider, accountId)) {
        return false;
    }
    const registryAfter = (0, registry_1.loadAccountsRegistry)();
    const pausedAt = registryAfter.providers[provider]?.accounts[accountId]?.pausedAt;
    if (!pausedAt) {
        return false;
    }
    const data = loadQuotaPaused();
    data.entries = data.entries.filter((entry) => !(entry.provider === provider && entry.accountId === accountId));
    data.entries.push({
        provider,
        accountId,
        pausedAt,
        until: now + cooldownMinutes * 60 * 1000,
        reason: 'quota_exhausted',
    });
    saveQuotaPaused(data);
    return true;
}
exports.pauseAccountForQuotaCooldown = pauseAccountForQuotaCooldown;
/**
 * Enforce provider isolation by auto-pausing conflicting accounts in other providers.
 * Records paused accounts for crash recovery and session exit restore.
 * Returns number of accounts paused.
 */
function enforceProviderIsolation(provider) {
    if (!GOOGLE_OAUTH_PROVIDERS.includes(provider))
        return 0;
    // If another provider session is actively managing isolation, just warn
    const data = loadAutoPaused();
    const otherActive = data.sessions.filter((s) => s.initiator !== provider && isPidAlive(s.pid));
    if (otherActive.length > 0)
        return 0;
    const registry = (0, registry_1.loadAccountsRegistry)();
    const currentAccounts = registry.providers[provider];
    if (!currentAccounts)
        return 0;
    // Collect active emails for current provider
    const myEmails = new Set();
    for (const [, account] of Object.entries(currentAccounts.accounts)) {
        if (account.email && !account.paused) {
            myEmails.add(account.email.toLowerCase());
        }
    }
    if (myEmails.size === 0)
        return 0;
    // Find conflicting accounts in other Google OAuth providers
    const toPause = [];
    for (const other of GOOGLE_OAUTH_PROVIDERS) {
        if (other === provider)
            continue;
        const otherAccounts = registry.providers[other];
        if (!otherAccounts)
            continue;
        for (const [accountId, account] of Object.entries(otherAccounts.accounts)) {
            if (account.email && !account.paused && myEmails.has(account.email.toLowerCase())) {
                toPause.push({ provider: other, accountId });
            }
        }
    }
    if (toPause.length === 0)
        return 0;
    // Pause conflicting accounts
    for (const { provider: p, accountId } of toPause) {
        (0, registry_1.pauseAccount)(p, accountId);
    }
    // Record for crash recovery (re-read to reduce concurrent write race window).
    // TOCTOU race is acceptable for a single-user CLI tool — self-heals on next launch.
    const freshData = loadAutoPaused();
    freshData.sessions = freshData.sessions.filter((s) => s.initiator !== provider);
    freshData.sessions.push({
        initiator: provider,
        pid: process.pid,
        pausedAt: new Date().toISOString(),
        accounts: toPause,
    });
    saveAutoPaused(freshData);
    console.error('');
    console.error((0, ui_1.info)(`Account safety: auto-paused ${toPause.length} conflicting account(s)`));
    for (const { provider: p, accountId } of toPause) {
        const acct = registry.providers[p]?.accounts[accountId];
        const display = acct?.email ? maskEmail(acct.email) : accountId;
        console.error(`    ${display} (${p})`);
    }
    console.error('    Will restore on session exit.');
    console.error('');
    return toPause.length;
}
exports.enforceProviderIsolation = enforceProviderIsolation;
/**
 * Restore accounts that were auto-paused by this session.
 * Called on session exit (process 'exit' event).
 * Skips accounts re-paused after enforcement (e.g., by ban handler).
 */
function restoreAutoPausedAccounts(provider) {
    const data = loadAutoPaused();
    const mySession = data.sessions.find((s) => s.initiator === provider && s.pid === process.pid);
    if (!mySession)
        return;
    const registry = (0, registry_1.loadAccountsRegistry)();
    for (const { provider: p, accountId } of mySession.accounts) {
        // Don't restore if account was re-paused after enforcement (e.g., ban detected)
        const account = registry.providers[p]?.accounts[accountId];
        if (account?.pausedAt && account.pausedAt > mySession.pausedAt) {
            continue;
        }
        (0, registry_1.resumeAccount)(p, accountId);
    }
    data.sessions = data.sessions.filter((s) => !(s.initiator === provider && s.pid === process.pid));
    saveAutoPaused(data);
}
exports.restoreAutoPausedAccounts = restoreAutoPausedAccounts;
// Error patterns that indicate Google has disabled/banned an account
const BAN_PATTERNS = [
    'disabled in this account',
    'violation of terms of service',
    'account has been disabled',
    'account is disabled',
    'account has been suspended',
    'account has been banned',
];
/**
 * Check if an error message indicates an account ban/disable.
 */
function isBanResponse(errorMessage) {
    const lower = errorMessage.toLowerCase();
    return BAN_PATTERNS.some((pattern) => lower.includes(pattern));
}
exports.isBanResponse = isBanResponse;
/**
 * Handle detected account ban by auto-pausing the affected account.
 * Returns true if account was paused.
 */
function handleBanDetection(provider, accountId, errorMessage) {
    if (!isBanResponse(errorMessage))
        return false;
    console.error('');
    console.error((0, ui_1.warn)('Account safety: account appears disabled by Google'));
    console.error(`    Account "${maskEmail(accountId)}" (${provider}) returned:`);
    console.error(`    "${truncate(errorMessage, 120)}"`);
    console.error('');
    console.error((0, ui_1.info)('Auto-pausing this account to prevent further issues.'));
    console.error(`    Resume later: ccs ${provider} --resume ${accountId}`);
    console.error('');
    return (0, registry_1.pauseAccount)(provider, accountId);
}
exports.handleBanDetection = handleBanDetection;
/** Mask email for privacy in terminal output */
function maskEmail(email) {
    const [local, domain] = email.split('@');
    if (!local || !domain)
        return email;
    return `${local.slice(0, 3)}***@${domain}`;
}
exports.maskEmail = maskEmail;
/** Truncate string with ellipsis */
function truncate(str, maxLen) {
    return str.length > maxLen ? str.slice(0, maxLen - 3) + '...' : str;
}
// --- Quota Exhaustion Handling ---
/**
 * Write boxed quota warning to stderr (20% threshold).
 * Uses process.stderr.write() to work alongside inherited stdio.
 * ASCII-only output (no emojis) per project constraints.
 */
function writeQuotaWarning(accountId, quotaPercent) {
    const masked = maskEmail(accountId);
    const lines = [
        `[!] Quota Low: ${masked} (${Math.round(quotaPercent)}% remaining)`,
        `    Next session will use a different account if available`,
    ];
    const maxLen = Math.max(...lines.map((l) => l.length));
    const border = '\u2550'.repeat(maxLen + 2);
    process.stderr.write('\n');
    process.stderr.write(`\u2554${border}\u2557\n`);
    for (const line of lines) {
        process.stderr.write(`\u2551 ${line.padEnd(maxLen)} \u2551\n`);
    }
    process.stderr.write(`\u255A${border}\u255D\n`);
    process.stderr.write('\n');
}
exports.writeQuotaWarning = writeQuotaWarning;
/**
 * Write boxed quota exhaustion alert to stderr.
 * Called when quota falls below exhaustion_threshold — account will be cooled down.
 */
function writeQuotaExhausted(accountId, switchedTo, cooldownMinutes) {
    const masked = maskEmail(accountId);
    const lines = [`[X] Quota Exhausted: ${masked}`, `    Cooldown: ${cooldownMinutes} minutes`];
    if (switchedTo) {
        lines.push(`    Next session default: ${maskEmail(switchedTo)}`);
    }
    else {
        lines.push(`    No alternative accounts available`);
    }
    const maxLen = Math.max(...lines.map((l) => l.length));
    const border = '\u2550'.repeat(maxLen + 2);
    process.stderr.write('\n');
    process.stderr.write(`\u2554${border}\u2557\n`);
    for (const line of lines) {
        process.stderr.write(`\u2551 ${line.padEnd(maxLen)} \u2551\n`);
    }
    process.stderr.write(`\u255A${border}\u255D\n`);
    process.stderr.write('\n');
}
/**
 * Handle quota exhaustion for an active session.
 * Applies cooldown to exhausted account, finds healthy alternative,
 * switches default, and alerts user via stderr.
 *
 * @returns switchedTo account ID or null if no alternatives
 */
async function handleQuotaExhaustion(provider, accountId, cooldownMinutes) {
    // Dynamic imports to avoid circular dependencies
    const { applyCooldown, findHealthyAccount } = await Promise.resolve().then(() => __importStar(require('../quota/quota-manager')));
    const { setDefaultAccount, touchAccount } = await Promise.resolve().then(() => __importStar(require('./account-manager')));
    const { loadOrCreateUnifiedConfig } = await Promise.resolve().then(() => __importStar(require('../../config/config-loader-facade')));
    const config = loadOrCreateUnifiedConfig();
    const threshold = config.quota_management?.auto?.exhaustion_threshold ?? 5;
    // Apply cooldown to exhausted account
    applyCooldown(provider, accountId, cooldownMinutes);
    // Find healthy alternative
    const alternative = await findHealthyAccount(provider, [accountId]);
    if (alternative) {
        if (alternative.lastQuota !== null && alternative.lastQuota >= threshold) {
            pauseAccountForQuotaCooldown(provider, accountId, cooldownMinutes);
        }
        setDefaultAccount(provider, alternative.id);
        touchAccount(provider, alternative.id);
        writeQuotaExhausted(accountId, alternative.id, cooldownMinutes);
        return {
            switchedTo: alternative.id,
            reason: `Quota exhausted, switched to ${maskEmail(alternative.id)}`,
        };
    }
    // No alternatives — warn but continue (graceful degradation)
    writeQuotaExhausted(accountId, null, cooldownMinutes);
    return {
        switchedTo: null,
        reason: 'Quota exhausted, no alternatives available',
    };
}
exports.handleQuotaExhaustion = handleQuotaExhaustion;
//# sourceMappingURL=account-safety.js.map