feat(primer): hold off-slot Codex turns so the fixed-slot primer anchors the reset

The usage primer fires at 08/13/18/23 KST but could not pin the Codex 5h
reset because reviewer/arbiter turns on the same shared account ran off-slot
and anchored the window first. Add a time-based hold
(shouldHoldCodexForPrimerAlignment) that keeps non-primer Codex turns quiet
during the 04-08 KST dawn gap and for a few minutes after each slot, so the
scheduled primer wins the first-request race. Applied at the reviewer/arbiter
dispatch site (the actual off-slot consumer here) and for codex-owner rooms;
urgent @-mention turns bypass it. Time-based by design — the usage/reset
figures are exactly the unreliable data, so a blunt clock rule is predictable
and testable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-09 14:31:01 +09:00
parent efc8c00380
commit e324a1baa1
5 changed files with 175 additions and 11 deletions

View File

@@ -9,7 +9,45 @@ vi.mock('./session-commands.js', () => ({
handleSessionCommand: handleSessionCommandMock,
}));
import { handleQueuedRunGates } from './message-runtime-gating.js';
import {
handleQueuedRunGates,
msSincePreviousPrimerSlotKST,
shouldHoldCodexForPrimerAlignment,
} from './message-runtime-gating.js';
// nowMs such that the shifted KST clock reads the given hour/minute on a day.
const KST_OFFSET = 9 * 60 * 60 * 1000;
const atKst = (hour: number, minute = 0): number =>
Date.UTC(2026, 5, 9, hour, minute) - KST_OFFSET;
describe('primer-alignment hold timing', () => {
it('measures ms since the most recent fixed slot', () => {
expect(msSincePreviousPrimerSlotKST(atKst(8, 0))).toBe(0);
expect(msSincePreviousPrimerSlotKST(atKst(13, 30))).toBe(30 * 60 * 1000);
// Before the day's first slot → previous slot was yesterday's 23:00.
expect(msSincePreviousPrimerSlotKST(atKst(2, 0))).toBe(3 * 60 * 60 * 1000);
});
it('holds during the 0408 KST dawn gap', () => {
expect(shouldHoldCodexForPrimerAlignment(atKst(4, 0))).toBe(true);
expect(shouldHoldCodexForPrimerAlignment(atKst(5, 0))).toBe(true);
expect(shouldHoldCodexForPrimerAlignment(atKst(7, 59))).toBe(true);
expect(shouldHoldCodexForPrimerAlignment(atKst(3, 59))).toBe(false);
});
it('holds briefly right after each fixed slot, then releases', () => {
expect(shouldHoldCodexForPrimerAlignment(atKst(8, 0))).toBe(true);
expect(shouldHoldCodexForPrimerAlignment(atKst(13, 2))).toBe(true);
expect(shouldHoldCodexForPrimerAlignment(atKst(13, 6))).toBe(false);
expect(shouldHoldCodexForPrimerAlignment(atKst(18, 4))).toBe(true);
expect(shouldHoldCodexForPrimerAlignment(atKst(23, 30))).toBe(false);
});
it('does not hold mid-window away from slots and the dawn gap', () => {
expect(shouldHoldCodexForPrimerAlignment(atKst(15, 0))).toBe(false);
expect(shouldHoldCodexForPrimerAlignment(atKst(2, 0))).toBe(false);
});
});
describe('message-runtime-gating', () => {
const baseArgs = {