mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 00:26:41 +03:00
Add experimental Codex Live worker
Introduce a Live-owned app-server supervisor with progressive fenced publishing, partitioned control polling, cancellation and recovery safety, and measured integration coverage. AI-assisted implementation under maintainer direction.
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { createCodexAppServerClient } from './live/codex-app-server-client.mjs';
|
||||
import {
|
||||
codexWorkerStateIsOwned,
|
||||
resolveCodexWorkerConfig,
|
||||
} from './live/codex-worker.mjs';
|
||||
import { CodexLiveWorkerSupervisor } from './live/codex-worker-supervisor.mjs';
|
||||
import {
|
||||
getLiveCodexWorkerStatePath,
|
||||
readLiveServerInfo,
|
||||
resolveLiveConfigPath,
|
||||
} from './lib/impeccable-paths.mjs';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const cwd = process.cwd();
|
||||
const scriptPath = fileURLToPath(import.meta.url);
|
||||
const scriptsDir = path.dirname(scriptPath);
|
||||
const statePath = getLiveCodexWorkerStatePath(cwd);
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(`Usage: node live-codex-worker.mjs [--background | --status | --stop]
|
||||
|
||||
Experimental, Codex-only Live generation supervisor. It owns a separate
|
||||
app-server process and dedicated worker thread; it never attaches to the
|
||||
foreground desktop task.
|
||||
|
||||
Opt in explicitly for this Codex process with IMPECCABLE_LIVE_CODEX_WORKER=1.
|
||||
Project config may tune the worker but cannot activate it across harnesses.
|
||||
|
||||
Optional environment:
|
||||
IMPECCABLE_LIVE_CODEX_MODEL Model override; otherwise Spark/mini/default is selected dynamically
|
||||
IMPECCABLE_LIVE_CODEX_EFFORT Reasoning effort override (default: low)
|
||||
IMPECCABLE_CODEX_PATH Codex binary path (default: codex)
|
||||
|
||||
Without the opt-in this command exits without polling, leaving the portable
|
||||
foreground Live path unchanged.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args.includes('--status')) {
|
||||
const state = readJson(statePath);
|
||||
console.log(JSON.stringify(state
|
||||
? { ...state, reachable: pidReachable(state.pid) }
|
||||
: { ok: false, status: 'not_started' }));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args.includes('--stop')) {
|
||||
const state = readJson(statePath);
|
||||
if (state?.pid && !codexWorkerStateIsOwned(state, cwd)) {
|
||||
console.log(JSON.stringify({
|
||||
ok: false,
|
||||
status: 'not_stopped',
|
||||
error: 'codex_worker_state_unowned',
|
||||
}));
|
||||
process.exitCode = 2;
|
||||
process.exit();
|
||||
}
|
||||
if (!state?.pid || !pidReachable(state.pid)) {
|
||||
console.log(JSON.stringify({ ok: true, status: 'not_running' }));
|
||||
process.exit(0);
|
||||
}
|
||||
process.kill(state.pid, 'SIGTERM');
|
||||
const stopped = await waitFor(
|
||||
() => !pidReachable(state.pid),
|
||||
positiveInteger(process.env.IMPECCABLE_LIVE_CODEX_STOP_TIMEOUT_MS, 5_000),
|
||||
);
|
||||
if (!stopped) {
|
||||
console.log(JSON.stringify({ ok: false, status: 'stop_timeout', pid: state.pid }));
|
||||
process.exitCode = 2;
|
||||
process.exit();
|
||||
}
|
||||
console.log(JSON.stringify({ ok: true, status: 'stopped', pid: state.pid }));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const liveConfig = readLiveConfig(cwd);
|
||||
const config = resolveCodexWorkerConfig({ env: process.env, liveConfig });
|
||||
if (!config.enabled) {
|
||||
console.log(JSON.stringify({
|
||||
ok: false,
|
||||
error: 'codex_worker_disabled',
|
||||
fallback: 'foreground',
|
||||
}));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args.includes('--background')) {
|
||||
const existing = readJson(statePath);
|
||||
if (codexWorkerStateIsOwned(existing, cwd)
|
||||
&& existing?.pid
|
||||
&& pidReachable(existing.pid)
|
||||
&& ['ready', 'working'].includes(existing.status)) {
|
||||
console.log(JSON.stringify({ ...existing, ok: true, reused: true }));
|
||||
process.exit(0);
|
||||
}
|
||||
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
||||
const logPath = path.join(path.dirname(statePath), 'codex-worker.log');
|
||||
const logFd = fs.openSync(logPath, 'a');
|
||||
const child = spawn(process.execPath, [scriptPath, '--foreground'], {
|
||||
cwd,
|
||||
env: process.env,
|
||||
detached: true,
|
||||
stdio: ['ignore', logFd, logFd],
|
||||
});
|
||||
child.unref();
|
||||
fs.closeSync(logFd);
|
||||
const ready = await waitFor(() => {
|
||||
const state = readJson(statePath);
|
||||
if (state?.pid !== child.pid) return null;
|
||||
if (state.status === 'error') return state;
|
||||
return ['ready', 'working'].includes(state.status) ? state : null;
|
||||
}, positiveInteger(process.env.IMPECCABLE_LIVE_CODEX_START_TIMEOUT_MS, 12_000));
|
||||
if (!ready || ready.status === 'error') {
|
||||
let terminated = true;
|
||||
if (pidReachable(child.pid)) {
|
||||
process.kill(child.pid, 'SIGTERM');
|
||||
terminated = Boolean(await waitFor(
|
||||
() => !pidReachable(child.pid),
|
||||
positiveInteger(process.env.IMPECCABLE_LIVE_CODEX_STOP_TIMEOUT_MS, 2_000),
|
||||
));
|
||||
}
|
||||
console.log(JSON.stringify({
|
||||
ok: false,
|
||||
error: ready?.error || 'codex_worker_start_timeout',
|
||||
fallback: terminated ? 'foreground' : null,
|
||||
terminated,
|
||||
childPid: child.pid,
|
||||
logPath,
|
||||
}));
|
||||
process.exitCode = 2;
|
||||
} else {
|
||||
console.log(JSON.stringify({ ...ready, ok: true, logPath }));
|
||||
}
|
||||
process.exit();
|
||||
}
|
||||
|
||||
await runForeground();
|
||||
|
||||
async function runForeground() {
|
||||
const server = readLiveServerInfo(cwd)?.info;
|
||||
if (!server?.port || !server?.token) {
|
||||
writeState({ ok: false, status: 'error', error: 'live_server_not_running' });
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const client = createCodexAppServerClient({
|
||||
command: config.codexPath,
|
||||
cwd,
|
||||
requestTimeoutMs: 30_000,
|
||||
turnTimeoutMs: 240_000,
|
||||
clientInfo: {
|
||||
name: 'impeccable_live',
|
||||
title: 'Impeccable Live dedicated worker',
|
||||
version: '0.1.0',
|
||||
},
|
||||
});
|
||||
const supervisor = new CodexLiveWorkerSupervisor({
|
||||
cwd,
|
||||
base: `http://localhost:${server.port}`,
|
||||
token: server.token,
|
||||
client,
|
||||
config,
|
||||
statePath,
|
||||
scriptsDir,
|
||||
log: (message) => process.stderr.write(`[impeccable-codex-worker] ${message}\n`),
|
||||
});
|
||||
let shuttingDown = false;
|
||||
const shutdown = async () => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
await supervisor.shutdown({ archive: true }).catch(() => {});
|
||||
process.exit(0);
|
||||
};
|
||||
process.once('SIGINT', shutdown);
|
||||
process.once('SIGTERM', shutdown);
|
||||
try {
|
||||
await supervisor.initialize();
|
||||
await supervisor.run();
|
||||
} catch (error) {
|
||||
writeState({
|
||||
ok: false,
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
await supervisor.shutdown().catch(() => {});
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function readLiveConfig(projectCwd) {
|
||||
const configPath = resolveLiveConfigPath({ cwd: projectCwd, scriptsDir });
|
||||
return readJson(configPath) || {};
|
||||
}
|
||||
|
||||
function writeState(value) {
|
||||
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
||||
const state = {
|
||||
cwd: path.resolve(cwd),
|
||||
pid: process.pid,
|
||||
updatedAt: new Date().toISOString(),
|
||||
...value,
|
||||
};
|
||||
const temporary = `${statePath}.${process.pid}.${Date.now()}.tmp`;
|
||||
fs.writeFileSync(temporary, JSON.stringify(state, null, 2) + '\n', 'utf-8');
|
||||
fs.renameSync(temporary, statePath);
|
||||
}
|
||||
|
||||
function readJson(file) {
|
||||
try { return JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { return null; }
|
||||
}
|
||||
|
||||
function pidReachable(pid) {
|
||||
if (!Number.isInteger(pid) || pid < 1) return false;
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error?.code === 'EPERM';
|
||||
}
|
||||
}
|
||||
|
||||
async function waitFor(check, timeoutMs) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
const result = check();
|
||||
if (result) return result;
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function positiveInteger(value, fallback) {
|
||||
const parsed = Number(value);
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
Reference in New Issue
Block a user