@madarco/agentbox 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +21 -7
  3. package/dist/{_cloud-attach-XKO4SHR3.js → _cloud-attach-GUBB5RH2.js} +4 -4
  4. package/dist/{chunk-R5XIDQFR.js → chunk-BKU34KYY.js} +170 -6
  5. package/dist/chunk-BKU34KYY.js.map +1 -0
  6. package/dist/{chunk-HFV6THYG.js → chunk-BYCLD6D6.js} +308 -36
  7. package/dist/chunk-BYCLD6D6.js.map +1 -0
  8. package/dist/chunk-LDMYHWUS.js +346 -0
  9. package/dist/chunk-LDMYHWUS.js.map +1 -0
  10. package/dist/{chunk-2LF5YILI.js → chunk-RSKG7AFU.js} +80 -6
  11. package/dist/chunk-RSKG7AFU.js.map +1 -0
  12. package/dist/{chunk-DHJ7OMIP.js → chunk-TBSIJVSN.js} +149 -47
  13. package/dist/chunk-TBSIJVSN.js.map +1 -0
  14. package/dist/{chunk-IZXPJPPV.js → chunk-TCS5HXJX.js} +389 -176
  15. package/dist/chunk-TCS5HXJX.js.map +1 -0
  16. package/dist/{chunk-ECLLV5JH.js → chunk-VATTS2MR.js} +156 -5
  17. package/dist/chunk-VATTS2MR.js.map +1 -0
  18. package/dist/{chunk-SNTHHWKY.js → chunk-XKH7NTT7.js} +80 -22
  19. package/dist/chunk-XKH7NTT7.js.map +1 -0
  20. package/dist/dist-34RKQ74M.js +662 -0
  21. package/dist/dist-34RKQ74M.js.map +1 -0
  22. package/dist/{dist-47LVLYUV.js → dist-3IMQNTTV.js} +14 -69
  23. package/dist/dist-3IMQNTTV.js.map +1 -0
  24. package/dist/{dist-RZZSSUNB.js → dist-4DPOL5A7.js} +5 -3
  25. package/dist/{dist-24PY2ZMO.js → dist-57M6ZA7H.js} +25 -177
  26. package/dist/dist-57M6ZA7H.js.map +1 -0
  27. package/dist/{dist-SWUOU34W.js → dist-J2IHD5T7.js} +37 -226
  28. package/dist/dist-J2IHD5T7.js.map +1 -0
  29. package/dist/index.js +1524 -921
  30. package/dist/index.js.map +1 -1
  31. package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
  32. package/package.json +9 -7
  33. package/runtime/docker/Dockerfile.box +21 -26
  34. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +37 -1
  35. package/runtime/docker/packages/ctl/dist/bin.cjs +46 -17
  36. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +17 -6
  37. package/runtime/docker/packages/sandbox-docker/scripts/chromium-resolver +57 -0
  38. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +2 -1
  39. package/runtime/e2b/agentbox-checkpoint-cleanup +52 -0
  40. package/runtime/e2b/agentbox-codex-hooks.json +68 -0
  41. package/runtime/e2b/agentbox-open +28 -0
  42. package/runtime/e2b/agentbox-setup-skill.md +233 -0
  43. package/runtime/e2b/agentbox-vnc-start +102 -0
  44. package/runtime/e2b/attach-helper.cjs +167 -0
  45. package/runtime/e2b/claude-managed-settings.json +116 -0
  46. package/runtime/e2b/ctl.cjs +23864 -0
  47. package/runtime/e2b/custom-system-CLAUDE.md +46 -0
  48. package/runtime/e2b/gh-shim +344 -0
  49. package/runtime/e2b/git-shim +131 -0
  50. package/runtime/e2b/scripts/build-template.sh +295 -0
  51. package/runtime/hetzner/agentbox-setup-skill.md +37 -1
  52. package/runtime/hetzner/agentbox-vnc-start +17 -6
  53. package/runtime/hetzner/claude-managed-settings.json +2 -1
  54. package/runtime/hetzner/ctl.cjs +46 -17
  55. package/runtime/relay/bin.cjs +305 -230
  56. package/runtime/vercel/agentbox-setup-skill.md +37 -1
  57. package/runtime/vercel/agentbox-vnc-start +17 -6
  58. package/runtime/vercel/claude-managed-settings.json +2 -1
  59. package/runtime/vercel/ctl.cjs +46 -17
  60. package/share/agentbox-setup/SKILL.md +37 -1
  61. package/share/host-skills/agentbox-info/SKILL.md +26 -34
  62. package/dist/chunk-2LF5YILI.js.map +0 -1
  63. package/dist/chunk-DHJ7OMIP.js.map +0 -1
  64. package/dist/chunk-ECLLV5JH.js.map +0 -1
  65. package/dist/chunk-HFV6THYG.js.map +0 -1
  66. package/dist/chunk-IZXPJPPV.js.map +0 -1
  67. package/dist/chunk-R5XIDQFR.js.map +0 -1
  68. package/dist/chunk-SNTHHWKY.js.map +0 -1
  69. package/dist/dist-24PY2ZMO.js.map +0 -1
  70. package/dist/dist-47LVLYUV.js.map +0 -1
  71. package/dist/dist-SWUOU34W.js.map +0 -1
  72. /package/dist/{_cloud-attach-XKO4SHR3.js.map → _cloud-attach-GUBB5RH2.js.map} +0 -0
  73. /package/dist/{dist-RZZSSUNB.js.map → dist-4DPOL5A7.js.map} +0 -0
  74. /package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js.map → prepared-state-MQHD3M5F-Q27AZU53.js.map} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  readPreparedDockerState,
