feat(primer): broaden Codex alignment hold to all real consumers; pre+post slot window
Address review gaps in the primer-alignment hold so the fixed-slot primer reliably wins the first-request race for the shared Codex window: - Extract the time-based hold into a dependency-free leaf module (codex-primer-alignment.ts) so every Codex path can share it without import cycles. - Add a pre-slot hold window (2 min) in addition to the post-slot (5 min) and the 04-08 KST dawn gap, closing the "request a beat before the slot" gap. - Hold the dashboard's periodic Codex warm-up too: runCodexWarmupCycle now skips during the hold unless called with isPrimer (the scheduled primer sets it), so the warm-up can't anchor the window ahead of the primer. - Remove the urgent @-mention bypass on the reviewer/arbiter and owner holds — the reset is anchored unconditionally as requested. - Reviewer/arbiter hold resumes exactly once after the window via the existing per-revision claim (poll re-detect; no double run). Note: the unified lease boundary (syncHostCodexSessionFiles) is synchronous and throws a terminal "Codex unavailable", so it can't host a clean defer+resume; gating stays at the async turn-scheduling layer that can re-queue. Honest caveat unchanged: making the primer first is necessary but not sufficient to pin a trailing reset. Verified: typecheck, build, bundle-smoke; full suite 1482 passing with only the 9 pre-existing env-config baseline failures (service-routing/paired-context/ migrate-room owner=claude vs codex-main); new alignment + warmup-hold tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import os from 'os';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { shouldHoldCodexForPrimerAlignment } from './codex-primer-alignment.js';
|
||||
import { DATA_DIR } from './config.js';
|
||||
import type { AppConfig } from './config/schema.js';
|
||||
import {
|
||||
@@ -36,6 +37,13 @@ interface CodexWarmupRuntimeOptions {
|
||||
statePath?: string;
|
||||
shouldSkip?: () => boolean;
|
||||
ignoreZeroUsageWindow?: boolean;
|
||||
/**
|
||||
* Set by the scheduled usage primer so its call is exempt from the
|
||||
* primer-alignment hold. Any other warm-up caller (e.g. the dashboard's
|
||||
* periodic warm-up) is held during the slot windows so it can't anchor the
|
||||
* shared Codex window ahead of the primer.
|
||||
*/
|
||||
isPrimer?: boolean;
|
||||
}
|
||||
|
||||
export type CodexWarmupCycleResult =
|
||||
@@ -317,6 +325,12 @@ export async function runCodexWarmupCycle(
|
||||
return { status: 'skipped', reason: 'runtime_busy' };
|
||||
|
||||
const nowMs = runtime.nowMs ?? Date.now();
|
||||
// Hold every non-primer warm-up during the slot windows so it can't anchor
|
||||
// the shared Codex 5h window ahead of the scheduled primer (which passes
|
||||
// isPrimer). Skipping is safe — a warm-up is best-effort.
|
||||
if (!runtime.isPrimer && shouldHoldCodexForPrimerAlignment(nowMs)) {
|
||||
return { status: 'skipped', reason: 'primer_alignment_hold' };
|
||||
}
|
||||
const nowIso = new Date(nowMs).toISOString();
|
||||
const statePath = runtime.statePath ?? DEFAULT_STATE_FILE;
|
||||
const state = readWarmupState(statePath);
|
||||
|
||||
Reference in New Issue
Block a user