源码快照 @ 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
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env node
const [runtimeManifestPath] = process.argv.slice(2);
if (!runtimeManifestPath) process.exit(1);
const { monitorHeadlessRuntime } = await import("../src/headless-web.ts");
await monitorHeadlessRuntime(runtimeManifestPath);
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env node
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
if (nodeMajor < 24 || !process.features?.typescript) {
console.error("Node 24+ with native TypeScript required — run `nvm use`");
process.exit(1);
}
const { runStandaloneWorldCli } = await import("../src/standalone-cli.ts");
try {
process.exitCode = await runStandaloneWorldCli();
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "@openwork/world",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"development": "./src/index.ts",
"default": "./src/index.ts"
}
},
"bin": {
"openwork-world": "./bin/world.mjs"
},
"engines": {
"node": ">=24"
},
"dependencies": {
"@openwork/paths": "workspace:*"
},
"scripts": {
"test": "node --test test/*.test.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"devDependencies": {
"@types/node": "^24.0.0",
"typescript": "^5.9.0"
}
}
+192
View File
@@ -0,0 +1,192 @@
import { join, resolve } from "node:path";
import { discoverWorlds, displayWorldPath, resolveWorldScript } from "./loader.ts";
import { WorldStateStore } from "./store.ts";
import {
downScriptWorld,
isProcessAlive,
launchScriptWorld,
readScriptWorldSnapshot,
scriptWorldSnapshotDirectory,
scriptWorldSnapshotPath,
} from "./script-world.ts";
export type WorldCommand =
| {
kind: "up";
source: string;
detach?: boolean;
timeoutMs?: number;
args: string[];
}
| { kind: "down"; name: string }
| { kind: "list" }
| { kind: "forget"; name: string }
| { kind: "help"; error?: string };
export interface WorldCliOptions {
cwd: string;
worldsDirectory: string;
print?: (line: string) => void;
}
function helpError(message: string): WorldCommand {
return { kind: "help", error: message };
}
export function parseWorldArgs(argv: string[]): WorldCommand {
const [command, ...args] = argv;
if (!command || command === "help") {
return args.length === 0 ? { kind: "help" } : helpError("The help command does not take arguments.");
}
if (command === "up") {
const [source, ...rest] = args;
if (!source || source === "--" || source.startsWith("--")) {
return helpError("The up command needs a script path or world name.");
}
const separator = rest.indexOf("--");
const options = separator === -1 ? rest : rest.slice(0, separator);
const scriptArgs = separator === -1 ? [] : rest.slice(separator + 1);
let detach = false;
let timeoutMs: number | undefined;
for (let index = 0; index < options.length; index += 1) {
const option = options[index];
if (option === "--detach" && !detach) {
detach = true;
continue;
}
if (option === "--timeout" && timeoutMs === undefined) {
const value = options[index + 1];
const parsed = value === undefined ? Number.NaN : Number(value);
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
return helpError("Use --timeout followed by a positive number of milliseconds.");
}
timeoutMs = parsed;
index += 1;
continue;
}
return helpError(`Unknown world CLI option ${JSON.stringify(option)}. Pass script arguments after --.`);
}
if (timeoutMs !== undefined && !detach) return helpError("Use --timeout only with --detach.");
return {
kind: "up",
source,
...(detach ? { detach: true } : {}),
...(timeoutMs === undefined ? {} : { timeoutMs }),
args: scriptArgs,
};
}
if (command === "list") {
return args.length === 0 ? { kind: "list" } : helpError("The list command does not take arguments.");
}
if (command === "down") {
return args.length === 1 && args[0]
? { kind: "down", name: args[0] }
: helpError("The down command needs exactly one world name.");
}
if (command === "forget") {
return args.length === 1 && args[0]
? { kind: "forget", name: args[0] }
: helpError("The forget command needs exactly one world name.");
}
return helpError(`Unknown command ${JSON.stringify(command)}. Script worlds support up, down, list, forget, and help.`);
}
function messageText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
async function helpText(options: WorldCliOptions): Promise<string> {
const discovered = await discoverWorlds(options.worldsDirectory);
const sources = discovered.map((world) => displayWorldPath(world.path, options.cwd));
return `Usage:
pnpm world up <script-path-or-name> [--detach] [--timeout <ms>] [-- <script args...>]
pnpm world down <name>
pnpm world list
pnpm world forget <name>
pnpm world help
World scripts run in the foreground by default; use --detach for background lifecycle receipts.
Available world scripts: ${sources.join(", ") || "(none)"}`;
}
export async function main(argv: string[], options: WorldCliOptions): Promise<number> {
const print = options.print ?? console.log;
const command = parseWorldArgs(argv);
if (command.kind === "help") {
if (command.error) print(command.error);
print(await helpText(options));
return command.error ? 1 : 0;
}
if (command.kind === "up") {
try {
const script = await resolveWorldScript(command.source, options);
return await launchScriptWorld({
path: script.path,
name: script.name,
args: command.args,
snapshotDirectory: scriptWorldSnapshotDirectory(options.cwd),
detach: command.detach === true,
...(command.timeoutMs === undefined ? {} : { timeoutMs: command.timeoutMs }),
print,
});
} catch (error) {
print(messageText(error));
return 1;
}
}
if (command.kind === "down") {
try {
const path = scriptWorldSnapshotPath(scriptWorldSnapshotDirectory(options.cwd), command.name);
const result = await downScriptWorld(path);
if (!result.found) {
print(`World receipt ${JSON.stringify(command.name)} does not exist.`);
return 1;
}
if (result.forced) {
print(`World ${JSON.stringify(command.name)} teardown was forced (pid ${result.pid}).`);
} else {
print(`World ${JSON.stringify(command.name)} torn down.`);
}
return 0;
} catch (error) {
print(messageText(error));
return 1;
}
}
if (command.kind === "list") {
const discovered = await discoverWorlds(options.worldsDirectory);
print(`World scripts: ${discovered.map((world) => `${world.name} (${displayWorldPath(world.path, options.cwd)}, script)`).join(", ") || "(none)"}`);
let count = 0;
const receiptsDirectory = scriptWorldSnapshotDirectory(options.cwd);
for (const path of await new WorldStateStore(receiptsDirectory).list()) {
try {
const receipt = await readScriptWorldSnapshot(path);
if (!receipt) continue;
print(`${receipt.name} ${receipt.createdAt} script ${isProcessAlive(receipt.pid) ? "alive" : `dead(pid ${receipt.pid})`}`);
count += 1;
} catch (error) {
print(`Warning: skipped ${displayWorldPath(path, options.cwd)}: ${messageText(error)}`);
}
}
if (count === 0) print("No script world receipts.");
return 0;
}
try {
const store = new WorldStateStore(scriptWorldSnapshotDirectory(options.cwd));
if (!await store.forget(command.name)) {
print(`World receipt ${JSON.stringify(command.name)} does not exist.`);
return 1;
}
print(`Removed receipt metadata for ${JSON.stringify(command.name)}. The script process was not stopped.`);
return 0;
} catch (error) {
print(messageText(error));
return 1;
}
}
export function defaultWorldCliPaths(repoRoot: string): { cwd: string; worldsDirectory: string } {
const cwd = resolve(repoRoot);
return { cwd, worldsDirectory: join(cwd, "worlds") };
}
+217
View File
@@ -0,0 +1,217 @@
import path from "node:path";
export type HeadlessServerConfigDocument = Record<string, unknown> & {
authorizedRoots: string[];
workspaces: Array<Record<string, unknown>>;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function stripTrailingSlashes(value: string): string {
let end = value.length;
while (end > 0 && value[end - 1] === "/") end -= 1;
return value.slice(0, end);
}
export type HeadlessRuntimePids = {
launcher: number;
web: number | null;
openworkServer: number | null;
};
export type HeadlessRuntimeManifest = {
mode: "local-server";
webUrl: string;
openworkUrl: string;
healthUrl: string;
workspace: string;
token: string;
hostToken: string;
serverConfigPath: string;
runtimeManifestPath: string;
webLogPath: string;
headlessLogPath: string;
denTarget: string | null;
denApiUrl: string | null;
notes: string;
startedAt: string;
pid: number;
pids: HeadlessRuntimePids;
supervisorPid?: number | null;
world?: { name: string; state: HeadlessWebState; launchId?: string };
};
export type HeadlessWebState = "isolated" | "installed-production";
/** Args forwarded by the legacy detached launcher. */
export function buildDetachedRespawnArgs(argv: string[]): string[] {
return argv.filter((arg) => arg !== "--detach");
}
export function normalizeDenTarget(value: string | undefined): string {
const raw = (value ?? "https://app.openworklabs.com").trim();
const withProtocol = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
return new URL(withProtocol).origin;
}
export function isHeadlessStackCommand(command: string): boolean {
return command.includes("dev-headless-web")
|| command.includes("openwork-server")
|| command.includes("apps/server/src/cli.ts")
|| command.includes("packages/world")
|| command.includes("vite");
}
export function buildHeadlessServerLaunch(
cwd: string,
serverArgs: string[],
): { command: string; args: string[] } {
return {
command: "bun",
args: [
"--conditions=development",
path.join(cwd, "apps/server/src/cli.ts"),
...serverArgs,
],
};
}
export function resolveHeadlessServerConfigPath(
cwd: string,
override?: string | null,
): string {
const trimmed = override?.trim();
if (trimmed) return path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
return path.join(cwd, "tmp", "headless-server.json");
}
export function resolveHeadlessRuntimeManifestPath(cwd: string): string {
return path.join(cwd, "tmp", "dev-headless-web.json");
}
export function mergeHeadlessServerConfig(
existingRaw: string | null,
workspace: string,
): HeadlessServerConfigDocument {
const workspaceRoot = path.resolve(workspace);
let existing: Record<string, unknown> = {};
if (existingRaw) {
try {
const parsed: unknown = JSON.parse(existingRaw);
if (isRecord(parsed)) existing = parsed;
} catch {
// Corrupt config: fall back to a minimal isolated document.
}
}
const existingRoots = Array.isArray(existing.authorizedRoots)
? existing.authorizedRoots.filter((root): root is string => typeof root === "string")
: [];
const authorizedRoots = existingRoots.some((root) => path.resolve(root) === workspaceRoot)
? existingRoots
: [...existingRoots, workspaceRoot];
const existingWorkspaces = Array.isArray(existing.workspaces)
? existing.workspaces.filter(isRecord)
: [];
const hasWorkspace = existingWorkspaces.some(
(entry) => typeof entry.path === "string" && path.resolve(entry.path) === workspaceRoot,
);
return {
...existing,
authorizedRoots,
workspaces: hasWorkspace
? existingWorkspaces
: [...existingWorkspaces, { path: workspaceRoot }],
};
}
export function resolveHeadlessTokens(input: {
envToken: string | undefined;
envHostToken: string | undefined;
previous: Pick<HeadlessRuntimeManifest, "token" | "hostToken"> | null;
generate: () => string;
}): { token: string; hostToken: string } {
const token = input.envToken?.trim() || input.previous?.token?.trim() || input.generate();
const hostToken = input.envHostToken?.trim() || input.previous?.hostToken?.trim() || input.generate();
return { token, hostToken };
}
export function buildHeadlessCorsOrigins(input: {
webUrl: string;
webPort: number;
}): string[] {
return Array.from(new Set([
new URL(input.webUrl).origin,
`http://127.0.0.1:${input.webPort}`,
`http://localhost:${input.webPort}`,
]));
}
export function buildOpenworkServerArgs(input: {
host: string;
port: number;
configPath: string;
corsOrigins: string[];
}): string[] {
return [
"--config",
input.configPath,
"--host",
input.host,
"--port",
String(input.port),
"--approval",
"auto",
"--cors",
input.corsOrigins.join(","),
"--verbose",
];
}
export function buildHeadlessRuntimeManifest(input: {
webUrl: string;
openworkUrl: string;
workspace: string;
token: string;
hostToken: string;
serverConfigPath: string;
runtimeManifestPath: string;
webLogPath: string;
headlessLogPath: string;
denTarget?: string | null;
pid?: number;
webPid?: number | null;
openworkServerPid?: number | null;
startedAt?: string;
supervisorPid?: number | null;
world?: { name: string; state: HeadlessWebState; launchId?: string };
}): HeadlessRuntimeManifest {
const denTarget = input.denTarget ?? null;
const launcherPid = input.pid ?? process.pid;
return {
mode: "local-server",
webUrl: input.webUrl,
openworkUrl: input.openworkUrl,
healthUrl: `${stripTrailingSlashes(input.openworkUrl)}/health`,
workspace: path.resolve(input.workspace),
token: input.token,
hostToken: input.hostToken,
serverConfigPath: input.serverConfigPath,
runtimeManifestPath: input.runtimeManifestPath,
webLogPath: input.webLogPath,
headlessLogPath: input.headlessLogPath,
denTarget,
denApiUrl: denTarget ? `${stripTrailingSlashes(input.webUrl)}/api/den` : null,
notes: "Local openwork-server session. Workspace auth uses token/hostToken; the server config and state selection are owned by the selected world. Den/Cloud API calls go same-origin through denApiUrl (Vite proxies them to denTarget; the app is pinned there via VITE_DEN_API_BASE_URL).",
startedAt: input.startedAt ?? new Date().toISOString(),
pid: launcherPid,
pids: {
launcher: launcherPid,
web: input.webPid ?? null,
openworkServer: input.openworkServerPid ?? null,
},
...(input.supervisorPid === undefined ? {} : { supervisorPid: input.supervisorPid }),
...(input.world ? { world: input.world } : {}),
};
}
+905
View File
@@ -0,0 +1,905 @@
import { execFileSync, spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { closeSync, openSync } from "node:fs";
import { chmod, mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { createServer } from "node:net";
import { setTimeout as delay } from "node:timers/promises";
import {
desktopBootstrapPath,
globalOpencodeConfigDir,
opencodeDbCandidates,
openworkEnvStorePath,
openworkServerConfigPath,
openworkServerDataDir,
} from "@openwork/paths";
import {
buildHeadlessCorsOrigins,
buildHeadlessRuntimeManifest,
buildHeadlessServerLaunch,
buildOpenworkServerArgs,
mergeHeadlessServerConfig,
normalizeDenTarget,
resolveHeadlessRuntimeManifestPath,
resolveHeadlessServerConfigPath,
resolveHeadlessTokens,
} from "./headless-web-helpers.ts";
import type { ChildProcess } from "node:child_process";
import type { HeadlessRuntimeManifest, HeadlessWebState } from "./headless-web-helpers.ts";
import { assertWorldName } from "./store.ts";
const DEFAULT_WEB_PORT = "5178";
const DEFAULT_SERVER_PORT = "8778";
const DEFAULT_DEN_TARGET = "https://app.openworklabs.com";
export interface HeadlessWebLaunchOptions {
repoRoot: string;
name: string;
state: HeadlessWebState;
workspace?: string;
allowSharedState?: boolean;
replace?: boolean;
keepTokens?: boolean;
rotateTokens?: boolean;
env?: NodeJS.ProcessEnv;
}
export interface HeadlessWebHandle {
manifest: HeadlessRuntimeManifest;
reused: boolean;
waitForExit(): Promise<string>;
detach(): Promise<void>;
stop(): Promise<void>;
}
export interface HeadlessWorldRuntimePaths {
directory: string;
runtimeManifestPath: string;
serverConfigPath: string;
webLogPath: string;
headlessLogPath: string;
}
export interface InstalledProductionHeadlessState {
bootstrapPath: string;
dataDir: string;
envStorePath: string;
homeDir: string;
opencodeDb: string;
opencodeConfigDir: string;
serverConfigPath: string;
serverStatePath: string;
serverTokenStorePath: string;
workspaceStatePath: string;
token: string;
hostToken: string;
workspace: string;
}
export interface HeadlessClientConnection {
url: string;
port: string;
token: string;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function readBool(value: string | undefined): boolean {
return ["1", "true", "yes", "on"].includes((value ?? "").trim().toLowerCase());
}
function messageText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
async function isPortFree(port: number, host: string): Promise<boolean> {
return new Promise((done) => {
const server = createServer();
server.once("error", () => done(false));
server.listen(port, host, () => server.close(() => done(true)));
});
}
async function getFreePort(host: string): Promise<number> {
return new Promise((done, reject) => {
const server = createServer();
server.once("error", reject);
server.listen(0, host, () => {
const address = server.address();
if (!address || typeof address === "string") {
server.close(() => reject(new Error("Unable to resolve free port")));
return;
}
server.close(() => done(address.port));
});
});
}
async function resolvePort(value: string | undefined, host: string): Promise<number> {
if (value) {
const parsed = Number(value);
if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65_535 && await isPortFree(parsed, host)) {
return parsed;
}
}
return getFreePort(host);
}
async function probeOk(url: string): Promise<boolean> {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(1_500) });
return response.ok;
} catch {
return false;
}
}
async function probeStack(manifest: HeadlessRuntimeManifest): Promise<boolean> {
return await probeOk(manifest.healthUrl) && await probeOk(manifest.webUrl);
}
export function headlessRuntimeIsHealthy(manifest: HeadlessRuntimeManifest): Promise<boolean> {
return headlessRuntimeProcessesAreOwned(manifest)
? probeStack(manifest)
: Promise.resolve(false);
}
function processCommand(pid: number): string | null {
try {
const output = execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" }).trim();
return output || null;
} catch {
return null;
}
}
function commandHasArgument(command: string, value: string): boolean {
return command.includes(value) || command.includes(JSON.stringify(value));
}
function runtimePort(value: string): string {
return new URL(value).port;
}
function isOwnedRuntimeProcess(
manifest: HeadlessRuntimeManifest,
pid: number,
kind: "web" | "server" | "supervisor",
): boolean {
const command = processCommand(pid);
if (!command) return false;
if (kind === "web") {
return command.includes("vite")
&& commandHasArgument(command, "@openwork/app")
&& commandHasArgument(command, runtimePort(manifest.webUrl));
}
if (kind === "server") {
return command.includes("apps/server/src/cli.ts")
&& commandHasArgument(command, manifest.serverConfigPath)
&& commandHasArgument(command, runtimePort(manifest.openworkUrl));
}
return command.includes("headless-monitor.mjs")
&& commandHasArgument(command, manifest.runtimeManifestPath);
}
async function killOwnedRuntimeProcess(
manifest: HeadlessRuntimeManifest,
pid: number | null | undefined,
kind: "web" | "server" | "supervisor",
): Promise<void> {
if (!pid || pid <= 0 || pid === process.pid || !isOwnedRuntimeProcess(manifest, pid, kind)) return;
const signal = (value: NodeJS.Signals): void => {
try {
process.kill(-pid, value);
} catch {
try {
process.kill(pid, value);
} catch {
// Already gone.
}
}
};
signal("SIGTERM");
await delay(500);
if (isOwnedRuntimeProcess(manifest, pid, kind)) signal("SIGKILL");
const deadline = Date.now() + 2_000;
while (Date.now() < deadline && isOwnedRuntimeProcess(manifest, pid, kind)) {
await delay(50);
}
if (isOwnedRuntimeProcess(manifest, pid, kind)) {
throw new Error(`Owned headless ${kind} process ${pid} did not stop.`);
}
}
function parsedWorld(value: unknown): HeadlessRuntimeManifest["world"] {
if (!isRecord(value) || typeof value.name !== "string") return undefined;
if (value.state !== "isolated" && value.state !== "installed-production") return undefined;
const launchId = typeof value.launchId === "string" ? value.launchId : undefined;
return {
name: value.name,
state: value.state,
...(launchId === undefined ? {} : { launchId }),
};
}
function parsedPid(value: unknown): number | null {
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
}
function parseRuntimeManifest(value: unknown): HeadlessRuntimeManifest | null {
if (!isRecord(value) || value.mode !== "local-server" || !isRecord(value.pids)) return null;
const requiredStrings = [
"webUrl",
"openworkUrl",
"healthUrl",
"workspace",
"token",
"hostToken",
"serverConfigPath",
"runtimeManifestPath",
"webLogPath",
"headlessLogPath",
"notes",
"startedAt",
];
if (requiredStrings.some((key) => typeof value[key] !== "string")) return null;
const launcher = parsedPid(value.pids.launcher) ?? parsedPid(value.pid);
if (!launcher) return null;
const denTarget = value.denTarget === null || typeof value.denTarget === "string" ? value.denTarget : null;
const denApiUrl = value.denApiUrl === null || typeof value.denApiUrl === "string" ? value.denApiUrl : null;
return {
mode: "local-server",
webUrl: String(value.webUrl),
openworkUrl: String(value.openworkUrl),
healthUrl: String(value.healthUrl),
workspace: String(value.workspace),
token: String(value.token),
hostToken: String(value.hostToken),
serverConfigPath: String(value.serverConfigPath),
runtimeManifestPath: String(value.runtimeManifestPath),
webLogPath: String(value.webLogPath),
headlessLogPath: String(value.headlessLogPath),
denTarget,
denApiUrl,
notes: String(value.notes),
startedAt: String(value.startedAt),
pid: parsedPid(value.pid) ?? launcher,
pids: {
launcher,
web: parsedPid(value.pids.web),
openworkServer: parsedPid(value.pids.openworkServer),
},
...(parsedPid(value.supervisorPid) === null ? {} : { supervisorPid: parsedPid(value.supervisorPid) }),
...(parsedWorld(value.world) ? { world: parsedWorld(value.world) } : {}),
};
}
export function resolveHeadlessWorldRuntimePaths(
repoRootInput: string,
name: string,
): HeadlessWorldRuntimePaths {
assertWorldName(name);
const repoRoot = resolve(repoRootInput);
if (name === "dev-headless") {
const directory = join(repoRoot, "tmp");
return {
directory,
runtimeManifestPath: resolveHeadlessRuntimeManifestPath(repoRoot),
serverConfigPath: resolveHeadlessServerConfigPath(repoRoot),
webLogPath: join(directory, "dev-web.log"),
headlessLogPath: join(directory, "dev-headless.log"),
};
}
const directory = join(repoRoot, "tmp", "worlds", "runtime", name);
return {
directory,
runtimeManifestPath: join(directory, "runtime.json"),
serverConfigPath: join(directory, "server.json"),
webLogPath: join(directory, "web.log"),
headlessLogPath: join(directory, "server.log"),
};
}
export async function readHeadlessRuntimeManifest(path: string): Promise<HeadlessRuntimeManifest | null> {
try {
const parsed: unknown = JSON.parse(await readFile(path, "utf8"));
return parseRuntimeManifest(parsed);
} catch {
return null;
}
}
function spawnLogged(
command: string,
args: string[],
logPath: string,
cwd: string,
env: NodeJS.ProcessEnv,
): ChildProcess {
const logFd = openSync(logPath, "w");
try {
return spawn(command, args, {
cwd,
env,
stdio: ["ignore", logFd, logFd],
detached: true,
});
} finally {
closeSync(logFd);
}
}
function waitForSpawn(child: ChildProcess, label: string): Promise<void> {
return new Promise((done, reject) => {
const onError = (error: Error): void => {
child.off("spawn", onSpawn);
reject(new Error(`${label} failed to spawn: ${error.message}`));
};
const onSpawn = (): void => {
child.off("error", onError);
done();
};
child.once("error", onError);
child.once("spawn", onSpawn);
});
}
async function requirePath(path: string, kind: "directory" | "file", label: string): Promise<void> {
const metadata = await stat(path).catch(() => null);
const valid = kind === "directory" ? metadata?.isDirectory() : metadata?.isFile();
if (!valid) {
throw new Error(`${label} is unavailable at ${path}. Start the installed production OpenWork desktop once and confirm its local state exists.`);
}
}
function credentials(value: unknown): { token: string; hostToken: string } | null {
if (!isRecord(value) || !isRecord(value.credentials)) return null;
const token = value.credentials.clientToken;
const hostToken = value.credentials.hostToken;
return typeof token === "string" && token.length > 0 && typeof hostToken === "string" && hostToken.length > 0
? { token, hostToken }
: null;
}
function firstWorkspace(value: unknown): string | null {
if (!isRecord(value)) return null;
const candidates = [value.workspaces, value.workspacePaths, value.items];
for (const candidate of candidates) {
if (!Array.isArray(candidate)) continue;
for (const entry of candidate) {
if (typeof entry === "string" && entry.trim()) return entry;
if (isRecord(entry) && typeof entry.path === "string" && entry.path.trim()) return entry.path;
}
}
return null;
}
export async function resolveInstalledProductionHeadlessState(options: {
env?: NodeJS.ProcessEnv;
fallbackWorkspace: string;
homeDir?: string;
platform?: NodeJS.Platform;
}): Promise<InstalledProductionHeadlessState> {
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 userDataDir = join(homeDir, "Library", "Application Support", "com.differentai.openwork");
const pathOptions = { env, homeDir, platform };
const dataDir = openworkServerDataDir(pathOptions);
const serverConfigPath = openworkServerConfigPath(pathOptions);
const envStorePath = openworkEnvStorePath(pathOptions);
const bootstrapPath = desktopBootstrapPath(pathOptions);
const opencodeConfigDir = globalOpencodeConfigDir(pathOptions);
const serverTokenStorePath = join(userDataDir, "openwork-server-tokens.json");
const serverStatePath = join(userDataDir, "openwork-server-state.json");
const workspaceStatePath = join(userDataDir, "openwork-workspaces.json");
const dbCandidates = opencodeDbCandidates({ ...pathOptions, defaultChannel: "latest" });
let opencodeDb: string | null = null;
for (const candidate of dbCandidates) {
if ((await stat(candidate).catch(() => null))?.isFile()) {
opencodeDb = candidate;
break;
}
}
for (const [path, kind, label] of [
[dataDir, "directory", "Installed production OpenWork data directory"],
[opencodeConfigDir, "directory", "Installed production OpenCode config directory"],
[workspaceStatePath, "file", "Installed production workspace state"],
[serverTokenStorePath, "file", "Installed production server token store"],
[serverStatePath, "file", "Installed production server state"],
[serverConfigPath, "file", "Installed production server config"],
[envStorePath, "file", "Installed production environment store"],
[bootstrapPath, "file", "Installed production desktop bootstrap"],
] satisfies Array<[string, "directory" | "file", string]>) {
await requirePath(path, kind, label);
}
if (!opencodeDb) {
throw new Error(`Installed production OpenCode database is unavailable. Checked channel-aware candidates: ${dbCandidates.join(", ")}.`);
}
const tokenJson: unknown = JSON.parse(await readFile(serverTokenStorePath, "utf8"));
const resolvedCredentials = credentials(tokenJson);
if (!resolvedCredentials) throw new Error(`Installed production server credentials are unavailable in ${serverTokenStorePath}.`);
const workspaceJson: unknown = JSON.parse(await readFile(workspaceStatePath, "utf8"));
return {
bootstrapPath,
dataDir,
envStorePath,
homeDir,
opencodeDb,
opencodeConfigDir,
serverConfigPath,
serverStatePath,
serverTokenStorePath,
workspaceStatePath,
...resolvedCredentials,
workspace: firstWorkspace(workspaceJson) ?? options.fallbackWorkspace,
};
}
export function installedProductionHeadlessEnv(state: InstalledProductionHeadlessState): NodeJS.ProcessEnv {
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,
};
}
function isLoopbackHost(value: string | undefined): boolean {
if (!value) return true;
const normalized = value.trim().toLowerCase();
return normalized === "127.0.0.1"
|| normalized === "localhost"
|| normalized === "::1"
|| normalized === "[::1]";
}
export function assertHeadlessLaunchSafety(
state: HeadlessWebState,
env: NodeJS.ProcessEnv,
): void {
if (state !== "installed-production") return;
if (
readBool(env.OPENWORK_REMOTE_ACCESS)
|| Boolean(env.OPENWORK_PUBLIC_HOST?.trim())
|| !isLoopbackHost(env.VITE_HOST)
|| !isLoopbackHost(env.HOST)
) {
throw new Error(
"LIVE SHARED PRODUCTION STATE headless worlds require loopback-only access; OPENWORK_REMOTE_ACCESS, OPENWORK_PUBLIC_HOST, and non-loopback HOST/VITE_HOST values are refused.",
);
}
}
export function resolveHeadlessClientConnection(input: {
state: HeadlessWebState;
env: NodeJS.ProcessEnv;
openworkUrl: string;
openworkPort: number;
token: string;
}): HeadlessClientConnection {
if (input.state === "installed-production") {
return {
url: input.openworkUrl,
port: String(input.openworkPort),
token: input.token,
};
}
return {
url: input.env.VITE_OPENWORK_URL ?? input.openworkUrl,
port: input.env.VITE_OPENWORK_PORT ?? String(input.openworkPort),
token: input.env.VITE_OPENWORK_TOKEN ?? input.token,
};
}
function killChild(child: ChildProcess, signal: NodeJS.Signals): void {
if (child.exitCode !== null) return;
const pid = child.pid;
if (pid) {
try {
process.kill(-pid, signal);
return;
} catch {
// Fall through to direct kill.
}
}
child.kill(signal);
}
function childProcessGroupIsAlive(child: ChildProcess): boolean {
const pid = child.pid;
if (!pid) return false;
try {
process.kill(-pid, 0);
return true;
} catch {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
}
async function stopAcquiredChild(child: ChildProcess): Promise<void> {
if (!childProcessGroupIsAlive(child)) return;
killChild(child, "SIGTERM");
let deadline = Date.now() + 2_000;
while (Date.now() < deadline && childProcessGroupIsAlive(child)) await delay(50);
if (!childProcessGroupIsAlive(child)) return;
killChild(child, "SIGKILL");
deadline = Date.now() + 2_000;
while (Date.now() < deadline && childProcessGroupIsAlive(child)) await delay(50);
if (childProcessGroupIsAlive(child)) {
throw new Error(`Acquired headless process group ${child.pid ?? "unknown"} did not stop.`);
}
}
export function headlessRuntimeProcessesAreOwned(manifest: HeadlessRuntimeManifest): boolean {
const webPid = manifest.pids.web;
const serverPid = manifest.pids.openworkServer;
return webPid !== null
&& serverPid !== null
&& isOwnedRuntimeProcess(manifest, webPid, "web")
&& isOwnedRuntimeProcess(manifest, serverPid, "server");
}
async function removeRuntimeManifest(manifest: HeadlessRuntimeManifest): Promise<void> {
await unlink(manifest.runtimeManifestPath).catch((error: unknown) => {
if (
typeof error !== "object"
|| error === null
|| !("code" in error)
|| error.code !== "ENOENT"
) {
throw error;
}
});
}
async function stopManifest(manifest: HeadlessRuntimeManifest): Promise<void> {
await killOwnedRuntimeProcess(manifest, manifest.pids.web, "web");
await killOwnedRuntimeProcess(manifest, manifest.pids.openworkServer, "server");
await killOwnedRuntimeProcess(manifest, manifest.supervisorPid, "supervisor");
await removeRuntimeManifest(manifest);
}
export function stopHeadlessRuntime(manifest: HeadlessRuntimeManifest): Promise<void> {
return stopManifest(manifest);
}
async function waitForHealthy(manifest: HeadlessRuntimeManifest): Promise<void> {
const deadline = Date.now() + 45_000;
while (Date.now() < deadline) {
if (await probeStack(manifest)) return;
await delay(500);
}
throw new Error(`Headless web did not become healthy; inspect ${manifest.webLogPath} and ${manifest.headlessLogPath}.`);
}
async function waitForOwnedRuntimeExit(manifest: HeadlessRuntimeManifest): Promise<string> {
while (headlessRuntimeProcessesAreOwned(manifest)) await delay(500);
const webRunning = manifest.pids.web !== null
&& isOwnedRuntimeProcess(manifest, manifest.pids.web, "web");
const serverRunning = manifest.pids.openworkServer !== null
&& isOwnedRuntimeProcess(manifest, manifest.pids.openworkServer, "server");
return `headless process exited (web=${webRunning ? "running" : "stopped"}, server=${serverRunning ? "running" : "stopped"})`;
}
export async function monitorHeadlessRuntime(runtimeManifestPath: string): Promise<void> {
const manifest = await readHeadlessRuntimeManifest(runtimeManifestPath);
if (!manifest || manifest.runtimeManifestPath !== runtimeManifestPath) return;
await waitForOwnedRuntimeExit(manifest);
await killOwnedRuntimeProcess(manifest, manifest.pids.web, "web");
await killOwnedRuntimeProcess(manifest, manifest.pids.openworkServer, "server");
await removeRuntimeManifest(manifest);
}
async function writeRuntimeManifest(manifest: HeadlessRuntimeManifest): Promise<void> {
const temporaryPath = `${manifest.runtimeManifestPath}.${process.pid}.${randomUUID()}.tmp`;
try {
await writeFile(
temporaryPath,
`${JSON.stringify(manifest, null, 2)}\n`,
{ encoding: "utf8", mode: 0o600 },
);
await chmod(temporaryPath, 0o600);
await rename(temporaryPath, manifest.runtimeManifestPath);
} catch (error) {
await unlink(temporaryPath).catch(() => {});
throw error;
}
}
function startRuntimeSupervisor(
manifest: HeadlessRuntimeManifest,
repoRoot: string,
): ChildProcess {
return spawn(
process.execPath,
[join(repoRoot, "packages", "world", "bin", "headless-monitor.mjs"), manifest.runtimeManifestPath],
{
cwd: repoRoot,
env: process.env,
stdio: "ignore",
detached: true,
},
);
}
async function ensureRuntimeSupervisor(
manifest: HeadlessRuntimeManifest,
repoRoot: string,
): Promise<{ manifest: HeadlessRuntimeManifest; supervisor: ChildProcess | null }> {
if (
manifest.supervisorPid
&& isOwnedRuntimeProcess(manifest, manifest.supervisorPid, "supervisor")
) {
return { manifest, supervisor: null };
}
const supervisor = startRuntimeSupervisor(manifest, repoRoot);
try {
await waitForSpawn(supervisor, "headless supervisor");
const supervised: HeadlessRuntimeManifest = {
...manifest,
supervisorPid: supervisor.pid ?? null,
};
await writeRuntimeManifest(supervised);
return { manifest: supervised, supervisor };
} catch (error) {
killChild(supervisor, "SIGTERM");
throw error;
}
}
export async function launchHeadlessWeb(options: HeadlessWebLaunchOptions): Promise<HeadlessWebHandle> {
if (options.state === "installed-production" && options.allowSharedState !== true) {
throw new Error("Refusing LIVE SHARED PRODUCTION STATE launch without explicit --allow-shared-state opt-in.");
}
const repoRoot = resolve(options.repoRoot);
const env = options.env ?? process.env;
assertWorldName(options.name);
assertHeadlessLaunchSafety(options.state, env);
const runtimePaths = resolveHeadlessWorldRuntimePaths(repoRoot, options.name);
await mkdir(runtimePaths.directory, { recursive: true });
const runtimeManifestPath = runtimePaths.runtimeManifestPath;
const existing = await readHeadlessRuntimeManifest(runtimeManifestPath);
const existingHealthy = existing ? await headlessRuntimeIsHealthy(existing) : false;
const existingState = existing?.world?.state ?? "isolated";
const existingName = existing?.world?.name ?? (options.name === "dev-headless" ? "dev-headless" : null);
if (
existing
&& existingHealthy
&& existingState === options.state
&& existingName === options.name
&& options.replace !== true
) {
const adopted: HeadlessRuntimeManifest = existing.world?.launchId
? existing
: {
...existing,
world: {
name: options.name,
state: options.state,
launchId: randomUUID(),
},
};
await writeRuntimeManifest(adopted);
const supervised = await ensureRuntimeSupervisor(adopted, repoRoot);
supervised.supervisor?.unref();
return {
manifest: supervised.manifest,
reused: true,
waitForExit: () => waitForOwnedRuntimeExit(supervised.manifest),
async detach() {},
async stop() { await stopManifest(supervised.manifest); },
};
}
if (existing) await stopManifest(existing);
const remoteAccessEnabled = readBool(env.OPENWORK_REMOTE_ACCESS);
const host = remoteAccessEnabled ? "0.0.0.0" : "127.0.0.1";
const viteHost = env.VITE_HOST ?? env.HOST ?? host;
const publicHost = env.OPENWORK_PUBLIC_HOST ?? null;
const clientHost = publicHost ?? (host === "0.0.0.0" ? "127.0.0.1" : host);
const declaredWorkspace = resolve(options.workspace ?? env.OPENWORK_WORKSPACE ?? repoRoot);
const productionState = options.state === "installed-production"
? await resolveInstalledProductionHeadlessState({ env, fallbackWorkspace: declaredWorkspace })
: null;
const workspace = productionState?.workspace ?? declaredWorkspace;
const openworkPort = await resolvePort(env.OPENWORK_PORT ?? DEFAULT_SERVER_PORT, "127.0.0.1");
const webPort = await resolvePort(env.OPENWORK_WEB_PORT ?? DEFAULT_WEB_PORT, "127.0.0.1");
const rotateTokens = options.rotateTokens === true || (options.replace === true && options.keepTokens !== true);
const resolvedTokens = productionState ?? resolveHeadlessTokens({
envToken: env.OPENWORK_TOKEN,
envHostToken: env.OPENWORK_HOST_TOKEN,
previous: rotateTokens ? null : existing,
generate: randomUUID,
});
const serverConfigPath = productionState?.serverConfigPath
?? (options.name === "dev-headless" && env.OPENWORK_DEV_HEADLESS_WEB_CONFIG
? resolveHeadlessServerConfigPath(repoRoot, env.OPENWORK_DEV_HEADLESS_WEB_CONFIG)
: runtimePaths.serverConfigPath);
if (!productionState) {
const existingConfig = await readFile(serverConfigPath, "utf8").catch(() => null);
await writeFile(
serverConfigPath,
`${JSON.stringify(mergeHeadlessServerConfig(existingConfig, workspace), null, 2)}\n`,
"utf8",
);
}
const webLogPath = runtimePaths.webLogPath;
const headlessLogPath = runtimePaths.headlessLogPath;
const openworkUrl = `http://${clientHost}:${openworkPort}`;
const webUrl = `http://${clientHost}:${webPort}`;
const denProxyEnabled = env.OPENWORK_DEV_HEADLESS_WEB_DEN_PROXY === undefined
? true
: readBool(env.OPENWORK_DEV_HEADLESS_WEB_DEN_PROXY);
const denTarget = denProxyEnabled ? normalizeDenTarget(env.OPENWORK_DEV_DEN_PROXY_TARGET) : null;
const denApiUrl = denTarget ? `${webUrl}/api/den` : null;
const clientConnection = resolveHeadlessClientConnection({
state: options.state,
env,
openworkUrl,
openworkPort,
token: resolvedTokens.token,
});
const viteEnv: NodeJS.ProcessEnv = {
...env,
OPENWORK_DEV_MODE: "1",
HOST: viteHost,
PORT: String(webPort),
VITE_OPENWORK_URL: clientConnection.url,
VITE_OPENWORK_PORT: clientConnection.port,
VITE_OPENWORK_TOKEN: clientConnection.token,
VITE_OPENWORK_FORCE_ENV_SETTINGS: "1",
VITE_OPENWORK_DEPLOYMENT: env.VITE_OPENWORK_DEPLOYMENT ?? "web",
...(denTarget && denApiUrl ? {
OPENWORK_DEV_HEADLESS_DEN_TARGET: denTarget,
VITE_DEN_API_BASE_URL: env.VITE_DEN_API_BASE_URL ?? denApiUrl,
...(denTarget === DEFAULT_DEN_TARGET ? {} : { VITE_DEN_BASE_URL: env.VITE_DEN_BASE_URL ?? denTarget }),
} : {}),
};
const headlessEnv: NodeJS.ProcessEnv = {
...env,
OPENWORK_DEV_MODE: "1",
...(productionState ? installedProductionHeadlessEnv(productionState) : {}),
...(productionState ? {} : { OPENWORK_WORKSPACE: workspace }),
OPENWORK_HOST: host,
OPENWORK_REMOTE_ACCESS: remoteAccessEnabled ? "1" : "0",
OPENWORK_PORT: String(openworkPort),
OPENWORK_TOKEN: resolvedTokens.token,
OPENWORK_HOST_TOKEN: resolvedTokens.hostToken,
OPENWORK_SERVER_CONFIG: serverConfigPath,
OPENWORK_MANAGE_OPENCODE: "1",
OPENWORK_OPENCODE_BIN: env.OPENWORK_OPENCODE_BIN ?? "opencode",
};
const pnpmExecPath = env.npm_execpath?.trim();
const pnpmCommand = pnpmExecPath ? env.npm_node_execpath?.trim() || "node" : "pnpm";
const pnpmArgs = pnpmExecPath ? [pnpmExecPath] : [];
const acquiredChildren: ChildProcess[] = [];
let acquiredManifest: HeadlessRuntimeManifest | null = null;
let acquiredSupervisor: ChildProcess | null = null;
const launchId = randomUUID();
const provisionalManifest = (
webPid: number | null,
openworkServerPid: number | null,
): HeadlessRuntimeManifest => buildHeadlessRuntimeManifest({
webUrl,
openworkUrl,
workspace,
token: resolvedTokens.token,
hostToken: resolvedTokens.hostToken,
serverConfigPath,
runtimeManifestPath,
webLogPath,
headlessLogPath,
denTarget,
webPid,
openworkServerPid,
world: { name: options.name, state: options.state, launchId },
});
try {
const webProcess = spawnLogged(pnpmCommand, [...pnpmArgs,
"--filter",
"@openwork/app",
"exec",
"vite",
"--host",
viteHost,
"--port",
String(webPort),
"--strictPort",
], webLogPath, repoRoot, viteEnv);
acquiredChildren.push(webProcess);
acquiredManifest = provisionalManifest(webProcess.pid ?? null, null);
const serverLaunch = buildHeadlessServerLaunch(repoRoot, buildOpenworkServerArgs({
host,
port: openworkPort,
configPath: serverConfigPath,
corsOrigins: buildHeadlessCorsOrigins({ webUrl, webPort }),
}));
const serverProcess = spawnLogged(serverLaunch.command, serverLaunch.args, headlessLogPath, repoRoot, headlessEnv);
acquiredChildren.push(serverProcess);
let manifest = provisionalManifest(
webProcess.pid ?? null,
serverProcess.pid ?? null,
);
acquiredManifest = manifest;
await Promise.all([
waitForSpawn(webProcess, "headless web"),
waitForSpawn(serverProcess, "openwork-server"),
]);
await writeRuntimeManifest(manifest);
await waitForHealthy(manifest);
const supervised = await ensureRuntimeSupervisor(manifest, repoRoot);
manifest = supervised.manifest;
acquiredManifest = manifest;
acquiredSupervisor = supervised.supervisor;
const children = [...acquiredChildren];
const supervisor = acquiredSupervisor;
let stopped = false;
return {
manifest,
reused: false,
waitForExit: () => waitForOwnedRuntimeExit(manifest),
async detach() {
for (const child of children) child.unref();
supervisor?.unref();
},
async stop() {
if (stopped) return;
stopped = true;
await Promise.all(children.map(stopAcquiredChild));
if (supervisor) await stopAcquiredChild(supervisor);
await stopManifest(manifest);
},
};
} catch (error) {
const cleanupErrors: string[] = [];
for (const child of acquiredChildren) {
await stopAcquiredChild(child).catch((cleanupError: unknown) => {
cleanupErrors.push(messageText(cleanupError));
});
}
if (acquiredSupervisor) {
await stopAcquiredChild(acquiredSupervisor).catch((cleanupError: unknown) => {
cleanupErrors.push(messageText(cleanupError));
});
}
const failedManifest = acquiredManifest;
if (cleanupErrors.length > 0 && failedManifest) {
await writeRuntimeManifest(failedManifest).catch((manifestError: unknown) => {
cleanupErrors.push(`ownership manifest publish failed: ${messageText(manifestError)}`);
});
} else if (failedManifest) {
await removeRuntimeManifest(failedManifest);
} else if (cleanupErrors.length === 0) {
await unlink(runtimeManifestPath).catch(() => {});
}
const cleanupDetail = cleanupErrors.length === 0
? ""
: ` Cleanup could not confirm ownership release: ${cleanupErrors.join("; ")}.`;
throw new Error(`${messageText(error)} Headless server: ${openworkUrl}; web: ${webUrl}.${cleanupDetail}`);
}
}
+103
View File
@@ -0,0 +1,103 @@
import { chmod, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
import { basename, dirname, extname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
const SAFE_WORLD_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
export interface ScriptWorldSnapshot {
version: 1;
kind: "script";
name: string;
createdAt: string;
pid: number;
sourcePath: string;
outputs: Record<string, string>;
}
export interface HoldOptions {
name?: string;
outputs?: Record<string, string>;
snapshotDir?: string;
}
function recordedPid(text: string): number | undefined {
const value: unknown = JSON.parse(text);
if (typeof value !== "object" || value === null || !("pid" in value)) return undefined;
return typeof value.pid === "number" && Number.isInteger(value.pid) && value.pid > 0
? value.pid
: undefined;
}
function isAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
async function aliveSnapshotPid(path: string): Promise<number | undefined> {
try {
const pid = recordedPid(await readFile(path, "utf8"));
return pid !== undefined && isAlive(pid) ? pid : undefined;
} catch {
return undefined;
}
}
/** Keep a script world alive until SIGINT or SIGTERM, then release its resources. */
export async function hold(options: HoldOptions = {}): Promise<void> {
const sourcePath = process.argv[1];
const name = options.name ?? basename(sourcePath, extname(sourcePath));
if (!SAFE_WORLD_NAME.test(name)) {
throw new Error("World names must use only letters, numbers, dots, underscores, and hyphens.");
}
const snapshotDirectory = resolve(
options.snapshotDir
?? process.env.OPENWORK_WORLD_SNAPSHOT_DIR
?? join(REPO_ROOT, "evals", "results", ".worlds", "scripts"),
);
const snapshotPath = join(snapshotDirectory, `${name}.json`);
const existingPid = await aliveSnapshotPid(snapshotPath);
if (existingPid !== undefined) {
throw new Error(
`Script world ${JSON.stringify(name)} is already running (pid ${existingPid}); run \`pnpm world down ${name}\` first.`,
);
}
const outputs = options.outputs ?? {};
const snapshot: ScriptWorldSnapshot = {
version: 1,
kind: "script",
name,
createdAt: new Date().toISOString(),
pid: process.pid,
sourcePath,
outputs,
};
await mkdir(dirname(snapshotPath), { recursive: true });
await writeFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
await chmod(snapshotPath, 0o600);
for (const [key, value] of Object.entries(outputs)) console.log(`${key} ${value}`);
console.log(`World ${JSON.stringify(name)} is up. Ctrl-C (or pnpm world down ${name}) tears it down.`);
await new Promise<void>((done) => {
let stopping = false;
const stop = (): void => {
if (stopping) return;
stopping = true;
process.off("SIGINT", stop);
process.off("SIGTERM", stop);
void unlink(snapshotPath).then(done, (error: unknown) => {
console.error(`Could not remove script world snapshot ${snapshotPath}: ${error instanceof Error ? error.message : String(error)}`);
done();
});
};
process.once("SIGINT", stop);
process.once("SIGTERM", stop);
});
}
+8
View File
@@ -0,0 +1,8 @@
export * from "./loader.ts";
export * from "./store.ts";
export * from "./hold.ts";
export * from "./script-world.ts";
export * from "./cli.ts";
export * from "./headless-web-helpers.ts";
export * from "./headless-web.ts";
export * from "./standalone-cli.ts";
+53
View File
@@ -0,0 +1,53 @@
import { readdir, stat } from "node:fs/promises";
import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
export interface ScriptWorld {
kind: "script";
name: string;
path: string;
}
export function worldScriptName(path: string): string {
return basename(path, extname(path));
}
async function isFile(path: string): Promise<boolean> {
return stat(path).then((entry) => entry.isFile(), () => false);
}
export async function discoverWorlds(directory: string): Promise<ScriptWorld[]> {
let entries: string[];
try {
entries = await readdir(directory);
} catch {
return [];
}
return entries
.filter((name) => name.endsWith(".ts") && !name.endsWith(".test.ts"))
.sort()
.map((name) => ({ kind: "script", name: worldScriptName(name), path: join(directory, name) }));
}
export async function resolveWorldScript(
source: string,
options: { cwd: string; worldsDirectory: string },
): Promise<ScriptWorld> {
const pathLike = isAbsolute(source) || source.includes("/") || source.includes("\\");
const requestedPath = pathLike
? resolve(options.cwd, source)
: join(options.worldsDirectory, source.endsWith(".ts") ? source : `${source}.ts`);
if (!requestedPath.endsWith(".ts")) {
throw new Error(`World script ${JSON.stringify(source)} must be a TypeScript file.`);
}
if (await isFile(requestedPath)) {
return { kind: "script", name: worldScriptName(requestedPath), path: requestedPath };
}
const available = (await discoverWorlds(options.worldsDirectory)).map((world) => world.name);
throw new Error(`Unknown world script ${JSON.stringify(source)}. Available: ${available.join(", ") || "(none)"}.`);
}
export function displayWorldPath(path: string, cwd: string): string {
const displayed = relative(cwd, path);
return displayed.startsWith("..") ? path : displayed;
}
+243
View File
@@ -0,0 +1,243 @@
import { spawn } from "node:child_process";
import { access, chmod, mkdir, open, readFile, rm } from "node:fs/promises";
import { join, resolve } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { assertWorldName } from "./store.ts";
import type { ScriptWorldSnapshot } from "./hold.ts";
const DEFAULT_START_TIMEOUT_MS = 10 * 60 * 1_000;
const DOWN_TIMEOUT_MS = 60 * 1_000;
const POLL_INTERVAL_MS = 500;
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isStringRecord(value: unknown): value is Record<string, string> {
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
}
function recordedPid(text: string): number | undefined {
const value: unknown = JSON.parse(text);
if (!isRecord(value) || typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0) {
return undefined;
}
return value.pid;
}
export function parseScriptWorldSnapshot(text: string): ScriptWorldSnapshot {
const value: unknown = JSON.parse(text);
if (
!isRecord(value)
|| value.version !== 1
|| value.kind !== "script"
|| typeof value.name !== "string"
|| typeof value.createdAt !== "string"
|| typeof value.pid !== "number"
|| !Number.isInteger(value.pid)
|| value.pid <= 0
|| typeof value.sourcePath !== "string"
|| !isStringRecord(value.outputs)
) {
throw new Error("The file is not a valid script world snapshot.");
}
return {
version: 1,
kind: "script",
name: value.name,
createdAt: value.createdAt,
pid: value.pid,
sourcePath: value.sourcePath,
outputs: value.outputs,
};
}
export function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
export function scriptWorldSnapshotDirectory(repoRoot: string): string {
return resolve(
process.env.OPENWORK_WORLD_SNAPSHOT_DIR
?? join(repoRoot, "evals", "results", ".worlds", "scripts"),
);
}
export function scriptWorldSnapshotPath(directory: string, name: string): string {
assertWorldName(name);
return join(directory, `${name}.json`);
}
export function scriptWorldLogPath(directory: string, name: string): string {
assertWorldName(name);
return join(directory, `${name}.log`);
}
function isMissingFile(error: unknown): boolean {
return isRecord(error) && error.code === "ENOENT";
}
async function fileExists(path: string): Promise<boolean> {
return access(path).then(() => true, () => false);
}
export async function readScriptWorldSnapshot(path: string): Promise<ScriptWorldSnapshot | undefined> {
try {
return parseScriptWorldSnapshot(await readFile(path, "utf8"));
} catch (error) {
if (isMissingFile(error)) return undefined;
throw error;
}
}
async function removeDeadSnapshot(path: string): Promise<void> {
try {
const snapshot = await readScriptWorldSnapshot(path);
if (snapshot && !isProcessAlive(snapshot.pid)) await rm(path, { force: true });
} catch {
await rm(path, { force: true });
}
}
async function assertNoRunningSnapshot(path: string, name: string): Promise<void> {
let pid: number | undefined;
try {
pid = recordedPid(await readFile(path, "utf8"));
} catch (error) {
if (isMissingFile(error)) return;
await rm(path, { force: true });
return;
}
if (pid !== undefined && isProcessAlive(pid)) {
throw new Error(
`Script world ${JSON.stringify(name)} is already running (pid ${pid}); run \`pnpm world down ${name}\` first.`,
);
}
await rm(path, { force: true });
}
async function waitForChild(path: string, args: readonly string[], env: NodeJS.ProcessEnv): Promise<number> {
const ignoreSigint = (): void => {};
process.on("SIGINT", ignoreSigint);
try {
const child = spawn(process.execPath, [path, ...args], { env, stdio: "inherit" });
return await new Promise<number>((done, reject) => {
child.once("error", reject);
child.once("close", (code) => done(code ?? 1));
});
} finally {
process.off("SIGINT", ignoreSigint);
}
}
async function lastLogLines(path: string): Promise<string[]> {
try {
const text = await readFile(path, "utf8");
return text.trimEnd().split(/\r?\n/).slice(-40);
} catch {
return [];
}
}
export interface LaunchScriptWorldOptions {
path: string;
name: string;
args: readonly string[];
snapshotDirectory: string;
detach: boolean;
timeoutMs?: number;
print: (line: string) => void;
}
export async function launchScriptWorld(options: LaunchScriptWorldOptions): Promise<number> {
const path = resolve(options.path);
const snapshotPath = scriptWorldSnapshotPath(options.snapshotDirectory, options.name);
const env = { ...process.env, OPENWORK_WORLD_SNAPSHOT_DIR: options.snapshotDirectory };
await assertNoRunningSnapshot(snapshotPath, options.name);
if (!options.detach) return waitForChild(path, options.args, env);
const logPath = scriptWorldLogPath(options.snapshotDirectory, options.name);
await mkdir(options.snapshotDirectory, { recursive: true });
const log = await open(logPath, "w", 0o600);
await chmod(logPath, 0o600);
const child = await (async () => {
try {
return spawn(process.execPath, [path, ...options.args], {
detached: true,
env,
stdio: ["ignore", log.fd, log.fd],
});
} finally {
await log.close();
}
})();
let spawnError: Error | undefined;
child.once("error", (error) => { spawnError = error; });
child.unref();
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS);
while (Date.now() < deadline) {
if (spawnError) throw spawnError;
const snapshot = await readScriptWorldSnapshot(snapshotPath);
if (snapshot) {
for (const [key, value] of Object.entries(snapshot.outputs)) options.print(`${key} ${value}`);
options.print(`snapshot ${snapshotPath}`);
options.print(`log ${logPath}`);
return 0;
}
if (child.exitCode !== null || child.signalCode !== null) {
await removeDeadSnapshot(snapshotPath);
options.print(`Script world ${JSON.stringify(options.name)} exited before creating its snapshot.`);
for (const line of await lastLogLines(logPath)) options.print(line);
return 1;
}
await delay(Math.min(POLL_INTERVAL_MS, Math.max(1, deadline - Date.now())));
}
if (child.pid !== undefined) {
try { process.kill(-child.pid, "SIGTERM"); } catch {}
try { process.kill(child.pid, "SIGTERM"); } catch {}
}
await removeDeadSnapshot(snapshotPath);
options.print(`Timed out after ${options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS}ms waiting for script world ${JSON.stringify(options.name)}.`);
for (const line of await lastLogLines(logPath)) options.print(line);
return 1;
}
export interface DownScriptWorldResult {
found: boolean;
forced: boolean;
pid?: number;
}
export async function downScriptWorld(path: string): Promise<DownScriptWorldResult> {
const snapshot = await readScriptWorldSnapshot(path);
if (!snapshot) return { found: false, forced: false };
if (!isProcessAlive(snapshot.pid)) {
await rm(path, { force: true });
return { found: true, forced: false, pid: snapshot.pid };
}
try {
process.kill(snapshot.pid, "SIGTERM");
} catch {}
const deadline = Date.now() + DOWN_TIMEOUT_MS;
while (Date.now() < deadline) {
if (!await fileExists(path) && !isProcessAlive(snapshot.pid)) {
return { found: true, forced: false, pid: snapshot.pid };
}
await delay(Math.min(POLL_INTERVAL_MS, Math.max(1, deadline - Date.now())));
}
try { process.kill(-snapshot.pid, "SIGKILL"); } catch {}
try { process.kill(snapshot.pid, "SIGKILL"); } catch {}
await rm(path, { force: true });
return { found: true, forced: true, pid: snapshot.pid };
}
+9
View File
@@ -0,0 +1,9 @@
import { defaultWorldCliPaths, main } from "./cli.ts";
/** Standalone script-world CLI. */
export function runStandaloneWorldCli(
argv: string[] = process.argv.slice(2),
cwdInput: string = process.cwd(),
): Promise<number> {
return main(argv, defaultWorldCliPaths(cwdInput));
}
+78
View File
@@ -0,0 +1,78 @@
import { chmod, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
import { isAbsolute, join } from "node:path";
const SAFE_WORLD_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
export function assertWorldName(name: string): void {
if (!SAFE_WORLD_NAME.test(name)) {
throw new Error("World names must use only letters, numbers, dots, underscores, and hyphens.");
}
}
function worldFileName(name: string): string {
assertWorldName(name);
return `${name}.json`;
}
function isMissingFile(error: unknown): boolean {
return typeof error === "object"
&& error !== null
&& "code" in error
&& error.code === "ENOENT";
}
export class WorldStateStore {
readonly directory: string;
constructor(directory: string) {
if (!isAbsolute(directory)) throw new Error("World state directories must be absolute paths.");
this.directory = directory;
}
path(name: string): string {
return join(this.directory, worldFileName(name));
}
resolve(nameOrPath: string): string {
return nameOrPath.endsWith(".json") || nameOrPath.includes("/") || nameOrPath.includes("\\")
? nameOrPath
: this.path(nameOrPath);
}
async save(name: string, jsonText: string): Promise<string> {
const path = this.path(name);
await mkdir(this.directory, { recursive: true });
await writeFile(path, jsonText.endsWith("\n") ? jsonText : `${jsonText}\n`, {
encoding: "utf8",
mode: 0o600,
});
await chmod(path, 0o600);
return path;
}
read(nameOrPath: string): Promise<string> {
return readFile(this.resolve(nameOrPath), "utf8");
}
async list(): Promise<string[]> {
try {
return (await readdir(this.directory))
.filter((name) => name.endsWith(".json"))
.sort()
.map((name) => join(this.directory, name));
} catch (error) {
if (isMissingFile(error)) return [];
throw error;
}
}
async forget(name: string): Promise<boolean> {
try {
await unlink(this.path(name));
return true;
} catch (error) {
if (isMissingFile(error)) return false;
throw error;
}
}
}
+105
View File
@@ -0,0 +1,105 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { main, parseWorldArgs } from "../src/cli.ts";
import { discoverWorlds, resolveWorldScript } from "../src/loader.ts";
test("world arguments expose only script lifecycle flags and forward arguments after --", () => {
assert.deepEqual(
parseWorldArgs([
"up",
"./worlds/dev-headless.ts",
"--detach",
"--timeout",
"5000",
"--",
"--replace",
"value",
]),
{
kind: "up",
source: "./worlds/dev-headless.ts",
detach: true,
timeoutMs: 5000,
args: ["--replace", "value"],
},
);
const foregroundTimeout = parseWorldArgs(["up", "dev-headless", "--timeout", "5000"]);
assert.equal(foregroundTimeout.kind, "help");
if (foregroundTimeout.kind !== "help") throw new Error("expected help");
assert.match(foregroundTimeout.error ?? "", /only with --detach/);
const oldFlag = parseWorldArgs(["up", "dev-headless", "--keep"]);
assert.equal(oldFlag.kind, "help");
if (oldFlag.kind !== "help") throw new Error("expected help");
assert.match(oldFlag.error ?? "", /Unknown world CLI option "--keep"/);
const oldCommand = parseWorldArgs(["resume", "dev-headless"]);
assert.equal(oldCommand.kind, "help");
if (oldCommand.kind !== "help") throw new Error("expected help");
assert.match(oldCommand.error ?? "", /Unknown command "resume"/);
});
test("discovery, resolution, list, and help classify scripts without importing them", async () => {
const root = await mkdtemp(join(tmpdir(), "openwork-world-cli-discovery-"));
try {
const worldsDirectory = join(root, "worlds");
const fixturePath = join(worldsDirectory, "throwing.ts");
await mkdir(worldsDirectory);
await writeFile(fixturePath, 'throw new Error("must not import");\n', "utf8");
assert.deepEqual(await discoverWorlds(worldsDirectory), [{
kind: "script",
name: "throwing",
path: fixturePath,
}]);
for (const source of ["throwing", "throwing.ts", fixturePath]) {
assert.deepEqual(await resolveWorldScript(source, { cwd: root, worldsDirectory }), {
kind: "script",
name: "throwing",
path: fixturePath,
});
}
for (const command of [["list"], ["help"]]) {
const lines: string[] = [];
assert.equal(await main(command, {
cwd: root,
worldsDirectory,
print: (line) => lines.push(line),
}), 0);
assert.match(lines.join("\n"), /world scripts/i);
assert.match(lines.join("\n"), /throwing/);
}
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("foreground scripts receive argv after -- and mirror their exit code", async () => {
const root = await mkdtemp(join(tmpdir(), "openwork-world-cli-argv-"));
try {
const worldsDirectory = join(root, "worlds");
const outputPath = join(root, "argv.json");
await mkdir(worldsDirectory);
await writeFile(join(worldsDirectory, "argv.ts"), `
import { writeFile } from "node:fs/promises";
await writeFile(process.argv[2], JSON.stringify(process.argv.slice(3)), "utf8");
process.exitCode = 7;
`, "utf8");
const code = await main(["up", "argv", "--", outputPath, "--detach", "plain"], {
cwd: root,
worldsDirectory,
print: () => {},
});
assert.equal(code, 7);
const recordedArgs: unknown = JSON.parse(await readFile(outputPath, "utf8"));
assert.deepEqual(recordedArgs, ["--detach", "plain"]);
} finally {
await rm(root, { recursive: true, force: true });
}
});
@@ -0,0 +1,137 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import test from "node:test";
import {
assertHeadlessLaunchSafety,
installedProductionHeadlessEnv,
resolveHeadlessClientConnection,
resolveInstalledProductionHeadlessState,
resolveHeadlessWorldRuntimePaths,
} from "../src/headless-web.ts";
test("headless production state resolves installed stores and credentials without copying them", async () => {
const root = await mkdtemp(join(tmpdir(), "openwork-headless-production-"));
try {
const dataDir = join(root, ".openwork", "openwork-server");
const configDir = join(root, ".config", "openwork");
const opencodeConfigDir = join(root, ".config", "opencode");
const userDataDir = join(root, "Library", "Application Support", "com.differentai.openwork");
const opencodeDb = join(root, "Library", "Application Support", "opencode", "opencode.db");
await mkdir(dataDir, { recursive: true });
await mkdir(configDir, { recursive: true });
await mkdir(opencodeConfigDir, { recursive: true });
await mkdir(userDataDir, { recursive: true });
await mkdir(dirname(opencodeDb), { recursive: true });
await writeFile(opencodeDb, "fixture", "utf8");
for (const name of ["desktop-bootstrap.json", "env.json", "server.json"]) {
await writeFile(join(configDir, name), "{}\n", "utf8");
}
await writeFile(join(userDataDir, "openwork-server-state.json"), "{}\n", "utf8");
await writeFile(join(userDataDir, "openwork-workspaces.json"), JSON.stringify({
workspaces: [{ path: "/Users/me/production-workspace" }],
}), "utf8");
await writeFile(join(userDataDir, "openwork-server-tokens.json"), JSON.stringify({
version: 2,
credentials: { clientToken: "client-token", hostToken: "host-token" },
}), "utf8");
const state = await resolveInstalledProductionHeadlessState({
env: {},
fallbackWorkspace: "/fallback",
homeDir: root,
platform: "darwin",
});
assert.equal(state.workspace, "/Users/me/production-workspace");
assert.equal(state.token, "client-token");
assert.equal(state.hostToken, "host-token");
assert.equal(state.opencodeDb, opencodeDb);
const env = installedProductionHeadlessEnv(state);
assert.equal(env.OPENWORK_DATA_DIR, dataDir);
assert.equal(env.OPENWORK_SERVER_CONFIG, join(configDir, "server.json"));
assert.equal(env.OPENCODE_DB, opencodeDb);
assert.equal(env.OPENWORK_DEV_SHARED_STATE, "1");
assert.equal(env.OPENWORK_DESKTOP_DISABLE_WORKSPACE_RECOVERY, "0");
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("headless production state remains macOS-only", async () => {
await assert.rejects(
() => resolveInstalledProductionHeadlessState({
env: {},
fallbackWorkspace: "/tmp",
homeDir: "/tmp",
platform: "linux",
}),
/supported only on macOS/,
);
});
test("headless production state refuses every remote exposure setting", () => {
for (const env of [
{ OPENWORK_REMOTE_ACCESS: "1" },
{ OPENWORK_PUBLIC_HOST: "dev.example.com" },
{ HOST: "0.0.0.0" },
{ VITE_HOST: "192.0.2.10" },
]) {
assert.throws(
() => assertHeadlessLaunchSafety("installed-production", env),
/require loopback-only access/,
);
}
assert.doesNotThrow(() => assertHeadlessLaunchSafety("installed-production", {
HOST: "127.0.0.1",
VITE_HOST: "localhost",
}));
assert.doesNotThrow(() => assertHeadlessLaunchSafety("isolated", {
OPENWORK_REMOTE_ACCESS: "1",
}));
});
test("headless production state pins browser credentials to its generated loopback server", () => {
const production = resolveHeadlessClientConnection({
state: "installed-production",
env: {
VITE_OPENWORK_URL: "https://attacker.example",
VITE_OPENWORK_PORT: "443",
VITE_OPENWORK_TOKEN: "attacker-selected-token",
},
openworkUrl: "http://127.0.0.1:8778",
openworkPort: 8778,
token: "installed-production-token",
});
assert.deepEqual(production, {
url: "http://127.0.0.1:8778",
port: "8778",
token: "installed-production-token",
});
const isolated = resolveHeadlessClientConnection({
state: "isolated",
env: { VITE_OPENWORK_URL: "http://127.0.0.1:9999" },
openworkUrl: "http://127.0.0.1:8778",
openworkPort: 8778,
token: "isolated-token",
});
assert.equal(isolated.url, "http://127.0.0.1:9999");
});
test("named headless worlds own separate runtime paths while the compatibility world keeps legacy paths", () => {
const repoRoot = "/repo/openwork";
const compatibility = resolveHeadlessWorldRuntimePaths(repoRoot, "dev-headless");
const production = resolveHeadlessWorldRuntimePaths(repoRoot, "headless-prod-live");
assert.equal(compatibility.runtimeManifestPath, join(repoRoot, "tmp", "dev-headless-web.json"));
assert.equal(production.runtimeManifestPath, join(
repoRoot,
"tmp",
"worlds",
"runtime",
"headless-prod-live",
"runtime.json",
));
assert.notEqual(production.serverConfigPath, compatibility.serverConfigPath);
});
+21
View File
@@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import { mkdtemp, rm, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { WorldStateStore } from "../src/store.ts";
test("local world state is owner-only and addressable by world name", async () => {
const root = await mkdtemp(join(tmpdir(), "openwork-world-store-"));
try {
const store = new WorldStateStore(join(root, "worlds"));
const path = await store.save("demo", '{"name":"demo"}');
assert.equal((await stat(path)).mode & 0o777, 0o600);
assert.equal(await store.read("demo"), '{"name":"demo"}\n');
assert.deepEqual(await store.list(), [path]);
assert.equal(await store.forget("demo"), true);
assert.equal(await store.forget("demo"), false);
} finally {
await rm(root, { recursive: true, force: true });
}
});
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"strict": true,
"noEmit": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2023",
"lib": ["ES2023", "ESNext.Disposable"],
"allowImportingTsExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": ["node"]
},
"include": ["src/**/*.ts", "test/**/*.ts"]
}