7
7
  resolveContextFiles,
8
8
  writePreparedDockerState
9
- } from "./chunk-SNTHHWKY.js";
9
+ } from "./chunk-XKH7NTT7.js";
10
10
  export {
11
11
  DOCKERFILE_PATH,
12
12
  computeDockerContextFingerprint,
@@ -15,4 +15,4 @@ export {
15
15
  resolveContextFiles,
16
16
  writePreparedDockerState
17
17
  };
18
- //# sourceMappingURL=prepared-state-MQHD3M5F-KE4DT3GX.js.map
18
+ //# sourceMappingURL=prepared-state-MQHD3M5F-Q27AZU53.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madarco/agentbox",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Launch Claude Code, Codex, and other coding agents in isolated sandboxes",
5
5
  "license": "MIT",
6
6
  "author": "Marco D'Alia",
@@ -46,6 +46,7 @@
46
46
  "@vercel/sandbox": "^2.0.1",
47
47
  "@xterm/headless": "^5.5.0",
48
48
  "commander": "^12.1.0",
49
+ "e2b": "^2.27.1",
49
50
  "execa": "^9.5.2",
50
51
  "supports-hyperlinks": "^3.1.0",
51
52
  "yaml": "^2.6.1"
@@ -58,16 +59,17 @@
58
59
  "tsup": "^8.3.5",
59
60
  "typescript": "^5.7.2",
60
61
  "vitest": "^2.1.8",
61
- "@agentbox/ctl": "0.0.0",
62
- "@agentbox/core": "0.0.0",
63
62
  "@agentbox/config": "0.0.0",
64
- "@agentbox/relay": "0.0.0",
63
+ "@agentbox/ctl": "0.0.0",
65
64
  "@agentbox/sandbox-cloud": "0.0.0",
65
+ "@agentbox/relay": "0.0.0",
66
+ "@agentbox/core": "0.0.0",
66
67
  "@agentbox/sandbox-daytona": "0.0.0",
67
- "@agentbox/sandbox-docker": "0.0.0",
68
68
  "@agentbox/sandbox-core": "0.0.0",
69
- "@agentbox/sandbox-hetzner": "0.0.0",
70
- "@agentbox/sandbox-vercel": "0.0.0"
69
+ "@agentbox/sandbox-docker": "0.0.0",
70
+ "@agentbox/sandbox-e2b": "0.0.0",
71
+ "@agentbox/sandbox-vercel": "0.0.0",
72
+ "@agentbox/sandbox-hetzner": "0.0.0"
71
73
  },
