feat: ship enterprise control and desktop governance
This commit is contained in:
335
local-agent/codex-desktop-refresh-bridge.mjs
Normal file
335
local-agent/codex-desktop-refresh-bridge.mjs
Normal file
@@ -0,0 +1,335 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user