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 parseCsv(value) { return String(value || "") .split(",") .map((item) => item.trim()) .filter(Boolean); } function parseTimeoutMs(value) { const parsed = Number.parseInt(String(value || ""), 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : 45000; } 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 getComputerUseTaskRunnerConfig(env = process.env, config = {}) { const enabled = parseBoolean(pickConfigValue(config, "computerUseEnabled", env.BOSS_COMPUTER_USE_ENABLED)); const command = String(pickConfigValue(config, "computerUseCommand", env.BOSS_COMPUTER_USE_COMMAND) || "").trim() || undefined; const args = Array.isArray(config?.computerUseArgs) ? config.computerUseArgs.map((item) => String(item)).filter(Boolean) : parseArgs(pickConfigValue(config, "computerUseArgs", env.BOSS_COMPUTER_USE_ARGS)); const cwd = String(pickConfigValue(config, "computerUseWorkdir", env.BOSS_COMPUTER_USE_WORKDIR) || "").trim() || undefined; const timeoutMs = parseTimeoutMs(pickConfigValue(config, "computerUseTimeoutMs", env.BOSS_COMPUTER_USE_TIMEOUT_MS)); const dialogGuardEnabled = parseBoolean(pickConfigValue(config, "dialogGuardEnabled", env.BOSS_DIALOG_GUARD_ENABLED)); const dialogGuardConsentRequired = parseBoolean( pickConfigValue(config, "dialogGuardConsentRequired", env.BOSS_DIALOG_GUARD_CONSENT_REQUIRED), ); const dialogGuardPlatformAdapters = Array.isArray(config?.dialogGuardPlatformAdapters) ? config.dialogGuardPlatformAdapters.map((item) => String(item).trim()).filter(Boolean) : parseCsv(pickConfigValue(config, "dialogGuardPlatformAdapters", env.BOSS_DIALOG_GUARD_PLATFORM_ADAPTERS)); const dialogGuardMacActionCommand = String( pickConfigValue(config, "dialogGuardMacActionCommand", env.BOSS_MAC_DIALOG_GUARD_ACTION_COMMAND) || "", ).trim(); const dialogGuardMacActionArgs = Array.isArray(config?.dialogGuardMacActionArgs) ? config.dialogGuardMacActionArgs.map((item) => String(item)).filter(Boolean) : parseArgs(pickConfigValue(config, "dialogGuardMacActionArgs", env.BOSS_MAC_DIALOG_GUARD_ACTION_ARGS)); const dialogGuardWindowsActionCommand = String( pickConfigValue(config, "dialogGuardWindowsActionCommand", env.BOSS_WINDOWS_DIALOG_GUARD_ACTION_COMMAND) || "", ).trim(); const dialogGuardWindowsActionArgs = Array.isArray(config?.dialogGuardWindowsActionArgs) ? config.dialogGuardWindowsActionArgs.map((item) => String(item)).filter(Boolean) : parseArgs(pickConfigValue(config, "dialogGuardWindowsActionArgs", env.BOSS_WINDOWS_DIALOG_GUARD_ACTION_ARGS)); return { enabled, command, args, cwd, timeoutMs, dialogGuardEnabled, dialogGuardConsentRequired, dialogGuardPlatformAdapters, dialogGuardMacActionCommand, dialogGuardMacActionArgs, dialogGuardWindowsActionCommand, dialogGuardWindowsActionArgs, }; } export function canHandleComputerUseTask(task) { return String(task?.taskType || "").trim() === "desktop_control"; } export function buildComputerUseTaskExecution(config, task) { if (!config?.enabled) { throw new Error("COMPUTER_USE_RUNTIME_DISABLED"); } if (!config?.command) { throw new Error("COMPUTER_USE_COMMAND_REQUIRED"); } const cwd = config.cwd || process.cwd(); return { command: config.command, args: resolveCommandArgs(config.command, config.args || [], cwd), cwd, timeoutMs: config.timeoutMs || 45000, env: { BOSS_DIALOG_GUARD_ENABLED: config.dialogGuardEnabled ? "true" : "false", BOSS_DIALOG_GUARD_CONSENT_REQUIRED: config.dialogGuardConsentRequired ? "true" : "false", BOSS_DIALOG_GUARD_PLATFORM_ADAPTERS: Array.isArray(config.dialogGuardPlatformAdapters) ? config.dialogGuardPlatformAdapters.join(",") : "", BOSS_MAC_DIALOG_GUARD_ACTION_COMMAND: config.dialogGuardMacActionCommand || "", BOSS_MAC_DIALOG_GUARD_ACTION_ARGS_JSON: JSON.stringify(config.dialogGuardMacActionArgs || []), BOSS_WINDOWS_DIALOG_GUARD_ACTION_COMMAND: config.dialogGuardWindowsActionCommand || "", BOSS_WINDOWS_DIALOG_GUARD_ACTION_ARGS_JSON: JSON.stringify(config.dialogGuardWindowsActionArgs || []), }, stdinPayload: { requestKind: "desktop_control", requestId: String(task?.taskId || "").trim(), objective: String(task?.requestText || task?.executionPrompt || "").trim(), 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, }, }, }; } export function parseComputerUseTaskResult(rawOutput) { const parsed = parseJsonLine(rawOutput); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("INVALID_COMPUTER_USE_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() : "COMPUTER_USE_FAILED", }; } if (parsed.status === "needs_user_action") { return { status: "needs_user_action", requestId: typeof parsed.requestId === "string" ? parsed.requestId.trim() || undefined : undefined, kind: typeof parsed.kind === "string" && parsed.kind.trim() ? parsed.kind.trim() : "user_action_required", dialogId: typeof parsed.dialogId === "string" && parsed.dialogId.trim() ? parsed.dialogId.trim() : undefined, risk: typeof parsed.risk === "string" && parsed.risk.trim() ? parsed.risk.trim() : "medium", summary: typeof parsed.summary === "string" && parsed.summary.trim() ? parsed.summary.trim() : "", recommendedAction: typeof parsed.recommendedAction === "string" && parsed.recommendedAction.trim() ? parsed.recommendedAction.trim() : undefined, availableActions: Array.isArray(parsed.availableActions) ? parsed.availableActions.map((item) => String(item).trim()).filter(Boolean) : [], platform: typeof parsed.platform === "string" && parsed.platform.trim() ? parsed.platform.trim() : undefined, appName: typeof parsed.appName === "string" && parsed.appName.trim() ? parsed.appName.trim() : undefined, }; } 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_COMPUTER_USE_RUNTIME_PAYLOAD"); } return { status: "completed", requestId: typeof parsed.requestId === "string" ? parsed.requestId.trim() || undefined : undefined, replyBody, targetApp: typeof parsed.targetApp === "string" && parsed.targetApp.trim() ? parsed.targetApp.trim() : undefined, executionSummary: typeof parsed.executionSummary === "string" && parsed.executionSummary.trim() ? parsed.executionSummary.trim() : undefined, }; } export async function executeComputerUseTask(task, config = {}) { const runnerConfig = getComputerUseTaskRunnerConfig(process.env, config); if (!runnerConfig.enabled) { return { status: "failed", errorMessage: "COMPUTER_USE_RUNTIME_DISABLED", }; } const execution = buildComputerUseTaskExecution(runnerConfig, task); return new Promise((resolve, reject) => { const child = spawn(execution.command, execution.args, { cwd: execution.cwd, env: { ...process.env, ...(execution.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("COMPUTER_USE_TIMEOUT")); return; } if (code !== 0) { reject(new Error(stderr.trim() || `computer use exit code ${code}`)); return; } try { resolve(parseComputerUseTaskResult(stdout)); } catch (error) { reject(error); } }); child.stdin.write(JSON.stringify(execution.stdinPayload)); child.stdin.end(); }); }