feat(bar): per-window subscription quota detail + codex multi-session scan

Expose per-window detail (5h / weekly / Opus / Sonnet) on native subscription
rows instead of collapsing to a single percentage, so the bar can show the
binding window, resets, and a burn-rate pace line. Fix the Codex collector to
scan recent session rollouts newest-first for the latest non-null rate_limits
(today's exec-mode session is null) and mark the result stale with its source
time, so Codex quota appears instead of silently vanishing.
This commit is contained in:
Tam Nhu Tran committed 2026-06-09 14:39:41 -04:00
1 parent e9df0757a2
commit e96967c224
5 files changed
+441 -46

No files matched your search

@@ -23,14 +23,25 @@ function tokenCountLine(rateLimits: unknown): string {
});
}
function writeRollout(name: string, lines: string[]): string {
const day = path.join(sessionsDir, '2026', '06', '09');
function writeRollout(sessions: string, name: string, lines: string[]): string {
const day = path.join(sessions, '2026', '06', '09');
fs.mkdirSync(day, { recursive: true });
const file = path.join(day, name);
fs.writeFileSync(file, lines.join('\n') + '\n');
return file;
}
/**
* Each test gets its OWN codex home so the multi-session scan never bleeds a
* fixture from another test (the scan walks ALL recent sessions, not just one).
*/
function freshHome(slug: string): { env: NodeJS.ProcessEnv; sessions: string } {
const home = path.join(tmpDir, `.codex-${slug}`);
const sessions = path.join(home, 'sessions');
fs.mkdirSync(sessions, { recursive: true });
return { env: { CODEX_HOME: home }, sessions };
}
beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-local-quota-'));
codexHome = path.join(tmpDir, '.codex');
@@ -44,7 +55,8 @@ afterAll(() => {
describe('getCodexLocalQuota', () => {
it('parses the last non-null rate_limits into a normalized quota', async () => {
writeRollout('rollout-2026-06-09T10-00-00-aaaa.jsonl', [
const { env, sessions } = freshHome('parse');
writeRollout(sessions, 'rollout-2026-06-09T10-00-00-aaaa.jsonl', [
tokenCountLine(null),
tokenCountLine({
primary: { used_percent: 0.0, window_minutes: 300, resets_at: 1781033803 },
@@ -53,7 +65,7 @@ describe('getCodexLocalQuota', () => {
}),
]);
const quota = await getCodexLocalQuota({ env: { CODEX_HOME: codexHome }, now: Date.now() });
const quota = await getCodexLocalQuota({ env, now: Date.now() });
expect(quota).not.toBeNull();
// min(100-0, 100-48) = 52
expect(quota?.quotaPercentage).toBe(52);
@@ -61,20 +73,94 @@ describe('getCodexLocalQuota', () => {
// soonest reset = min(1781033803, 1781192122) -> primary
expect(quota?.nextReset).toBe(new Date(1781033803 * 1000).toISOString());
expect(quota?.stale).toBe(false);
expect(quota?.staleAsOf).toBeNull();
});
it('returns null when every rate_limits is null (exec-mode session)', async () => {
// A newer session (later filename) with only null rate_limits wins the sort.
writeRollout('rollout-2026-06-09T11-00-00-bbbb.jsonl', [
it('surfaces per-window detail incl. window_minutes (300 / 10080)', async () => {
const { env, sessions } = freshHome('windows');
writeRollout(sessions, 'rollout-2026-06-09T10-00-00-aaaa.jsonl', [
tokenCountLine({
primary: { used_percent: 19.0, window_minutes: 300, resets_at: 1781033803 },
secondary: { used_percent: 30.0, window_minutes: 10080, resets_at: 1781192122 },
plan_type: 'pro',
}),
]);
const quota = await getCodexLocalQuota({ env, now: Date.now() });
expect(quota?.windows).toHaveLength(2);
const five = quota?.windows.find((w) => w.key === 'five_hour');
expect(five?.label).toBe('5h');
expect(five?.usedPercent).toBe(19);
expect(five?.remainingPercent).toBe(81);
expect(five?.windowMinutes).toBe(300);
expect(five?.resetAt).toBe(new Date(1781033803 * 1000).toISOString());
const week = quota?.windows.find((w) => w.key === 'seven_day');
expect(week?.label).toBe('week');
expect(week?.usedPercent).toBe(30);
expect(week?.remainingPercent).toBe(70);
expect(week?.windowMinutes).toBe(10080);
expect(week?.resetAt).toBe(new Date(1781192122 * 1000).toISOString());
});
it('scans an OLDER session when the newest is exec-mode (rate_limits:null)', async () => {
const { env, sessions } = freshHome('fallback');
// Older interactive session carries real quota.
writeRollout(sessions, 'rollout-2026-06-09T10-00-00-aaaa.jsonl', [
tokenCountLine({
primary: { used_percent: 0.0, window_minutes: 300, resets_at: 1781033803 },
secondary: { used_percent: 48.0, window_minutes: 10080, resets_at: 1781192122 },
plan_type: 'pro',
}),
]);
// Newest session is exec-mode: only null rate_limits.
writeRollout(sessions, 'rollout-2026-06-09T11-00-00-bbbb.jsonl', [
tokenCountLine(null),
tokenCountLine(null),
]);
const quota = await getCodexLocalQuota({ env: { CODEX_HOME: codexHome }, now: Date.now() });
const quota = await getCodexLocalQuota({ env, now: Date.now() });
// Skips the null-newest file, reads the older file's rate_limits.
expect(quota).not.toBeNull();
expect(quota?.quotaPercentage).toBe(52);
expect(quota?.tier).toBe('pro');
});
it('returns null when NO scanned session carries rate_limits (no fake row)', async () => {
const { env, sessions } = freshHome('all-null');
writeRollout(sessions, 'rollout-2026-06-09T10-00-00-aaaa.jsonl', [tokenCountLine(null)]);
writeRollout(sessions, 'rollout-2026-06-09T11-00-00-bbbb.jsonl', [
tokenCountLine(null),
tokenCountLine(null),
]);
const quota = await getCodexLocalQuota({ env, now: Date.now() });
expect(quota).toBeNull();
});
it('flags stale when the source file mtime is older than 5 minutes', async () => {
const file = writeRollout('rollout-2026-06-09T12-00-00-cccc.jsonl', [
it('flags stale from the SOURCE file mtime and sets staleAsOf', async () => {
const { env, sessions } = freshHome('stale');
// Newest is exec-mode (null); the data comes from the older file, so stale
// must reflect the OLDER file's mtime, not the newest's.
const sourceFile = writeRollout(sessions, 'rollout-2026-06-09T10-00-00-aaaa.jsonl', [
tokenCountLine({
primary: { used_percent: 10, window_minutes: 300, resets_at: 1781033803 },
secondary: { used_percent: 5, window_minutes: 10080, resets_at: 1781192122 },
plan_type: 'plus',
}),
]);
writeRollout(sessions, 'rollout-2026-06-09T11-00-00-bbbb.jsonl', [tokenCountLine(null)]);
const sourceMtime = fs.statSync(sourceFile).mtimeMs;
const quota = await getCodexLocalQuota({ env, now: sourceMtime + 6 * 60 * 1000 });
expect(quota?.stale).toBe(true);
expect(quota?.staleAsOf).toBe(new Date(sourceMtime).toISOString());
expect(quota?.tier).toBe('plus');
});
it('is fresh (no staleAsOf) when the source file is recent', async () => {
const { env, sessions } = freshHome('fresh');
const file = writeRollout(sessions, 'rollout-2026-06-09T10-00-00-aaaa.jsonl', [
tokenCountLine({
primary: { used_percent: 10, window_minutes: 300, resets_at: 1781033803 },
secondary: { used_percent: 5, window_minutes: 10080, resets_at: 1781192122 },
@@ -82,19 +168,14 @@ describe('getCodexLocalQuota', () => {
}),
]);
const mtime = fs.statSync(file).mtimeMs;
// now = mtime + 6 minutes -> stale
const quota = await getCodexLocalQuota({
env: { CODEX_HOME: codexHome },
now: mtime + 6 * 60 * 1000,
});
expect(quota?.stale).toBe(true);
expect(quota?.tier).toBe('plus');
const quota = await getCodexLocalQuota({ env, now: mtime + 60 * 1000 });
expect(quota?.stale).toBe(false);
expect(quota?.staleAsOf).toBeNull();
});
it('returns null when there are no rollout files at all', async () => {
const emptyHome = path.join(tmpDir, '.codex-empty');
fs.mkdirSync(path.join(emptyHome, 'sessions'), { recursive: true });
const quota = await getCodexLocalQuota({ env: { CODEX_HOME: emptyHome }, now: Date.now() });
const { env } = freshHome('empty');
const quota = await getCodexLocalQuota({ env, now: Date.now() });
expect(quota).toBeNull();
});
});
@@ -47,6 +47,34 @@ function successQuota(): ClaudeQuotaResult {
};
}
/** A Max-plan quota carrying the Opus/Sonnet weekly splits in windows[]. */
function maxQuotaWithSplits(): ClaudeQuotaResult {
const base = successQuota();
return {
...base,
windows: [
{
rateLimitType: 'seven_day_opus',
label: 'Opus weekly',
status: 'allowed',
utilization: 0.25,
usedPercent: 25,
remainingPercent: 75,
resetAt: '2026-06-15T00:00:00.000Z',
},
{
rateLimitType: 'seven_day_sonnet',
label: 'Sonnet weekly',
status: 'allowed',
utilization: 0.6,
usedPercent: 60,
remainingPercent: 40,
resetAt: '2026-06-15T00:00:00.000Z',
},
],
};
}
function reauthQuota(): ClaudeQuotaResult {
return {
success: false,
@@ -115,6 +143,51 @@ describe('Claude native row mapping', () => {
expect(row?.needsReauth).toBe(false);
});
it('emits quotaWindows with five_hour + seven_day from coreUsage (no opus/sonnet on non-Max)', async () => {
const clock = { now: 1_000_000 };
const deps = makeDeps(async () => successQuota(), clock);
const rows = await getNativeAccountRows(deps);
const row = rows.find((r) => r.provider === 'claude-code');
expect(row?.quotaWindows).toHaveLength(2);
const five = row?.quotaWindows?.find((w) => w.key === 'five_hour');
expect(five?.label).toBe('5h');
expect(five?.remainingPercent).toBe(42);
expect(five?.usedPercent).toBe(58); // 100 - 42
expect(five?.windowMinutes).toBe(300);
expect(five?.resetAt).toBe('2026-06-09T20:00:00.000Z');
const week = row?.quotaWindows?.find((w) => w.key === 'seven_day');
expect(week?.label).toBe('week');
expect(week?.remainingPercent).toBe(70);
expect(week?.usedPercent).toBe(30);
expect(week?.windowMinutes).toBe(10080);
// Max-only splits absent here.
expect(row?.quotaWindows?.find((w) => w.key === 'seven_day_opus')).toBeUndefined();
expect(row?.quotaWindows?.find((w) => w.key === 'seven_day_sonnet')).toBeUndefined();
});
it('adds seven_day_opus + seven_day_sonnet windows when present (Max plan)', async () => {
const clock = { now: 1_000_000 };
const deps = makeDeps(async () => maxQuotaWithSplits(), clock);
const rows = await getNativeAccountRows(deps);
const row = rows.find((r) => r.provider === 'claude-code');
// five_hour, seven_day, seven_day_opus, seven_day_sonnet
expect(row?.quotaWindows).toHaveLength(4);
const opus = row?.quotaWindows?.find((w) => w.key === 'seven_day_opus');
expect(opus?.label).toBe('Opus · week');
expect(opus?.usedPercent).toBe(25);
expect(opus?.remainingPercent).toBe(75);
expect(opus?.windowMinutes).toBe(10080);
const sonnet = row?.quotaWindows?.find((w) => w.key === 'seven_day_sonnet');
expect(sonnet?.label).toBe('Sonnet · week');
expect(sonnet?.usedPercent).toBe(60);
expect(sonnet?.remainingPercent).toBe(40);
});
it('emits a reauth error row on 401/needsReauth', async () => {
const clock = { now: 1_000_000 };
const deps = makeDeps(async () => reauthQuota(), clock);
@@ -288,6 +361,25 @@ describe('Codex path', () => {
nextReset: '2026-06-09T19:00:00.000Z',
tier: 'pro',
stale: false,
staleAsOf: null,
windows: [
{
key: 'five_hour',
label: '5h',
usedPercent: 19,
remainingPercent: 81,
resetAt: '2026-06-09T19:00:00.000Z',
windowMinutes: 300,
},
{
key: 'seven_day',
label: 'week',
usedPercent: 48,
remainingPercent: 52,
resetAt: '2026-06-14T00:00:00.000Z',
windowMinutes: 10080,
},
],
}),
now: () => clock.now,
};
@@ -297,6 +389,9 @@ describe('Codex path', () => {
expect(row?.quota_percentage).toBe(52);
expect(row?.tier).toBe('pro');
expect(row?.health).toBe('ok');
expect(row?.quotaWindows).toHaveLength(2);
expect(row?.quotaWindows?.[0].windowMinutes).toBe(300);
expect(row?.staleAsOf).toBeUndefined();
});
it('flags health warning when the Codex source is stale', async () => {
@@ -308,11 +403,16 @@ describe('Codex path', () => {
nextReset: null,
tier: null,
stale: true,
staleAsOf: '2026-06-09T13:30:00.000Z',
windows: [],
}),
now: () => clock.now,
};
const rows = await getNativeAccountRows(deps);
expect(rows.find((r) => r.provider === 'codex')?.health).toBe('warning');
const row = rows.find((r) => r.provider === 'codex');
expect(row?.health).toBe('warning');
// staleAsOf flows through so the bar can render the freshness footnote.
expect(row?.staleAsOf).toBe('2026-06-09T13:30:00.000Z');
});
it('omits the codex row when there is no rate_limits (exec-mode)', async () => {