import { spawn } from "node:child_process"; function nonEmpty(value) { const text = String(value ?? "").trim(); return text || undefined; } function parseBoolean(value, fallback = false) { if (value === undefined || value === null || value === "") return fallback; if (value === false || value === "false" || value === "0" || value === 0) return false; return true; } function normalizeArgs(value, fallback = []) { if (!Array.isArray(value)) return [...fallback]; return value.map((item) => nonEmpty(item)).filter(Boolean); } function redactSensitiveText(value) { return String(value ?? "") .replace(/sk-[A-Za-z0-9_-]{8,}/g, "[REDACTED]") .replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]") .replace(/token["']?\s*[:=]\s*["']?[^"',\s}]+/gi, "token:[REDACTED]") .slice(0, 1200); } function parseEnvArgs(value) { const text = nonEmpty(value); if (!text) return undefined; try { const parsed = JSON.parse(text); if (Array.isArray(parsed)) { return parsed.map((item) => nonEmpty(item)).filter(Boolean); } } catch { // Fall back to whitespace parsing for simple local overrides. } return text.split(/\s+/).map((item) => nonEmpty(item)).filter(Boolean); } export function getCodexRemoteControlDaemonConfig(env = process.env, config = {}) { const appServerEnabled = parseBoolean(config.codexAppServerEnabled, false); const enabled = parseBoolean( env.BOSS_CODEX_REMOTE_CONTROL_ENABLED ?? config.codexRemoteControlEnabled, appServerEnabled, ); const command = nonEmpty(env.BOSS_CODEX_REMOTE_CONTROL_COMMAND) ?? nonEmpty(config.codexRemoteControlCommand) ?? nonEmpty(config.codexAppServerCommand) ?? "codex"; const startArgs = normalizeArgs( parseEnvArgs(env.BOSS_CODEX_REMOTE_CONTROL_START_ARGS) ?? config.codexRemoteControlStartArgs ?? config.codexRemoteControlArgs, ["remote-control", "start", "--json"], ); const stopArgs = normalizeArgs( parseEnvArgs(env.BOSS_CODEX_REMOTE_CONTROL_STOP_ARGS) ?? config.codexRemoteControlStopArgs, ["remote-control", "stop", "--json"], ); const timeoutMs = Number(env.BOSS_CODEX_REMOTE_CONTROL_TIMEOUT_MS ?? config.codexRemoteControlTimeoutMs ?? 15_000); return { enabled, command, startArgs, stopArgs, timeoutMs: Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 15_000, }; } function runCommand(command, args, options = {}) { return new Promise((resolve) => { const child = spawn(command, args, { cwd: options.cwd, env: options.env, stdio: ["ignore", "pipe", "pipe"], }); let stdout = ""; let stderr = ""; let timedOut = false; const timer = setTimeout(() => { timedOut = true; child.kill("SIGKILL"); }, options.timeoutMs ?? 15_000); child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); child.on("error", (error) => { clearTimeout(timer); resolve({ ok: false, exitCode: null, stdout, stderr: error.message, timedOut }); }); child.on("close", (code) => { clearTimeout(timer); resolve({ ok: code === 0 && !timedOut, exitCode: code, stdout, stderr, timedOut }); }); }); } function parseJsonOutput(stdout) { const text = String(stdout ?? "").trim(); if (!text) return null; try { return JSON.parse(text); } catch { return null; } } export async function runCodexRemoteControlDaemonAction(action, config = {}, options = {}) { if (action !== "start" && action !== "stop") { throw new Error(`Unsupported codex remote-control action: ${action}`); } const daemonConfig = getCodexRemoteControlDaemonConfig(options.env ?? process.env, config); if (!daemonConfig.enabled) { return { status: "failed", action, commandLabel: "", outputSummary: "Codex Remote Control daemon is disabled.", error: "CODEX_REMOTE_CONTROL_DISABLED", }; } const args = action === "start" ? daemonConfig.startArgs : daemonConfig.stopArgs; const commandResult = await runCommand(daemonConfig.command, args, { cwd: nonEmpty(config.codexRemoteControlWorkdir) ?? nonEmpty(config.codexAppServerWorkdir), env: options.env ?? process.env, timeoutMs: daemonConfig.timeoutMs, }); const outputSummary = redactSensitiveText(commandResult.stdout || commandResult.stderr); const parsedOutput = parseJsonOutput(outputSummary); return { status: commandResult.ok ? "completed" : "failed", action, exitCode: commandResult.exitCode, timedOut: commandResult.timedOut, commandLabel: [daemonConfig.command, ...args].join(" "), outputSummary, ...(parsedOutput && typeof parsedOutput === "object" ? { parsedOutput } : {}), }; }