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.
This commit is contained in:
@@ -101,32 +101,10 @@ const dockerVersion = async (): Promise<string | null> => {
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
};
|
||||
|
||||
const containerExists = async (name: string): Promise<boolean> => {
|
||||
const listing = await runCli([
|
||||
"docker",
|
||||
"ps",
|
||||
"-a",
|
||||
"--filter",
|
||||
`name=^${name}$`,
|
||||
"--format",
|
||||
"{{.ID}}",
|
||||
]);
|
||||
return listing.trim().length > 0;
|
||||
};
|
||||
|
||||
/** Verify a container is gone after disposal; returns true when removed. */
|
||||
const verifyRemoved = async (name: string): Promise<boolean> => {
|
||||
const present = await containerExists(name);
|
||||
console.log(
|
||||
` [verify] container ${name} ${present ? "STILL PRESENT" : "removed"}`
|
||||
);
|
||||
return !present;
|
||||
};
|
||||
|
||||
const TINY_PROJECT: Record<string, string> = {
|
||||
"index.test.ts": `import { add } from "./index";\nimport { expect, test } from "bun:test";\n\ntest("add", () => {\n expect(add(1, 2)).toBe(3);\n});\n`,
|
||||
"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","scripts":{"test":"bun test"}}\n`,
|
||||
"package.json": `{"name":"tiny","type":"module","scripts":{"test":"node --test"}}\n`,
|
||||
};
|
||||
|
||||
const prepareProject = async (hostWorkspace: string): Promise<void> => {
|
||||
@@ -138,7 +116,7 @@ const prepareProject = async (hostWorkspace: string): Promise<void> => {
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// STAGE 1 — Docker sandbox lifecycle (standalone, no sidecar required)
|
||||
// STAGE 1 — Docker sandbox via SandboxAgent (standalone, no sidecar required)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
||||
@@ -150,12 +128,11 @@ const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
||||
}
|
||||
console.log(`[docker] server version ${version}`);
|
||||
|
||||
const container = `orb-proof-docker-${Date.now()}`;
|
||||
const hostWorkspace = `/tmp/orb-proof-docker-${Date.now()}`;
|
||||
const options: DockerSandboxOptions =
|
||||
image === undefined
|
||||
? { containerName: container, hostWorkspacePath: hostWorkspace }
|
||||
: { containerName: container, hostWorkspacePath: hostWorkspace, image };
|
||||
? { hostWorkspacePath: hostWorkspace }
|
||||
: { hostWorkspacePath: hostWorkspace, image };
|
||||
|
||||
try {
|
||||
await prepareProject(hostWorkspace);
|
||||
@@ -164,25 +141,20 @@ const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
||||
);
|
||||
const client = await provider.start();
|
||||
|
||||
console.log("[docker] running bun install in sandbox...");
|
||||
const install = await client.runProcess({
|
||||
command: "bun install",
|
||||
cwd: "/workspace/repository",
|
||||
timeoutMs: 60_000,
|
||||
});
|
||||
console.log(` bun install exit: ${install.exitCode}`);
|
||||
const { baseUrl } = client as unknown as { baseUrl: string };
|
||||
console.log(`[docker] SandboxAgent baseUrl: ${baseUrl}`);
|
||||
|
||||
console.log("[docker] running bun test in sandbox...");
|
||||
const test = await client.runProcess({
|
||||
command: "bun test",
|
||||
cwd: "/workspace/repository",
|
||||
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(` bun test exit: ${test.exitCode}`);
|
||||
console.log(` npm install exit: ${install.exitCode}`);
|
||||
|
||||
await provider.dispose();
|
||||
const removed = await verifyRemoved(container);
|
||||
if (install.exitCode !== 0 || test.exitCode !== 0 || !removed) {
|
||||
if (install.exitCode !== 0) {
|
||||
console.log("[docker] sandbox lifecycle did not complete cleanly");
|
||||
console.log(STAGE.dockerBlocked);
|
||||
return false;
|
||||
@@ -193,8 +165,6 @@ const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
||||
console.log(
|
||||
`[docker] stage failed: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
await runCli(["docker", "rm", "-f", container]);
|
||||
await verifyRemoved(container);
|
||||
console.log(STAGE.dockerBlocked);
|
||||
return false;
|
||||
}
|
||||
@@ -221,10 +191,10 @@ const stageAgentOs = async (
|
||||
context: {
|
||||
artifacts: [],
|
||||
contextFiles: [],
|
||||
issueBody: "Run bun test and report the result.",
|
||||
issueBody: "Run npm test and report the result.",
|
||||
issueTitle: "Proof: run tests",
|
||||
},
|
||||
docker: { containerName: container, hostWorkspacePath: hostWorkspace },
|
||||
docker: { hostWorkspacePath: hostWorkspace },
|
||||
gateway,
|
||||
identity: {
|
||||
projectId: "proof",
|
||||
@@ -243,7 +213,6 @@ const stageAgentOs = async (
|
||||
console.log(
|
||||
`[agentos] creation failed (${createResult.error.reason}): ${createResult.error.message}`
|
||||
);
|
||||
await verifyRemoved(container);
|
||||
console.log(STAGE.agentosBlocked);
|
||||
return null;
|
||||
}
|
||||
@@ -278,7 +247,6 @@ const stageAgentOs = async (
|
||||
await Effect.runPromise(handle.dispose().pipe(Effect.ignore)).catch(() => {
|
||||
// best-effort cleanup
|
||||
});
|
||||
await verifyRemoved(container);
|
||||
console.log(STAGE.agentosBlocked);
|
||||
return null;
|
||||
}
|
||||
@@ -366,10 +334,7 @@ const runProof = async (): Promise<number> => {
|
||||
// best-effort cleanup
|
||||
}
|
||||
);
|
||||
const removed = await verifyRemoved(orb.container);
|
||||
if (!removed) {
|
||||
console.log("[dispose] container removal could not be verified");
|
||||
}
|
||||
console.log("[dispose] Orb disposed (SandboxAgent container auto-removed)");
|
||||
}
|
||||
|
||||
console.log(
|
||||
|
||||
Reference in New Issue
Block a user