Files
zopu-code/scripts/orb-proof.ts
-Puter 8ddecc098f fix(agents/orb): use standard SandboxAgent client for AgentOS sandbox
The in-process DockerSandboxClient could never satisfy AgentOS 0.2.10's
sandbox serialization contract: AgentOS serializes sandbox mounts through
getSerializableClientConfig, which reads client.baseUrl and passes it to the
sidecar. An in-process object has no network endpoint.

Replace the custom adapter with the supported boundary: SandboxAgent.start
with sandbox-agent/docker, which starts a sandbox-agent server inside a
Docker container with a dynamically mapped host port and returns a
SandboxAgent client whose baseUrl both the main process and the sidecar
subprocess reach over 127.0.0.1. Dispose calls destroySandbox so no
containers are left behind.

Remove 700+ lines of dead DockerSandboxClient infrastructure (PID tracking,
log files, signal handling) now handled natively by the sandbox-agent
server. Add a sqlite_file database descriptor to AgentOs.create so session
storage works. Wrap command execution in sh -c for the SandboxAgent API.
Apply chmod 600 to the real OpenCode config path containing the gateway key.

The real proof with the main .env now passes all three stages:
ORB_STAGE_DOCKER_OK, ORB_STAGE_AGENTOS_OK, ORB_STAGE_MODEL_TURN_OK,
ending ORB_PROOF_PASSED with zero leftover containers.

Update README to describe the real SandboxAgent + Docker topology and
remove the misleading 'missing sidecar' limitation.
2026-07-24 22:38:11 +05:30

371 lines
12 KiB
TypeScript

