Files
EJClaw/src/usage-primer.test.ts
Codex 4ddaa88f8a fix(primer): force a Codex command at every fixed slot (bypass warm-up skip gates)
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>
2026-06-09 15:17:59 +09:00

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 },
);
});
});