Files
openworker/deploy/reference/qualify-coding-runner.mjs

151 lines
4.0 KiB
JavaScript

// Container-level coding-tool smoke. Not an OpenShell/kernel-isolation claim.
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { createInterface } from "node:readline";
const image = process.argv[2];
assert(
image && /^[A-Za-z0-9./_:@-]+$/.test(image),
"Pass a trusted local image tag or immutable digest",
);
const child = spawn(
"docker",
[
"run",
"--rm",
"-i",
"--network",
"none",
"--read-only",
"--cap-drop",
"ALL",
"--security-opt",
"no-new-privileges",
"--pids-limit",
"64",
"--memory",
"512m",
"--tmpfs",
"/sandbox:rw,size=64m",
"--tmpfs",
"/tmp:rw,size=64m",
"--entrypoint",
"/opt/openworker/openworker",
image,
"mcp-coding",
"--workspace",
"/sandbox",
],
{ stdio: ["pipe", "pipe", "pipe"] },
);
const pending = new Map();
let id = 0,
stderr = "",
exited = false;
child.stderr.on("data", (b) => {
stderr = (stderr + b).slice(-8000);
});
child.on("error", (e) => {
for (const p of pending.values()) p.reject(e);
});
child.on("exit", (code) => {
exited = true;
for (const p of pending.values())
p.reject(Error(`Container exited ${code}: ${stderr}`));
});
const lines = createInterface({ input: child.stdout });
lines.on("line", (line) => {
try {
const r = JSON.parse(line);
const p = pending.get(r.id);
if (!p) return;
pending.delete(r.id);
clearTimeout(p.timer);
r.error ? p.reject(Error("MCP protocol error")) : p.resolve(r.result);
} catch (e) {
for (const p of pending.values()) p.reject(e);
}
});
function rpc(method, params = {}) {
const n = ++id;
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
pending.delete(n);
reject(Error("Coding MCP deadline"));
}, 20000);
pending.set(n, { resolve, reject, timer });
child.stdin.write(
JSON.stringify({ jsonrpc: "2.0", id: n, method, params }) + "\n",
);
});
}
async function tool(name, args) {
const r = await rpc("tools/call", { name, arguments: args });
assert(!r.isError, JSON.stringify(r.content));
return JSON.parse(r.content[0].text);
}
try {
await rpc("initialize", {
protocolVersion: "2025-11-25",
capabilities: {},
clientInfo: { name: "coding-qualification", version: "1" },
});
const catalog = await rpc("tools/list");
for (const name of [
"read_file",
"write_file",
"apply_patch",
"process_start",
"process_logs",
"process_status",
"process_stop",
"git_status",
])
assert(
catalog.tools.some((t) => t.name === name),
`Missing ${name}`,
);
await tool("write_file", {
path: "smoke.txt",
content: "coding runner smoke\n",
create_only: true,
});
const file = await tool("read_file", { path: "smoke.txt" });
assert(JSON.stringify(file).includes("coding runner smoke"));
const shell = await tool("run_shell", {
command:
"node --version && python3 --version && git --version && curl --version | head -n 1",
yield_ms: 10000,
timeout_seconds: 20,
});
assert.equal(shell.exit_code, 0);
assert(shell.stdout.includes("v24."));
const process = await tool("process_start", {
command: 'node -e "setInterval(() => {}, 1000)"',
timeout_seconds: 30,
});
assert(process.process_id && process.status === "running");
const status = await tool("process_status", {
process_id: process.process_id,
});
assert.equal(status.status, "running");
await tool("process_stop", { process_id: process.process_id });
console.log(
JSON.stringify({
passed: true,
scope: "network-disabled read-only container; no OpenShell claim",
tools: catalog.tools.length,
node24: true,
managedProcessSurvivedToolCalls: true,
hostCredentialsPassed: false,
}),
);
} finally {
child.stdin.end();
for (const p of pending.values()) clearTimeout(p.timer);
lines.close();
const timer = setTimeout(() => {
if (!exited) child.kill("SIGKILL");
}, 3000);
timer.unref();
}