/**
* Orb Runtime Proof Fixture
*
* Opt-in integration proof that exercises the Orb lifecycle in distinct,
* independently reported stages, each with a stable marker:
*
* STAGE 1 Docker sandbox lifecycle ORB_STAGE_DOCKER_OK / _BLOCKED
* STAGE 2 AgentOS VM + OpenCode session ORB_STAGE_AGENTOS_OK / _BLOCKED
* STAGE 3 Model turn via the gateway ORB_STAGE_MODEL_TURN_OK / _BLOCKED
*
* A missing or unreachable model gateway is BLOCKED, never passed. The proof
* only prints ORB_PROOF_PASSED when the model turn completes. It never prints
* gateway secrets; only provider/model identifiers and reachability labels.
*
* Gateway credentials resolve from ORB_GATEWAY_* with safe fallbacks to the
* shared AGENT_MODEL_* variables. Container removal is always verified after
* disposal.
*
* Usage:
* ORB_PROOF=1 \
* ORB_GATEWAY_API_KEY=... \
* ORB_GATEWAY_BASE_URL=https://ai.example.com/v1 \
* ORB_GATEWAY_MODEL=glm-5.2 \
* ORB_GATEWAY_PROVIDER=cheaptricks \
* bun run scripts/orb-proof.ts
*/
/* eslint-disable no-console -- proof fixture is a CLI tool */
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { Effect } from "effect";
import { DockerSandboxProvider } from "../packages/agents/src/orb/docker-sandbox";
import type { DockerSandboxOptions } from "../packages/agents/src/orb/docker-sandbox";
import type { OrbEvent } from "../packages/agents/src/orb/events";
import { OrbRuntime } from "../packages/agents/src/orb/runtime";
import type { OrbHandle } from "../packages/agents/src/orb/runtime";
const MARKER = {
blocked: "ORB_PROOF_BLOCKED",
failed: "ORB_PROOF_FAILED",
passed: "ORB_PROOF_PASSED",
} as const;
const STAGE = {
agentosBlocked: "ORB_STAGE_AGENTOS_BLOCKED",
agentosOk: "ORB_STAGE_AGENTOS_OK",
dockerBlocked: "ORB_STAGE_DOCKER_BLOCKED",
dockerOk: "ORB_STAGE_DOCKER_OK",
modelBlocked: "ORB_STAGE_MODEL_TURN_BLOCKED",
modelOk: "ORB_STAGE_MODEL_TURN_OK",
} as const;
interface Gateway {
apiKey: string;
baseUrl: string;
model: string;
provider: string;
}
const envValue = (key: string): string | undefined => process.env[key];
const resolveGateway = (): Gateway | null => {
const apiKey =
envValue("ORB_GATEWAY_API_KEY") ?? envValue("AGENT_MODEL_API_KEY");
const baseUrl =
envValue("ORB_GATEWAY_BASE_URL") ?? envValue("AGENT_MODEL_BASE_URL");
const model = envValue("ORB_GATEWAY_MODEL") ?? envValue("AGENT_MODEL_NAME");
const provider =
envValue("ORB_GATEWAY_PROVIDER") ?? envValue("AGENT_MODEL_PROVIDER");
if (
!apiKey?.trim() ||
!baseUrl?.trim() ||
!model?.trim() ||
!provider?.trim()
) {
return null;
}
return { apiKey, baseUrl, model, provider };
};
const runCli = async (args: readonly string[]): Promise<string> => {
try {
const proc = Bun.spawn([...args], { stderr: "pipe", stdout: "pipe" });
const stdout = await new Response(proc.stdout).text();
await proc.exited;
return stdout;
} catch {
return "";
}
};
const dockerVersion = async (): Promise<string | null> => {
const out = await runCli([
"docker",
"version",
"--format",
"{{.Server.Version}}",
]);
const trimmed = out.trim();
return trimmed.length > 0 ? trimmed : null;
};
const TINY_PROJECT: Record<string, string> = {
"index.test.ts": `import { test } from "node:test";\nimport assert from "node:assert/strict";\nimport { add } from "./index.ts";\n\ntest("add", () => {\n assert.equal(add(1, 2), 3);\n});\n`,
"index.ts": `export function add(a: number, b: number): number {\n return a + b;\n}\n`,
"package.json": `{"name":"tiny","type":"module","scripts":{"test":"node --test"}}\n`,
};
const prepareProject = async (hostWorkspace: string): Promise<void> => {
await mkdir(path.join(hostWorkspace, "repository"), { recursive: true });
for (const [file, content] of Object.entries(TINY_PROJECT)) {
// eslint-disable-next-line no-await-in-loop -- sequential file writes are intentional
await writeFile(path.join(hostWorkspace, "repository", file), content);
}
};
// ---------------------------------------------------------------------------
// STAGE 1 — Docker sandbox via SandboxAgent (standalone, no sidecar required)
// ---------------------------------------------------------------------------
const stageDocker = async (image: string | undefined): Promise<boolean> => {
const version = await dockerVersion();
if (!version) {
console.log("[docker] Docker daemon not available");
console.log(STAGE.dockerBlocked);
return false;
}
console.log(`[docker] server version ${version}`);
const hostWorkspace = `/tmp/orb-proof-docker-${Date.now()}`;
const options: DockerSandboxOptions =
image === undefined
? { hostWorkspacePath: hostWorkspace }
: { hostWorkspacePath: hostWorkspace, image };
try {
await prepareProject(hostWorkspace);
const provider = await Effect.runPromise(
DockerSandboxProvider.create(options)
);
const client = await provider.start();
const { baseUrl } = client as unknown as { baseUrl: string };
console.log(`[docker] SandboxAgent baseUrl: ${baseUrl}`);
console.log("[docker] running npm install in sandbox...");
const install = await client.runProcess({
args: ["-c", "npm install"],
command: "sh",
cwd: "/home/sandbox/repository",
timeoutMs: 60_000,
});
console.log(` npm install exit: ${install.exitCode}`);
await provider.dispose();
if (install.exitCode !== 0) {
console.log("[docker] sandbox lifecycle did not complete cleanly");
console.log(STAGE.dockerBlocked);
return false;
}
console.log(STAGE.dockerOk);
return true;
} catch (error) {
console.log(
`[docker] stage failed: ${error instanceof Error ? error.message : String(error)}`
);
console.log(STAGE.dockerBlocked);
return false;
}
};
// ---------------------------------------------------------------------------
// STAGE 2 — AgentOS VM + OpenCode session
// ---------------------------------------------------------------------------
const stageAgentOs = async (
gateway: Gateway,
image: string | undefined,
events: OrbEvent[]
): Promise<{ container: string; handle: OrbHandle } | null> => {
const container = `orb-proof-${Date.now()}`;
const hostWorkspace = `/tmp/orb-proof-${Date.now()}`;
const runtime = new OrbRuntime(
image === undefined ? {} : { dockerImage: image }
);
const createResult = await Effect.runPromise(
Effect.match(
runtime.createOrb({
context: {
artifacts: [],
contextFiles: [],
issueBody: "Run npm test and report the result.",
issueTitle: "Proof: run tests",
},
docker: { hostWorkspacePath: hostWorkspace },
gateway,
identity: {
projectId: "proof",
runId: `proof-${Date.now()}`,
workUnitId: "proof-wu",
},
}),
{
onFailure: (error) => ({ error, ok: false as const }),
onSuccess: (handle) => ({ handle, ok: true as const }),
}
)
);
if (!createResult.ok) {
console.log(
`[agentos] creation failed (${createResult.error.reason}): ${createResult.error.message}`
);
console.log(STAGE.agentosBlocked);
return null;
}
const { handle } = createResult;
handle.onEvent((event) => {
events.push(event);
const summary = (event.text ?? event.command ?? event.type).slice(0, 160);
console.log(` [event] ${event.type}: ${summary}`);
});
try {
const vm = handle.vmInstance;
if (!vm) {
throw new Error("AgentOS VM is not attached");
}
const agents = await vm.listAgents();
const opencode = agents.find((a) => a.id === "opencode" && a.installed);
if (!opencode) {
throw new Error("OpenCode agent is not installed in the VM");
}
console.log("[agentos] OpenCode agent available");
await Effect.runPromise(handle.openSession());
console.log(`[agentos] session opened: ${handle.currentSessionId ?? "?"}`);
console.log(STAGE.agentosOk);
return { container, handle };
} catch (error) {
console.log(
`[agentos] stage failed: ${error instanceof Error ? error.message : String(error)}`
);
await Effect.runPromise(handle.dispose().pipe(Effect.ignore)).catch(() => {
// best-effort cleanup
});
console.log(STAGE.agentosBlocked);
return null;
}
};
// ---------------------------------------------------------------------------
// STAGE 3 — Model turn through the gateway
// ---------------------------------------------------------------------------
const stageModelTurn = async (handle: OrbHandle): Promise<boolean> => {
const vm = handle.vmInstance;
const sessionId = handle.currentSessionId;
if (!vm || !sessionId) {
console.log("[model] no active session to drive a model turn");
console.log(STAGE.modelBlocked);
return false;
}
try {
console.log("[model] sending bounded turn to gateway...");
const result = await vm.prompt({
content: [
{ text: "Reply with exactly: ORB_PROOF_MODEL_ECHO", type: "text" },
],
sessionId,
});
console.log(
`[model] turn completed (stopReason=${result.stopReason ?? "?"})`
);
console.log(STAGE.modelOk);
return true;
} catch (error) {
// An unreachable/missing gateway surfaces here as a prompt failure.
console.log(
`[model] gateway turn failed: ${error instanceof Error ? error.message : String(error)}`
);
console.log(STAGE.modelBlocked);
return false;
}
};
// ---------------------------------------------------------------------------
// Orchestration
// ---------------------------------------------------------------------------
const runProof = async (): Promise<number> => {
const image = envValue("ORB_DOCKER_IMAGE");
const events: OrbEvent[] = [];
// STAGE 1 — Docker sandbox.
console.log("[stage 1] Docker sandbox lifecycle...");
const dockerOk = await stageDocker(image);
if (!dockerOk) {
console.log(`\n${MARKER.blocked}`);
return 2;
}
// STAGE 2 + 3 — AgentOS/OpenCode + model turn.
const gateway = resolveGateway();
if (!gateway) {
console.error(
"[gateway] missing config: set ORB_GATEWAY_* (or AGENT_MODEL_* fallbacks)."
);
console.log(STAGE.agentosBlocked);
console.log(`\n${MARKER.blocked}`);
return 2;
}
// Identifiers only — never the key or base URL.
console.log(`[gateway] provider=${gateway.provider} model=${gateway.model}`);
console.log("[stage 2] AgentOS VM + OpenCode session...");
const orb = await stageAgentOs(gateway, image, events);
if (!orb) {
console.log(`\n${MARKER.blocked}`);
return 2;
}
console.log("[stage 3] model turn...");
let modelOk = false;
try {
modelOk = await stageModelTurn(orb.handle);
} finally {
console.log("[dispose] disposing Orb...");
await Effect.runPromise(orb.handle.dispose().pipe(Effect.ignore)).catch(
() => {
// best-effort cleanup
}
);
console.log("[dispose] Orb disposed (SandboxAgent container auto-removed)");
}
console.log(
`\nStreamed ${events.length} normalized events (${[...new Set(events.map((e) => e.type))].join(", ")}).`
);
if (modelOk) {
console.log(`\n${MARKER.passed}`);
return 0;
}
console.log(`\n${MARKER.blocked}`);
return 2;
};
// Entry point
if (envValue("ORB_PROOF") !== "1") {
console.error(
"Set ORB_PROOF=1 to run the proof fixture (requires Docker + model gateway)."
);
console.error(MARKER.blocked);
process.exit(2);
}
(async () => {
try {
const code = await runProof();
process.exit(code);
} catch (error) {
console.error("[proof] unexpected failure:", error);
console.error(MARKER.failed);
process.exit(1);
}
})();