From 568a5915513baaad11c2e0472d733caa13835cb0 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 3 Jul 2026 16:23:05 -0700 Subject: [PATCH] fix(sandbox): keep VM resources per instance --- Cargo.lock | 147 +----- examples/agent-to-agent/package.json | 3 - examples/approvals/package.json | 3 - examples/authentication/package.json | 3 - examples/bindings/package.json | 3 - examples/browser-terminal/README.md | 5 +- examples/browser-terminal/package.json | 5 - examples/browser-terminal/server.ts | 2 - examples/browser-terminal/src/ActorView.tsx | 10 +- examples/browser-terminal/src/rivet.ts | 3 +- examples/browser-terminal/tsconfig.json | 8 +- examples/claude/package.json | 3 - examples/codex/package.json | 3 - examples/core/create-options.ts | 20 + examples/core/package.json | 3 - examples/crash-course/package.json | 3 - examples/crash-course/sandbox-stubs.d.ts | 56 --- examples/crash-course/sandbox.ts | 28 +- examples/cron/package.json | 3 - examples/custom/package.json | 3 - examples/debugging/package.json | 3 - examples/filesystem/isolation.ts | 2 +- examples/filesystem/package.json | 3 - examples/js-runtime/package.json | 3 - examples/llm-credentials/package.json | 3 - examples/multiplayer/package.json | 3 - examples/networking/package.json | 3 - examples/opencode/package.json | 3 - examples/permissions/package.json | 3 - examples/persistence/package.json | 3 - examples/pi/package.json | 3 - examples/processes/package.json | 3 - examples/quickstart-app/package.json | 3 - .../quickstart/agent-session/package.json | 3 - examples/quickstart/bash/package.json | 3 - examples/quickstart/bindings/package.json | 3 - examples/quickstart/cron/package.json | 3 - examples/quickstart/filesystem/package.json | 3 - examples/quickstart/git/package.json | 3 - examples/quickstart/hello-world/package.json | 3 - examples/quickstart/network/package.json | 3 - examples/quickstart/nodejs/package.json | 3 - examples/quickstart/package.json | 3 - .../quickstart/pi-extensions/package.json | 3 - examples/quickstart/processes/package.json | 3 - .../quickstart/s3-filesystem/package.json | 3 - examples/quickstart/sandbox/README.md | 8 +- examples/quickstart/sandbox/advanced.ts | 23 + examples/quickstart/sandbox/index.ts | 65 +-- examples/quickstart/sandbox/package.json | 8 +- examples/resource-limits/package.json | 3 - examples/sandbox/README.md | 11 +- examples/sandbox/client.ts | 44 +- examples/sandbox/package.json | 4 - examples/sandbox/server.ts | 46 +- examples/sessions/package.json | 3 - examples/software/package.json | 3 - examples/webhooks/package.json | 3 - examples/workflows/package.json | 3 - packages/agentos-sandbox/package.json | 4 +- packages/agentos-sandbox/src/bindings.ts | 221 +-------- packages/agentos-sandbox/src/index.ts | 26 +- packages/agentos-sandbox/src/mount.ts | 5 + .../tests/vm-integration.test.ts | 13 +- packages/agentos/src/actor.ts | 392 ++++++++++++++- packages/agentos/src/config.ts | 39 +- packages/agentos/src/index.ts | 23 + packages/agentos/src/types.ts | 3 + packages/agentos/tests/actor.test.ts | 24 +- packages/agentos/tests/config.test.ts | 50 ++ packages/core/src/agent-os.ts | 22 +- packages/core/src/index.ts | 6 + packages/core/src/options-schema.ts | 8 + packages/core/src/sandbox.ts | 463 ++++++++++++++++++ packages/core/src/types.ts | 13 + packages/core/tests/options-schema.test.ts | 89 +++- .../core/tests/sandbox-integration.test.ts | 16 +- pnpm-lock.yaml | 405 +-------------- website/public/docs/docs/core.md | 2 +- website/public/docs/docs/crash-course.md | 2 +- website/public/docs/docs/sandbox.md | 28 +- .../src/content/docs/docs/crash-course.mdx | 2 +- website/src/content/docs/docs/sandbox.mdx | 43 +- website/src/pages/registry/[slug].astro | 15 +- 84 files changed, 1406 insertions(+), 1123 deletions(-) create mode 100644 examples/core/create-options.ts delete mode 100644 examples/crash-course/sandbox-stubs.d.ts create mode 100644 examples/quickstart/sandbox/advanced.ts create mode 100644 packages/agentos-sandbox/src/mount.ts create mode 100644 packages/agentos/tests/config.test.ts create mode 100644 packages/core/src/sandbox.ts diff --git a/Cargo.lock b/Cargo.lock index c959c08a4..47969956d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,41 +14,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "agentos-actor-plugin" version = "0.2.0-rc.3" @@ -1098,19 +1063,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "data-encoding" version = "2.10.0" @@ -1479,16 +1434,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "glob" version = "0.3.3" @@ -2426,12 +2371,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.76" @@ -2645,18 +2584,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - [[package]] name = "portable-atomic" version = "1.13.1" @@ -3143,10 +3070,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "secure-exec-build-support" -version = "0.3.0-rc.1" - [[package]] name = "secure-exec-client" version = "0.3.0-rc.1" @@ -3154,7 +3077,7 @@ dependencies = [ "futures", "parking_lot", "scc", - "secure-exec-sidecar-protocol", + "secure-exec-sidecar", "thiserror", "tokio", "tracing", @@ -3169,7 +3092,6 @@ dependencies = [ "getrandom 0.2.17", "nix", "secure-exec-bridge", - "secure-exec-build-support", "secure-exec-v8-runtime", "serde", "serde_json", @@ -3183,22 +3105,18 @@ version = "0.3.0-rc.1" dependencies = [ "base64 0.22.1", "getrandom 0.2.17", - "hickory-proto", "hickory-resolver", "secure-exec-bridge", "secure-exec-vfs-core", "serde", "serde_json", "tokio", - "web-time", ] [[package]] name = "secure-exec-sidecar" version = "0.3.0-rc.1" dependencies = [ - "aes", - "aes-gcm", "async-trait", "aws-config", "aws-credential-types", @@ -3206,7 +3124,6 @@ dependencies = [ "base64 0.22.1", "bytes", "cap-std", - "ctr", "filetime", "h2 0.4.13", "hickory-resolver", @@ -3218,6 +3135,7 @@ dependencies = [ "nix", "openssl", "pbkdf2", + "rivet-vbare-compiler", "rusqlite", "rustls 0.23.37", "rustls-native-certs", @@ -3226,12 +3144,11 @@ dependencies = [ "secure-exec-bridge", "secure-exec-execution", "secure-exec-kernel", - "secure-exec-sidecar-core", - "secure-exec-sidecar-protocol", "secure-exec-vfs", "secure-exec-vfs-core", "secure-exec-vm-config", "serde", + "serde_bare", "serde_json", "sha1", "sha2", @@ -3242,47 +3159,15 @@ dependencies = [ "tracing-subscriber", "ureq", "url", + "vbare", ] [[package]] name = "secure-exec-sidecar-browser" version = "0.3.0-rc.1" dependencies = [ - "base64 0.22.1", - "getrandom 0.2.17", - "js-sys", "secure-exec-bridge", "secure-exec-kernel", - "secure-exec-sidecar-core", - "secure-exec-sidecar-protocol", - "secure-exec-vm-config", - "serde_json", - "wasm-bindgen", -] - -[[package]] -name = "secure-exec-sidecar-core" -version = "0.3.0-rc.1" -dependencies = [ - "base64 0.22.1", - "secure-exec-bridge", - "secure-exec-kernel", - "secure-exec-sidecar-protocol", - "secure-exec-vfs-core", - "secure-exec-vm-config", - "serde_json", -] - -[[package]] -name = "secure-exec-sidecar-protocol" -version = "0.3.0-rc.1" -dependencies = [ - "rivet-vbare-compiler", - "secure-exec-vm-config", - "serde", - "serde_bare", - "serde_json", - "vbare", ] [[package]] @@ -3292,10 +3177,9 @@ dependencies = [ "ciborium", "crossbeam-channel", "libc", + "openssl", "secure-exec-bridge", - "secure-exec-build-support", "serde", - "sha2", "signal-hook", "v8", ] @@ -3322,7 +3206,6 @@ dependencies = [ "serde", "serde_json", "tokio", - "web-time", ] [[package]] @@ -3964,16 +3847,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "untrusted" version = "0.7.1" @@ -4221,16 +4094,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webpki-roots" version = "0.26.11" diff --git a/examples/agent-to-agent/package.json b/examples/agent-to-agent/package.json index 8a3a370eb..9d007c63c 100644 --- a/examples/agent-to-agent/package.json +++ b/examples/agent-to-agent/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/approvals/package.json b/examples/approvals/package.json index a070b0ea4..e2ee710bc 100644 --- a/examples/approvals/package.json +++ b/examples/approvals/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/authentication/package.json b/examples/authentication/package.json index 7cdbf654f..bd5316e89 100644 --- a/examples/authentication/package.json +++ b/examples/authentication/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/bindings/package.json b/examples/bindings/package.json index f13f7fed8..e95806fca 100644 --- a/examples/bindings/package.json +++ b/examples/bindings/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/browser-terminal/README.md b/examples/browser-terminal/README.md index eef3e036e..a65d8ff33 100644 --- a/examples/browser-terminal/README.md +++ b/examples/browser-terminal/README.md @@ -66,9 +66,8 @@ Override the web→server endpoint with `VITE_AGENTOS_ENDPOINT` (default ## Notes -- Software: `@agentos-software/common` (provides `sh` + coreutils) plus `git`, - `curl`, `ripgrep`, `jq`, and `sqlite3`. Agent OS has no vim/editor package, so - there is no in-VM editor. +- Software: `@agentos-software/common` provides `sh` plus the standard shell + tools. Agent OS has no vim/editor package, so there is no in-VM editor. - The shipped actor has no `listShells` action and keeps no server-side scrollback, so reconnect re-adopts saved shell ids and resumes **live** output only (history from before the reload is not replayed). Stale ids (VM recreated) diff --git a/examples/browser-terminal/package.json b/examples/browser-terminal/package.json index 538b61e6d..222744478 100644 --- a/examples/browser-terminal/package.json +++ b/examples/browser-terminal/package.json @@ -14,11 +14,6 @@ }, "dependencies": { "@agentos-software/common": "link:../../../secure-exec/registry/software/common", - "@agentos-software/curl": "link:../../../secure-exec/registry/software/curl", - "@agentos-software/git": "link:../../../secure-exec/registry/software/git", - "@agentos-software/jq": "link:../../../secure-exec/registry/software/jq", - "@agentos-software/ripgrep": "link:../../../secure-exec/registry/software/ripgrep", - "@agentos-software/sqlite3": "link:../../../secure-exec/registry/software/sqlite3", "@rivet-dev/agentos": "workspace:*", "@rivetkit/react": "catalog:rivetkit", "@xterm/addon-fit": "^0.10.0", diff --git a/examples/browser-terminal/server.ts b/examples/browser-terminal/server.ts index 6aac03e59..a2a0e7f15 100644 --- a/examples/browser-terminal/server.ts +++ b/examples/browser-terminal/server.ts @@ -1,8 +1,6 @@ import common from "@agentos-software/common"; import { agentOS, setup } from "@rivet-dev/agentos"; -// TEMP (local debug): only `common` (sha 42d8146) has valid projected packs; -// `sqlite3` (efc374f) is missing its `dist/package`, so it's dropped for now. const shellVm = agentOS({ software: [common], }); diff --git a/examples/browser-terminal/src/ActorView.tsx b/examples/browser-terminal/src/ActorView.tsx index 155a39c63..4444e4668 100644 --- a/examples/browser-terminal/src/ActorView.tsx +++ b/examples/browser-terminal/src/ActorView.tsx @@ -16,6 +16,12 @@ interface ShellExitPayload { shellId: string; } +interface ShellEventConnection { + on(name: "shellData", cb: (payload: ShellDataPayload) => void): () => void; + on(name: "shellStderr", cb: (payload: ShellDataPayload) => void): () => void; + on(name: "shellExit", cb: (payload: ShellExitPayload) => void): () => void; +} + function toBytes(data: unknown): Uint8Array { if (data instanceof Uint8Array) return data; if (data instanceof ArrayBuffer) return new Uint8Array(data); @@ -89,9 +95,7 @@ export function ActorView({ actorId }: { actorId: string }) { useEffect(() => { if (!conn) return; - const events = conn as unknown as { - on(name: string, cb: (p: never) => void): () => void; - }; + const events = conn as ShellEventConnection; const offData = events.on("shellData", (p: ShellDataPayload) => dispatchData(p.shellId, toBytes(p.data)), ); diff --git a/examples/browser-terminal/src/rivet.ts b/examples/browser-terminal/src/rivet.ts index a2c4c1241..65f706155 100644 --- a/examples/browser-terminal/src/rivet.ts +++ b/examples/browser-terminal/src/rivet.ts @@ -1,9 +1,10 @@ import { createRivetKit } from "@rivetkit/react"; +import type { registry } from "../server"; const ENDPOINT = (import.meta.env.VITE_AGENTOS_ENDPOINT as string | undefined) ?? "http://localhost:6420"; -export const { useActor } = createRivetKit(ENDPOINT); +export const { useActor } = createRivetKit(ENDPOINT); export const ACTOR_NAME = "shellVm"; diff --git a/examples/browser-terminal/tsconfig.json b/examples/browser-terminal/tsconfig.json index c97ccc68f..d93161ff6 100644 --- a/examples/browser-terminal/tsconfig.json +++ b/examples/browser-terminal/tsconfig.json @@ -12,7 +12,13 @@ "esModuleInterop": true, "resolveJsonModule": true, "allowImportingTsExtensions": true, - "verbatimModuleSyntax": false + "verbatimModuleSyntax": false, + "baseUrl": ".", + "paths": { + "@rivet-dev/agentos": ["../../packages/agentos/src/index.ts"], + "@rivet-dev/agentos/client": ["../../packages/agentos/src/client.ts"], + "@rivet-dev/agentos/react": ["../../packages/agentos/src/react.ts"] + } }, "include": ["server.ts", "vite.config.ts", "src"] } diff --git a/examples/claude/package.json b/examples/claude/package.json index 819e8760a..503485e96 100644 --- a/examples/claude/package.json +++ b/examples/claude/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/codex/package.json b/examples/codex/package.json index 28a7eee58..915f64dd7 100644 --- a/examples/codex/package.json +++ b/examples/codex/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/core/create-options.ts b/examples/core/create-options.ts new file mode 100644 index 000000000..448cd8d16 --- /dev/null +++ b/examples/core/create-options.ts @@ -0,0 +1,20 @@ +import { agentOS, setup } from "@rivet-dev/agentos"; + +let nextVmId = 0; + +const vm = agentOS({ + defaultSoftware: false, + createOptions: async () => { + const vmId = ++nextVmId; + return { + options: { + additionalInstructions: `This is actor VM ${vmId}.`, + }, + dispose: () => { + console.log(`disposed actor VM ${vmId}`); + }, + }; + }, +}); + +export const registry = setup({ use: { vm } }); diff --git a/examples/core/package.json b/examples/core/package.json index b37d27e01..63ca8cd02 100644 --- a/examples/core/package.json +++ b/examples/core/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/crash-course/package.json b/examples/crash-course/package.json index 17a6341b7..8ab0565a0 100644 --- a/examples/crash-course/package.json +++ b/examples/crash-course/package.json @@ -9,9 +9,6 @@ "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", "rivetkit": "catalog:rivetkit", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/crash-course/sandbox-stubs.d.ts b/examples/crash-course/sandbox-stubs.d.ts deleted file mode 100644 index 034f202f5..000000000 --- a/examples/crash-course/sandbox-stubs.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Local ambient stubs for this example only. - * - * The sandbox integration ships as `@rivet-dev/agentos-sandbox`, which depends on - * the third-party `sandbox-agent` package. Their runtimes pull native/Docker - * deps, so these declarations model just enough of the public surface for the - * sandbox example to type-check. Remove once the packages are installed here. - * - * Real packages and exports: - * - `@rivet-dev/agentos-sandbox` -> `createSandboxFs`, `createSandboxBindings` - * - `sandbox-agent` -> `SandboxAgent` - * - `sandbox-agent/docker` -> `docker` - */ - -declare module "sandbox-agent" { - export class SandboxAgent { - static start(options: { sandbox: unknown }): Promise; - dispose(): Promise; - } -} - -declare module "sandbox-agent/docker" { - export function docker(options?: unknown): unknown; -} - -declare module "@rivet-dev/agentos-sandbox" { - import type { - NativeMountPluginDescriptor, - ToolKit, - } from "@rivet-dev/agentos-core"; - import type { SandboxAgent } from "sandbox-agent"; - - export interface SandboxFsOptions { - /** A connected SandboxAgent client instance. */ - client: SandboxAgent; - /** Base path to scope all operations under. Defaults to "/". */ - basePath?: string; - /** Per-request timeout for sandbox-agent HTTP calls. */ - timeoutMs?: number; - /** Maximum file size allowed for buffered pread/truncate fallbacks. */ - maxFullReadBytes?: number; - } - export interface SandboxToolkitOptions { - /** A connected SandboxAgent client instance. */ - client: SandboxAgent; - } - /** - * Build the mount plugin descriptor that projects the sandbox filesystem into - * the VM. Use it as the `plugin` of a `{ path, plugin }` mount entry. - */ - export function createSandboxFs( - options: SandboxFsOptions, - ): NativeMountPluginDescriptor; - /** Build a toolkit that exposes the sandbox's process management. */ - export function createSandboxBindings(options: SandboxToolkitOptions): ToolKit; -} diff --git a/examples/crash-course/sandbox.ts b/examples/crash-course/sandbox.ts index 2e52ebb76..4f140421c 100644 --- a/examples/crash-course/sandbox.ts +++ b/examples/crash-course/sandbox.ts @@ -1,24 +1,8 @@ -import { AgentOs } from "@rivet-dev/agentos-core"; -import { - createSandboxBindings, - createSandboxFs, -} from "@rivet-dev/agentos-sandbox"; -import { SandboxAgent } from "sandbox-agent"; -import { docker } from "sandbox-agent/docker"; +import { agentOS } from "@rivet-dev/agentos"; +import { docker } from "@rivet-dev/agentos-sandbox"; -const sandbox = await SandboxAgent.start({ sandbox: docker() }); - -const vm = await AgentOs.create({ - // Bindings let the agent control the sandbox. - bindings: [createSandboxBindings({ client: sandbox })], - // Mounts let the agent read the sandbox filesystem. - mounts: [ - { - path: "/home/agentos/sandbox", - plugin: createSandboxFs({ client: sandbox }), - }, - ], +const vm = agentOS({ + sandbox: { + provider: docker(), + }, }); - -await vm.dispose(); -await sandbox.dispose(); diff --git a/examples/cron/package.json b/examples/cron/package.json index c96b031ef..3acaabed2 100644 --- a/examples/cron/package.json +++ b/examples/cron/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/custom/package.json b/examples/custom/package.json index 6ba218f43..6e501afb4 100644 --- a/examples/custom/package.json +++ b/examples/custom/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/debugging/package.json b/examples/debugging/package.json index d60545db7..f64c3dc19 100644 --- a/examples/debugging/package.json +++ b/examples/debugging/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/filesystem/isolation.ts b/examples/filesystem/isolation.ts index 9b908054f..c6a75ac01 100644 --- a/examples/filesystem/isolation.ts +++ b/examples/filesystem/isolation.ts @@ -1,3 +1,4 @@ +import { existsSync } from "node:fs"; import { createClient } from "@rivet-dev/agentos/client"; import type { registry } from "./server"; @@ -26,7 +27,6 @@ const bytes = await agent.readFile("/home/agentos/seed.json"); console.log("host readFile:", new TextDecoder().decode(bytes)); // The same path on the real host disk does not exist. The VFS is isolated. -const { existsSync } = await import("node:fs"); console.log( "host disk sees /home/agentos/seed.json?", existsSync("/home/agentos/seed.json") ? "YES (unexpected!)" : "NO - isolated from host", diff --git a/examples/filesystem/package.json b/examples/filesystem/package.json index 920d23481..b85ce4adf 100644 --- a/examples/filesystem/package.json +++ b/examples/filesystem/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/js-runtime/package.json b/examples/js-runtime/package.json index 0665adb42..4528cbea0 100644 --- a/examples/js-runtime/package.json +++ b/examples/js-runtime/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/llm-credentials/package.json b/examples/llm-credentials/package.json index 43a72715b..c0c3dd14c 100644 --- a/examples/llm-credentials/package.json +++ b/examples/llm-credentials/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/multiplayer/package.json b/examples/multiplayer/package.json index aacb14dfd..cb9ecf076 100644 --- a/examples/multiplayer/package.json +++ b/examples/multiplayer/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/networking/package.json b/examples/networking/package.json index b8b5c36df..b251975bd 100644 --- a/examples/networking/package.json +++ b/examples/networking/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/opencode/package.json b/examples/opencode/package.json index 352b1f301..46fe32e45 100644 --- a/examples/opencode/package.json +++ b/examples/opencode/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/permissions/package.json b/examples/permissions/package.json index 300f04c24..5cd8679a4 100644 --- a/examples/permissions/package.json +++ b/examples/permissions/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/persistence/package.json b/examples/persistence/package.json index 3435de60b..2eee74b13 100644 --- a/examples/persistence/package.json +++ b/examples/persistence/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/pi/package.json b/examples/pi/package.json index 005b95b57..8d17d9d23 100644 --- a/examples/pi/package.json +++ b/examples/pi/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/processes/package.json b/examples/processes/package.json index 71a6bf178..54d8e60ed 100644 --- a/examples/processes/package.json +++ b/examples/processes/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/quickstart-app/package.json b/examples/quickstart-app/package.json index 2f0c938fa..184503ed2 100644 --- a/examples/quickstart-app/package.json +++ b/examples/quickstart-app/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/quickstart/agent-session/package.json b/examples/quickstart/agent-session/package.json index 55085c246..af4761d23 100644 --- a/examples/quickstart/agent-session/package.json +++ b/examples/quickstart/agent-session/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/bash/package.json b/examples/quickstart/bash/package.json index 55055ebad..1892072c2 100644 --- a/examples/quickstart/bash/package.json +++ b/examples/quickstart/bash/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/bindings/package.json b/examples/quickstart/bindings/package.json index d329d4b4a..469f249f5 100644 --- a/examples/quickstart/bindings/package.json +++ b/examples/quickstart/bindings/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/cron/package.json b/examples/quickstart/cron/package.json index 45bf1eb8a..313263fb9 100644 --- a/examples/quickstart/cron/package.json +++ b/examples/quickstart/cron/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/filesystem/package.json b/examples/quickstart/filesystem/package.json index ff2907a66..bcf69b37d 100644 --- a/examples/quickstart/filesystem/package.json +++ b/examples/quickstart/filesystem/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/git/package.json b/examples/quickstart/git/package.json index 67bed7adc..0088e590d 100644 --- a/examples/quickstart/git/package.json +++ b/examples/quickstart/git/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/hello-world/package.json b/examples/quickstart/hello-world/package.json index 498426279..3aaa694fe 100644 --- a/examples/quickstart/hello-world/package.json +++ b/examples/quickstart/hello-world/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/network/package.json b/examples/quickstart/network/package.json index f99c6b845..628b47142 100644 --- a/examples/quickstart/network/package.json +++ b/examples/quickstart/network/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/nodejs/package.json b/examples/quickstart/nodejs/package.json index c79ad613b..6c7d16b07 100644 --- a/examples/quickstart/nodejs/package.json +++ b/examples/quickstart/nodejs/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json index f6b45a621..a0beac907 100644 --- a/examples/quickstart/package.json +++ b/examples/quickstart/package.json @@ -24,9 +24,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/quickstart/pi-extensions/package.json b/examples/quickstart/pi-extensions/package.json index 925e75da6..21d4a06b7 100644 --- a/examples/quickstart/pi-extensions/package.json +++ b/examples/quickstart/pi-extensions/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/processes/package.json b/examples/quickstart/processes/package.json index f900ea4f4..fe77c9824 100644 --- a/examples/quickstart/processes/package.json +++ b/examples/quickstart/processes/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/s3-filesystem/package.json b/examples/quickstart/s3-filesystem/package.json index 870100ffb..1d5ac02bc 100644 --- a/examples/quickstart/s3-filesystem/package.json +++ b/examples/quickstart/s3-filesystem/package.json @@ -10,9 +10,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", diff --git a/examples/quickstart/sandbox/README.md b/examples/quickstart/sandbox/README.md index 7bd13e11a..114611be9 100644 --- a/examples/quickstart/sandbox/README.md +++ b/examples/quickstart/sandbox/README.md @@ -1,15 +1,15 @@ --- title: "Sandbox" -description: "Mount a Docker sandbox filesystem and run commands through sandbox bindings." +description: "Give an agent a Docker-backed environment for native tools." category: "Quickstart" order: 11 --- -Back a VM with a Docker-backed sandbox so guest reads, writes, and commands run inside a real container. Reach for this when you want host-isolated execution with a familiar filesystem and shell, instead of the in-process runtime. +Back a VM with a Docker-backed environment so an agent can reach native tools when the in-process runtime is not enough. Reach for this when you want the agent to solve tasks that may need a familiar Linux filesystem, package manager, or shell. ## How it works -The quickstart starts one Docker container for one VM. Two pieces wire it into the VM: `createSandboxFs` mounts the container's filesystem at `/sandbox`, and `createSandboxBindings` registers sandbox bindings for running commands. Files written under `/sandbox` land in the container, and commands like `run-command` and `list-processes` execute through the `agentos-sandbox` CLI. Set `SKIP_DOCKER=1` to no-op the example where Docker is unavailable. +The quickstart passes `sandbox: { provider: docker() }` to `AgentOs.create`, then asks an agent to write, compile, and run a C program. Agent OS mounts the container filesystem, registers process bindings, and disposes the sandbox client when the VM is disposed. The prompt does not mention the sandbox; the agent sees the available environment and chooses how to complete the task. Set `SKIP_DOCKER=1` to no-op the example where Docker is unavailable. ## Run it @@ -18,7 +18,7 @@ npm install npx tsx index.ts ``` -You should see a file read back from the sandbox mount, the output of an `echo` command, and a process listing from inside the Docker sandbox. +You should see streamed agent events and a final response containing the compiled program's output. ## Source diff --git a/examples/quickstart/sandbox/advanced.ts b/examples/quickstart/sandbox/advanced.ts new file mode 100644 index 000000000..6eb3185b3 --- /dev/null +++ b/examples/quickstart/sandbox/advanced.ts @@ -0,0 +1,23 @@ +// Advanced sandbox configuration: custom mount path and manual disposal. + +import { AgentOs } from "@rivet-dev/agentos-core"; +import { SandboxAgent } from "sandbox-agent"; +import { docker } from "sandbox-agent/docker"; + +const sandbox = await SandboxAgent.start({ sandbox: docker() }); + +const vm = await AgentOs.create({ + sandbox: { + client: sandbox, + mountPath: "/work", + dispose: false, + }, +}); + +try { + await vm.writeFile("/work/hello.txt", "Hello from /work"); + console.log(new TextDecoder().decode(await vm.readFile("/work/hello.txt"))); +} finally { + await vm.dispose(); + await sandbox.dispose(); +} diff --git a/examples/quickstart/sandbox/index.ts b/examples/quickstart/sandbox/index.ts index 788a9a837..e2df37269 100644 --- a/examples/quickstart/sandbox/index.ts +++ b/examples/quickstart/sandbox/index.ts @@ -1,16 +1,14 @@ -// Sandbox extension: mount a Docker sandbox filesystem and run commands. +// Give an agent access to a Docker-backed environment for native tools. // -// Requires Docker. Starts a sandbox-agent container, mounts its filesystem -// at /sandbox, and registers sandbox bindings for running commands. +// Requires Docker and ANTHROPIC_API_KEY. The prompt asks for a normal developer +// task that needs a C compiler; the agent decides how to use the available +// environment and bindings. +import pi from "@agentos-software/pi"; import { AgentOs } from "@rivet-dev/agentos-core"; -import { - createSandboxBindings, - createSandboxFs, -} from "@rivet-dev/agentos-sandbox"; -import { SandboxAgent } from "sandbox-agent"; -import { docker } from "sandbox-agent/docker"; +import { docker } from "@rivet-dev/agentos-sandbox"; +const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; const SANDBOX_QUICKSTART_PERMISSIONS = { fs: "allow", network: "allow", @@ -20,40 +18,45 @@ const SANDBOX_QUICKSTART_PERMISSIONS = { } as const; const skipDocker = process.env.SKIP_DOCKER === "1"; +if (!ANTHROPIC_API_KEY) { + console.error("Set ANTHROPIC_API_KEY to run this example."); + process.exit(1); +} + if (skipDocker) { console.log("Skipping sandbox quickstart because SKIP_DOCKER=1."); process.exit(0); } -const sandbox = await SandboxAgent.start({ - sandbox: docker(), -}); - const vm = await AgentOs.create({ + software: [pi], permissions: SANDBOX_QUICKSTART_PERMISSIONS, - mounts: [ - { - path: "/sandbox", - plugin: createSandboxFs({ client: sandbox }), - }, - ], - bindings: [createSandboxBindings({ client: sandbox })], + sandbox: { + provider: docker(), + }, }); try { - // Write and read a file through the mounted sandbox filesystem. - await vm.writeFile("/sandbox/hello.txt", "Hello from agentOS!"); - const content = await vm.readFile("/sandbox/hello.txt"); - console.log("Read from sandbox mount:", new TextDecoder().decode(content)); - - const runCommandResult = await vm.exec( - "agentos-sandbox run-command --command echo --args 'hello from Docker sandbox'", + const { sessionId } = await vm.createSession("pi", { + env: { ANTHROPIC_API_KEY }, + }); + console.log("Session ID:", sessionId); + + vm.onSessionEvent(sessionId, (event) => { + console.log("Event:", JSON.stringify(event, null, 2)); + }); + + const { text } = await vm.prompt( + sessionId, + [ + "Create a small C program that prints the first 10 Fibonacci numbers.", + "Compile it with a C compiler, run the compiled binary, and report the exact output.", + "If the required compiler or build tools are missing, set up the build environment you need.", + ].join(" "), ); - console.log("Sandbox command:", JSON.stringify(runCommandResult)); - const processList = await vm.exec("agentos-sandbox list-processes"); - console.log("Sandbox processes:", JSON.stringify(processList)); + console.log("Agent:", text); + vm.closeSession(sessionId); } finally { await vm.dispose(); - await sandbox.dispose(); } diff --git a/examples/quickstart/sandbox/package.json b/examples/quickstart/sandbox/package.json index 212dc4ef6..4d8328de3 100644 --- a/examples/quickstart/sandbox/package.json +++ b/examples/quickstart/sandbox/package.json @@ -10,16 +10,14 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "catalog:", "@agentos-software/claude-code": "catalog:", "@agentos-software/opencode": "catalog:", "@agentos-software/pi": "catalog:", - "zod": "^4.1.11", "@rivet-dev/agentos": "workspace:*", - "rivetkit": "catalog:rivetkit" + "rivetkit": "catalog:rivetkit", + "sandbox-agent": "^0.4.2", + "zod": "^4.1.11" }, "devDependencies": { "@types/node": "^22.10.2", diff --git a/examples/resource-limits/package.json b/examples/resource-limits/package.json index 9668b2892..76c9a8777 100644 --- a/examples/resource-limits/package.json +++ b/examples/resource-limits/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/sandbox/README.md b/examples/sandbox/README.md index 79e3c4570..15666b9d3 100644 --- a/examples/sandbox/README.md +++ b/examples/sandbox/README.md @@ -1,26 +1,25 @@ --- title: "Sandbox" -description: "Mount a fresh Sandbox Agent (Docker) filesystem into each VM and expose its process management as bindings." +description: "Give each VM a Docker-backed environment for native tools." category: "Reference" order: 5 --- -Back each VM with its own real Sandbox Agent container: the sandbox's filesystem appears as a mount inside the VM, and its process management is callable through sandbox bindings. Reach for this when you want guest code to read, write, and run against a live Docker sandbox instead of the in-memory VFS. +Back each VM with its own real Sandbox Agent container so agents can reach native tools when the in-process runtime is not enough. ## How it works -The helper starts one sandbox for one VM, then wires it in two ways. `createSandboxFs({ client })` returns a mount-plugin descriptor that projects the sandbox filesystem under `/home/agentos/sandbox`, so `vm.writeFile` and `vm.exec` operate on real container files. `createSandboxBindings({ client })` exposes the sandbox's process management as the `agentos-sandbox` CLI command. The client disposes both the VM and sandbox together. - -Do not create one `SandboxAgent` at module scope and reuse it for multiple actor instances. Dynamic per-actor sandbox creation for `agentOS(...)` needs a future actor-scoped options hook; no `createOptions` callback is supported today. +The server passes `sandbox: { provider: docker() }` directly to `agentOS(...)`. The provider is a factory, so every actor VM gets its own sandbox client/container and Agent OS disposes it when the actor VM sleeps or is destroyed. The client asks an agent to write, compile, and run a C program without naming the sandbox; the agent uses the available environment to complete the task. ## Run it ```sh npm install +tsx server.ts tsx client.ts ``` -You should see text read back from a file written through the sandbox mount. +You should see streamed agent events and a final response containing the compiled program's output. ## Source diff --git a/examples/sandbox/client.ts b/examples/sandbox/client.ts index 8d0c0b601..ae5412249 100644 --- a/examples/sandbox/client.ts +++ b/examples/sandbox/client.ts @@ -1,14 +1,32 @@ -import { createSandboxVm, disposeSandboxVm } from "./server"; - -const handle = await createSandboxVm(); - -try { - await handle.vm.writeFile( - "/home/agentos/sandbox/hello.txt", - "Hello from a fresh sandbox", - ); - const content = await handle.vm.readFile("/home/agentos/sandbox/hello.txt"); - console.log(new TextDecoder().decode(content)); -} finally { - await disposeSandboxVm(handle); +import { createClient } from "@rivet-dev/agentos/client"; +import type { registry } from "./server"; + +const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; +if (!ANTHROPIC_API_KEY) { + console.error("Set ANTHROPIC_API_KEY to run this example."); + process.exit(1); } + +const client = createClient({ endpoint: "http://localhost:6420" }); +const vm = client.vm.getOrCreate("native-tools-demo"); + +const conn = vm.connect(); +conn.on("sessionEvent", (data) => { + console.log(data.event); +}); + +const sessionId = await vm.createSession("pi", { + env: { ANTHROPIC_API_KEY }, +}); + +const response = await vm.sendPrompt( + sessionId, + [ + "Create a small C program that prints the first 10 Fibonacci numbers.", + "Compile it with a C compiler, run the compiled binary, and report the exact output.", + "If the required compiler or build tools are missing, set up the build environment you need.", + ].join(" "), +); + +console.log(response.text); +await vm.closeSession(sessionId); diff --git a/examples/sandbox/package.json b/examples/sandbox/package.json index e4934660d..6ac8ca4ff 100644 --- a/examples/sandbox/package.json +++ b/examples/sandbox/package.json @@ -7,11 +7,7 @@ "check-types": "tsc --noEmit" }, "dependencies": { - "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/sandbox/server.ts b/examples/sandbox/server.ts index 9a9bdbdf1..e7d3be230 100644 --- a/examples/sandbox/server.ts +++ b/examples/sandbox/server.ts @@ -1,32 +1,18 @@ -import { AgentOs } from "@rivet-dev/agentos-core"; -import { - createSandboxBindings, - createSandboxFs, -} from "@rivet-dev/agentos-sandbox"; -import { SandboxAgent } from "sandbox-agent"; -import { docker } from "sandbox-agent/docker"; +import { agentOS, setup } from "@rivet-dev/agentos"; +import { docker } from "@rivet-dev/agentos-sandbox"; -export async function createSandboxVm() { - const sandbox = await SandboxAgent.start({ sandbox: docker() }); - try { - const vm = await AgentOs.create({ - mounts: [ - { - path: "/home/agentos/sandbox", - plugin: createSandboxFs({ client: sandbox }), - }, - ], - bindings: [createSandboxBindings({ client: sandbox })], - }); - return { vm, sandbox }; - } catch (error) { - await sandbox.dispose(); - throw error; - } -} +const vm = agentOS({ + permissions: { + fs: "allow", + network: "allow", + childProcess: "allow", + env: "allow", + binding: "allow", + }, + sandbox: { + provider: docker(), + }, +}); -export async function disposeSandboxVm( - handle: Awaited>, -) { - await Promise.allSettled([handle.vm.dispose(), handle.sandbox.dispose()]); -} +export const registry = setup({ use: { vm } }); +registry.start(); diff --git a/examples/sessions/package.json b/examples/sessions/package.json index 7244d7d46..9ff78265f 100644 --- a/examples/sessions/package.json +++ b/examples/sessions/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/software/package.json b/examples/software/package.json index 1a9484365..07cbd70fe 100644 --- a/examples/software/package.json +++ b/examples/software/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/coreutils": "link:../../../secure-exec/registry/software/coreutils", "@agentos-software/ripgrep": "link:../../../secure-exec/registry/software/ripgrep", diff --git a/examples/webhooks/package.json b/examples/webhooks/package.json index 9d7421000..99d612da2 100644 --- a/examples/webhooks/package.json +++ b/examples/webhooks/package.json @@ -10,9 +10,6 @@ "@rivet-dev/agentos-sandbox": "workspace:*", "rivetkit": "catalog:rivetkit", "hono": "^4.6.0", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/examples/workflows/package.json b/examples/workflows/package.json index 7408c011e..486cd54dd 100644 --- a/examples/workflows/package.json +++ b/examples/workflows/package.json @@ -9,9 +9,6 @@ "dependencies": { "@rivet-dev/agentos-core": "workspace:*", "@rivet-dev/agentos-sandbox": "workspace:*", - "sandbox-agent": "^0.4.2", - "dockerode": "^4.0.9", - "get-port": "^7.1.0", "@agentos-software/git": "link:../../../secure-exec/registry/software/git", "@agentos-software/claude-code": "link:../../../secure-exec/registry/agent/claude", "@agentos-software/opencode": "link:../../../secure-exec/registry/agent/opencode", diff --git a/packages/agentos-sandbox/package.json b/packages/agentos-sandbox/package.json index d22d0320d..781f95f57 100644 --- a/packages/agentos-sandbox/package.json +++ b/packages/agentos-sandbox/package.json @@ -22,7 +22,9 @@ }, "dependencies": { "@rivet-dev/agentos-core": "workspace:*", - "@secure-exec/sandbox": "link:../../../secure-exec/registry/tool/sandbox", + "@fly/sprites": ">=0.0.1", + "dockerode": "^4.0.9", + "get-port": "^7.1.0", "sandbox-agent": "^0.4.2", "zod": "^4.1.11" }, diff --git a/packages/agentos-sandbox/src/bindings.ts b/packages/agentos-sandbox/src/bindings.ts index 71f8df227..2f84df718 100644 --- a/packages/agentos-sandbox/src/bindings.ts +++ b/packages/agentos-sandbox/src/bindings.ts @@ -1,219 +1,2 @@ -/** - * Sandbox bindings exposing process management and command execution - * for agents running inside an agentOS VM. - */ - -import type { Binding, BindingGroup } from "@rivet-dev/agentos-core"; -import type { SandboxAgent } from "sandbox-agent"; -import { z } from "zod"; - -export interface SandboxBindingsOptions { - /** A connected SandboxAgent client instance. */ - client: SandboxAgent; -} - -function binding( - def: Binding, -): Binding { - return def; -} - -/** - * Create sandbox bindings that expose sandbox process management operations. - */ -export function createSandboxBindings( - options: SandboxBindingsOptions, -): BindingGroup { - const { client } = options; - - return { - name: "sandbox", - description: - "Execute commands and manage processes in a remote sandbox environment.", - bindings: { - "run-command": binding({ - description: - "Run a command synchronously in the sandbox and return its stdout, stderr, and exit code.", - inputSchema: z.object({ - command: z - .string() - .describe("The command to execute (e.g. 'ls', 'python3')."), - args: z - .array(z.string()) - .optional() - .describe("Arguments to pass to the command."), - cwd: z - .string() - .optional() - .describe("Working directory for the command."), - env: z - .record(z.string(), z.string()) - .optional() - .describe("Additional environment variables."), - timeoutMs: z - .number() - .optional() - .describe("Maximum execution time in milliseconds."), - }), - timeout: 120_000, - execute: async (input) => { - const result = await client.runProcess({ - command: input.command, - args: input.args, - cwd: input.cwd, - env: input.env, - timeoutMs: input.timeoutMs, - }); - return { - stdout: result.stdout, - stderr: result.stderr, - exitCode: result.exitCode, - timedOut: result.timedOut, - durationMs: result.durationMs, - }; - }, - }), - - "create-process": binding({ - description: - "Start a long-running background process in the sandbox. Returns a process ID for later management.", - inputSchema: z.object({ - command: z.string().describe("The command to execute."), - args: z - .array(z.string()) - .optional() - .describe("Arguments to pass to the command."), - cwd: z - .string() - .optional() - .describe("Working directory for the process."), - env: z - .record(z.string(), z.string()) - .optional() - .describe("Additional environment variables."), - }), - execute: async (input) => { - const proc = await client.createProcess({ - command: input.command, - args: input.args, - cwd: input.cwd, - env: input.env, - }); - return { - id: proc.id, - command: proc.command, - args: proc.args, - status: proc.status, - pid: proc.pid, - }; - }, - }), - - "list-processes": binding({ - description: "List all processes running in the sandbox.", - inputSchema: z.object({}), - execute: async () => { - const result = await client.listProcesses(); - return { - processes: result.processes.map((p) => ({ - id: p.id, - command: p.command, - args: p.args, - status: p.status, - exitCode: p.exitCode, - pid: p.pid, - })), - }; - }, - }), - - "stop-process": binding({ - description: "Gracefully stop a running process in the sandbox.", - inputSchema: z.object({ - id: z.string().describe("The process ID to stop."), - }), - execute: async (input) => { - const proc = await client.stopProcess(input.id); - return { - id: proc.id, - status: proc.status, - exitCode: proc.exitCode, - }; - }, - }), - - "kill-process": binding({ - description: "Forcefully kill a running process in the sandbox.", - inputSchema: z.object({ - id: z.string().describe("The process ID to kill."), - }), - execute: async (input) => { - const proc = await client.killProcess(input.id); - return { - id: proc.id, - status: proc.status, - exitCode: proc.exitCode, - }; - }, - }), - - "get-process-logs": binding({ - description: "Get stdout/stderr logs from a sandbox process.", - inputSchema: z.object({ - id: z.string().describe("The process ID."), - stream: z - .enum(["stdout", "stderr", "combined"]) - .optional() - .describe("Which output stream to read. Defaults to combined."), - tail: z - .number() - .optional() - .describe("Only return the last N log entries."), - }), - execute: async (input) => { - const result = await client.getProcessLogs(input.id, { - stream: input.stream as - | "stdout" - | "stderr" - | "combined" - | undefined, - tail: input.tail, - }); - return { - logs: result.entries.map((e) => { - // The sandbox-agent SDK returns log data as base64. Decode it - // so callers receive plain text. - let text = e.data; - if (e.encoding === "base64") { - text = Buffer.from(e.data, "base64").toString("utf-8"); - } - return { - data: text, - stream: e.stream, - timestampMs: e.timestampMs, - }; - }), - }; - }, - }), - - "send-input": binding({ - description: - "Send text input to an interactive sandbox process via stdin.", - inputSchema: z.object({ - id: z.string().describe("The process ID."), - data: z.string().describe("The text to send to the process stdin."), - }), - execute: async (input) => { - // Encode the text as base64 for the sandbox-agent API. - const encoded = Buffer.from(input.data, "utf-8").toString("base64"); - await client.sendProcessInput(input.id, { - data: encoded, - encoding: "base64", - }); - return { sent: true }; - }, - }), - }, - }; -} +export type { AgentOsSandboxClientOptions as SandboxBindingsOptions } from "@rivet-dev/agentos-core"; +export { createSandboxBindings } from "@rivet-dev/agentos-core"; diff --git a/packages/agentos-sandbox/src/index.ts b/packages/agentos-sandbox/src/index.ts index daf7aa07b..ea3b52b83 100644 --- a/packages/agentos-sandbox/src/index.ts +++ b/packages/agentos-sandbox/src/index.ts @@ -1,8 +1,30 @@ +import type { AgentOsSandboxProvider } from "@rivet-dev/agentos-core"; +import { SandboxAgent } from "sandbox-agent"; +import type { DockerProviderOptions } from "sandbox-agent/docker"; +import { docker as sandboxAgentDocker } from "sandbox-agent/docker"; + export type { SandboxFsOptions, SandboxMountPluginConfig, -} from "@secure-exec/sandbox"; -export { createSandboxFs } from "@secure-exec/sandbox"; +} from "./mount.js"; +export { createSandboxFs } from "./mount.js"; export type { SandboxBindingsOptions } from "./bindings.js"; export { createSandboxBindings } from "./bindings.js"; + +export type { + AgentOsSandboxClient as SandboxClient, + AgentOsSandboxClientOptions as SandboxClientOptions, + AgentOsSandboxInput as SandboxInput, + AgentOsSandboxOptions as SandboxOptions, + AgentOsSandboxProvider as SandboxProvider, + AgentOsSandboxProviderOptions as SandboxProviderOptions, +} from "@rivet-dev/agentos-core"; +export type { DockerProviderOptions }; + +export function docker(options?: DockerProviderOptions): AgentOsSandboxProvider { + const provider = sandboxAgentDocker(options); + return { + start: () => SandboxAgent.start({ sandbox: provider }), + }; +} diff --git a/packages/agentos-sandbox/src/mount.ts b/packages/agentos-sandbox/src/mount.ts new file mode 100644 index 000000000..cc92dfc03 --- /dev/null +++ b/packages/agentos-sandbox/src/mount.ts @@ -0,0 +1,5 @@ +export type { + AgentOsSandboxClientOptions as SandboxFsOptions, + SandboxMountPluginConfig, +} from "@rivet-dev/agentos-core"; +export { createSandboxFs } from "@rivet-dev/agentos-core"; diff --git a/packages/agentos-sandbox/tests/vm-integration.test.ts b/packages/agentos-sandbox/tests/vm-integration.test.ts index b794b9c5d..769933000 100644 --- a/packages/agentos-sandbox/tests/vm-integration.test.ts +++ b/packages/agentos-sandbox/tests/vm-integration.test.ts @@ -11,7 +11,7 @@ import { expect, it, } from "vitest"; -import { createSandboxBindings, createSandboxFs } from "../src/index.js"; +import { createSandboxBindings } from "../src/index.js"; let sandbox: MockSandboxAgentHandle; @@ -37,19 +37,14 @@ describe("VM integration", () => { beforeEach(async () => { vm = await AgentOs.create({ permissions: SANDBOX_TEST_PERMISSIONS, + defaultSoftware: false, software: [common], - mounts: [ - { - path: "/sandbox", - plugin: createSandboxFs({ client: sandbox.client }), - }, - ], - bindings: [createSandboxBindings({ client: sandbox.client })], + sandbox: { client: sandbox.client }, }); }); afterEach(async () => { - await vm.dispose(); + await vm?.dispose(); }); // -- Filesystem mount tests -- diff --git a/packages/agentos/src/actor.ts b/packages/agentos/src/actor.ts index e54b908c5..4947c1273 100644 --- a/packages/agentos/src/actor.ts +++ b/packages/agentos/src/actor.ts @@ -15,12 +15,16 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import common from "@agentos-software/common"; import { + AgentOs, + type AgentOsOptions, OPT_AGENTOS_BIN, OPT_AGENTOS_ROOT, + parseAgentOsOptions, } from "@rivet-dev/agentos-core"; import { getSidecarPath } from "@rivet-dev/agentos-sidecar"; import { actor, + event, type ActorDefinition, type ActorFactoryHandle, type CoreRuntime, @@ -35,8 +39,18 @@ import { nativeAgentOsOptionsSchema, } from "./config.js"; import { getPluginPath } from "./plugin-binary.js"; -import type { AgentOsActions } from "./actor-actions.js"; -import type { AgentOsActorState, AgentOsActorVars } from "./types.js"; +import type { + AgentOsActions, + DirEntry, + MountInfo, + SoftwareInfo, + VmFetchOptions, +} from "./actor-actions.js"; +import type { + AgentOsActorState, + AgentOsActorVars, + AgentOsEvents, +} from "./types.js"; /** * Build the JSON envelope the Rust plugin consumes. The Rust deserializer @@ -325,7 +339,7 @@ function serializeNativeMounts(input: unknown): NativeMountLike[] | undefined { if (!mount || typeof mount !== "object") { throw new Error(`agentOS() options.mounts[${index}] must be an object`); } - const record = mount as Record; + const record = mount as unknown as Record; if (record.driver !== undefined) { throw new Error( "agentOS() only supports Native mounts across the NAPI boundary; Plain mounts with driver callbacks are not serializable", @@ -504,12 +518,380 @@ const AGENTOS_INSPECTOR_CONFIG = { ], }; +const AGENTOS_EVENTS = { + sessionEvent: event(), + permissionRequest: event(), + vmBooted: event(), + vmShutdown: event(), + processOutput: event(), + processExit: event(), + shellData: event(), + shellStderr: event(), + shellExit: event(), + cronEvent: event(), +}; + +function requiresJsActor( + parsed: AgentOsActorConfig, +): boolean { + return Boolean( + parsed.createOptions || parsed.options?.bindings || parsed.options?.sandbox, + ); +} + +function hasSandboxClient(options: AgentOsOptions | undefined): boolean { + return Boolean( + options?.sandbox && + typeof options.sandbox === "object" && + "client" in options.sandbox, + ); +} + +function splitCreateOptionsResult( + result: unknown, +): { options: AgentOsOptions; dispose?: () => void | Promise } { + if ( + result && + typeof result === "object" && + !Array.isArray(result) && + "options" in result + ) { + const record = result as { + options?: AgentOsOptions; + dispose?: () => void | Promise; + }; + return { + options: record.options ?? {}, + dispose: record.dispose, + }; + } + return { options: (result ?? {}) as AgentOsOptions }; +} + +async function resolveActorVmOptions( + parsed: AgentOsActorConfig, + c: unknown, +): Promise<{ options: AgentOsOptions; dispose?: () => void | Promise }> { + const created = parsed.createOptions + ? splitCreateOptionsResult(await parsed.createOptions(c as never)) + : { options: {} }; + return { + options: parseAgentOsOptions({ + ...(parsed.options ?? {}), + ...created.options, + }), + dispose: created.dispose, + }; +} + +function requireActorVm(c: { vars: AgentOsActorVars }): AgentOs { + if (!c.vars.agentOs) { + throw new Error("agentOS VM is not running"); + } + return c.vars.agentOs; +} + +function dirEntryType(stat: { isSymbolicLink?: boolean; isDirectory?: boolean }): DirEntry["type"] { + if (stat.isSymbolicLink) return "symlink"; + if (stat.isDirectory) return "directory"; + return "file"; +} + +async function readdirEntries(vm: AgentOs, path: string): Promise { + const names = await vm.readdir(path); + return Promise.all( + names + .filter((name) => name !== "." && name !== "..") + .map(async (name) => { + const childPath = path === "/" ? `/${name}` : `${path}/${name}`; + const stat = await vm.stat(childPath); + return { + path: childPath, + name, + type: dirEntryType(stat), + }; + }), + ); +} + +function unsupportedDynamicAction(name: string): never { + throw new Error(`agentOS createOptions actor does not support ${name} yet`); +} + +async function disposeActorVm( + c: { vars: AgentOsActorVars; broadcast: (name: string, payload: unknown) => void }, + reason: AgentOsEvents["vmShutdown"]["reason"], +): Promise { + const vm = c.vars.agentOs; + const disposeCreateOptions = c.vars.disposeCreateOptions; + c.vars.agentOs = null; + c.vars.disposeCreateOptions = undefined; + await Promise.allSettled([ + vm?.dispose(), + disposeCreateOptions?.(), + ...c.vars.activeHooks, + ]); + c.broadcast("vmShutdown", { reason }); +} + +function toMountInfo(options: AgentOsOptions): MountInfo[] { + return (options.mounts ?? []).map((mount) => { + const record = mount as unknown as Record; + const plugin = record.plugin as Record | undefined; + return { + path: String(record.path ?? ""), + kind: + typeof plugin?.id === "string" + ? (plugin.id as MountInfo["kind"]) + : "host_dir", + config: plugin?.config ?? null, + readOnly: record.readOnly === true, + }; + }); +} + +function toSoftwareInfo(options: AgentOsOptions): SoftwareInfo[] { + const software = Array.isArray(options.software) ? options.software.flat() : []; + return software.map((entry) => { + const record = + entry && typeof entry === "object" + ? (entry as unknown as Record) + : {}; + return { + package: + typeof record.name === "string" + ? record.name + : typeof entry === "string" + ? entry + : typeof record.packageDir === "string" + ? record.packageDir + : typeof record.dir === "string" + ? record.dir + : "unknown", + kind: "tool", + version: typeof record.version === "string" ? record.version : null, + }; + }); +} + +const jsActorActions: AgentOsActions = { + readFile: async (c, path) => requireActorVm(c).readFile(path), + writeFile: async (c, path, content) => requireActorVm(c).writeFile(path, content), + stat: async (c, path) => requireActorVm(c).stat(path), + mkdir: async (c, path) => requireActorVm(c).mkdir(path, { recursive: true }), + readdir: async (c, path) => readdirEntries(requireActorVm(c), path), + exists: async (c, path) => requireActorVm(c).exists(path), + move: async (c, from, to) => requireActorVm(c).move(from, to), + deleteFile: async (c, path, options) => requireActorVm(c).delete(path, options), + writeFiles: async (c, entries) => + (await requireActorVm(c).writeFiles(entries)).map((result) => ({ + path: result.path, + ok: result.success, + error: result.error, + })), + readFiles: async (c, paths) => + (await requireActorVm(c).readFiles(paths)).map((result) => ({ + path: result.path, + ...(result.content ? { content: result.content } : {}), + error: result.error, + })), + readdirRecursive: async (c, path) => + (await requireActorVm(c).readdirRecursive(path)).map((entry) => ({ + path: entry.path, + name: entry.path.split("/").filter(Boolean).at(-1) ?? entry.path, + type: entry.type, + })), + + exec: async (c, command) => requireActorVm(c).exec(command), + spawn: async (c, command, args, options) => { + const vm = requireActorVm(c); + let pid = 0; + const spawned = vm.spawn(command, args, { + ...options, + onStdout: (data) => c.broadcast("processOutput", { pid, stream: "stdout", data }), + onStderr: (data) => c.broadcast("processOutput", { pid, stream: "stderr", data }), + }); + pid = spawned.pid; + c.vars.activeProcesses.add(pid); + const hook = vm.waitProcess(pid).then((exitCode) => { + c.vars.activeProcesses.delete(pid); + c.broadcast("processExit", { pid, exitCode }); + }); + c.vars.activeHooks.add(hook); + void hook.finally(() => c.vars.activeHooks.delete(hook)); + return { pid }; + }, + waitProcess: async (c, pid) => requireActorVm(c).waitProcess(pid), + killProcess: async (c, pid) => { + requireActorVm(c).killProcess(pid); + }, + stopProcess: async (c, pid) => { + requireActorVm(c).stopProcess(pid); + }, + listProcesses: async (c) => requireActorVm(c).listProcesses() as never, + allProcesses: async (c) => requireActorVm(c).allProcesses(), + processTree: async (c) => requireActorVm(c).processTree() as never, + getProcess: async (c, pid) => requireActorVm(c).getProcess(pid) as never, + writeProcessStdin: async (c, pid, data) => requireActorVm(c).writeProcessStdin(pid, data), + closeProcessStdin: async (c, pid) => requireActorVm(c).closeProcessStdin(pid), + + openShell: async (c, options) => { + const vm = requireActorVm(c); + let shellId = ""; + const opened = vm.openShell({ + ...options, + onStderr: (data) => c.broadcast("shellStderr", { shellId, data }), + }); + shellId = opened.shellId; + c.vars.activeShells.add(shellId); + vm.onShellData(shellId, (data) => c.broadcast("shellData", { shellId, data })); + const hook = vm.waitShell(shellId).then((exitCode) => { + c.vars.activeShells.delete(shellId); + c.broadcast("shellExit", { shellId, exitCode }); + }); + c.vars.activeHooks.add(hook); + void hook.finally(() => c.vars.activeHooks.delete(hook)); + return { shellId }; + }, + writeShell: async (c, shellId, data) => requireActorVm(c).writeShell(shellId, data), + resizeShell: async (c, shellId, cols, rows) => { + requireActorVm(c).resizeShell(shellId, cols, rows); + }, + closeShell: async (c, shellId) => { + requireActorVm(c).closeShell(shellId); + }, + waitShell: async (c, shellId) => requireActorVm(c).waitShell(shellId), + + vmFetch: async (c, port, url, options?: VmFetchOptions) => { + const response = await requireActorVm(c).fetch( + port, + new Request(url, options as RequestInit), + ); + const headers: Record = {}; + response.headers.forEach((value, key) => { + headers[key] = value; + }); + return { + status: response.status, + statusText: response.statusText, + headers, + body: new Uint8Array(await response.arrayBuffer()), + }; + }, + + scheduleCron: async (c, options) => { + const action = options.action; + const job = requireActorVm(c).scheduleCron({ + ...options, + action: { + type: "callback", + fn: + action.type === "exec" + ? async () => { + await requireActorVm(c).exec([ + action.command, + ...(action.args ?? []), + ].join(" ")); + } + : async () => { + const { sessionId } = await requireActorVm(c).createSession( + action.agentType, + { cwd: action.cwd }, + ); + await requireActorVm(c).prompt(sessionId, action.prompt); + }, + }, + }); + return { id: job.id }; + }, + listCronJobs: async (c) => requireActorVm(c).listCronJobs() as never, + cancelCronJob: async (c, id) => { + requireActorVm(c).cancelCronJob(id); + }, + + createSession: async (c, agentType, options) => { + const vm = requireActorVm(c); + const { sessionId } = await vm.createSession(agentType, options); + c.vars.sessions.add(sessionId); + c.vars.activeSessionIds.add(sessionId); + vm.onSessionEvent(sessionId, (sessionEvent) => { + c.broadcast("sessionEvent", { sessionId, event: sessionEvent }); + }); + vm.onPermissionRequest(sessionId, (request) => { + c.broadcast("permissionRequest", { sessionId, request }); + }); + return sessionId; + }, + sendPrompt: async (c, sessionId, text) => requireActorVm(c).prompt(sessionId, text), + closeSession: async (c, sessionId) => { + await requireActorVm(c).destroySession(sessionId); + c.vars.activeSessionIds.delete(sessionId); + }, + listPersistedSessions: async (c) => + requireActorVm(c).listSessions().map((session) => ({ + ...session, + capabilities: {}, + agentInfo: null, + createdAt: Date.now(), + })), + getSessionEvents: async () => [], + + createSignedPreviewUrl: async () => unsupportedDynamicAction("createSignedPreviewUrl"), + expireSignedPreviewUrl: async () => unsupportedDynamicAction("expireSignedPreviewUrl"), + + listMounts: async (c) => toMountInfo(c.vars.options), + listSoftware: async (c) => toSoftwareInfo(c.vars.options), +}; + +function createJsAgentOS( + parsed: AgentOsActorConfig, + actorOptions: Record, +): AgentOsActorDefinition { + return actor({ + events: AGENTOS_EVENTS, + actions: jsActorActions, + options: actorOptions, + inspector: AGENTOS_INSPECTOR_CONFIG, + onBeforeConnect: parsed.onBeforeConnect, + createVars: async (c) => { + const { options, dispose } = await resolveActorVmOptions(parsed, c); + let vm: AgentOs | undefined; + try { + vm = await AgentOs.create(options); + return { + agentOs: vm, + activeSessionIds: new Set(), + activeProcesses: new Set(), + activeHooks: new Set>(), + activeShells: new Set(), + sessions: new Set(), + options, + disposeCreateOptions: dispose, + }; + } catch (error) { + await Promise.allSettled([vm?.dispose(), dispose?.()]); + throw error; + } + }, + onWake: (c) => { + c.broadcast("vmBooted", {}); + }, + onSleep: (c) => disposeActorVm(c as never, "sleep"), + onDestroy: (c) => disposeActorVm(c as never, "destroy"), + } as Parameters[0]) as unknown as AgentOsActorDefinition; +} + export function createAgentOS( config: AgentOsActorConfigInput, ): AgentOsActorDefinition { const parsed = agentOsActorConfigSchema.parse( config, ) as AgentOsActorConfig; + if (hasSandboxClient(parsed.options)) { + throw new Error( + "agentOS actor sandbox clients must be returned from createOptions so each actor instance gets its own sandbox client. Top-level sandbox: { provider } is allowed.", + ); + } // Construct a minimal definition through the existing actor() helper, then // attach the Rust factory builder marker. The actions block stays empty @@ -523,6 +905,10 @@ export function createAgentOS( ...DEFAULT_AGENTOS_ACTOR_OPTIONS, ...(userActorOptions ?? {}), }; + if (requiresJsActor(parsed)) { + return createJsAgentOS(parsed, actorOptions); + } + nativeAgentOsOptionsSchema.parse(parsed.options ?? {}); const definition = actor({ actions: {}, options: actorOptions, diff --git a/packages/agentos/src/config.ts b/packages/agentos/src/config.ts index 510f26e7f..e8c1a4785 100644 --- a/packages/agentos/src/config.ts +++ b/packages/agentos/src/config.ts @@ -50,7 +50,17 @@ export const agentOsActorOptionsSchema = z export const agentOsActorConfigSchema = z .object({ - options: nativeAgentOsOptionsSchema.optional(), + options: z + .custom( + (value) => + value === undefined || + (typeof value === "object" && + value !== null && + !Array.isArray(value)), + { message: "Expected AgentOsOptions object" }, + ) + .optional(), + createOptions: zFunction().optional(), actorOptions: agentOsActorOptionsSchema, preview: z .object({ @@ -120,18 +130,37 @@ interface AgentOsActorConfigCallbacks { ) => void | Promise; } +export type AgentOsCreateOptionsResult = + | AgentOsOptions + | { + options: AgentOsOptions; + dispose?: () => void | Promise; + }; + +export type AgentOsCreateOptions = ( + c: AgentOsActorContext, +) => AgentOsCreateOptionsResult | Promise; + // Parsed config (after Zod defaults/transforms applied). export type AgentOsActorConfig = Omit< z.infer, - "options" | "onBeforeConnect" | "onSessionEvent" | "onPermissionRequest" + | "options" + | "createOptions" + | "onBeforeConnect" + | "onSessionEvent" + | "onPermissionRequest" > & - { options?: NativeAgentOsOptions } & + { options?: AgentOsOptions; createOptions?: AgentOsCreateOptions } & AgentOsActorConfigCallbacks; // Input config (what users pass in before Zod transforms). export type AgentOsActorConfigInput = Omit< z.input, - "options" | "onBeforeConnect" | "onSessionEvent" | "onPermissionRequest" + | "options" + | "createOptions" + | "onBeforeConnect" + | "onSessionEvent" + | "onPermissionRequest" > & - { options?: NativeAgentOsOptions } & + { options?: AgentOsOptions; createOptions?: AgentOsCreateOptions } & AgentOsActorConfigCallbacks; diff --git a/packages/agentos/src/index.ts b/packages/agentos/src/index.ts index a70972e28..d9ee640df 100644 --- a/packages/agentos/src/index.ts +++ b/packages/agentos/src/index.ts @@ -17,6 +17,7 @@ import { } from "./actor.js"; import type { AgentOsActorConfigInput, + AgentOsCreateOptions, NativeAgentOsOptions, } from "./config.js"; @@ -51,6 +52,8 @@ export { export { type AgentOsActorConfig, type AgentOsActorConfigInput, + type AgentOsCreateOptions, + type AgentOsCreateOptionsResult, type NativeAgentOsOptions, agentOsActorConfigSchema, nativeAgentOsOptionsSchema, @@ -100,6 +103,7 @@ export type AgentOSActorConfigInput = Omit, "options">; export type AgentOSConfigInput = AgentOsOptions & { + createOptions?: AgentOsCreateOptions; preview?: AgentOsActorConfigInput["preview"]; onBeforeConnect?: AgentOsActorConfigInput["onBeforeConnect"]; onSessionEvent?: ( @@ -112,19 +116,38 @@ export type AgentOSConfigInput = AgentOsOptions & { ) => void | Promise; }; +function hasSandboxClient(options: AgentOsOptions): boolean { + return Boolean( + options.sandbox && + typeof options.sandbox === "object" && + "client" in options.sandbox, + ); +} + +function rejectTopLevelSandboxClient(options: AgentOsOptions): void { + if (hasSandboxClient(options)) { + throw new Error( + "agentOS({ sandbox: { client } }) is not allowed because it would reuse the same sandbox client across actor instances. Use sandbox: { provider } at the top level, or return a manually managed client from createOptions.", + ); + } +} + export function agentOS( config: AgentOSConfigInput = {}, ): AgentOsActorDefinition { const { preview, + createOptions, onBeforeConnect, onSessionEvent, onPermissionRequest, ...options } = config; + rejectTopLevelSandboxClient(options as AgentOsOptions); return createAgentOS({ options, + createOptions, preview, onBeforeConnect, onSessionEvent: onSessionEvent diff --git a/packages/agentos/src/types.ts b/packages/agentos/src/types.ts index 9a36b745d..5e8a6f389 100644 --- a/packages/agentos/src/types.ts +++ b/packages/agentos/src/types.ts @@ -2,6 +2,7 @@ import type { AgentCapabilities, AgentInfo, AgentOs, + AgentOsOptions, CronEvent, JsonRpcNotification, JsonRpcResponse, @@ -23,6 +24,8 @@ export interface AgentOsActorVars { activeHooks: Set>; activeShells: Set; sessions: Set; + options: AgentOsOptions; + disposeCreateOptions?: () => void | Promise; } // --- Event payloads --- diff --git a/packages/agentos/tests/actor.test.ts b/packages/agentos/tests/actor.test.ts index 00b464759..2fc0372d7 100644 --- a/packages/agentos/tests/actor.test.ts +++ b/packages/agentos/tests/actor.test.ts @@ -373,19 +373,21 @@ describe("@rivet-dev/agentos native plugin package bridge", () => { expect(calls[0].configJson).not.toContain("onSessionEvent"); }); - test("rejects native actor options that cannot cross the NAPI config boundary", () => { - expect(() => + test("agentOS flat config uses JS actor path for create options and bindings", () => { + expect( agentOS({ - bindings: [], - } as never), - ).toThrow(/bindings/); + createOptions: () => ({ defaultSoftware: false }), + }).nativeFactoryBuilder, + ).toBeUndefined(); - expect(() => + expect( agentOS({ - createOptions: () => ({}), - } as never), - ).toThrow(/createOptions/); + bindings: [], + }).nativeFactoryBuilder, + ).toBeUndefined(); + }); + test("rejects native actor options that cannot cross the NAPI config boundary", () => { expect(() => agentOS({ mounts: [{ path: "/data", driver: {} }], @@ -487,7 +489,7 @@ describe("@rivet-dev/agentos native plugin package bridge", () => { packageDir: "/abs/project/node_modules/@agentos-software/pi", agent: {}, }, - { packageDir: "/abs/tool-package", hostTool: {} }, + { packageDir: "/abs/binding-package", hostBinding: {} }, ], }, preview: { @@ -502,7 +504,7 @@ describe("@rivet-dev/agentos native plugin package bridge", () => { package: "/abs/project/node_modules/@agentos-software/pi", kind: "agent", }, - { package: "/abs/tool-package", kind: "tool" }, + { package: "/abs/binding-package", kind: "tool" }, ]); }); diff --git a/packages/agentos/tests/config.test.ts b/packages/agentos/tests/config.test.ts new file mode 100644 index 000000000..5c03795ed --- /dev/null +++ b/packages/agentos/tests/config.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "vitest"; +import { agentOS } from "../src/index.js"; + +describe("@rivet-dev/agentos native actor config", () => { + test("create options use the JS actor path instead of the native config boundary", () => { + const definition = agentOS({ + createOptions: () => ({ defaultSoftware: false }), + }); + + expect(definition.nativeFactoryBuilder).toBeUndefined(); + }); + + test("top-level sandbox provider uses the JS actor path", () => { + const definition = agentOS({ + sandbox: { + provider: { + start: async () => + ({ + baseUrl: "http://127.0.0.1:1234", + }) as never, + }, + }, + }); + + expect(definition.nativeFactoryBuilder).toBeUndefined(); + }); + + test("top-level sandbox client is rejected for actor instances", () => { + expect(() => + agentOS({ + sandbox: { + client: { + baseUrl: "http://127.0.0.1:1234", + } as never, + }, + } as never), + ).toThrow(/sandbox: \{ client \}/); + + const definition = agentOS({ + createOptions: () => ({ + sandbox: { + client: { + baseUrl: "http://127.0.0.1:1234", + } as never, + }, + }), + }); + expect(definition.nativeFactoryBuilder).toBeUndefined(); + }); +}); diff --git a/packages/core/src/agent-os.ts b/packages/core/src/agent-os.ts index db8067561..083b7d1ae 100644 --- a/packages/core/src/agent-os.ts +++ b/packages/core/src/agent-os.ts @@ -49,6 +49,11 @@ import type { JsonRpcResponse, } from "./json-rpc.js"; import { parseAgentOsOptions } from "./options-schema.js"; +import { + getSandboxDisposeHooks, + resolveSandboxOptions, + type AgentOsSandboxInput, +} from "./sandbox.js"; import type { ConnectTerminalOptions, Kernel, @@ -615,6 +620,8 @@ export interface AgentOsOptions { rootFilesystem?: RootFilesystemConfig; /** Filesystems to mount at boot time. */ mounts?: MountConfig[]; + /** Sandbox Agent integration. Adds a filesystem mount and sandbox process bindings. */ + sandbox?: AgentOsSandboxInput; /** * @deprecated Use `mounts: [nodeModulesMount(path)]` instead. * Compatibility alias for mounting `/node_modules` at `/root/node_modules`. @@ -2756,6 +2763,7 @@ export class AgentOs { private readonly _agentStderrHandler?: AgentStderrHandler; private readonly _agentExitHandler?: AgentExitHandler; private readonly _limitWarningHandler?: LimitWarningHandler; + private readonly _disposeHooks: Array<() => void | Promise> = []; private constructor( kernel: Kernel, @@ -2812,6 +2820,8 @@ export class AgentOs { static async create(options?: AgentOsOptions): Promise { options = parseAgentOsOptions(options); + options = await resolveSandboxOptions(options); + const sandboxDisposeHooks = getSandboxDisposeHooks(options); // Default software is FULLY DYNAMIC: this package's own NON-agent // @agentos-software/* dependencies (e.g. common), each default-exporting // its registry-built descriptor. Agent packages are NOT projected here — @@ -3153,6 +3163,7 @@ export class AgentOs { vm._bindingGroups = vmAdmin.bindingGroups; vm._bindingReference = vmAdmin.bindingReference; vm._permissions = vmAdmin.permissions; + vm._disposeHooks.push(...sandboxDisposeHooks); vm._installSidecarRequestHandler(); vm._cronManager = new CronManager( vm, @@ -3162,6 +3173,7 @@ export class AgentOs { return vm; } catch (error) { await sidecarLease?.dispose().catch(() => {}); + await Promise.allSettled(sandboxDisposeHooks.map((hook) => hook())); throw error; } } @@ -5684,10 +5696,14 @@ export class AgentOs { const sidecarLease = this._sidecarLease; this._sidecarLease = null; - if (sidecarLease) { - return sidecarLease.dispose(); + const disposeVm = + sidecarLease ? sidecarLease.dispose() : this.#kernel.dispose(); + try { + await disposeVm; + } finally { + const hooks = this._disposeHooks.splice(0); + await Promise.allSettled(hooks.map((hook) => hook())); } - return this.#kernel.dispose(); } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3be51aed8..559b9024d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -31,6 +31,12 @@ export { sharedSidecarConfigSchema, sidecarConfigSchema, } from "./options-schema.js"; +export { + createSandboxBindings, + createSandboxFs, + getSandboxDisposeHooks, + resolveSandboxOptions, +} from "./sandbox.js"; export { createInMemoryLayerStore, createSnapshotExport, diff --git a/packages/core/src/options-schema.ts b/packages/core/src/options-schema.ts index b156378c0..9ce0f1ba3 100644 --- a/packages/core/src/options-schema.ts +++ b/packages/core/src/options-schema.ts @@ -280,6 +280,14 @@ export const agentOsOptionFieldSchemas = { highResolutionTime: z.boolean().optional(), rootFilesystem: rootFilesystemConfigSchema.optional(), mounts: z.array(mountConfigSchema).optional(), + sandbox: z + .custom( + (value) => + value === undefined || + (typeof value === "object" && value !== null && !Array.isArray(value)), + { message: "Expected sandbox options object" }, + ) + .optional(), moduleAccessCwd: z.string().optional(), additionalInstructions: z.string().optional(), scheduleDriver: z diff --git a/packages/core/src/sandbox.ts b/packages/core/src/sandbox.ts new file mode 100644 index 000000000..48713df38 --- /dev/null +++ b/packages/core/src/sandbox.ts @@ -0,0 +1,463 @@ +import { z } from "zod"; +import type { + MountConfig, + MountConfigJsonObject, + NativeMountPluginDescriptor, +} from "./agent-os.js"; +import type { Binding, BindingGroup } from "./host-bindings.js"; + +export interface AgentOsSandboxProcessResult { + stdout?: string; + stderr?: string; + exitCode?: number | null; + timedOut?: boolean; + durationMs?: number; +} + +export interface AgentOsSandboxProcessInfo { + id: string; + command?: string; + args?: string[]; + status?: string; + exitCode?: number | null; + pid?: number | null; +} + +export interface AgentOsSandboxProcessLogs { + entries: Array<{ + data: string; + encoding?: "base64" | string; + stream?: "stdout" | "stderr" | "combined" | string; + timestampMs?: number; + }>; +} + +export interface AgentOsSandboxClient { + dispose?(): Promise | void; + runProcess(options: { + command: string; + args?: string[]; + cwd?: string; + env?: Record; + timeoutMs?: number; + }): Promise; + createProcess(options: { + command: string; + args?: string[]; + cwd?: string; + env?: Record; + }): Promise; + listProcesses(): Promise<{ processes: AgentOsSandboxProcessInfo[] }>; + stopProcess(id: string): Promise; + killProcess(id: string): Promise; + getProcessLogs( + id: string, + options?: { stream?: "stdout" | "stderr" | "combined"; tail?: number }, + ): Promise; + sendProcessInput( + id: string, + input: { data: string; encoding: "base64" }, + ): Promise; +} + +export interface AgentOsSandboxProvider { + start(): Promise; +} + +export interface AgentOsSandboxCommonOptions { + /** Mount path inside the agentOS VM. Defaults to "/sandbox". */ + mountPath?: string; + /** Root path inside the external sandbox provider. Defaults to "/". */ + sandboxRoot?: string; + /** Per-request timeout for sandbox-agent filesystem calls. */ + timeoutMs?: number; + /** Maximum file size allowed for buffered pread/truncate fallbacks. */ + maxFullReadBytes?: number; + /** Marks the VM mount read-only. Defaults to false. */ + readOnly?: boolean; +} + +export interface AgentOsSandboxProviderOptions extends AgentOsSandboxCommonOptions { + /** Provider used to start a Sandbox Agent client for this VM. */ + provider: AgentOsSandboxProvider; +} + +export interface AgentOsSandboxClientOptions extends AgentOsSandboxCommonOptions { + /** Externally provisioned Sandbox Agent-compatible client instance. */ + client: AgentOsSandboxClient; + /** + * Advanced lifecycle control. Set true to dispose `client` with the VM, or + * provide a custom dispose hook. Defaults to false for the object form. + */ + dispose?: boolean | (() => void | Promise); +} + +export type AgentOsSandboxOptions = + | AgentOsSandboxProviderOptions + | AgentOsSandboxClientOptions; + +export type AgentOsSandboxInput = AgentOsSandboxOptions; + +const sandboxDisposeHooks = Symbol("agentos.sandboxDisposeHooks"); + +type SandboxDisposeHook = () => void | Promise; + +export type AgentOsSandboxExpandedOptions = { + mounts?: MountConfig[]; + bindings?: BindingGroup[]; + [sandboxDisposeHooks]?: SandboxDisposeHook[]; +}; + +type ResolvedSandboxOptions = AgentOsSandboxCommonOptions & { + client: AgentOsSandboxClient; +}; + +export type SandboxMountPluginConfig = MountConfigJsonObject & { + baseUrl: string; + token?: string; + headers?: Record; + basePath?: string; + timeoutMs?: number; + maxFullReadBytes?: number; +}; + +interface SerializableSandboxClient { + baseUrl?: string; + token?: string; + defaultHeaders?: RequestInit["headers"]; +} + +function binding( + def: Binding, +): Binding { + return def; +} + +function normalizeHeaders( + headers: RequestInit["headers"] | undefined, +): Record | undefined { + if (!headers) { + return undefined; + } + + if (headers instanceof Headers) { + return Object.fromEntries(headers.entries()); + } + + if (Array.isArray(headers)) { + return Object.fromEntries( + headers as Iterable, + ); + } + + return Object.fromEntries( + Object.entries(headers).map(([name, value]) => [name, String(value)]), + ); +} + +function getSerializableClientConfig(client: AgentOsSandboxClient): Pick< + SandboxMountPluginConfig, + "baseUrl" | "token" | "headers" +> { + const serializable = client as unknown as SerializableSandboxClient; + const baseUrl = serializable.baseUrl?.trim().replace(/\/+$/, ""); + if (!baseUrl) { + throw new Error( + "Sandbox client does not expose a serializable baseUrl; connect with a standard SandboxAgent client instance", + ); + } + + return { + baseUrl, + ...(serializable.token ? { token: serializable.token } : {}), + ...(serializable.defaultHeaders + ? { headers: normalizeHeaders(serializable.defaultHeaders) } + : {}), + }; +} + +export function createSandboxFs( + input: ResolvedSandboxOptions | AgentOsSandboxClientOptions, +): NativeMountPluginDescriptor { + const options = input; + return { + id: "sandbox_agent", + config: { + ...getSerializableClientConfig(options.client), + ...(options.sandboxRoot ? { basePath: options.sandboxRoot } : {}), + ...(options.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}), + ...(options.maxFullReadBytes != null + ? { maxFullReadBytes: options.maxFullReadBytes } + : {}), + }, + }; +} + +export function createSandboxBindings( + input: ResolvedSandboxOptions | AgentOsSandboxClientOptions, +): BindingGroup { + const options = input; + const { client } = options; + + return { + name: "sandbox", + description: + "Execute commands and manage processes in a remote sandbox environment.", + bindings: { + "run-command": binding({ + description: + "Run a command synchronously in the sandbox and return its stdout, stderr, and exit code.", + inputSchema: z.object({ + command: z + .string() + .describe("The command to execute (e.g. 'ls', 'python3')."), + args: z.array(z.string()).optional(), + cwd: z.string().optional(), + env: z.record(z.string(), z.string()).optional(), + timeoutMs: z.number().optional(), + }), + timeout: 120_000, + execute: async (input) => { + const result = await client.runProcess(input); + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: result.exitCode, + timedOut: result.timedOut, + durationMs: result.durationMs, + }; + }, + }), + + "create-process": binding({ + description: + "Start a long-running background process in the sandbox. Returns a process ID for later management.", + inputSchema: z.object({ + command: z.string(), + args: z.array(z.string()).optional(), + cwd: z.string().optional(), + env: z.record(z.string(), z.string()).optional(), + }), + execute: async (input) => { + const proc = await client.createProcess(input); + return { + id: proc.id, + command: proc.command, + args: proc.args, + status: proc.status, + pid: proc.pid, + }; + }, + }), + + "list-processes": binding({ + description: "List all processes running in the sandbox.", + inputSchema: z.object({}), + execute: async () => { + const result = await client.listProcesses(); + return { + processes: result.processes.map((p) => ({ + id: p.id, + command: p.command, + args: p.args, + status: p.status, + exitCode: p.exitCode, + pid: p.pid, + })), + }; + }, + }), + + "stop-process": binding({ + description: "Gracefully stop a running process in the sandbox.", + inputSchema: z.object({ id: z.string() }), + execute: async (input) => { + const proc = await client.stopProcess(input.id); + return { + id: proc.id, + status: proc.status, + exitCode: proc.exitCode, + }; + }, + }), + + "kill-process": binding({ + description: "Forcefully kill a running process in the sandbox.", + inputSchema: z.object({ id: z.string() }), + execute: async (input) => { + const proc = await client.killProcess(input.id); + return { + id: proc.id, + status: proc.status, + exitCode: proc.exitCode, + }; + }, + }), + + "get-process-logs": binding({ + description: "Get stdout/stderr logs from a sandbox process.", + inputSchema: z.object({ + id: z.string(), + stream: z.enum(["stdout", "stderr", "combined"]).optional(), + tail: z.number().optional(), + }), + execute: async (input) => { + const result = await client.getProcessLogs(input.id, { + stream: input.stream, + tail: input.tail, + }); + return { + logs: result.entries.map((e) => { + const data = + e.encoding === "base64" + ? Buffer.from(e.data, "base64").toString("utf-8") + : e.data; + return { + data, + stream: e.stream, + timestampMs: e.timestampMs, + }; + }), + }; + }, + }), + + "send-input": binding({ + description: + "Send text input to an interactive sandbox process via stdin.", + inputSchema: z.object({ + id: z.string(), + data: z.string(), + }), + execute: async (input) => { + await client.sendProcessInput(input.id, { + data: Buffer.from(input.data, "utf-8").toString("base64"), + encoding: "base64", + }); + return { sent: true }; + }, + }), + }, + }; +} + +function isProviderOptions( + input: AgentOsSandboxInput, +): input is AgentOsSandboxProviderOptions { + return "provider" in input; +} + +function isClientOptions( + input: AgentOsSandboxInput, +): input is AgentOsSandboxClientOptions { + return "client" in input; +} + +function assertNoLegacySandboxOptions(input: AgentOsSandboxInput): void { + const legacyKeys = ["mount", "bindings", "path", "basePath"] as const; + for (const key of legacyKeys) { + if (key in input) { + const replacement = + key === "path" || key === "basePath" ? "sandboxRoot" : undefined; + throw new Error( + replacement + ? `sandbox.${key} has been removed; use sandbox.${replacement} instead.` + : `sandbox.${key} has been removed; sandbox mounts and bindings are always enabled.`, + ); + } + } +} + +async function normalizeSandboxInput(input: AgentOsSandboxInput): Promise<{ + options: ResolvedSandboxOptions; + dispose?: SandboxDisposeHook; +}> { + assertNoLegacySandboxOptions(input); + if (isProviderOptions(input)) { + if (typeof input.provider?.start !== "function") { + throw new Error("sandbox.provider must expose a start() function."); + } + const client = await input.provider.start(); + return { + options: { ...input, client }, + dispose: () => client.dispose?.(), + }; + } + if (!isClientOptions(input)) { + throw new Error( + "sandbox must be configured with either { provider } or { client }.", + ); + } + const dispose = + typeof input.dispose === "function" + ? input.dispose + : input.dispose === true + ? () => input.client.dispose?.() + : undefined; + return { + options: input, + dispose, + }; +} + +function attachSandboxDisposeHooks( + options: T, + hooks: SandboxDisposeHook[], +): T { + if (hooks.length === 0) { + return options; + } + Object.defineProperty(options, sandboxDisposeHooks, { + value: hooks, + enumerable: false, + configurable: false, + writable: false, + }); + return options; +} + +export function getSandboxDisposeHooks( + options: object | undefined, +): SandboxDisposeHook[] { + return options + ? ((options as AgentOsSandboxExpandedOptions)[sandboxDisposeHooks] ?? []) + : []; +} + +export async function resolveSandboxOptions( + options: T, +): Promise & { + mounts?: MountConfig[]; + bindings?: BindingGroup[]; +}> { + const { sandbox, ...rest } = options; + if (!sandbox) { + return rest; + } + + const normalizedSandbox = await normalizeSandboxInput(sandbox); + const sandboxOptions = normalizedSandbox.options; + const expanded = rest as Omit & { + mounts?: MountConfig[]; + bindings?: BindingGroup[]; + }; + const mountPath = sandboxOptions.mountPath ?? "/sandbox"; + const mounts = [ + ...(expanded.mounts ?? []), + { + path: mountPath, + plugin: createSandboxFs(sandboxOptions), + readOnly: sandboxOptions.readOnly, + }, + ]; + const bindings = [ + ...(expanded.bindings ?? []), + createSandboxBindings(sandboxOptions), + ]; + + return attachSandboxDisposeHooks({ + ...expanded, + mounts, + bindings, + }, normalizedSandbox.dispose ? [normalizedSandbox.dispose] : []); +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index aaaa951b0..e2e8fcb36 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -49,6 +49,19 @@ export type { SessionModeState, SpawnedProcessInfo, } from "./agent-os.js"; +export type { + AgentOsSandboxClient, + AgentOsSandboxClientOptions, + AgentOsSandboxCommonOptions, + AgentOsSandboxInput, + AgentOsSandboxOptions, + AgentOsSandboxProvider, + AgentOsSandboxProviderOptions, + AgentOsSandboxProcessInfo, + AgentOsSandboxProcessLogs, + AgentOsSandboxProcessResult, + SandboxMountPluginConfig, +} from "./sandbox.js"; export type { AgentConfig, AgentType } from "./agents.js"; export type { CronAction, diff --git a/packages/core/tests/options-schema.test.ts b/packages/core/tests/options-schema.test.ts index ce3bd563f..d8eff9616 100644 --- a/packages/core/tests/options-schema.test.ts +++ b/packages/core/tests/options-schema.test.ts @@ -1,5 +1,11 @@ import { describe, expect, test } from "vitest"; -import { AgentOs, agentOsOptionsSchema } from "../src/index.js"; +import { + AgentOs, + agentOsOptionsSchema, + getSandboxDisposeHooks, + parseAgentOsOptions, + resolveSandboxOptions, +} from "../src/index.js"; describe("AgentOsOptions validation", () => { test("rejects unknown top-level options before booting a VM", async () => { @@ -41,4 +47,85 @@ describe("AgentOsOptions validation", () => { }).success, ).toBe(true); }); + + test("provider sandbox starts a client and owns disposal", async () => { + let disposed = false; + const client = { + baseUrl: "http://127.0.0.1:1234", + dispose: () => { + disposed = true; + }, + } as never; + + const options = await resolveSandboxOptions( + parseAgentOsOptions({ + sandbox: { + provider: { + start: async () => client, + }, + }, + }), + ); + expect(options).not.toHaveProperty("sandbox"); + expect(options.mounts?.[0]?.path).toBe("/sandbox"); + expect(options.bindings?.[0]?.name).toBe("sandbox"); + + for (const hook of getSandboxDisposeHooks(options)) { + await hook(); + } + expect(disposed).toBe(true); + }); + + test("advanced sandbox client leaves disposal manual by default", async () => { + const client = { baseUrl: "http://127.0.0.1:1234" } as never; + const parsed = parseAgentOsOptions({ + sandbox: { + client, + mountPath: "/work", + }, + }); + + const options = await resolveSandboxOptions(parsed); + expect(options.mounts?.[0]?.path).toBe("/work"); + expect(getSandboxDisposeHooks(options)).toHaveLength(0); + }); + + test("rejects removed sandbox mount and binding toggles", async () => { + const client = { baseUrl: "http://127.0.0.1:1234" } as never; + await expect( + resolveSandboxOptions( + parseAgentOsOptions({ + sandbox: { + client, + mount: false, + } as never, + }), + ), + ).rejects.toThrow(/sandbox\.mount has been removed/); + + await expect( + resolveSandboxOptions( + parseAgentOsOptions({ + sandbox: { + client, + bindings: false, + } as never, + }), + ), + ).rejects.toThrow(/sandbox\.bindings has been removed/); + }); + + test("rejects old sandbox path option names", async () => { + const client = { baseUrl: "http://127.0.0.1:1234" } as never; + await expect( + resolveSandboxOptions( + parseAgentOsOptions({ + sandbox: { + client, + basePath: "/app", + } as never, + }), + ), + ).rejects.toThrow(/sandbox\.basePath has been removed/); + }); }); diff --git a/packages/core/tests/sandbox-integration.test.ts b/packages/core/tests/sandbox-integration.test.ts index e1ec1f5cb..733a17a57 100644 --- a/packages/core/tests/sandbox-integration.test.ts +++ b/packages/core/tests/sandbox-integration.test.ts @@ -1,8 +1,5 @@ import common from "@agentos-software/common"; -import { - createSandboxBindings, - createSandboxFs, -} from "@rivet-dev/agentos-sandbox"; +import { createSandboxBindings } from "@rivet-dev/agentos-sandbox"; import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest"; import { AgentOs } from "../src/index.js"; import type { MockSandboxAgentHandle } from "../src/test/sandbox-agent.js"; @@ -46,21 +43,16 @@ describe("sandbox quickstart truth test", () => { } }); - test("mounts createSandboxFs and exercises sandbox bindings", async () => { + test("mounts sandbox option and exercises sandbox bindings", async () => { if (!sandbox) { throw new Error("Sandbox test harness did not start."); } vm = await AgentOs.create({ permissions: SANDBOX_QUICKSTART_PERMISSIONS, + defaultSoftware: false, software: [common], - mounts: [ - { - path: SANDBOX_MOUNT_PATH, - plugin: createSandboxFs({ client: sandbox.client }), - }, - ], - bindings: [createSandboxBindings({ client: sandbox.client })], + sandbox: { client: sandbox.client }, }); await sandbox.client.writeFsFile( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f32946ac..2eb3a111e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,18 +102,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -167,18 +158,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -216,18 +198,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -265,18 +238,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -363,18 +327,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -412,18 +367,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -461,18 +407,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -510,18 +447,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -559,18 +487,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -608,18 +527,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -657,18 +567,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -706,18 +607,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -755,18 +647,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -804,18 +687,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -853,18 +727,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -902,18 +767,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -951,18 +807,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1000,18 +847,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1049,18 +887,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1098,18 +927,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1147,18 +967,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1196,18 +1007,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1245,21 +1047,12 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 react: specifier: ^18.3.1 version: 18.3.1 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1300,18 +1093,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1349,18 +1133,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1398,18 +1173,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1447,18 +1213,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1496,18 +1253,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1545,18 +1293,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1594,18 +1333,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1643,18 +1373,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1692,18 +1413,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1741,18 +1453,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1790,18 +1493,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1839,18 +1533,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1888,18 +1573,12 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) sandbox-agent: specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) + version: 0.4.2(@fly/sprites@0.0.1)(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1937,18 +1616,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -1980,24 +1650,12 @@ importers: '@rivet-dev/agentos': specifier: workspace:* version: link:../../packages/agentos - '@rivet-dev/agentos-core': - specifier: workspace:* - version: link:../../packages/core '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -2035,18 +1693,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -2093,18 +1742,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -2142,21 +1782,12 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 hono: specifier: ^4.6.0 version: 4.12.9 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -2194,18 +1825,9 @@ importers: '@rivet-dev/agentos-sandbox': specifier: workspace:* version: link:../../packages/agentos-sandbox - dockerode: - specifier: ^4.0.9 - version: 4.0.10 - get-port: - specifier: ^7.1.0 - version: 7.2.0 rivetkit: specifier: catalog:rivetkit version: 0.0.0-stack-feat-rivetkit-forward-inspector-tabs-to-native-plugin-actors-qkwmksyy.d2859b0(@opentelemetry/api@1.9.0)(better-sqlite3@12.8.0)(ws@8.20.0(bufferutil@4.1.0)) - sandbox-agent: - specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -2301,15 +1923,21 @@ importers: packages/agentos-sandbox: dependencies: + '@fly/sprites': + specifier: '>=0.0.1' + version: 0.0.1 '@rivet-dev/agentos-core': specifier: workspace:* version: link:../core - '@secure-exec/sandbox': - specifier: link:../../../secure-exec/registry/tool/sandbox - version: link:../../../secure-exec/registry/tool/sandbox + dockerode: + specifier: ^4.0.9 + version: 4.0.10 + get-port: + specifier: ^7.1.0 + version: 7.2.0 sandbox-agent: specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) + version: 0.4.2(@fly/sprites@0.0.1)(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) zod: specifier: ^4.1.11 version: 4.3.6 @@ -2519,7 +2147,7 @@ importers: version: 0.0.23 sandbox-agent: specifier: ^0.4.2 - version: 0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) + version: 0.4.2(@fly/sprites@0.0.1)(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6) typescript: specifier: ^5.7.2 version: 5.9.3 @@ -3791,6 +3419,10 @@ packages: cpu: [x64] os: [win32] + '@fly/sprites@0.0.1': + resolution: {integrity: sha512-1s+dIVi/pTMP4Aj4Mkg+4LoZ/+a0Kp6l9piPRxvpgEKm11b/eRiZgJwVytwAHeI/vtg2fuwcFExjtXOEfny/TA==} + engines: {node: '>=24.0.0'} + '@google/genai@1.47.0': resolution: {integrity: sha512-0VV7AaXm5rQu3oRHNZNEubRAOL2lv5u+YA72eWnDwcOx3B1jFRbvtgL4drRHlocRHOnludvr3xmbQGbR+/RQAQ==} engines: {node: '>=20.0.0'} @@ -8782,6 +8414,8 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true + '@fly/sprites@0.0.1': {} + '@google/genai@1.47.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(bufferutil@4.1.0)': dependencies: google-auth-library: 10.6.2 @@ -12109,11 +11743,12 @@ snapshots: safer-buffer@2.1.2: {} - sandbox-agent@0.4.2(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6): + sandbox-agent@0.4.2(@fly/sprites@0.0.1)(dockerode@4.0.10)(get-port@7.2.0)(zod@4.3.6): dependencies: '@sandbox-agent/cli-shared': 0.4.2 acp-http-client: 0.4.2(zod@4.3.6) optionalDependencies: + '@fly/sprites': 0.0.1 '@sandbox-agent/cli': 0.4.2 dockerode: 4.0.10 get-port: 7.2.0 diff --git a/website/public/docs/docs/core.md b/website/public/docs/docs/core.md index 13ca65558..87d5fc9b4 100644 --- a/website/public/docs/docs/core.md +++ b/website/public/docs/docs/core.md @@ -83,4 +83,4 @@ The top-level fields are documented inline above. See [Mounts](#mounts), [Softwa | Action timeout | 15 minutes | Maximum time for any single action | | Sleep grace period | 15 minutes | Time before sleeping after all activity stops | -These are set internally by the `agentOS()` factory and cannot be overridden per-call. See [Persistence & Sleep](/docs/persistence) for details on the sleep lifecycle. \ No newline at end of file +These are set internally by the `agentOS()` factory and cannot be overridden per-call. See [Persistence & Sleep](/docs/persistence) for details on the sleep lifecycle. diff --git a/website/public/docs/docs/crash-course.md b/website/public/docs/docs/crash-course.md index c5b23d92b..9d8a90484 100644 --- a/website/public/docs/docs/crash-course.md +++ b/website/public/docs/docs/crash-course.md @@ -82,6 +82,6 @@ Schedule recurring commands and agent sessions with cron expressions. ### Sandbox Mounting -agentOS uses a hybrid model: agents run in a lightweight VM by default and can mount a full sandbox for heavy workloads like browsers, compilation, and desktop automation. Sandboxes are powered by [Sandbox Agent](https://sandboxagent.dev), so you can swap providers without changing agent code. In direct `AgentOs.create(...)` code, start one sandbox for that VM, mount its filesystem, expose its process management as bindings, and dispose both resources together. +agentOS uses a hybrid model: agents run in a lightweight VM by default and can mount a full sandbox for heavy workloads like browsers, compilation, and desktop automation. Sandboxes are powered by [Sandbox Agent](https://sandboxagent.dev), so you can swap providers without changing agent code. In RivetKit, pass `sandbox: { provider }` directly to `agentOS(...)`; the provider starts a fresh sandbox client for each actor VM, and Agent OS disposes it with that VM. [Documentation](/docs/sandbox) diff --git a/website/public/docs/docs/sandbox.md b/website/public/docs/docs/sandbox.md index 4491de7be..18ec2ef47 100644 --- a/website/public/docs/docs/sandbox.md +++ b/website/public/docs/docs/sandbox.md @@ -27,21 +27,35 @@ The sandbox integration ships as the `@rivet-dev/agentos-sandbox` package. It wo - **Filesystem mount**: Projects the sandbox into the VM as a native directory, like mounting a hard drive on your own machine. Read and write files through the mount directly. - **Bindings**: Exposes sandbox process management as the `agentos-sandbox` command inside the VM. -Both are powered by [Sandbox Agent](https://sandboxagent.dev), and you can swap providers without changing agent code. Install both packages: +Both are powered by [Sandbox Agent](https://sandboxagent.dev), and you can swap providers without changing agent code. Install the agentOS sandbox package: ```bash -npm install @rivet-dev/agentos-sandbox sandbox-agent +npm install @rivet-dev/agentos-sandbox ``` -`createSandboxFs` and `createSandboxBindings` come from `@rivet-dev/agentos-sandbox`. `SandboxAgent` and the provider helpers (such as `docker`) come from the `sandbox-agent` package. +Provider helpers such as `docker` come from `@rivet-dev/agentos-sandbox`. You should not install or import `sandbox-agent` directly for normal usage. -**Warning:** do not create one `SandboxAgent` at module scope and reuse it for multiple actor instances. Direct `AgentOs.create(...)` examples should start one sandbox for the one VM they create and dispose both together. Dynamic per-actor sandbox creation for `agentOS(...)` needs a future actor-scoped options hook; no `createOptions` callback is supported today. +Pass a provider as `sandbox: { provider: docker() }`. Agent OS starts a Sandbox Agent client, mounts it at `/sandbox`, registers the process bindings, and disposes the sandbox client when the VM is disposed. In RivetKit actors, pass the provider directly to `agentOS(...)`; the provider starts a fresh client for each actor VM. -## Calling the mounted bindings +## Configuration -Once the sandbox is mounted, write code through the filesystem and run it inside the sandbox. The sandbox bindings are exposed inside the VM as a CLI command, so you call them through the same `exec`/`spawn` surface as any other command. +Use `mountPath` to choose where the sandbox appears inside agentOS, and `sandboxRoot` to scope the external sandbox directory exposed there. + +## Advanced + +### Core API + +Core can use the same provider API directly with `AgentOs.create(...)`. + +### Custom Sandbox Agent Client -## Bindings reference +If you provision a Sandbox Agent client yourself, pass it with `client`. This is the manual lifecycle path, so Agent OS will not dispose the client unless you opt in with `dispose: true` or provide a custom dispose function. + +In RivetKit actors, manually managed clients must come from `createOptions` so a running client is never shared across actor instances. Use the flat `sandbox: { provider }` form unless you need tenant-specific setup or custom lifecycle control. + +### Binding Reference + +Once the sandbox is mounted, write code through the filesystem and run it inside the sandbox. The sandbox bindings are exposed inside the VM as a CLI command, so you call them through the same `exec`/`spawn` surface as any other command. The bindings expose these commands inside the VM: diff --git a/website/src/content/docs/docs/crash-course.mdx b/website/src/content/docs/docs/crash-course.mdx index 309e9b79c..72d3bbcf2 100644 --- a/website/src/content/docs/docs/crash-course.mdx +++ b/website/src/content/docs/docs/crash-course.mdx @@ -150,7 +150,7 @@ Schedule recurring commands and agent sessions with cron expressions. ### Sandbox Mounting -agentOS uses a hybrid model: agents run in a lightweight VM by default and can mount a full sandbox for heavy workloads like browsers, compilation, and desktop automation. Sandboxes are powered by [Sandbox Agent](https://sandboxagent.dev), so you can swap providers without changing agent code. In direct `AgentOs.create(...)` code, start one sandbox for that VM, mount its filesystem, expose its process management as bindings, and dispose both resources together. +agentOS uses a hybrid model: agents run in a lightweight VM by default and can mount a full sandbox for heavy workloads like browsers, compilation, and desktop automation. Sandboxes are powered by [Sandbox Agent](https://sandboxagent.dev), so you can swap providers without changing agent code. In RivetKit, pass `sandbox: { provider }` directly to `agentOS(...)`; the provider starts a fresh sandbox client for each actor VM, and Agent OS disposes it with that VM. diff --git a/website/src/content/docs/docs/sandbox.mdx b/website/src/content/docs/docs/sandbox.mdx index da009479b..148aa055a 100644 --- a/website/src/content/docs/docs/sandbox.mdx +++ b/website/src/content/docs/docs/sandbox.mdx @@ -29,25 +29,54 @@ The sandbox integration ships as the `@rivet-dev/agentos-sandbox` package. It wo - **Filesystem mount**: Projects the sandbox into the VM as a native directory, like mounting a hard drive on your own machine. Read and write files through the mount directly. - **Bindings**: Exposes sandbox process management as the `agentos-sandbox` command inside the VM. -Both are powered by [Sandbox Agent](https://sandboxagent.dev), and you can swap providers without changing agent code. Install both packages: +Both are powered by [Sandbox Agent](https://sandboxagent.dev), and you can swap providers without changing agent code. Install the agentOS sandbox package: ```bash -npm install @rivet-dev/agentos-sandbox sandbox-agent +npm install @rivet-dev/agentos-sandbox ``` -`createSandboxFs` and `createSandboxBindings` come from `@rivet-dev/agentos-sandbox`. `SandboxAgent` and the provider helpers (such as `docker`) come from the `sandbox-agent` package. +Provider helpers such as `docker` come from `@rivet-dev/agentos-sandbox`. You should not install or import `sandbox-agent` directly for normal usage. -**Warning:** do not create one `SandboxAgent` at module scope and reuse it for multiple actor instances. Direct `AgentOs.create(...)` examples should start one sandbox for the one VM they create and dispose both together. Dynamic per-actor sandbox creation for `agentOS(...)` needs a future actor-scoped options hook; no `createOptions` callback is supported today. +Pass a provider as `sandbox: { provider: docker() }`. Agent OS starts a Sandbox Agent client, mounts it at `/sandbox`, registers the process bindings, and disposes the sandbox client when the VM is disposed. In RivetKit actors, pass the provider directly to `agentOS(...)`; the provider starts a fresh client for each actor VM. + + -## Calling the mounted bindings +## Configuration -Once the sandbox is mounted, write code through the filesystem and run it inside the sandbox. The sandbox bindings are exposed inside the VM as a CLI command, so you call them through the same `exec`/`spawn` surface as any other command. +Use `mountPath` to choose where the sandbox appears inside agentOS, and `sandboxRoot` to scope the external sandbox directory exposed there: + +```ts +sandbox: { + provider: docker(), + mountPath: "/sandbox", + sandboxRoot: "/app", + readOnly: true, +} +``` + +## Advanced + +### Core API + +Core can use the same provider API directly with `AgentOs.create(...)`: + + -## Bindings reference +### Custom Sandbox Agent Client + +If you provision a Sandbox Agent client yourself, pass it with `client`. This is the manual lifecycle path, so Agent OS will not dispose the client unless you opt in with `dispose: true` or provide a custom dispose function. + +In RivetKit actors, manually managed clients must come from `createOptions` so a running client is never shared across actor instances. Use the flat `sandbox: { provider }` form unless you need tenant-specific setup or custom lifecycle control. + + + +### Binding Reference + +Once the sandbox is mounted, write code through the filesystem and run it inside the sandbox. The sandbox bindings are exposed inside the VM as a CLI command, so you call them through the same `exec`/`spawn` surface as any other command. The bindings expose these commands inside the VM: diff --git a/website/src/pages/registry/[slug].astro b/website/src/pages/registry/[slug].astro index 2df586df6..3c1d94342 100644 --- a/website/src/pages/registry/[slug].astro +++ b/website/src/pages/registry/[slug].astro @@ -72,20 +72,23 @@ const vm = agentOS({ software: [${ident}] }); export const registry = setup({ use: { vm } });`; } else if (isSandboxExt) { - exampleSource = `import { agentOS, setup } from "@rivet-dev/agentos"; -import { createSandboxFs, createSandboxBindings } from "@rivet-dev/agentos-sandbox"; + exampleSource = `import { AgentOs } from "@rivet-dev/agentos-core"; +import { createSandboxBindings, createSandboxFs } from "@rivet-dev/agentos-sandbox"; import { SandboxAgent } from "sandbox-agent"; import { ${entry.slug} } from "sandbox-agent/${entry.slug}"; -// Start a ${entry.title}-backed sandbox and mount it into the VM const sandbox = await SandboxAgent.start({ sandbox: ${entry.slug}() }); -const vm = agentOS({ - mounts: [{ path: "/home/agentos/sandbox", plugin: createSandboxFs({ client: sandbox }) }], +const vm = await AgentOs.create({ + mounts: [ + { path: "/home/agentos/sandbox", plugin: createSandboxFs({ client: sandbox }) }, + ], bindings: [createSandboxBindings({ client: sandbox })], }); -export const registry = setup({ use: { vm } });`; +await vm.dispose(); +await sandbox.dispose(); +`; } else if (isDocs && entry.status === "docs" && entry.package && entry.agentId) { // Built-in agent adapter: register it as VM software, then open a session // with its agent id. The import identifier matches the agent id (e.g.