Prewarm Codex Live with safe fallback

Return after a durable starting record, overlap app-server initialization with page startup, dynamically reclaim generation after worker failure, and cap hard-crash leases at 15 seconds.\n\nAI-assisted: OpenAI Codex.
This commit is contained in:
Paul Bakaus
2026-07-12 21:11:50 -07:00
parent 6f3076051f
commit 6ed43ca682
9 changed files with 170 additions and 24 deletions
+47 -10
View File
@@ -10,10 +10,12 @@
*/
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { completionAckForAcceptResult, completionTypeForAcceptResult } from './live/completion.mjs';
import { readLiveServerInfo } from './lib/impeccable-paths.mjs';
import { getLiveCodexWorkerStatePath, readLiveServerInfo } from './lib/impeccable-paths.mjs';
import { codexWorkerProcessStateIsOwned } from './live/codex-worker.mjs';
// Absolute path to a sibling script in this skill's scripts dir, so runtime
// error hints print a directly-runnable command instead of a placeholder.
@@ -152,7 +154,13 @@ export async function waitForEventAck(base, token, eventId, {
return false;
}
export async function fetchNextEvent(base, token, { totalDeadline, types } = {}) {
export async function fetchNextEvent(base, token, {
totalDeadline,
types,
resolveTypes,
perRequestTimeoutMs = PER_REQUEST_TIMEOUT_MS,
leaseMs = DEFAULT_EVENT_LEASE_MS,
} = {}) {
while (true) {
if (totalDeadline && Date.now() >= totalDeadline) {
return { type: 'timeout' };
@@ -161,13 +169,13 @@ export async function fetchNextEvent(base, token, { totalDeadline, types } = {})
const remaining = totalDeadline
? totalDeadline - Date.now()
: PER_REQUEST_TIMEOUT_MS;
const slice = Math.min(Math.max(remaining, 1000), PER_REQUEST_TIMEOUT_MS);
const slice = Math.min(Math.max(remaining, 1000), perRequestTimeoutMs);
const query = new URLSearchParams({
token,
timeout: String(slice),
leaseMs: String(DEFAULT_EVENT_LEASE_MS),
leaseMs: String(leaseMs),
});
const normalizedTypes = normalizePollTypes(types);
const normalizedTypes = normalizePollTypes(resolveTypes ? await resolveTypes() : types);
if (normalizedTypes.length > 0) query.set('types', normalizedTypes.join(','));
const res = await fetch(`${base}/poll?${query}`);
@@ -253,9 +261,9 @@ export function printPollEvent(event) {
console.log(JSON.stringify(event));
}
export async function runPollOnce(base, token, { totalTimeout = 600_000, types } = {}) {
export async function runPollOnce(base, token, { totalTimeout = 600_000, types, resolveTypes, perRequestTimeoutMs } = {}) {
const deadline = Date.now() + totalTimeout;
const event = await fetchNextEvent(base, token, { totalDeadline: deadline, types });
const event = await fetchNextEvent(base, token, { totalDeadline: deadline, types, resolveTypes, perRequestTimeoutMs });
await augmentEventWithAcceptHandling(event, base, token);
writeCarbonizeBanner(event);
printPollEvent(event);
@@ -267,11 +275,13 @@ export async function runPollStream(base, token, {
ackPollIntervalMs = 400,
shouldContinue = () => true,
types,
resolveTypes,
perRequestTimeoutMs,
} = {}) {
process.stderr.write('[impeccable-poll] stream mode: one JSON object per line on stdout; use --reply while this process stays running\n');
while (shouldContinue()) {
const event = await fetchNextEvent(base, token, { types });
const event = await fetchNextEvent(base, token, { types, resolveTypes, perRequestTimeoutMs });
await augmentEventWithAcceptHandling(event, base, token);
writeCarbonizeBanner(event);
printPollEvent(event);
@@ -332,6 +342,8 @@ Modes:
Options:
--timeout=MS One-shot poll timeout in ms (default: 600000). Ignored in --stream mode
--types=A,B Lease only these event types (used by partitioned Codex control lane)
--codex-worker-fallback
Add generation events only if the dedicated Codex worker fails or exits
--ack-timeout=MS Stream mode: max wait for --reply after generate/steer (default: 600000)
--file PATH Attach a source file path to the reply (generate/steer flow)
--data JSON Attach a JSON result object to the reply (manual_edit_apply flow). Must be valid JSON
@@ -372,18 +384,21 @@ Harness note:
const streamMode = args.includes('--stream');
const typesArg = args.find((a) => a.startsWith('--types='));
const types = normalizePollTypes(typesArg ? typesArg.slice('--types='.length) : null);
const workerFallback = args.includes('--codex-worker-fallback');
const resolveTypes = workerFallback ? () => resolveCodexWorkerFallbackTypes(types) : null;
const perRequestTimeoutMs = workerFallback ? 2_000 : undefined;
const ackTimeoutArg = args.find((a) => a.startsWith('--ack-timeout='));
const ackTimeoutMs = ackTimeoutArg ? parseInt(ackTimeoutArg.split('=')[1], 10) : 600_000;
try {
if (streamMode) {
await runPollStream(base, info.token, { ackTimeoutMs, types });
await runPollStream(base, info.token, { ackTimeoutMs, types, resolveTypes, perRequestTimeoutMs });
return;
}
const timeoutArg = args.find((a) => a.startsWith('--timeout='));
const totalTimeout = timeoutArg ? parseInt(timeoutArg.split('=')[1], 10) : 600_000;
await runPollOnce(base, info.token, { totalTimeout, types });
await runPollOnce(base, info.token, { totalTimeout, types, resolveTypes, perRequestTimeoutMs });
} catch (err) {
handlePollError(err);
}
@@ -394,6 +409,28 @@ export function normalizePollTypes(value) {
return [...new Set(values.map((type) => String(type).trim()).filter(Boolean))];
}
export function resolveCodexWorkerFallbackTypes(baseTypes, {
cwd = process.cwd(),
state = readJson(getLiveCodexWorkerStatePath(cwd)),
isPidReachable = pidReachable,
} = {}) {
const base = normalizePollTypes(baseTypes);
const workerOwnsGeneration = codexWorkerProcessStateIsOwned(state, cwd)
&& ['starting', 'ready', 'working'].includes(state?.status)
&& isPidReachable(state?.pid);
if (workerOwnsGeneration) return base;
return normalizePollTypes([...base, 'generate', 'accept', 'discard', 'prefetch']);
}
function readJson(file) {
try { return JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { return null; }
}
function pidReachable(pid) {
if (!Number.isInteger(pid) || pid <= 0) return false;
try { process.kill(pid, 0); return true; } catch { return false; }
}
// Auto-execute when run directly
const _running = process.argv[1];
if (_running?.endsWith('live-poll.mjs') || _running?.endsWith('live-poll.mjs/')) {