feat: harden enterprise control plane
This commit is contained in:
178
scripts/codex-computer-use-runtime.mjs
Normal file
178
scripts/codex-computer-use-runtime.mjs
Normal file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
import {
|
||||
executeCodexAppServerTask,
|
||||
getCodexAppServerRunnerConfig,
|
||||
} from "../local-agent/codex-app-server-runner.mjs";
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 120_000;
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || "").trim();
|
||||
}
|
||||
|
||||
function parseArgs(value) {
|
||||
const args = String(value || "")
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
return args.length > 0 ? args : undefined;
|
||||
}
|
||||
|
||||
function parseArgsJson(value) {
|
||||
const raw = normalizeText(value);
|
||||
if (!raw) return undefined;
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? parsed.map((item) => String(item)).filter(Boolean) : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function parseTimeoutMs(value) {
|
||||
const parsed = Number.parseInt(String(value || ""), 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
function writeJson(payload) {
|
||||
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
||||
}
|
||||
|
||||
async function readStdin() {
|
||||
const chunks = [];
|
||||
for await (const chunk of process.stdin) {
|
||||
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
||||
}
|
||||
return chunks.join("").trim();
|
||||
}
|
||||
|
||||
function parseJsonPayload(raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(String(raw || "{}"));
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function detectTargetApp(objective) {
|
||||
const text = normalizeText(objective).toLowerCase();
|
||||
const candidates = [
|
||||
["Codex", ["codex"]],
|
||||
["Google Chrome", ["chrome", "google chrome", "谷歌"]],
|
||||
["Safari", ["safari"]],
|
||||
["QQ", ["qq"]],
|
||||
["微信", ["微信", "wechat"]],
|
||||
["飞书", ["飞书", "lark", "feishu"]],
|
||||
["Telegram", ["telegram", "tg"]],
|
||||
["Finder", ["finder", "访达"]],
|
||||
["系统设置", ["系统设置", "system settings", "settings"]],
|
||||
];
|
||||
const matched = candidates.find(([, aliases]) => aliases.some((alias) => text.includes(alias)));
|
||||
return matched?.[0];
|
||||
}
|
||||
|
||||
function buildComputerUsePrompt(payload) {
|
||||
const objective = normalizeText(payload.objective);
|
||||
const targetApp = detectTargetApp(objective);
|
||||
return [
|
||||
"你是 Boss 的 Codex Computer Use 执行器,正在被要求控制当前这台 macOS 电脑。",
|
||||
"请优先使用 Codex 自带的 Computer Use / Browser / Desktop 能力完成用户目标。",
|
||||
"只执行当前目标直接需要的动作;不要扩展需求,不要改动无关文件。",
|
||||
"遇到发送、提交、删除、支付、授权等高风险动作时,必须停下来要求用户确认,不要静默点击。",
|
||||
targetApp ? `目标应用:${targetApp}` : "目标应用:如果用户没有明确说明,请先根据目标判断最小必要应用。",
|
||||
`用户目标:${objective}`,
|
||||
"完成后用中文返回简短小结,说明已做的动作、结果和是否需要用户下一步确认。",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function buildRunnerConfig(env, payload) {
|
||||
const cwd =
|
||||
normalizeText(env.BOSS_CODEX_COMPUTER_USE_WORKDIR) ||
|
||||
normalizeText(payload?.context?.projectCwd) ||
|
||||
process.cwd();
|
||||
return getCodexAppServerRunnerConfig(env, {
|
||||
codexAppServerEnabled: true,
|
||||
codexAppServerCommand: normalizeText(env.BOSS_CODEX_COMPUTER_USE_CODEX_COMMAND) || "codex",
|
||||
codexAppServerArgs:
|
||||
parseArgsJson(env.BOSS_CODEX_COMPUTER_USE_CODEX_ARGS_JSON) ??
|
||||
parseArgs(env.BOSS_CODEX_COMPUTER_USE_CODEX_ARGS) ??
|
||||
["app-server"],
|
||||
codexAppServerWorkdir: cwd,
|
||||
codexAppServerTimeoutMs: parseTimeoutMs(env.BOSS_CODEX_COMPUTER_USE_TIMEOUT_MS),
|
||||
codexAppServerClientName: "boss_codex_computer_use",
|
||||
codexAppServerClientTitle: "Boss Codex Computer Use",
|
||||
codexAppServerClientVersion: "0.1.0",
|
||||
masterAgentWorkdir: cwd,
|
||||
masterAgentModel: normalizeText(env.BOSS_CODEX_COMPUTER_USE_MODEL),
|
||||
});
|
||||
}
|
||||
|
||||
export async function runCodexComputerUseTask(payload, options = {}) {
|
||||
const env = options.env || process.env;
|
||||
const requestId = normalizeText(payload?.requestId);
|
||||
const objective = normalizeText(payload?.objective);
|
||||
if (!objective) {
|
||||
return {
|
||||
status: "failed",
|
||||
requestId: requestId || undefined,
|
||||
error: "CODEX_COMPUTER_USE_OBJECTIVE_REQUIRED",
|
||||
computerUseProvider: "codex-computer-use",
|
||||
};
|
||||
}
|
||||
|
||||
const runnerConfig = buildRunnerConfig(env, payload);
|
||||
const result = await executeCodexAppServerTask(runnerConfig, {
|
||||
taskId: requestId || "codex-computer-use",
|
||||
taskType: "conversation_reply",
|
||||
targetCodexThreadRef: normalizeText(payload?.context?.codexComputerUseThreadId),
|
||||
targetCodexFolderRef: runnerConfig.cwd,
|
||||
executionPrompt: buildComputerUsePrompt(payload),
|
||||
});
|
||||
|
||||
if (result.status !== "completed") {
|
||||
return {
|
||||
status: "failed",
|
||||
requestId: requestId || undefined,
|
||||
error: result.errorMessage || "CODEX_COMPUTER_USE_FAILED",
|
||||
detail: result.stderr,
|
||||
computerUseProvider: "codex-computer-use",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "completed",
|
||||
requestId: requestId || undefined,
|
||||
replyBody: result.replyBody || "Codex Computer Use 已完成本轮桌面控制任务。",
|
||||
targetApp: detectTargetApp(objective),
|
||||
executionSummary: "codex app-server computer use",
|
||||
computerUseProvider: "codex-computer-use",
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const raw = await readStdin();
|
||||
const payload = parseJsonPayload(raw);
|
||||
const result = await runCodexComputerUseTask(payload, {
|
||||
env: process.env,
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
writeJson(result);
|
||||
}
|
||||
|
||||
const currentFile = fileURLToPath(import.meta.url);
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(currentFile)) {
|
||||
main().catch((error) => {
|
||||
writeJson({
|
||||
status: "failed",
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
computerUseProvider: "codex-computer-use",
|
||||
});
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user