The scheduled primer reused the opportunistic warm-up gates, so the 18:00 slot (exactly 5h after 13:00, but firing a few hundred ms later) fell just under minIntervalMs and was silently skipped as no_eligible_accounts — the primer did not actually fire at every slot. Add a forceAttempt path: the fixed-slot primer bypasses stagger, post-failure cooldown, min-interval, and per-account usage/rate-limit filters, attempting a real Codex command on the active account at every 08/13/18/23 slot and recording success/failure. Reviewer/arbiter holding stays removed (per user). This does not pin the reset clock — a trailing window still slides with later usage — but it guarantees the "simple task at every fixed slot" the user asked for. Verified: typecheck, build, bundle-smoke; new regression tests (13:00 success then sub-5h 18:00 still fires; control without forceAttempt is skipped; forceAttempt bypasses cooldown/stagger) + usage-primer/codex-warmup suites pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
78 lines
2.0 KiB
TypeScript
78 lines
2.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
const callOrder: string[] = [];
|
|
|
|
const refreshAllCodexAccountUsage = vi.fn(async () => {
|
|
callOrder.push('refreshAll');
|
|
return { rows: [], fetchedAt: '2026-06-01T00:00:00.000Z' };
|
|
});
|
|
const refreshActiveCodexUsage = vi.fn(async () => {
|
|
callOrder.push('refreshActive');
|
|
return { rows: [], fetchedAt: '2026-06-01T00:00:01.000Z' };
|
|
});
|
|
const runCodexWarmupCycle = vi.fn(async () => {
|
|
callOrder.push('warmup');
|
|
return { status: 'warmed', accountIndex: 0 };
|
|
});
|
|
|
|
vi.mock('./config.js', () => ({
|
|
CODEX_WARMUP_CONFIG: {
|
|
enabled: false,
|
|
prompt: 'Reply exactly OK. Do not run tools.',
|
|
model: 'gpt-5.5',
|
|
intervalMs: 300_000,
|
|
minIntervalMs: 18_300_000,
|
|
staggerMs: 1_800_000,
|
|
maxUsagePct: 0,
|
|
maxD7UsagePct: 0,
|
|
commandTimeoutMs: 120_000,
|
|
failureCooldownMs: 21_600_000,
|
|
maxConsecutiveFailures: 2,
|
|
},
|
|
}));
|
|
|
|
vi.mock('./logger.js', () => ({
|
|
logger: {
|
|
debug: vi.fn(),
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock('./token-rotation.js', () => ({
|
|
getAllTokens: vi.fn(() => []),
|
|
}));
|
|
|
|
vi.mock('./codex-usage-collector.js', () => ({
|
|
refreshAllCodexAccountUsage,
|
|
refreshActiveCodexUsage,
|
|
}));
|
|
|
|
vi.mock('./codex-warmup.js', () => ({
|
|
runCodexWarmupCycle,
|
|
}));
|
|
|
|
describe('usage-primer', () => {
|
|
it('refreshes Codex usage before primer selection and fires regardless of usage level', async () => {
|
|
callOrder.length = 0;
|
|
refreshAllCodexAccountUsage.mockClear();
|
|
refreshActiveCodexUsage.mockClear();
|
|
runCodexWarmupCycle.mockClear();
|
|
|
|
const { runCodexPrimerCycle } = await import('./usage-primer.js');
|
|
await runCodexPrimerCycle();
|
|
|
|
expect(callOrder).toEqual(['refreshAll', 'refreshActive', 'warmup']);
|
|
expect(runCodexWarmupCycle).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
enabled: true,
|
|
minIntervalMs: 18_000_000,
|
|
maxUsagePct: 100,
|
|
maxD7UsagePct: 100,
|
|
}),
|
|
{ ignoreZeroUsageWindow: true, forceAttempt: true },
|
|
);
|
|
});
|
|
});
|