Files
boss/tests/browser-desktop-smoke-runtime-scripts.test.mjs
2026-05-17 02:20:08 +08:00

1272 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import fs from "node:fs/promises";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
async function writeOpenMarkerScript(markerFile) {
const scriptDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-open-marker-script-"));
const scriptPath = path.join(scriptDir, "open-marker.mjs");
await fs.writeFile(
scriptPath,
`import fs from "node:fs";\nfs.writeFileSync(${JSON.stringify(markerFile)}, process.argv[2] || "", "utf8");\n`,
"utf8",
);
await fs.chmod(scriptPath, 0o755);
return { scriptDir, scriptPath };
}
async function writeArgumentMarkerCommand(markerFile, commandName = "open") {
const scriptDir = await fs.mkdtemp(path.join(os.tmpdir(), `boss-${commandName}-marker-script-`));
const scriptPath = path.join(scriptDir, commandName);
await fs.writeFile(
scriptPath,
[
"#!/usr/bin/env node",
'import fs from "node:fs";',
`fs.writeFileSync(${JSON.stringify(markerFile)}, JSON.stringify(process.argv.slice(2)), "utf8");`,
].join("\n"),
"utf8",
);
await fs.chmod(scriptPath, 0o755);
return { scriptDir, scriptPath };
}
async function writeBrowserAutomationScript(logFile) {
const scriptDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-automation-script-"));
const scriptPath = path.join(scriptDir, "browser-automation.mjs");
await fs.writeFile(
scriptPath,
[
'#!/usr/bin/env node',
'import fs from "node:fs";',
`fs.appendFileSync(${JSON.stringify(logFile)}, JSON.stringify(process.argv.slice(2)) + "\\n", "utf8");`,
'if (process.argv.includes("eval")) {',
' process.stdout.write("Boss Automated Title\\n");',
'}',
].join("\n"),
"utf8",
);
await fs.chmod(scriptPath, 0o755);
return { scriptDir, scriptPath };
}
async function writeCodexHomePlaywrightWrapper(codexHome, logFile) {
const wrapperDir = path.join(codexHome, "skills", "playwright", "scripts");
await fs.mkdir(wrapperDir, { recursive: true });
const wrapperPath = path.join(wrapperDir, "playwright_cli.sh");
await fs.writeFile(
wrapperPath,
[
"#!/bin/zsh",
`printf '%s\\n' \"$(python3 -c 'import json,sys; args=sys.argv[1:]; print(json.dumps(args[1:] if args[:1] == [\"--\"] else args))' -- \"$@\")\" >> ${JSON.stringify(logFile)}`,
'if [[ " $* " == *" eval "* ]]; then',
' printf "Boss Auto Wrapper Title\\n"',
"fi",
].join("\n"),
"utf8",
);
await fs.chmod(wrapperPath, 0o755);
return wrapperPath;
}
async function runRuntimeWithServer(scriptPath, payload, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [scriptPath], {
cwd: repoRoot,
env: {
...process.env,
...(options.env || {}),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(JSON.stringify(payload));
child.stdin.end();
});
}
async function runRuntime(scriptPath, payload, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [scriptPath], {
cwd: repoRoot,
env: {
...process.env,
BOSS_BROWSER_AUTOMATION_MODE: "off",
...(options.env || {}),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(JSON.stringify(payload));
child.stdin.end();
});
}
test("browser smoke runtime returns normalized completed payload", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-smoke-1",
objective: "打开 boss 控制台首页",
context: {
riskLevel: "medium",
},
});
assert.equal(result.status, "completed");
assert.equal(result.requestId, "browser-smoke-1");
assert.match(result.replyBody, /浏览器控制已完成/);
assert.match(result.replyBody, /打开 boss 控制台首页/);
});
test("browser smoke runtime emits target url when objective contains a website", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-smoke-url",
objective: "打开 https://example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
assert.match(result.executionSummary, /open_url/);
});
test("browser smoke runtime normalizes bare domains from app control text", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-smoke-bare-domain",
objective: "打开Chrome浏览器访问 example.com完成后回复一句任务小结。",
context: {
riskLevel: "medium",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
assert.match(result.executionSummary, /open_url/);
});
test("browser smoke runtime derives a YouTube search url from natural language playback requests", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-youtube-search",
objective: "打开浏览器用浏览器打开YouTube找一个蔡徐坤的MV播放",
context: {
riskLevel: "medium",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(
result.targetUrl,
"https://www.youtube.com/results?search_query=%E8%94%A1%E5%BE%90%E5%9D%A4%20MV",
);
assert.match(result.executionSummary, /open_url/);
});
test("browser smoke runtime can invoke configured browser automation command", async () => {
const markerDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-automation-marker-"));
const markerFile = path.join(markerDir, "automation.log");
let automationScript;
try {
automationScript = await writeBrowserAutomationScript(markerFile);
const result = await runRuntime(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-automation-1",
objective: "打开 https://example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: false,
},
},
{
env: {
BOSS_BROWSER_AUTOMATION_MODE: "playwright",
BOSS_BROWSER_AUTOMATION_COMMAND: process.execPath,
BOSS_BROWSER_AUTOMATION_ARGS_JSON: JSON.stringify([automationScript.scriptPath]),
BOSS_BROWSER_AUTOMATION_SESSION: "boss-browser-test",
BOSS_BROWSER_VISIBLE_OPEN_AFTER_AUTOMATION: "off",
},
},
);
assert.equal(result.status, "completed");
assert.match(result.replyBody, /Boss Automated Title/);
const lines = (await fs.readFile(markerFile, "utf8")).trim().split(/\r?\n/).map((line) => JSON.parse(line));
assert.deepEqual(lines[0], ["--session", "boss-browser-test", "open", "https://example.com"]);
assert.deepEqual(lines[1], ["--session", "boss-browser-test", "eval", "document.title"]);
} finally {
if (automationScript?.scriptDir) {
await fs.rm(automationScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(markerDir, { recursive: true, force: true });
}
});
test("browser smoke runtime still opens a visible browser after automation succeeds", async () => {
const markerDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-visible-after-automation-"));
const automationLog = path.join(markerDir, "automation.log");
const openerMarker = path.join(markerDir, "visible-open.txt");
let automationScript;
let openerScript;
try {
automationScript = await writeBrowserAutomationScript(automationLog);
openerScript = await writeOpenMarkerScript(openerMarker);
const result = await runRuntimeWithServer(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-visible-after-automation",
objective: "打开 https://example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: false,
},
},
{
env: {
BOSS_BROWSER_AUTOMATION_MODE: "playwright",
BOSS_BROWSER_AUTOMATION_COMMAND: process.execPath,
BOSS_BROWSER_AUTOMATION_ARGS_JSON: JSON.stringify([automationScript.scriptPath]),
BOSS_BROWSER_OPEN_COMMAND: process.execPath,
BOSS_BROWSER_OPEN_ARGS_JSON: JSON.stringify([openerScript.scriptPath]),
},
},
);
assert.equal(result.status, "completed");
assert.match(result.replyBody, /Boss Automated Title/);
assert.match(result.executionSummary, /browser_automation_executed\+open_url_executed/);
assert.equal(await fs.readFile(openerMarker, "utf8"), "https://example.com");
} finally {
if (automationScript?.scriptDir) {
await fs.rm(automationScript.scriptDir, { recursive: true, force: true });
}
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(markerDir, { recursive: true, force: true });
}
});
test("browser smoke runtime falls back to opener when browser automation fails", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-automation-fallback-"));
const openerMarker = path.join(marker, "opened.txt");
let openerScript;
try {
const failingAutomationPath = path.join(marker, "failing-automation.mjs");
await fs.writeFile(failingAutomationPath, "process.stderr.write('automation failed'); process.exit(1);", "utf8");
openerScript = await writeOpenMarkerScript(openerMarker);
const result = await runRuntime(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-automation-fallback",
objective: "打开 example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: false,
},
},
{
env: {
BOSS_BROWSER_AUTOMATION_MODE: "playwright",
BOSS_BROWSER_AUTOMATION_COMMAND: process.execPath,
BOSS_BROWSER_AUTOMATION_ARGS_JSON: JSON.stringify([failingAutomationPath]),
BOSS_BROWSER_OPEN_COMMAND: process.execPath,
BOSS_BROWSER_OPEN_ARGS_JSON: JSON.stringify([openerScript.scriptPath]),
},
},
);
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
assert.match(result.executionSummary, /open_url_executed/);
assert.equal(await fs.readFile(openerMarker, "utf8"), "https://example.com");
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("browser smoke runtime auto-detects bundled playwright wrapper and uses request id as session", async () => {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-autodetect-"));
const logFile = path.join(tmpRoot, "wrapper.log");
try {
const codexHome = path.join(tmpRoot, ".codex");
await writeCodexHomePlaywrightWrapper(codexHome, logFile);
const result = await runRuntimeWithServer(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-auto-session",
objective: "打开 https://example.com 看一下首页",
context: {
riskLevel: "medium",
dryRun: false,
},
},
{
env: {
CODEX_HOME: codexHome,
BOSS_BROWSER_VISIBLE_OPEN_AFTER_AUTOMATION: "off",
},
},
);
assert.equal(result.status, "completed");
assert.match(result.replyBody, /Boss Auto Wrapper Title/);
const lines = (await fs.readFile(logFile, "utf8")).trim().split(/\r?\n/).map((line) => JSON.parse(line));
assert.deepEqual(lines[0], ["--session", "browser-auto-session", "open", "https://example.com"]);
assert.deepEqual(lines[1], ["--session", "browser-auto-session", "eval", "document.title"]);
} finally {
await fs.rm(tmpRoot, { recursive: true, force: true });
}
});
test("browser smoke runtime fetches page title for a reachable target url", async () => {
const server = http.createServer((_request, response) => {
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
response.end("<html><head><title>Boss Browser Runtime Test</title></head><body>ok</body></html>");
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
const port = typeof address === "object" && address ? address.port : 0;
try {
const result = await runRuntimeWithServer(path.join(repoRoot, "scripts", "browser-control-smoke.mjs"), {
requestKind: "browser_control",
requestId: "browser-smoke-title",
objective: `打开 http://127.0.0.1:${port}/ 看一下首页`,
context: {
riskLevel: "medium",
dryRun: false,
},
}, {
env: {
BOSS_BROWSER_AUTOMATION_MODE: "fetch",
},
});
assert.equal(result.status, "completed");
assert.match(result.replyBody, /Boss Browser Runtime Test/);
assert.match(result.executionSummary, /title=Boss Browser Runtime Test/);
} finally {
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
});
test("computer use smoke runtime returns normalized completed payload", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "computer-use-smoke.mjs"), {
requestKind: "desktop_control",
requestId: "computer-smoke-1",
objective: "打开系统设置",
context: {
riskLevel: "high",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(result.requestId, "computer-smoke-1");
assert.match(result.replyBody, /桌面控制已完成/);
assert.match(result.replyBody, /打开系统设置/);
});
test("computer use smoke runtime emits target app when objective contains an app name", async () => {
const result = await runRuntime(path.join(repoRoot, "scripts", "computer-use-smoke.mjs"), {
requestKind: "desktop_control",
requestId: "computer-smoke-app",
objective: "打开微信并准备切到聊天窗口",
context: {
riskLevel: "medium",
dryRun: true,
},
});
assert.equal(result.status, "completed");
assert.equal(result.targetApp, "微信");
assert.match(result.executionSummary, /open_wechat|open_app/);
});
test("computer use smoke runtime auto-handles safe cross-platform dialog snapshots", async () => {
const result = await runRuntime(
path.join(repoRoot, "scripts", "computer-use-smoke.mjs"),
{
requestKind: "desktop_control",
requestId: "computer-dialog-safe",
objective: "打开 QQ",
context: {
riskLevel: "medium",
dryRun: true,
},
},
{
env: {
BOSS_DIALOG_GUARD_ENABLED: "true",
BOSS_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
platform: "win32",
deviceId: "win-node",
appName: "QQ",
title: "Welcome",
text: "Welcome. Not now",
buttons: ["Get started", "Not now"],
}),
},
},
);
assert.equal(result.status, "completed");
assert.equal(result.dialogGuard?.disposition, "auto_action");
assert.equal(result.dialogGuard?.button, "Not now");
assert.match(result.executionSummary, /dialogGuard=auto_action/);
});
test("computer use smoke runtime invokes configured platform dialog action command for safe dialogs", async () => {
const markerDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-dialog-action-marker-"));
const markerFile = path.join(markerDir, "action.json");
let actionCommand;
try {
actionCommand = await writeArgumentMarkerCommand(markerFile, "dialog-action");
const result = await runRuntime(
path.join(repoRoot, "scripts", "computer-use-smoke.mjs"),
{
requestKind: "desktop_control",
requestId: "computer-dialog-action",
objective: "打开 QQ",
context: {
riskLevel: "medium",
dryRun: true,
},
},
{
env: {
BOSS_DIALOG_GUARD_ENABLED: "true",
BOSS_WINDOWS_DIALOG_GUARD_ACTION_COMMAND: actionCommand.scriptPath,
BOSS_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
platform: "win32",
deviceId: "win-node",
appName: "QQ",
title: "Welcome",
text: "Welcome. Not now",
buttons: ["Get started", "Not now"],
}),
},
},
);
assert.equal(result.status, "completed");
assert.equal(result.dialogGuard?.actionApplied, true);
const args = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.ok(args.includes("--platform"));
assert.ok(args.includes("win32"));
assert.ok(args.includes("--button"));
assert.ok(args.includes("Not now"));
} finally {
if (actionCommand?.scriptDir) {
await fs.rm(actionCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(markerDir, { recursive: true, force: true });
}
});
test("computer use smoke runtime pauses before action for blocked system permission dialogs", async () => {
const result = await runRuntime(
path.join(repoRoot, "scripts", "computer-use-smoke.mjs"),
{
requestKind: "desktop_control",
requestId: "computer-dialog-blocked",
objective: "打开系统设置",
context: {
riskLevel: "high",
dryRun: false,
},
},
{
env: {
BOSS_DIALOG_GUARD_ENABLED: "true",
BOSS_DIALOG_GUARD_SNAPSHOT_JSON: JSON.stringify({
platform: "darwin",
deviceId: "mac-node",
appName: "System Settings",
title: "Screen Recording",
text: "BossComputerUseHelper would like to record this computer's screen",
buttons: ["Allow", "Don't Allow"],
}),
},
},
);
assert.equal(result.status, "needs_user_action");
assert.equal(result.kind, "dialog_intervention_required");
assert.equal(result.risk, "high");
assert.deepEqual(result.availableActions, ["handled_on_device", "cancel_task"]);
});
test("browser smoke runtime writes action artifact when BOSS_CONTROL_ARTIFACT_DIR is set", async () => {
const artifactDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-artifacts-"));
try {
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "browser-control-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CONTROL_ARTIFACT_DIR: artifactDir,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "browser_control",
requestId: "browser-artifact-1",
objective: "打开 https://example.com 看一下首页",
context: { dryRun: true },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.ok(Array.isArray(result.artifacts));
assert.ok(result.artifacts[0]?.path);
const artifactText = await fs.readFile(result.artifacts[0].path, "utf8");
assert.match(artifactText, /https:\/\/example\.com/);
} finally {
await fs.rm(artifactDir, { recursive: true, force: true });
}
});
test("browser smoke runtime can execute an injected url opener when not dry-run", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-open-"));
let openerScript;
const markerFile = path.join(marker, "opened.txt");
try {
openerScript = await writeOpenMarkerScript(markerFile);
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "browser-control-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_BROWSER_AUTOMATION_MODE: "off",
BOSS_BROWSER_OPEN_COMMAND: process.execPath,
BOSS_BROWSER_OPEN_ARGS_JSON: JSON.stringify([openerScript.scriptPath]),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "browser_control",
requestId: "browser-open-1",
objective: "打开 https://example.com 看一下首页",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
assert.match(result.executionSummary, /executed/);
const openedUrl = await fs.readFile(markerFile, "utf8");
assert.equal(openedUrl, "https://example.com");
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("browser smoke runtime opens Chrome when app control text asks for Chrome", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-chrome-open-"));
const markerFile = path.join(marker, "open-args.json");
let openerCommand;
try {
openerCommand = await writeArgumentMarkerCommand(markerFile, "open");
const result = await runRuntime(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-open-chrome",
objective: "打开Chrome浏览器访问 example.com完成后回复一句任务小结。",
context: { dryRun: false },
},
{
env: {
BOSS_BROWSER_OPEN_COMMAND: openerCommand.scriptPath,
},
},
);
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
assert.deepEqual(JSON.parse(await fs.readFile(markerFile, "utf8")), [
"-a",
"Google Chrome",
"https://example.com",
]);
} finally {
if (openerCommand?.scriptDir) {
await fs.rm(openerCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("browser smoke runtime uses osascript for real Chrome URL opens on macOS", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-browser-chrome-osascript-"));
const osascriptMarker = path.join(marker, "osascript-args.json");
let osascriptCommand;
try {
osascriptCommand = await writeArgumentMarkerCommand(osascriptMarker, "osascript");
const failingOpenPath = path.join(osascriptCommand.scriptDir, "open");
await fs.writeFile(
failingOpenPath,
"#!/usr/bin/env node\nprocess.stderr.write('open should not be used for Chrome URL opens'); process.exit(1);\n",
"utf8",
);
await fs.chmod(failingOpenPath, 0o755);
const result = await runRuntime(
path.join(repoRoot, "scripts", "browser-control-smoke.mjs"),
{
requestKind: "browser_control",
requestId: "browser-open-chrome-osascript",
objective: "打开Chrome浏览器访问 example.com完成后回复一句任务小结。",
context: { dryRun: false },
},
{
env: {
PATH: `${osascriptCommand.scriptDir}:${process.env.PATH}`,
},
},
);
assert.equal(result.status, "completed");
assert.equal(result.targetUrl, "https://example.com");
const args = JSON.parse(await fs.readFile(osascriptMarker, "utf8"));
assert.match(args.join(" "), /Google Chrome/);
assert.match(args.join(" "), /https:\/\/example\.com/);
} finally {
if (osascriptCommand?.scriptDir) {
await fs.rm(osascriptCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime can execute an injected app opener when not dry-run", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-"));
let openerScript;
const markerFile = path.join(marker, "opened.txt");
try {
openerScript = await writeOpenMarkerScript(markerFile);
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "open",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: process.execPath,
BOSS_COMPUTER_USE_OPEN_APP_ARGS_JSON: JSON.stringify([openerScript.scriptPath]),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-1",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.targetApp, "微信");
assert.match(result.executionSummary, /executed/);
const openedApp = await fs.readFile(markerFile, "utf8");
assert.equal(openedApp, "微信");
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime defaults to open -a style args for macOS opener", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-default-"));
let openerCommand;
const markerFile = path.join(marker, "argv.json");
try {
openerCommand = await writeArgumentMarkerCommand(markerFile, "open");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "open",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: openerCommand.scriptPath,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-default",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.deepEqual(argv, ["-a", "微信"]);
} finally {
if (openerCommand?.scriptDir) {
await fs.rm(openerCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime maps Chrome to Google Chrome for macOS app opens", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-chrome-"));
let openerCommand;
const markerFile = path.join(marker, "argv.json");
try {
openerCommand = await writeArgumentMarkerCommand(markerFile, "open");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "open",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: openerCommand.scriptPath,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-chrome",
objective: "打开Chrome浏览器",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.targetApp, "Chrome");
assert.equal(result.automationTargetApp, "Google Chrome");
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.deepEqual(argv, ["-a", "Google Chrome"]);
} finally {
if (openerCommand?.scriptDir) {
await fs.rm(openerCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime opens browser urls directly without keystroke automation", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-browser-url-"));
let openerCommand;
const markerFile = path.join(marker, "argv.json");
try {
openerCommand = await writeArgumentMarkerCommand(markerFile, "open");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "osascript",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: openerCommand.scriptPath,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-browser-url",
objective: "打开软件 Chrome输入“https://example.com”回车",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.targetApp, "Chrome");
assert.equal(result.automationTargetApp, "Google Chrome");
assert.equal(result.targetUrl, "https://example.com");
assert.match(result.executionSummary, /open_app_url_executed/);
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.deepEqual(argv, ["-a", "Google Chrome", "https://example.com"]);
} finally {
if (openerCommand?.scriptDir) {
await fs.rm(openerCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime can prepend configured open -a style args", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-open-default-"));
let openerCommand;
const markerFile = path.join(marker, "argv.json");
try {
openerCommand = await writeArgumentMarkerCommand(markerFile, "open");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "open",
BOSS_COMPUTER_USE_OPEN_APP_COMMAND: openerCommand.scriptPath,
BOSS_COMPUTER_USE_OPEN_APP_ARGS_JSON: JSON.stringify(["-a"]),
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-open-default",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.deepEqual(argv, ["-a", "微信"]);
} finally {
if (openerCommand?.scriptDir) {
await fs.rm(openerCommand.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime supports osascript mode via injected command", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-osascript-"));
let openerScript;
const markerFile = path.join(marker, "argv.json");
try {
openerScript = await writeArgumentMarkerCommand(markerFile, "osascript");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "osascript",
PATH: `${openerScript.scriptDir}:${process.env.PATH || ""}`,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-osascript",
objective: "打开微信并准备切到聊天窗口",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.match(result.executionSummary, /mode=osascript/);
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.equal(argv[0], "-e");
assert.match(argv[1], /tell application "微信"/);
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime types quoted text in osascript mode and can submit", async () => {
const marker = await fs.mkdtemp(path.join(os.tmpdir(), "boss-computer-osascript-type-"));
let openerScript;
const markerFile = path.join(marker, "argv.json");
try {
openerScript = await writeArgumentMarkerCommand(markerFile, "osascript");
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_COMPUTER_USE_MODE: "osascript",
PATH: `${openerScript.scriptDir}:${process.env.PATH || ""}`,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "computer-osascript-type",
objective: "打开微信并输入“Boss 已接管当前线程”后发送",
context: { dryRun: false },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.equal(result.typedText, "Boss 已接管当前线程");
const argv = JSON.parse(await fs.readFile(markerFile, "utf8"));
assert.equal(argv[0], "-e");
assert.match(argv[1], /keystroke "Boss 已接管当前线程"/);
assert.match(argv[1], /key code 36/);
} finally {
if (openerScript?.scriptDir) {
await fs.rm(openerScript.scriptDir, { recursive: true, force: true });
}
await fs.rm(marker, { recursive: true, force: true });
}
});
test("computer use smoke runtime writes action artifact when BOSS_CONTROL_ARTIFACT_DIR is set", async () => {
const artifactDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-desktop-artifacts-"));
try {
const result = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, [path.join(repoRoot, "scripts", "computer-use-smoke.mjs")], {
cwd: repoRoot,
env: {
...process.env,
BOSS_CONTROL_ARTIFACT_DIR: artifactDir,
},
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
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", reject);
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(stderr.trim() || `exit code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout.trim().split(/\r?\n/).at(-1) || ""));
} catch (error) {
reject(error);
}
});
child.stdin.write(
JSON.stringify({
requestKind: "desktop_control",
requestId: "desktop-artifact-1",
objective: "打开系统设置",
context: { dryRun: true },
}),
);
child.stdin.end();
});
assert.equal(result.status, "completed");
assert.ok(Array.isArray(result.artifacts));
assert.ok(result.artifacts[0]?.path);
const artifactText = await fs.readFile(result.artifacts[0].path, "utf8");
assert.match(artifactText, /系统设置/);
assert.match(artifactText, /mode/);
} finally {
await fs.rm(artifactDir, { recursive: true, force: true });
}
});