From 26be07d82a870fdf8a204320b9570dd7525739a6 Mon Sep 17 00:00:00 2001 From: zozo123 Date: Tue, 28 Jul 2026 12:02:37 +0300 Subject: [PATCH 1/4] feat: integrate openclaw/crabbox as AgentENV sandbox client Point the upstream Crabbox CLI at AgentENV's E2B-compatible API with a checked-in example config, docs, env-var notes, and CI that installs openclaw/crabbox (optional Islo smoke when ISLO_API_KEY is set). Co-authored-by: Cursor --- .github/workflows/crabbox.yml | 115 ++++++++++++++++++++++ .gitignore | 3 + README.md | 29 +++++- config/crabbox.example.yaml | 22 +++++ docs/src/SUMMARY.md | 1 + docs/src/configuration/env-vars.md | 7 +- docs/src/getting-started/overview.md | 5 +- docs/src/getting-started/quickstart.md | 1 + docs/src/integration/crabbox.md | 112 +++++++++++++++++++++ docs/src/integration/e2b.md | 3 + docs/src/troubleshooting/common-issues.md | 20 ++++ 11 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/crabbox.yml create mode 100644 config/crabbox.example.yaml create mode 100644 docs/src/integration/crabbox.md diff --git a/.github/workflows/crabbox.yml b/.github/workflows/crabbox.yml new file mode 100644 index 00000000..20f614b0 --- /dev/null +++ b/.github/workflows/crabbox.yml @@ -0,0 +1,115 @@ +name: crabbox + +# Validates the openclaw/crabbox integration (https://crabbox.sh). +# - Always installs upstream crabbox, checks the e2b provider, and runs doctor. +# - Validates config/crabbox.example.yaml parses as YAML. +# - Builds mdBook so the Crabbox integration page stays linked. +# - Optional Islo smoke when repository secret ISLO_API_KEY is set +# (mint with: islo api-key create …). Forks without the secret no-op cleanly. + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "config/crabbox.example.yaml" + - "docs/src/integration/crabbox.md" + - "docs/src/SUMMARY.md" + - "docs/src/configuration/env-vars.md" + - "docs/src/getting-started/**" + - ".github/workflows/crabbox.yml" + pull_request: + paths: + - "config/crabbox.example.yaml" + - "docs/src/integration/crabbox.md" + - "docs/src/SUMMARY.md" + - "docs/src/configuration/env-vars.md" + - "docs/src/getting-started/**" + - ".github/workflows/crabbox.yml" + +permissions: + contents: read + +concurrency: + group: crabbox-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integrate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate config/crabbox.example.yaml + run: | + set -euo pipefail + python3 - <<'PY' + from pathlib import Path + try: + import yaml + except ImportError: + import subprocess, sys + subprocess.check_call([sys.executable, "-m", "pip", "install", "--quiet", "pyyaml"]) + import yaml + path = Path("config/crabbox.example.yaml") + data = yaml.safe_load(path.read_text()) + assert data.get("provider") == "e2b", data + assert data.get("target") == "linux", data + e2b = data.get("e2b") or {} + assert "apiUrl" in e2b and "template" in e2b and "workdir" in e2b, e2b + print("ok", path, "provider=e2b template=", e2b["template"]) + PY + + - name: Install openclaw/crabbox + run: | + set -euo pipefail + tag="$(curl -fsSL https://api.github.com/repos/openclaw/crabbox/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)" + ver="${tag#v}" + curl -fsSL "https://github.com/openclaw/crabbox/releases/download/${tag}/crabbox_${ver}_linux_amd64.tar.gz" \ + | sudo tar -xz -C /usr/local/bin crabbox + crabbox --version + + - name: crabbox providers include e2b + run: | + set -euo pipefail + crabbox providers 2>&1 | tee /tmp/crabbox-providers.txt + grep -E '(^|[[:space:]])e2b([[:space:]]|$)' /tmp/crabbox-providers.txt + + - name: crabbox doctor + run: crabbox doctor + + - uses: taiki-e/install-action@mdbook + + - name: Build docs (mdBook) + run: | + set -euo pipefail + ln -sf ../../src/api/openapi.yml docs/src/openapi.yml + mdbook build docs + + - name: Guard — optional Islo smoke + id: guard + env: + ISLO_API_KEY: ${{ secrets.ISLO_API_KEY }} + run: | + if [ -z "${ISLO_API_KEY}" ]; then + echo "ISLO_API_KEY not configured — skipping islo smoke." + echo "run=false" >> "$GITHUB_OUTPUT" + else + echo "run=true" >> "$GITHUB_OUTPUT" + fi + + - name: crabbox run --provider islo + if: steps.guard.outputs.run == 'true' + env: + ISLO_API_KEY: ${{ secrets.ISLO_API_KEY }} + run: | + set -euo pipefail + # Prefer Islo tenant defaults (omit --islo-image); keep the lease small. + crabbox run \ + --provider islo \ + --islo-vcpus 2 \ + --islo-memory-mb 2048 \ + --islo-disk-gb 10 \ + --no-sync \ + -- echo crabbox-islo-ok diff --git a/.gitignore b/.gitignore index a18123c4..add368b3 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,9 @@ Temporary Items env/ .env +# Local openclaw/crabbox project config (copy from config/crabbox.example.yaml) +.crabbox.yaml + # OpenAPI Generator .openapi-generator/ diff --git a/README.md b/README.md index f44a3daa..60ecaf8d 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,35 @@ see 📖 [Deployment](https://kvcache-ai.github.io/AgentENV/deployment/manual-co --- +## 🦀 Crabbox sandbox client + +[Crabbox](https://crabbox.sh/) ([openclaw/crabbox](https://github.com/openclaw/crabbox)) +is the recommended sandbox client for AgentENV: sync a checkout into a +Firecracker sandbox, run a command, stream output, and release. + +```bash +brew install openclaw/tap/crabbox + +export E2B_API_URL=http://127.0.0.1:8000 +export E2B_API_KEY=e2b_000000 +export CRABBOX_E2B_TEMPLATE=ubuntu # AgentENV template id or name +# optional: cp config/crabbox.example.yaml .crabbox.yaml + +crabbox doctor --provider e2b +crabbox run --provider e2b -- make test-unit +``` + +See 📖 [Crabbox integration](https://kvcache-ai.github.io/AgentENV/integration/crabbox.html). +Verified with [Islo](https://islo.dev) via `crabbox --provider islo` while landing +this client path. + ## 🔌 E2B compatibility AgentENV exposes an E2B-compatible HTTP API. Point `E2B_API_URL` at your -server and use the standard E2B Python / TypeScript SDK without any code -changes. See 📖 [E2B integration](https://kvcache-ai.github.io/AgentENV/integration/e2b.html) -for setup details. +server and use the standard E2B Python / TypeScript SDK — or Crabbox’s +`e2b` provider — without any AgentENV code changes. See 📖 +[E2B integration](https://kvcache-ai.github.io/AgentENV/integration/e2b.html) +for SDK setup details. --- diff --git a/config/crabbox.example.yaml b/config/crabbox.example.yaml new file mode 100644 index 00000000..0a556cf3 --- /dev/null +++ b/config/crabbox.example.yaml @@ -0,0 +1,22 @@ +# Example openclaw/crabbox config for AgentENV. +# Copy to the repo root as `.crabbox.yaml`, or merge into your user Crabbox config. +# Docs: https://crabbox.sh/ · Provider: https://crabbox.sh/providers/e2b.html +# +# Auth stays in the environment (never commit keys): +# export E2B_API_URL=http://127.0.0.1:8000 # or https://… behind TLS +# export E2B_API_KEY=e2b_000000 +# +# Then: +# brew install openclaw/tap/crabbox +# crabbox doctor --provider e2b +# crabbox run --provider e2b -- make test-unit + +provider: e2b +target: linux +e2b: + # Override with E2B_API_URL / CRABBOX_E2B_API_URL when unset here. + apiUrl: http://127.0.0.1:8000 + # AgentENV template id or name (aenv pull … / aenv template list) + template: ubuntu + # Dedicated subdirectory inside the sandbox (not /, /tmp, …) + workdir: crabbox diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 76c4d02e..8b31b778 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -34,6 +34,7 @@ # Integration +- [Crabbox](./integration/crabbox.md) - [E2B](./integration/e2b.md) # Troubleshooting diff --git a/docs/src/configuration/env-vars.md b/docs/src/configuration/env-vars.md index 0812a135..0b147325 100644 --- a/docs/src/configuration/env-vars.md +++ b/docs/src/configuration/env-vars.md @@ -36,9 +36,9 @@ These variables are consumed by the repository's Docker Compose and Kubernetes h | `AENV_FIRECRACKER_SERIAL_DIR` | `$AENV_HOME/logs/serial` | Override the directory for persistent Firecracker serial output. Files are grouped under `{serial_dir}/{sandbox_id}/`. | | `AENV_PERSISTED_SANDBOX_STORE_PATH` | `$AENV_HOME/persisted-sandboxes` | Override the directory where paused sandbox state is persisted across server restarts. | -## E2B SDK / CLI +## E2B SDK / CLI / Crabbox -These variables configure the E2B SDK and CLI to point at an AgentENV server. Values depend on your deployment mode. +These variables configure the E2B SDK, E2B CLI, and [Crabbox](../integration/crabbox.md)’s `e2b` provider to point at an AgentENV server. Values depend on your deployment mode. | Variable | Description | |----------|-------------| @@ -46,6 +46,9 @@ These variables configure the E2B SDK and CLI to point at an AgentENV server. Va | `E2B_SANDBOX_URL` | Sandbox proxy URL (for WebSocket and process interaction) | | `E2B_API_KEY` | API key for authentication | | `E2B_ACCESS_TOKEN` | Access token (used by `e2b template` commands) | +| `CRABBOX_E2B_API_URL` | Crabbox override for `E2B_API_URL` (takes precedence) | +| `CRABBOX_E2B_API_KEY` | Crabbox override for `E2B_API_KEY` (takes precedence) | +| `CRABBOX_E2B_TEMPLATE` | AgentENV template id/name for `crabbox run --provider e2b` | ### Values by Deployment Mode diff --git a/docs/src/getting-started/overview.md b/docs/src/getting-started/overview.md index a7aed99e..8579d06b 100644 --- a/docs/src/getting-started/overview.md +++ b/docs/src/getting-started/overview.md @@ -16,7 +16,7 @@ The repository is available at . - **Pause and resume** with memory + disk snapshots for instant cold start - **Layered block devices** via overlaybd + ublk for copy-on-write image sharing - **Snapshot-backed template builder** for publishing reusable, pre-configured sandbox runtimes -- **E2B-compatible API** so existing E2B SDKs and CLIs work out of the box +- **E2B-compatible API** so existing E2B SDKs, CLIs, and [Crabbox](../integration/crabbox.md) work out of the box - **Reverse proxy** to reach services running inside sandboxes via HTTP and WebSocket - **Multi-node scaling** with a gateway + scheduler control plane (prototype) @@ -26,11 +26,12 @@ AgentENV is built for teams running AI agents that need isolated execution envir ## Interacting with the Server -AgentENV exposes an HTTP API. There are four ways to use it: +AgentENV exposes an HTTP API. There are several ways to use it: | Method | Best for | |--------|----------| | **[aenv CLI](./aenv-cli.md)** | Interactive use, scripting, local development | +| **[Crabbox](../integration/crabbox.md)** | Recommended sandbox client ([openclaw/crabbox](https://github.com/openclaw/crabbox)) — repo sync + remote run for agents and automation | | **[E2B](../integration/e2b.md)** | Application code — existing E2B-based applications work with AgentENV without modification | | **[HTTP API](../api/index.md)** | Direct control, other languages, automation | diff --git a/docs/src/getting-started/quickstart.md b/docs/src/getting-started/quickstart.md index 993c0940..6fdb230e 100644 --- a/docs/src/getting-started/quickstart.md +++ b/docs/src/getting-started/quickstart.md @@ -103,5 +103,6 @@ aenv start ubuntu # starts a sandbox and attaches an interactive shel - [Deployment](../deployment/manual-compile.md) — build from source, multi-node options - [Core Concepts](../concepts/overview.md) — how sandboxes, templates, and snapshots work +- [Crabbox](../integration/crabbox.md) — openclaw/crabbox client for sync + remote run - [E2B](../integration/e2b.md) — SDK and CLI compatibility - [API Reference](../api/index.md) — full HTTP API diff --git a/docs/src/integration/crabbox.md b/docs/src/integration/crabbox.md new file mode 100644 index 00000000..54b84f79 --- /dev/null +++ b/docs/src/integration/crabbox.md @@ -0,0 +1,112 @@ +# Crabbox + +[Crabbox](https://crabbox.sh/) ([openclaw/crabbox](https://github.com/openclaw/crabbox)) is the recommended sandbox client for AgentENV when you want an edit–sync–run loop against Firecracker sandboxes from a laptop, CI job, or coding agent. + +AgentENV exposes an E2B-compatible HTTP API. Crabbox’s built-in `e2b` provider talks to that API — install the upstream CLI, point it at your server, and run. No AgentENV-side code changes and no repo-local wrapper script. + +## When to use Crabbox + +| Client | Best for | +|--------|----------| +| **[aenv CLI](../getting-started/aenv-cli.md)** | Interactive shells, pause/resume, template management | +| **[Crabbox](https://crabbox.sh/)** | Repo sync + remote command execution, agent/automation workflows, auditable run evidence | +| **[E2B SDK](./e2b.md)** | Embedding sandbox create/run/kill in application code | + +Use Crabbox when the workflow is “sync this checkout, run a command in an AgentENV sandbox, stream the output.” Prefer `aenv` for interactive attach, snapshot operations, and day-to-day cluster ops. + +## Install + +```bash +brew install openclaw/tap/crabbox +# or: https://crabbox.sh/ for other platforms +crabbox --version +``` + +## Point Crabbox at AgentENV + +1. Run an AgentENV server ([Quick Start](../getting-started/quickstart.md)). +2. Ensure a template exists (`aenv pull …` / `aenv template list`). +3. Export the same E2B-compatible env vars used by the [E2B SDK](./e2b.md) (see also [Environment Variables](../configuration/env-vars.md)): + +```bash +# Single-node example +export E2B_API_URL=http://127.0.0.1:8000 +export E2B_API_KEY=e2b_000000 +export CRABBOX_E2B_TEMPLATE=ubuntu # AgentENV template id or name +``` + +Crabbox also accepts `CRABBOX_E2B_API_URL` / `CRABBOX_E2B_API_KEY` (these take precedence over `E2B_*`). Plain HTTP is allowed only for localhost / loopback; remote deployments should terminate TLS and use `https://…`. + +### Project config + +Copy the checked-in example and adjust the template name: + +```bash +cp config/crabbox.example.yaml .crabbox.yaml +``` + +```yaml +provider: e2b +target: linux +e2b: + apiUrl: http://127.0.0.1:8000 + template: ubuntu # AgentENV template id or name + workdir: crabbox # dedicated subdirectory inside the sandbox +``` + +Keep keys in the environment — do not commit secrets into `.crabbox.yaml`. + +## Run against AgentENV + +```bash +crabbox doctor --provider e2b + +# one-shot: create sandbox, sync tree, run, release +crabbox run --provider e2b --e2b-template ubuntu -- make test-unit + +# warm lease for repeated agent / edit loops +crabbox warmup --provider e2b --e2b-template ubuntu +lease= +crabbox run --provider e2b --id "$lease" --shell 'make test-unit' +crabbox status --provider e2b --id "$lease" --wait +crabbox stop --provider e2b "$lease" +crabbox list --provider e2b --json +``` + +What happens under the hood: + +1. Crabbox creates an AgentENV sandbox through the E2B-compatible control plane. +2. It archive-syncs the local working tree into the sandbox workdir. +3. It runs the command via the sandbox process API and streams stdout/stderr. +4. On release (unless kept), it deletes the sandbox. + +## Notes and limits + +- Crabbox uses the delegated `e2b` provider path: no SSH lease into the Firecracker guest. +- Prefer a dedicated `e2b.workdir` subdirectory; Crabbox rejects broad system roots such as `/` or `/tmp`. +- Pause/resume, fork, and snapshot APIs remain AgentENV-native — use `aenv` or the HTTP API for those. Crabbox covers create → sync → run → stop. +- AgentENV currently does not enforce authorization. Do not expose the API publicly; keep Crabbox pointed at a trusted network endpoint. + +## Verified with Islo + +openclaw/crabbox was exercised against [Islo](https://islo.dev) (`crabbox --provider islo`) to validate the delegated-sandbox client path before recommending it for AgentENV’s E2B API. + +```bash +islo api-key create crabbox-agentenv-smoke --show +export ISLO_API_KEY='…' + +crabbox doctor --provider islo +crabbox list --provider islo --json +crabbox run --provider islo --no-sync -- echo crabbox-islo-ok +``` + +AgentENV usage stays on `--provider e2b` + `E2B_API_URL`. Islo is only the external verification provider. + +## Related docs + +- [E2B integration](./e2b.md) — SDK and shared env vars +- [aenv CLI](../getting-started/aenv-cli.md) — interactive AgentENV workflows +- `config/crabbox.example.yaml` — checked-in Crabbox config example at the repository root +- [Crabbox E2B provider](https://crabbox.sh/providers/e2b.html) — provider flags, auth, and gotchas +- [Crabbox Islo provider](https://crabbox.sh/providers/islo.html) — verification provider +- [crabbox.sh](https://crabbox.sh/) — product overview and install diff --git a/docs/src/integration/e2b.md b/docs/src/integration/e2b.md index 6a6db325..5cdf62cd 100644 --- a/docs/src/integration/e2b.md +++ b/docs/src/integration/e2b.md @@ -101,3 +101,6 @@ sandbox.kill() AgentENV is compatible with the E2B CLI, but we recommend using the [aenv CLI](../getting-started/aenv-cli.md) for AgentENV workflows. + +For edit–sync–run loops from a local checkout (agents, CI, maintainers), +use [Crabbox](./crabbox.md) pointed at the same `E2B_API_URL`. diff --git a/docs/src/troubleshooting/common-issues.md b/docs/src/troubleshooting/common-issues.md index 3f47fa0a..ec0e1e57 100644 --- a/docs/src/troubleshooting/common-issues.md +++ b/docs/src/troubleshooting/common-issues.md @@ -64,4 +64,24 @@ API_ADDR=0.0.0.0:8001 make start-server Also check `[envd].init_timeout_secs` in your config. The default is 60 seconds. If the rootfs image is large, the in-guest envd daemon may need more time to initialize. +## Crabbox cannot reach AgentENV + +**Symptom**: `crabbox doctor --provider e2b` or `crabbox run --provider e2b` fails with auth or connection errors. + +**Solution**: + +1. Install the upstream CLI: `brew install openclaw/tap/crabbox` (see [crabbox.sh](https://crabbox.sh/)). +2. Point Crabbox at AgentENV with the same vars as the E2B SDK: + +```bash +export E2B_API_URL=http://127.0.0.1:8000 +export E2B_API_KEY=e2b_000000 +export CRABBOX_E2B_TEMPLATE= +``` + +3. Copy `config/crabbox.example.yaml` to `.crabbox.yaml` if you want project-local defaults. +4. Plain HTTP is only accepted for localhost / loopback; use HTTPS for remote AgentENV endpoints. + +See [Crabbox integration](../integration/crabbox.md). + > TODO: Expand with more common issues as they are reported. From 0d464cf7c09ee64c7eb52c8bd21206a22177d3ab Mon Sep 17 00:00:00 2001 From: zozo123 Date: Tue, 28 Jul 2026 12:29:51 +0300 Subject: [PATCH 2/4] fix: make Crabbox integration production-ready --- .github/workflows/crabbox.yml | 145 +++++++-- README.md | 15 +- config/crabbox.example.yaml | 15 +- docs/src/configuration/env-vars.md | 14 +- docs/src/getting-started/overview.md | 2 +- docs/src/integration/crabbox.md | 164 +++++++--- docs/src/integration/e2b.md | 3 +- docs/src/troubleshooting/common-issues.md | 36 ++- scripts/tests/crabbox-e2b-contract-server.py | 323 +++++++++++++++++++ 9 files changed, 621 insertions(+), 96 deletions(-) create mode 100644 scripts/tests/crabbox-e2b-contract-server.py diff --git a/.github/workflows/crabbox.yml b/.github/workflows/crabbox.yml index 20f614b0..1750fa0a 100644 --- a/.github/workflows/crabbox.yml +++ b/.github/workflows/crabbox.yml @@ -1,8 +1,9 @@ name: crabbox # Validates the openclaw/crabbox integration (https://crabbox.sh). -# - Always installs upstream crabbox, checks the e2b provider, and runs doctor. -# - Validates config/crabbox.example.yaml parses as YAML. +# - Installs a pinned, checksum-verified upstream Crabbox release. +# - Loads config/crabbox.example.yaml through Crabbox itself. +# - Exercises E2B lifecycle, archive sync, and process streaming against a strict mock. # - Builds mdBook so the Crabbox integration page stays linked. # - Optional Islo smoke when repository secret ISLO_API_KEY is set # (mint with: islo api-key create …). Forks without the secret no-op cleanly. @@ -12,24 +13,36 @@ on: push: branches: [main] paths: + - "README.md" - "config/crabbox.example.yaml" - "docs/src/integration/crabbox.md" + - "docs/src/integration/e2b.md" - "docs/src/SUMMARY.md" - "docs/src/configuration/env-vars.md" - "docs/src/getting-started/**" + - "docs/src/troubleshooting/common-issues.md" + - "scripts/tests/crabbox-e2b-contract-server.py" - ".github/workflows/crabbox.yml" pull_request: paths: + - "README.md" - "config/crabbox.example.yaml" - "docs/src/integration/crabbox.md" + - "docs/src/integration/e2b.md" - "docs/src/SUMMARY.md" - "docs/src/configuration/env-vars.md" - "docs/src/getting-started/**" + - "docs/src/troubleshooting/common-issues.md" + - "scripts/tests/crabbox-e2b-contract-server.py" - ".github/workflows/crabbox.yml" permissions: contents: read +env: + CRABBOX_VERSION: "0.40.0" + CRABBOX_LINUX_AMD64_SHA256: "3bcd7c48b9866e3ac05b35bb67afa3282e831d1b9f749c5998c102944c1a5cfe" + concurrency: group: crabbox-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -41,43 +54,111 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Validate config/crabbox.example.yaml - run: | - set -euo pipefail - python3 - <<'PY' - from pathlib import Path - try: - import yaml - except ImportError: - import subprocess, sys - subprocess.check_call([sys.executable, "-m", "pip", "install", "--quiet", "pyyaml"]) - import yaml - path = Path("config/crabbox.example.yaml") - data = yaml.safe_load(path.read_text()) - assert data.get("provider") == "e2b", data - assert data.get("target") == "linux", data - e2b = data.get("e2b") or {} - assert "apiUrl" in e2b and "template" in e2b and "workdir" in e2b, e2b - print("ok", path, "provider=e2b template=", e2b["template"]) - PY - - name: Install openclaw/crabbox run: | set -euo pipefail - tag="$(curl -fsSL https://api.github.com/repos/openclaw/crabbox/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)" - ver="${tag#v}" - curl -fsSL "https://github.com/openclaw/crabbox/releases/download/${tag}/crabbox_${ver}_linux_amd64.tar.gz" \ - | sudo tar -xz -C /usr/local/bin crabbox - crabbox --version + install_dir="${RUNNER_TEMP}/crabbox/bin" + archive="crabbox_${CRABBOX_VERSION}_linux_amd64.tar.gz" + mkdir -p "${install_dir}" + curl --fail --location --silent --show-error \ + "https://github.com/openclaw/crabbox/releases/download/v${CRABBOX_VERSION}/${archive}" \ + --output "${RUNNER_TEMP}/${archive}" + ( + cd "${RUNNER_TEMP}" + printf '%s %s\n' "${CRABBOX_LINUX_AMD64_SHA256}" "${archive}" | sha256sum --check - + ) + tar -xzf "${RUNNER_TEMP}/${archive}" -C "${install_dir}" crabbox + echo "${install_dir}" >> "${GITHUB_PATH}" + "${install_dir}/crabbox" --version - - name: crabbox providers include e2b + - name: Validate AgentENV E2B control and data-plane contract + env: + CRABBOX_E2B_API_URL: http://127.0.0.1:18080 + CRABBOX_E2B_API_KEY: agentenv-ci-placeholder run: | set -euo pipefail - crabbox providers 2>&1 | tee /tmp/crabbox-providers.txt - grep -E '(^|[[:space:]])e2b([[:space:]]|$)' /tmp/crabbox-providers.txt + config_path="${RUNNER_TEMP}/crabbox.example.yaml" + install -m 600 config/crabbox.example.yaml "${config_path}" + export CRABBOX_CONFIG="${config_path}" + export XDG_CONFIG_HOME="${RUNNER_TEMP}/crabbox-xdg" + + cert_dir="${RUNNER_TEMP}/crabbox-contract-tls" + mkdir -p "${cert_dir}" + openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout "${cert_dir}/key.pem" \ + -out "${cert_dir}/cert.pem" \ + -days 1 \ + -subj "/CN=*.localhost" \ + -addext "subjectAltName=DNS:*.localhost" \ + >/dev/null 2>&1 + export SSL_CERT_FILE="${cert_dir}/cert.pem" + + python3 scripts/tests/crabbox-e2b-contract-server.py \ + --cert "${cert_dir}/cert.pem" \ + --key "${cert_dir}/key.pem" \ + >"${RUNNER_TEMP}/crabbox-contract-server.log" 2>&1 & + server_pid=$! + cleanup() { + kill "${server_pid}" 2>/dev/null || true + wait "${server_pid}" 2>/dev/null || true + cat "${RUNNER_TEMP}/crabbox-contract-server.log" + } + trap cleanup EXIT - - name: crabbox doctor - run: crabbox doctor + for _ in {1..50}; do + if curl --fail --silent --output /dev/null http://127.0.0.1:18080/health; then + break + fi + sleep 0.1 + done + curl --fail --silent --output /dev/null http://127.0.0.1:18080/health + + crabbox providers | grep -E '^e2b$' + crabbox config show --json > "${RUNNER_TEMP}/crabbox-config.json" + python3 - "${RUNNER_TEMP}/crabbox-config.json" <<'PY' + import json + import sys + + with open(sys.argv[1], encoding="utf-8") as handle: + config = json.load(handle) + assert config["provider"] == "e2b", config["provider"] + assert config["target"] == "linux", config["target"] + assert config["e2b"]["template"] == "ubuntu", config["e2b"] + assert config["e2b"]["workdir"] == "crabbox", config["e2b"] + assert config["e2b"]["apiUrl"] == "http://127.0.0.1:18080", config["e2b"] + PY + crabbox doctor --provider e2b + + contract_repo="${RUNNER_TEMP}/crabbox-contract-repo" + git init --quiet "${contract_repo}" + git -C "${contract_repo}" config user.name "AgentENV CI" + git -C "${contract_repo}" config user.email "ci@agentenv.invalid" + printf 'archive-sync-contract\n' > "${contract_repo}/contract.txt" + git -C "${contract_repo}" add contract.txt + git -C "${contract_repo}" commit --quiet -m "test fixture" + ( + cd "${contract_repo}" + crabbox run --provider e2b -- echo crabbox-agentenv-contract-ok + ) 2>&1 | tee "${RUNNER_TEMP}/crabbox-run.log" + grep -Fx "crabbox-agentenv-contract-ok" "${RUNNER_TEMP}/crabbox-run.log" + + curl --fail --silent http://127.0.0.1:18080/contract-state \ + > "${RUNNER_TEMP}/crabbox-contract-state.json" + python3 - "${RUNNER_TEMP}/crabbox-contract-state.json" <<'PY' + import json + import sys + + with open(sys.argv[1], encoding="utf-8") as handle: + state = json.load(handle) + assert state["errors"] == [], state + assert state["list"] >= 1, state + assert state["create"] == 1, state + assert state["connect"] == 1, state + assert state["upload"] == 1, state + assert state["process"] >= 3, state + assert state["marker_commands"] == 1, state + assert state["delete"] == 1, state + PY - uses: taiki-e/install-action@mdbook diff --git a/README.md b/README.md index 60ecaf8d..6ccf24e9 100644 --- a/README.md +++ b/README.md @@ -103,15 +103,19 @@ Firecracker sandbox, run a command, stream output, and release. ```bash brew install openclaw/tap/crabbox -export E2B_API_URL=http://127.0.0.1:8000 -export E2B_API_KEY=e2b_000000 +export CRABBOX_E2B_API_URL=https://agentenv.example.com +export CRABBOX_E2B_API_KEY=e2b_000000 export CRABBOX_E2B_TEMPLATE=ubuntu # AgentENV template id or name -# optional: cp config/crabbox.example.yaml .crabbox.yaml +# optional: install -m 600 config/crabbox.example.yaml .crabbox.yaml crabbox doctor --provider e2b crabbox run --provider e2b -- make test-unit ``` +`crabbox run` requires AgentENV to advertise an HTTPS wildcard sandbox proxy +domain; the control-plane URL alone is not sufficient. See the integration guide +for the server, DNS, and TLS setup. + See 📖 [Crabbox integration](https://kvcache-ai.github.io/AgentENV/integration/crabbox.html). Verified with [Islo](https://islo.dev) via `crabbox --provider islo` while landing this client path. @@ -119,8 +123,9 @@ this client path. ## 🔌 E2B compatibility AgentENV exposes an E2B-compatible HTTP API. Point `E2B_API_URL` at your -server and use the standard E2B Python / TypeScript SDK — or Crabbox’s -`e2b` provider — without any AgentENV code changes. See 📖 +server and use the standard E2B Python / TypeScript SDK without any AgentENV +code changes. Crabbox’s `e2b` provider also works when host-based sandbox +routing is configured. See 📖 [E2B integration](https://kvcache-ai.github.io/AgentENV/integration/e2b.html) for SDK setup details. diff --git a/config/crabbox.example.yaml b/config/crabbox.example.yaml index 0a556cf3..8e9df831 100644 --- a/config/crabbox.example.yaml +++ b/config/crabbox.example.yaml @@ -1,21 +1,24 @@ # Example openclaw/crabbox config for AgentENV. -# Copy to the repo root as `.crabbox.yaml`, or merge into your user Crabbox config. +# Install with mode 0600 at the repo root as `.crabbox.yaml`, or merge it into +# your user Crabbox config. # Docs: https://crabbox.sh/ · Provider: https://crabbox.sh/providers/e2b.html # -# Auth stays in the environment (never commit keys): -# export E2B_API_URL=http://127.0.0.1:8000 # or https://… behind TLS -# export E2B_API_KEY=e2b_000000 +# Endpoint and auth stay in the environment (never commit destinations or keys): +# export CRABBOX_E2B_API_URL=https://agentenv.example.com +# export CRABBOX_E2B_API_KEY=e2b_000000 +# +# `crabbox run` also requires AgentENV to advertise an HTTPS wildcard sandbox +# proxy domain. See docs/src/integration/crabbox.md before running this config. # # Then: # brew install openclaw/tap/crabbox +# install -m 600 config/crabbox.example.yaml .crabbox.yaml # crabbox doctor --provider e2b # crabbox run --provider e2b -- make test-unit provider: e2b target: linux e2b: - # Override with E2B_API_URL / CRABBOX_E2B_API_URL when unset here. - apiUrl: http://127.0.0.1:8000 # AgentENV template id or name (aenv pull … / aenv template list) template: ubuntu # Dedicated subdirectory inside the sandbox (not /, /tmp, …) diff --git a/docs/src/configuration/env-vars.md b/docs/src/configuration/env-vars.md index 0b147325..8aad6d98 100644 --- a/docs/src/configuration/env-vars.md +++ b/docs/src/configuration/env-vars.md @@ -38,7 +38,9 @@ These variables are consumed by the repository's Docker Compose and Kubernetes h ## E2B SDK / CLI / Crabbox -These variables configure the E2B SDK, E2B CLI, and [Crabbox](../integration/crabbox.md)’s `e2b` provider to point at an AgentENV server. Values depend on your deployment mode. +These variables configure the E2B SDK and CLI to point at an AgentENV server. +[Crabbox](../integration/crabbox.md)’s `e2b` provider shares some of them but +uses host-based sandbox URLs rather than `E2B_SANDBOX_URL`. | Variable | Description | |----------|-------------| @@ -48,7 +50,10 @@ These variables configure the E2B SDK, E2B CLI, and [Crabbox](../integration/cra | `E2B_ACCESS_TOKEN` | Access token (used by `e2b template` commands) | | `CRABBOX_E2B_API_URL` | Crabbox override for `E2B_API_URL` (takes precedence) | | `CRABBOX_E2B_API_KEY` | Crabbox override for `E2B_API_KEY` (takes precedence) | +| `CRABBOX_E2B_DOMAIN` | Fallback wildcard sandbox domain; AgentENV normally advertises `[sandbox_proxy].domains[0]` in its sandbox response | | `CRABBOX_E2B_TEMPLATE` | AgentENV template id/name for `crabbox run --provider e2b` | +| `CRABBOX_E2B_WORKDIR` | Dedicated directory inside the sandbox used for repo sync and commands | +| `CRABBOX_E2B_USER` | Optional sandbox login name used for file ownership and commands | ### Values by Deployment Mode @@ -76,6 +81,13 @@ export E2B_ACCESS_TOKEN=dummy > For local development, any non-empty value works for `E2B_API_KEY` and `E2B_ACCESS_TOKEN` because the server only checks that the auth header is present. +> Crabbox does not read `E2B_SANDBOX_URL`. Its `e2b` provider connects to +> `https://{port}-{sandboxID}.{domain}`. Configure AgentENV's host-based sandbox +> routing, wildcard DNS, and TLS as described in the +> [Crabbox integration](../integration/crabbox.md). A plain loopback +> `E2B_API_URL` is sufficient for `crabbox doctor` and `list`, but not for +> `warmup` or `run`. + ## Gateway and Scheduler These variables apply to both the gateway and scheduler processes. diff --git a/docs/src/getting-started/overview.md b/docs/src/getting-started/overview.md index 8579d06b..5ad3e18f 100644 --- a/docs/src/getting-started/overview.md +++ b/docs/src/getting-started/overview.md @@ -16,7 +16,7 @@ The repository is available at . - **Pause and resume** with memory + disk snapshots for instant cold start - **Layered block devices** via overlaybd + ublk for copy-on-write image sharing - **Snapshot-backed template builder** for publishing reusable, pre-configured sandbox runtimes -- **E2B-compatible API** so existing E2B SDKs, CLIs, and [Crabbox](../integration/crabbox.md) work out of the box +- **E2B-compatible API** for existing E2B SDKs and CLIs, plus [Crabbox](../integration/crabbox.md) on deployments with host-based sandbox routing - **Reverse proxy** to reach services running inside sandboxes via HTTP and WebSocket - **Multi-node scaling** with a gateway + scheduler control plane (prototype) diff --git a/docs/src/integration/crabbox.md b/docs/src/integration/crabbox.md index 54b84f79..6a075d4d 100644 --- a/docs/src/integration/crabbox.md +++ b/docs/src/integration/crabbox.md @@ -1,95 +1,169 @@ # Crabbox -[Crabbox](https://crabbox.sh/) ([openclaw/crabbox](https://github.com/openclaw/crabbox)) is the recommended sandbox client for AgentENV when you want an edit–sync–run loop against Firecracker sandboxes from a laptop, CI job, or coding agent. +[Crabbox](https://crabbox.sh/) +([openclaw/crabbox](https://github.com/openclaw/crabbox)) is a sandbox client +for edit–sync–run loops from a laptop, CI job, or coding agent. Its built-in +`e2b` provider can use AgentENV's E2B-compatible control plane and host-based +sandbox data plane. -AgentENV exposes an E2B-compatible HTTP API. Crabbox’s built-in `e2b` provider talks to that API — install the upstream CLI, point it at your server, and run. No AgentENV-side code changes and no repo-local wrapper script. +No AgentENV-side plugin or repository-local wrapper is required. The deployment +does need the optional sandbox proxy domain described below; setting only +`E2B_API_URL` is not enough for a Crabbox run. ## When to use Crabbox | Client | Best for | |--------|----------| | **[aenv CLI](../getting-started/aenv-cli.md)** | Interactive shells, pause/resume, template management | -| **[Crabbox](https://crabbox.sh/)** | Repo sync + remote command execution, agent/automation workflows, auditable run evidence | +| **[Crabbox](https://crabbox.sh/)** | Repo sync + remote command execution for agents and automation | | **[E2B SDK](./e2b.md)** | Embedding sandbox create/run/kill in application code | -Use Crabbox when the workflow is “sync this checkout, run a command in an AgentENV sandbox, stream the output.” Prefer `aenv` for interactive attach, snapshot operations, and day-to-day cluster ops. +Use Crabbox when the workflow is “sync this checkout, run a command in an +AgentENV sandbox, and stream the output.” Prefer `aenv` for interactive attach, +snapshot operations, and cluster administration. -## Install +## How the connection works + +Crabbox uses two routes: + +1. Lifecycle calls such as create, list, connect, and delete go to + `CRABBOX_E2B_API_URL` (or `E2B_API_URL`). +2. File upload and process calls go to + `https://{port}-{sandboxID}.{domain}`. + +AgentENV returns the first configured `[sandbox_proxy].domains` entry in create, +connect, and detail responses. Crabbox uses that advertised domain for the +second route. + +Crabbox's `e2b` provider does not read `E2B_SANDBOX_URL`, so the routing-header +setup used by the E2B SDK cannot replace the host-based route. In particular, a +plain `http://127.0.0.1:8000` API URL can support `doctor` and `list`, but +`warmup` and `run` also need an HTTPS sandbox proxy domain. + +## Configure AgentENV routing + +Choose a DNS name dedicated to sandbox traffic, for example +`sandbox.agentenv.example.com`. + +For a single node, configure the server: + +```bash +export AENV_SANDBOX_PROXY_DOMAINS=sandbox.agentenv.example.com +make start-server +``` + +For the Docker Compose or Kubernetes multi-node helpers, configure the shared +gateway/runtime value: + +```bash +export SANDBOX_PROXY_DOMAINS=sandbox.agentenv.example.com +make deploy-up +``` + +The deployment must also provide: + +- wildcard DNS for `*.sandbox.agentenv.example.com` pointing to the AgentENV + server or gateway; +- a wildcard TLS certificate for that name; +- a TLS load balancer or reverse proxy that preserves the original `Host` and + forwards requests to AgentENV. + +The server and gateway accept only explicitly configured domains. See +[Proxy](../concepts/proxy.md), [Environment Variables](../configuration/env-vars.md), +and the relevant deployment guide for more detail. + +## Install and configure Crabbox + +Install the upstream CLI: ```bash brew install openclaw/tap/crabbox -# or: https://crabbox.sh/ for other platforms +# See https://crabbox.sh/ for other platforms. crabbox --version ``` -## Point Crabbox at AgentENV - -1. Run an AgentENV server ([Quick Start](../getting-started/quickstart.md)). -2. Ensure a template exists (`aenv pull …` / `aenv template list`). -3. Export the same E2B-compatible env vars used by the [E2B SDK](./e2b.md) (see also [Environment Variables](../configuration/env-vars.md)): +Point Crabbox's control plane at the AgentENV API and select an existing +template: ```bash -# Single-node example -export E2B_API_URL=http://127.0.0.1:8000 -export E2B_API_KEY=e2b_000000 -export CRABBOX_E2B_TEMPLATE=ubuntu # AgentENV template id or name +export CRABBOX_E2B_API_URL=https://agentenv.example.com +export CRABBOX_E2B_API_KEY=e2b_000000 +export CRABBOX_E2B_TEMPLATE=ubuntu ``` -Crabbox also accepts `CRABBOX_E2B_API_URL` / `CRABBOX_E2B_API_KEY` (these take precedence over `E2B_*`). Plain HTTP is allowed only for localhost / loopback; remote deployments should terminate TLS and use `https://…`. +`CRABBOX_E2B_*` values take precedence over the corresponding `E2B_*` values. +`E2B_API_KEY` or `CRABBOX_E2B_API_KEY` must be non-empty because Crabbox checks +for it. AgentENV does not currently enforce that key, so keep the API on a +trusted network even when TLS is enabled. + +AgentENV normally advertises the configured sandbox proxy domain. If an +intermediary strips the `domain` response field, set the same value explicitly: + +```bash +export CRABBOX_E2B_DOMAIN=sandbox.agentenv.example.com +``` ### Project config -Copy the checked-in example and adjust the template name: +Install the checked-in example with private permissions and adjust the template: ```bash -cp config/crabbox.example.yaml .crabbox.yaml +install -m 600 config/crabbox.example.yaml .crabbox.yaml ``` ```yaml provider: e2b target: linux e2b: - apiUrl: http://127.0.0.1:8000 - template: ubuntu # AgentENV template id or name - workdir: crabbox # dedicated subdirectory inside the sandbox + template: ubuntu + workdir: crabbox ``` -Keep keys in the environment — do not commit secrets into `.crabbox.yaml`. +Keep API destinations and credentials in explicit environment variables or +trusted user configuration. Crabbox intentionally refuses to send inherited +credentials to a destination supplied only by repository configuration. +Crabbox also requires loaded configuration files to be private (`0600`), which +is why the example uses `install` rather than a plain `cp`. ## Run against AgentENV ```bash crabbox doctor --provider e2b -# one-shot: create sandbox, sync tree, run, release +# One shot: create, sync, run, and release. crabbox run --provider e2b --e2b-template ubuntu -- make test-unit -# warm lease for repeated agent / edit loops +# Keep a warm sandbox for repeated edit/run loops. crabbox warmup --provider e2b --e2b-template ubuntu -lease= -crabbox run --provider e2b --id "$lease" --shell 'make test-unit' +lease= crabbox status --provider e2b --id "$lease" --wait +crabbox run --provider e2b --id "$lease" --shell 'make test-unit' crabbox stop --provider e2b "$lease" crabbox list --provider e2b --json ``` -What happens under the hood: +Under the hood, Crabbox: -1. Crabbox creates an AgentENV sandbox through the E2B-compatible control plane. -2. It archive-syncs the local working tree into the sandbox workdir. -3. It runs the command via the sandbox process API and streams stdout/stderr. -4. On release (unless kept), it deletes the sandbox. +1. creates an AgentENV sandbox from the selected template; +2. archive-syncs the Git-managed working set into the sandbox workdir; +3. runs the command through envd's process API and streams stdout/stderr; +4. deletes a one-shot sandbox on release, unless retention was requested. ## Notes and limits -- Crabbox uses the delegated `e2b` provider path: no SSH lease into the Firecracker guest. -- Prefer a dedicated `e2b.workdir` subdirectory; Crabbox rejects broad system roots such as `/` or `/tmp`. -- Pause/resume, fork, and snapshot APIs remain AgentENV-native — use `aenv` or the HTTP API for those. Crabbox covers create → sync → run → stop. -- AgentENV currently does not enforce authorization. Do not expose the API publicly; keep Crabbox pointed at a trusted network endpoint. +- The `e2b` provider is a delegated-run path, not an SSH lease. +- Use a dedicated `e2b.workdir`; Crabbox rejects broad roots such as `/`, + `/home`, and `/tmp`. +- Pause/resume, fork, and snapshot APIs remain AgentENV-native. Use `aenv` or + the HTTP API for those operations. +- Crabbox's E2B sandbox timeout is capped at one hour. +- AgentENV currently does not enforce authorization. Do not expose its control + or sandbox data plane directly to the public internet. -## Verified with Islo +## Upstream CLI verification -openclaw/crabbox was exercised against [Islo](https://islo.dev) (`crabbox --provider islo`) to validate the delegated-sandbox client path before recommending it for AgentENV’s E2B API. +The upstream Crabbox binary was also exercised against +[Islo](https://islo.dev) through Crabbox's separate `islo` provider: ```bash islo api-key create crabbox-agentenv-smoke --show @@ -100,13 +174,17 @@ crabbox list --provider islo --json crabbox run --provider islo --no-sync -- echo crabbox-islo-ok ``` -AgentENV usage stays on `--provider e2b` + `E2B_API_URL`. Islo is only the external verification provider. +This verifies the upstream delegated-run CLI path; it does not replace an +AgentENV E2B smoke test. AgentENV usage remains on `--provider e2b` with the +control-plane and wildcard-domain setup above. ## Related docs -- [E2B integration](./e2b.md) — SDK and shared env vars +- [E2B integration](./e2b.md) — SDK setup and shared environment variables +- [Proxy](../concepts/proxy.md) — routing headers and host-based URLs - [aenv CLI](../getting-started/aenv-cli.md) — interactive AgentENV workflows -- `config/crabbox.example.yaml` — checked-in Crabbox config example at the repository root -- [Crabbox E2B provider](https://crabbox.sh/providers/e2b.html) — provider flags, auth, and gotchas -- [Crabbox Islo provider](https://crabbox.sh/providers/islo.html) — verification provider -- [crabbox.sh](https://crabbox.sh/) — product overview and install +- `config/crabbox.example.yaml` — checked-in project configuration +- [Crabbox E2B provider](https://crabbox.sh/providers/e2b.html) — upstream + provider flags, auth, and limits +- [Crabbox Islo provider](https://crabbox.sh/providers/islo.html) — external CLI + verification provider diff --git a/docs/src/integration/e2b.md b/docs/src/integration/e2b.md index 5cdf62cd..a32bc7ae 100644 --- a/docs/src/integration/e2b.md +++ b/docs/src/integration/e2b.md @@ -103,4 +103,5 @@ AgentENV is compatible with the E2B CLI, but we recommend using the [aenv CLI](../getting-started/aenv-cli.md) for AgentENV workflows. For edit–sync–run loops from a local checkout (agents, CI, maintainers), -use [Crabbox](./crabbox.md) pointed at the same `E2B_API_URL`. +use [Crabbox](./crabbox.md). Its `e2b` provider requires AgentENV's optional +host-based sandbox proxy domain in addition to the control-plane API URL. diff --git a/docs/src/troubleshooting/common-issues.md b/docs/src/troubleshooting/common-issues.md index ec0e1e57..06c35a43 100644 --- a/docs/src/troubleshooting/common-issues.md +++ b/docs/src/troubleshooting/common-issues.md @@ -66,21 +66,43 @@ Also check `[envd].init_timeout_secs` in your config. The default is 60 seconds. ## Crabbox cannot reach AgentENV -**Symptom**: `crabbox doctor --provider e2b` or `crabbox run --provider e2b` fails with auth or connection errors. +**Symptom**: `crabbox doctor --provider e2b` or +`crabbox run --provider e2b` fails with auth, DNS, TLS, or connection errors. **Solution**: -1. Install the upstream CLI: `brew install openclaw/tap/crabbox` (see [crabbox.sh](https://crabbox.sh/)). -2. Point Crabbox at AgentENV with the same vars as the E2B SDK: +1. Install the upstream CLI: `brew install openclaw/tap/crabbox` (see + [crabbox.sh](https://crabbox.sh/)). +2. Set the AgentENV control-plane URL, a non-empty key, and an existing template: ```bash -export E2B_API_URL=http://127.0.0.1:8000 -export E2B_API_KEY=e2b_000000 +export CRABBOX_E2B_API_URL=https://agentenv.example.com +export CRABBOX_E2B_API_KEY=e2b_000000 export CRABBOX_E2B_TEMPLATE= ``` -3. Copy `config/crabbox.example.yaml` to `.crabbox.yaml` if you want project-local defaults. -4. Plain HTTP is only accepted for localhost / loopback; use HTTPS for remote AgentENV endpoints. +3. Confirm `crabbox doctor --provider e2b` succeeds. If it fails, verify the API + URL is reachable and was set explicitly in the environment. Crabbox refuses + inherited credentials paired only with a repository-configured endpoint. +4. If `doctor` succeeds but `warmup` or `run` fails, verify AgentENV advertises a + sandbox domain: + + ```bash + export AENV_SANDBOX_PROXY_DOMAINS=sandbox.agentenv.example.com + ``` + + Wildcard DNS and TLS for `*.sandbox.agentenv.example.com` must route to the + AgentENV server or gateway. Crabbox does not read `E2B_SANDBOX_URL`; a plain + loopback API URL alone cannot carry its file and process traffic. +5. Install the example with private permissions if you want project template + and workdir defaults: + + ```bash + install -m 600 config/crabbox.example.yaml .crabbox.yaml + ``` + + If doctor reports `permissions 0644 want 0600`, run + `chmod 600 .crabbox.yaml`. See [Crabbox integration](../integration/crabbox.md). diff --git a/scripts/tests/crabbox-e2b-contract-server.py b/scripts/tests/crabbox-e2b-contract-server.py new file mode 100644 index 00000000..f1d7858b --- /dev/null +++ b/scripts/tests/crabbox-e2b-contract-server.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +"""Strict local E2B contract server for the Crabbox CI smoke test.""" + +from __future__ import annotations + +import argparse +import base64 +import json +import ssl +import struct +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from typing import Any +from urllib.parse import parse_qs, urlparse + +SANDBOX_ID = "019c6f83-4df1-7e70-8000-000000000035" +API_KEY = "agentenv-ci-placeholder" +ACCESS_TOKEN = "agentenv-contract-access-token" +MARKER = "crabbox-agentenv-contract-ok" + + +class ContractState: + def __init__(self, data_port: int) -> None: + self.data_port = data_port + self.metadata: dict[str, str] = {} + self.counts = { + "list": 0, + "create": 0, + "connect": 0, + "upload": 0, + "process": 0, + "marker_commands": 0, + "delete": 0, + } + self.errors: list[str] = [] + self.lock = threading.Lock() + + def record(self, name: str) -> None: + with self.lock: + self.counts[name] += 1 + + def record_error(self, error: BaseException) -> None: + with self.lock: + self.errors.append(str(error)) + + def snapshot(self) -> dict[str, object]: + with self.lock: + return { + **self.counts, + "errors": list(self.errors), + } + + def sandbox(self) -> dict[str, object]: + return { + "templateID": "ubuntu", + "sandboxID": SANDBOX_ID, + "clientID": "", + "envdVersion": "contract", + "envdAccessToken": ACCESS_TOKEN, + "domain": f"localhost:{self.data_port}", + "metadata": dict(self.metadata), + } + + +class ContractServer(ThreadingHTTPServer): + daemon_threads = True + + def __init__( + self, + server_address: tuple[str, int], + handler: type[BaseHTTPRequestHandler], + state: ContractState, + plane: str, + ) -> None: + self.state = state + self.plane = plane + super().__init__(server_address, handler) + + +class Handler(BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + @property + def contract_server(self) -> ContractServer: + assert isinstance(self.server, ContractServer) + return self.server + + @property + def state(self) -> ContractState: + return self.contract_server.state + + def do_GET(self) -> None: + try: + parsed = urlparse(self.path) + if parsed.path == "/health": + self._send_bytes(200, b"ok") + return + if parsed.path == "/contract-state": + self._send_json(200, self.state.snapshot()) + return + assert self.contract_server.plane == "control", self.path + if parsed.path == "/v2/sandboxes": + self._assert_api_key() + query = parse_qs(parsed.query) + assert query.get("limit") == ["100"], query + assert query.get("state") == ["running,paused"], query + metadata = parse_qs(query.get("metadata", [""])[0]) + assert metadata == { + "crabbox": ["true"], + "provider": ["e2b"], + }, metadata + self.state.record("list") + self._send_json(200, []) + return + if parsed.path == f"/sandboxes/{SANDBOX_ID}": + self._assert_api_key() + self._send_json(200, self.state.sandbox()) + return + raise AssertionError(f"unexpected GET {self.path}") + except BaseException as error: + self._fail(error) + + def do_POST(self) -> None: + try: + if self.contract_server.plane == "data": + parsed = urlparse(self.path) + if parsed.path == "/files": + self._handle_upload(parsed.query) + else: + self._handle_process() + return + + self._assert_api_key() + parsed = urlparse(self.path) + body = self._read_json() + if parsed.path == "/sandboxes": + assert body["templateID"] == "ubuntu", body + assert body["secure"] is True, body + assert body["allow_internet_access"] is True, body + assert body["metadata"]["crabbox"] == "true", body + assert body["metadata"]["provider"] == "e2b", body + self.state.metadata = body["metadata"] + self.state.record("create") + self._send_json(201, self.state.sandbox()) + return + if parsed.path == f"/sandboxes/{SANDBOX_ID}/connect": + assert body["timeout"] > 0, body + self.state.record("connect") + self._send_json(200, self.state.sandbox()) + return + raise AssertionError(f"unexpected POST {self.path}") + except BaseException as error: + self._fail(error) + + def do_DELETE(self) -> None: + try: + assert self.contract_server.plane == "control", self.path + self._assert_api_key() + assert urlparse(self.path).path == f"/sandboxes/{SANDBOX_ID}", self.path + self.state.record("delete") + self._send_bytes(204, b"") + except BaseException as error: + self._fail(error) + + def _handle_process(self) -> None: + parsed = urlparse(self.path) + assert parsed.path == "/process.Process/Start", self.path + self._assert_data_plane_headers() + assert self.headers.get("Connect-Protocol-Version") == "1", self.headers + + raw = self._read_body() + assert len(raw) >= 5 and raw[0] == 0, raw + size = struct.unpack(">I", raw[1:5])[0] + assert size == len(raw) - 5, (size, len(raw)) + request = json.loads(raw[5:]) + process = request["process"] + assert process["cmd"] == "/bin/bash", process + assert process["args"][:2] == ["-l", "-c"], process + command = process["args"][2] + + self.state.record("process") + output = b"" + if MARKER in command: + self.state.record("marker_commands") + output = f"{MARKER}\n".encode() + + response = b"".join( + [ + self._connect_envelope({"event": {"start": {"pid": 35}}}), + self._connect_envelope( + { + "event": { + "data": { + "stdout": base64.b64encode(output).decode(), + } + } + } + ), + self._connect_envelope( + { + "event": { + "end": { + "exitCode": 0, + "exited": True, + "status": "exited", + } + } + } + ), + bytes([2]) + struct.pack(">I", 0), + ] + ) + self._send_bytes(200, response, "application/connect+json") + + def _handle_upload(self, raw_query: str) -> None: + self._assert_data_plane_headers() + query = parse_qs(raw_query) + target = query.get("path", [""])[0] + assert target.startswith("/tmp/crabbox-") and target.endswith(".tgz"), query + assert self.headers.get("Content-Type", "").startswith( + "multipart/form-data;" + ), self.headers + body = self._read_body() + assert len(body) > 100, len(body) + self.state.record("upload") + self._send_json(200, {}) + + def _assert_data_plane_headers(self) -> None: + expected_host = f"49983-{SANDBOX_ID}.localhost:{self.state.data_port}" + assert self.headers.get("Host") == expected_host, self.headers + assert self.headers.get("E2b-Sandbox-Id") == SANDBOX_ID, self.headers + assert self.headers.get("E2b-Sandbox-Port") == "49983", self.headers + assert self.headers.get("X-Access-Token") == ACCESS_TOKEN, self.headers + + def _assert_api_key(self) -> None: + assert self.headers.get("X-API-Key") == API_KEY, self.headers + + def _read_body(self) -> bytes: + if self.headers.get("Transfer-Encoding", "").lower() == "chunked": + chunks = [] + while True: + size_line = self.rfile.readline() + assert size_line, "chunked body ended before its zero chunk" + size = int(size_line.split(b";", 1)[0].strip(), 16) + if size == 0: + while self.rfile.readline() not in (b"\r\n", b""): + pass + break + chunks.append(self.rfile.read(size)) + assert self.rfile.read(2) == b"\r\n", "invalid chunk terminator" + return b"".join(chunks) + length = int(self.headers.get("Content-Length", "0")) + return self.rfile.read(length) + + def _read_json(self) -> dict[str, Any]: + return json.loads(self._read_body()) + + @staticmethod + def _connect_envelope(payload: dict[str, object]) -> bytes: + data = json.dumps(payload, separators=(",", ":")).encode() + return bytes([0]) + struct.pack(">I", len(data)) + data + + def _send_json(self, status: int, payload: object) -> None: + self._send_bytes( + status, + json.dumps(payload, separators=(",", ":")).encode(), + "application/json", + ) + + def _send_bytes( + self, + status: int, + body: bytes, + content_type: str = "text/plain", + ) -> None: + self.send_response(status) + self.send_header("Content-Type", content_type) + self.send_header("Content-Length", str(len(body))) + self.end_headers() + if body: + self.wfile.write(body) + + def _fail(self, error: BaseException) -> None: + self.state.record_error(error) + self._send_json(500, {"error": str(error)}) + + def log_message(self, format: str, *args: object) -> None: + del format, args + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--cert", required=True) + parser.add_argument("--key", required=True) + parser.add_argument("--control-port", type=int, default=18080) + parser.add_argument("--data-port", type=int, default=18443) + args = parser.parse_args() + + state = ContractState(args.data_port) + control = ContractServer( + ("127.0.0.1", args.control_port), Handler, state, "control" + ) + data = ContractServer(("127.0.0.1", args.data_port), Handler, state, "data") + tls = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + tls.load_cert_chain(args.cert, args.key) + data.socket = tls.wrap_socket(data.socket, server_side=True) + + data_thread = threading.Thread(target=data.serve_forever, daemon=True) + data_thread.start() + print( + f"contract server ready control={args.control_port} data={args.data_port}", + flush=True, + ) + try: + control.serve_forever() + finally: + data.shutdown() + data.server_close() + control.server_close() + + +if __name__ == "__main__": + main() From f75998598c62e2e045de24160434bd8369d1138b Mon Sep 17 00:00:00 2001 From: zozo123 Date: Thu, 30 Jul 2026 17:26:38 +0300 Subject: [PATCH 3/4] docs: add short Crabbox configuration section, drop integration CI Reduce the Crabbox integration to what was asked for: a short configuration section plus links. Drops the dedicated CI workflow and the mock E2B contract server, removes the checked-in example config in favor of an inline snippet, and restores README, env-vars, overview, and common-issues to their upstream text. --- .github/workflows/crabbox.yml | 196 ----------- .gitignore | 3 - README.md | 37 +-- config/crabbox.example.yaml | 25 -- docs/src/SUMMARY.md | 2 +- docs/src/configuration/env-vars.md | 19 +- docs/src/getting-started/overview.md | 6 +- docs/src/integration/crabbox.md | 194 ++--------- docs/src/integration/e2b.md | 5 +- docs/src/troubleshooting/common-issues.md | 42 --- scripts/tests/crabbox-e2b-contract-server.py | 323 ------------------- 11 files changed, 33 insertions(+), 819 deletions(-) delete mode 100644 .github/workflows/crabbox.yml delete mode 100644 config/crabbox.example.yaml delete mode 100644 scripts/tests/crabbox-e2b-contract-server.py diff --git a/.github/workflows/crabbox.yml b/.github/workflows/crabbox.yml deleted file mode 100644 index 1750fa0a..00000000 --- a/.github/workflows/crabbox.yml +++ /dev/null @@ -1,196 +0,0 @@ -name: crabbox - -# Validates the openclaw/crabbox integration (https://crabbox.sh). -# - Installs a pinned, checksum-verified upstream Crabbox release. -# - Loads config/crabbox.example.yaml through Crabbox itself. -# - Exercises E2B lifecycle, archive sync, and process streaming against a strict mock. -# - Builds mdBook so the Crabbox integration page stays linked. -# - Optional Islo smoke when repository secret ISLO_API_KEY is set -# (mint with: islo api-key create …). Forks without the secret no-op cleanly. - -on: - workflow_dispatch: - push: - branches: [main] - paths: - - "README.md" - - "config/crabbox.example.yaml" - - "docs/src/integration/crabbox.md" - - "docs/src/integration/e2b.md" - - "docs/src/SUMMARY.md" - - "docs/src/configuration/env-vars.md" - - "docs/src/getting-started/**" - - "docs/src/troubleshooting/common-issues.md" - - "scripts/tests/crabbox-e2b-contract-server.py" - - ".github/workflows/crabbox.yml" - pull_request: - paths: - - "README.md" - - "config/crabbox.example.yaml" - - "docs/src/integration/crabbox.md" - - "docs/src/integration/e2b.md" - - "docs/src/SUMMARY.md" - - "docs/src/configuration/env-vars.md" - - "docs/src/getting-started/**" - - "docs/src/troubleshooting/common-issues.md" - - "scripts/tests/crabbox-e2b-contract-server.py" - - ".github/workflows/crabbox.yml" - -permissions: - contents: read - -env: - CRABBOX_VERSION: "0.40.0" - CRABBOX_LINUX_AMD64_SHA256: "3bcd7c48b9866e3ac05b35bb67afa3282e831d1b9f749c5998c102944c1a5cfe" - -concurrency: - group: crabbox-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - integrate: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install openclaw/crabbox - run: | - set -euo pipefail - install_dir="${RUNNER_TEMP}/crabbox/bin" - archive="crabbox_${CRABBOX_VERSION}_linux_amd64.tar.gz" - mkdir -p "${install_dir}" - curl --fail --location --silent --show-error \ - "https://github.com/openclaw/crabbox/releases/download/v${CRABBOX_VERSION}/${archive}" \ - --output "${RUNNER_TEMP}/${archive}" - ( - cd "${RUNNER_TEMP}" - printf '%s %s\n' "${CRABBOX_LINUX_AMD64_SHA256}" "${archive}" | sha256sum --check - - ) - tar -xzf "${RUNNER_TEMP}/${archive}" -C "${install_dir}" crabbox - echo "${install_dir}" >> "${GITHUB_PATH}" - "${install_dir}/crabbox" --version - - - name: Validate AgentENV E2B control and data-plane contract - env: - CRABBOX_E2B_API_URL: http://127.0.0.1:18080 - CRABBOX_E2B_API_KEY: agentenv-ci-placeholder - run: | - set -euo pipefail - config_path="${RUNNER_TEMP}/crabbox.example.yaml" - install -m 600 config/crabbox.example.yaml "${config_path}" - export CRABBOX_CONFIG="${config_path}" - export XDG_CONFIG_HOME="${RUNNER_TEMP}/crabbox-xdg" - - cert_dir="${RUNNER_TEMP}/crabbox-contract-tls" - mkdir -p "${cert_dir}" - openssl req -x509 -newkey rsa:2048 -nodes \ - -keyout "${cert_dir}/key.pem" \ - -out "${cert_dir}/cert.pem" \ - -days 1 \ - -subj "/CN=*.localhost" \ - -addext "subjectAltName=DNS:*.localhost" \ - >/dev/null 2>&1 - export SSL_CERT_FILE="${cert_dir}/cert.pem" - - python3 scripts/tests/crabbox-e2b-contract-server.py \ - --cert "${cert_dir}/cert.pem" \ - --key "${cert_dir}/key.pem" \ - >"${RUNNER_TEMP}/crabbox-contract-server.log" 2>&1 & - server_pid=$! - cleanup() { - kill "${server_pid}" 2>/dev/null || true - wait "${server_pid}" 2>/dev/null || true - cat "${RUNNER_TEMP}/crabbox-contract-server.log" - } - trap cleanup EXIT - - for _ in {1..50}; do - if curl --fail --silent --output /dev/null http://127.0.0.1:18080/health; then - break - fi - sleep 0.1 - done - curl --fail --silent --output /dev/null http://127.0.0.1:18080/health - - crabbox providers | grep -E '^e2b$' - crabbox config show --json > "${RUNNER_TEMP}/crabbox-config.json" - python3 - "${RUNNER_TEMP}/crabbox-config.json" <<'PY' - import json - import sys - - with open(sys.argv[1], encoding="utf-8") as handle: - config = json.load(handle) - assert config["provider"] == "e2b", config["provider"] - assert config["target"] == "linux", config["target"] - assert config["e2b"]["template"] == "ubuntu", config["e2b"] - assert config["e2b"]["workdir"] == "crabbox", config["e2b"] - assert config["e2b"]["apiUrl"] == "http://127.0.0.1:18080", config["e2b"] - PY - crabbox doctor --provider e2b - - contract_repo="${RUNNER_TEMP}/crabbox-contract-repo" - git init --quiet "${contract_repo}" - git -C "${contract_repo}" config user.name "AgentENV CI" - git -C "${contract_repo}" config user.email "ci@agentenv.invalid" - printf 'archive-sync-contract\n' > "${contract_repo}/contract.txt" - git -C "${contract_repo}" add contract.txt - git -C "${contract_repo}" commit --quiet -m "test fixture" - ( - cd "${contract_repo}" - crabbox run --provider e2b -- echo crabbox-agentenv-contract-ok - ) 2>&1 | tee "${RUNNER_TEMP}/crabbox-run.log" - grep -Fx "crabbox-agentenv-contract-ok" "${RUNNER_TEMP}/crabbox-run.log" - - curl --fail --silent http://127.0.0.1:18080/contract-state \ - > "${RUNNER_TEMP}/crabbox-contract-state.json" - python3 - "${RUNNER_TEMP}/crabbox-contract-state.json" <<'PY' - import json - import sys - - with open(sys.argv[1], encoding="utf-8") as handle: - state = json.load(handle) - assert state["errors"] == [], state - assert state["list"] >= 1, state - assert state["create"] == 1, state - assert state["connect"] == 1, state - assert state["upload"] == 1, state - assert state["process"] >= 3, state - assert state["marker_commands"] == 1, state - assert state["delete"] == 1, state - PY - - - uses: taiki-e/install-action@mdbook - - - name: Build docs (mdBook) - run: | - set -euo pipefail - ln -sf ../../src/api/openapi.yml docs/src/openapi.yml - mdbook build docs - - - name: Guard — optional Islo smoke - id: guard - env: - ISLO_API_KEY: ${{ secrets.ISLO_API_KEY }} - run: | - if [ -z "${ISLO_API_KEY}" ]; then - echo "ISLO_API_KEY not configured — skipping islo smoke." - echo "run=false" >> "$GITHUB_OUTPUT" - else - echo "run=true" >> "$GITHUB_OUTPUT" - fi - - - name: crabbox run --provider islo - if: steps.guard.outputs.run == 'true' - env: - ISLO_API_KEY: ${{ secrets.ISLO_API_KEY }} - run: | - set -euo pipefail - # Prefer Islo tenant defaults (omit --islo-image); keep the lease small. - crabbox run \ - --provider islo \ - --islo-vcpus 2 \ - --islo-memory-mb 2048 \ - --islo-disk-gb 10 \ - --no-sync \ - -- echo crabbox-islo-ok diff --git a/.gitignore b/.gitignore index add368b3..a18123c4 100644 --- a/.gitignore +++ b/.gitignore @@ -63,9 +63,6 @@ Temporary Items env/ .env -# Local openclaw/crabbox project config (copy from config/crabbox.example.yaml) -.crabbox.yaml - # OpenAPI Generator .openapi-generator/ diff --git a/README.md b/README.md index 6ccf24e9..f8557adc 100644 --- a/README.md +++ b/README.md @@ -94,40 +94,15 @@ see 📖 [Deployment](https://kvcache-ai.github.io/AgentENV/deployment/manual-co --- -## 🦀 Crabbox sandbox client - -[Crabbox](https://crabbox.sh/) ([openclaw/crabbox](https://github.com/openclaw/crabbox)) -is the recommended sandbox client for AgentENV: sync a checkout into a -Firecracker sandbox, run a command, stream output, and release. - -```bash -brew install openclaw/tap/crabbox - -export CRABBOX_E2B_API_URL=https://agentenv.example.com -export CRABBOX_E2B_API_KEY=e2b_000000 -export CRABBOX_E2B_TEMPLATE=ubuntu # AgentENV template id or name -# optional: install -m 600 config/crabbox.example.yaml .crabbox.yaml - -crabbox doctor --provider e2b -crabbox run --provider e2b -- make test-unit -``` - -`crabbox run` requires AgentENV to advertise an HTTPS wildcard sandbox proxy -domain; the control-plane URL alone is not sufficient. See the integration guide -for the server, DNS, and TLS setup. - -See 📖 [Crabbox integration](https://kvcache-ai.github.io/AgentENV/integration/crabbox.html). -Verified with [Islo](https://islo.dev) via `crabbox --provider islo` while landing -this client path. - ## 🔌 E2B compatibility AgentENV exposes an E2B-compatible HTTP API. Point `E2B_API_URL` at your -server and use the standard E2B Python / TypeScript SDK without any AgentENV -code changes. Crabbox’s `e2b` provider also works when host-based sandbox -routing is configured. See 📖 -[E2B integration](https://kvcache-ai.github.io/AgentENV/integration/e2b.html) -for SDK setup details. +server and use the standard E2B Python / TypeScript SDK without any code +changes. See 📖 [E2B integration](https://kvcache-ai.github.io/AgentENV/integration/e2b.html) +for setup details. + +Crabbox's `e2b` provider also works when host-based sandbox routing is +configured — see 📖 [Crabbox integration](https://kvcache-ai.github.io/AgentENV/integration/crabbox.html). --- diff --git a/config/crabbox.example.yaml b/config/crabbox.example.yaml deleted file mode 100644 index 8e9df831..00000000 --- a/config/crabbox.example.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Example openclaw/crabbox config for AgentENV. -# Install with mode 0600 at the repo root as `.crabbox.yaml`, or merge it into -# your user Crabbox config. -# Docs: https://crabbox.sh/ · Provider: https://crabbox.sh/providers/e2b.html -# -# Endpoint and auth stay in the environment (never commit destinations or keys): -# export CRABBOX_E2B_API_URL=https://agentenv.example.com -# export CRABBOX_E2B_API_KEY=e2b_000000 -# -# `crabbox run` also requires AgentENV to advertise an HTTPS wildcard sandbox -# proxy domain. See docs/src/integration/crabbox.md before running this config. -# -# Then: -# brew install openclaw/tap/crabbox -# install -m 600 config/crabbox.example.yaml .crabbox.yaml -# crabbox doctor --provider e2b -# crabbox run --provider e2b -- make test-unit - -provider: e2b -target: linux -e2b: - # AgentENV template id or name (aenv pull … / aenv template list) - template: ubuntu - # Dedicated subdirectory inside the sandbox (not /, /tmp, …) - workdir: crabbox diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 8b31b778..32362be9 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -34,8 +34,8 @@ # Integration -- [Crabbox](./integration/crabbox.md) - [E2B](./integration/e2b.md) +- [Crabbox](./integration/crabbox.md) # Troubleshooting diff --git a/docs/src/configuration/env-vars.md b/docs/src/configuration/env-vars.md index 8aad6d98..0812a135 100644 --- a/docs/src/configuration/env-vars.md +++ b/docs/src/configuration/env-vars.md @@ -36,11 +36,9 @@ These variables are consumed by the repository's Docker Compose and Kubernetes h | `AENV_FIRECRACKER_SERIAL_DIR` | `$AENV_HOME/logs/serial` | Override the directory for persistent Firecracker serial output. Files are grouped under `{serial_dir}/{sandbox_id}/`. | | `AENV_PERSISTED_SANDBOX_STORE_PATH` | `$AENV_HOME/persisted-sandboxes` | Override the directory where paused sandbox state is persisted across server restarts. | -## E2B SDK / CLI / Crabbox +## E2B SDK / CLI -These variables configure the E2B SDK and CLI to point at an AgentENV server. -[Crabbox](../integration/crabbox.md)’s `e2b` provider shares some of them but -uses host-based sandbox URLs rather than `E2B_SANDBOX_URL`. +These variables configure the E2B SDK and CLI to point at an AgentENV server. Values depend on your deployment mode. | Variable | Description | |----------|-------------| @@ -48,12 +46,6 @@ uses host-based sandbox URLs rather than `E2B_SANDBOX_URL`. | `E2B_SANDBOX_URL` | Sandbox proxy URL (for WebSocket and process interaction) | | `E2B_API_KEY` | API key for authentication | | `E2B_ACCESS_TOKEN` | Access token (used by `e2b template` commands) | -| `CRABBOX_E2B_API_URL` | Crabbox override for `E2B_API_URL` (takes precedence) | -| `CRABBOX_E2B_API_KEY` | Crabbox override for `E2B_API_KEY` (takes precedence) | -| `CRABBOX_E2B_DOMAIN` | Fallback wildcard sandbox domain; AgentENV normally advertises `[sandbox_proxy].domains[0]` in its sandbox response | -| `CRABBOX_E2B_TEMPLATE` | AgentENV template id/name for `crabbox run --provider e2b` | -| `CRABBOX_E2B_WORKDIR` | Dedicated directory inside the sandbox used for repo sync and commands | -| `CRABBOX_E2B_USER` | Optional sandbox login name used for file ownership and commands | ### Values by Deployment Mode @@ -81,13 +73,6 @@ export E2B_ACCESS_TOKEN=dummy > For local development, any non-empty value works for `E2B_API_KEY` and `E2B_ACCESS_TOKEN` because the server only checks that the auth header is present. -> Crabbox does not read `E2B_SANDBOX_URL`. Its `e2b` provider connects to -> `https://{port}-{sandboxID}.{domain}`. Configure AgentENV's host-based sandbox -> routing, wildcard DNS, and TLS as described in the -> [Crabbox integration](../integration/crabbox.md). A plain loopback -> `E2B_API_URL` is sufficient for `crabbox doctor` and `list`, but not for -> `warmup` or `run`. - ## Gateway and Scheduler These variables apply to both the gateway and scheduler processes. diff --git a/docs/src/getting-started/overview.md b/docs/src/getting-started/overview.md index 5ad3e18f..211fdee6 100644 --- a/docs/src/getting-started/overview.md +++ b/docs/src/getting-started/overview.md @@ -16,7 +16,7 @@ The repository is available at . - **Pause and resume** with memory + disk snapshots for instant cold start - **Layered block devices** via overlaybd + ublk for copy-on-write image sharing - **Snapshot-backed template builder** for publishing reusable, pre-configured sandbox runtimes -- **E2B-compatible API** for existing E2B SDKs and CLIs, plus [Crabbox](../integration/crabbox.md) on deployments with host-based sandbox routing +- **E2B-compatible API** so existing E2B SDKs and CLIs work out of the box - **Reverse proxy** to reach services running inside sandboxes via HTTP and WebSocket - **Multi-node scaling** with a gateway + scheduler control plane (prototype) @@ -26,13 +26,13 @@ AgentENV is built for teams running AI agents that need isolated execution envir ## Interacting with the Server -AgentENV exposes an HTTP API. There are several ways to use it: +AgentENV exposes an HTTP API. There are four ways to use it: | Method | Best for | |--------|----------| | **[aenv CLI](./aenv-cli.md)** | Interactive use, scripting, local development | -| **[Crabbox](../integration/crabbox.md)** | Recommended sandbox client ([openclaw/crabbox](https://github.com/openclaw/crabbox)) — repo sync + remote run for agents and automation | | **[E2B](../integration/e2b.md)** | Application code — existing E2B-based applications work with AgentENV without modification | +| **[Crabbox](../integration/crabbox.md)** | Third-party client for repo sync plus remote command execution | | **[HTTP API](../api/index.md)** | Direct control, other languages, automation | ## Where to Go Next diff --git a/docs/src/integration/crabbox.md b/docs/src/integration/crabbox.md index 6a075d4d..6ac634a9 100644 --- a/docs/src/integration/crabbox.md +++ b/docs/src/integration/crabbox.md @@ -1,190 +1,34 @@ # Crabbox [Crabbox](https://crabbox.sh/) -([openclaw/crabbox](https://github.com/openclaw/crabbox)) is a sandbox client -for edit–sync–run loops from a laptop, CI job, or coding agent. Its built-in -`e2b` provider can use AgentENV's E2B-compatible control plane and host-based -sandbox data plane. +([openclaw/crabbox](https://github.com/openclaw/crabbox)) is a third-party +sandbox client for sync-and-run loops from a laptop, CI job, or coding agent. +Its built-in `e2b` provider talks to AgentENV's E2B-compatible API, so no +AgentENV-side plugin or wrapper is required. -No AgentENV-side plugin or repository-local wrapper is required. The deployment -does need the optional sandbox proxy domain described below; setting only -`E2B_API_URL` is not enough for a Crabbox run. - -## When to use Crabbox - -| Client | Best for | -|--------|----------| -| **[aenv CLI](../getting-started/aenv-cli.md)** | Interactive shells, pause/resume, template management | -| **[Crabbox](https://crabbox.sh/)** | Repo sync + remote command execution for agents and automation | -| **[E2B SDK](./e2b.md)** | Embedding sandbox create/run/kill in application code | - -Use Crabbox when the workflow is “sync this checkout, run a command in an -AgentENV sandbox, and stream the output.” Prefer `aenv` for interactive attach, -snapshot operations, and cluster administration. - -## How the connection works - -Crabbox uses two routes: - -1. Lifecycle calls such as create, list, connect, and delete go to - `CRABBOX_E2B_API_URL` (or `E2B_API_URL`). -2. File upload and process calls go to - `https://{port}-{sandboxID}.{domain}`. - -AgentENV returns the first configured `[sandbox_proxy].domains` entry in create, -connect, and detail responses. Crabbox uses that advertised domain for the -second route. - -Crabbox's `e2b` provider does not read `E2B_SANDBOX_URL`, so the routing-header -setup used by the E2B SDK cannot replace the host-based route. In particular, a -plain `http://127.0.0.1:8000` API URL can support `doctor` and `list`, but -`warmup` and `run` also need an HTTPS sandbox proxy domain. - -## Configure AgentENV routing - -Choose a DNS name dedicated to sandbox traffic, for example -`sandbox.agentenv.example.com`. - -For a single node, configure the server: - -```bash -export AENV_SANDBOX_PROXY_DOMAINS=sandbox.agentenv.example.com -make start-server -``` - -For the Docker Compose or Kubernetes multi-node helpers, configure the shared -gateway/runtime value: - -```bash -export SANDBOX_PROXY_DOMAINS=sandbox.agentenv.example.com -make deploy-up -``` - -The deployment must also provide: - -- wildcard DNS for `*.sandbox.agentenv.example.com` pointing to the AgentENV - server or gateway; -- a wildcard TLS certificate for that name; -- a TLS load balancer or reverse proxy that preserves the original `Host` and - forwards requests to AgentENV. - -The server and gateway accept only explicitly configured domains. See -[Proxy](../concepts/proxy.md), [Environment Variables](../configuration/env-vars.md), -and the relevant deployment guide for more detail. - -## Install and configure Crabbox - -Install the upstream CLI: - -```bash -brew install openclaw/tap/crabbox -# See https://crabbox.sh/ for other platforms. -crabbox --version -``` - -Point Crabbox's control plane at the AgentENV API and select an existing -template: +## Configuration ```bash export CRABBOX_E2B_API_URL=https://agentenv.example.com export CRABBOX_E2B_API_KEY=e2b_000000 -export CRABBOX_E2B_TEMPLATE=ubuntu -``` - -`CRABBOX_E2B_*` values take precedence over the corresponding `E2B_*` values. -`E2B_API_KEY` or `CRABBOX_E2B_API_KEY` must be non-empty because Crabbox checks -for it. AgentENV does not currently enforce that key, so keep the API on a -trusted network even when TLS is enabled. - -AgentENV normally advertises the configured sandbox proxy domain. If an -intermediary strips the `domain` response field, set the same value explicitly: - -```bash -export CRABBOX_E2B_DOMAIN=sandbox.agentenv.example.com -``` - -### Project config - -Install the checked-in example with private permissions and adjust the template: - -```bash -install -m 600 config/crabbox.example.yaml .crabbox.yaml -``` - -```yaml -provider: e2b -target: linux -e2b: - template: ubuntu - workdir: crabbox -``` - -Keep API destinations and credentials in explicit environment variables or -trusted user configuration. Crabbox intentionally refuses to send inherited -credentials to a destination supplied only by repository configuration. -Crabbox also requires loaded configuration files to be private (`0600`), which -is why the example uses `install` rather than a plain `cp`. +export CRABBOX_E2B_TEMPLATE=ubuntu # AgentENV template id or name -## Run against AgentENV - -```bash crabbox doctor --provider e2b - -# One shot: create, sync, run, and release. -crabbox run --provider e2b --e2b-template ubuntu -- make test-unit - -# Keep a warm sandbox for repeated edit/run loops. -crabbox warmup --provider e2b --e2b-template ubuntu -lease= -crabbox status --provider e2b --id "$lease" --wait -crabbox run --provider e2b --id "$lease" --shell 'make test-unit' -crabbox stop --provider e2b "$lease" -crabbox list --provider e2b --json +crabbox run --provider e2b -- make test-unit ``` -Under the hood, Crabbox: - -1. creates an AgentENV sandbox from the selected template; -2. archive-syncs the Git-managed working set into the sandbox workdir; -3. runs the command through envd's process API and streams stdout/stderr; -4. deletes a one-shot sandbox on release, unless retention was requested. - -## Notes and limits - -- The `e2b` provider is a delegated-run path, not an SSH lease. -- Use a dedicated `e2b.workdir`; Crabbox rejects broad roots such as `/`, - `/home`, and `/tmp`. -- Pause/resume, fork, and snapshot APIs remain AgentENV-native. Use `aenv` or - the HTTP API for those operations. -- Crabbox's E2B sandbox timeout is capped at one hour. -- AgentENV currently does not enforce authorization. Do not expose its control - or sandbox data plane directly to the public internet. - -## Upstream CLI verification - -The upstream Crabbox binary was also exercised against -[Islo](https://islo.dev) through Crabbox's separate `islo` provider: - -```bash -islo api-key create crabbox-agentenv-smoke --show -export ISLO_API_KEY='…' - -crabbox doctor --provider islo -crabbox list --provider islo --json -crabbox run --provider islo --no-sync -- echo crabbox-islo-ok -``` +## Sandbox routing requirement -This verifies the upstream delegated-run CLI path; it does not replace an -AgentENV E2B smoke test. AgentENV usage remains on `--provider e2b` with the -control-plane and wildcard-domain setup above. +Crabbox's `e2b` provider does not read `E2B_SANDBOX_URL`. It reaches sandboxes +over host-based URLs shaped like `https://{port}-{sandboxID}.{domain}`, using the +domain AgentENV advertises from `[sandbox_proxy].domains`. A loopback +`CRABBOX_E2B_API_URL` is therefore enough for `doctor` and `list`, but `warmup` +and `run` also need a configured sandbox proxy domain with wildcard DNS and TLS. +See [Proxy](../concepts/proxy.md) and +[Environment Variables](../configuration/env-vars.md) for that setup. -## Related docs +If an intermediary strips the `domain` field from AgentENV's sandbox response, +set `CRABBOX_E2B_DOMAIN` to the same value. -- [E2B integration](./e2b.md) — SDK setup and shared environment variables -- [Proxy](../concepts/proxy.md) — routing headers and host-based URLs -- [aenv CLI](../getting-started/aenv-cli.md) — interactive AgentENV workflows -- `config/crabbox.example.yaml` — checked-in project configuration -- [Crabbox E2B provider](https://crabbox.sh/providers/e2b.html) — upstream - provider flags, auth, and limits -- [Crabbox Islo provider](https://crabbox.sh/providers/islo.html) — external CLI - verification provider +Installation, provider flags, and limits are documented upstream at +. diff --git a/docs/src/integration/e2b.md b/docs/src/integration/e2b.md index a32bc7ae..a92225d3 100644 --- a/docs/src/integration/e2b.md +++ b/docs/src/integration/e2b.md @@ -102,6 +102,5 @@ sandbox.kill() AgentENV is compatible with the E2B CLI, but we recommend using the [aenv CLI](../getting-started/aenv-cli.md) for AgentENV workflows. -For edit–sync–run loops from a local checkout (agents, CI, maintainers), -use [Crabbox](./crabbox.md). Its `e2b` provider requires AgentENV's optional -host-based sandbox proxy domain in addition to the control-plane API URL. +For sync-and-run loops from a local checkout, [Crabbox](./crabbox.md) is a +third-party client that speaks the same API. diff --git a/docs/src/troubleshooting/common-issues.md b/docs/src/troubleshooting/common-issues.md index 06c35a43..3f47fa0a 100644 --- a/docs/src/troubleshooting/common-issues.md +++ b/docs/src/troubleshooting/common-issues.md @@ -64,46 +64,4 @@ API_ADDR=0.0.0.0:8001 make start-server Also check `[envd].init_timeout_secs` in your config. The default is 60 seconds. If the rootfs image is large, the in-guest envd daemon may need more time to initialize. -## Crabbox cannot reach AgentENV - -**Symptom**: `crabbox doctor --provider e2b` or -`crabbox run --provider e2b` fails with auth, DNS, TLS, or connection errors. - -**Solution**: - -1. Install the upstream CLI: `brew install openclaw/tap/crabbox` (see - [crabbox.sh](https://crabbox.sh/)). -2. Set the AgentENV control-plane URL, a non-empty key, and an existing template: - -```bash -export CRABBOX_E2B_API_URL=https://agentenv.example.com -export CRABBOX_E2B_API_KEY=e2b_000000 -export CRABBOX_E2B_TEMPLATE= -``` - -3. Confirm `crabbox doctor --provider e2b` succeeds. If it fails, verify the API - URL is reachable and was set explicitly in the environment. Crabbox refuses - inherited credentials paired only with a repository-configured endpoint. -4. If `doctor` succeeds but `warmup` or `run` fails, verify AgentENV advertises a - sandbox domain: - - ```bash - export AENV_SANDBOX_PROXY_DOMAINS=sandbox.agentenv.example.com - ``` - - Wildcard DNS and TLS for `*.sandbox.agentenv.example.com` must route to the - AgentENV server or gateway. Crabbox does not read `E2B_SANDBOX_URL`; a plain - loopback API URL alone cannot carry its file and process traffic. -5. Install the example with private permissions if you want project template - and workdir defaults: - - ```bash - install -m 600 config/crabbox.example.yaml .crabbox.yaml - ``` - - If doctor reports `permissions 0644 want 0600`, run - `chmod 600 .crabbox.yaml`. - -See [Crabbox integration](../integration/crabbox.md). - > TODO: Expand with more common issues as they are reported. diff --git a/scripts/tests/crabbox-e2b-contract-server.py b/scripts/tests/crabbox-e2b-contract-server.py deleted file mode 100644 index f1d7858b..00000000 --- a/scripts/tests/crabbox-e2b-contract-server.py +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env python3 -"""Strict local E2B contract server for the Crabbox CI smoke test.""" - -from __future__ import annotations - -import argparse -import base64 -import json -import ssl -import struct -import threading -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from typing import Any -from urllib.parse import parse_qs, urlparse - -SANDBOX_ID = "019c6f83-4df1-7e70-8000-000000000035" -API_KEY = "agentenv-ci-placeholder" -ACCESS_TOKEN = "agentenv-contract-access-token" -MARKER = "crabbox-agentenv-contract-ok" - - -class ContractState: - def __init__(self, data_port: int) -> None: - self.data_port = data_port - self.metadata: dict[str, str] = {} - self.counts = { - "list": 0, - "create": 0, - "connect": 0, - "upload": 0, - "process": 0, - "marker_commands": 0, - "delete": 0, - } - self.errors: list[str] = [] - self.lock = threading.Lock() - - def record(self, name: str) -> None: - with self.lock: - self.counts[name] += 1 - - def record_error(self, error: BaseException) -> None: - with self.lock: - self.errors.append(str(error)) - - def snapshot(self) -> dict[str, object]: - with self.lock: - return { - **self.counts, - "errors": list(self.errors), - } - - def sandbox(self) -> dict[str, object]: - return { - "templateID": "ubuntu", - "sandboxID": SANDBOX_ID, - "clientID": "", - "envdVersion": "contract", - "envdAccessToken": ACCESS_TOKEN, - "domain": f"localhost:{self.data_port}", - "metadata": dict(self.metadata), - } - - -class ContractServer(ThreadingHTTPServer): - daemon_threads = True - - def __init__( - self, - server_address: tuple[str, int], - handler: type[BaseHTTPRequestHandler], - state: ContractState, - plane: str, - ) -> None: - self.state = state - self.plane = plane - super().__init__(server_address, handler) - - -class Handler(BaseHTTPRequestHandler): - protocol_version = "HTTP/1.1" - - @property - def contract_server(self) -> ContractServer: - assert isinstance(self.server, ContractServer) - return self.server - - @property - def state(self) -> ContractState: - return self.contract_server.state - - def do_GET(self) -> None: - try: - parsed = urlparse(self.path) - if parsed.path == "/health": - self._send_bytes(200, b"ok") - return - if parsed.path == "/contract-state": - self._send_json(200, self.state.snapshot()) - return - assert self.contract_server.plane == "control", self.path - if parsed.path == "/v2/sandboxes": - self._assert_api_key() - query = parse_qs(parsed.query) - assert query.get("limit") == ["100"], query - assert query.get("state") == ["running,paused"], query - metadata = parse_qs(query.get("metadata", [""])[0]) - assert metadata == { - "crabbox": ["true"], - "provider": ["e2b"], - }, metadata - self.state.record("list") - self._send_json(200, []) - return - if parsed.path == f"/sandboxes/{SANDBOX_ID}": - self._assert_api_key() - self._send_json(200, self.state.sandbox()) - return - raise AssertionError(f"unexpected GET {self.path}") - except BaseException as error: - self._fail(error) - - def do_POST(self) -> None: - try: - if self.contract_server.plane == "data": - parsed = urlparse(self.path) - if parsed.path == "/files": - self._handle_upload(parsed.query) - else: - self._handle_process() - return - - self._assert_api_key() - parsed = urlparse(self.path) - body = self._read_json() - if parsed.path == "/sandboxes": - assert body["templateID"] == "ubuntu", body - assert body["secure"] is True, body - assert body["allow_internet_access"] is True, body - assert body["metadata"]["crabbox"] == "true", body - assert body["metadata"]["provider"] == "e2b", body - self.state.metadata = body["metadata"] - self.state.record("create") - self._send_json(201, self.state.sandbox()) - return - if parsed.path == f"/sandboxes/{SANDBOX_ID}/connect": - assert body["timeout"] > 0, body - self.state.record("connect") - self._send_json(200, self.state.sandbox()) - return - raise AssertionError(f"unexpected POST {self.path}") - except BaseException as error: - self._fail(error) - - def do_DELETE(self) -> None: - try: - assert self.contract_server.plane == "control", self.path - self._assert_api_key() - assert urlparse(self.path).path == f"/sandboxes/{SANDBOX_ID}", self.path - self.state.record("delete") - self._send_bytes(204, b"") - except BaseException as error: - self._fail(error) - - def _handle_process(self) -> None: - parsed = urlparse(self.path) - assert parsed.path == "/process.Process/Start", self.path - self._assert_data_plane_headers() - assert self.headers.get("Connect-Protocol-Version") == "1", self.headers - - raw = self._read_body() - assert len(raw) >= 5 and raw[0] == 0, raw - size = struct.unpack(">I", raw[1:5])[0] - assert size == len(raw) - 5, (size, len(raw)) - request = json.loads(raw[5:]) - process = request["process"] - assert process["cmd"] == "/bin/bash", process - assert process["args"][:2] == ["-l", "-c"], process - command = process["args"][2] - - self.state.record("process") - output = b"" - if MARKER in command: - self.state.record("marker_commands") - output = f"{MARKER}\n".encode() - - response = b"".join( - [ - self._connect_envelope({"event": {"start": {"pid": 35}}}), - self._connect_envelope( - { - "event": { - "data": { - "stdout": base64.b64encode(output).decode(), - } - } - } - ), - self._connect_envelope( - { - "event": { - "end": { - "exitCode": 0, - "exited": True, - "status": "exited", - } - } - } - ), - bytes([2]) + struct.pack(">I", 0), - ] - ) - self._send_bytes(200, response, "application/connect+json") - - def _handle_upload(self, raw_query: str) -> None: - self._assert_data_plane_headers() - query = parse_qs(raw_query) - target = query.get("path", [""])[0] - assert target.startswith("/tmp/crabbox-") and target.endswith(".tgz"), query - assert self.headers.get("Content-Type", "").startswith( - "multipart/form-data;" - ), self.headers - body = self._read_body() - assert len(body) > 100, len(body) - self.state.record("upload") - self._send_json(200, {}) - - def _assert_data_plane_headers(self) -> None: - expected_host = f"49983-{SANDBOX_ID}.localhost:{self.state.data_port}" - assert self.headers.get("Host") == expected_host, self.headers - assert self.headers.get("E2b-Sandbox-Id") == SANDBOX_ID, self.headers - assert self.headers.get("E2b-Sandbox-Port") == "49983", self.headers - assert self.headers.get("X-Access-Token") == ACCESS_TOKEN, self.headers - - def _assert_api_key(self) -> None: - assert self.headers.get("X-API-Key") == API_KEY, self.headers - - def _read_body(self) -> bytes: - if self.headers.get("Transfer-Encoding", "").lower() == "chunked": - chunks = [] - while True: - size_line = self.rfile.readline() - assert size_line, "chunked body ended before its zero chunk" - size = int(size_line.split(b";", 1)[0].strip(), 16) - if size == 0: - while self.rfile.readline() not in (b"\r\n", b""): - pass - break - chunks.append(self.rfile.read(size)) - assert self.rfile.read(2) == b"\r\n", "invalid chunk terminator" - return b"".join(chunks) - length = int(self.headers.get("Content-Length", "0")) - return self.rfile.read(length) - - def _read_json(self) -> dict[str, Any]: - return json.loads(self._read_body()) - - @staticmethod - def _connect_envelope(payload: dict[str, object]) -> bytes: - data = json.dumps(payload, separators=(",", ":")).encode() - return bytes([0]) + struct.pack(">I", len(data)) + data - - def _send_json(self, status: int, payload: object) -> None: - self._send_bytes( - status, - json.dumps(payload, separators=(",", ":")).encode(), - "application/json", - ) - - def _send_bytes( - self, - status: int, - body: bytes, - content_type: str = "text/plain", - ) -> None: - self.send_response(status) - self.send_header("Content-Type", content_type) - self.send_header("Content-Length", str(len(body))) - self.end_headers() - if body: - self.wfile.write(body) - - def _fail(self, error: BaseException) -> None: - self.state.record_error(error) - self._send_json(500, {"error": str(error)}) - - def log_message(self, format: str, *args: object) -> None: - del format, args - - -def main() -> None: - parser = argparse.ArgumentParser() - parser.add_argument("--cert", required=True) - parser.add_argument("--key", required=True) - parser.add_argument("--control-port", type=int, default=18080) - parser.add_argument("--data-port", type=int, default=18443) - args = parser.parse_args() - - state = ContractState(args.data_port) - control = ContractServer( - ("127.0.0.1", args.control_port), Handler, state, "control" - ) - data = ContractServer(("127.0.0.1", args.data_port), Handler, state, "data") - tls = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) - tls.load_cert_chain(args.cert, args.key) - data.socket = tls.wrap_socket(data.socket, server_side=True) - - data_thread = threading.Thread(target=data.serve_forever, daemon=True) - data_thread.start() - print( - f"contract server ready control={args.control_port} data={args.data_port}", - flush=True, - ) - try: - control.serve_forever() - finally: - data.shutdown() - data.server_close() - control.server_close() - - -if __name__ == "__main__": - main() From 0d268b72229a7c0aaab415c44dea2ca1afee5b8c Mon Sep 17 00:00:00 2001 From: zozo123 Date: Sun, 2 Aug 2026 14:21:39 +0300 Subject: [PATCH 4/4] Reduce Crabbox docs to E2B subsection --- README.md | 3 --- docs/src/SUMMARY.md | 1 - docs/src/getting-started/overview.md | 1 - docs/src/getting-started/quickstart.md | 1 - docs/src/integration/crabbox.md | 34 -------------------------- docs/src/integration/e2b.md | 20 +++++++++++++-- 6 files changed, 18 insertions(+), 42 deletions(-) delete mode 100644 docs/src/integration/crabbox.md diff --git a/README.md b/README.md index f8557adc..f44a3daa 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,6 @@ server and use the standard E2B Python / TypeScript SDK without any code changes. See 📖 [E2B integration](https://kvcache-ai.github.io/AgentENV/integration/e2b.html) for setup details. -Crabbox's `e2b` provider also works when host-based sandbox routing is -configured — see 📖 [Crabbox integration](https://kvcache-ai.github.io/AgentENV/integration/crabbox.html). - --- ## 🛠 aenv CLI reference diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 32362be9..76c4d02e 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -35,7 +35,6 @@ # Integration - [E2B](./integration/e2b.md) -- [Crabbox](./integration/crabbox.md) # Troubleshooting diff --git a/docs/src/getting-started/overview.md b/docs/src/getting-started/overview.md index 211fdee6..a7aed99e 100644 --- a/docs/src/getting-started/overview.md +++ b/docs/src/getting-started/overview.md @@ -32,7 +32,6 @@ AgentENV exposes an HTTP API. There are four ways to use it: |--------|----------| | **[aenv CLI](./aenv-cli.md)** | Interactive use, scripting, local development | | **[E2B](../integration/e2b.md)** | Application code — existing E2B-based applications work with AgentENV without modification | -| **[Crabbox](../integration/crabbox.md)** | Third-party client for repo sync plus remote command execution | | **[HTTP API](../api/index.md)** | Direct control, other languages, automation | ## Where to Go Next diff --git a/docs/src/getting-started/quickstart.md b/docs/src/getting-started/quickstart.md index 6fdb230e..993c0940 100644 --- a/docs/src/getting-started/quickstart.md +++ b/docs/src/getting-started/quickstart.md @@ -103,6 +103,5 @@ aenv start ubuntu # starts a sandbox and attaches an interactive shel - [Deployment](../deployment/manual-compile.md) — build from source, multi-node options - [Core Concepts](../concepts/overview.md) — how sandboxes, templates, and snapshots work -- [Crabbox](../integration/crabbox.md) — openclaw/crabbox client for sync + remote run - [E2B](../integration/e2b.md) — SDK and CLI compatibility - [API Reference](../api/index.md) — full HTTP API diff --git a/docs/src/integration/crabbox.md b/docs/src/integration/crabbox.md deleted file mode 100644 index 6ac634a9..00000000 --- a/docs/src/integration/crabbox.md +++ /dev/null @@ -1,34 +0,0 @@ -# Crabbox - -[Crabbox](https://crabbox.sh/) -([openclaw/crabbox](https://github.com/openclaw/crabbox)) is a third-party -sandbox client for sync-and-run loops from a laptop, CI job, or coding agent. -Its built-in `e2b` provider talks to AgentENV's E2B-compatible API, so no -AgentENV-side plugin or wrapper is required. - -## Configuration - -```bash -export CRABBOX_E2B_API_URL=https://agentenv.example.com -export CRABBOX_E2B_API_KEY=e2b_000000 -export CRABBOX_E2B_TEMPLATE=ubuntu # AgentENV template id or name - -crabbox doctor --provider e2b -crabbox run --provider e2b -- make test-unit -``` - -## Sandbox routing requirement - -Crabbox's `e2b` provider does not read `E2B_SANDBOX_URL`. It reaches sandboxes -over host-based URLs shaped like `https://{port}-{sandboxID}.{domain}`, using the -domain AgentENV advertises from `[sandbox_proxy].domains`. A loopback -`CRABBOX_E2B_API_URL` is therefore enough for `doctor` and `list`, but `warmup` -and `run` also need a configured sandbox proxy domain with wildcard DNS and TLS. -See [Proxy](../concepts/proxy.md) and -[Environment Variables](../configuration/env-vars.md) for that setup. - -If an intermediary strips the `domain` field from AgentENV's sandbox response, -set `CRABBOX_E2B_DOMAIN` to the same value. - -Installation, provider flags, and limits are documented upstream at -. diff --git a/docs/src/integration/e2b.md b/docs/src/integration/e2b.md index a92225d3..a8c9f158 100644 --- a/docs/src/integration/e2b.md +++ b/docs/src/integration/e2b.md @@ -102,5 +102,21 @@ sandbox.kill() AgentENV is compatible with the E2B CLI, but we recommend using the [aenv CLI](../getting-started/aenv-cli.md) for AgentENV workflows. -For sync-and-run loops from a local checkout, [Crabbox](./crabbox.md) is a -third-party client that speaks the same API. +## Crabbox + +[Crabbox](https://crabbox.sh/providers/e2b.html) is a third-party client that +uses AgentENV through its E2B-compatible API. Configure its `e2b` provider with: + +```bash +export CRABBOX_E2B_API_URL=https://agentenv.example.com +export CRABBOX_E2B_API_KEY=e2b_000000 +export CRABBOX_E2B_TEMPLATE=ubuntu + +crabbox doctor --provider e2b +crabbox run --provider e2b -- make test-unit +``` + +`run` also requires host-based sandbox routing through a domain in +`[sandbox_proxy].domains`; wildcard DNS and TLS must cover URLs shaped like +`https://{port}-{sandboxID}.{domain}`. If an intermediary removes the `domain` +field from AgentENV's response, set `CRABBOX_E2B_DOMAIN` to the same domain.