mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 00:03:04 +00:00
Add full Docker image defaults, fix actor deadlocks, and improve dev experience
- Add Dockerfile.full and --all flag to install-agent CLI for pre-built images
- Centralize Docker image constant (FULL_IMAGE) pinned to 0.3.1-full
- Remove examples/shared/Dockerfile{,.dev} and daytona snapshot example
- Expand Docker docs with full runnable Dockerfile
- Fix self-deadlock in createWorkbenchSession (fire-and-forget provisioning)
- Audit and convert 12 task actions from wait:true to wait:false
- Add bun --hot for dev backend hot reload
- Remove --force from pnpm install in dev Dockerfile for faster startup
- Add env_file support to compose.dev.yaml for automatic credential loading
- Add mock frontend compose config and dev panel
- Update CLAUDE.md with wait:true policy and dev environment setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
58c54156f1
commit
110e969f98
29 changed files with 804 additions and 283 deletions
|
|
@ -1,5 +0,0 @@
|
|||
FROM node:22-bookworm-slim
|
||||
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates > /dev/null 2>&1 && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
npm install -g --silent @sandbox-agent/cli@latest && \
|
||||
sandbox-agent install-agent claude
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
FROM node:22-bookworm-slim AS frontend
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
WORKDIR /build
|
||||
|
||||
# Copy workspace root config
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
|
||||
# Copy packages needed for the inspector build chain:
|
||||
# inspector -> sandbox-agent SDK -> acp-http-client, cli-shared, persist-indexeddb
|
||||
COPY sdks/typescript/ sdks/typescript/
|
||||
COPY sdks/acp-http-client/ sdks/acp-http-client/
|
||||
COPY sdks/cli-shared/ sdks/cli-shared/
|
||||
COPY sdks/persist-indexeddb/ sdks/persist-indexeddb/
|
||||
COPY sdks/react/ sdks/react/
|
||||
COPY frontend/packages/inspector/ frontend/packages/inspector/
|
||||
COPY docs/openapi.json docs/
|
||||
|
||||
# Create stub package.json for workspace packages referenced in pnpm-workspace.yaml
|
||||
# but not needed for the inspector build (avoids install errors).
|
||||
RUN set -e; for dir in \
|
||||
sdks/cli sdks/gigacode \
|
||||
sdks/persist-postgres sdks/persist-sqlite sdks/persist-rivet \
|
||||
resources/agent-schemas resources/vercel-ai-sdk-schemas \
|
||||
scripts/release scripts/sandbox-testing \
|
||||
examples/shared examples/docker examples/e2b examples/vercel \
|
||||
examples/daytona examples/cloudflare examples/file-system \
|
||||
examples/mcp examples/mcp-custom-tool \
|
||||
examples/skills examples/skills-custom-tool \
|
||||
frontend/packages/website; do \
|
||||
mkdir -p "$dir"; \
|
||||
printf '{"name":"@stub/%s","private":true,"version":"0.0.0"}\n' "$(basename "$dir")" > "$dir/package.json"; \
|
||||
done; \
|
||||
for parent in sdks/cli/platforms sdks/gigacode/platforms; do \
|
||||
for plat in darwin-arm64 darwin-x64 linux-arm64 linux-x64 win32-x64; do \
|
||||
mkdir -p "$parent/$plat"; \
|
||||
printf '{"name":"@stub/%s-%s","private":true,"version":"0.0.0"}\n' "$(basename "$parent")" "$plat" > "$parent/$plat/package.json"; \
|
||||
done; \
|
||||
done
|
||||
|
||||
RUN pnpm install --no-frozen-lockfile
|
||||
ENV SKIP_OPENAPI_GEN=1
|
||||
RUN pnpm --filter sandbox-agent build && \
|
||||
pnpm --filter @sandbox-agent/inspector build
|
||||
|
||||
FROM rust:1.88.0-bookworm AS builder
|
||||
WORKDIR /build
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY server/ ./server/
|
||||
COPY gigacode/ ./gigacode/
|
||||
COPY resources/agent-schemas/artifacts/ ./resources/agent-schemas/artifacts/
|
||||
COPY scripts/agent-configs/ ./scripts/agent-configs/
|
||||
COPY --from=frontend /build/frontend/packages/inspector/dist/ ./frontend/packages/inspector/dist/
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=/build/target \
|
||||
cargo build -p sandbox-agent --release && \
|
||||
cp target/release/sandbox-agent /sandbox-agent
|
||||
|
||||
FROM node:22-bookworm-slim
|
||||
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates > /dev/null 2>&1 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /sandbox-agent /usr/local/bin/sandbox-agent
|
||||
RUN sandbox-agent install-agent claude
|
||||
|
|
@ -6,10 +6,10 @@ import { PassThrough } from "node:stream";
|
|||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const EXAMPLE_IMAGE = "sandbox-agent-examples:latest";
|
||||
const EXAMPLE_IMAGE_DEV = "sandbox-agent-examples-dev:latest";
|
||||
const DOCKERFILE_DIR = path.resolve(__dirname, "..");
|
||||
const REPO_ROOT = path.resolve(DOCKERFILE_DIR, "../..");
|
||||
const REPO_ROOT = path.resolve(__dirname, "..", "..", "..");
|
||||
|
||||
/** Pre-built Docker image with all agents installed. */
|
||||
export const FULL_IMAGE = "rivetdev/sandbox-agent:0.3.1-full";
|
||||
|
||||
export interface DockerSandboxOptions {
|
||||
/** Container port used by sandbox-agent inside Docker. */
|
||||
|
|
@ -18,7 +18,7 @@ export interface DockerSandboxOptions {
|
|||
hostPort?: number;
|
||||
/** Additional shell commands to run before starting sandbox-agent. */
|
||||
setupCommands?: string[];
|
||||
/** Docker image to use. Defaults to the pre-built sandbox-agent-examples image. */
|
||||
/** Docker image to use. Defaults to the pre-built full image. */
|
||||
image?: string;
|
||||
}
|
||||
|
||||
|
|
@ -131,33 +131,31 @@ function stripAnsi(value: string): string {
|
|||
return value.replace(/[\u001B\u009B][[\]()#;?]*(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007|(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-ORZcf-nqry=><])/g, "");
|
||||
}
|
||||
|
||||
async function ensureExampleImage(_docker: Docker): Promise<string> {
|
||||
const dev = !!process.env.SANDBOX_AGENT_DEV;
|
||||
const imageName = dev ? EXAMPLE_IMAGE_DEV : EXAMPLE_IMAGE;
|
||||
|
||||
if (dev) {
|
||||
console.log(" Building sandbox image from source (may take a while, only runs once)...");
|
||||
async function ensureImage(docker: Docker, image: string): Promise<void> {
|
||||
if (process.env.SANDBOX_AGENT_DEV) {
|
||||
console.log(" Building sandbox image from source (may take a while)...");
|
||||
try {
|
||||
execFileSync("docker", ["build", "-t", imageName, "-f", path.join(DOCKERFILE_DIR, "Dockerfile.dev"), REPO_ROOT], {
|
||||
stdio: ["ignore", "ignore", "pipe"],
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
const stderr = err instanceof Error && "stderr" in err ? String((err as { stderr: unknown }).stderr) : "";
|
||||
throw new Error(`Failed to build sandbox image: ${stderr}`);
|
||||
}
|
||||
} else {
|
||||
console.log(" Building sandbox image (may take a while, only runs once)...");
|
||||
try {
|
||||
execFileSync("docker", ["build", "-t", imageName, DOCKERFILE_DIR], {
|
||||
execFileSync("docker", ["build", "-t", image, "-f", path.join(REPO_ROOT, "docker/runtime/Dockerfile.full"), REPO_ROOT], {
|
||||
stdio: ["ignore", "ignore", "pipe"],
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
const stderr = err instanceof Error && "stderr" in err ? String((err as { stderr: unknown }).stderr) : "";
|
||||
throw new Error(`Failed to build sandbox image: ${stderr}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return imageName;
|
||||
try {
|
||||
await docker.getImage(image).inspect();
|
||||
} catch {
|
||||
console.log(` Pulling ${image}...`);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {
|
||||
if (err) return reject(err);
|
||||
docker.modem.followProgress(stream, (err: Error | null) => (err ? reject(err) : resolve()));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,8 +164,7 @@ async function ensureExampleImage(_docker: Docker): Promise<string> {
|
|||
*/
|
||||
export async function startDockerSandbox(opts: DockerSandboxOptions): Promise<DockerSandbox> {
|
||||
const { port, hostPort } = opts;
|
||||
const useCustomImage = !!opts.image;
|
||||
let image = opts.image ?? EXAMPLE_IMAGE;
|
||||
const image = opts.image ?? FULL_IMAGE;
|
||||
// TODO: Replace setupCommands shell bootstrapping with native sandbox-agent exec API once available.
|
||||
const setupCommands = [...(opts.setupCommands ?? [])];
|
||||
const credentialEnv = collectCredentialEnv();
|
||||
|
|
@ -197,27 +194,13 @@ export async function startDockerSandbox(opts: DockerSandboxOptions): Promise<Do
|
|||
|
||||
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
||||
|
||||
if (useCustomImage) {
|
||||
try {
|
||||
await docker.getImage(image).inspect();
|
||||
} catch {
|
||||
console.log(` Pulling ${image}...`);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {
|
||||
if (err) return reject(err);
|
||||
docker.modem.followProgress(stream, (err: Error | null) => (err ? reject(err) : resolve()));
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
image = await ensureExampleImage(docker);
|
||||
}
|
||||
await ensureImage(docker, image);
|
||||
|
||||
const bootCommands = [...setupCommands, `sandbox-agent server --no-token --host 0.0.0.0 --port ${port}`];
|
||||
|
||||
const container = await docker.createContainer({
|
||||
Image: image,
|
||||
WorkingDir: "/root",
|
||||
WorkingDir: "/home/sandbox",
|
||||
Cmd: ["sh", "-c", bootCommands.join(" && ")],
|
||||
Env: [...Object.entries(credentialEnv).map(([key, value]) => `${key}=${value}`), ...Object.entries(bootstrapEnv).map(([key, value]) => `${key}=${value}`)],
|
||||
ExposedPorts: { [`${port}/tcp`]: {} },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue