179 lines
5.8 KiB
JavaScript
179 lines
5.8 KiB
JavaScript
#!/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;
|
|
});
|
|
}
|