Files
boss/local-agent/codex-desktop-refresh-bridge.mjs

336 lines
10 KiB
JavaScript

import { spawn } from "node:child_process";
import path from "node:path";
function parseBoolean(value) {
return String(value || "").trim().toLowerCase() === "true";
}
function parseArgs(value) {
return String(value || "")
.trim()
.split(/\s+/)
.filter(Boolean);
}
function parseTimeoutMs(value) {
const parsed = Number.parseInt(String(value || ""), 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 3000;
}
function parseNonNegativeInteger(value, fallback) {
const parsed = Number.parseInt(String(value ?? ""), 10);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function trimToDefined(value) {
const trimmed = String(value ?? "").trim();
return trimmed ? trimmed : undefined;
}
function pickConfigValue(config, key, fallback) {
if (config && config[key] !== undefined && config[key] !== null && `${config[key]}`.trim() !== "") {
return config[key];
}
return fallback;
}
function resolveCommandArgs(command, args, cwd) {
const runtimeName = path.basename(command || "").toLowerCase();
const scriptRuntimes = new Set([
"node",
"node.exe",
"tsx",
"tsx.cmd",
"bun",
"bun.exe",
"deno",
"deno.exe",
]);
if (!scriptRuntimes.has(runtimeName) || args.length === 0) {
return args;
}
const [first, ...rest] = args;
if (!first || first.startsWith("-")) {
return args;
}
return [path.isAbsolute(first) ? first : path.resolve(cwd || process.cwd(), first), ...rest];
}
function parseJsonLine(rawOutput) {
const lines = String(rawOutput || "")
.trim()
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
return JSON.parse(lines.at(-1) || "");
}
export function getCodexDesktopRefreshBridgeConfig(env = process.env, config = {}) {
const enabled = parseBoolean(
pickConfigValue(config, "codexDesktopRefreshEnabled", env.BOSS_CODEX_DESKTOP_REFRESH_ENABLED),
);
const command =
trimToDefined(
pickConfigValue(config, "codexDesktopRefreshCommand", env.BOSS_CODEX_DESKTOP_REFRESH_COMMAND),
) || undefined;
const endpoint =
trimToDefined(
pickConfigValue(config, "codexDesktopRefreshEndpoint", env.BOSS_CODEX_DESKTOP_REFRESH_ENDPOINT),
) || undefined;
const args = Array.isArray(config?.codexDesktopRefreshArgs)
? config.codexDesktopRefreshArgs.map((item) => String(item)).filter(Boolean)
: parseArgs(pickConfigValue(config, "codexDesktopRefreshArgs", env.BOSS_CODEX_DESKTOP_REFRESH_ARGS));
const cwd =
trimToDefined(
pickConfigValue(config, "codexDesktopRefreshWorkdir", env.BOSS_CODEX_DESKTOP_REFRESH_WORKDIR),
) || undefined;
const timeoutMs = parseTimeoutMs(
pickConfigValue(config, "codexDesktopRefreshTimeoutMs", env.BOSS_CODEX_DESKTOP_REFRESH_TIMEOUT_MS),
);
const appName =
trimToDefined(pickConfigValue(config, "codexDesktopRefreshAppName", env.BOSS_CODEX_DESKTOP_APP_NAME)) ||
"Codex";
const refreshMode =
trimToDefined(
pickConfigValue(config, "codexDesktopRefreshMode", env.BOSS_CODEX_DESKTOP_REFRESH_MODE),
) || "deeplink-reload";
const retryCount = parseNonNegativeInteger(
pickConfigValue(config, "codexDesktopRefreshRetryCount", env.BOSS_CODEX_DESKTOP_REFRESH_RETRY_COUNT),
2,
);
const retryDelayMs = parseNonNegativeInteger(
pickConfigValue(config, "codexDesktopRefreshRetryDelayMs", env.BOSS_CODEX_DESKTOP_REFRESH_RETRY_DELAY_MS),
120,
);
return {
enabled,
endpoint,
command,
args,
cwd,
timeoutMs,
appName,
refreshMode,
retryCount,
retryDelayMs,
};
}
function buildCodexDesktopRefreshPayload(config, mirrorHint) {
if (!config?.enabled) {
throw new Error("CODEX_DESKTOP_REFRESH_DISABLED");
}
const targetThreadRef = trimToDefined(mirrorHint?.targetThreadRef);
const sourceMessageId = trimToDefined(mirrorHint?.sourceMessageId);
if (!targetThreadRef || !sourceMessageId) {
throw new Error("CODEX_DESKTOP_REFRESH_HINT_REQUIRED");
}
return {
kind: "codex_desktop_refresh_hint",
targetThreadRef,
sourceMessageId,
rolloutPath: trimToDefined(mirrorHint?.rolloutPath),
threadTouchStatus: trimToDefined(mirrorHint?.threadTouchStatus),
appName: trimToDefined(config.appName) || "Codex",
refreshMode: trimToDefined(config.refreshMode) || "deeplink-reload",
requestedAt: new Date().toISOString(),
};
}
export function buildCodexDesktopRefreshExecution(config, mirrorHint) {
if (!config?.enabled) {
throw new Error("CODEX_DESKTOP_REFRESH_DISABLED");
}
if (!config?.command) {
throw new Error("CODEX_DESKTOP_REFRESH_COMMAND_REQUIRED");
}
const cwd = config.cwd || process.cwd();
return {
command: config.command,
args: resolveCommandArgs(config.command, config.args || [], cwd),
cwd,
timeoutMs: config.timeoutMs || 3000,
stdinPayload: buildCodexDesktopRefreshPayload(config, mirrorHint),
};
}
export function parseCodexDesktopRefreshResult(rawOutput) {
const parsed = parseJsonLine(rawOutput);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("INVALID_CODEX_DESKTOP_REFRESH_PAYLOAD");
}
const attemptCount = parseNonNegativeInteger(parsed.attemptCount, undefined);
const baseResult = {
targetThreadRef: trimToDefined(parsed.targetThreadRef),
appName: trimToDefined(parsed.appName),
deepLink: trimToDefined(parsed.deepLink),
attemptCount,
};
if (parsed.status === "failed") {
return {
status: "failed",
...baseResult,
detail: trimToDefined(parsed.error) || "CODEX_DESKTOP_REFRESH_FAILED",
};
}
if (parsed.status !== "completed" && parsed.status !== "skipped") {
throw new Error("INVALID_CODEX_DESKTOP_REFRESH_PAYLOAD");
}
return {
status: parsed.status,
...baseResult,
detail: trimToDefined(parsed.detail),
};
}
function compactUndefinedFields(result) {
return Object.fromEntries(Object.entries(result).filter(([, value]) => value !== undefined));
}
function attachBridgeAttemptCount(result, attemptIndex) {
if (result.attemptCount !== undefined || attemptIndex > 1) {
return compactUndefinedFields({
...result,
attemptCount: result.attemptCount ?? attemptIndex,
});
}
return compactUndefinedFields(result);
}
function runCodexDesktopRefreshExecution(execution) {
return new Promise((resolve, reject) => {
const child = spawn(execution.command, execution.args, {
cwd: execution.cwd,
env: process.env,
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
child.kill("SIGKILL");
}, execution.timeoutMs);
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);
reject(error);
});
child.on("close", (code) => {
clearTimeout(timer);
if (timedOut) {
reject(new Error("CODEX_DESKTOP_REFRESH_TIMEOUT"));
return;
}
if (code !== 0) {
reject(new Error(stderr.trim() || `codex desktop refresh exit code ${code}`));
return;
}
try {
resolve(parseCodexDesktopRefreshResult(stdout));
} catch (error) {
reject(error);
}
});
child.stdin.write(JSON.stringify(execution.stdinPayload));
child.stdin.end();
});
}
async function runCodexDesktopRefreshEndpoint(config, payload) {
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort();
}, config.timeoutMs || 3000);
try {
const response = await fetch(config.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: controller.signal,
});
const body = await response.text();
if (!response.ok) {
throw new Error(body.trim() || `codex desktop refresh endpoint status ${response.status}`);
}
return parseCodexDesktopRefreshResult(body);
} finally {
clearTimeout(timer);
}
}
async function executeWithRetries(operation, runnerConfig) {
const maxAttempts = Math.max(1, parseNonNegativeInteger(runnerConfig.retryCount, 2) + 1);
const retryDelayMs = parseNonNegativeInteger(runnerConfig.retryDelayMs, 120);
let lastError;
let lastFailedResult;
for (let attemptIndex = 1; attemptIndex <= maxAttempts; attemptIndex += 1) {
try {
const result = attachBridgeAttemptCount(await operation(), attemptIndex);
if (result.status !== "failed") {
return result;
}
lastFailedResult = result;
} catch (error) {
lastError = error;
}
if (attemptIndex < maxAttempts) {
await sleep(retryDelayMs);
}
}
if (lastFailedResult) {
return attachBridgeAttemptCount(lastFailedResult, maxAttempts);
}
const message = lastError instanceof Error ? lastError.message : String(lastError || "CODEX_DESKTOP_REFRESH_FAILED");
throw new Error(`${message}; attempts=${maxAttempts}`);
}
export async function executeCodexDesktopRefreshBridge(mirrorHint, config = {}) {
const runnerConfig =
config && Object.prototype.hasOwnProperty.call(config, "enabled")
? config
: getCodexDesktopRefreshBridgeConfig(process.env, config);
if (!runnerConfig.enabled) {
return {
status: "skipped",
reason: "disabled",
};
}
if (runnerConfig.endpoint) {
const endpointPayload = buildCodexDesktopRefreshPayload(runnerConfig, mirrorHint);
try {
return await executeWithRetries(() => runCodexDesktopRefreshEndpoint(runnerConfig, endpointPayload), runnerConfig);
} catch (error) {
if (!runnerConfig.command) {
throw error;
}
}
}
const execution = buildCodexDesktopRefreshExecution(runnerConfig, mirrorHint);
return executeWithRetries(() => runCodexDesktopRefreshExecution(execution), runnerConfig);
}