Something went wrong. Try again.
presents omp as codex for use with chatgpt remote
Something went wrong. Try again.
5.9 kB · 151 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152import fs from "node:fs";import fsp from "node:fs/promises";import os from "node:os";import path from "node:path";import { execFile, spawn } from "node:child_process";import { promisify } from "node:util";import { sleep, socketAccepting } from "./util.mjs";
const execFileAsync = promisify(execFile);
export function runtimePaths() { const root = process.env.OMP_CODEX_RUNTIME_DIR || path.join(os.homedir(), ".omp", "codex-bridge", "run"); return { root, socket: process.env.OMP_CODEX_SOCKET || path.join(root, "app-server.sock"), pid: path.join(root, "app-server.pid"), log: path.join(root, "app-server.log"), };}
/** * Liveness is decided by the socket, not the pid file. * * The pid file goes stale whenever a daemon dies without cleaning up, and a * stale pid previously made `start`/`stop` treat a perfectly live daemon as * absent - unlinking its socket, orphaning it with every running turn still * inside it, and standing up a replacement that knew nothing about that work. * From a client's perspective its sessions simply stopped. */export async function daemonStatus(paths = runtimePaths()) { let pid = null; try { pid = Number((await fsp.readFile(paths.pid, "utf8")).trim()); } catch {} let pidAlive = pid ? processAlive(pid) : false; let socket = false; try { socket = (await fsp.lstat(paths.socket)).isSocket(); } catch {} const reachable = socket ? await socketAccepting(paths.socket) : false; if (reachable && !pidAlive) { const discoveredPid = await discoverBridgeSocketOwner(paths.socket); if (discoveredPid) { pid = discoveredPid; pidAlive = true; await fsp.writeFile(paths.pid, `${pid}\n`, { mode: 0o600 }).catch(() => {}); } } return { running: reachable || pidAlive, reachable, pid: pidAlive ? pid : null, socket, socketPath: paths.socket, logPath: paths.log, };}
export async function startDaemon({ executable, backend } = {}) { const paths = runtimePaths(); await fsp.mkdir(paths.root, { recursive: true, mode: 0o700 }); const existing = await daemonStatus(paths); if (existing.reachable) return existing; // Only a socket with no listener is safe to remove; unlinking a reachable // one strands whichever daemon is still serving it. if (existing.pid && !existing.running) await fsp.unlink(paths.pid).catch(() => {}); if (existing.socket && !existing.reachable) await fsp.unlink(paths.socket).catch(() => {});
const logFd = fs.openSync(paths.log, "a", 0o600); const child = spawn(process.execPath, [executable, "app-server", "--listen", `unix://${paths.socket}`], { detached: true, stdio: ["ignore", logFd, logFd], env: { ...process.env, ...(backend ? { OMP_CODEX_BACKEND: backend } : {}), OMP_CODEX_DAEMON: "1" }, }); child.unref(); fs.closeSync(logFd); await fsp.writeFile(paths.pid, `${child.pid}\n`, { mode: 0o600 }); const deadline = Date.now() + 10_000; while (Date.now() < deadline) { const status = await daemonStatus(paths); if (status.running && status.socket) return status; if (!processAlive(child.pid)) break; await sleep(50); } const tail = await fsp.readFile(paths.log, "utf8").catch(() => ""); throw new Error(`app-server daemon did not become ready${tail ? `\n${tail.slice(-4000)}` : ""}`);}
export async function stopDaemon() { const paths = runtimePaths(); const status = await daemonStatus(paths); if (!status.reachable) { // Nothing is serving this path, so the leftovers are safe to clear. await Promise.all([fsp.unlink(paths.pid).catch(() => {}), fsp.unlink(paths.socket).catch(() => {})]); return { stopped: false, ...status }; } if (!status.pid) { // Reachable but unowned as far as the pid file knows. Removing the socket // here is what creates an invisible daemon, so refuse and say why. return { stopped: false, reachable: true, pid: null, socketPath: paths.socket, error: "A daemon is serving the socket but its pid is unknown; refusing to orphan it. Find it with: lsof -U | grep app-server.sock", }; } process.kill(status.pid, "SIGTERM"); const deadline = Date.now() + 5_000; while (Date.now() < deadline && processAlive(status.pid)) await sleep(50); if (processAlive(status.pid)) process.kill(status.pid, "SIGKILL"); if (await socketAccepting(paths.socket)) { // Someone else answers here: the pid file named the wrong process. return { stopped: false, reachable: true, pid: status.pid, socketPath: paths.socket, error: "Killed the recorded pid but the socket still answers; another daemon owns it.", }; } await Promise.all([fsp.unlink(paths.pid).catch(() => {}), fsp.unlink(paths.socket).catch(() => {})]); return { stopped: true, pid: status.pid, socketPath: paths.socket };}
async function discoverBridgeSocketOwner(socketPath) { try { const { stdout } = await execFileAsync("lsof", ["-n", "-P", "-t", "--", socketPath], { timeout: 1_000, maxBuffer: 64 * 1024, }); const candidates = Array.from(new Set( stdout.split(/\s+/).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0 && processAlive(pid)) )); const owners = []; for (const candidate of candidates) { const { stdout: command } = await execFileAsync("ps", ["-o", "command=", "-p", String(candidate)], { timeout: 1_000, maxBuffer: 64 * 1024, }); if (/(?:^|\s|\/)codex(?:\.mjs)?\s+app-server(?:\s|$)/.test(command.trim())) { owners.push(candidate); } } return owners.length === 1 ? owners[0] : null; } catch { return null; }}
function processAlive(pid) { if (!Number.isInteger(pid) || pid <= 0) return false; try { process.kill(pid, 0); return true; } catch (error) { return error?.code === "EPERM"; }}