72
74
  "scripts": {
73
75
  "build": "tsup",
@@ -268,16 +268,20 @@ RUN npm install -g opencode-ai
268
268
  # the noble transition. If the base image moves back to jammy these will
269
269
  # need the un-suffixed names.
270
270
  #
271
- # 2. Get an actual Chromium binary. `agent-browser install` would normally
272
- # download Chrome for Testing, but Chrome for Testing has no Linux ARM64
273
- # build (Apple Silicon hosts run linux/arm64 containers) and Noble's
274
- # `chromium-browser` apt package is a snap stub that won't run inside a
275
- # container. The reliable cross-arch source is Playwright's bundled
276
- # Chromium, so we install playwright globally just for its
277
- # `playwright install chromium` downloader, then point agent-browser at
278
- # the resulting binary via AGENT_BROWSER_EXECUTABLE_PATH. agent-browser
279
- # honours that env var (priority: CLI flag > env > config file >
280
- # built-in default; see agent-browser's README).
271
+ # 2. Provide a Chromium *lazily*, shared with the project's Playwright. We do
272
+ # NOT bake a Chromium download into the image: Playwright pins an exact
273
+ # build per playwright version, so a baked browser goes stale the instant a
274
+ # project pins a different Playwright its `playwright install` fetches a
275
+ # different build and anything waiting on the baked path hangs. Instead,
276
+ # `AGENT_BROWSER_EXECUTABLE_PATH` points at the `chromium-resolver` script
277
+ # (installed as /usr/local/bin/chromium below), which reuses the project's
278
+ # Playwright Chromium when present and otherwise installs one on first use
279
+ # with the project's pinned Playwright (matching build), falling back to the
280
+ # box's global Playwright. agent-browser honours that env var (priority: CLI
281
+ # flag > env > config file > built-in default; see agent-browser's README).
282
+ # Chrome-for-Testing has no linux/arm64 build and Noble's chromium apt
283
+ # package is a snap stub, so Playwright's downloader stays the only reliable
284
+ # cross-arch source — we just invoke it lazily instead of at build time.
281
285
  #
282
286
  # Runtime state (sessions, auth cookies under ~/.agent-browser/) lives in the
283
287
  # container's writable layer, preserved across pause/unpause/stop/start and
@@ -290,6 +294,8 @@ RUN apt-get update \
290
294
  fonts-liberation xdg-utils \
291
295
  && rm -rf /var/lib/apt/lists/*
292
296
 
297
+ # agent-browser + a global playwright (the latter is only the lazy fallback
298
+ # Chromium downloader for projects that don't pin their own Playwright).
293
299
  RUN npm install -g agent-browser playwright
294
300
 
295
301
  # Portless CLI (https://portless.sh). Only the client — the box never runs the
@@ -299,22 +305,11 @@ RUN npm install -g agent-browser playwright
299
305
  # Requires Node 24+ — hence the setup_24.x bump above.
300
306
  RUN npm install -g portless
301
307
 
302
- # Download Chromium as `vscode` so the ms-playwright cache lands in vscode's
303
- # home (the user agent-browser runs as). The downloaded binary lives at
304
- # `chromium-XXXX/chrome-linux*/chrome`, where XXXX is a Playwright-internal
305
- # revision number that changes between releases — and the inner dir is
306
- # `chrome-linux` for old releases and `chrome-linux64` (or `chrome-linux/arm64`)
307
- # for current Chrome-for-Testing builds. Glob both. We resolve once and write
308
- # a stable symlink so AGENT_BROWSER_EXECUTABLE_PATH can point at something
309
- # predictable.
310
- USER vscode
311
- RUN playwright install chromium \
312
- && CHROME_BIN="$(ls /home/vscode/.cache/ms-playwright/chromium-*/chrome-linux*/chrome 2>/dev/null | sort | tail -1)" \
313
- && test -n "$CHROME_BIN" \
314
- && ln -sf "$CHROME_BIN" /tmp/chromium-link \
315
- && test -x "$(readlink /tmp/chromium-link)"
316
- USER root
317
- RUN mv /tmp/chromium-link /usr/local/bin/chromium
308
+ # /usr/local/bin/chromium is a resolver (not a baked binary): on first launch it
309
+ # reuses the project's Playwright Chromium or installs one on demand, then execs
310
+ # it. See the script header and the rationale in the browser-support note above.
311
+ COPY packages/sandbox-docker/scripts/chromium-resolver /usr/local/bin/chromium
312
+ RUN chmod +x /usr/local/bin/chromium
318
313
 
319
314
  ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/local/bin/chromium
320
315
 
@@ -48,7 +48,42 @@ Look at `/workspace`:
48
48
  - No cycles in `needs:`.
49
49
  - **Always generate a dependency-install task** and make it the root of the `needs:` graph (every service that needs deps gets `needs: [install, …]`). Future boxes start from a snapshot of the final filesystem so they won't need this, but updates or moving to a cloud provider might need to rebuild the container from scratch. The filesystem can be then later captured by `agentbox-ctl checkpoint --set-default`. The task must be **idempotent and self-healing**: `agentbox-ctl` re-runs pending tasks on every box stop/start (the daemon dies with the container and is relaunched), so a plain `rm -rf node_modules && install` would wipe + reinstall on every start. Guard the rebuild with a marker file *inside* `node_modules` (the `.agentbox-installed` convention AgentBox uses internally): rebuild only when the marker is absent (fresh box), and be a fast no-op once it exists. Detect the package manager from the lockfile — never hardcode `pnpm`. See the worked example below.
50
50
  - **Add a comment to the beginning** of the file to explain what you did and what issues you encountered, so that future run might use this information in case the project evolves and you need to update the agentbox.yaml file.
51
- -
51
+
52
+ ### Stateful services: data persistence & re-seeding (read this for databases)
53
+
54
+ **A checkpoint does NOT capture docker-in-docker data.** `agentbox checkpoint` is a `docker commit` of the box's writable filesystem (the system + `/workspace`). The in-box `dockerd` keeps its storage in a *separate* per-box volume (`/var/lib/docker`), which is **not** part of that image — it's fresh on every new box and wiped on `agentbox destroy`. So a database or cache you run as a **docker container** (e.g. `docker run … postgres`) starts **empty on every new box** created from a checkpoint (every `agentbox claude` / `agentbox create`), even though `/workspace` and any marker files you wrote were restored. (A DB run as a **native process** with its data dir on the box filesystem — e.g. `postgres -D /var/lib/postgresql/data` — *is* captured by the checkpoint, since it lives in the writable layer.)
55
+
56
+ **Consequence for migrate/seed tasks of a containerized DB: do not gate them on a filesystem marker.** A marker like `node_modules/.agentbox-installed` is correct for deps (they live in `/workspace`, which the checkpoint captures), but **wrong** for DB data living in a docker volume: the marker is restored from the checkpoint while the DB is empty, so a marker-guarded seed wrongly skips and the app boots against an empty database. Instead, **gate on the actual data** — connect to the DB and check whether a sentinel table/row exists, and seed only when it's missing:
57
+
58
+ ```yaml
59
+ seed:
60
+ # Re-seed when the DB is empty. The postgres data lives in the in-box
61
+ # docker volume, which is NOT captured by `agentbox checkpoint` — so a box
62
+ # started from a checkpoint has the workspace warm but an empty DB. We can't
63
+ # use a filesystem marker here (it would be restored while the DB is blank);
64
+ # instead probe the DB and seed only if the data is absent. Fast no-op once
65
+ # the data is present.
66
+ command: |
67
+ set -e
68
+ export PGPASSWORD=postgres
69
+ # Probe for existing data. If the table is missing the query errors,
70
+ # stderr is suppressed, stdout is empty, the grep fails — so we seed.
71
+ if psql -h 127.0.0.1 -p 5432 -U postgres -d app -tAc \
72
+ "SELECT EXISTS (SELECT 1 FROM users LIMIT 1)" 2>/dev/null | grep -q t; then
73
+ echo "data present — skip seed"
74
+ exit 0
75
+ fi
76
+ pnpm db:seed
77
+ needs: [install, migrate]
78
+ ```
79
+
80
+ **Lifecycle nuance (this is why the data check, not a marker, is right):**
81
+
82
+ - **Box stop → start** (`agentbox stop`/`start`): the supervisor daemon dies with the container and is relaunched, so it **re-runs all tasks** from `pending`. The per-box docker volume *does* survive stop/start, so the DB still has data — the data check makes the seed a fast no-op.
83
+ - **New box from a checkpoint** (`agentbox claude`/`create`): tasks run and the DB volume is empty → the check fails → the seed runs. Correct.
84
+ - **Resume after pause** (`agentbox pause`/`unpause`): the daemon is frozen and thawed, **not** restarted, so tasks do **not** re-run at all — nothing to seed, the running DB is untouched.
85
+
86
+ (Migrations are usually safe to re-run as-is: migration tools track applied migrations in their own table, which on a fresh box is empty, so they simply re-apply. Only the *data* seed needs the existence check.) Install the DB client the seed/migrate tasks need (e.g. `postgresql-client`) in the `install` task — don't `docker exec` the DB container for these checks (nested `docker exec` fails inside a box with a `setns` error); reach it over TCP with the client tools instead.
52
87
 
53
88
  ## 3. Wire readiness probes (services only)
54
89
 
@@ -184,6 +219,7 @@ Tell the user (verbatim):
184
219
  ## 9. Checkpoint the warm state - DON't SKIP THIS STEP
185
220
 
186
221
  Checkpoint (snapshot) this box writable layer: once the box is warmed up (deps installed, services ready), checkpoint it with `agentbox-ctl checkpoint --name setup --replace --set-default` so future boxes start ready.
222
+ Remember the checkpoint captures the writable layer (`/workspace` + system), **not** docker-in-docker volumes — so a containerized DB's data does not carry into new boxes. That's expected; the data-existence-gated seed task from section 2 re-seeds those automatically. (If you need the data itself to persist into new boxes, run the DB as a native process with its data dir on the box filesystem, or bind a `/workspace` path as the container's data volume so it lands in the checkpoint.)
187
223
  Run this command exactly once. The `--name setup --replace` makes it idempotent — if it ever needs to run again it overwrites the existing `setup` checkpoint instead of stacking duplicates.
188
224
  On all providers except Vercel, this doesn't need to be confirmed by the user. It will pause the container for several seconds so warn the user about it and write Done when it's done.
189
225
  On Vercel: this actually STOPS the sandbox, so warn the user about it. Also the system will ask confirmation.
@@ -3344,15 +3344,15 @@ var require_windows = __commonJS({
3344
3344
  }
3345
3345
  return false;
3346
3346
  }
3347
- function checkStat(stat2, path6, options) {
3348
- if (!stat2.isSymbolicLink() && !stat2.isFile()) {
3347
+ function checkStat(stat3, path6, options) {
3348
+ if (!stat3.isSymbolicLink() && !stat3.isFile()) {
3349
3349
  return false;
3350
3350
  }
3351
3351
  return checkPathExt(path6, options);
3352
3352
  }
3353
3353
  function isexe(path6, options, cb) {
3354
- fs.stat(path6, function(er, stat2) {
3355
- cb(er, er ? false : checkStat(stat2, path6, options));
3354
+ fs.stat(path6, function(er, stat3) {
3355
+ cb(er, er ? false : checkStat(stat3, path6, options));
3356
3356
  });
3357
3357
  }
3358
3358
  function sync(path6, options) {
@@ -3369,20 +3369,20 @@ var require_mode = __commonJS({
3369
3369
  isexe.sync = sync;
3370
3370
  var fs = require("fs");
3371
3371
  function isexe(path6, options, cb) {
3372
- fs.stat(path6, function(er, stat2) {
3373
- cb(er, er ? false : checkStat(stat2, options));
3372
+ fs.stat(path6, function(er, stat3) {
3373
+ cb(er, er ? false : checkStat(stat3, options));
3374
3374
  });
3375
3375
  }
3376
3376
  function sync(path6, options) {
3377
3377
  return checkStat(fs.statSync(path6), options);
3378
3378
  }
3379
- function checkStat(stat2, options) {
3380
- return stat2.isFile() && checkMode(stat2, options);
3379
+ function checkStat(stat3, options) {
3380
+ return stat3.isFile() && checkMode(stat3, options);
3381
3381
  }
3382
- function checkMode(stat2, options) {
3383
- var mod = stat2.mode;
3384
- var uid = stat2.uid;
3385
- var gid = stat2.gid;
3382
+ function checkMode(stat3, options) {
3383
+ var mod = stat3.mode;
3384
+ var uid = stat3.uid;
3385
+ var gid = stat3.gid;
3386
3386
  var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
3387
3387
  var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
3388
3388
  var u2 = parseInt("100", 8);
@@ -12239,11 +12239,11 @@ var replacements = Object.entries(specialMainSymbols);
12239
12239
  // ../../node_modules/.pnpm/yoctocolors@2.1.2/node_modules/yoctocolors/base.js
12240
12240
  var import_node_tty = __toESM(require("tty"), 1);
12241
12241
  var hasColors = import_node_tty.default?.WriteStream?.prototype?.hasColors?.() ?? false;
12242
- var format = (open, close) => {
12242
+ var format = (open2, close) => {
12243
12243
  if (!hasColors) {
12244
12244
  return (input) => input;
12245
12245
  }
12246
- const openCode = `\x1B[${open}m`;
12246
+ const openCode = `\x1B[${open2}m`;
12247
12247
  const closeCode = `\x1B[${close}m`;
12248
12248
  return (input) => {
12249
12249
  const string = input + "";
@@ -18472,8 +18472,8 @@ var KEY_REGISTRY = [
18472
18472
  {
18473
18473
  key: "box.provider",
18474
18474
  type: "enum",
18475
- enumValues: ["docker", "daytona", "hetzner", "vercel"],
18476
- description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, or Vercel Sandboxes."
18475
+ enumValues: ["docker", "daytona", "hetzner", "vercel", "e2b"],
18476
+ description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, Vercel Sandboxes, or E2B microVMs."
18477
18477
  },
18478
18478
  {
18479
18479
  key: "box.hostSnapshot",
@@ -18509,6 +18509,12 @@ var KEY_REGISTRY = [
18509
18509
  description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
18510
18510
  advanced: true
18511
18511
  },
18512
+ {
18513
+ key: "box.defaultCheckpointE2b",
18514
+ type: "string",
18515
+ description: "Per-provider override of `box.defaultCheckpoint` for e2b. Wins over the global when set; set via `agentbox checkpoint set-default --provider e2b`.",
18516
+ advanced: true
18517
+ },
18512
18518
  {
18513
18519
  key: "box.size",
18514
18520
  type: "string",
@@ -18538,6 +18544,12 @@ var KEY_REGISTRY = [
18538
18544
  description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
18539
18545
  advanced: true
18540
18546
  },
18547
+ {
18548
+ key: "box.sizeE2b",
18549
+ type: "string",
18550
+ description: "Per-provider override of `box.size` for e2b. Reserved \u2014 e2b sizing is template-level (set at `agentbox prepare --provider e2b` time via --vcpus / --memory).",
18551
+ advanced: true
18552
+ },
18541
18553
  {
18542
18554
  key: "checkpoint.maxLayers",
18543
18555
  type: "int",
@@ -18609,6 +18621,12 @@ var KEY_REGISTRY = [
18609
18621
  description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
18610
18622
  advanced: true
18611
18623
  },
18624
+ {
18625
+ key: "box.imageE2b",
18626
+ type: "string",
18627
+ description: "Per-provider override of `box.image` for e2b (template id or `name:tag`, e.g. `agentbox-base:latest`). Written by `agentbox prepare --provider e2b`.",
18628
+ advanced: true
18629
+ },
18612
18630
  {
18613
18631
  key: "box.imageRegistry",
18614
18632
  type: "string",
@@ -18690,7 +18708,12 @@ var KEY_REGISTRY = [
18690
18708
  key: "attach.openIn",
18691
18709
  type: "enum",
18692
18710
  enumValues: ["split", "window", "tab", "same"],
18693
- description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux or iTerm2: `split` (tmux split-window / iTerm2 vertical split, default), `window` (tmux new-window / new iTerm2 window), `tab` (tmux new-window / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/iTerm2 every value behaves like `same`."
18711
+ description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux, cmux, or iTerm2: `split` (tmux split-window / cmux new-split / iTerm2 vertical split, default \u2014 same workspace), `window` (tmux new-window / cmux new-workspace / new iTerm2 window), `tab` (tmux new-window / cmux new-surface tab in the current pane, same workspace / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/cmux/iTerm2 every value behaves like `same`."
18712
+ },
18713
+ {
18714
+ key: "attach.cmuxStatus",
18715
+ type: "bool",
18716
+ description: "When attached inside cmux, reflect the box agent's live activity on its cmux workspace (colour + description: blue=working, amber=needs input, idle clears; restored on detach) and, when the agent needs input, flag the box's own tab via a cmux notification (tab badge + reorder + desktop notification) so it stands out among sibling tabs. cmux only; no-op in other terminals."
18694
18717
  },
18695
18718
  {
18696
18719
  key: "code.ide",
@@ -19539,6 +19562,10 @@ async function resolveCloudBackend(name) {
19539
19562
  const pkg = "@agentbox/sandbox-vercel";
19540
19563
  return loadCloudBackend(pkg, async () => (await import(pkg)).vercelBackend);
19541
19564
  }
19565
+ if (name === "e2b") {
19566
+ const pkg = "@agentbox/sandbox-e2b";
19567
+ return loadCloudBackend(pkg, async () => (await import(pkg)).e2bBackend);
19568
+ }
19542
19569
  throw new Error(`no host executor for cloud backend '${name}'`);
19543
19570
  }
19544
19571
  async function loadCloudBackend(pkg, load2) {
@@ -21092,6 +21119,8 @@ async function startRelayServer(opts) {
21092
21119
  return handle;
21093
21120
  }
21094
21121
  var QUEUE_DIR = (0, import_path6.join)(STATE_DIR, "queue");
21122
+ var TERMINAL_RETENTION_MS = 60 * 60 * 1e3;
21123
+ var SWEEP_INTERVAL_MS = 60 * 1e3;
21095
21124
  var QUEUE_LOGS_DIR = (0, import_path6.join)(STATE_DIR, "logs");
21096
21125
 
21097
21126
  // src/config.ts
@@ -23,10 +23,22 @@ mkdir -p "$HOME/.vnc"
23
23
  # VncAuth truncates >8 chars at compare time, which is fine — the host writes
24
24
  # an 8-char password. Write to a temp file + rename so a failure (e.g.,
25
25
  # vncpasswd missing) doesn't leave an empty file that Xvnc would then reject.
26
- TMP_PASSWD="$(mktemp "$HOME/.vnc/passwd.XXXXXX")"
27
- printf '%s\n' "$PASS" | vncpasswd -f > "$TMP_PASSWD"
28
- chmod 600 "$TMP_PASSWD"
29
- mv "$TMP_PASSWD" "$HOME/.vnc/passwd"
26
+ #
27
+ # Debian 12 (E2B base) doesn't package vncpasswd at all — tigervnc-tools is
28
+ # Ubuntu-only. When vncpasswd is missing, fall back to `-SecurityTypes None`
29
+ # and rely on the cloud provider's signed preview URL as the access boundary
30
+ # (same effective model: holding the URL = holding the credential). Other
31
+ # providers (docker, hetzner, daytona, vercel) keep VncAuth.
32
+ VNC_SECURITY_ARGS=(-SecurityTypes VncAuth -PasswordFile "$HOME/.vnc/passwd")
33
+ if command -v vncpasswd >/dev/null 2>&1; then
34
+ TMP_PASSWD="$(mktemp "$HOME/.vnc/passwd.XXXXXX")"
35
+ printf '%s\n' "$PASS" | vncpasswd -f > "$TMP_PASSWD"
36
+ chmod 600 "$TMP_PASSWD"
37
+ mv "$TMP_PASSWD" "$HOME/.vnc/passwd"
38
+ else
39
+ echo "agentbox-vnc-start: vncpasswd not installed; starting Xvnc with -SecurityTypes None (preview URL is the access boundary)" >&2
40
+ VNC_SECURITY_ARGS=(-SecurityTypes None)
41
+ fi
30
42
 
31
43
  mkdir -p /var/log/agentbox 2>/dev/null || true
32
44
 
@@ -37,8 +49,7 @@ mkdir -p /var/log/agentbox 2>/dev/null || true
37
49
  # accept cut-text from noVNC, set both the X CLIPBOARD and PRIMARY selections.
38
50
  Xvnc :1 \
39
51
  -localhost \
40
- -SecurityTypes VncAuth \
41
- -PasswordFile "$HOME/.vnc/passwd" \
52
+ "${VNC_SECURITY_ARGS[@]}" \
42
53
  -geometry 1280x800 \
43
54
  -depth 24 \
44
55
  -AlwaysShared \
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ # agentbox: resolve the Chromium that agent-browser drives.
3
+ #
4
+ # We deliberately do NOT bake a Chromium into the box image. Playwright pins an
5
+ # exact Chromium build per playwright version, so a baked browser goes stale the
6
+ # moment a project pins a different Playwright — the project's `playwright
7
+ # install` then fetches a *different* build, and anything waiting on the baked
8
+ # one (a hard-coded ms-playwright path) hangs forever. Instead we reuse the
9
+ # *project's* Playwright Chromium — the exact build its own tests use — so
10
+ # agent-browser and Playwright share a single binary that is always correct.
11
+ #
12
+ # `AGENT_BROWSER_EXECUTABLE_PATH=/usr/local/bin/chromium` points at this script,
13
+ # so every agent-browser launch runs it. Resolution is effectively cached: once
14
+ # a build exists the hot path is just an `ls` + `exec` (no download).
15
+ #
16
+ # 1. newest installed Playwright Chromium under ~/.cache/ms-playwright; else
17
+ # 2. install one with the project's pinned Playwright (matching build), or the
18
+ # box's global Playwright as a fallback when the project has none; then
19
+ # 3. exec the real binary with the args agent-browser passed.
20
+ #
21
+ # Chrome-for-Testing has no linux/arm64 build and Noble's chromium apt package
22
+ # is a snap stub, so Playwright's downloader stays the only reliable cross-arch
23
+ # source — we just invoke it lazily, with the project's version, instead of
24
+ # baking a fixed one.
25
+
26
+ newest_chrome() {
27
+ ls -d "$HOME"/.cache/ms-playwright/chromium-*/chrome-linux*/chrome 2>/dev/null | sort -V | tail -1
28
+ }
29
+
30
+ real="$(newest_chrome)"
31
+ if [ -z "$real" ]; then
32
+ echo "agentbox: no Chromium yet; installing via Playwright (one-time)..." >&2
33
+ # Serialize concurrent first-launches: without a lock, two simultaneous
34
+ # agent-browser launches would both run `playwright install` into the same
35
+ # ~/.cache/ms-playwright, and one could exec a half-extracted binary. flock
36
+ # makes the second waiter re-check the cache and skip the redundant install.
37
+ mkdir -p "$HOME/.cache" 2>/dev/null
38
+ exec 9>"$HOME/.cache/agentbox-chromium-install.lock"
39
+ flock 9
40
+ real="$(newest_chrome)" # another launch may have installed it while we waited
41
+ if [ -z "$real" ]; then
42
+ if [ -x /workspace/node_modules/.bin/playwright ]; then
43
+ ( cd /workspace && ./node_modules/.bin/playwright install chromium ) >&2
44
+ else
45
+ playwright install chromium >&2
46
+ fi
47
+ real="$(newest_chrome)"
48
+ fi
49
+ flock -u 9
50
+ fi
51
+
52
+ if [ -z "$real" ] || [ ! -x "$real" ]; then
53
+ echo "agentbox: could not resolve a Chromium binary (Playwright install failed?)." >&2
54
+ exit 127
55
+ fi
56
+
57
+ exec "$real" "$@"
@@ -1,5 +1,6 @@
1
1
  {
2
- "$comment": "AgentBox enterprise-managed Claude Code settings, baked into the box image at /etc/claude-code/managed-settings.json. Highest precedence and NOT synced from the host ~/.claude, so claude-hooks-filter.ts never touches it; per Claude Code, hook arrays MERGE across settings sources, so the user's own hooks still run. These hooks report Claude's activity to the box supervisor (agentbox-ctl claude-state -> ctl socket -> relay -> ~/.agentbox/boxes/<id>/status.json) so `agentbox status/list/inspect` can show it even when the box is paused. Each command is exit-0 fast and shell-backgrounded so a hook can never block or fail a Claude turn. The ExitPlanMode / AskUserQuestion entries run SYNCHRONOUSLY (no &) because they consume the hook's stdin payload; the catchall PreToolUse 'working' hook races with them, but the supervisor's sticky-state semantics swallow that race (a 'working' set while in end-plan/question is ignored unless --clear-pending is set, which only the matching PostToolUse hook passes).",
2
+ "$comment": "AgentBox enterprise-managed Claude Code settings, baked into the box image at /etc/claude-code/managed-settings.json. Highest precedence and NOT synced from the host ~/.claude, so claude-hooks-filter.ts never touches it; per Claude Code, hook arrays MERGE across settings sources, so the user's own hooks still run. These hooks report Claude's activity to the box supervisor (agentbox-ctl claude-state -> ctl socket -> relay -> ~/.agentbox/boxes/<id>/status.json) so `agentbox status/list/inspect` can show it even when the box is paused. Each command is exit-0 fast and shell-backgrounded so a hook can never block or fail a Claude turn. The ExitPlanMode / AskUserQuestion entries run SYNCHRONOUSLY (no &) because they consume the hook's stdin payload; the catchall PreToolUse 'working' hook races with them, but the supervisor's sticky-state semantics swallow that race (a 'working' set while in end-plan/question is ignored unless --clear-pending is set, which only the matching PostToolUse hook passes). skipDangerousModePermissionPrompt pre-accepts the bypass-permissions disclaimer at policy precedence — AgentBox boxes are isolated, and `agentbox claude` defaults to --dangerously-skip-permissions, so the one-time accept gate would just trap every fresh box.",
3
+ "skipDangerousModePermissionPrompt": true,
3
4
  "hooks": {
4
5
  "UserPromptSubmit": [
5
6
  {
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-`docker commit` cleanup: strip ephemeral / disposable state so the
3
+ # captured checkpoint image is closer to "warm project state, nothing else".
4
+ #
5
+ # Invoked by the host via `docker exec --user root <container>
6
+ # /usr/local/bin/agentbox-checkpoint-cleanup` right before
7
+ # `docker commit`. Best-effort: every step is allowed to fail (a checkpoint
8
+ # capture should never block on cleanup hiccups).
9
+ #
10
+ # What we DELIBERATELY keep:
11
+ # - /workspace the actual point of the checkpoint
12
+ # - /home/vscode/.npm warm npm cache (next install is fast)
13
+ # - /home/vscode/.cache pnpm/yarn/Cargo/etc. caches
14
+ # - /var/lib/docker in-box dockerd's data root
15
+ # - /home/vscode/.claude the named volume is bind-mounted; image
16
+ # layer never sees it anyway
17
+ set +e
18
+
19
+ # apt: drop downloaded .deb cache and the package index. The index is ~50MB
20
+ # and gets refreshed on the next `apt-get update`; the .deb cache is reusable
21
+ # only if we don't change versions, which we usually do.
22
+ apt-get clean 2>/dev/null
23
+ rm -rf /var/lib/apt/lists/* 2>/dev/null
24
+
25
+ # Throwaway scratch dirs. Preserve /tmp/claude-* — that is the live in-box
26
+ # Claude Code session's working tree (its per-task stdout/stderr files). The
27
+ # agent that triggered this checkpoint *is* that session; deleting its task
28
+ # output mid-run makes its harness see ENOENT, treat the command as failed,
29
+ # and retry the checkpoint (observed: 5 duplicate auto-named checkpoints).
30
+ # Stale claude-* dirs baked into the image are tiny and Claude Code prunes
31
+ # them itself on the next session start.
32
+ find /tmp /var/tmp -mindepth 1 -maxdepth 1 ! -name 'claude-*' -exec rm -rf {} + 2>/dev/null
33
+
34
+ # Logs: truncate (don't delete) so the original file modes / ownerships stay
35
+ # intact for the next run. Targets common rotated archives too.
36
+ find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
37
+ -exec truncate -s0 {} + 2>/dev/null
38
+ find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
39
+
40
+ # Bash history (root + vscode). Re-assert vscode ownership: `: >` run as root
41
+ # (re)creates the file root-owned 0644 when it didn't exist, which the uid-1000
42
+ # vscode user cannot append to, silently dropping all shell history.
43
+ : > /root/.bash_history 2>/dev/null
44
+ : > /home/vscode/.bash_history 2>/dev/null
45
+ chown vscode:vscode /home/vscode/.bash_history 2>/dev/null
46
+ chmod 600 /home/vscode/.bash_history 2>/dev/null
47
+
48
+ # Anthropic's installer writes a transient marker; redundant once the binary
49
+ # is in place. Safe to wipe.
50
+ rm -rf /home/vscode/.claude-installer 2>/dev/null
51
+
52
+ exit 0
@@ -0,0 +1,68 @@
1
+ {
2
+ "$comment": "Codex 0.134.0 expects `~/.codex/hooks.json` to be `{ hooks: { Event: [...] } }` (matching the `HooksFile` Rust struct), NOT a top-level event map. The `hooks` feature flag must also be enabled (`codex --enable hooks`) and hook trust must be either persisted via the in-TUI dialog or bypassed at launch (`--dangerously-bypass-hook-trust`). startCodexSession() does both. In practice the hook firing on the JSON-config path is still unreliable in 0.134.0 (TUI mode skips them on at least some startup paths) — the real mechanism that lights up state in production is the tmux-pane scraper in packages/ctl/src/codex-scraper.ts. These hooks remain as a defense-in-depth seed so any future codex build that fixes the firing also lights up state without further work.",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "hooks": [
7
+ { "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
8
+ ]
9
+ }
10
+ ],
11
+ "UserPromptSubmit": [
12
+ {
13
+ "hooks": [
14
+ { "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
15
+ ]
16
+ }
17
+ ],
18
+ "PreToolUse": [
19
+ {
20
+ "hooks": [
21
+ { "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
22
+ ]
23
+ }
24
+ ],
25
+ "PermissionRequest": [
26
+ {
27
+ "hooks": [
28
+ { "type": "command", "command": "agentbox-ctl codex-state waiting >/dev/null 2>&1 &", "timeout": 3 }
29
+ ]
30
+ }
31
+ ],
32
+ "PreCompact": [
33
+ {
34
+ "hooks": [
35
+ { "type": "command", "command": "agentbox-ctl codex-state compacting >/dev/null 2>&1 &", "timeout": 3 }
36
+ ]
37
+ }
38
+ ],
39
+ "PostCompact": [
40
+ {
41
+ "hooks": [
42
+ { "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
43
+ ]
44
+ }
45
+ ],
46
+ "SubagentStart": [
47
+ {
48
+ "hooks": [
49
+ { "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
50
+ ]
51
+ }
52
+ ],
53
+ "SubagentStop": [
54
+ {
55
+ "hooks": [
56
+ { "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
57
+ ]
58
+ }
59
+ ],
60
+ "Stop": [
61
+ {
62
+ "hooks": [
63
+ { "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
64
+ ]
65
+ }
66
+ ]
67
+ }
68
+ }
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ # Routes in-box URL opens to `agentbox-ctl open`, which opens the link in the
3
+ # box's own Chromium (agent-browser, visible via `agentbox screen`) and asks
4
+ # the host user — in the footer/dashboard — whether to also open it on the
5
+ # host. This script is installed at /usr/local/bin (earlier in PATH than
6
+ # xdg-utils' /usr/bin/xdg-open, which it is also symlinked over) and is the
7
+ # box's $BROWSER, so `xdg-open`, Claude Code's OAuth flow, `gh`,
8
+ # `git web--browse`, python's webbrowser, etc. all land here.
9
+ #
10
+ # Only http(s) URLs are routed. Anything else (a file path, another scheme)
11
+ # falls through to the real xdg-open, which resolves it locally in the box.
12
+
13
+ set -uo pipefail
14
+
15
+ target="${1:-}"
16
+
17
+ case "$target" in
18
+ http://* | https://*)
19
+ exec agentbox-ctl open "$target"
20
+ ;;
21
+ *)
22
+ if [[ -x /usr/bin/xdg-open ]]; then
23
+ exec /usr/bin/xdg-open "$@"
24
+ fi
25
+ echo "agentbox-open: not an http(s) URL: $target" >&2
26
+ exit 1
27
+ ;;
28
+ esac