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 : 45000; } function normalizeControlPlatform(value) { const platform = String(value || "").trim().toLowerCase(); if (!platform || platform === "macos") return "macos"; throw new Error("UNSUPPORTED_CONTROL_PLATFORM"); } function normalizeComputerUseProvider(value) { const provider = String(value || "").trim(); return provider === "openai-computer-use" ? provider : "openai-computer-use"; } 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 getBrowserControlTaskRunnerConfig(env = process.env, config = {}) { const enabled = parseBoolean(pickConfigValue(config, "browserControlEnabled", env.BOSS_BROWSER_CONTROL_ENABLED)); const command = String(pickConfigValue(config, "browserControlCommand", env.BOSS_BROWSER_CONTROL_COMMAND) || "").trim() || undefined; const args = Array.isArray(config?.browserControlArgs) ? config.browserControlArgs.map((item) => String(item)).filter(Boolean) : parseArgs(pickConfigValue(config, "browserControlArgs", env.BOSS_BROWSER_CONTROL_ARGS)); const cwd = String(pickConfigValue(config, "browserControlWorkdir", env.BOSS_BROWSER_CONTROL_WORKDIR) || "").trim() || undefined; const timeoutMs = parseTimeoutMs(pickConfigValue(config, "browserControlTimeoutMs", env.BOSS_BROWSER_CONTROL_TIMEOUT_MS)); return { enabled, command, args, cwd, timeoutMs, }; } export function canHandleBrowserControlTask(task) { return String(task?.taskType || "").trim() === "browser_control"; } export function buildBrowserControlTaskExecution(config, task) { if (!config?.enabled) { throw new Error("BROWSER_CONTROL_RUNTIME_DISABLED"); } if (!config?.command) { throw new Error("BROWSER_CONTROL_COMMAND_REQUIRED"); } const cwd = config.cwd || process.cwd(); const controlPlatform = normalizeControlPlatform(task?.controlPlatform); const computerUseProvider = normalizeComputerUseProvider(task?.computerUseProvider); return { command: config.command, args: resolveCommandArgs(config.command, config.args || [], cwd), cwd, timeoutMs: config.timeoutMs || 45000, stdinPayload: { requestKind: "browser_control", requestId: String(task?.taskId || "").trim(), objective: String(task?.requestText || task?.executionPrompt || "").trim(), platform: controlPlatform, provider: computerUseProvider, context: { projectId: String(task?.projectId || "").trim() || undefined, threadId: String(task?.threadId || task?.targetThreadId || "").trim() || undefined, requestedBy: String(task?.requestedByAccount || task?.requestedBy || "").trim() || undefined, requestedAt: String(task?.requestedAt || "").trim() || undefined, confirmationScopeKey: String(task?.confirmationScopeKey || "").trim() || undefined, riskLevel: String(task?.riskLevel || "").trim() || undefined, controlPlatform, computerUseProvider, }, }, }; } export function parseBrowserControlTaskResult(rawOutput) { const parsed = parseJsonLine(rawOutput); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("INVALID_BROWSER_CONTROL_RUNTIME_PAYLOAD"); } if (parsed.status === "failed") { return { status: "failed", requestId: typeof parsed.requestId === "string" ? parsed.requestId.trim() || undefined : undefined, errorMessage: typeof parsed.error === "string" && parsed.error.trim() ? parsed.error.trim() : "BROWSER_CONTROL_FAILED", }; } const replyBody = typeof parsed.replyBody === "string" && parsed.replyBody.trim() ? parsed.replyBody.trim() : typeof parsed.summary === "string" && parsed.summary.trim() ? parsed.summary.trim() : ""; if (!replyBody) { throw new Error("INVALID_BROWSER_CONTROL_RUNTIME_PAYLOAD"); } return { status: "completed", requestId: typeof parsed.requestId === "string" ? parsed.requestId.trim() || undefined : undefined, replyBody, targetUrl: typeof parsed.targetUrl === "string" && parsed.targetUrl.trim() ? parsed.targetUrl.trim() : undefined, executionSummary: typeof parsed.executionSummary === "string" && parsed.executionSummary.trim() ? parsed.executionSummary.trim() : undefined, }; } export async function executeBrowserControlTask(task, config = {}) { const runnerConfig = getBrowserControlTaskRunnerConfig(process.env, config); if (!runnerConfig.enabled) { return { status: "failed", errorMessage: "BROWSER_CONTROL_RUNTIME_DISABLED", }; } const execution = buildBrowserControlTaskExecution(runnerConfig, task); 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("BROWSER_CONTROL_TIMEOUT")); return; } if (code !== 0) { reject(new Error(stderr.trim() || `browser control exit code ${code}`)); return; } try { resolve(parseBrowserControlTaskResult(stdout)); } catch (error) { reject(error); } }); child.stdin.write(JSON.stringify(execution.stdinPayload)); child.stdin.end(); }); }