Files
boss/tests/local-agent-heartbeat-computer-control-capabilities.test.mjs
2026-05-17 02:20:08 +08:00

216 lines
7.2 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { spawn } from "node:child_process";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
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 startMockControlPlane() {
let resolveHeartbeat;
const heartbeatReceived = new Promise((resolve) => {
resolveHeartbeat = resolve;
});
const server = createServer(async (request, response) => {
const chunks = [];
for await (const chunk of request) {
chunks.push(chunk);
}
if (request.method === "POST" && request.url === "/api/device-heartbeat") {
resolveHeartbeat(JSON.parse(Buffer.concat(chunks).toString("utf8")));
}
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({ ok: true }));
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("failed to bind mock control plane");
}
return { server, port: address.port, heartbeatReceived };
}
test("local-agent heartbeat reports browser automation and computer use capabilities", async () => {
const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-local-agent-computer-capabilities-"));
const skillsDir = path.join(runtimeRoot, "skills");
await mkdir(skillsDir, { recursive: true });
const mockControlPlane = await startMockControlPlane();
const exampleConfig = JSON.parse(
await readFile(path.join(repoRoot, "local-agent", "config.example.json"), "utf8"),
);
const configPath = path.join(runtimeRoot, "config.json");
await writeFile(
configPath,
JSON.stringify(
{
...exampleConfig,
bindHost: "127.0.0.1",
port: 0,
controlPlaneUrl: `http://127.0.0.1:${mockControlPlane.port}`,
heartbeatIntervalMs: 60_000,
masterAgentPollIntervalMs: 60_000,
masterAgentEnabled: false,
codexSessionDiscoveryEnabled: false,
projects: [],
projectCandidates: [],
skillsDir,
browserAutomationConnected: true,
computerUseConnected: false,
cuaDriverCommand: process.execPath,
},
null,
2,
),
"utf8",
);
const child = spawn(process.execPath, ["local-agent/server.mjs", configPath], {
cwd: repoRoot,
stdio: ["ignore", "pipe", "pipe"],
});
try {
const payload = await Promise.race([
mockControlPlane.heartbeatReceived,
new Promise((_, reject) => {
setTimeout(() => reject(new Error("timed out waiting for heartbeat")), 8000);
}),
]);
assert.equal(payload.capabilities.browserAutomation.connected, true);
assert.equal(payload.capabilities.computerUse.connected, true);
} finally {
child.kill("SIGTERM");
await new Promise((resolve) => child.once("close", resolve)).catch(() => null);
await new Promise((resolve) => mockControlPlane.server.close(resolve));
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("local-agent heartbeat reports Cua desktop control disconnected when cua-driver is unavailable", async () => {
const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-local-agent-cua-missing-"));
const skillsDir = path.join(runtimeRoot, "skills");
await mkdir(skillsDir, { recursive: true });
const mockControlPlane = await startMockControlPlane();
const exampleConfig = JSON.parse(
await readFile(path.join(repoRoot, "local-agent", "config.example.json"), "utf8"),
);
const configPath = path.join(runtimeRoot, "config.json");
await writeFile(
configPath,
JSON.stringify(
{
...exampleConfig,
bindHost: "127.0.0.1",
port: 0,
controlPlaneUrl: `http://127.0.0.1:${mockControlPlane.port}`,
heartbeatIntervalMs: 60_000,
masterAgentPollIntervalMs: 60_000,
masterAgentEnabled: false,
codexSessionDiscoveryEnabled: false,
projects: [],
projectCandidates: [],
skillsDir,
computerUseConnected: true,
cuaDriverCommand: path.join(runtimeRoot, "missing-cua-driver"),
},
null,
2,
),
"utf8",
);
const child = spawn(process.execPath, ["local-agent/server.mjs", configPath], {
cwd: repoRoot,
stdio: ["ignore", "pipe", "pipe"],
});
try {
const payload = await Promise.race([
mockControlPlane.heartbeatReceived,
new Promise((_, reject) => {
setTimeout(() => reject(new Error("timed out waiting for heartbeat")), 8000);
}),
]);
assert.equal(payload.capabilities.computerUse.connected, false);
} finally {
child.kill("SIGTERM");
await new Promise((resolve) => child.once("close", resolve)).catch(() => null);
await new Promise((resolve) => mockControlPlane.server.close(resolve));
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("local-agent heartbeat derives browser and computer control capabilities from runtime config", async () => {
const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-local-agent-runtime-capabilities-"));
const skillsDir = path.join(runtimeRoot, "skills");
await mkdir(skillsDir, { recursive: true });
const mockControlPlane = await startMockControlPlane();
const exampleConfig = JSON.parse(
await readFile(path.join(repoRoot, "local-agent", "config.example.json"), "utf8"),
);
const configPath = path.join(runtimeRoot, "config.json");
await writeFile(
configPath,
JSON.stringify(
{
...exampleConfig,
bindHost: "127.0.0.1",
port: 0,
controlPlaneUrl: `http://127.0.0.1:${mockControlPlane.port}`,
heartbeatIntervalMs: 60_000,
masterAgentPollIntervalMs: 60_000,
masterAgentEnabled: false,
codexSessionDiscoveryEnabled: false,
projects: [],
projectCandidates: [],
skillsDir,
browserAutomationConnected: false,
computerUseConnected: false,
browserControlEnabled: true,
browserControlCommand: process.execPath,
browserControlArgs: ["tests/fixtures/browser-control-runtime.mjs"],
computerUseEnabled: true,
computerUseCommand: process.execPath,
computerUseArgs: ["tests/fixtures/computer-use-runtime.mjs"],
},
null,
2,
),
"utf8",
);
const child = spawn(process.execPath, ["local-agent/server.mjs", configPath], {
cwd: repoRoot,
stdio: ["ignore", "pipe", "pipe"],
});
try {
const payload = await Promise.race([
mockControlPlane.heartbeatReceived,
new Promise((_, reject) => {
setTimeout(() => reject(new Error("timed out waiting for heartbeat")), 8000);
}),
]);
assert.equal(payload.capabilities.browserAutomation.connected, true);
assert.equal(payload.capabilities.computerUse.connected, true);
} finally {
child.kill("SIGTERM");
await new Promise((resolve) => child.once("close", resolve)).catch(() => null);
await new Promise((resolve) => mockControlPlane.server.close(resolve));
await rm(runtimeRoot, { recursive: true, force: true });
}
});