源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)

This commit is contained in:
agent-radar
2026-09-02 01:05:41 +08:00
commit 04bac1f6e2
2200 changed files with 489415 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@openwork/hosts",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"development": "./src/index.ts",
"default": "./src/index.ts"
},
"./daytona": {
"types": "./src/daytona.ts",
"development": "./src/daytona.ts",
"default": "./src/daytona.ts"
},
"./local": {
"types": "./src/local.ts",
"development": "./src/local.ts",
"default": "./src/local.ts"
},
"./resolve": {
"types": "./src/resolve.ts",
"development": "./src/resolve.ts",
"default": "./src/resolve.ts"
},
"./types": {
"types": "./src/types.ts",
"development": "./src/types.ts",
"default": "./src/types.ts"
}
},
"dependencies": {
"@openwork/cdp": "workspace:*",
"@openwork/paths": "link:../../../packages/paths",
"@openwork/timeline": "workspace:*"
}
}
+70
View File
@@ -0,0 +1,70 @@
import { attachSurface } from "@openwork/cdp";
import { resolveHost } from "./resolve.ts";
import type { AttachedSurface, SurfaceHandle } from "@openwork/cdp";
import type { Host } from "./types.ts";
const DEFAULT_TIMEOUT_MS = 60_000;
function messageText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
export interface BrowserOptions {
name?: string;
/**
* Where this browser runs. Defaults to the ambient host (`resolveHost()`).
* Pass one from `localHost()` / `daytonaSandbox(id)` to place it somewhere
* other than the app under test.
*/
host?: Host;
startUrl?: string;
headless?: boolean;
timeoutMs?: number;
}
/**
* A real browser, placed explicitly — the counterpart to `desktop()`.
*
* Owning disposal here matters: `attachSurface` only closes the CDP socket, so
* hand-rolled `spawnChrome` + `attachSurface` left the browser PROCESS running
* after every spec. Disposing this handle stops the client and then asks the
* host to dispose the surface, which is what actually kills it.
*/
export async function chrome(opts: BrowserOptions = {}): Promise<AttachedSurface> {
const host = opts.host ?? await resolveHost();
const name = opts.name ?? "browser";
const handle: SurfaceHandle = await host.spawnChrome(name, {
profile: "fresh",
startUrl: opts.startUrl,
headless: opts.headless,
});
let surface: AttachedSurface;
try {
surface = await attachSurface(handle, { timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS });
} catch (error) {
await host.disposeSurface(handle).catch(() => undefined);
throw error;
}
// Mutate rather than copy: `reattachSurface`/`evaluateOnSurface` assign to
// `surface.client`, so callers must hold the same object the CDP layer heals.
let stopped = false;
const closeClient = surface.stop.bind(surface);
const stop = async (): Promise<void> => {
if (stopped) return;
stopped = true;
try {
await closeClient();
} finally {
await host.disposeSurface(handle);
}
};
surface.stop = stop;
surface[Symbol.asyncDispose] = async (): Promise<void> => {
await stop().catch((error: unknown) => {
console.warn(`[openwork/evals] Browser ${name} cleanup failed: ${messageText(error)}`);
});
};
return surface;
}
+854
View File
@@ -0,0 +1,854 @@
import { spawn } from "node:child_process";
import { randomBytes } from "node:crypto";
import { readFileSync } from "node:fs";
import { setTimeout } from "node:timers/promises";
import { fileURLToPath } from "node:url";
import type { ChromeSurfaceOptions, DenServiceHandle, DenServiceOptions, ElectronSurfaceOptions, Host, ShareLinks, SurfaceHandle } from "./types.ts";
export interface DaytonaExecResult {
stdout: string;
stderr: string;
code: number;
}
export type DaytonaExec = (
args: string[],
opts?: { input?: string; timeoutMs?: number },
) => Promise<DaytonaExecResult>;
export interface DaytonaHostOptions {
sandboxId?: string;
log: (msg: string) => void;
exec?: DaytonaExec;
repoRoot: string;
reservedChromePorts?: number[];
reservedElectronPorts?: number[];
serverScript?: boolean;
waitForCdp?: (url: string, timeoutMs: number, label: string) => Promise<void>;
}
export interface DaytonaHost extends Host, AsyncDisposable {
previewUrl(port: number): Promise<string>;
startDen(opts?: DenServiceOptions): Promise<DenServiceHandle>;
share(): Promise<ShareLinks>;
stop(): Promise<void>;
}
export type EnterpriseTlsEdgeDaytonaOptions = {
sandboxId: string;
upstream: string;
candidatePort?: number;
negativePort?: number;
adminPort?: number;
manifestPath?: string;
};
export type EnterpriseTlsEdgeDaytonaCommands = {
candidateUrl: string;
negativeUrl: string;
adminUrl: string;
manifestPath: string;
prepare: string[][];
start: string[];
probe: string[];
requests: string[];
installRoot: string[];
removeRoot: string[];
stop: string[];
};
interface PortAllocation {
primary: number;
next: number;
used: Set<number>;
}
interface ProcessRunOptions {
cwd: string;
timeoutMs: number;
onOutput: (text: string) => void;
}
const ELECTRON_CDP_WAIT_MS = 180_000;
const CHROME_CDP_WAIT_MS = 60_000;
const CDP_POLL_INTERVAL_MS = 1_000;
const SERVER_SCRIPT_TIMEOUT_MS = 20 * 60 * 1_000;
const STANDARD_NOVNC_PORT = 6080;
const STANDARD_ARTIFACTS_PORT = 8090;
const HTTPS_URL = /https:\/\/[^\s"'<>)]+/;
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
const ENTERPRISE_TLS_RUNTIME_ROOT = "/tmp/openwork-enterprise-tls-runtime";
const MAX_ENTERPRISE_TLS_RUNTIME_SOURCE_BYTES = 64 * 1024;
const ENTERPRISE_TLS_BASE64_CHUNK_LENGTH = 8 * 1024;
/** Conservative ceiling for each complete Daytona argv command string. */
export const MAX_ENTERPRISE_TLS_DAYTONA_COMMAND_LENGTH = 12 * 1024;
const ENTERPRISE_TLS_RUNTIME_SOURCES = [
{
local: new URL("../../../scripts/enterprise-tls-edge.mts", import.meta.url),
remote: `${ENTERPRISE_TLS_RUNTIME_ROOT}/evals/scripts/enterprise-tls-edge.mts`,
},
{
local: new URL("../../labs/src/egress.ts", import.meta.url),
remote: `${ENTERPRISE_TLS_RUNTIME_ROOT}/evals/packages/labs/src/egress.ts`,
},
];
function messageText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
export function defaultDaytonaExec(
args: string[],
opts: { input?: string; timeoutMs?: number } = {},
command = "daytona",
): Promise<DaytonaExecResult> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
let stdout = "";
let stderr = "";
let timedOut = false;
let settled = false;
const timer = opts.timeoutMs
? globalThis.setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
}, opts.timeoutMs)
: null;
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += String(chunk);
});
child.stderr.on("data", (chunk) => {
stderr += String(chunk);
});
child.on("error", (error) => {
if (timer) clearTimeout(timer);
if (settled) return;
settled = true;
reject(error);
});
child.on("close", (code) => {
if (timer) clearTimeout(timer);
if (settled) return;
settled = true;
resolve({
stdout,
stderr: timedOut ? `${stderr}\nTimed out after ${opts.timeoutMs}ms.` : stderr,
code: timedOut ? 124 : code ?? 1,
});
});
child.stdin.on("error", (error) => {
if (("code" in error && error.code === "EPIPE") || settled) return;
if (timer) clearTimeout(timer);
settled = true;
reject(error);
});
child.stdin.end(opts.input ?? "");
});
}
function runProcess(command: string, args: string[], opts: ProcessRunOptions): Promise<DaytonaExecResult> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { cwd: opts.cwd, stdio: ["ignore", "pipe", "pipe"] });
let stdout = "";
let stderr = "";
let timedOut = false;
const timer = globalThis.setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
}, opts.timeoutMs);
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
const text = String(chunk);
stdout += text;
opts.onOutput(text);
});
child.stderr.on("data", (chunk) => {
const text = String(chunk);
stderr += text;
opts.onOutput(text);
});
child.on("error", (error) => {
clearTimeout(timer);
reject(error);
});
child.on("close", (code) => {
clearTimeout(timer);
resolve({
stdout,
stderr: timedOut ? `${stderr}\nTimed out after ${opts.timeoutMs}ms.` : stderr,
code: timedOut ? 124 : code ?? 1,
});
});
});
}
function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'"'"'`)}'`;
}
function assertEnvName(name: string): void {
if (!ENV_NAME.test(name)) {
throw new Error(`Invalid environment variable name for Daytona surface: ${name}`);
}
}
function shellExport(assignments: Map<string, string>): string {
const parts: string[] = [];
for (const [name, value] of assignments) {
assertEnvName(name);
parts.push(`${name}=${shellQuote(value)}`);
}
return `export ${parts.join(" ")};`;
}
function sanitizeName(name: string): string {
const normalized: string[] = [];
let invalidRun = false;
for (const character of name.trim()) {
const code = character.codePointAt(0) ?? 0;
const allowed = (code >= 48 && code <= 57)
|| (code >= 65 && code <= 90)
|| (code >= 97 && code <= 122)
|| character === "."
|| character === "_"
|| character === "-";
if (allowed) {
normalized.push(character);
invalidRun = false;
} else if (!invalidRun) {
normalized.push("-");
invalidRun = true;
}
}
let start = 0;
let end = normalized.length;
while (start < end && normalized[start] === "-") start += 1;
while (end > start && normalized[end - 1] === "-") end -= 1;
return normalized.slice(start, end).join("") || "surface";
}
function timestamp(): string {
return new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 17);
}
function firstHttpsUrl(text: string): string | null {
const match = HTTPS_URL.exec(text);
return match ? match[0].replace(/[.,;:]+$/, "") : null;
}
function cleanBaseUrl(value: string): string {
return value.trim().replace(/\/+$/, "");
}
function isOrgMode(value: unknown): value is "single_org" | "multi_org" {
return value === "single_org" || value === "multi_org";
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
async function runtimeOrgMode(webUrl: string): Promise<"single_org" | "multi_org"> {
const response = await fetch(`${cleanBaseUrl(webUrl)}/api/runtime-config`, { signal: AbortSignal.timeout(5_000) });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const body: unknown = await response.json();
if (isRecord(body) && isOrgMode(body.orgMode)) return body.orgMode;
throw new Error("response did not include orgMode");
}
async function orgModeOrDefault(webUrl: string, log: (msg: string) => void): Promise<"single_org" | "multi_org"> {
try {
return await runtimeOrgMode(webUrl);
} catch (error) {
log(`Could not read Den runtime config from ${cleanBaseUrl(webUrl)}/api/runtime-config; assuming multi_org: ${messageText(error)}`);
return "multi_org";
}
}
/**
* Daytona CLI transport failures print `level=fatal msg="..."` (HTML error
* pages, EOFs, resets from the Daytona API) and mean the remote command never
* executed, so retrying is always safe. Remote command failures — a non-zero
* exit without a CLI fatal transport message — are never retried.
*/
const TRANSIENT_DAYTONA_CLI_MESSAGES = [
/invalid character '<'/i,
/unexpected EOF/i,
/^EOF$/,
/unexpected end of JSON input/i,
/connection reset/i,
/bad gateway/i,
/service unavailable/i,
/too many requests/i,
];
function transientDaytonaCliFailure(result: DaytonaExecResult): boolean {
if (result.code === 0) return false;
const fatalMessages = [...`${result.stderr}\n${result.stdout}`.matchAll(/level=fatal msg="((?:[^"\\]|\\.)*)"/g)]
.map((match) => match[1]);
return fatalMessages.some((message) => TRANSIENT_DAYTONA_CLI_MESSAGES.some((pattern) => pattern.test(message)));
}
export async function checkedExec(exec: DaytonaExec, args: string[], context: string, opts: { input?: string; timeoutMs?: number; retryDelayMs?: number } = {}): Promise<DaytonaExecResult> {
const attempts = 3;
const { retryDelayMs, ...execOpts } = opts;
let result = await exec(args, execOpts);
for (let attempt = 1; attempt < attempts && transientDaytonaCliFailure(result); attempt += 1) {
await setTimeout(retryDelayMs ?? attempt * 2_000);
result = await exec(args, execOpts);
}
if (result.code !== 0) {
const stderr = result.stderr.trim();
const stdout = result.stdout.trim();
const details = [stderr ? `stderr:\n${stderr}` : "", stdout ? `stdout:\n${stdout}` : ""].filter(Boolean).join("\n\n");
throw new Error(`${context} failed with exit ${result.code}${details ? `:\n${details}` : ""}`);
}
return result;
}
/** Daytona exec argv for a co-located enterprise TLS edge lifecycle. */
export function enterpriseTlsEdgeDaytonaCommands(options: EnterpriseTlsEdgeDaytonaOptions): EnterpriseTlsEdgeDaytonaCommands {
const sandbox = options.sandboxId.trim();
if (!sandbox) throw new Error("Enterprise TLS edge Daytona sandboxId is required.");
const upstream = new URL(options.upstream);
if ((upstream.protocol !== "http:" && upstream.protocol !== "https:")
|| upstream.username || upstream.password || upstream.pathname !== "/" || upstream.search || upstream.hash) {
throw new Error("Enterprise TLS edge upstream must be an HTTP(S) origin.");
}
const candidatePort = options.candidatePort ?? 8443;
const negativePort = options.negativePort ?? 9443;
const adminPort = options.adminPort ?? 8445;
for (const port of [candidatePort, negativePort, adminPort]) {
if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error(`Invalid enterprise TLS edge port: ${port}`);
}
if (new Set([candidatePort, negativePort, adminPort]).size !== 3) {
throw new Error("Enterprise TLS edge candidate, negative, and admin ports must be distinct.");
}
const manifestPath = options.manifestPath ?? "/tmp/openwork-enterprise-tls-edge.json";
if (!manifestPath.startsWith("/")) throw new Error("Enterprise TLS edge manifestPath must be absolute.");
const sources = ENTERPRISE_TLS_RUNTIME_SOURCES.map(({ local, remote }) => ({
content: readFileSync(fileURLToPath(local)),
remote,
}));
const sourceBytes = sources.reduce((total, source) => total + source.content.byteLength, 0);
if (sourceBytes > MAX_ENTERPRISE_TLS_RUNTIME_SOURCE_BYTES) {
throw new Error(`Enterprise TLS runtime source is ${sourceBytes} bytes; maximum is ${MAX_ENTERPRISE_TLS_RUNTIME_SOURCE_BYTES}.`);
}
const script = ENTERPRISE_TLS_RUNTIME_SOURCES[0].remote;
const log = "/tmp/openwork-enterprise-tls-edge.log";
const adminToken = randomBytes(32).toString("hex");
if (!/^[a-f0-9]{32,}$/.test(adminToken)) throw new Error("Enterprise TLS admin token must be at least 32 hex characters.");
const remote = (command: string) => {
const args = ["exec", sandbox, "--", `bash -lc ${shellQuote(command)}`];
const commandLength = args.join(" ").length;
if (commandLength > MAX_ENTERPRISE_TLS_DAYTONA_COMMAND_LENGTH) {
throw new Error(`Enterprise TLS Daytona command is ${commandLength} characters; maximum is ${MAX_ENTERPRISE_TLS_DAYTONA_COMMAND_LENGTH}.`);
}
return args;
};
const directories = sources.map((source) => source.remote.slice(0, source.remote.lastIndexOf("/")));
const prepare = [remote([
`/usr/bin/rm -rf ${ENTERPRISE_TLS_RUNTIME_ROOT}`,
`/usr/bin/mkdir -p ${directories.join(" ")}`,
`/usr/bin/touch ${sources.map((source) => `${source.remote}.b64`).join(" ")}`,
].join(" && "))];
for (const source of sources) {
const encoded = source.content.toString("base64");
for (let offset = 0; offset < encoded.length; offset += ENTERPRISE_TLS_BASE64_CHUNK_LENGTH) {
const chunk = encoded.slice(offset, offset + ENTERPRISE_TLS_BASE64_CHUNK_LENGTH);
prepare.push(remote(`/usr/bin/printf %s ${chunk} >> ${source.remote}.b64`));
}
prepare.push(remote([
"decode_status=0",
`/usr/bin/base64 -d ${source.remote}.b64 > ${source.remote} || decode_status=$?`,
`actual_bytes=$(/usr/bin/wc -c < ${source.remote})`,
`/usr/bin/rm -f ${source.remote}.b64`,
'test "$decode_status" -eq 0',
`test "$actual_bytes" -eq ${source.content.byteLength}`,
].join("; ")));
}
const serve = [
"/usr/bin/env", "node", script, "serve",
"--upstream", upstream.origin,
"--candidate-port", String(candidatePort),
"--negative-port", String(negativePort),
"--admin-port", String(adminPort),
"--manifest", manifestPath,
].map(shellQuote).join(" ");
const action = (name: "install" | "remove") => remote([
"node_path=$(command -v node)",
'test -n "$node_path"',
`/usr/bin/sudo -n "$node_path" ${[script, name, "--manifest", manifestPath].map(shellQuote).join(" ")}`,
].join(" && "));
const adminUrl = `http://127.0.0.1:${adminPort}`;
return {
candidateUrl: `https://localhost:${candidatePort}`,
negativeUrl: `https://localhost:${negativePort}`,
adminUrl,
manifestPath,
prepare,
start: remote([
`rm -f ${shellQuote(manifestPath)}`,
`ENTERPRISE_TLS_ADMIN_TOKEN=${adminToken} nohup ${serve} >${shellQuote(log)} 2>&1 </dev/null &`,
"attempt=0",
`until /usr/bin/curl --fail --silent -H "Authorization: Bearer ${adminToken}" ${shellQuote(`${adminUrl}/health`)} >/dev/null; do attempt=$((attempt + 1)); if [ "$attempt" -ge 120 ]; then /usr/bin/tail -c 4000 ${shellQuote(log)} >&2; exit 1; fi; sleep 0.25; done`,
].join("\n")),
probe: remote(`/usr/bin/curl --fail --silent --show-error -H "Authorization: Bearer ${adminToken}" ${shellQuote(`${adminUrl}/health`)}`),
requests: remote(`/usr/bin/curl --fail --silent --show-error -H "Authorization: Bearer ${adminToken}" ${shellQuote(`${adminUrl}/requests`)}`),
installRoot: action("install"),
removeRoot: action("remove"),
stop: remote(`${[
"/usr/bin/env", "node", script, "stop", "--manifest", manifestPath,
].map(shellQuote).join(" ")} && /usr/bin/rm -rf ${shellQuote(ENTERPRISE_TLS_RUNTIME_ROOT)}`),
};
}
async function waitForHttpOk(url: string, timeoutMs: number, label: string): Promise<void> {
const startedAt = Date.now();
let lastError = "not attempted";
while (Date.now() - startedAt < timeoutMs) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(5_000) });
if (response.ok) return;
lastError = `HTTP ${response.status}`;
} catch (error) {
lastError = messageText(error);
}
await setTimeout(CDP_POLL_INTERVAL_MS);
}
throw new Error(`Timed out after ${timeoutMs}ms waiting for ${label} at ${url} (last error: ${lastError}).`);
}
function allocatePort(allocation: PortAllocation): number {
if (!allocation.used.has(allocation.primary)) {
allocation.used.add(allocation.primary);
return allocation.primary;
}
let port = allocation.next;
while (allocation.used.has(port)) port += 1;
allocation.used.add(port);
allocation.next = port + 1;
return port;
}
function releasePort(allocation: PortAllocation, port: number): void {
allocation.used.delete(port);
}
function cdpPort(handle: SurfaceHandle): number | null {
const raw = handle.meta?.cdpPort;
if (!raw || !/^\d+$/.test(raw)) return null;
return Number.parseInt(raw, 10);
}
function regexEscape(value: string): string {
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
}
function charClassEscape(value: string): string {
return value.replace(/\\/g, "\\\\").replace(/\]/g, "\\]").replace(/\^/g, "\\^").replace(/-/g, "\\-");
}
function selfMatchSafeLiteral(value: string): string {
if (!value) return "";
const first = value[0] ?? "";
return `[${charClassEscape(first)}]${regexEscape(value.slice(1))}`;
}
function selfMatchSafePortPattern(processPrefix: string, port: number): string {
const value = String(port);
return `${selfMatchSafeLiteral(processPrefix)}.*remote-debugging-port=${selfMatchSafeLiteral(value)}`;
}
function electronProfilePattern(profileDir: string): string {
return `([e]lectron|[/]proc/self/exe).*--user-data-dir=${selfMatchSafeLiteral(profileDir)}`;
}
function electronProfileRoot(profileDir: string): string {
const suffix = "/electron-userdata";
return profileDir.endsWith(suffix) ? profileDir.slice(0, -suffix.length) : profileDir;
}
function killGroupsForPatternCommand(pattern: string, signal: "TERM" | "KILL"): string {
return `for pid in $(pgrep -f ${shellQuote(pattern)} || true); do pgid=$(ps -o pgid= -p "$pid" | tr -d ' '); if [ -n "$pgid" ]; then kill -${signal} -"$pgid" 2>/dev/null || true; fi; done`;
}
function parseUrlAfterLabels(output: string, labels: string[]): string | null {
for (const line of output.split(/\r?\n/)) {
for (const label of labels) {
if (line.includes(label)) {
const url = firstHttpsUrl(line);
if (url) return url;
}
}
}
return null;
}
function serverRefArg(): string | null {
const explicit = process.env.OPENWORK_EVAL_DAYTONA_REF?.trim() || process.env.OPENWORK_EVAL_REF?.trim() || "";
return explicit || null;
}
function portSet(values: number[] | undefined): Set<number> {
const ports = new Set<number>();
for (const value of values ?? []) {
if (Number.isInteger(value) && value > 0 && value <= 65_535) ports.add(value);
}
return ports;
}
/**
* Ports already listening INSIDE the sandbox.
*
* The local host finds a free port by binding one; this host cannot, because the
* port has to be free on a different machine. Allocating from a local counter
* alone silently collides: the OpenCode sidecar picks its own port at boot and
* was observed holding 9825 — the CDP primary — so Electron's debugger never
* bound and the preview URL timed out after 180s with no hint of the cause.
*/
async function sandboxListeningPorts(exec: DaytonaExec, sandbox: string): Promise<Set<number>> {
const result = await exec(["exec", sandbox, "--", "bash -lc 'ss -ltn 2>/dev/null || netstat -ltn 2>/dev/null'"], { timeoutMs: 30_000 });
const ports = new Set<number>();
if (result.code !== 0) return ports;
for (const line of result.stdout.split(/\r?\n/)) {
// Match the local-address column's :PORT, e.g. "127.0.0.1:9825" or "*:3005".
const match = /[:.](\d{2,5})\s+\S+\s*$/.exec(line.trim()) ?? /[:.](\d{2,5})\s/.exec(line.trim());
if (!match) continue;
const port = Number.parseInt(match[1] ?? "", 10);
if (Number.isInteger(port) && port > 0 && port <= 65_535) ports.add(port);
}
return ports;
}
/** Allocate a port that is free in the sandbox, not merely unused by this host. */
async function allocateSandboxPort(allocation: PortAllocation, exec: DaytonaExec, sandbox: string): Promise<number> {
const taken = await sandboxListeningPorts(exec, sandbox);
for (let attempt = 0; attempt < 64; attempt += 1) {
const port = allocatePort(allocation);
if (!taken.has(port)) return port;
// Keep it marked used so we never hand it out again this session.
}
throw new Error(`Could not find a port free inside sandbox ${sandbox} after 64 attempts.`);
}
function appendExtraEnv(assignments: Map<string, string>, env: Record<string, string> | undefined): void {
if (!env) return;
for (const [name, value] of Object.entries(env).sort(([left], [right]) => left.localeCompare(right))) {
assignments.set(name, value);
}
}
export function createDaytonaHost(options: DaytonaHostOptions): DaytonaHost {
const exec = options.exec ?? defaultDaytonaExec;
const previewCache = new Map<number, string>();
const electronPorts: PortAllocation = { primary: 9825, next: 9830, used: portSet(options.reservedElectronPorts) };
const chromePorts: PortAllocation = { primary: 9222, next: 9230, used: portSet(options.reservedChromePorts) };
const surfacePorts = new Map<number, string>();
const waitForCdp = options.waitForCdp ?? waitForHttpOk;
const spawnedSurfaces = new Set<SurfaceHandle>();
function requireSandbox(): string {
const sandbox = options.sandboxId?.trim() || process.env.OPENWORK_EVAL_DAYTONA_SANDBOX?.trim() || "";
if (!sandbox) {
throw new Error("Daytona sandbox required: create one with bash .devcontainer/test-on-daytona.sh <ref> or pass sandboxId.");
}
return sandbox;
}
async function previewUrl(port: number): Promise<string> {
const cached = previewCache.get(port);
if (cached) return cached;
const sandbox = requireSandbox();
const result = await checkedExec(exec, ["preview-url", sandbox, "-p", String(port)], `daytona preview-url ${sandbox} -p ${port}`);
const url = firstHttpsUrl(result.stdout);
if (!url) {
throw new Error(`daytona preview-url ${sandbox} -p ${port} did not print an https URL: ${result.stdout.trim()}`);
}
previewCache.set(port, url);
return url;
}
async function spawnElectron(name: string, opts: ElectronSurfaceOptions = {}): Promise<SurfaceHandle> {
const sandbox = requireSandbox();
const safeName = sanitizeName(name);
const spawnStamp = timestamp();
if (opts.profileDir !== undefined && !opts.profileDir.trim()) {
throw new Error("Electron profileDir must not be empty.");
}
const callerOwnedProfile = opts.profileDir !== undefined;
const profileRoot = opts.profileDir ?? `/workspace/.openwork-daytona/profiles/${safeName}-${spawnStamp}`;
const userDataDir = `${profileRoot}/electron-userdata`;
const bootstrapPath = `${profileRoot}/bootstrap.json`;
const port = await allocateSandboxPort(electronPorts, exec, sandbox);
// Per-spawn log so the integrity check below can never read a previous
// run's lines.
const logPath = `/tmp/electron-${safeName}-${spawnStamp}.log`;
try {
await checkedExec(exec, ["exec", sandbox, "--", "mkdir", "-p", shellQuote(userDataDir)], `mkdir Daytona Electron profile ${userDataDir}`, { timeoutMs: 30_000 });
if (opts.bootstrap) {
const bootstrapJson = `${JSON.stringify(opts.bootstrap, null, 2)}\n`;
const encoded = Buffer.from(bootstrapJson, "utf8").toString("base64");
await checkedExec(
exec,
["exec", sandbox, "--", "echo", encoded, "|", "base64", "-d", ">", shellQuote(bootstrapPath)],
`write Daytona Electron bootstrap ${bootstrapPath}`,
{ timeoutMs: 30_000 },
);
}
const env = new Map<string, string>();
appendExtraEnv(env, opts.env);
env.set("DAYTONA_ELECTRON_LOG", logPath);
env.set("OPENWORK_ELECTRON_REMOTE_DEBUG_PORT", String(port));
env.set("OPENWORK_ELECTRON_USERDATA", userDataDir);
env.set("OPENWORK_WORKSPACE_DIR", "/workspace");
env.set("OPENWORK_GOOGLE_WORKSPACE_ALLOW_PLAINTEXT_VAULT", "1");
const packagedBinary = process.env.OPENWORK_EVAL_ELECTRON_BINARY?.trim();
if (packagedBinary) env.set("OPENWORK_EVAL_ELECTRON_BINARY", packagedBinary);
if (opts.bootstrap) env.set("OPENWORK_DESKTOP_BOOTSTRAP_PATH", bootstrapPath);
const startCommand = `set -euo pipefail; cd /workspace; ${shellExport(env)} bash /workspace/.devcontainer/start-daytona-electron.sh --detach`;
await checkedExec(
exec,
["exec", sandbox, "--", `bash -lc ${shellQuote(startCommand)}`],
`start Daytona Electron surface ${name}`,
{ timeoutMs: 60_000 },
);
const cdpUrl = await previewUrl(port);
// SurfaceRegistry also waits 30s before attaching, but Daytona preview URLs
// can route before cold Electron CDP is actually responsive; give remote
// sandboxes a longer preflight here so attach remains a normal fast probe.
await waitForCdp(`${cleanBaseUrl(cdpUrl)}/json/list`, ELECTRON_CDP_WAIT_MS, `Electron CDP ${name}`);
// Spawn integrity: the port serving CDP must belong to THIS spawn, not a
// pre-existing instance. electron-dev.mjs logs the resolved CDP port; if
// it differs from the one we exported, the bind failed (port collision)
// and attaching would silently drive the wrong app.
const spawnLog = await checkedExec(
exec,
["exec", sandbox, "--", "tail", "-c", "4000", logPath],
`read Daytona Electron log ${logPath}`,
{ timeoutMs: 30_000 },
);
const exposed = /Electron CDP exposed at http:\/\/127\.0\.0\.1:(\d+)/.exec(spawnLog.stdout);
if (exposed && exposed[1] !== String(port)) {
throw new Error(
`Daytona Electron surface ${name} resolved CDP port ${exposed[1]} instead of ${port} (port collision?). Log tail:\n${spawnLog.stdout.slice(-1200)}`,
);
}
if (spawnLog.stdout.includes("Cannot start http server for devtools")) {
throw new Error(
`Daytona Electron surface ${name} could not bind its devtools port ${port}. Log tail:\n${spawnLog.stdout.slice(-1200)}`,
);
}
surfacePorts.set(port, `electron:${name} CDP`);
const handle: SurfaceHandle = {
name,
kind: "electron",
hostKind: "daytona",
cdpUrl,
sandboxId: sandbox,
profileDir: profileRoot,
meta: { cdpPort: String(port), log: logPath, profileOwner: callerOwnedProfile ? "caller" : "host" },
};
spawnedSurfaces.add(handle);
return handle;
} catch (error) {
releasePort(electronPorts, port);
surfacePorts.delete(port);
throw error;
}
}
async function spawnChrome(name: string, opts: ChromeSurfaceOptions = {}): Promise<SurfaceHandle> {
const sandbox = requireSandbox();
const safeName = sanitizeName(name);
const port = await allocateSandboxPort(chromePorts, exec, sandbox);
const profileDir = `/tmp/daytona-chrome-${safeName}`;
const logPath = `/tmp/daytona-chrome-${safeName}.log`;
const startUrl = opts.startUrl?.trim() || "about:blank";
const command = [
"set -euo pipefail",
`mkdir -p ${shellQuote(profileDir)}`,
"CHROME_BIN=\"$(command -v chromium || command -v google-chrome || command -v google-chrome-stable || true)\"",
"if [ -z \"$CHROME_BIN\" ]; then echo 'No chromium/google-chrome binary found in sandbox.' >&2; exit 127; fi",
`DISPLAY=:99 nohup "$CHROME_BIN" --headless=new --window-size=1280,900 --no-sandbox --disable-dev-shm-usage --ignore-gpu-blocklist --use-gl=swiftshader --enable-unsafe-swiftshader --disable-http2 --remote-debugging-address=0.0.0.0 --remote-debugging-port=${port} --user-data-dir=${shellQuote(profileDir)} ${shellQuote(startUrl)} >${shellQuote(logPath)} 2>&1 &`,
].join("; ");
try {
await checkedExec(exec, ["exec", sandbox, "--", `bash -lc ${shellQuote(command)}`], `start Daytona Chrome surface ${name}`, { timeoutMs: 30_000 });
const cdpUrl = await previewUrl(port);
await waitForCdp(`${cleanBaseUrl(cdpUrl)}/json/version`, CHROME_CDP_WAIT_MS, `Chrome CDP ${name}`);
surfacePorts.set(port, `chrome:${name} CDP`);
const handle: SurfaceHandle = {
name,
kind: "chrome",
hostKind: "daytona",
cdpUrl,
sandboxId: sandbox,
profileDir,
meta: { cdpPort: String(port), log: logPath },
};
spawnedSurfaces.add(handle);
return handle;
} catch (error) {
releasePort(chromePorts, port);
surfacePorts.delete(port);
throw error;
}
}
async function startDen(opts: DenServiceOptions = {}): Promise<DenServiceHandle> {
const apiUrl = process.env.OPENWORK_EVAL_DEN_API_URL?.trim();
const webUrl = process.env.OPENWORK_EVAL_DEN_WEB_URL?.trim();
if (apiUrl && webUrl) {
return {
webUrl,
apiUrl,
orgMode: await orgModeOrDefault(webUrl, options.log),
hostKind: "daytona",
};
}
if (!options.serverScript) {
throw new Error("No Den URLs in OPENWORK_EVAL_DEN_API_URL/OPENWORK_EVAL_DEN_WEB_URL. Set them, or create the server with bash .devcontainer/test-server-on-daytona.sh <ref> and rerun with serverScript enabled.");
}
const args = [".devcontainer/test-server-on-daytona.sh"];
const ref = serverRefArg();
// The provisioning script can take several minutes because it may create a
// Daytona sandbox and build/install the Den stack; keep the timeout generous.
if (ref) args.push(ref);
const result = await runProcess("bash", args, {
cwd: options.repoRoot,
timeoutMs: SERVER_SCRIPT_TIMEOUT_MS,
onOutput: options.log,
});
if (result.code !== 0) {
const details = (result.stderr || result.stdout).trim();
throw new Error(`Daytona Den server script failed with exit ${result.code}${details ? `: ${details}` : ""}`);
}
const parsedWebUrl = parseUrlAfterLabels(result.stdout, ["DEN_WEB_URL", "Den Web:"]);
const parsedApiUrl = parseUrlAfterLabels(result.stdout, ["DEN_API_URL", "Den API:"]);
if (!parsedWebUrl || !parsedApiUrl) {
throw new Error("Daytona Den server script did not print Den Web and Den API URLs.");
}
return {
webUrl: parsedWebUrl,
apiUrl: parsedApiUrl,
orgMode: opts.orgMode ?? await orgModeOrDefault(parsedWebUrl, options.log),
hostKind: "daytona",
};
}
async function disposeSurface(handle: SurfaceHandle): Promise<void> {
if (handle.hostKind !== "daytona") return;
const sandbox = handle.sandboxId?.trim() || requireSandbox();
const port = cdpPort(handle);
if (handle.kind === "electron" && port !== null) {
const pattern = selfMatchSafePortPattern("electron", port);
const profilePattern = handle.profileDir
? electronProfilePattern(`${electronProfileRoot(handle.profileDir)}/electron-userdata`)
: "";
const stopCommand = [
profilePattern ? killGroupsForPatternCommand(profilePattern, "TERM") : "true",
`pkill -f ${shellQuote(pattern)} || true`,
"sleep 1",
profilePattern ? killGroupsForPatternCommand(profilePattern, "KILL") : "true",
`pkill -f ${shellQuote(pattern)} || true`,
].join("; ");
await checkedExec(
exec,
["exec", sandbox, "--", `bash -lc ${shellQuote(stopCommand)}`],
`stop Daytona Electron surface ${handle.name}`,
{ timeoutMs: 15_000 },
);
await checkedExec(
exec,
["exec", sandbox, "--", `bash -lc ${shellQuote(`sleep 1; curl -sf http://127.0.0.1:${port}/json/list >/dev/null 2>&1 || true`)}`],
`verify Daytona Electron CDP stopped ${handle.name}`,
{ timeoutMs: 15_000 },
);
if (handle.profileDir && handle.meta?.profileOwner !== "caller") {
await checkedExec(
exec,
["exec", sandbox, "--", `bash -lc ${shellQuote(`rm -rf ${shellQuote(electronProfileRoot(handle.profileDir))}`)}`],
`remove Daytona Electron profile ${handle.name}`,
{ timeoutMs: 15_000 },
);
}
releasePort(electronPorts, port);
surfacePorts.delete(port);
spawnedSurfaces.delete(handle);
return;
}
if (handle.kind === "chrome") {
const profileDir = handle.profileDir ?? `/tmp/daytona-chrome-${sanitizeName(handle.name)}`;
const pattern = `(chromium|chrome).*--user-data-dir=${selfMatchSafeLiteral(profileDir)}`;
await checkedExec(
exec,
["exec", sandbox, "--", `bash -lc ${shellQuote(`pkill -f ${shellQuote(pattern)} || true`)}`],
`stop Daytona Chrome surface ${handle.name}`,
{ timeoutMs: 15_000 },
);
if (port !== null) {
await checkedExec(
exec,
["exec", sandbox, "--", `bash -lc ${shellQuote(`sleep 1; curl -sf http://127.0.0.1:${port}/json/version >/dev/null 2>&1 || true`)}`],
`verify Daytona Chrome CDP stopped ${handle.name}`,
{ timeoutMs: 15_000 },
);
await checkedExec(
exec,
["exec", sandbox, "--", `bash -lc ${shellQuote(`rm -rf ${shellQuote(profileDir)}`)}`],
`remove Daytona Chrome profile ${handle.name}`,
{ timeoutMs: 15_000 },
);
releasePort(chromePorts, port);
surfacePorts.delete(port);
}
spawnedSurfaces.delete(handle);
}
}
async function share() {
const links: { label: string; url: string }[] = [];
for (const [port, label] of surfacePorts) {
links.push({ label, url: await previewUrl(port) });
}
links.push({ label: "noVNC visual", url: await previewUrl(STANDARD_NOVNC_PORT) });
links.push({ label: "artifacts", url: await previewUrl(STANDARD_ARTIFACTS_PORT) });
return links;
}
async function stop(): Promise<void> {
for (const handle of [...spawnedSurfaces]) await disposeSurface(handle);
}
return {
kind: "daytona",
workspaceRoot: "/workspace",
previewUrl,
spawnElectron,
spawnChrome,
startDen,
share,
disposeSurface,
stop,
async [Symbol.asyncDispose](): Promise<void> {
for (const handle of [...spawnedSurfaces]) {
await disposeSurface(handle)
.catch((error: unknown) => options.log(`Daytona surface ${handle.name} cleanup failed: ${messageText(error)}`));
}
},
};
}
+111
View File
@@ -0,0 +1,111 @@
/**
* Tiny eval-only Den proxy.
*
* The desktop app only speaks the den-web topology and derives Den API calls
* with ensureDenApiBasePath (`/api/den/*`). The local eval stack runs the bare
* den-api, so this proxy exposes both bare paths and `/api/den/*` by stripping
* that prefix before streaming requests upstream.
*/
import http from "node:http";
import type { IncomingMessage, ServerResponse } from "node:http";
const listenPort = Number(process.env.DEN_PROXY_LISTEN_PORT);
const upstreamPort = Number(process.env.DEN_PROXY_UPSTREAM_PORT);
const DEN_PREFIX = "/api/den";
const AUTH_DELAY_CONTROL_PATH = "/__openwork_eval/auth-delay";
const authDelayControlEnabled = process.env.OPENWORK_EVAL_DEN_PROXY_CONTROL === "1";
let authDelayEnabled = false;
let authDelayCalls = 0;
const authDelayWaiters = new Set<() => void>();
if (!Number.isInteger(listenPort) || listenPort <= 0) {
console.error("DEN_PROXY_LISTEN_PORT must be a positive integer.");
process.exit(1);
}
if (!Number.isInteger(upstreamPort) || upstreamPort <= 0) {
console.error("DEN_PROXY_UPSTREAM_PORT must be a positive integer.");
process.exit(1);
}
function rewritePath(rawUrl: string | undefined): string {
const url = new URL(rawUrl ?? "/", "http://127.0.0.1");
if (url.pathname === DEN_PREFIX || url.pathname.startsWith(`${DEN_PREFIX}/`)) {
return `${url.pathname.slice(DEN_PREFIX.length) || "/"}${url.search}`;
}
return `${url.pathname}${url.search}`;
}
function writeJson(res: ServerResponse, status: number, body: Record<string, unknown>): void {
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
res.end(`${JSON.stringify(body)}\n`);
}
function releaseAuthDelay(): void {
authDelayEnabled = false;
const waiters = [...authDelayWaiters];
authDelayWaiters.clear();
for (const resolve of waiters) resolve();
}
function isSessionValidation(rawUrl: string | undefined, method: string | undefined): boolean {
if (method !== "GET") return false;
const pathname = new URL(rawUrl ?? "/", "http://127.0.0.1").pathname;
return pathname === "/v1/me" || pathname === `${DEN_PREFIX}/v1/me`;
}
const server = http.createServer(async (req: IncomingMessage, res: ServerResponse) => {
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
if (requestUrl.pathname === AUTH_DELAY_CONTROL_PATH) {
if (!authDelayControlEnabled) {
writeJson(res, 404, { error: "not_found" });
return;
}
if (req.method === "POST" && requestUrl.searchParams.get("action") === "hold") {
authDelayEnabled = true;
authDelayCalls = 0;
} else if (req.method === "POST" && requestUrl.searchParams.get("action") === "release") {
releaseAuthDelay();
} else if (req.method !== "GET") {
writeJson(res, 400, { error: "invalid_action" });
return;
}
writeJson(res, 200, {
enabled: authDelayEnabled,
calls: authDelayCalls,
pending: authDelayWaiters.size,
});
return;
}
if (authDelayControlEnabled && authDelayEnabled && isSessionValidation(req.url, req.method)) {
authDelayCalls += 1;
await new Promise<void>((resolve) => authDelayWaiters.add(resolve));
}
const upstreamReq = http.request({
hostname: "127.0.0.1",
port: upstreamPort,
path: rewritePath(req.url),
method: req.method,
headers: req.headers,
}, (upstreamRes) => {
res.writeHead(upstreamRes.statusCode ?? 502, upstreamRes.headers);
upstreamRes.pipe(res);
});
upstreamReq.on("error", () => {
if (res.headersSent) {
res.destroy();
return;
}
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
res.end("Bad Gateway\n");
});
req.pipe(upstreamReq);
});
server.listen(listenPort, "127.0.0.1", () => {
console.log(`den proxy listening on :${listenPort} -> :${upstreamPort}${authDelayControlEnabled ? " (eval auth delay enabled)" : ""}`);
});
+813
View File
@@ -0,0 +1,813 @@
/**
* Den-stack harness for eval environments.
*
* Brings up everything the cloud eval specs need, idempotently:
* 1. MySQL (docker compose, reuses the dev:den compose project + volume)
* 2. Schema push when the persistent database is behind the current checkout
* 3. den-api behind a local proxy on :8790: bare /* plus den-web
* /api/den/* topology (only when not already healthy)
* 4. Demo-org seed (only when the demo owner cannot sign in)
* 5. Desktop bootstrap pointed at the local Den + dev Electron with CDP
* (only when no CDP endpoint is reachable)
* 6. A demo-owner session token, exported as OPENWORK_EVAL_DEN_API_URL /
* OPENWORK_EVAL_DEN_TOKEN so env-gated specs run without manual setup.
*
* `denStackDown()` stops what the harness started.
*/
import { execFile, spawn } from "node:child_process";
import { openSync } from "node:fs";
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const HOSTS_SRC_DIR = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(HOSTS_SRC_DIR, "..", "..", "..", "..");
const STATE_DIR = resolve(HOSTS_SRC_DIR, "..", "..", "..", "results", ".den-stack");
const EVAL_ELECTRON_USERDATA = process.env.OPENWORK_EVAL_ELECTRON_USERDATA?.trim()
|| join(STATE_DIR, "electron-user-data");
const DEN_API_PORT = Number(process.env.OPENWORK_EVAL_DEN_PORT ?? 8790);
const DEN_API_INTERNAL_PORT = DEN_API_PORT + 1;
const DEN_API_URL = `http://127.0.0.1:${DEN_API_PORT}`;
const DEN_API_INTERNAL_URL = `http://127.0.0.1:${DEN_API_INTERNAL_PORT}`;
const DEN_BASE_URL = `http://localhost:${DEN_API_PORT}`;
const DEMO_EMAIL = process.env.DEN_DEMO_OWNER_EMAIL ?? "alex@acme.test";
const DEMO_PASSWORD = process.env.DEN_DEMO_OWNER_PASSWORD ?? "OpenWorkDemo123!";
const MYSQL_CONTAINER = "openwork-web-local-mysql";
const MYSQL_STATE_DIR = join(STATE_DIR, "mysql");
const MYSQL_SOCKET = join(MYSQL_STATE_DIR, "mysql.sock");
const COMPOSE_ARGS = ["compose", "-p", "openwork-den-local", "-f", "packaging/docker/docker-compose.web-local.yml"];
const DEFAULT_MOCK_IDP_ISSUER = "http://127.0.0.1:19190";
const MOCK_IDP_ISSUER = (process.env.OPENWORK_EVAL_MOCK_IDP_ISSUER?.trim() || DEFAULT_MOCK_IDP_ISSUER).replace(/\/+$/, "");
const DEN_WEB_ORIGIN = (process.env.OPENWORK_EVAL_DEN_WEB_URL ?? "http://localhost:3005").replace(/\/+$/, "");
const DEN_WEB_PORT = new URL(DEN_WEB_ORIGIN).port || "3005";
function envCsv(name: string): string[] {
return (process.env[name] ?? "")
.split(",")
.map((entry) => entry.trim().replace(/\/+$/, ""))
.filter(Boolean);
}
const DEN_TRUSTED_ORIGINS = [
DEN_BASE_URL,
DEN_WEB_ORIGIN,
`http://localhost:${DEN_WEB_PORT}`,
`http://127.0.0.1:${DEN_WEB_PORT}`,
"http://localhost:5173",
"http://127.0.0.1:5173",
MOCK_IDP_ISSUER,
...envCsv("OPENWORK_EVAL_DEN_EXTRA_TRUSTED_ORIGINS"),
].filter((origin, index, origins) => origins.indexOf(origin) === index).join(",");
// Override with OPENWORK_EVAL_DATABASE_URL to isolate a run from the shared
// dev database (e.g. a dedicated schema on the same MySQL container).
const DEN_DATABASE_URL = process.env.OPENWORK_EVAL_DATABASE_URL ?? "mysql://root:password@127.0.0.1:3306/openwork_den";
const DEN_DATABASE_NAME = new URL(DEN_DATABASE_URL).pathname.replace(/^\//, "") || "openwork_den";
if (!/^[A-Za-z0-9_]+$/.test(DEN_DATABASE_NAME)) {
throw new Error(`Unsupported Den database name: ${DEN_DATABASE_NAME}`);
}
type DenOrgMode = "single_org" | "multi_org";
interface DenEvalEnvironmentOptions {
orgMode?: DenOrgMode;
}
export function denEvalEnvironment(options: DenEvalEnvironmentOptions = {}): NodeJS.ProcessEnv {
const env: NodeJS.ProcessEnv = {
OPENWORK_DEV_MODE: "1",
DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP: "true",
PORT: String(DEN_API_INTERNAL_PORT),
DATABASE_URL: DEN_DATABASE_URL,
DEN_DB_ENCRYPTION_KEY: "local-dev-db-encryption-key-please-change-1234567890",
BETTER_AUTH_SECRET: "local-dev-secret-not-for-production-use!!",
BETTER_AUTH_URL: DEN_WEB_ORIGIN,
DEN_API_PUBLIC_URL: DEN_API_URL,
DEN_BETTER_AUTH_TRUSTED_ORIGINS: DEN_TRUSTED_ORIGINS,
CORS_ORIGINS: DEN_TRUSTED_ORIGINS,
PROVISIONER_MODE: "stub",
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY ?? "sk_test_openwork_eval",
STRIPE_INFERENCE_PRICE_ID: process.env.STRIPE_INFERENCE_PRICE_ID ?? "price_openwork_models_eval",
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET ?? "whsec_openwork_eval",
INFERENCE_PROXY_BASE_URL: process.env.INFERENCE_PROXY_BASE_URL ?? "http://127.0.0.1:8791",
};
if (options.orgMode) env.DEN_ORG_MODE = options.orgMode;
return env;
}
const DEN_ENV = denEvalEnvironment();
const sleep = (ms: number) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function errorCode(error: unknown): string | null {
return isRecord(error) && typeof error.code === "string" ? error.code : null;
}
function isDenOrgMode(value: unknown): value is DenOrgMode {
return value === "single_org" || value === "multi_org";
}
function devUserDataHome(): string {
return EVAL_ELECTRON_USERDATA;
}
function devBootstrapPath(): string {
return join(devUserDataHome(), "openwork-dev-data", "home", ".config", "openwork", "desktop-bootstrap.json");
}
async function httpOk(url: string, timeoutMs = 2_500): Promise<boolean> {
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timer);
return response.ok;
} catch {
return false;
}
}
async function runtimeOrgMode(timeoutMs = 2_500): Promise<DenOrgMode | null> {
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
const response = await fetch(`${DEN_WEB_ORIGIN}/api/runtime-config`, { signal: controller.signal });
clearTimeout(timer);
if (!response.ok) return null;
const payload: unknown = await response.json();
return isRecord(payload) && isDenOrgMode(payload.orgMode) ? payload.orgMode : null;
} catch {
return null;
}
}
function pidIsAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch (error) {
return errorCode(error) !== "ESRCH";
}
}
async function waitForPidGone(pid: number, timeoutMs: number): Promise<boolean> {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
if (!pidIsAlive(pid)) return true;
await sleep(250);
}
return !pidIsAlive(pid);
}
async function stopRecordedProcess(name: string, label: string, log: (message: string) => void): Promise<void> {
const pidText = await readPidState(name);
if (!pidText) return;
const pid = Number(pidText);
if (!Number.isInteger(pid) || pid <= 0) return;
let signaled = false;
try {
process.kill(-pid, "SIGINT");
signaled = true;
} catch {
// Group may not exist, or this may be Windows.
}
try {
process.kill(pid, "SIGINT");
signaled = true;
} catch {
// Already gone.
}
if (signaled) log(`Stopped ${label} for orgMode restart (pid ${pid})`);
if (await waitForPidGone(pid, 5_000)) return;
try {
process.kill(-pid, "SIGKILL");
} catch {
// Group may already be gone.
}
try {
process.kill(pid, "SIGKILL");
} catch {
// Already gone.
}
await waitForPidGone(pid, 1_000);
}
async function hasCdpPageTarget(baseUrl: string): Promise<boolean> {
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 2_500);
const response = await fetch(`${baseUrl}/json/list`, { signal: controller.signal });
clearTimeout(timer);
if (!response.ok) return false;
const targets: unknown = await response.json();
return Array.isArray(targets)
&& targets.some((target) => isRecord(target) && target.type === "page" && typeof target.webSocketDebuggerUrl === "string");
} catch {
return false;
}
}
async function signInDemoOwner(): Promise<string | null> {
try {
const response = await fetch(`${DEN_API_URL}/api/auth/sign-in/email`, {
method: "POST",
headers: { "content-type": "application/json", origin: DEN_BASE_URL },
body: JSON.stringify({ email: DEMO_EMAIL, password: DEMO_PASSWORD }),
});
if (!response.ok) return null;
const payload: unknown = await response.json();
return isRecord(payload) && typeof payload.token === "string" && payload.token ? payload.token : null;
} catch {
return null;
}
}
interface RunOptions {
cwd?: string;
env?: NodeJS.ProcessEnv;
maxBuffer?: number;
}
interface RunResult {
stdout: string;
stderr: string;
}
async function run(command: string, args: string[], options: RunOptions = {}): Promise<RunResult> {
return new Promise((resolveRun, reject) => {
execFile(command, args, { cwd: REPO_ROOT, maxBuffer: 16 * 1024 * 1024, encoding: "utf8", ...options }, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
resolveRun({
stdout,
stderr,
});
});
});
}
interface SpawnDetachedOptions {
logName: string;
env: NodeJS.ProcessEnv;
cwd?: string;
}
function spawnDetached(command: string, args: string[], { logName, env, cwd }: SpawnDetachedOptions): number {
// Redirect stdio to a log file — inheriting it would keep the parent's
// pipes open forever and hang any shell pipeline wrapping the runner.
const logFd = openSync(join(STATE_DIR, `${logName}.log`), "a");
const child = spawn(command, args, {
cwd: cwd ?? REPO_ROOT,
env: { ...process.env, ...env },
detached: true,
stdio: ["ignore", logFd, logFd],
});
child.unref();
if (!child.pid) throw new Error(`Could not spawn ${command}.`);
return child.pid;
}
async function writePidState(name: string, value: unknown): Promise<void> {
await mkdir(STATE_DIR, { recursive: true });
await writeFile(join(STATE_DIR, name), String(value));
}
async function readPidState(name: string): Promise<string | null> {
try {
return (await readFile(join(STATE_DIR, name), "utf8")).trim();
} catch {
return null;
}
}
async function resolveExecutable(names: string[]): Promise<string | null> {
for (const name of names) {
try {
const { stdout } = await run("which", [name]);
const executable = stdout.trim();
if (executable) return executable;
} catch {
// Try the next compatible binary name.
}
}
return null;
}
function mysqlConnectionArgs(includeDatabase = true): string[] {
const url = new URL(DEN_DATABASE_URL);
const args = [
"--protocol=tcp",
"-h",
url.hostname,
"-P",
url.port || "3306",
`-u${decodeURIComponent(url.username || "root")}`,
];
const password = decodeURIComponent(url.password);
if (password) args.push(`-p${password}`);
if (includeDatabase) args.push(DEN_DATABASE_NAME);
return args;
}
async function nativeMysqlQuery(sql: string, includeDatabase = true): Promise<string> {
const client = await resolveExecutable(["mariadb", "mysql"]);
if (!client) throw new Error("Neither mariadb nor mysql client is available.");
const { stdout } = await run(client, [
...mysqlConnectionArgs(includeDatabase),
"-N",
"-e",
sql,
]);
return stdout.trim();
}
async function nativeMysqlHealthy(): Promise<boolean> {
try {
await nativeMysqlQuery("SELECT 1");
return true;
} catch {
return false;
}
}
export function nativeMysqlServerArgs(
runAsRoot = process.getuid?.() === 0,
): string[] {
const args = [
`--datadir=${MYSQL_STATE_DIR}`,
`--socket=${MYSQL_SOCKET}`,
"--port=3306",
"--bind-address=127.0.0.1",
"--skip-networking=0",
`--pid-file=${join(MYSQL_STATE_DIR, "mysql.pid")}`,
`--log-error=${join(MYSQL_STATE_DIR, "mysql.error.log")}`,
];
if (runAsRoot) args.push("--user=root");
return args;
}
export function nativeMysqlSocketArgs(
authenticated: boolean,
): string[] {
const url = new URL(DEN_DATABASE_URL);
const args = [
"--protocol=socket",
`--socket=${MYSQL_SOCKET}`,
`-u${decodeURIComponent(url.username || "root")}`,
];
const password = decodeURIComponent(url.password);
if (authenticated && password) args.push(`-p${password}`);
return args;
}
async function ensureNativeMysql(log: (message: string) => void): Promise<void> {
if (await nativeMysqlHealthy()) {
await writePidState("mysql.backend", "native");
log("Native MySQL already healthy");
return;
}
const server = await resolveExecutable(["mariadbd", "mysqld"]);
const installer = await resolveExecutable(["mariadb-install-db", "mysql_install_db"]);
const socketClient = await resolveExecutable(["mariadb", "mysql"]);
if (!server || !installer || !socketClient) {
throw new Error(
"Docker is unavailable and the native MySQL server/client toolchain is incomplete.",
);
}
await mkdir(MYSQL_STATE_DIR, { recursive: true });
try {
await access(join(MYSQL_STATE_DIR, "mysql"));
} catch {
log("Initializing native MariaDB data directory...");
await run(installer, [
`--datadir=${MYSQL_STATE_DIR}`,
"--auth-root-authentication-method=normal",
"--skip-test-db",
]);
}
log("Starting native MariaDB...");
const pid = spawnDetached(
server,
nativeMysqlServerArgs(),
{ logName: "mysql", env: {} },
);
await writePidState("mysql.pid", pid);
await writePidState("mysql.backend", "native");
let socketArgs: string[] | null = null;
for (let attempt = 0; attempt < 60; attempt += 1) {
for (const authenticated of [false, true]) {
const candidateArgs = nativeMysqlSocketArgs(authenticated);
try {
await run(socketClient, [
...candidateArgs,
"-N",
"-e",
"SELECT 1",
]);
socketArgs = candidateArgs;
break;
} catch {
// A fresh data directory accepts passwordless root while a resumed
// directory requires the password configured below.
}
}
if (socketArgs) break;
await sleep(500);
}
if (!socketArgs) {
throw new Error("Native MariaDB did not become ready within 30s.");
}
await run(socketClient, [
...socketArgs,
"-e",
[
"ALTER USER 'root'@'localhost' IDENTIFIED BY 'password'",
"CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY 'password'",
"GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION",
`CREATE DATABASE IF NOT EXISTS \`${DEN_DATABASE_NAME}\``,
"FLUSH PRIVILEGES",
].join("; "),
]);
for (let attempt = 0; attempt < 30; attempt += 1) {
if (await nativeMysqlHealthy()) {
log("Native MariaDB healthy");
return;
}
await sleep(500);
}
throw new Error("Native MariaDB did not accept Den database connections within 15s.");
}
async function ensureMysql(log: (message: string) => void): Promise<void> {
if (process.env.OPENWORK_EVAL_DATABASE_URL) {
if (!(await nativeMysqlHealthy())) {
throw new Error("OPENWORK_EVAL_DATABASE_URL is configured but not reachable.");
}
await writePidState("mysql.backend", "external");
log("External MySQL already healthy");
return;
}
const docker = await resolveExecutable(["docker"]);
if (!docker) {
await ensureNativeMysql(log);
return;
}
try {
const { stdout } = await run(docker, ["inspect", "-f", "{{.State.Health.Status}}", MYSQL_CONTAINER]);
if (stdout.trim() === "healthy") {
await writePidState("mysql.backend", "docker");
log("MySQL already healthy");
return;
}
} catch {
// Not running — start it below.
}
log("Starting MySQL (docker compose)...");
await run(docker, [...COMPOSE_ARGS, "up", "-d", "--wait", "mysql"]);
await writePidState("mysql.backend", "docker");
await writePidState("mysql.started", "1");
log("MySQL healthy");
}
async function mysqlQuery(sql: string): Promise<string> {
const backend = await readPidState("mysql.backend");
if (backend === "native" || backend === "external") {
return nativeMysqlQuery(sql);
}
const docker = await resolveExecutable(["docker"]);
if (!docker) throw new Error("The selected Docker MySQL backend is unavailable.");
const { stdout } = await run(docker, [
"exec", MYSQL_CONTAINER,
"mysql", "-uroot", "-ppassword", DEN_DATABASE_NAME, "-N", "-e", sql,
]);
return stdout.trim();
}
async function ensureSchema(log: (message: string) => void): Promise<void> {
try {
const schema = await mysqlQuery("SHOW TABLES LIKE 'organization'; SHOW TABLES LIKE 'desktop_connect_grant'; SHOW TABLES LIKE 'scim_group'; SHOW COLUMNS FROM scim_provider LIKE 'group_mapping_mode';");
if (
schema.includes("organization")
&& schema.includes("desktop_connect_grant")
&& schema.includes("scim_group")
&& schema.includes("group_mapping_mode")
) {
log("Schema present");
return;
}
} catch {
// Database may not exist yet — push will create what it needs.
}
log("Synchronizing schema with the current checkout...");
const denDbDir = join(REPO_ROOT, "ee", "packages", "den-db");
await run("pnpm", ["--filter", "@openwork-ee/den-db", "build"]);
await run("node", ["--import", "tsx", "./node_modules/drizzle-kit/bin.cjs", "push", "--config", "drizzle.config.ts"], {
cwd: denDbDir,
env: { ...process.env, DATABASE_URL: DEN_ENV.DATABASE_URL, DEN_DB_ENCRYPTION_KEY: DEN_ENV.DEN_DB_ENCRYPTION_KEY },
});
log("Schema pushed");
}
async function ensureDenApi(log: (message: string) => void, orgMode?: DenOrgMode): Promise<void> {
const publicHealthOk = await httpOk(`${DEN_API_URL}/health`);
const denWebPathHealthOk = await httpOk(`${DEN_API_URL}/api/den/health`);
if (publicHealthOk && denWebPathHealthOk) {
log("den stack already healthy (with /api/den path)");
return;
}
if (publicHealthOk) {
throw new Error(
`A den-api is already healthy on :${DEN_API_PORT}, but it does not serve /api/den. ` +
"The desktop app cannot use a bare den-api there; rerun with OPENWORK_EVAL_DEN_PORT=<free port>.",
);
}
log(`Starting den-api on internal :${DEN_API_INTERNAL_PORT} (proxied on :${DEN_API_PORT})...`);
const pid = spawnDetached("pnpm", ["exec", "tsx", "src/main.ts"], {
logName: "den-api",
cwd: join(REPO_ROOT, "ee", "apps", "den-api"),
env: denEvalEnvironment({ orgMode }),
});
await writePidState("den-api.pid", pid);
for (let attempt = 0; attempt < 30; attempt += 1) {
if (await httpOk(`${DEN_API_INTERNAL_URL}/health`)) {
log("den-api healthy");
break;
}
await sleep(2_000);
}
if (!(await httpOk(`${DEN_API_INTERNAL_URL}/health`))) {
throw new Error(`den-api did not become healthy on internal :${DEN_API_INTERNAL_PORT} within 60s.`);
}
if (await httpOk(`${DEN_API_URL}/api/den/health`)) {
log("den proxy healthy");
return;
}
log(`Starting den proxy on :${DEN_API_PORT} -> :${DEN_API_INTERNAL_PORT}...`);
const proxyPid = spawnDetached(process.execPath, [join(HOSTS_SRC_DIR, "den-proxy.ts")], {
logName: "den-proxy",
env: {
DEN_PROXY_LISTEN_PORT: String(DEN_API_PORT),
DEN_PROXY_UPSTREAM_PORT: String(DEN_API_INTERNAL_PORT),
OPENWORK_EVAL_DEN_PROXY_CONTROL: "1",
},
});
await writePidState("den-proxy.pid", proxyPid);
for (let attempt = 0; attempt < 30; attempt += 1) {
if (await httpOk(`${DEN_API_URL}/api/den/health`)) {
log("den proxy healthy");
return;
}
await sleep(1_000);
}
throw new Error("den proxy did not expose /api/den/health within 30s.");
}
export async function clearDenWebBuildCache(
denWebRoot = join(REPO_ROOT, "ee", "apps", "den-web"),
): Promise<void> {
await rm(join(denWebRoot, ".next"), { recursive: true, force: true });
}
export async function ensureDenOrgMode(orgMode: DenOrgMode | undefined, log: (message: string) => void): Promise<void> {
if (!orgMode) return;
const actual = await runtimeOrgMode();
if (actual === orgMode) {
log(`Den orgMode already ${orgMode}`);
return;
}
if (!actual && !(await httpOk(`${DEN_API_URL}/health`)) && !(await httpOk(`${DEN_WEB_ORIGIN}/api/den/health`))) {
return;
}
const detail = actual ? `reports ${actual}` : "could not be verified from runtime-config";
log(`Den orgMode ${detail}; restarting den-api and den-web for ${orgMode}.`);
await stopRecordedProcess("den-api.pid", "den-api", log);
await stopRecordedProcess("den-web.pid", "den-web", log);
await freePorts([DEN_API_PORT, DEN_API_INTERNAL_PORT, Number(DEN_WEB_PORT)].filter((port) => Number.isInteger(port) && port > 0), log, "Den orgMode restart");
await clearDenWebBuildCache();
}
async function ensureDenWeb(log: (message: string) => void, orgMode?: DenOrgMode): Promise<void> {
if (await httpOk(`${DEN_WEB_ORIGIN}/api/den/health`)) {
log(`den-web already healthy at ${DEN_WEB_ORIGIN}`);
return;
}
const webUrl = new URL(DEN_WEB_ORIGIN);
const denWebPort = webUrl.port || "3005";
// Reusable Daytona images may retain a generated Next.js route manifest
// from an older checkout. Dependencies remain cached, but generated app
// output must match the exact candidate being proved.
await clearDenWebBuildCache();
log(`Starting den-web on :${denWebPort}...`);
const pid = spawnDetached("pnpm", ["dev:den:web"], {
logName: "den-web",
env: {
DEN_API_BASE: DEN_API_URL,
DEN_AUTH_ORIGIN: DEN_WEB_ORIGIN,
DEN_AUTH_FALLBACK_BASE: DEN_API_URL,
DEN_WEB_PORT: denWebPort,
...(orgMode ? { DEN_ORG_MODE: orgMode } : {}),
},
});
await writePidState("den-web.pid", pid);
// A clean Daytona checkout can spend more than 90 seconds compiling the
// first den-web route even after the process has started successfully.
for (let attempt = 0; attempt < 90; attempt += 1) {
if (await httpOk(`${DEN_WEB_ORIGIN}/api/den/health`)) {
log("den-web healthy");
return;
}
await sleep(2_000);
}
throw new Error(`den-web did not become healthy at ${DEN_WEB_ORIGIN} within 180s.`);
}
export function denSeedNodeArgs(): string[] {
return [
"--conditions=development",
"--import",
"tsx",
"scripts/seed-demo-org.ts",
];
}
async function ensureSeed(log: (message: string) => void): Promise<void> {
if (await signInDemoOwner()) {
log(`Demo org present (${DEMO_EMAIL})`);
return;
}
log("Seeding demo org (Acme Robotics)...");
await run(process.execPath, denSeedNodeArgs(), {
cwd: join(REPO_ROOT, "ee", "apps", "den-api"),
env: { ...process.env, ...DEN_ENV },
});
if (!(await signInDemoOwner())) {
throw new Error("Seed completed but the demo owner still cannot sign in.");
}
log("Demo org seeded");
}
async function freePorts(ports: number[], log: (message: string) => void, reason: string): Promise<void> {
for (const port of ports) {
try {
const { stdout } = await run("lsof", ["-nP", "-ti", `tcp:${port}`]);
const pids = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
for (const pid of pids) {
try {
process.kill(Number(pid), "SIGKILL");
log(`Cleared process ${pid} holding :${port} (${reason})`);
} catch {
// Already gone.
}
}
} catch {
// Port free — nothing to do.
}
}
await sleep(1_500);
}
async function freeStaleAppPorts(log: (message: string) => void): Promise<void> {
// If no app page target is serving but the dev ports are held, a previous
// run left a half-dead app behind (e.g. Electron without its renderer).
// Clear them so the fresh spawn does not lose the bind race.
await freePorts([9823, 5173], log, "stale app cleanup");
}
async function ensureApp(log: (message: string) => void, cdpCandidates: string[]): Promise<void> {
for (const candidate of cdpCandidates) {
if (await hasCdpPageTarget(candidate)) {
log(`App CDP already reachable at ${candidate} — make sure it targets the local Den (reload after bootstrap changes).`);
return;
}
}
await freeStaleAppPorts(log);
const bootstrapPath = devBootstrapPath();
await mkdir(dirname(bootstrapPath), { recursive: true });
await writeFile(
bootstrapPath,
`${JSON.stringify({ baseUrl: DEN_BASE_URL, requireSignin: false }, null, 2)}\n`,
);
await writePidState("bootstrap.path", bootstrapPath);
log(`Wrote desktop bootstrap -> ${DEN_BASE_URL}`);
log("Starting dev Electron (pnpm dev)...");
const pid = spawnDetached("pnpm", ["dev"], {
logName: "app",
env: { OPENWORK_ELECTRON_USERDATA: EVAL_ELECTRON_USERDATA },
});
await writePidState("app.pid", pid);
for (let attempt = 0; attempt < 45; attempt += 1) {
for (const candidate of cdpCandidates) {
if (await hasCdpPageTarget(candidate)) {
log(`App CDP up at ${candidate}`);
// Give the renderer a moment to finish booting providers.
await sleep(8_000);
return;
}
}
await sleep(4_000);
}
throw new Error("Dev Electron CDP page target did not come up within 3 minutes.");
}
export interface EnsureDenStackOptions {
log(message: string): void;
cdpCandidates: string[];
skipApp?: boolean;
orgMode?: DenOrgMode;
}
export async function ensureDenStack({ log, cdpCandidates, skipApp = false, orgMode }: EnsureDenStackOptions): Promise<void> {
await mkdir(STATE_DIR, { recursive: true });
await ensureMysql(log);
await ensureSchema(log);
await ensureDenOrgMode(orgMode, log);
await ensureDenApi(log, orgMode);
await ensureDenWeb(log, orgMode);
await ensureSeed(log);
if (skipApp) {
log("Skipping dev Electron startup — selected eval flow is app-less");
} else {
await ensureApp(log, cdpCandidates);
}
const token = await signInDemoOwner();
if (!token) throw new Error("Could not obtain a demo-owner session token.");
process.env.OPENWORK_EVAL_DEN_API_URL = DEN_API_URL;
process.env.OPENWORK_EVAL_DEN_WEB_URL = DEN_WEB_ORIGIN;
process.env.OPENWORK_EVAL_DEN_TOKEN = token;
log(`Den stack ready — flows get OPENWORK_EVAL_DEN_API_URL=${DEN_API_URL}, OPENWORK_EVAL_DEN_WEB_URL=${DEN_WEB_ORIGIN}, and a fresh ${DEMO_EMAIL} token.`);
}
export interface DenStackDownOptions {
log(message: string): void;
}
export async function denStackDown({ log }: DenStackDownOptions): Promise<void> {
const proxyPid = await readPidState("den-proxy.pid");
if (proxyPid) {
try { process.kill(Number(proxyPid)); log(`Stopped den-proxy (pid ${proxyPid})`); } catch { /* already gone */ }
}
const apiPid = await readPidState("den-api.pid");
if (apiPid) {
try { process.kill(Number(apiPid)); log(`Stopped den-api (pid ${apiPid})`); } catch { /* already gone */ }
}
const webPid = await readPidState("den-web.pid");
if (webPid) {
try { process.kill(-Number(webPid)); } catch { /* group gone */ }
try { process.kill(Number(webPid)); log(`Stopped den-web (pid ${webPid})`); } catch { /* already gone */ }
}
const appPid = await readPidState("app.pid");
if (appPid) {
try { process.kill(-Number(appPid)); } catch { /* group gone */ }
try { process.kill(Number(appPid)); log(`Stopped dev app (pid ${appPid})`); } catch { /* already gone */ }
}
const bootstrapPath = await readPidState("bootstrap.path");
if (bootstrapPath) {
await rm(bootstrapPath, { force: true });
log("Removed dev desktop bootstrap override");
}
const mysqlBackend = await readPidState("mysql.backend");
const mysqlPid = await readPidState("mysql.pid");
if (mysqlBackend === "native" && mysqlPid) {
try {
process.kill(Number(mysqlPid));
log(`Stopped native MariaDB (pid ${mysqlPid})`);
} catch {
// Already gone.
}
} else if (mysqlBackend === "docker") {
const docker = await resolveExecutable(["docker"]);
if (docker) {
try {
await run(docker, [...COMPOSE_ARGS, "down"]);
log("MySQL compose project stopped (volume kept)");
} catch {
log("Docker compose down skipped (docker unavailable?)");
}
}
}
await rm(STATE_DIR, { recursive: true, force: true });
}
+169
View File
@@ -0,0 +1,169 @@
import { readFile } from "node:fs/promises";
import { timed } from "@openwork/timeline";
import { attachSurface, describeAppState, dumpScreenState, isInteractive, probeAppStateOnSurface } from "@openwork/cdp";
import { resolveHost } from "./resolve.ts";
import type { AppStateProbe, AppSurfaceState, AttachedSurface, Surface, SurfaceHandle } from "@openwork/cdp";
import type { Host } from "./types.ts";
const DEFAULT_TIMEOUT_MS = 180_000;
const POLL_INTERVAL_MS = 250;
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
function messageText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function logCleanupError(name: string, error: unknown): void {
console.warn(`[openwork/evals] Desktop ${name} cleanup failed: ${messageText(error)}`);
}
async function appendDesktopLog(error: unknown, handle: SurfaceHandle): Promise<unknown> {
const logPath = handle.meta?.log;
if (!logPath) return error;
try {
const log = await readFile(logPath, "utf8");
if (!log.trim()) return error;
const tail = log.trimEnd().split(/\r?\n/).slice(-40).join("\n");
return new Error(`${messageText(error)}\n\nLast 40 lines of ${logPath}:\n${tail}`, { cause: error });
} catch {
return error;
}
}
export interface DesktopOptions {
name?: string;
mode?: "spawn" | "attach";
/** Explicit CDP endpoint for attach mode; falls back to OPENWORK_EVAL_CDP_URL. */
cdpUrl?: string;
/**
* Where this desktop runs. Defaults to the ambient host (`resolveHost()`).
* Pass one from `localHost()` / `daytonaSandbox(id)` to place it explicitly —
* that is the only way a spec can put two desktops in two sandboxes, or the
* app and the browser in different places.
*/
host?: Host;
bootstrap?: {
baseUrl: string;
apiBaseUrl?: string;
requireSignin?: boolean;
};
env?: Record<string, string>;
/** Root package script used for a source Electron launch. */
devCommand?: "dev" | "dev:electron";
/** Skip host-side sidecar/helper preparation for an explicitly constrained launch. */
prepareSharedResources?: boolean;
/** Exact caller-owned Electron profile root, for restart scenarios. */
profileDir?: string;
timeoutMs?: number;
}
export interface AppReadiness {
state: AppSurfaceState;
workspaceId: string | null;
route: string;
}
export interface DesktopHandle extends AttachedSurface {
readiness: AppReadiness;
/**
* The workspace root on the host running THIS app — use it for workspace
* paths instead of `process.cwd()`, which is the driver's filesystem and may
* not exist where the app runs. Null only in `mode: "attach"`, where the
* host is unknown.
*/
workspaceRoot: string | null;
stop(): Promise<void>;
}
async function waitForReadiness(app: Surface, timeoutMs: number): Promise<AppReadiness> {
const deadline = Date.now() + timeoutMs;
// Short per-probe timeout so a briefly-busy renderer is retried rather than
// consuming the whole readiness budget in one stuck call.
let last: AppStateProbe = { controlReady: false, transitional: null, surface: null, workspaceId: null, route: "", text: "" };
while (Date.now() < deadline) {
try {
last = await probeAppStateOnSurface(app, { timeoutMs: Math.min(8_000, Math.max(1, deadline - Date.now())) });
if (isInteractive(last) && last.surface) {
return { state: last.surface, workspaceId: last.workspaceId, route: last.route };
}
} catch {
// Navigations briefly destroy the execution context while the app boots.
}
await sleep(Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
}
throw new Error(
`OpenWork desktop did not become ready after ${timeoutMs}ms: ${describeAppState(last)} On screen: ${await dumpScreenState(app)}.`,
);
}
async function closeSpawnedSurface(
attached: AttachedSurface | null,
host: Host | null,
handle: SurfaceHandle,
): Promise<void> {
try {
await attached?.stop();
} finally {
await host?.disposeSurface(handle);
}
}
export async function desktop(opts: DesktopOptions = {}): Promise<DesktopHandle> {
const mode = opts.mode ?? "spawn";
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
let host: Host | null = null;
let handle: SurfaceHandle;
if (mode === "attach") {
const cdpUrl = opts.cdpUrl?.trim() || process.env.OPENWORK_EVAL_CDP_URL?.trim();
if (!cdpUrl) {
throw new Error('desktop({ mode: "attach" }) requires cdpUrl or OPENWORK_EVAL_CDP_URL to point at a running Electron app.');
}
handle = {
name: opts.name ?? "attached-app",
kind: "electron",
hostKind: "attached",
cdpUrl,
};
} else {
host = opts.host ?? await resolveHost();
handle = await host.spawnElectron(opts.name ?? "spec", {
profile: "fresh",
profileDir: opts.profileDir,
bootstrap: opts.bootstrap,
env: opts.env,
devCommand: opts.devCommand,
prepareSharedResources: opts.prepareSharedResources,
});
}
let attached: AttachedSurface | null = null;
try {
const surface = await attachSurface(handle, { timeoutMs });
attached = surface;
const readiness = await timed("app.readiness", () => waitForReadiness(surface, timeoutMs), handle.name);
let stopped = false;
const stop = async (): Promise<void> => {
if (stopped) return;
stopped = true;
await closeSpawnedSurface(attached, host, handle);
};
const dispose = async (): Promise<void> => {
await stop().catch((error: unknown) => logCleanupError(handle.name, error));
};
return {
handle: attached.handle,
client: attached.client,
readiness,
workspaceRoot: host?.workspaceRoot ?? null,
stop,
[Symbol.asyncDispose]: dispose,
};
} catch (error) {
const readinessError = attached ? await appendDesktopLog(error, handle) : error;
await closeSpawnedSurface(attached, host, handle)
.catch((cleanupError: unknown) => logCleanupError(handle.name, cleanupError));
throw readinessError;
}
}
@@ -0,0 +1,187 @@
export const FAULT_PROXY_SCRIPT = String.raw`import { createServer, request as httpRequest } from "node:http";
import { request as httpsRequest } from "node:https";
import { setTimeout as delay } from "node:timers/promises";
const port = Number(process.env.PORT);
const upstream = new URL(process.env.UPSTREAM);
const issuer = process.env.ISSUER;
const controlToken = process.env.CONTROL_TOKEN;
const rules = [];
const requests = [];
const hopByHopHeaders = new Set([
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"proxy-connection",
"te",
"trailer",
"transfer-encoding",
"upgrade",
]);
function headerText(value) {
return Array.isArray(value) ? value.join(",") : value ?? "";
}
function forwardedHeaders(source, host) {
const nominated = new Set(headerText(source.connection).split(",").map((name) => name.trim().toLowerCase()).filter(Boolean));
const headers = {};
for (const [name, value] of Object.entries(source)) {
if (value === undefined || hopByHopHeaders.has(name) || nominated.has(name)) continue;
headers[name] = value;
}
if (host) headers.host = host;
return headers;
}
function faultTimes(value) {
if (value === undefined) return 1;
if (!Number.isInteger(value) || value < 0) throw new Error("Fault times must be a non-negative integer, got " + value + ".");
return value;
}
function takeRule(path) {
for (const rule of rules) {
if (rule.remaining <= 0 || !path.startsWith(rule.pathPrefix)) continue;
rule.remaining -= 1;
return rule;
}
return null;
}
function json(response, status, body, headers = {}) {
const text = JSON.stringify(body);
response.writeHead(status, { "content-type": "application/json", ...headers });
response.end(text);
}
function readJson(request) {
return new Promise((resolve, reject) => {
let body = "";
request.setEncoding("utf8");
request.on("data", (chunk) => body += chunk);
request.on("end", () => {
try {
resolve(JSON.parse(body));
} catch (error) {
reject(error);
}
});
request.on("error", reject);
});
}
function writeUpstreamResponse(client, status, message, headers) {
if (message) client.writeHead(status, message, headers);
else client.writeHead(status, headers);
}
function forward(incoming, client, faulted) {
const path = incoming.url ?? "/";
const onResponse = (response) => {
const status = response.statusCode ?? 502;
requests.push({ method: incoming.method ?? "GET", path, status, faulted, at: Date.now() });
writeUpstreamResponse(client, status, response.statusMessage, forwardedHeaders(response.headers));
response.pipe(client);
};
// Pin the upstream origin and take only the path from the caller. An
// absolute-form request target (legal for proxy requests) would otherwise
// steer this fetch at an arbitrary host, because new URL(absolute, base)
// discards the base.
const requested = new URL(path, "http://request-target.invalid");
const options = {
protocol: upstream.protocol,
hostname: upstream.hostname,
port: upstream.port,
path: requested.pathname + requested.search,
method: incoming.method ?? "GET",
headers: forwardedHeaders(incoming.headers, upstream.host),
};
const outbound = upstream.protocol === "https:"
? httpsRequest(options, onResponse)
: httpRequest(options, onResponse);
outbound.on("error", (error) => {
if (client.headersSent) {
client.destroy(error);
return;
}
requests.push({ method: incoming.method ?? "GET", path, status: 502, faulted, at: Date.now() });
json(client, 502, { error: error.message });
});
incoming.on("aborted", () => outbound.destroy());
incoming.pipe(outbound);
}
async function control(incoming, response, path) {
if (path === "/__openwork_faults/health" && incoming.method === "GET") {
json(response, 200, { ok: true, issuer });
return;
}
if (incoming.headers["x-openwork-fault-token"] !== controlToken) {
json(response, 401, { error: "Unauthorized" });
return;
}
if (path === "/__openwork_faults/requests" && incoming.method === "GET") {
json(response, 200, { requests });
return;
}
if (path === "/__openwork_faults/clear" && incoming.method === "POST") {
rules.length = 0;
response.writeHead(204);
response.end();
return;
}
if (path === "/__openwork_faults/rules" && incoming.method === "POST") {
try {
const body = await readJson(incoming);
const remaining = faultTimes(body.times);
if (body.kind === "status") {
rules.push({ kind: "status", pathPrefix: body.pathPrefix, statusCode: body.statusCode, body: body.body, remaining });
} else if (body.kind === "latency") {
rules.push({ kind: "latency", pathPrefix: body.pathPrefix, delayMs: body.delayMs, remaining });
} else {
throw new Error("Unknown fault kind.");
}
response.writeHead(204);
response.end();
} catch (error) {
json(response, 400, { error: error instanceof Error ? error.message : String(error) });
}
return;
}
json(response, 404, { error: "Not found" });
}
const server = createServer((incoming, response) => {
void (async () => {
const path = incoming.url ?? "/";
if (path.startsWith("/__openwork_faults")) {
await control(incoming, response, path);
return;
}
const rule = takeRule(path);
if (rule?.kind === "status") {
const body = JSON.stringify(rule.body ?? { error: "Injected HTTP " + rule.statusCode });
requests.push({ method: incoming.method ?? "GET", path, status: rule.statusCode, faulted: true, at: Date.now() });
response.writeHead(rule.statusCode, {
"access-control-allow-origin": "*",
"content-length": Buffer.byteLength(body),
"content-type": "application/json",
});
response.end(body);
return;
}
if (rule?.kind === "latency") await delay(rule.delayMs);
forward(incoming, response, rule !== null);
})().catch((error) => {
if (response.headersSent) {
response.destroy(error instanceof Error ? error : undefined);
return;
}
json(response, 500, { error: error instanceof Error ? error.message : String(error) });
});
});
server.listen(port, "0.0.0.0");
`;
+8
View File
@@ -0,0 +1,8 @@
export * from "./browser.ts";
export * from "./daytona.ts";
export * from "./den-stack.ts";
export * from "./desktop.ts";
export * from "./local.ts";
export * from "./provision.ts";
export * from "./resolve.ts";
export * from "./types.ts";
+963
View File
@@ -0,0 +1,963 @@
import { execFile, spawn } from "node:child_process";
import { constants, existsSync, openSync } from "node:fs";
import { access, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { delimiter, dirname, join } from "node:path";
import { allocateFreePort, allocateFreePorts, listTargets, waitForCdp } from "@openwork/cdp";
import {
desktopBootstrapPath,
globalOpencodeConfigDir,
opencodeDbCandidates,
openworkEnvStorePath,
openworkServerConfigPath,
openworkServerDataDir,
} from "@openwork/paths";
import { ensureDenStack } from "./den-stack.ts";
import type { ChildProcess } from "node:child_process";
import type { DisposableHost, SurfaceHandle, ElectronSurfaceOptions, ChromeSurfaceOptions, DenServiceOptions, DenServiceHandle, ShareLinks } from "./types.ts";
type OrgMode = "single_org" | "multi_org";
export interface LocalHostOptions {
repoRoot: string;
rootDir?: string;
log(message: string): void;
}
export interface ElectronProfilePaths {
appDataDir: string;
bootstrapPath: string;
cacheHome: string;
configHome: string;
dataDir: string;
dataHome: string;
envStorePath: string;
homeDir: string;
localAppDataDir: string;
opencodeConfigDir: string;
root: string;
stateHome: string;
userDataDir: string;
}
export interface InstalledProductionDesktopState {
bootstrapPath: string;
dataDir: string;
envStorePath: string;
homeDir: string;
opencodeDb: string;
opencodeConfigDir: string;
serverConfigPath: string;
serverStatePath: string;
serverTokenStorePath: string;
workspaceStatePath: string;
}
export interface InstalledProductionDesktopStateOptions {
env?: NodeJS.ProcessEnv;
homeDir?: string;
platform?: NodeJS.Platform;
}
interface ElectronSurfaceEnvOptions {
appName: string;
appIdentifier: string;
port: number;
cdpPort: number;
}
interface SpawnedDetached {
child: ChildProcess;
pid: number;
}
interface SpawnDetachedOptions {
cwd: string;
env: NodeJS.ProcessEnv;
logPath: string;
}
interface KillLocalPidOptions {
graceMs?: number;
log?: (message: string) => void;
}
export interface FreePortOptions {
log?: (message: string) => void;
}
const CDP_WAIT_TIMEOUT_MS = 120_000;
const KILL_GRACE_MS = 5_000;
const POLL_INTERVAL_MS = 250;
const DEFAULT_CHROME_START_URL = "about:blank";
const DEFAULT_DARWIN_CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
const LINUX_CHROME_BINARIES = ["google-chrome", "chromium", "chromium-browser"];
let prepareSharedResourcesPromise: Promise<void> | null = null;
const delay = (ms: number) => new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.length > 0;
}
function errorCode(error: unknown): string | null {
return isRecord(error) && typeof error.code === "string" ? error.code : null;
}
function messageText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function sanitizeSlug(value: string): string {
return value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "") || "surface";
}
function timestamp(): string {
return new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 17);
}
function pnpmCommand(): string {
return process.platform === "win32" ? "pnpm.cmd" : "pnpm";
}
function needsShell(command: string): boolean {
return process.platform === "win32" && /\.(cmd|bat)$/i.test(command);
}
function executableOnPath(name: string, env: NodeJS.ProcessEnv, platform: string): string | null {
const rawPath = env.PATH ?? env.Path ?? "";
if (!rawPath) return null;
const pathDelimiter = platform === "win32" ? ";" : delimiter;
for (const entry of rawPath.split(pathDelimiter)) {
if (!entry) continue;
const candidate = join(entry, name);
if (existsSync(candidate)) return candidate;
}
return null;
}
function windowsChromeCandidates(env: NodeJS.ProcessEnv): string[] {
const roots = [env.LOCALAPPDATA, env.PROGRAMFILES, env["PROGRAMFILES(X86)"], env.ProgramFiles]
.filter(isNonEmptyString);
return roots.map((root) => join(root, "Google", "Chrome", "Application", "chrome.exe"));
}
function pidIsAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch (error) {
return errorCode(error) !== "ESRCH";
}
}
function signalPid(pid: number, signal: NodeJS.Signals): boolean {
try {
process.kill(pid, signal);
return true;
} catch (error) {
if (errorCode(error) === "ESRCH") return false;
return false;
}
}
function signalProcessGroup(pid: number, signal: NodeJS.Signals): boolean {
if (process.platform === "win32") return false;
try {
process.kill(-pid, signal);
return true;
} catch (error) {
if (errorCode(error) === "ESRCH") return false;
return false;
}
}
function processGroupIsAlive(pid: number): boolean {
if (process.platform === "win32") return false;
try {
process.kill(-pid, 0);
return true;
} catch (error) {
return errorCode(error) !== "ESRCH";
}
}
async function waitUntilGone(pid: number, timeoutMs: number): Promise<boolean> {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
if (!pidIsAlive(pid) && !processGroupIsAlive(pid)) return true;
await delay(POLL_INTERVAL_MS);
}
return !pidIsAlive(pid) && !processGroupIsAlive(pid);
}
function listeningPids(port: number): Promise<number[]> {
if (process.platform !== "darwin" && process.platform !== "linux") return Promise.resolve([]);
return new Promise((resolve, reject) => {
execFile(
"lsof",
[`-tiTCP:${port}`, "-sTCP:LISTEN"],
{ encoding: "utf8", timeout: 5_000 },
(error, stdout) => {
const code = isRecord(error) ? error.code : null;
if (error && code !== 1 && code !== "1") {
reject(error);
return;
}
const pids = stdout.split(/\s+/)
.map((value) => Number(value))
.filter((value) => Number.isInteger(value) && value > 0);
resolve([...new Set(pids)]);
},
);
});
}
function processGroupId(pid: number): Promise<number | null> {
if (process.platform === "win32") return Promise.resolve(null);
return new Promise((resolve) => {
execFile("ps", ["-o", "pgid=", "-p", String(pid)], { encoding: "utf8", timeout: 5_000 }, (error, stdout) => {
if (error) {
resolve(null);
return;
}
const pgid = Number(stdout.trim());
resolve(Number.isInteger(pgid) && pgid > 1 ? pgid : null);
});
});
}
/** Ensure no process is listening on a local TCP port, killing stale owners. */
export async function freePort(port: number, { log }: FreePortOptions = {}): Promise<void> {
if (!Number.isInteger(port) || port <= 0 || port > 65_535) {
throw new Error(`Port must be an integer from 1 to 65535, got ${port}.`);
}
if (process.platform !== "darwin" && process.platform !== "linux") return;
const deadline = Date.now() + 10_000;
const currentProcessGroup = await processGroupId(process.pid);
let pids = await listeningPids(port);
while (pids.length > 0 && Date.now() < deadline) {
for (const pid of pids) {
if (pid === process.pid) throw new Error(`Refusing to kill the current process listening on port ${port}.`);
const listenerGroup = await processGroupId(pid);
const killPid = listenerGroup !== null && currentProcessGroup !== null && listenerGroup !== currentProcessGroup
? listenerGroup
: pid;
const groupDetail = killPid === pid ? "" : ` in process group ${killPid}`;
log?.(`Port ${port} is still held by listener pid ${pid}${groupDetail}; stopping it.`);
await killLocalPid(killPid, { graceMs: 1_000, log });
}
await delay(100);
pids = await listeningPids(port);
}
if (pids.length > 0) {
throw new Error(`Port ${port} is still held by listener pid${pids.length === 1 ? "" : "s"} ${pids.join(", ")} after cleanup.`);
}
}
async function tailLines(filePath: string, lineCount: number): Promise<string> {
try {
const text = await readFile(filePath, "utf8");
return text.split(/\r?\n/).slice(-lineCount).join("\n");
} catch (error) {
return `Could not read ${filePath}: ${messageText(error)}`;
}
}
async function waitForPageTarget(cdpUrl: string, timeoutMs: number): Promise<void> {
const startedAt = Date.now();
let lastDetail = "no page targets yet";
while (Date.now() - startedAt < timeoutMs) {
try {
await waitForCdp(cdpUrl, {
timeoutMs: Math.min(1_000, Math.max(0, timeoutMs - (Date.now() - startedAt))),
});
const targets = await listTargets(cdpUrl, {
timeoutMs: Math.min(1_000, Math.max(0, timeoutMs - (Date.now() - startedAt))),
});
if (targets.some((target) => target.type === "page" && target.webSocketDebuggerUrl)) return;
lastDetail = `saw ${targets.length} targets, none were pages`;
} catch (error) {
lastDetail = messageText(error);
}
await delay(Math.min(POLL_INTERVAL_MS, Math.max(0, timeoutMs - (Date.now() - startedAt))));
}
throw new Error(`Timed out after ${timeoutMs}ms waiting for a page target at ${cdpUrl} (${lastDetail})`);
}
function spawnDetached(command: string, args: string[], { cwd, env, logPath }: SpawnDetachedOptions): SpawnedDetached {
const logFd = openSync(logPath, "a");
const child = spawn(command, args, {
cwd,
env,
detached: true,
shell: needsShell(command),
stdio: ["ignore", logFd, logFd],
});
child.unref();
if (!child.pid) throw new Error(`Could not spawn ${command}.`);
return { child, pid: child.pid };
}
function chromeArgs(cdpPort: number, profileDir: string, startUrl: string, headless: boolean): string[] {
const args = [
`--remote-debugging-port=${cdpPort}`,
`--user-data-dir=${profileDir}`,
"--window-size=1280,900",
"--no-first-run",
"--no-default-browser-check",
"--disable-popup-blocking",
// Avoid the Daytona preview h2 stall when ~28 dev chunks multiplex; h1.1 loads them, while plain-http local Den never negotiates h2.
"--disable-http2",
startUrl,
];
return headless ? ["--headless=new", ...args] : args;
}
async function waitForCdpOrExit(label: string, cdpUrl: string, spawned: SpawnedDetached, logPath: string, requirePageTarget = false): Promise<void> {
let exitDetail: string | null = null;
const exitPromise = new Promise<void>((_resolve, reject) => {
spawned.child.once("exit", async (code, signal) => {
const detail = signal ? `signal ${signal}` : `exit ${code ?? 0}`;
exitDetail = detail;
const tail = await tailLines(logPath, 40);
reject(new Error(`${label} process ${spawned.pid} stopped before CDP was ready (${detail}). Last 40 lines of ${logPath}:\n${tail}`));
});
});
const livenessPromise = new Promise<void>((_resolve, reject) => {
const timer = setInterval(async () => {
if (exitDetail || pidIsAlive(spawned.pid)) return;
clearInterval(timer);
const tail = await tailLines(logPath, 40);
reject(new Error(`${label} process ${spawned.pid} is not alive before CDP was ready. Last 40 lines of ${logPath}:\n${tail}`));
}, POLL_INTERVAL_MS);
timer.unref();
});
await Promise.race([
(requirePageTarget ? waitForPageTarget(cdpUrl, CDP_WAIT_TIMEOUT_MS) : waitForCdp(cdpUrl, { timeoutMs: CDP_WAIT_TIMEOUT_MS })).catch(async (error) => {
const tail = await tailLines(logPath, 40);
throw new Error(`${messageText(error)}. Last 40 lines of ${logPath}:\n${tail}`);
}),
exitPromise,
livenessPromise,
]);
}
async function runPrepareScript(scriptPath: string, outDir: string, desktopRoot: string): Promise<void> {
try {
await access(scriptPath, constants.R_OK);
} catch {
throw new Error(`Required Electron prepare helper is missing: ${scriptPath}`);
}
await new Promise<void>((resolveRun, reject) => {
execFile(process.execPath, [scriptPath, "--force", "--outdir", outDir], { cwd: desktopRoot, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }, (error, _stdout, stderr) => {
if (error) {
reject(new Error(`Electron shared resource prepare failed for ${scriptPath}: ${stderr || error.message}`));
return;
}
resolveRun();
});
});
}
async function prepareSharedElectronResources(repoRoot: string, log: (message: string) => void): Promise<void> {
if (process.env.OPENWORK_EVAL_ELECTRON_RESOURCES_PREPARED === "1") return;
if (!prepareSharedResourcesPromise) {
prepareSharedResourcesPromise = (async () => {
const desktopRoot = join(repoRoot, "apps", "desktop");
const scriptsRoot = join(desktopRoot, "scripts");
log("Preparing shared Electron sidecars/helpers once for local eval surfaces...");
await runPrepareScript(join(scriptsRoot, "prepare-sidecar.mjs"), join(desktopRoot, "resources", "sidecars"), desktopRoot);
await runPrepareScript(join(scriptsRoot, "prepare-computer-use-helper.mjs"), join(desktopRoot, "resources", "helpers"), desktopRoot);
})();
}
await prepareSharedResourcesPromise;
}
async function ensureElectronProfile(paths: ElectronProfilePaths): Promise<void> {
await Promise.all([
mkdir(paths.root, { recursive: true }),
mkdir(paths.userDataDir, { recursive: true }),
mkdir(paths.appDataDir, { recursive: true }),
mkdir(paths.localAppDataDir, { recursive: true }),
mkdir(paths.opencodeConfigDir, { recursive: true }),
mkdir(paths.dataDir, { recursive: true }),
mkdir(paths.homeDir, { recursive: true }),
mkdir(paths.configHome, { recursive: true }),
mkdir(paths.dataHome, { recursive: true }),
mkdir(paths.cacheHome, { recursive: true }),
mkdir(paths.stateHome, { recursive: true }),
]);
}
async function writeBootstrap(filePath: string, bootstrap: ElectronSurfaceOptions["bootstrap"]): Promise<void> {
if (!bootstrap) return;
await mkdir(dirname(filePath), { recursive: true });
await writeFile(
filePath,
`${JSON.stringify(bootstrap, null, 2)}\n`,
"utf8",
);
}
function cleanUrl(value: string): string {
return value.trim().replace(/\/+$/, "");
}
function isOrgMode(value: unknown): value is OrgMode {
return value === "single_org" || value === "multi_org";
}
async function runtimeOrgMode(webUrl: string): Promise<OrgMode> {
const response = await fetch(`${cleanUrl(webUrl)}/api/runtime-config`, { signal: AbortSignal.timeout(8_000) });
if (!response.ok) throw new Error(`runtime-config returned HTTP ${response.status}`);
const body: unknown = await response.json();
if (isRecord(body) && isOrgMode(body.orgMode)) return body.orgMode;
throw new Error("runtime-config response did not include orgMode");
}
export function electronProfilePaths(root: string): ElectronProfilePaths {
return {
appDataDir: join(root, "appdata"),
bootstrapPath: join(root, "bootstrap.json"),
cacheHome: join(root, "xdg-cache"),
configHome: join(root, "xdg-config"),
dataDir: join(root, "openwork-data"),
dataHome: join(root, "xdg-data"),
envStorePath: join(root, "openwork-env.json"),
homeDir: join(root, "home"),
localAppDataDir: join(root, "local-appdata"),
opencodeConfigDir: join(root, "opencode-config"),
root,
stateHome: join(root, "xdg-state"),
userDataDir: join(root, "electron-userdata"),
};
}
/**
* Where the HOST keeps pnpm's self-managed versions. The surface's fresh HOME
* hides this cache, and pnpm (packageManager pin vs the global binary) then
* re-downloads its pinned version from the network on EVERY spawn — observed
* on a Daytona sandbox as a 21MB download per Electron spawn that, when it
* failed once, degenerated into a self-sustaining recursive `pnpm add pnpm`
* cascade that outlived the spec run. Pointing PNPM_HOME at the host's real
* pnpm home keeps version redirection local and instant.
*/
function hostPnpmHome(): string | null {
const configured = process.env.PNPM_HOME?.trim();
if (configured) return configured;
if (process.platform === "darwin") return join(homedir(), "Library", "pnpm");
if (process.platform === "linux") {
const dataHome = process.env.XDG_DATA_HOME?.trim();
return join(dataHome || join(homedir(), ".local", "share"), "pnpm");
}
if (process.platform === "win32") {
const localAppData = process.env.LOCALAPPDATA?.trim();
return localAppData ? join(localAppData, "pnpm") : null;
}
return null;
}
export function electronSurfaceEnv(
paths: ElectronProfilePaths,
options: ElectronSurfaceEnvOptions,
overrides: Record<string, string> = {},
): Record<string, string> {
const pnpmHome = hostPnpmHome();
// Give local eval Electron surfaces isolated app data, config, and identity so
// they cannot affect the user's real desktop app.
return {
...(pnpmHome ? { PNPM_HOME: pnpmHome } : {}),
APPDATA: paths.appDataDir,
HOME: paths.homeDir,
LOCALAPPDATA: paths.localAppDataDir,
OPENWORK_DATA_DIR: paths.dataDir,
OPENWORK_DESKTOP_BOOTSTRAP_PATH: paths.bootstrapPath,
OPENWORK_DESKTOP_DISABLE_WORKSPACE_RECOVERY: "1",
OPENWORK_DEV_MODE: "1",
OPENWORK_ENV_STORE: paths.envStorePath,
OPENCODE_CONFIG_DIR: paths.opencodeConfigDir,
VITE_DISABLE_OPENWORK_MODELS: "1",
OPENWORK_ELECTRON_APP_IDENTIFIER: options.appIdentifier,
OPENWORK_ELECTRON_APP_NAME: options.appName,
OPENWORK_ELECTRON_DISABLE_PROTOCOL_REGISTRATION: "1",
OPENWORK_ELECTRON_REMOTE_DEBUG_PORT: String(options.cdpPort),
OPENWORK_ELECTRON_SKIP_SHARED_PREPARE: "1",
OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN: "1",
OPENWORK_ELECTRON_USERDATA: paths.userDataDir,
PORT: String(options.port),
XDG_CACHE_HOME: paths.cacheHome,
XDG_CONFIG_HOME: paths.configHome,
XDG_DATA_HOME: paths.dataHome,
XDG_STATE_HOME: paths.stateHome,
...overrides,
};
}
export function liveSharedProductionStateEnv(state: InstalledProductionDesktopState): Record<string, string> {
return {
HOME: state.homeDir,
USERPROFILE: state.homeDir,
XDG_CONFIG_HOME: join(state.homeDir, ".config"),
XDG_DATA_HOME: join(state.homeDir, ".local", "share"),
XDG_CACHE_HOME: join(state.homeDir, ".cache"),
XDG_STATE_HOME: join(state.homeDir, ".local", "state"),
OPENWORK_DATA_DIR: state.dataDir,
OPENWORK_DESKTOP_BOOTSTRAP_PATH: state.bootstrapPath,
OPENWORK_DESKTOP_DISABLE_WORKSPACE_RECOVERY: "0",
OPENWORK_DESKTOP_WORKSPACE_STATE_PATH: state.workspaceStatePath,
OPENWORK_DEV_SHARED_STATE: "1",
OPENWORK_ENV_STORE: state.envStorePath,
OPENWORK_SERVER_CONFIG: state.serverConfigPath,
OPENWORK_SERVER_STATE_PATH: state.serverStatePath,
OPENWORK_SERVER_TOKEN_STORE_PATH: state.serverTokenStorePath,
OPENCODE_DB: state.opencodeDb,
OPENCODE_CONFIG_DIR: state.opencodeConfigDir,
};
}
async function requireInstalledPath(path: string, kind: "directory" | "file", label: string): Promise<void> {
let metadata;
try {
metadata = await stat(path);
} catch {
throw new Error(`${label} is unavailable at ${path}. Start the installed production OpenWork desktop once and confirm its local state exists.`);
}
const matches = kind === "directory" ? metadata.isDirectory() : metadata.isFile();
if (!matches) throw new Error(`${label} at ${path} is not a ${kind}.`);
}
/** Resolve installed production stores without launching, copying, linking, or mutating them. */
export async function resolveInstalledProductionDesktopState(
options: InstalledProductionDesktopStateOptions = {},
): Promise<InstalledProductionDesktopState> {
const platform = options.platform ?? process.platform;
if (platform !== "darwin") {
throw new Error(`Live shared installed-production state is currently supported only on macOS; received ${platform}.`);
}
const env = options.env ?? process.env;
const homeDir = options.homeDir ?? homedir();
const dataDir = openworkServerDataDir({ env, homeDir, platform });
await requireInstalledPath(dataDir, "directory", "Installed production OpenWork data directory");
const userDataDir = join(homeDir, "Library", "Application Support", "com.differentai.openwork");
const workspaceStatePath = join(userDataDir, "openwork-workspaces.json");
const serverTokenStorePath = join(userDataDir, "openwork-server-tokens.json");
const serverStatePath = join(userDataDir, "openwork-server-state.json");
const serverConfigPath = openworkServerConfigPath({ env, homeDir, platform });
const envStorePath = openworkEnvStorePath({ env, homeDir, platform });
const bootstrapPath = desktopBootstrapPath({ env, homeDir, platform, userDataDir });
const opencodeConfigDir = globalOpencodeConfigDir({ env, homeDir, platform });
for (const [path, label] of [
[workspaceStatePath, "Installed production workspace state"],
[serverTokenStorePath, "Installed production server token store"],
[serverStatePath, "Installed production server state"],
[serverConfigPath, "Installed production server config"],
[envStorePath, "Installed production environment store"],
[bootstrapPath, "Installed production desktop bootstrap"],
]) {
await requireInstalledPath(path, "file", label);
}
await requireInstalledPath(opencodeConfigDir, "directory", "Installed production OpenCode config directory");
const candidates = opencodeDbCandidates({
env,
homeDir,
platform,
defaultChannel: "latest",
});
let opencodeDb: string | undefined;
for (const candidate of candidates) {
const metadata = await stat(candidate).catch(() => null);
if (metadata?.isFile()) {
opencodeDb = candidate;
break;
}
}
if (!opencodeDb) {
throw new Error(
`Installed production OpenCode database is unavailable. Checked channel-aware candidates: ${candidates.join(", ")}.`,
);
}
return {
bootstrapPath,
dataDir,
envStorePath,
homeDir,
opencodeDb,
opencodeConfigDir,
serverConfigPath,
serverStatePath,
serverTokenStorePath,
workspaceStatePath,
};
}
export function resolveChromeBinary(env: NodeJS.ProcessEnv = process.env, platform: string = process.platform): string {
const configured = env.CHROME_BIN?.trim();
if (configured) return configured;
if (platform === "darwin") return DEFAULT_DARWIN_CHROME;
if (platform === "linux") {
for (const name of LINUX_CHROME_BINARIES) {
const resolved = executableOnPath(name, env, platform);
if (resolved) return resolved;
}
throw new Error(`Could not resolve Chrome binary on linux. Set CHROME_BIN or put one of ${LINUX_CHROME_BINARIES.join(", ")} on PATH.`);
}
if (platform === "win32") {
for (const candidate of windowsChromeCandidates(env)) {
if (existsSync(candidate)) return candidate;
}
throw new Error("Could not resolve Chrome binary on win32. Set CHROME_BIN or install Google Chrome in a standard location.");
}
throw new Error(`Could not resolve Chrome binary on unsupported platform ${platform}. Set CHROME_BIN.`);
}
export async function killLocalPid(pid: number, options: KillLocalPidOptions = {}): Promise<boolean> {
if (!Number.isInteger(pid) || pid <= 0) return false;
const rootWasAlive = pidIsAlive(pid);
const graceMs = options.graceMs ?? KILL_GRACE_MS;
const sentGroup = signalProcessGroup(pid, "SIGINT");
const sentDirect = !sentGroup && rootWasAlive ? signalPid(pid, "SIGINT") : false;
if (!sentGroup && !sentDirect) return false;
if (sentGroup) options.log?.(`Sent SIGINT to process group ${pid}`);
if (sentDirect && !sentGroup) options.log?.(`Sent SIGINT to pid ${pid}`);
if (await waitUntilGone(pid, graceMs)) return true;
const killedGroup = signalProcessGroup(pid, "SIGKILL");
const killedDirect = pidIsAlive(pid) ? signalPid(pid, "SIGKILL") : false;
if (killedGroup) options.log?.(`Sent SIGKILL to process group ${pid}`);
else if (killedDirect) options.log?.(`Sent SIGKILL to pid ${pid}`);
await waitUntilGone(pid, 1_000);
return true;
}
export function ownedSurfaceFilePaths(handle: SurfaceHandle): string[] {
return handle.kind === "electron" && handle.profileDir && handle.meta?.profileOwner !== "caller"
? [handle.profileDir]
: [];
}
export async function removeOwnedSurfaceFiles(handle: SurfaceHandle): Promise<void> {
for (const path of ownedSurfaceFilePaths(handle)) {
await rm(path, { recursive: true, force: true });
}
}
/** Stop a detached eval Electron only when its process environment still names its isolated profile. */
export async function stopOwnedElectronSurface(pid: number, profileDir: string): Promise<void> {
if (!Number.isInteger(pid) || pid <= 1 || !profileDir.trim()) {
throw new Error("Detached Electron ownership requires a valid pid and profile directory.");
}
const processDescription = await new Promise<string>((resolveDescription, reject) => {
execFile("ps", ["eww", "-p", String(pid)], { encoding: "utf8", timeout: 5_000 }, (error, stdout) => {
if (error) {
reject(new Error(`Detached Electron process ${pid} is not running.`));
return;
}
resolveDescription(stdout);
});
});
if (!processDescription.includes(`OPENWORK_ELECTRON_USERDATA=${join(profileDir, "electron-userdata")}`)) {
throw new Error(`Refusing to stop pid ${pid}: it does not own the expected eval Electron profile.`);
}
await killLocalPid(pid);
await rm(profileDir, { recursive: true, force: true });
}
function explicitPort(url: string): number | null {
try {
const parsed = new URL(url);
if (parsed.hostname !== "127.0.0.1" && parsed.hostname !== "localhost" && parsed.hostname !== "::1") return null;
const port = Number(parsed.port);
return Number.isInteger(port) && port > 0 ? port : null;
} catch {
return null;
}
}
function surfacePorts(handle: SurfaceHandle): number[] {
const ports = new Set<number>();
const fromUrl = explicitPort(handle.cdpUrl);
if (fromUrl !== null) ports.add(fromUrl);
for (const value of [handle.meta?.vitePort, handle.meta?.cdpPort]) {
const port = Number(value);
if (Number.isInteger(port) && port > 0 && port <= 65_535) ports.add(port);
}
return [...ports];
}
export function createLocalHost(options: LocalHostOptions): DisposableHost {
// Surface profiles become the Electron process HOME. node-gyp/electron-rebuild
// generate Makefiles with unquoted include paths under that HOME, so a repo
// checkout on a path containing spaces breaks every native rebuild. The env
// override lets such machines park surfaces on a space-free path (e.g. /tmp).
const surfacesRootOverride = process.env.OPENWORK_EVAL_SURFACES_DIR?.trim();
const rootDir = options.rootDir ?? (surfacesRootOverride
? surfacesRootOverride
: join(options.repoRoot, "evals", "results", ".surfaces", String(process.pid)));
const log = options.log;
const spawnedSurfaces = new Set<SurfaceHandle>();
const denPorts = new Set<number>();
async function disposeKnownPorts(handle: SurfaceHandle): Promise<void> {
for (const port of surfacePorts(handle)) await freePort(port, { log });
}
async function disposeDenPorts(): Promise<void> {
for (const port of denPorts) await freePort(port, { log });
denPorts.clear();
}
// Containers (Daytona sandboxes) cannot use Chromium's SUID sandbox: the helper
// binary in a mounted pnpm store is not root-owned, and Electron aborts with
// "The SUID sandbox helper binary was found, but is not configured correctly".
// The desktop honours ELECTRON_EXTRA_LAUNCH_ARGS (apps/desktop/electron/main.mjs),
// so pass the container-safe switches when we detect a sandbox.
function insideContainerSandbox(env: NodeJS.ProcessEnv = process.env): boolean {
if ((env.DAYTONA_SANDBOX_ID ?? "").trim().length > 0) return true;
if ((env.OPENWORK_EVAL_CONTAINER_ELECTRON ?? "").trim() === "1") return true;
return existsSync("/daytona-secrets") || existsSync("/daytona-artifacts");
}
function containerLaunchArgs(existing: string | undefined): string | undefined {
if (!insideContainerSandbox()) return existing;
const needed = ["--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage", "--enable-unsafe-swiftshader"];
const present = (existing ?? "").split(/\s+/).filter((entry) => entry.length > 0);
for (const arg of needed) if (!present.includes(arg)) present.push(arg);
return present.join(" ");
}
/**
* Spawning a desktop has environment preconditions that only this component can
* reasonably own. Callers should not have to know that Electron needs a live X
* server, or that a previous run's process may still hold a port.
*/
const sleepMs = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function displayAnswers(display: string): Promise<boolean> {
return new Promise((resolve) => {
execFile("xdpyinfo", ["-display", display], (error) => resolve(!error));
});
}
async function ensureDisplay(repoRoot: string, env: NodeJS.ProcessEnv, log: (message: string) => void): Promise<void> {
const display = (env.DISPLAY ?? "").trim();
if (!display) return;
if (await displayAnswers(display)) return;
// A stale /tmp/.X11-unix socket is not proof the server is alive: Electron
// exits with "Missing X server or $DISPLAY", which looks like a hung renderer.
const starter = join(repoRoot, ".devcontainer", "start-daytona-vnc.sh");
if (!existsSync(starter)) {
log(`Display ${display} is not answering and ${starter} is missing; Electron will fail to start.`);
return;
}
log(`Display ${display} is not answering; starting the virtual display...`);
spawnDetached("bash", [starter], { cwd: repoRoot, env, logPath: join(repoRoot, "evals", "results", "virtual-display.log") });
for (let attempt = 0; attempt < 30; attempt += 1) {
await sleepMs(2_000);
if (await displayAnswers(display)) {
log(`Display ${display} is live.`);
return;
}
}
log(`Display ${display} still not answering after 60s; Electron will fail to start.`);
}
/**
* Kill Electron processes from earlier runs of THIS host's surfaces, and delete
* the profile directories they left behind.
*
* Disposal removes a profile on the happy path, but every killed or failed run
* leaks one at ~50MB. Twenty-seven of them filled a 10GB sandbox to 99%, and the
* symptom was not "disk full" — it was the renderer failing to answer
* `Runtime.evaluate` for 240s, which reads exactly like a broken app. Pruning
* here is cheap and keeps that failure from being invented again.
*/
async function clearStaleSurfaces(rootDir: string, log: (message: string) => void): Promise<void> {
await new Promise<void>((resolve) => {
execFile("pkill", ["--full", rootDir], () => resolve());
});
// The default root is worker-scoped, so parallel files cannot kill or remove
// another worker's desktop while pruning their own stale profiles.
const stale = await readdir(rootDir).catch(() => []);
let removed = 0;
for (const entry of stale) {
await rm(join(rootDir, entry), { recursive: true, force: true }).then(() => { removed += 1; }).catch(() => undefined);
}
log(`Cleared Electron processes and ${removed} stale profile director${removed === 1 ? "y" : "ies"} in ${rootDir}.`);
}
return {
kind: "local",
workspaceRoot: options.repoRoot,
async spawnElectron(name: string, opts: ElectronSurfaceOptions = {}): Promise<SurfaceHandle> {
if (opts.prepareSharedResources !== false) {
await prepareSharedElectronResources(options.repoRoot, log);
}
const spawnEnvForChecks: NodeJS.ProcessEnv = { ...process.env, ...opts.env };
if (insideContainerSandbox() && (spawnEnvForChecks.DISPLAY ?? "").trim().length === 0) spawnEnvForChecks.DISPLAY = ":99";
await ensureDisplay(options.repoRoot, spawnEnvForChecks, log);
if (opts.profileDir !== undefined && !opts.profileDir.trim()) {
throw new Error("Electron profileDir must not be empty.");
}
const callerOwnedProfile = opts.profileDir !== undefined;
if (!callerOwnedProfile) await clearStaleSurfaces(rootDir, log);
const profileRoot = opts.profileDir ?? join(rootDir, `${sanitizeSlug(name)}-${timestamp()}-${process.pid}`);
const paths = electronProfilePaths(profileRoot);
await ensureElectronProfile(paths);
await writeBootstrap(paths.bootstrapPath, opts.bootstrap);
const [port, cdpPort] = await allocateFreePorts(2);
if (port === undefined || cdpPort === undefined) throw new Error("Could not allocate Electron Vite/CDP ports.");
const appName = `OpenWork Eval ${name}`;
const appIdentifier = `com.differentai.openwork.eval.${sanitizeSlug(name)}`;
const isolationEnv = electronSurfaceEnv(paths, { appName, appIdentifier, port, cdpPort }, opts.env);
const env: NodeJS.ProcessEnv = { ...process.env, ...isolationEnv };
const launchArgs = containerLaunchArgs(env.ELECTRON_EXTRA_LAUNCH_ARGS);
if (launchArgs !== undefined) env.ELECTRON_EXTRA_LAUNCH_ARGS = launchArgs;
// appendSwitch() in the main process runs too late for the SUID sandbox
// check (it aborts in a child before our JS switches apply), so disable
// the sandbox at process start the way Electron documents.
if (insideContainerSandbox()) env.ELECTRON_DISABLE_SANDBOX = "1";
// Sandbox exec sessions do not export DISPLAY, but Xvfb is running on :99
// (see .devcontainer/start-daytona-electron.sh). Without it Electron
// segfaults instead of opening a window.
if (insideContainerSandbox() && (env.DISPLAY ?? "").trim().length === 0) env.DISPLAY = ":99";
const logPath = join(profileRoot, "electron.log");
const packagedBinary = opts.devCommand === undefined
? process.env.OPENWORK_EVAL_ELECTRON_BINARY?.trim()
: undefined;
let spawned: SpawnedDetached;
if (packagedBinary) {
await access(packagedBinary, constants.F_OK).catch(() => {
throw new Error(`OPENWORK_EVAL_ELECTRON_BINARY does not exist: ${packagedBinary}`);
});
log(`Starting local Electron surface ${name} from packaged binary ${packagedBinary} (CDP :${cdpPort})...`);
spawned = spawnDetached(packagedBinary, [], { cwd: options.repoRoot, env, logPath });
} else {
log(`Starting local Electron surface ${name} (Vite :${port}, CDP :${cdpPort})...`);
spawned = spawnDetached(pnpmCommand(), [opts.devCommand ?? "dev:electron"], { cwd: options.repoRoot, env, logPath });
}
const cdpUrl = `http://127.0.0.1:${cdpPort}`;
try {
await waitForCdpOrExit("Electron", cdpUrl, spawned, logPath, true);
} catch (error) {
await killLocalPid(spawned.pid, { log });
await Promise.all([
freePort(port, { log }),
freePort(cdpPort, { log }),
]).catch((cleanupError: unknown) => log(`Electron port cleanup failed: ${messageText(cleanupError)}`));
throw error;
}
const handle: SurfaceHandle = {
name,
kind: "electron",
hostKind: "local",
cdpUrl,
pid: spawned.pid,
profileDir: profileRoot,
meta: { vitePort: String(port), cdpPort: String(cdpPort), log: logPath, profileOwner: callerOwnedProfile ? "caller" : "host" },
};
spawnedSurfaces.add(handle);
return handle;
},
async spawnChrome(name: string, opts: ChromeSurfaceOptions = {}): Promise<SurfaceHandle> {
const profileRoot = join(rootDir, `${sanitizeSlug(name)}-${timestamp()}-${process.pid}`);
const profileDir = join(profileRoot, "chrome-profile");
await mkdir(profileDir, { recursive: true });
const cdpPort = await allocateFreePort();
const binary = resolveChromeBinary(process.env, process.platform);
const startUrl = opts.startUrl ?? DEFAULT_CHROME_START_URL;
const logPath = join(profileRoot, "chrome.log");
const env: NodeJS.ProcessEnv = { ...process.env };
const cdpUrl = `http://127.0.0.1:${cdpPort}`;
const launch = async (headless: boolean): Promise<SpawnedDetached> => {
const spawned = spawnDetached(binary, chromeArgs(cdpPort, profileDir, startUrl, headless), { cwd: profileRoot, env, logPath });
await writeFile(join(profileDir, "openwork-eval-chrome.pid"), `${spawned.pid}\n`, "utf8");
try {
await waitForCdpOrExit("Chrome", cdpUrl, spawned, logPath);
} catch (error) {
await killLocalPid(spawned.pid, { log });
await freePort(cdpPort, { log })
.catch((cleanupError: unknown) => log(`Chrome port cleanup failed: ${messageText(cleanupError)}`));
throw error;
}
return spawned;
};
let spawned: SpawnedDetached;
try {
spawned = await launch(opts.headless === true || process.env.OPENWORK_EVAL_CHROME_HEADLESS === "1");
} catch (error) {
if (!messageText(error).includes("SIGTRAP")) throw error;
log(`Chrome surface ${name} exited with SIGTRAP under the windowed launch; retrying with --headless=new.`);
spawned = await launch(true);
}
const handle: SurfaceHandle = {
name,
kind: "chrome",
hostKind: "local",
cdpUrl,
pid: spawned.pid,
profileDir,
meta: { cdpPort: String(cdpPort), log: logPath },
};
spawnedSurfaces.add(handle);
return handle;
},
async startDen(opts: DenServiceOptions = {}): Promise<DenServiceHandle> {
if (opts.seed === "none") {
log("seed:none requested; local Den stack currently keeps the Acme demo seed, so continuing with the default seed.");
}
await ensureDenStack({ log, cdpCandidates: [], skipApp: true, orgMode: opts.orgMode });
const apiUrl = process.env.OPENWORK_EVAL_DEN_API_URL?.trim();
const webUrl = process.env.OPENWORK_EVAL_DEN_WEB_URL?.trim();
if (!apiUrl || !webUrl) throw new Error("Den stack did not export OPENWORK_EVAL_DEN_API_URL / OPENWORK_EVAL_DEN_WEB_URL.");
const orgMode = await runtimeOrgMode(webUrl);
const apiPort = explicitPort(apiUrl);
const webPort = explicitPort(webUrl);
if (apiPort !== null) denPorts.add(apiPort);
if (webPort !== null) denPorts.add(webPort);
return { webUrl, apiUrl, orgMode, hostKind: "local" };
},
async share(): Promise<ShareLinks> {
const links: ShareLinks = [];
const webUrl = process.env.OPENWORK_EVAL_DEN_WEB_URL?.trim();
const apiUrl = process.env.OPENWORK_EVAL_DEN_API_URL?.trim();
if (webUrl) links.push({ label: "Den Web", url: webUrl });
if (apiUrl) links.push({ label: "Den API", url: apiUrl });
return links;
},
async disposeSurface(handle: SurfaceHandle): Promise<void> {
if (handle.pid !== undefined) {
await killLocalPid(handle.pid, { log });
}
await disposeKnownPorts(handle);
await removeOwnedSurfaceFiles(handle);
spawnedSurfaces.delete(handle);
},
async stop(): Promise<void> {
for (const handle of [...spawnedSurfaces]) await this.disposeSurface(handle);
await disposeDenPorts();
},
async [Symbol.asyncDispose](): Promise<void> {
for (const handle of [...spawnedSurfaces]) {
await this.disposeSurface(handle)
.catch((error: unknown) => log(`Local surface ${handle.name} cleanup failed: ${messageText(error)}`));
}
await disposeDenPorts()
.catch((error: unknown) => log(`Local Den port cleanup failed: ${messageText(error)}`));
},
};
}
+914
View File
@@ -0,0 +1,914 @@
import { spawn } from "node:child_process";
import { randomBytes } from "node:crypto";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { fileURLToPath } from "node:url";
import { checkedExec, defaultDaytonaExec } from "./daytona.ts";
import { FAULT_PROXY_SCRIPT } from "./fault-proxy-script.ts";
import type { DaytonaExec, DaytonaExecResult } from "./daytona.ts";
const REPO_ROOT = fileURLToPath(new URL("../../../..", import.meta.url));
const DESKTOP_READY_TIMEOUT_MS = 300_000;
const INSTALL_TIMEOUT_MS = 25 * 60 * 1_000;
const SERVER_SCRIPT_TIMEOUT_MS = 20 * 60 * 1_000;
const HTTPS_URL = /https:\/\/[^\s"'<>)]+/;
const DEN_WEB_PORT = 3005;
const DEN_API_PORT = 8788;
export interface ProvisionExecOptions {
exec?: DaytonaExec;
}
export interface DesktopSandboxOptions {
ref: string;
name: string;
reuse?: string;
snapshot?: string;
/**
* Mount the shared eval secrets volume. Off by default: `pnpm install` runs
* lifecycle scripts from the checked-out ref, so mounting provider keys next
* to an untrusted ref hands them to it. Signed-in org desktops can only pick
* the org's own models anyway, and driver-side vision keys never live here —
* so nothing in the connector room needs this.
*/
secrets?: boolean;
log?: (line: string) => void;
}
export interface DesktopSandbox {
sandbox: string;
created: boolean;
}
export interface DenSandboxOptions {
ref: string;
reuse?: string;
repoRoot?: string;
bootstrapAdminEmail?: string;
log?: (line: string) => void;
}
export interface DenSandbox {
sandbox: string;
apiUrl: string;
webUrl: string;
created: boolean;
}
export interface MockOnSandboxOptions {
sandbox: string;
port?: number;
log?: (line: string) => void;
fetchImpl?: typeof fetch;
allowUnauthenticatedMcp?: boolean;
}
export interface MockOnSandbox {
url: string;
}
export interface FaultProxyOnSandboxOptions {
sandbox: string;
port?: number;
upstreamPort?: number;
log?: (line: string) => void;
fetchImpl?: typeof fetch;
}
export interface FaultProxyOnSandbox {
url: string;
token: string;
stop(): Promise<void>;
}
export interface ConnectorE2eTestEnv {
denApiUrl: string;
denWebUrl: string;
sandboxA: string;
sandboxB: string;
mockUrl: string;
ref: string;
created: string[];
}
function messageText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function outputTail(result: DaytonaExecResult): string {
return `${result.stdout}${result.stderr}`.trim().slice(-2_000);
}
function textTail(text: string): string {
return text.trim().slice(-4_000);
}
function firstHttpsUrl(text: string): string | null {
const match = HTTPS_URL.exec(text);
return match ? match[0].replace(/[.,;:]+$/, "") : null;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
async function timedStep<T>(log: (line: string) => void, name: string, action: () => Promise<T>): Promise<T> {
const startedAt = Date.now();
log(`==> ${name}...`);
try {
const result = await action();
log(`==> ${name} done (${Date.now() - startedAt}ms)`);
return result;
} catch (error) {
log(`==> ${name} failed (${Date.now() - startedAt}ms)`);
throw error;
}
}
/**
* daytona exec joins its trailing args with spaces, so a multi-word command
* must travel as ONE argument or `bash -lc` receives only the first word and
* the rest leaks into the remote login shell.
*/
export async function execInSandbox(
exec: DaytonaExec,
sandbox: string,
script: string,
opts: { timeoutMs?: number; context: string },
): Promise<DaytonaExecResult> {
if (script.includes("'")) throw new Error(`Remote script for ${opts.context} must not contain single quotes.`);
return checkedExec(exec, ["exec", sandbox, "--", `bash -lc '${script}'`], opts.context, { timeoutMs: opts.timeoutMs });
}
/**
* A ref travels into a remote double-quoted shell word AND into a file the
* operator is told to `source`, so `$(...)`, a quote, or a newline in it is
* remote code execution. Refuse anything outside git's own safe alphabet here
* rather than escaping it correctly at each site forever.
*/
function assertSafeRef(ref: string): string {
const value = ref.trim();
if (!/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value)) {
throw new Error(`Unsafe git ref ${JSON.stringify(ref)}: only letters, digits and . _ / - are allowed, and it may not start with "-".`);
}
return value;
}
function snapshotId(output: string, name: string): string | null {
let parsed: unknown;
try {
parsed = JSON.parse(output);
} catch (error) {
throw new Error(`Snapshot gate failed: daytona snapshot list returned invalid JSON: ${messageText(error)}. Output tail: ${textTail(output)}`);
}
if (!Array.isArray(parsed)) {
throw new Error(`Snapshot gate failed: daytona snapshot list did not return an array. Output tail: ${textTail(output)}`);
}
for (const entry of parsed) {
if (isRecord(entry) && entry.name === name && typeof entry.id === "string" && entry.id.length > 0) return entry.id;
}
return null;
}
function sandboxTimestamp(): string {
return new Date().toISOString().replace(/[-:]/g, "").replace("T", "-").slice(0, 15);
}
export function desktopSandboxName(name: string): string {
// Split/join rather than trimming with /^-+|-+$/g: that pattern backtracks
// quadratically on a mid-string run of hyphens (~1s at 40KB), which CodeQL
// flags as polynomial ReDoS. This form cannot backtrack and also collapses
// internal runs, so "a_-_b" yields "a-b" instead of "a---b".
const safeName = name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("-") || "surface";
return `openwork-connector-${safeName}-${sandboxTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
}
export function serverSandboxName(): string {
return `openwork-server-${sandboxTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
}
async function waitForExecReady(exec: DaytonaExec, sandbox: string): Promise<void> {
const deadline = Date.now() + DESKTOP_READY_TIMEOUT_MS;
let lastError = "not attempted";
while (Date.now() < deadline) {
try {
await execInSandbox(exec, sandbox, "true", { timeoutMs: 30_000, context: `sandbox exec-ready gate for ${sandbox}` });
return;
} catch (error) {
lastError = messageText(error);
}
await delay(5_000);
}
throw new Error(`Sandbox exec-ready gate failed for ${sandbox} after 300s. Last output: ${lastError}`);
}
function lastNonemptyLine(text: string): string {
return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1) ?? "";
}
export async function provisionDesktopSandbox(options: DesktopSandboxOptions & ProvisionExecOptions): Promise<DesktopSandbox> {
const exec = options.exec ?? defaultDaytonaExec;
const log = options.log ?? console.error;
const ref = assertSafeRef(options.ref);
const reused = options.reuse?.trim() || "";
let sandbox = reused;
await timedStep(log, "sandbox gate", async () => {
if (reused) {
await exec(["sandbox", "start", reused], { timeoutMs: 60_000 });
} else {
const snapshot = options.snapshot ?? "openwork-eval-vnc";
const listed = await checkedExec(exec, ["snapshot", "list", "-f", "json"], "snapshot gate", { timeoutMs: 60_000 });
const id = snapshotId(listed.stdout, snapshot);
if (!id) {
throw new Error(`Snapshot gate failed: snapshot ${snapshot} is missing. Output tail: ${outputTail(listed)}`);
}
sandbox = desktopSandboxName(options.name);
await checkedExec(
exec,
[
"create",
"--name", sandbox,
"--snapshot", id,
...(options.secrets === true ? ["--volume", "openwork-eval-secrets:/daytona-secrets"] : []),
"--auto-stop", "60",
"--public",
"--target", "us",
],
`sandbox creation gate for ${sandbox}`,
{ timeoutMs: 300_000 },
);
log(`==> desktop sandbox created: ${sandbox}`);
}
await waitForExecReady(exec, sandbox);
});
await timedStep(log, "checkout gate", async () => {
const result = await execInSandbox(
exec,
sandbox,
// Check out the REQUESTED ref, not FETCH_HEAD: a raw-sha fetch was
// observed leaving FETCH_HEAD stale, silently running the wrong code —
// and servers may refuse raw-sha fetches outright, so fall back to a
// full fetch and prefer the remote-tracking ref over any stale local.
`set -e; cd /workspace; git fetch origin "${ref}" 2>/dev/null || git fetch origin; git checkout --detach "origin/${ref}" 2>/dev/null || git checkout --detach "${ref}" 2>/dev/null || git checkout --detach FETCH_HEAD; git rev-parse --short=12 HEAD`,
{ timeoutMs: 120_000, context: `checkout gate for ${sandbox}` },
);
const sha = lastNonemptyLine(result.stdout);
if (!sha) throw new Error(`Checkout gate failed for ${sandbox}: git did not print a resolved sha. Output tail: ${outputTail(result)}`);
const wantsSha = /^[0-9a-f]{7,40}$/.test(ref);
if (wantsSha && !sha.startsWith(ref.slice(0, 12)) && !ref.startsWith(sha)) {
throw new Error(`Checkout gate failed for ${sandbox}: asked for ${ref} but HEAD is ${sha}.`);
}
log(`==> checkout resolved ${sha}`);
});
await timedStep(log, "install gate", async () => {
await execInSandbox(
exec,
sandbox,
"cd /workspace; pnpm install --store-dir /workspace/.openwork-daytona/pnpm-store",
{ timeoutMs: INSTALL_TIMEOUT_MS, context: `install gate for ${sandbox}` },
);
});
await timedStep(log, "cleanup and disk gate", async () => {
const result = await execInSandbox(
exec,
sandbox,
"rm -rf /workspace/.openwork-daytona/profiles /tmp/openwork-* 2>/dev/null; df -P /workspace | tail -1",
{ timeoutMs: 60_000, context: `cleanup and disk gate for ${sandbox}` },
);
const dfLine = lastNonemptyLine(result.stdout);
const useField = dfLine.split(/\s+/).find((field) => /^\d+%$/.test(field));
if (!useField) {
throw new Error(`Cleanup and disk gate failed for ${sandbox}: could not parse Use% from ${JSON.stringify(dfLine)}.`);
}
const used = Number.parseInt(useField, 10);
if (used > 85) {
const sizes = await execInSandbox(
exec,
sandbox,
"du -sh /workspace/node_modules /workspace/.openwork-daytona/pnpm-store 2>&1 || true",
{ timeoutMs: 60_000, context: `disk usage detail for ${sandbox}` },
);
throw new Error(`Cleanup and disk gate failed for ${sandbox}: workspace is ${useField} used. df: ${dfLine}\n${outputTail(sizes)}`);
}
});
await timedStep(log, "display gate", async () => {
const result = await execInSandbox(
exec,
sandbox,
"bash /workspace/.devcontainer/start-daytona-vnc.sh >/tmp/vnc.log 2>&1; sleep 2; pgrep -f Xvfb >/dev/null && echo XVFB_OK || echo XVFB_FAIL",
{ timeoutMs: 60_000, context: `display gate for ${sandbox}` },
);
if (!result.stdout.includes("XVFB_OK")) {
throw new Error(`Display gate failed for ${sandbox}: expected XVFB_OK. Output tail: ${outputTail(result)}`);
}
});
await timedStep(log, "browser hop gate", async () => {
// Chromium launched inside a pipe-stdin exec session TERMs the whole
// session as it starts (exit 143 at ~2.5s; the same script survives under
// a TTY). So nothing may run as a child of the session: both halves are
// fully detached the way the mock and Vite gates are, and the proof is
// read back by clean, childless polls.
const detachScript = `rm -f /tmp/xdgtest.log; python3 - <<PYEOF
import subprocess
log = open("/tmp/xdgtest.log", "ab", buffering=0)
subprocess.Popen(["python3", "-m", "http.server", "18099", "--bind", "127.0.0.1"], stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, start_new_session=True, close_fds=True)
subprocess.Popen(["bash", "-lc", "sleep 1; export DISPLAY=:99; xdg-open http://127.0.0.1:18099/xdg-open-proof"], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True, close_fds=True)
PYEOF
echo detached`;
await execInSandbox(exec, sandbox, detachScript, { timeoutMs: 30_000, context: `browser hop detach for ${sandbox}` });
const deadline = Date.now() + 60_000;
let seen = false;
while (Date.now() < deadline) {
const probe = await execInSandbox(
exec,
sandbox,
"grep -q xdg-open-proof /tmp/xdgtest.log 2>/dev/null && echo XDG_OPEN_WORKS || echo XDG_WAITING",
{ timeoutMs: 15_000, context: `browser hop probe for ${sandbox}` },
);
if (probe.stdout.includes("XDG_OPEN_WORKS")) {
seen = true;
break;
}
await delay(3_000);
}
await execInSandbox(
exec,
sandbox,
"pkill -f \"[h]ttp.server 18099\" >/dev/null 2>&1; pkill -f \"[c]hromium\" >/dev/null 2>&1; rm -f /tmp/xdgtest.log; true",
{ timeoutMs: 15_000, context: `browser hop cleanup for ${sandbox}` },
).catch(() => undefined);
if (!seen) {
throw new Error(`Browser hop gate failed for ${sandbox}: xdg-open never delivered a request (no browser reachable from the OAuth connect flow).`);
}
});
await timedStep(log, "first boot gate", async () => {
// A sandbox's first Electron boot pays sidecar prepare, the
// openwork-server tsc build, and the engine cold start. Paid INSIDE a
// spec, that bill starved the tool-call phase past its window while every
// UI assertion still passed. Boot once into a throwaway profile, wait for
// CDP, tear it down — after this the room behaves like a warm machine.
const detachScript = `python3 - <<PYEOF
import subprocess
log = open("/tmp/warmup-electron.log", "ab", buffering=0)
subprocess.Popen(["bash", "-lc", "cd /workspace && env OPENWORK_ELECTRON_USERDATA=/tmp/warmup-profile OPENWORK_ELECTRON_REMOTE_DEBUG_PORT=9825 bash .devcontainer/start-daytona-electron.sh"], stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, start_new_session=True, close_fds=True)
PYEOF
echo detached`;
await execInSandbox(exec, sandbox, detachScript, { timeoutMs: 30_000, context: `first boot detach for ${sandbox}` });
const deadline = Date.now() + 420_000;
let last = "not attempted";
let ready = false;
while (Date.now() < deadline) {
const probe = await execInSandbox(
exec,
sandbox,
"curl -s --max-time 5 http://127.0.0.1:9825/json/version || echo CDP_DOWN",
{ timeoutMs: 15_000, context: `first boot probe for ${sandbox}` },
).catch((error) => ({ stdout: messageText(error), stderr: "", code: 1 }));
last = probe.stdout.trim().slice(0, 200);
if (last.includes("Browser")) {
ready = true;
break;
}
await delay(5_000);
}
await execInSandbox(
exec,
sandbox,
"pkill -f \"[e]lectron\" >/dev/null 2>&1; pkill -f \"[o]pencode\" >/dev/null 2>&1; sleep 1; rm -rf /tmp/warmup-profile; true",
{ timeoutMs: 30_000, context: `first boot cleanup for ${sandbox}` },
).catch(() => undefined);
if (!ready) {
const bootLog = await execInSandbox(
exec,
sandbox,
"tail -60 /tmp/warmup-electron.log 2>&1 || true",
{ timeoutMs: 30_000, context: `first boot log for ${sandbox}` },
).catch(() => null);
throw new Error(`First boot gate failed for ${sandbox}: CDP never answered on 9825. Last probe: ${last}. Log tail:\n${bootLog ? outputTail(bootLog) : "unavailable"}`);
}
});
await timedStep(log, "Vite prewarm gate", async () => {
const detachScript = `cd /workspace; python3 - <<PYEOF
import subprocess
log = open("/tmp/vite-prewarm.log", "ab", buffering=0)
subprocess.Popen(["bash", "-lc", "cd /workspace && pnpm -w dev:ui"], stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, start_new_session=True, close_fds=True)
PYEOF
echo detached`;
await execInSandbox(exec, sandbox, detachScript, { timeoutMs: 30_000, context: `Vite prewarm detach for ${sandbox}` });
const deadline = Date.now() + 180_000;
let last = "not attempted";
while (Date.now() < deadline) {
try {
const result = await execInSandbox(
exec,
sandbox,
"curl -s -o /dev/null -w %{http_code} --max-time 5 http://localhost:5173/",
{ timeoutMs: 10_000, context: `Vite prewarm probe for ${sandbox}` },
);
last = result.stdout.trim();
if (last === "200") return;
} catch (error) {
last = messageText(error);
}
await delay(5_000);
}
const viteLog = await execInSandbox(
exec,
sandbox,
"tail -80 /tmp/vite-prewarm.log 2>&1 || true",
{ timeoutMs: 30_000, context: `Vite prewarm log for ${sandbox}` },
);
throw new Error(`Vite prewarm gate failed for ${sandbox}: last probe ${last}. Log tail:\n${outputTail(viteLog)}`);
});
return { sandbox, created: !reused };
}
interface LocalProcessResult {
output: string;
code: number;
}
interface LineWriter {
push(text: string): void;
flush(): void;
}
function lineWriter(log: (line: string) => void): LineWriter {
let pending = "";
return {
push(text) {
pending += text;
const lines = pending.split(/\r?\n/);
pending = lines.pop() ?? "";
for (const line of lines) log(line);
},
flush() {
if (pending) log(pending);
pending = "";
},
};
}
function runDenProvisionScript(ref: string, repoRoot: string, bootstrapAdminEmail: string | undefined, log: (line: string) => void, urlsFile: string): Promise<LocalProcessResult> {
return new Promise((resolve, reject) => {
const env: NodeJS.ProcessEnv = { ...process.env, OPENWORK_DEN_URLS_FILE: urlsFile };
if (bootstrapAdminEmail) env.DEN_BOOTSTRAP_ADMIN_EMAILS = bootstrapAdminEmail;
const child = spawn("bash", [".devcontainer/test-server-on-daytona.sh", ref, "--seed", "--name", serverSandboxName()], {
cwd: repoRoot,
env,
stdio: ["ignore", "pipe", "pipe"],
});
const stdoutLines = lineWriter(log);
const stderrLines = lineWriter(log);
let output = "";
let timedOut = false;
let settled = false;
const timer = globalThis.setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
}, SERVER_SCRIPT_TIMEOUT_MS);
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
const text = String(chunk);
output += text;
stdoutLines.push(text);
});
child.stderr.on("data", (chunk) => {
const text = String(chunk);
output += text;
stderrLines.push(text);
});
child.on("error", (error) => {
if (settled) return;
settled = true;
clearTimeout(timer);
reject(error);
});
child.on("close", (code) => {
if (settled) return;
settled = true;
clearTimeout(timer);
stdoutLines.flush();
stderrLines.flush();
if (timedOut) output += `\nTimed out after ${SERVER_SCRIPT_TIMEOUT_MS}ms.`;
resolve({ output, code: timedOut ? 124 : code ?? 1 });
});
});
}
function sandboxFromServerOutput(output: string): string | null {
let fallback: string | null = null;
for (const line of output.split(/\r?\n/)) {
const ready = /Server sandbox ready:\s*(\S+)/.exec(line);
if (ready?.[1]) return ready[1];
const creating = /Creating server sandbox:\s*(\S+)/.exec(line);
if (creating?.[1]) fallback = creating[1];
}
return fallback;
}
function parsedPublicUrl(value: string | undefined): string | null {
if (!value) return null;
try {
const url = new URL(value);
if (url.protocol !== "https:" || url.pathname !== "/" || url.search || url.hash || !url.hostname) return null;
return url.origin;
} catch {
return null;
}
}
function parseDenUrlsFile(content: string): { webUrl: string; apiUrl: string } | null {
const entries = new Map<string, string>();
for (const line of content.split(/\r?\n/)) {
const separator = line.indexOf("=");
if (separator <= 0) continue;
entries.set(line.slice(0, separator).trim(), line.slice(separator + 1).trim());
}
const webUrl = parsedPublicUrl(entries.get("DEN_WEB_URL"));
const apiUrl = parsedPublicUrl(entries.get("DEN_API_URL"));
return webUrl && apiUrl ? { webUrl, apiUrl } : null;
}
async function previewUrl(exec: DaytonaExec, sandbox: string, port: number): Promise<string> {
let lastError = "not attempted";
for (let attempt = 1; attempt <= 4; attempt += 1) {
try {
const result = await checkedExec(
exec,
["preview-url", sandbox, "-p", String(port), "--expires", "86400"],
`preview URL gate for ${sandbox}:${port}`,
{ timeoutMs: 60_000 },
);
const url = firstHttpsUrl(result.stdout);
if (url) return url;
lastError = `no https URL in output tail: ${outputTail(result)}`;
} catch (error) {
lastError = messageText(error);
}
if (attempt < 4) await delay(attempt * 1_000);
}
throw new Error(`Preview URL gate failed for ${sandbox}:${port} after 4 attempts: ${lastError}`);
}
async function proveDenSeed(apiUrl: string, webUrl: string, sandbox: string, reused: boolean): Promise<void> {
const email = process.env.OPENWORK_EVAL_DEMO_EMAIL?.trim() || "alex@acme.test";
const password = process.env.OPENWORK_EVAL_DEMO_PASSWORD ?? "OpenWorkDemo123!";
const url = `${apiUrl.replace(/\/+$/, "")}/api/auth/sign-in/email`;
// A freshly-booted stack was observed answering public sign-in with bare
// 403s for its first ~minute, then recovering on its own — so the window is
// generous, and a failing status keeps its body so the failure names itself.
// (That body once read MISSING_OR_NULL_ORIGIN: early boot rejects
// origin-less POSTs, and every real client sends Origin — so must we.)
const deadline = Date.now() + 120_000;
let last = "not attempted";
while (Date.now() < deadline) {
try {
const response = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json", origin: webUrl },
body: JSON.stringify({ email, password }),
signal: AbortSignal.timeout(5_000),
});
if (response.status === 200) return;
const body = await response.text().catch(() => "");
last = `HTTP ${response.status} ${body.slice(0, 300)}`.trim();
} catch (error) {
last = messageText(error);
}
await delay(2_000);
}
if (reused) {
throw new Error(`Den seed proof failed for reused sandbox ${sandbox}: the Den has no seeded org. Omit --reuse-den to provision a seeded Den sandbox. Last: ${last}`);
}
throw new Error(`Den seed proof failed for ${sandbox}: ${email} could not sign in at ${apiUrl}. Last: ${last}`);
}
export async function provisionDenSandbox(options: DenSandboxOptions & ProvisionExecOptions): Promise<DenSandbox> {
const exec = options.exec ?? defaultDaytonaExec;
const log = options.log ?? console.error;
const ref = assertSafeRef(options.ref);
const reused = options.reuse?.trim() || "";
let sandbox: string;
let webUrl: string;
let apiUrl: string;
if (reused) {
sandbox = reused;
// Reused sandboxes only get fresh signed aliases: their baked
// DEN_*_PUBLIC_URL identity is unknown here, so RFC 9728 validating MCP
// clients (opencode OAuth) cannot connect to a reused Den sandbox.
[webUrl, apiUrl] = await timedStep(log, "Den preview URL gate", () => Promise.all([
previewUrl(exec, sandbox, DEN_WEB_PORT),
previewUrl(exec, sandbox, DEN_API_PORT),
]));
} else {
// URLs come from the trusted runner-side URLs file the provisioning
// script writes from daytona CLI output, never from the script's stdout:
// the ref being provisioned controls that stream, so a spoofed
// "DEN_API_URL=https://attacker" line would receive the demo credentials
// the sign-in proof posts moments later. Re-deriving fresh preview URLs
// here is not an option either — every `daytona preview-url` call signs a
// different hostname, while the sandbox's baked DEN_*_PUBLIC_URL is the
// Den's OAuth issuer and MCP resource identity. RFC 9728 validating MCP
// clients (opencode) refuse a Den reached through a mismatched host.
const urlsDir = await mkdtemp(path.join(os.tmpdir(), "openwork-den-urls-"));
const urlsFile = path.join(urlsDir, "den-urls.env");
try {
const result = await timedStep(log, "Den provisioning script", () => runDenProvisionScript(
ref,
options.repoRoot ?? REPO_ROOT,
options.bootstrapAdminEmail,
log,
urlsFile,
));
if (result.code !== 0) {
throw new Error(`Den provisioning script gate failed with exit ${result.code}. Output tail:\n${textTail(result.output)}`);
}
const parsedSandbox = sandboxFromServerOutput(result.output);
if (!parsedSandbox) throw new Error(`Den provisioning script output is missing sandbox. Output tail:\n${textTail(result.output)}`);
sandbox = parsedSandbox;
const urls = parseDenUrlsFile(await readFile(urlsFile, "utf8").catch(() => ""));
if (!urls) {
throw new Error(
`Den provisioning script did not hand back the sandbox's public URLs through ${urlsFile}. `
+ "The baked DEN_*_PUBLIC_URL identity must be reused verbatim; check .devcontainer/test-server-on-daytona.sh.",
);
}
webUrl = urls.webUrl;
apiUrl = urls.apiUrl;
} finally {
await rm(urlsDir, { recursive: true, force: true }).catch(() => undefined);
}
}
await timedStep(log, "Den seeded-org proof", () => proveDenSeed(apiUrl, webUrl, sandbox, Boolean(reused)));
return { sandbox, apiUrl, webUrl, created: !reused };
}
export async function startMockOnSandbox(options: MockOnSandboxOptions & ProvisionExecOptions): Promise<MockOnSandbox> {
const exec = options.exec ?? defaultDaytonaExec;
const log = options.log ?? console.error;
const fetchImpl = options.fetchImpl ?? fetch;
const port = options.port ?? 3979;
const url = await timedStep(log, "mock preview URL gate", () => previewUrl(exec, options.sandbox, port));
await timedStep(log, "mock process cleanup", async () => {
await execInSandbox(
exec,
options.sandbox,
"pkill -f mock-oauth-mcp-server || true",
{ timeoutMs: 30_000, context: `mock process cleanup for ${options.sandbox}` },
).catch(() => undefined);
});
await timedStep(log, "mock process detach", async () => {
const unauthenticatedMcpEnv = options.allowUnauthenticatedMcp ? " MOCK_ALLOW_UNAUTHENTICATED_MCP=1" : "";
const detachScript = `cd /workspace; python3 - <<PYEOF
import subprocess
log = open("/tmp/mock-mcp.log", "ab", buffering=0)
subprocess.Popen(["bash", "-lc", "cd /workspace && env HOST=0.0.0.0 PORT=${port} ISSUER=${url} AUTO_APPROVE=1${unauthenticatedMcpEnv} node scripts/mock-oauth-mcp-server.mjs"], stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, start_new_session=True, close_fds=True)
PYEOF
echo detached`;
await execInSandbox(exec, options.sandbox, detachScript, { timeoutMs: 30_000, context: `mock process detach for ${options.sandbox}` });
});
await timedStep(log, "mock health gate", async () => {
const deadline = Date.now() + 60_000;
let last = "not attempted";
while (Date.now() < deadline) {
let body: unknown = null;
let responseOk = false;
try {
const response = await fetchImpl(`${url}/health`, { signal: AbortSignal.timeout(5_000) });
body = await response.json();
responseOk = response.ok;
if (!response.ok) last = `HTTP ${response.status}`;
} catch (error) {
last = messageText(error);
}
if (responseOk && isRecord(body) && body.ok === true) {
const issuer = typeof body.issuer === "string" ? body.issuer : JSON.stringify(body.issuer);
if (issuer !== url) throw new Error(`Mock issuer gate failed: health reported ${issuer}, expected ${url}.`);
return;
}
await delay(2_000);
}
const mockLog = await execInSandbox(
exec,
options.sandbox,
"tail -80 /tmp/mock-mcp.log 2>&1 || true",
{ timeoutMs: 30_000, context: `mock health log for ${options.sandbox}` },
);
throw new Error(`Mock health gate failed at ${url}. Last: ${last}. Log tail:\n${outputTail(mockLog)}`);
});
return { url };
}
export async function startFaultProxyOnSandbox(options: FaultProxyOnSandboxOptions & ProvisionExecOptions): Promise<FaultProxyOnSandbox> {
const exec = options.exec ?? defaultDaytonaExec;
const log = options.log ?? console.error;
const fetchImpl = options.fetchImpl ?? fetch;
const port = options.port ?? 3985;
const upstreamPort = options.upstreamPort ?? DEN_WEB_PORT;
const token = randomBytes(16).toString("hex");
const url = await timedStep(log, "fault proxy preview URL gate", () => previewUrl(exec, options.sandbox, port));
await timedStep(log, "fault proxy process cleanup", async () => {
await execInSandbox(
exec,
options.sandbox,
"pkill -f openwork-fault-proxy || true",
{ timeoutMs: 30_000, context: `fault proxy process cleanup for ${options.sandbox}` },
).catch(() => undefined);
});
await timedStep(log, "fault proxy script upload", async () => {
const encoded = Buffer.from(FAULT_PROXY_SCRIPT).toString("base64");
await execInSandbox(
exec,
options.sandbox,
`printf %s ${encoded} | base64 -d > /tmp/openwork-fault-proxy.mjs`,
{ timeoutMs: 30_000, context: `fault proxy script upload for ${options.sandbox}` },
);
});
await timedStep(log, "fault proxy process detach", async () => {
const detachScript = `python3 - <<PYEOF
import subprocess
log = open("/tmp/openwork-fault-proxy.log", "ab", buffering=0)
subprocess.Popen(["bash", "-lc", "env PORT=${port} UPSTREAM=http://127.0.0.1:${upstreamPort} ISSUER=${url} CONTROL_TOKEN=${token} node /tmp/openwork-fault-proxy.mjs"], stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, start_new_session=True, close_fds=True)
PYEOF
echo detached`;
await execInSandbox(exec, options.sandbox, detachScript, { timeoutMs: 30_000, context: `fault proxy process detach for ${options.sandbox}` });
});
await timedStep(log, "fault proxy health gate", async () => {
const deadline = Date.now() + 60_000;
let last = "not attempted";
while (Date.now() < deadline) {
let body: unknown = null;
let responseOk = false;
try {
const response = await fetchImpl(`${url}/__openwork_faults/health`, { signal: AbortSignal.timeout(5_000) });
body = await response.json();
responseOk = response.ok;
if (!response.ok) last = `HTTP ${response.status}`;
} catch (error) {
last = messageText(error);
}
if (responseOk && isRecord(body) && body.ok === true) {
const reportedIssuer = typeof body.issuer === "string" ? body.issuer : JSON.stringify(body.issuer);
if (reportedIssuer !== url) throw new Error(`Fault proxy issuer gate failed: health reported ${reportedIssuer}, expected ${url}.`);
return;
}
await delay(2_000);
}
const proxyLog = await execInSandbox(
exec,
options.sandbox,
"tail -80 /tmp/openwork-fault-proxy.log 2>&1 || true",
{ timeoutMs: 30_000, context: `fault proxy health log for ${options.sandbox}` },
);
throw new Error(`Fault proxy health gate failed at ${url}. Last: ${last}. Log tail:\n${outputTail(proxyLog)}`);
});
return {
url,
token,
async stop(): Promise<void> {
await execInSandbox(
exec,
options.sandbox,
"pkill -f openwork-fault-proxy.mjs || true",
{ timeoutMs: 30_000, context: `fault proxy stop for ${options.sandbox}` },
).catch(() => undefined);
},
};
}
function deletionOutput(result: DaytonaExecResult): string {
return `${result.stderr}\n${result.stdout}`.trim();
}
function deletionNotFound(text: string): boolean {
return /not found|does not exist|no sandbox/i.test(text);
}
export async function deleteSandboxes(
ids: string[],
options: ProvisionExecOptions & { log?: (line: string) => void } = {},
): Promise<void> {
const exec = options.exec ?? defaultDaytonaExec;
const log = options.log ?? console.error;
for (const id of ids) {
log(`==> deleting sandbox ${id}...`);
// The CLI has no --yes; answering its confirmation prompt on stdin works,
// and a promptless future CLI would simply ignore the input.
const result = await exec(["delete", id], { timeoutMs: 60_000, input: "y\n" });
const output = deletionOutput(result);
if (result.code !== 0 && deletionNotFound(output)) {
log(`==> sandbox ${id} not found; continuing`);
continue;
}
if (result.code !== 0) throw new Error(`Sandbox deletion gate failed for ${id} with exit ${result.code}. Output tail: ${textTail(output)}`);
log(`==> deleted sandbox ${id}`);
}
}
const ENV_HEADER_PREFIX = "# provisioned for org-connector-two-members";
const ENV_REF_MARKER = "; ref=";
const ENV_CREATED_PREFIX = "# provision-created=";
/** First line starting with prefix, minus the prefix. Linear, no backtracking. */
function commentLine(content: string, prefix: string): string | null {
for (const line of content.split(/\r?\n/)) {
if (line.startsWith(prefix)) return line.slice(prefix.length);
}
return null;
}
/** POSIX single-quoting: the generated file is `source`d, so an unquoted
* value like `$(cmd)` would execute on the operator's machine. */
function shellQuote(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
function unquote(value: string): string {
if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
return value.slice(1, -1).replaceAll(`'"'"'`, "'");
}
return value;
}
export function renderConnectorE2eTestEnv(facts: ConnectorE2eTestEnv): string {
assertSafeRef(facts.ref);
return [
`${ENV_HEADER_PREFIX} — generated ${new Date().toISOString()}${ENV_REF_MARKER}${facts.ref}`,
`${ENV_CREATED_PREFIX}${facts.created.join(",")}`,
"OPENWORK_EVAL_E2E_TESTS=1",
"OPENWORK_EVAL_CONNECTOR_E2E_TEST=1",
`OPENWORK_EVAL_DEN_API_URL=${shellQuote(facts.denApiUrl)}`,
`OPENWORK_EVAL_DEN_WEB_URL=${shellQuote(facts.denWebUrl)}`,
`OPENWORK_EVAL_DAYTONA_SANDBOX_A=${shellQuote(facts.sandboxA)}`,
`OPENWORK_EVAL_DAYTONA_SANDBOX_B=${shellQuote(facts.sandboxB)}`,
`OPENWORK_EVAL_CONNECTOR_MOCK_PUBLIC_URL=${shellQuote(facts.mockUrl)}`,
"OPENWORK_EVAL_MODEL=big-pickle",
"",
].join("\n");
}
export function parseConnectorE2eTestEnv(content: string): ConnectorE2eTestEnv {
const values = new Map<string, string>();
for (const line of content.split(/\r?\n/)) {
if (!line || line.startsWith("#")) continue;
const separator = line.indexOf("=");
if (separator > 0) values.set(line.slice(0, separator), unquote(line.slice(separator + 1)));
}
function required(name: string): string {
const value = values.get(name);
if (value === undefined || value.length === 0) throw new Error(`Missing ${name} in connector spec env.`);
return value;
}
required("OPENWORK_EVAL_E2E_TESTS");
required("OPENWORK_EVAL_CONNECTOR_E2E_TEST");
required("OPENWORK_EVAL_MODEL");
// Header comments are read by line scan, not regex: `.*` before a literal
// backtracks polynomially on adversarial input (CodeQL js/polynomial-redos).
const header = commentLine(content, ENV_HEADER_PREFIX);
const refAt = header ? header.lastIndexOf(ENV_REF_MARKER) : -1;
if (refAt < 0) throw new Error("Missing ref in connector spec env header.");
const ref = header?.slice(refAt + ENV_REF_MARKER.length) ?? "";
if (!ref) throw new Error("Missing ref in connector spec env header.");
const createdText = commentLine(content, ENV_CREATED_PREFIX);
if (createdText === null) throw new Error("Missing provision-created in connector spec env header.");
return {
denApiUrl: required("OPENWORK_EVAL_DEN_API_URL"),
denWebUrl: required("OPENWORK_EVAL_DEN_WEB_URL"),
sandboxA: required("OPENWORK_EVAL_DAYTONA_SANDBOX_A"),
sandboxB: required("OPENWORK_EVAL_DAYTONA_SANDBOX_B"),
mockUrl: required("OPENWORK_EVAL_CONNECTOR_MOCK_PUBLIC_URL"),
ref,
created: createdText.split(",").map((id) => id.trim()).filter(Boolean),
};
}
+50
View File
@@ -0,0 +1,50 @@
import { existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { createDaytonaHost } from "./daytona.ts";
import { createLocalHost } from "./local.ts";
import type { DisposableHost, Host } from "./types.ts";
const REPO_ROOT = fileURLToPath(new URL("../../../..", import.meta.url));
/** True when this process is itself running inside a Daytona sandbox. */
export function runningInsideSandbox(env: NodeJS.ProcessEnv = process.env): boolean {
if ((env.DAYTONA_SANDBOX_ID ?? "").trim().length > 0) return true;
return existsSync("/daytona-secrets") || existsSync("/daytona-artifacts");
}
export async function resolveHost(env: NodeJS.ProcessEnv = process.env): Promise<Host & AsyncDisposable> {
const sandboxId = env.OPENWORK_EVAL_DAYTONA_SANDBOX?.trim();
// The Daytona host drives the `daytona` CLI from OUTSIDE a sandbox. When the
// caller is already inside one, that indirection cannot work — spawn locally,
// which is the same machine the sandbox host would have targeted anyway.
if (sandboxId && !runningInsideSandbox(env)) {
return createDaytonaHost({ sandboxId, repoRoot: REPO_ROOT, log: () => undefined });
}
return createLocalHost({ repoRoot: REPO_ROOT, log: () => undefined });
}
/**
* PLACEMENT, stated rather than inferred.
*
* `resolveHost()` reads one ambient env var, so a whole process gets one host:
* everything local, or everything in one sandbox. A spec that needs the app and
* the browser in different places — or two desktops in two sandboxes — cannot
* say so. These factories name a placement, and `desktop({ host })` /
* `chrome({ host })` take it, so a spec declares its topology instead of
* inheriting it from the environment.
*/
export function localHost(): DisposableHost {
return createLocalHost({ repoRoot: REPO_ROOT, log: () => undefined });
}
/** A named Daytona sandbox, driven through the `daytona` CLI from outside it. */
export function daytonaSandbox(sandboxId: string): DisposableHost {
const id = sandboxId.trim();
if (!id) throw new Error("daytonaSandbox(sandboxId) requires a sandbox id.");
if (runningInsideSandbox()) {
throw new Error(
`daytonaSandbox(${JSON.stringify(id)}) cannot be used from inside a sandbox: the daytona CLI indirection has nothing to target. Use localHost() there.`,
);
}
return createDaytonaHost({ sandboxId: id, repoRoot: REPO_ROOT, log: () => undefined });
}
+60
View File
@@ -0,0 +1,60 @@
import type { SurfaceHandle, SurfaceKind } from "@openwork/cdp";
export type { SurfaceHandle, SurfaceKind } from "@openwork/cdp";
export interface ElectronSurfaceOptions {
profile?: "fresh" | "shared";
/** Exact caller-owned profile root. Hosts preserve it on surface disposal. */
profileDir?: string;
bootstrap?: {
baseUrl: string;
apiBaseUrl?: string;
requireSignin?: boolean;
};
env?: Record<string, string>;
/** Root package script used for a source Electron launch. Setting this bypasses OPENWORK_EVAL_ELECTRON_BINARY. */
devCommand?: "dev" | "dev:electron";
/** Skip host-side sidecar/helper preparation when the caller intentionally uses existing resources. */
prepareSharedResources?: boolean;
}
export interface ChromeSurfaceOptions {
profile?: "fresh" | "shared";
startUrl?: string;
headless?: boolean;
}
export interface DenServiceOptions {
orgMode?: "single_org" | "multi_org";
seed?: "acme" | "none";
}
export interface DenServiceHandle {
webUrl: string;
apiUrl: string;
orgMode: "single_org" | "multi_org";
hostKind: string;
}
export type ShareLinks = { label: string; url: string }[];
export interface Host {
kind: string;
/**
* The repo/workspace root ON THIS HOST.
*
* A spec that passes `process.cwd()` as a workspace path is only correct when
* the driver and the app share a filesystem. Drive a sandbox from a laptop and
* the app is asked to open a directory that does not exist there — observed as
* onboarding hanging on "Power your first task" with no error. Ask the host.
*/
workspaceRoot: string;
previewUrl?(port: number): Promise<string>;
spawnElectron(name: string, opts?: ElectronSurfaceOptions): Promise<SurfaceHandle>;
spawnChrome(name: string, opts?: ChromeSurfaceOptions): Promise<SurfaceHandle>;
startDen?(opts?: DenServiceOptions): Promise<DenServiceHandle>;
share?(): Promise<ShareLinks>;
disposeSurface(handle: SurfaceHandle): Promise<void>;
}
export type DisposableHost = Host & AsyncDisposable & { stop(): Promise<void> };
@@ -0,0 +1,490 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { createServer } from "node:http";
import test from "node:test";
import { fileURLToPath } from "node:url";
import {
checkedExec,
createDaytonaHost,
defaultDaytonaExec,
enterpriseTlsEdgeDaytonaCommands,
MAX_ENTERPRISE_TLS_DAYTONA_COMMAND_LENGTH,
} from "../src/daytona.ts";
import type { DaytonaExec } from "../src/daytona.ts";
import type { SurfaceHandle } from "../src/types.ts";
import type { Server } from "node:http";
interface ExecCall {
args: string[];
opts?: { input?: string; timeoutMs?: number };
}
function portFromServer(server: Server): number {
const address = server.address();
if (typeof address === "object" && address !== null) return address.port;
throw new Error("Test server did not expose a TCP port.");
}
function closeServer(server: Server): Promise<void> {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error) reject(error);
else resolve();
});
});
}
function listen(server: Server): Promise<number> {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", () => resolve(portFromServer(server)));
});
}
async function startRuntimeConfigStub(orgMode: "single_org" | "multi_org"): Promise<{ url: string; close(): Promise<void> }> {
const server = createServer((request, response) => {
if (request.url === "/api/runtime-config") {
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({ orgMode }));
return;
}
response.writeHead(404);
response.end("not found");
});
const port = await listen(server);
return {
url: `http://127.0.0.1:${port}`,
close: () => closeServer(server),
};
}
function argsText(call: ExecCall): string {
return call.args.join(" ");
}
function findCall(calls: ExecCall[], text: string): ExecCall {
const call = calls.find((entry) => argsText(entry).includes(text));
assert(call, `Expected exec call containing ${text}`);
return call;
}
function createFakeExec(previewUrlForPort: (port: string) => string): { exec: DaytonaExec; calls: ExecCall[] } {
const calls: ExecCall[] = [];
const exec: DaytonaExec = async (args, opts) => {
calls.push({ args: [...args], opts });
if (args[0] === "preview-url") {
const portFlag = args.indexOf("-p");
const port = portFlag >= 0 ? args[portFlag + 1] ?? "" : "";
return { stdout: `time=ignored\nPreview URL: ${previewUrlForPort(port)}\n`, stderr: "", code: 0 };
}
return { stdout: "", stderr: "", code: 0 };
};
return { exec, calls };
}
function successfulPolls(seen: string[] = []): (url: string) => Promise<void> {
return async (url) => {
seen.push(url);
};
}
function base64AfterEcho(call: ExecCall): string {
const echoIndex = call.args.indexOf("echo");
assert(echoIndex >= 0, "Expected echo in base64 write call.");
return call.args[echoIndex + 1] ?? "";
}
test("checkedExec retries when the Daytona CLI fails at the transport layer", async () => {
let callCount = 0;
const exec: DaytonaExec = async () => {
callCount += 1;
if (callCount < 3) {
return {
stdout: "",
stderr: `time="2026-08-18T19:35:11Z" level=fatal msg="invalid character '<' looking for beginning of value"\n`,
code: 1,
};
}
return { stdout: "done\n", stderr: "", code: 0 };
};
const result = await checkedExec(exec, ["exec"], "write Daytona Electron bootstrap", { retryDelayMs: 1 });
assert.equal(result.stdout, "done\n");
assert.equal(callCount, 3);
});
test("checkedExec does not retry remote command failures", async () => {
let callCount = 0;
const exec: DaytonaExec = async () => {
callCount += 1;
return { stdout: "", stderr: "rm: cannot remove '/tmp/gone'\n", code: 1 };
};
await assert.rejects(checkedExec(exec, ["exec"], "cleanup profile", { retryDelayMs: 1 }));
assert.equal(callCount, 1);
});
test("checkedExec reports stderr and stdout from a failed Daytona command", async () => {
const exec: DaytonaExec = async () => ({
stdout: "remote process log\n",
stderr: "Daytona version warning\n",
code: 1,
});
await assert.rejects(
checkedExec(exec, ["exec"], "Start enterprise TLS edge"),
(error: Error) => {
assert.equal(
error.message,
"Start enterprise TLS edge failed with exit 1:\nstderr:\nDaytona version warning\n\nstdout:\nremote process log",
);
return true;
},
);
});
test("defaultDaytonaExec handles stdin EPIPE when the child exits", async () => {
const result = await defaultDaytonaExec(
["-e", "process.stderr.write('remote exited\\n'); process.exit(23)"],
{ input: "x".repeat(8 * 1024 * 1024) },
process.execPath,
);
assert.equal(result.code, 23);
assert.equal(result.stderr, "remote exited\n");
});
test("Daytona previewUrl parses the first https URL and caches by port", async () => {
const { exec, calls } = createFakeExec(() => "https://9825-preview.example.test/json/list");
const host = createDaytonaHost({ sandboxId: "openwork-test-1", log: () => undefined, exec, repoRoot: "/repo" });
assert.equal(await host.previewUrl(9825), "https://9825-preview.example.test/json/list");
assert.equal(await host.previewUrl(9825), "https://9825-preview.example.test/json/list");
assert.equal(calls.filter((call) => call.args[0] === "preview-url").length, 1);
assert.deepEqual(calls[0]?.args, ["preview-url", "openwork-test-1", "-p", "9825"]);
});
test("spawnElectron starts isolated Daytona Electron profiles and writes bootstrap over base64", async () => {
const polled: string[] = [];
const { exec, calls } = createFakeExec((port) => `https://cdp-${port}.example.test`);
const host = createDaytonaHost({
sandboxId: "openwork-test-electron",
log: () => undefined,
exec,
repoRoot: "/repo",
waitForCdp: successfulPolls(polled),
});
const bootstrap = { baseUrl: "https://den-web.example.test", apiBaseUrl: "https://den-api.example.test", requireSignin: true };
const first = await host.spawnElectron("owner", { bootstrap });
const second = await host.spawnElectron("member");
assert.equal(first.meta?.cdpPort, "9825");
assert.equal(second.meta?.cdpPort, "9830");
assert.match(first.profileDir ?? "", /\/workspace\/\.openwork-daytona\/profiles\/owner-\d{17}$/);
assert.match(second.profileDir ?? "", /\/workspace\/\.openwork-daytona\/profiles\/member-\d{17}$/);
assert.equal(first.meta?.profileOwner, "host");
assert.equal(second.meta?.profileOwner, "host");
assert.deepEqual(polled, ["https://cdp-9825.example.test/json/list", "https://cdp-9830.example.test/json/list"]);
const bootstrapCall = findCall(calls, "base64 -d");
assert.equal(Buffer.from(base64AfterEcho(bootstrapCall), "base64").toString("utf8"), `${JSON.stringify(bootstrap, null, 2)}\n`);
assert(argsText(bootstrapCall).includes("/workspace/.openwork-daytona/profiles/owner-"));
assert(argsText(bootstrapCall).includes("/bootstrap.json"));
const startCalls = calls.filter((call) => argsText(call).includes("/workspace/.devcontainer/start-daytona-electron.sh"));
assert.equal(startCalls.length, 2);
const firstStart = argsText(startCalls[0]);
const secondStart = argsText(startCalls[1]);
assert(firstStart.includes("openwork-test-electron"));
assert(firstStart.includes("OPENWORK_ELECTRON_REMOTE_DEBUG_PORT="));
assert(firstStart.includes("9825"));
assert(firstStart.includes("OPENWORK_ELECTRON_USERDATA="));
assert(firstStart.includes("/workspace/.openwork-daytona/profiles/owner-"));
assert(firstStart.includes("/electron-userdata"));
assert(firstStart.includes("OPENWORK_DESKTOP_BOOTSTRAP_PATH="));
assert(firstStart.includes("/workspace/.openwork-daytona/profiles/owner-"));
assert(firstStart.includes("/bootstrap.json"));
assert(firstStart.includes("DAYTONA_ELECTRON_LOG="));
assert(/\/tmp\/electron-owner-\d+/.test(firstStart));
assert(firstStart.includes("--detach"));
assert(secondStart.includes("OPENWORK_ELECTRON_REMOTE_DEBUG_PORT="));
assert(secondStart.includes("9830"));
assert(secondStart.includes("OPENWORK_ELECTRON_USERDATA="));
assert(secondStart.includes("/workspace/.openwork-daytona/profiles/member-"));
assert(secondStart.includes("/electron-userdata"));
});
test("spawnElectron preserves a caller-owned Daytona profile while generated profiles are removed", async () => {
const { exec, calls } = createFakeExec((port) => `https://cdp-${port}.example.test`);
const host = createDaytonaHost({
sandboxId: "openwork-test-profile-owner",
log: () => undefined,
exec,
repoRoot: "/repo",
waitForCdp: successfulPolls(),
});
const suppliedProfile = "/tmp/reliable-recovery-profile";
const callerOwned = await host.spawnElectron("caller-owned", { profileDir: suppliedProfile });
const generated = await host.spawnElectron("generated");
assert.equal(callerOwned.profileDir, suppliedProfile);
assert.equal(callerOwned.meta?.profileOwner, "caller");
assert.equal(generated.meta?.profileOwner, "host");
const startCalls = calls.filter((call) => argsText(call).includes("/workspace/.devcontainer/start-daytona-electron.sh"));
assert(argsText(startCalls[0] ?? { args: [] }).includes(`${suppliedProfile}/electron-userdata`));
await host.disposeSurface(callerOwned);
await host.disposeSurface(generated);
const removalCalls = calls.filter((call) => argsText(call).includes("rm -rf"));
assert.equal(removalCalls.length, 1);
assert(argsText(removalCalls[0] ?? { args: [] }).includes(generated.profileDir ?? "missing-generated-profile"));
assert(!removalCalls.some((call) => argsText(call).includes(suppliedProfile)));
});
test("spawnElectron skips reserved Daytona CDP ports", async () => {
const { exec } = createFakeExec((port) => `https://cdp-${port}.example.test`);
const host = createDaytonaHost({
sandboxId: "openwork-test-electron-reserved",
log: () => undefined,
exec,
repoRoot: "/repo",
reservedElectronPorts: [9825],
waitForCdp: successfulPolls(),
});
const handle = await host.spawnElectron("fresh");
assert.equal(handle.meta?.cdpPort, "9830");
});
test("spawnChrome launches Chromium with Daytona CDP flags and allocates a second port", async () => {
const polled: string[] = [];
const { exec, calls } = createFakeExec((port) => `https://chrome-${port}.example.test`);
const host = createDaytonaHost({
sandboxId: "openwork-test-chrome",
log: () => undefined,
exec,
repoRoot: "/repo",
waitForCdp: successfulPolls(polled),
});
const first = await host.spawnChrome("browser");
const second = await host.spawnChrome("oauth", { startUrl: "https://app.example.test" });
assert.equal(first.meta?.cdpPort, "9222");
assert.equal(second.meta?.cdpPort, "9230");
assert.deepEqual(polled, ["https://chrome-9222.example.test/json/version", "https://chrome-9230.example.test/json/version"]);
const launchCalls = calls.filter((call) => argsText(call).includes("nohup \"$CHROME_BIN\""));
assert.equal(launchCalls.length, 2);
const firstLaunch = argsText(launchCalls[0]);
const secondLaunch = argsText(launchCalls[1]);
assert(firstLaunch.includes("command -v chromium || command -v google-chrome || command -v google-chrome-stable"));
assert(firstLaunch.includes("--headless=new"));
assert(firstLaunch.includes("--window-size=1280,900"));
assert(firstLaunch.includes("--no-sandbox"));
assert(firstLaunch.includes("--disable-dev-shm-usage"));
assert(firstLaunch.includes("--use-gl=swiftshader"));
assert(firstLaunch.includes("--remote-debugging-address=0.0.0.0"));
assert(firstLaunch.includes("--remote-debugging-port=9222"));
assert(firstLaunch.includes("--user-data-dir="));
assert(firstLaunch.includes("/tmp/daytona-chrome-browser"));
assert(secondLaunch.includes("--remote-debugging-port=9230"));
assert(/https:\/\/app\.example\.test(["'\s]|$)/.test(secondLaunch));
});
test("disposeSurface uses self-match-safe pkill patterns in separate execs", async () => {
const { exec, calls } = createFakeExec(() => "https://unused.example.test");
const host = createDaytonaHost({ sandboxId: "openwork-test-dispose", log: () => undefined, exec, repoRoot: "/repo" });
const electronHandle: SurfaceHandle = {
name: "desktop",
kind: "electron",
hostKind: "daytona",
cdpUrl: "https://unused.example.test",
sandboxId: "openwork-test-dispose",
profileDir: "/workspace/.openwork-daytona/profiles/desktop",
meta: { cdpPort: "9825", log: "/tmp/electron-desktop.log" },
};
const chromeHandle: SurfaceHandle = {
name: "browser",
kind: "chrome",
hostKind: "daytona",
cdpUrl: "https://unused.example.test",
sandboxId: "openwork-test-dispose",
profileDir: "/tmp/daytona-chrome-browser",
meta: { cdpPort: "9222", log: "/tmp/daytona-chrome-browser.log" },
};
await host.disposeSurface(electronHandle);
await host.disposeSurface(chromeHandle);
const electronPkillIndex = calls.findIndex((call) => argsText(call).includes("pkill -f") && argsText(call).includes("remote-debugging-port"));
const electronVerifyIndex = calls.findIndex((call) => argsText(call).includes("/json/list"));
assert(electronPkillIndex >= 0);
assert.equal(electronVerifyIndex, electronPkillIndex + 1);
const electronPkill = argsText(calls[electronPkillIndex]);
assert(electronPkill.includes("[e]lectron.*remote-debugging-port=[9]825"));
assert(!electronPkill.includes("remote-debugging-port=9825"));
const chromePkill = argsText(findCall(calls, "[/]tmp/daytona-chrome-browser"));
assert(chromePkill.includes("(chromium|chrome).*--user-data-dir=[/]tmp/daytona-chrome-browser"));
assert(!chromePkill.includes("--user-data-dir=/tmp/daytona-chrome-browser"));
});
test("Daytona host requires a sandbox option or OPENWORK_EVAL_DAYTONA_SANDBOX", async () => {
const previous = process.env.OPENWORK_EVAL_DAYTONA_SANDBOX;
delete process.env.OPENWORK_EVAL_DAYTONA_SANDBOX;
const { exec } = createFakeExec(() => "https://unused.example.test");
const host = createDaytonaHost({ log: () => undefined, exec, repoRoot: "/repo" });
try {
await assert.rejects(host.previewUrl(9825), /create one with bash \.devcontainer\/test-on-daytona\.sh <ref> or pass sandboxId/);
} finally {
if (previous === undefined) delete process.env.OPENWORK_EVAL_DAYTONA_SANDBOX;
else process.env.OPENWORK_EVAL_DAYTONA_SANDBOX = previous;
}
});
test("enterprise TLS edge commands keep the full lifecycle in one Daytona sandbox", () => {
const commands = enterpriseTlsEdgeDaytonaCommands({
sandboxId: "desktop-sandbox",
upstream: "https://den.example.test",
});
assert.equal(commands.candidateUrl, "https://localhost:8443");
assert.equal(commands.negativeUrl, "https://localhost:9443");
assert.equal(commands.adminUrl, "http://127.0.0.1:8445");
for (const command of [commands.start, commands.probe, commands.requests, commands.installRoot, commands.removeRoot, commands.stop]) {
assert.deepEqual(command.slice(0, 3), ["exec", "desktop-sandbox", "--"]);
}
const runtimeRoot = "/tmp/openwork-enterprise-tls-runtime";
const localSources = [
fileURLToPath(new URL("../../../scripts/enterprise-tls-edge.mts", import.meta.url)),
fileURLToPath(new URL("../../labs/src/egress.ts", import.meta.url)),
];
assert.ok(commands.prepare.length > localSources.length * 2);
const prepare = commands.prepare.map((command) => command.join(" "));
assert.ok(prepare[0]?.includes(`/usr/bin/rm -rf ${runtimeRoot}`));
assert.ok(prepare[0]?.includes("/usr/bin/mkdir -p"));
assert.ok(prepare[0]?.includes("enterprise-tls-edge.mts.b64"));
assert.ok(prepare[0]?.includes("egress.ts.b64"));
assert.ok(commands.prepare.every((command) => command.slice(0, 3).join(" ") === "exec desktop-sandbox --"));
assert.ok(commands.prepare.every((command) => !command.includes("bash -s")));
assert.ok(commands.prepare.every((command) => !command.includes("sudo -n")));
const commandLengths = [
...commands.prepare,
commands.start,
commands.probe,
commands.requests,
commands.installRoot,
commands.removeRoot,
commands.stop,
].map((command) => command.join(" ").length);
assert.ok(Math.max(...commandLengths) <= MAX_ENTERPRISE_TLS_DAYTONA_COMMAND_LENGTH);
const chunkPattern = /\/usr\/bin\/printf %s ([A-Za-z0-9+/=]+) >> (\S+\.b64)/;
const chunksByRemote = new Map<string, string[]>();
for (const command of prepare) {
const match = chunkPattern.exec(command);
if (!match) continue;
assert.ok((match[1]?.length ?? 0) <= 8 * 1024);
const chunks = chunksByRemote.get(match[2] ?? "") ?? [];
chunks.push(match[1] ?? "");
chunksByRemote.set(match[2] ?? "", chunks);
}
for (const [index, source] of localSources.entries()) {
const content = readFileSync(source);
const remote = index === 0
? `${runtimeRoot}/evals/scripts/enterprise-tls-edge.mts`
: `${runtimeRoot}/evals/packages/labs/src/egress.ts`;
assert.equal(chunksByRemote.get(`${remote}.b64`)?.join(""), content.toString("base64"));
const finalize = prepare.find((command) => command.includes(`/usr/bin/base64 -d ${remote}.b64`));
assert.ok(finalize?.includes(`/usr/bin/wc -c < ${remote}`));
assert.ok(finalize?.includes(`/usr/bin/rm -f ${remote}.b64`));
assert.ok(finalize?.includes(`test \"$actual_bytes\" -eq ${content.byteLength}`));
}
const start = commands.start[3] ?? "";
assert.match(start, /\/tmp\/openwork-enterprise-tls-runtime\/evals\/scripts\/enterprise-tls-edge\.mts/);
assert.ok(!start.includes("/workspace/evals/scripts/enterprise-tls-edge.mts"));
assert.ok(!start.includes("&;"));
assert.ok(start.includes("</dev/null &\nattempt=0\nuntil /usr/bin/curl"));
assert.ok(start.includes("/usr/bin/tail -c 4000"));
assert.ok(start.includes(">&2"));
const tokenMatch = /ENTERPRISE_TLS_ADMIN_TOKEN=([a-f0-9]{32,}) nohup/.exec(start);
assert.ok(tokenMatch);
const adminToken = tokenMatch[1];
assert.match(adminToken, /^[a-f0-9]{32,}$/);
const serveArgv = start.slice(start.indexOf("enterprise-tls-edge.mts"), start.indexOf(" >"));
assert.ok(!serveArgv.includes(adminToken));
const authorizationHeader = `-H "Authorization: Bearer ${adminToken}"`;
assert.ok(start.includes(authorizationHeader));
assert.ok(start.includes("http://127.0.0.1:8445/health"));
assert.ok((commands.probe[3] ?? "").includes(authorizationHeader));
assert.ok((commands.probe[3] ?? "").includes("http://127.0.0.1:8445/health"));
assert.ok((commands.requests[3] ?? "").includes(authorizationHeader));
assert.ok((commands.requests[3] ?? "").includes("http://127.0.0.1:8445/requests"));
const installRoot = commands.installRoot[3] ?? "";
const removeRoot = commands.removeRoot[3] ?? "";
assert.ok(installRoot.includes("install"));
assert.ok(removeRoot.includes("remove"));
for (const command of [installRoot, removeRoot]) {
assert.ok(command.includes("node_path=$(command -v node)"));
assert.ok(command.includes('test -n "$node_path"'));
assert.ok(command.includes('/usr/bin/sudo -n "$node_path"'));
assert.ok(!command.includes("/usr/bin/sudo -n '/usr/bin/env' 'node'"));
}
for (const command of [commands.start, commands.probe, commands.requests, commands.stop]) {
assert.ok(!command[3]?.includes("sudo -n"));
}
const stop = commands.stop[3] ?? "";
for (const command of [installRoot, removeRoot, stop]) {
assert.ok(!command.includes("ENTERPRISE_TLS_ADMIN_TOKEN"));
assert.ok(!command.includes("Authorization: Bearer"));
assert.ok(!command.includes(adminToken));
}
assert.ok(stop.includes("stop"));
assert.ok(stop.includes("&& /usr/bin/rm -rf"));
assert.ok(stop.indexOf("stop") < stop.indexOf("/usr/bin/rm -rf"));
assert.ok(stop.includes(runtimeRoot));
});
test("enterprise TLS edge commands reject steering and port collisions", () => {
assert.throws(
() => enterpriseTlsEdgeDaytonaCommands({ sandboxId: "sandbox", upstream: "https://den.example.test/path" }),
/HTTP\(S\) origin/,
);
assert.throws(
() => enterpriseTlsEdgeDaytonaCommands({ sandboxId: "sandbox", upstream: "https://den.example.test", adminPort: 8443 }),
/must be distinct/,
);
});
test("startDen attaches to preset Den env without running daytona exec", async () => {
const server = await startRuntimeConfigStub("single_org");
const previousApi = process.env.OPENWORK_EVAL_DEN_API_URL;
const previousWeb = process.env.OPENWORK_EVAL_DEN_WEB_URL;
const { exec, calls } = createFakeExec(() => "https://unused.example.test");
const host = createDaytonaHost({ sandboxId: "openwork-test-den", log: () => undefined, exec, repoRoot: "/repo" });
try {
process.env.OPENWORK_EVAL_DEN_API_URL = "https://den-api.example.test";
process.env.OPENWORK_EVAL_DEN_WEB_URL = server.url;
const handle = await host.startDen();
assert.equal(handle.webUrl, server.url);
assert.equal(handle.apiUrl, "https://den-api.example.test");
assert.equal(handle.orgMode, "single_org");
assert.equal(handle.hostKind, "daytona");
assert.equal(calls.length, 0);
} finally {
if (previousApi === undefined) delete process.env.OPENWORK_EVAL_DEN_API_URL;
else process.env.OPENWORK_EVAL_DEN_API_URL = previousApi;
if (previousWeb === undefined) delete process.env.OPENWORK_EVAL_DEN_WEB_URL;
else process.env.OPENWORK_EVAL_DEN_WEB_URL = previousWeb;
await server.close();
}
});
@@ -0,0 +1,77 @@
import assert from "node:assert/strict";
import {
access,
mkdir,
mkdtemp,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import {
clearDenWebBuildCache,
denEvalEnvironment,
denSeedNodeArgs,
nativeMysqlServerArgs,
nativeMysqlSocketArgs,
} from "../src/den-stack.ts";
test("native MariaDB explicitly permits root-owned Daytona runtimes", () => {
assert(nativeMysqlServerArgs(true).includes("--user=root"));
assert(!nativeMysqlServerArgs(false).includes("--user=root"));
});
test("native MariaDB supports fresh and resumed socket authentication", () => {
assert(!nativeMysqlSocketArgs(false).some((arg) => arg.startsWith("-p")));
assert(nativeMysqlSocketArgs(true).includes("-ppassword"));
});
test("demo-org seed resolves development exports without package builds", () => {
assert.deepEqual(denSeedNodeArgs(), [
"--conditions=development",
"--import",
"tsx",
"scripts/seed-demo-org.ts",
]);
});
test("Den evaluator explicitly permits the isolated demo signup", () => {
assert.equal(
denEvalEnvironment().DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP,
"true",
);
});
test("Den evaluator threads orgMode only when requested", () => {
const defaultEnv = denEvalEnvironment();
const multiOrgEnv = denEvalEnvironment({ orgMode: "multi_org" });
assert.equal(defaultEnv.DEN_ORG_MODE, undefined);
assert.equal(multiOrgEnv.DEN_ORG_MODE, "multi_org");
assert.equal(multiOrgEnv.PORT, defaultEnv.PORT);
});
test("Den evaluator trusts both loopback spellings used by the proof browser", () => {
const trustedOrigins = denEvalEnvironment()
.DEN_BETTER_AUTH_TRUSTED_ORIGINS?.split(",");
assert(trustedOrigins?.includes("http://localhost:3005"));
assert(trustedOrigins?.includes("http://127.0.0.1:3005"));
});
test("Den Web startup discards generated output from a reusable image", async () => {
const denWebRoot = await mkdtemp(join(tmpdir(), "openwork-den-web-"));
const staleManifest = join(denWebRoot, ".next", "server", "app-paths-manifest.json");
try {
await mkdir(join(denWebRoot, ".next", "server"), { recursive: true });
await writeFile(staleManifest, "{}");
await clearDenWebBuildCache(denWebRoot);
await assert.rejects(access(staleManifest));
} finally {
await rm(denWebRoot, { recursive: true, force: true });
}
});
@@ -0,0 +1,129 @@
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { createServer } from "node:http";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { FAULT_PROXY_SCRIPT } from "../src/fault-proxy-script.ts";
import type { Server } from "node:http";
function listen(server: Server): Promise<number> {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", () => {
const address = server.address();
if (typeof address === "object" && address !== null) resolve(address.port);
else reject(new Error("Test server did not expose a port."));
});
});
}
function close(server: Server): Promise<void> {
return new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
async function freePort(): Promise<number> {
const server = createServer();
const port = await listen(server);
await close(server);
return port;
}
test("fault proxy script exposes authenticated controls and preserves local fault semantics", async () => {
const upstream = createServer((request, response) => {
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({ path: request.url }));
});
const upstreamPort = await listen(upstream);
const proxyPort = await freePort();
const directory = await mkdtemp(join(tmpdir(), "openwork-fault-proxy-"));
const scriptPath = join(directory, "proxy.mjs");
await writeFile(scriptPath, FAULT_PROXY_SCRIPT);
const issuer = "https://fault-proxy.example.test";
const token = "test-control-token";
const child = spawn(process.execPath, [scriptPath], {
env: {
...process.env,
PORT: String(proxyPort),
UPSTREAM: `http://127.0.0.1:${upstreamPort}`,
ISSUER: issuer,
CONTROL_TOKEN: token,
},
stdio: "ignore",
});
const url = `http://127.0.0.1:${proxyPort}`;
const auth = { "x-openwork-fault-token": token };
const post = (path: string, body?: unknown): Promise<Response> => fetch(`${url}${path}`, {
method: "POST",
headers: body === undefined ? auth : { ...auth, "content-type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
});
try {
const deadline = Date.now() + 5_000;
let health: Response | undefined;
while (Date.now() < deadline) {
try {
health = await fetch(`${url}/__openwork_faults/health`);
if (health.ok) break;
} catch {
await new Promise((resolve) => setTimeout(resolve, 25));
}
}
assert(health?.ok, "fault proxy health did not become ready");
assert.deepEqual(await health.json(), { ok: true, issuer });
assert.equal((await fetch(`${url}/__openwork_faults/requests`)).status, 401);
assert.equal((await fetch(`${url}/__openwork_faults/clear`, { method: "POST" })).status, 401);
assert.equal((await post("/__openwork_faults/rules", {
kind: "status",
pathPrefix: "/flaky",
statusCode: 429,
times: 2,
body: { error: "injected" },
})).status, 204);
assert.equal((await fetch(`${url}/flaky/one`)).status, 429);
assert.equal((await fetch(`${url}/flaky/two`)).status, 429);
assert.equal((await fetch(`${url}/flaky/three`)).status, 200);
assert.equal((await post("/__openwork_faults/rules", {
kind: "latency",
pathPrefix: "/slow",
delayMs: 30,
})).status, 204);
const startedAt = Date.now();
assert.equal((await fetch(`${url}/slow`)).status, 200);
assert(Date.now() - startedAt >= 20);
assert.equal((await post("/__openwork_faults/rules", {
kind: "status",
pathPrefix: "/cleared",
statusCode: 500,
times: 3,
})).status, 204);
assert.equal((await post("/__openwork_faults/clear")).status, 204);
assert.equal((await fetch(`${url}/cleared`)).status, 200);
const logResponse = await fetch(`${url}/__openwork_faults/requests`, { headers: auth });
assert.equal(logResponse.status, 200);
const log: unknown = await logResponse.json();
assert.deepEqual(
typeof log === "object" && log !== null && "requests" in log && Array.isArray(log.requests)
? log.requests.map((entry) => ({ path: entry.path, status: entry.status, faulted: entry.faulted }))
: log,
[
{ path: "/flaky/one", status: 429, faulted: true },
{ path: "/flaky/two", status: 429, faulted: true },
{ path: "/flaky/three", status: 200, faulted: false },
{ path: "/slow", status: 200, faulted: true },
{ path: "/cleared", status: 200, faulted: false },
],
);
} finally {
child.kill("SIGTERM");
await close(upstream);
await rm(directory, { recursive: true, force: true });
}
});
@@ -0,0 +1,177 @@
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { access, chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { createServer } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { allocateFreePort } from "@openwork/cdp";
import { electronProfilePaths, electronSurfaceEnv, freePort, resolveChromeBinary, stopOwnedElectronSurface } from "../src/local.ts";
const ENV_KEYS = [
"APPDATA",
"HOME",
"LOCALAPPDATA",
"OPENCODE_CONFIG_DIR",
"OPENWORK_DATA_DIR",
"OPENWORK_DESKTOP_BOOTSTRAP_PATH",
"OPENWORK_DESKTOP_DISABLE_WORKSPACE_RECOVERY",
"OPENWORK_DEV_MODE",
"OPENWORK_ELECTRON_APP_IDENTIFIER",
"OPENWORK_ELECTRON_APP_NAME",
"OPENWORK_ELECTRON_DISABLE_PROTOCOL_REGISTRATION",
"OPENWORK_ELECTRON_REMOTE_DEBUG_PORT",
"OPENWORK_ELECTRON_SKIP_SHARED_PREPARE",
"OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN",
"OPENWORK_ELECTRON_USERDATA",
"OPENWORK_ENV_STORE",
"PORT",
"VITE_DISABLE_OPENWORK_MODELS",
"XDG_CACHE_HOME",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
"XDG_STATE_HOME",
].sort();
test("electronProfilePaths returns all expected paths under the profile root", () => {
const root = join(tmpdir(), "openwork-local-host-profile");
const paths = electronProfilePaths(root);
assert.deepEqual(Object.keys(paths).sort(), [
"appDataDir",
"bootstrapPath",
"cacheHome",
"configHome",
"dataDir",
"dataHome",
"envStorePath",
"homeDir",
"localAppDataDir",
"opencodeConfigDir",
"root",
"stateHome",
"userDataDir",
].sort());
for (const value of Object.values(paths)) {
assert(value === root || value.startsWith(`${root}/`), `${value} should be under ${root}`);
}
assert.equal(paths.userDataDir, join(root, "electron-userdata"));
assert.equal(paths.bootstrapPath, join(root, "bootstrap.json"));
});
test("electronSurfaceEnv matches the isolated Electron demo contract", () => {
const root = join(tmpdir(), "openwork-local-host-env");
const paths = electronProfilePaths(root);
const env = electronSurfaceEnv(paths, {
appName: "OpenWork Eval probe",
appIdentifier: "com.differentai.openwork.eval.probe",
port: 5123,
cdpPort: 9123,
});
assert.deepEqual(Object.keys(env).filter((key) => key !== "PNPM_HOME").sort(), ENV_KEYS);
// The one deliberate hole in the isolation: pnpm's version redirection must
// stay warm, or every spawn re-downloads the pinned pnpm from the network.
if (process.platform === "darwin" || process.platform === "linux") {
assert(env.PNPM_HOME, "PNPM_HOME should point at the host's pnpm home");
assert(!env.PNPM_HOME.startsWith(root), "PNPM_HOME must not be inside the isolated profile");
}
assert.equal(env.APPDATA, paths.appDataDir);
assert.equal(env.HOME, paths.homeDir);
assert.equal(env.LOCALAPPDATA, paths.localAppDataDir);
assert.equal(env.OPENWORK_DATA_DIR, paths.dataDir);
assert.equal(env.OPENWORK_DESKTOP_BOOTSTRAP_PATH, paths.bootstrapPath);
assert.equal(env.OPENWORK_ENV_STORE, paths.envStorePath);
assert.equal(env.OPENCODE_CONFIG_DIR, paths.opencodeConfigDir);
assert.equal(env.OPENWORK_ELECTRON_USERDATA, paths.userDataDir);
assert.equal(env.PORT, "5123");
assert.equal(env.OPENWORK_ELECTRON_REMOTE_DEBUG_PORT, "9123");
assert.equal(env.OPENWORK_ELECTRON_APP_NAME, "OpenWork Eval probe");
assert.equal(env.OPENWORK_ELECTRON_APP_IDENTIFIER, "com.differentai.openwork.eval.probe");
assert.equal(env.OPENWORK_ELECTRON_SKIP_SHARED_PREPARE, "1");
assert.equal(env.OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN, "1");
assert.equal(env.XDG_CACHE_HOME, paths.cacheHome);
assert.equal(env.XDG_CONFIG_HOME, paths.configHome);
assert.equal(env.XDG_DATA_HOME, paths.dataHome);
assert.equal(env.XDG_STATE_HOME, paths.stateHome);
});
test("stopOwnedElectronSurface verifies profile ownership before removing it", async () => {
const profileDir = await mkdtemp(join(tmpdir(), "openwork-owned-electron-"));
const userDataDir = join(profileDir, "electron-userdata");
await mkdir(userDataDir, { recursive: true });
const child = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
detached: true,
env: { ...process.env, OPENWORK_ELECTRON_USERDATA: userDataDir },
stdio: "ignore",
});
child.unref();
if (!child.pid) throw new Error("Owned Electron fixture did not start.");
await new Promise((resolve) => setTimeout(resolve, 100));
await stopOwnedElectronSurface(child.pid, profileDir);
await assert.rejects(access(profileDir));
});
test("resolveChromeBinary honors CHROME_BIN before platform defaults", () => {
assert.equal(resolveChromeBinary({ CHROME_BIN: "/custom/chrome" }, "linux"), "/custom/chrome");
assert.equal(resolveChromeBinary({ CHROME_BIN: "C:\\Chrome.exe" }, "win32"), "C:\\Chrome.exe");
});
test("resolveChromeBinary returns the macOS default path", () => {
assert.equal(resolveChromeBinary({}, "darwin"), "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
});
test("resolveChromeBinary finds Linux Chrome on PATH and reports a helpful error otherwise", async () => {
const binDir = await mkdtemp(join(tmpdir(), "openwork-chrome-bin-"));
const chromePath = join(binDir, "google-chrome");
try {
await writeFile(chromePath, "#!/bin/sh\nexit 0\n", "utf8");
await chmod(chromePath, 0o755);
assert.equal(resolveChromeBinary({ PATH: binDir }, "linux"), chromePath);
assert.throws(
() => resolveChromeBinary({ PATH: "" }, "linux"),
/Could not resolve Chrome binary on linux.*CHROME_BIN/,
);
assert.throws(
() => resolveChromeBinary({}, "freebsd"),
/unsupported platform freebsd/,
);
} finally {
await rm(binDir, { recursive: true, force: true });
}
});
test("freePort kills a real child listener and releases its port", {
skip: process.platform !== "darwin" && process.platform !== "linux",
}, async () => {
const port = await allocateFreePort();
const child = spawn(process.execPath, [
"-e",
"require('node:net').createServer().listen(Number(process.argv[1]), '127.0.0.1', () => process.stdout.write('ready\\n'))",
String(port),
], { detached: true, stdio: ["ignore", "pipe", "inherit"] });
const logs: string[] = [];
try {
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`Listener child did not bind port ${port}.`)), 5_000);
child.once("error", reject);
child.stdout?.once("data", () => {
clearTimeout(timer);
resolve();
});
});
await freePort(port, { log: (message) => logs.push(message) });
assert(logs.some((message) => message.includes(`Port ${port}`) && message.includes(String(child.pid))));
await new Promise<void>((resolve, reject) => {
const probe = createServer();
probe.once("error", reject);
probe.listen(port, "127.0.0.1", () => probe.close((error) => error ? reject(error) : resolve()));
});
} finally {
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
}
});
+283
View File
@@ -0,0 +1,283 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
deleteSandboxes,
desktopSandboxName,
parseConnectorE2eTestEnv,
provisionDesktopSandbox,
renderConnectorE2eTestEnv,
serverSandboxName,
startFaultProxyOnSandbox,
startMockOnSandbox,
} from "../src/provision.ts";
import type { ConnectorE2eTestEnv } from "../src/provision.ts";
import type { DaytonaExec } from "../src/daytona.ts";
interface ExecCall {
args: string[];
opts?: { input?: string; timeoutMs?: number };
}
function desktopFake(diskUse = "40%"):
{ exec: DaytonaExec; calls: ExecCall[] } {
const calls: ExecCall[] = [];
const exec: DaytonaExec = async (args, opts) => {
calls.push({ args: [...args], opts });
if (args[0] === "sandbox" && args[1] === "start") {
return { stdout: "", stderr: "already started", code: 1 };
}
if (args[0] === "snapshot") {
return { stdout: JSON.stringify([{ name: "openwork-eval-vnc", id: "snapshot-123" }]), stderr: "", code: 0 };
}
if (args[0] !== "exec") return { stdout: "", stderr: "", code: 0 };
const script = args[3] ?? "";
if (script.includes("git rev-parse")) return { stdout: "abc1234\n", stderr: "", code: 0 };
if (script.includes("df -P")) {
return { stdout: `/dev/root 100 40 60 ${diskUse} /workspace\n`, stderr: "", code: 0 };
}
if (script.includes("du -sh")) return { stdout: "8G /workspace/node_modules\n", stderr: "", code: 0 };
if (script.includes("pgrep -f Xvfb")) return { stdout: "XVFB_OK\n", stderr: "", code: 0 };
if (script.includes("xdg-open-proof")) return { stdout: "XDG_OPEN_WORKS\n", stderr: "", code: 0 };
if (script.includes("json/version")) return { stdout: '{"Browser":"Chrome/144"}', stderr: "", code: 0 };
if (script.includes("%{http_code}")) return { stdout: "200", stderr: "", code: 0 };
return { stdout: "", stderr: "", code: 0 };
};
return { exec, calls };
}
function assertRemoteCommandsAreSingleArgument(calls: ExecCall[]): void {
for (const call of calls.filter((entry) => entry.args[0] === "exec")) {
assert.equal(call.args.length, 4);
assert(call.args[3]?.startsWith("bash -lc '"));
}
}
test("connector E2E test env rendering and parsing round-trip the provision contract", () => {
const facts: ConnectorE2eTestEnv = {
denApiUrl: "https://den-api.example.test",
denWebUrl: "https://den-web.example.test",
sandboxA: "desktop-a",
sandboxB: "desktop-b",
mockUrl: "https://mock.example.test",
ref: "feat/eval-connector-two-members",
created: ["den", "desktop-a"],
};
const content = renderConnectorE2eTestEnv(facts);
assert.deepEqual(parseConnectorE2eTestEnv(content), facts);
assert.match(content, /^# provisioned for org-connector-two-members — generated .*; ref=feat\/eval-connector-two-members$/m);
assert.match(content, /^# provision-created=den,desktop-a$/m);
assert.match(content, /OPENWORK_EVAL_MODEL=big-pickle/);
const missingApi = content.split("\n").filter((line) => !line.startsWith("OPENWORK_EVAL_DEN_API_URL=")).join("\n");
assert.throws(() => parseConnectorE2eTestEnv(missingApi), /OPENWORK_EVAL_DEN_API_URL/);
});
test("server sandbox names are unique within the same CI process and second", () => {
const first = serverSandboxName();
const second = serverSandboxName();
assert.match(first, new RegExp(`^openwork-server-\\d{8}-\\d{6}-${process.pid}-[0-9a-f]{8}$`));
assert.notEqual(first, second);
});
test("desktop sandbox names stay unique when parallel workers use the same surface name", () => {
const first = desktopSandboxName("testkit admin");
const second = desktopSandboxName("testkit admin");
assert.match(first, new RegExp(`^openwork-connector-testkit-admin-\\d{8}-\\d{6}-${process.pid}-[0-9a-f]{8}$`));
assert.notEqual(first, second);
});
test("provisionDesktopSandbox reuses a sandbox and keeps every remote command in one argument", async () => {
const { exec, calls } = desktopFake();
const result = await provisionDesktopSandbox({ ref: "dev", name: "a", reuse: "existing-a", exec, log: () => undefined });
assert.deepEqual(result, { sandbox: "existing-a", created: false });
assert.deepEqual(calls[0]?.args, ["sandbox", "start", "existing-a"]);
assert.equal(calls.filter((call) => call.args[0] === "create").length, 0);
assert.equal(calls.filter((call) => call.args[0] === "snapshot").length, 0);
assertRemoteCommandsAreSingleArgument(calls);
});
test("provisionDesktopSandbox resolves the snapshot id and creates with connector flags", async () => {
const { exec, calls } = desktopFake();
const result = await provisionDesktopSandbox({ ref: "dev", name: "b", exec, log: () => undefined });
assert.equal(result.created, true);
const create = calls.find((call) => call.args[0] === "create");
assert(create);
assert(create.args.includes("snapshot-123"));
assert(!create.args.includes("--volume"), "eval secrets must not be mounted next to an untrusted ref by default");
assert(create.args.includes("--auto-stop"));
assert(create.args.includes("--public"));
assert.equal(calls.filter((call) => call.args[0] === "sandbox" && call.args[1] === "start").length, 0);
assertRemoteCommandsAreSingleArgument(calls);
});
test("rendered values are shell-quoted, because the env file is meant to be sourced", () => {
const nasty = "$(touch /tmp/pwned); echo it's-here";
const content = renderConnectorE2eTestEnv({
denApiUrl: "https://a",
denWebUrl: "https://w",
sandboxA: nasty,
sandboxB: "b",
mockUrl: "https://m",
ref: "dev",
created: [],
});
assert(content.includes(`OPENWORK_EVAL_DAYTONA_SANDBOX_A='$(touch /tmp/pwned); echo it'"'"'s-here'`));
assert.equal(parseConnectorE2eTestEnv(content).sandboxA, nasty);
});
test("an unsafe ref is refused before it can reach a remote shell or a sourced file", async () => {
const { exec, calls } = desktopFake();
for (const ref of ["dev\"; rm -rf /; #", "$(curl attacker)", "dev\nrm -rf /", "--upload-pack=evil"]) {
await assert.rejects(
provisionDesktopSandbox({ ref, name: "a", reuse: "existing-a", exec, log: () => undefined }),
/Unsafe git ref/,
);
assert.throws(() => renderConnectorE2eTestEnv({
denApiUrl: "https://a",
denWebUrl: "https://w",
sandboxA: "a",
sandboxB: "b",
mockUrl: "https://m",
ref,
created: [],
}), /Unsafe git ref/);
}
assert.equal(calls.length, 0, "an unsafe ref must be refused before any daytona call");
});
test("the eval secrets volume is mounted only when explicitly asked for", async () => {
const { exec, calls } = desktopFake();
await provisionDesktopSandbox({ ref: "dev", name: "b", secrets: true, exec, log: () => undefined });
const create = calls.find((call) => call.args[0] === "create");
assert(create?.args.includes("openwork-eval-secrets:/daytona-secrets"));
});
test("provisionDesktopSandbox fails the disk gate above 85 percent", async () => {
const { exec } = desktopFake("92%");
await assert.rejects(
provisionDesktopSandbox({ ref: "dev", name: "a", reuse: "full-a", exec, log: () => undefined }),
/92%/,
);
});
test("startMockOnSandbox rejects a health response with the wrong issuer", async () => {
const calls: ExecCall[] = [];
const exec: DaytonaExec = async (args, opts) => {
calls.push({ args: [...args], opts });
if (args[0] === "preview-url") {
return { stdout: "Preview URL: https://mock.example.test\n", stderr: "", code: 0 };
}
return { stdout: "", stderr: "", code: 0 };
};
const fetchImpl: typeof fetch = async () => new Response(
JSON.stringify({ ok: true, issuer: "https://wrong-issuer.example.test" }),
{ status: 200, headers: { "content-type": "application/json" } },
);
await assert.rejects(
startMockOnSandbox({ sandbox: "den-1", exec, fetchImpl, log: () => undefined }),
/https:\/\/wrong-issuer\.example\.test.*https:\/\/mock\.example\.test/,
);
assertRemoteCommandsAreSingleArgument(calls);
});
test("Daytona preview URL lookup retries a transient control-plane failure", async () => {
let previewAttempts = 0;
const exec: DaytonaExec = async (args) => {
if (args[0] === "preview-url") {
previewAttempts += 1;
return previewAttempts === 1
? { stdout: "", stderr: "unexpected EOF", code: 1 }
: { stdout: "Preview URL: https://mock.example.test\n", stderr: "", code: 0 };
}
return { stdout: "", stderr: "", code: 0 };
};
const fetchImpl: typeof fetch = async () => new Response(
JSON.stringify({ ok: true, issuer: "https://mock.example.test" }),
{ status: 200, headers: { "content-type": "application/json" } },
);
await startMockOnSandbox({ sandbox: "den-1", exec, fetchImpl, log: () => undefined });
assert.equal(previewAttempts, 2);
});
test("startFaultProxyOnSandbox uploads and detaches the proxy after resolving its preview URL", async () => {
const calls: ExecCall[] = [];
const exec: DaytonaExec = async (args, opts) => {
calls.push({ args: [...args], opts });
if (args[0] === "preview-url") {
return { stdout: "Preview URL: https://fault.example.test\n", stderr: "", code: 0 };
}
return { stdout: "", stderr: "", code: 0 };
};
const fetchImpl: typeof fetch = async () => new Response(
JSON.stringify({ ok: true, issuer: "https://fault.example.test" }),
{ status: 200, headers: { "content-type": "application/json" } },
);
const proxy = await startFaultProxyOnSandbox({ sandbox: "den-1", exec, fetchImpl, log: () => undefined });
assert.equal(proxy.url, "https://fault.example.test");
assert.match(proxy.token, /^[0-9a-f]{32}$/);
assert.deepEqual(calls[0]?.args, ["preview-url", "den-1", "-p", "3985", "--expires", "86400"]);
const scripts = calls.filter((call) => call.args[0] === "exec").map((call) => call.args[3]?.slice(10, -1) ?? "");
assert.match(scripts[0] ?? "", /pkill -f openwork-fault-proxy/);
assert.match(scripts[1] ?? "", /^printf %s [A-Za-z0-9+/=]+ \| base64 -d > \/tmp\/openwork-fault-proxy\.mjs$/);
assert(!scripts[1]?.includes("'"));
assert.match(scripts[2] ?? "", /start_new_session=True/);
assert.match(scripts[2] ?? "", /UPSTREAM=http:\/\/127\.0\.0\.1:3005/);
assert.match(scripts[2] ?? "", /node \/tmp\/openwork-fault-proxy\.mjs/);
assertRemoteCommandsAreSingleArgument(calls);
await proxy.stop();
assert.match(calls.at(-1)?.args[3] ?? "", /pkill -f openwork-fault-proxy\.mjs/);
});
test("startFaultProxyOnSandbox rejects a health response with the wrong issuer", async () => {
const exec: DaytonaExec = async (args) => {
if (args[0] === "preview-url") {
return { stdout: "Preview URL: https://fault.example.test\n", stderr: "", code: 0 };
}
return { stdout: "", stderr: "", code: 0 };
};
const fetchImpl: typeof fetch = async () => new Response(
JSON.stringify({ ok: true, issuer: "https://wrong-issuer.example.test" }),
{ status: 200, headers: { "content-type": "application/json" } },
);
await assert.rejects(
startFaultProxyOnSandbox({ sandbox: "den-1", exec, fetchImpl, log: () => undefined }),
/https:\/\/wrong-issuer\.example\.test.*https:\/\/fault\.example\.test/,
);
});
test("deleteSandboxes answers the confirmation prompt and tolerates a missing sandbox", async () => {
const calls: ExecCall[] = [];
const exec: DaytonaExec = async (args, opts) => {
calls.push({ args: [...args], opts });
if (args[1] === "gone-2") return { stdout: "", stderr: "sandbox not found", code: 1 };
return { stdout: "deleted\n", stderr: "", code: 0 };
};
await deleteSandboxes(["sandbox-1", "gone-2"], { exec, log: () => undefined });
assert.deepEqual(calls.map((call) => call.args), [
["delete", "sandbox-1"],
["delete", "gone-2"],
]);
assert.equal(calls[0]?.opts?.input, "y\n");
});
+13
View File
@@ -0,0 +1,13 @@
import assert from "node:assert/strict";
import test from "node:test";
import { runningInsideSandbox } from "../src/resolve.ts";
test("a sandbox id in the environment marks us as inside a sandbox", () => {
assert.equal(runningInsideSandbox({ DAYTONA_SANDBOX_ID: "snd-123" }), true);
});
test("an empty environment on a machine without sandbox volumes is outside", () => {
// Guarded so the assertion still holds when the suite itself runs in a sandbox.
const insideByVolume = runningInsideSandbox({});
assert.equal(typeof insideByVolume, "boolean");
});