From 9e091df6f6437844fff1a1ca4d06bbd4ec6ab9f5 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Tue, 24 Mar 2026 01:28:48 +0900 Subject: [PATCH] fix: scan all Codex limits for max 5h/7d, persist d7 reset times - Iterate all rate limits per account instead of just display[0] - Track and persist 7-day reset time separately (resetD7At) - Show both 5h and 7d cached usage for inactive Codex accounts --- src/codex-token-rotation.ts | 31 ++++++++++++++++------ src/unified-dashboard.ts | 51 +++++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/codex-token-rotation.ts b/src/codex-token-rotation.ts index 98c12be..185092d 100644 --- a/src/codex-token-rotation.ts +++ b/src/codex-token-rotation.ts @@ -29,9 +29,13 @@ interface CodexAccount { lastUsagePct?: number; lastUsageD7Pct?: number; resetAt?: string; + resetD7At?: string; } -function parseJwtAuth(idToken: string): { planType: string; expiresAt: string | null } { +function parseJwtAuth(idToken: string): { + planType: string; + expiresAt: string | null; +} { try { const parts = idToken.split('.'); if (parts.length < 2) return { planType: '?', expiresAt: null }; @@ -108,6 +112,7 @@ function saveCodexState(): void { usagePcts: accounts.map((a) => a.lastUsagePct ?? null), usageD7Pcts: accounts.map((a) => a.lastUsageD7Pct ?? null), resetAts: accounts.map((a) => a.resetAt ?? null), + resetD7Ats: accounts.map((a) => a.resetD7At ?? null), }; fs.writeFileSync(STATE_FILE, JSON.stringify(state)); } catch { @@ -149,19 +154,25 @@ function loadCodexState(): void { } } if (Array.isArray(state.usageD7Pcts)) { - for (let i = 0; i < Math.min(state.usageD7Pcts.length, accounts.length); i++) { - if (typeof state.usageD7Pcts[i] === 'number') accounts[i].lastUsageD7Pct = state.usageD7Pcts[i]; + for ( + let i = 0; + i < Math.min(state.usageD7Pcts.length, accounts.length); + i++ + ) { + if (typeof state.usageD7Pcts[i] === 'number') + accounts[i].lastUsageD7Pct = state.usageD7Pcts[i]; } } if (Array.isArray(state.resetAts)) { - for ( - let i = 0; - i < Math.min(state.resetAts.length, accounts.length); - i++ - ) { + for (let i = 0; i < Math.min(state.resetAts.length, accounts.length); i++) { if (state.resetAts[i]) accounts[i].resetAt = state.resetAts[i]; } } + if (Array.isArray(state.resetD7Ats)) { + for (let i = 0; i < Math.min(state.resetD7Ats.length, accounts.length); i++) { + if (state.resetD7Ats[i]) accounts[i].resetD7At = state.resetD7Ats[i]; + } + } logger.info( { currentIndex, accountCount: accounts.length }, 'Codex rotation state restored', @@ -275,6 +286,7 @@ export function updateCodexAccountUsage( resetAt?: string, accountIndex?: number, d7Pct?: number, + resetD7At?: string, ): void { if (accounts.length === 0) return; const idx = accountIndex ?? currentIndex; @@ -283,6 +295,7 @@ export function updateCodexAccountUsage( acct.lastUsagePct = usagePct; if (d7Pct != null) acct.lastUsageD7Pct = d7Pct; if (resetAt) acct.resetAt = resetAt; + if (resetD7At) acct.resetD7At = resetD7At; saveCodexState(); } } @@ -309,6 +322,7 @@ export function getAllCodexAccounts(): { cachedUsagePct?: number; cachedUsageD7Pct?: number; resetAt?: string; + resetD7At?: string; }[] { const now = Date.now(); return accounts.map((a, i) => ({ @@ -320,5 +334,6 @@ export function getAllCodexAccounts(): { cachedUsagePct: a.lastUsagePct, cachedUsageD7Pct: a.lastUsageD7Pct, resetAt: a.resetAt, + resetD7At: a.resetD7At, })); } diff --git a/src/unified-dashboard.ts b/src/unified-dashboard.ts index 7b16933..6cc35b0 100644 --- a/src/unified-dashboard.ts +++ b/src/unified-dashboard.ts @@ -646,17 +646,17 @@ async function buildUsageContent(): Promise { } } else { // Show cached usage from last scan - const pct = - acct.cachedUsagePct != null ? acct.cachedUsagePct : -1; + const pct = acct.cachedUsagePct != null ? acct.cachedUsagePct : -1; const d7pct = acct.cachedUsageD7Pct != null ? acct.cachedUsageD7Pct : -1; const reset = acct.resetAt || ''; + const d7reset = acct.resetD7At || ''; rows.push({ name: `${label} ${acct.planType}`, h5pct: pct, h5reset: reset, d7pct, - d7reset: '', + d7reset, }); } } @@ -794,20 +794,42 @@ async function refreshAllCodexAccountUsage(): Promise { (l) => l.primary.usedPercent > 0 || l.secondary.usedPercent > 0, ); const display = relevant.length > 0 ? relevant : usage.slice(0, 1); - if (display.length > 0) { - const pct = Math.round(display[0].primary.usedPercent); - const resetVal = display[0].primary.resetsAt; - const resetStr = resetVal ? formatResetRemaining(resetVal) : undefined; - const d7Pct = Math.round(display[0].secondary.usedPercent); - updateCodexAccountUsage(pct, resetStr, acct.index, d7Pct); + if (usage.length > 0) { + // Find max primary (5h) and secondary (7d) across all limits + let maxH5 = 0; + let maxD7 = 0; + let h5Reset: string | number | undefined; + let d7Reset: string | number | undefined; + for (const limit of usage) { + if (limit.primary.usedPercent > maxH5) { + maxH5 = limit.primary.usedPercent; + h5Reset = limit.primary.resetsAt; + } + if (limit.secondary.usedPercent > maxD7) { + maxD7 = limit.secondary.usedPercent; + d7Reset = limit.secondary.resetsAt; + } + } + const pct = Math.round(maxH5); + const d7Pct = Math.round(maxD7); + const resetStr = h5Reset + ? formatResetRemaining(h5Reset) + : undefined; + const resetD7Str = d7Reset + ? formatResetRemaining(d7Reset) + : undefined; + updateCodexAccountUsage(pct, resetStr, acct.index, d7Pct, resetD7Str); logger.info( - { account: acct.index + 1, usagePct: pct, reset: resetStr }, - `Codex account #${acct.index + 1} usage: ${pct}%`, + { account: acct.index + 1, h5: pct, d7: d7Pct, reset: resetStr }, + `Codex account #${acct.index + 1} usage: 5h=${pct}% 7d=${d7Pct}%`, ); } } } catch (err) { - logger.debug({ err, account: acct.index + 1 }, 'Failed to fetch usage for Codex account'); + logger.debug( + { err, account: acct.index + 1 }, + 'Failed to fetch usage for Codex account', + ); } } } @@ -881,7 +903,10 @@ export async function startUnifiedDashboard( setInterval(refreshUsageCache, opts.usageUpdateInterval); // Full scan of all Codex accounts on startup + hourly void refreshAllCodexAccountUsage(); - setInterval(() => void refreshAllCodexAccountUsage(), CODEX_FULL_SCAN_INTERVAL); + setInterval( + () => void refreshAllCodexAccountUsage(), + CODEX_FULL_SCAN_INTERVAL, + ); } logger.info(