@madarco/agentbox 0.14.0 → 0.16.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.
- package/CHANGELOG.md +108 -0
- package/dist/{_cloud-attach-GUBB5RH2.js → _cloud-attach-5KJWOASL.js} +4 -4
- package/dist/{chunk-RSKG7AFU.js → chunk-3WCEB6RE.js} +2 -2
- package/dist/{chunk-XKH7NTT7.js → chunk-DBBUDKKB.js} +248 -5
- package/dist/chunk-DBBUDKKB.js.map +1 -0
- package/dist/{chunk-TCS5HXJX.js → chunk-GXJNJUEV.js} +1090 -527
- package/dist/chunk-GXJNJUEV.js.map +1 -0
- package/dist/{chunk-LDMYHWUS.js → chunk-NW2UZQV6.js} +10 -6
- package/dist/chunk-NW2UZQV6.js.map +1 -0
- package/dist/{chunk-TBSIJVSN.js → chunk-PIK47622.js} +37 -17
- package/dist/chunk-PIK47622.js.map +1 -0
- package/dist/{chunk-BKU34KYY.js → chunk-QXFNLKJJ.js} +9 -3
- package/dist/{chunk-BKU34KYY.js.map → chunk-QXFNLKJJ.js.map} +1 -1
- package/dist/{chunk-BYCLD6D6.js → chunk-SB4QTF2T.js} +98 -54
- package/dist/chunk-SB4QTF2T.js.map +1 -0
- package/dist/{chunk-VATTS2MR.js → chunk-SENASAU4.js} +10 -6
- package/dist/{chunk-VATTS2MR.js.map → chunk-SENASAU4.js.map} +1 -1
- package/dist/{dist-34RKQ74M.js → dist-4IQFJJQI.js} +5 -5
- package/dist/{dist-4DPOL5A7.js → dist-7YB7BMNG.js} +5 -5
- package/dist/{dist-3IMQNTTV.js → dist-SL2QSMBE.js} +5 -5
- package/dist/{dist-J2IHD5T7.js → dist-VHI5QOSQ.js} +6 -6
- package/dist/{dist-57M6ZA7H.js → dist-XC47DSCR.js} +5 -5
- package/dist/index.js +1043 -333
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js → prepared-state-MQHD3M5F-2LANTRL7.js} +2 -2
- package/package.json +6 -5
- package/runtime/docker/Dockerfile.box +21 -2
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +112 -29
- package/runtime/docker/packages/ctl/dist/bin.cjs +10353 -8575
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +5 -2
- package/runtime/docker/packages/sandbox-docker/scripts/linear-shim +181 -0
- package/runtime/docker/packages/sandbox-docker/scripts/ntn-shim +95 -0
- package/runtime/e2b/agentbox-checkpoint-cleanup +5 -2
- package/runtime/e2b/agentbox-setup-skill.md +112 -29
- package/runtime/e2b/ctl.cjs +10353 -8575
- package/runtime/e2b/linear-shim +181 -0
- package/runtime/e2b/ntn-shim +95 -0
- package/runtime/e2b/scripts/build-template.sh +13 -7
- package/runtime/hetzner/agentbox-checkpoint-cleanup +5 -2
- package/runtime/hetzner/agentbox-setup-skill.md +112 -29
- package/runtime/hetzner/ctl.cjs +10353 -8575
- package/runtime/hetzner/linear-shim +181 -0
- package/runtime/hetzner/ntn-shim +95 -0
- package/runtime/hetzner/scripts/install-box.sh +19 -9
- package/runtime/relay/bin.cjs +3707 -2828
- package/runtime/vercel/agentbox-checkpoint-cleanup +5 -2
- package/runtime/vercel/agentbox-setup-skill.md +112 -29
- package/runtime/vercel/ctl.cjs +10353 -8575
- package/runtime/vercel/linear-shim +181 -0
- package/runtime/vercel/ntn-shim +95 -0
- package/runtime/vercel/scripts/provision.sh +13 -7
- package/share/agentbox-setup/SKILL.md +112 -29
- package/share/host-skills/agentbox-info/SKILL.md +22 -2
- package/dist/chunk-BYCLD6D6.js.map +0 -1
- package/dist/chunk-LDMYHWUS.js.map +0 -1
- package/dist/chunk-TBSIJVSN.js.map +0 -1
- package/dist/chunk-TCS5HXJX.js.map +0 -1
- package/dist/chunk-XKH7NTT7.js.map +0 -1
- /package/dist/{_cloud-attach-GUBB5RH2.js.map → _cloud-attach-5KJWOASL.js.map} +0 -0
- /package/dist/{chunk-RSKG7AFU.js.map → chunk-3WCEB6RE.js.map} +0 -0
- /package/dist/{dist-34RKQ74M.js.map → dist-4IQFJJQI.js.map} +0 -0
- /package/dist/{dist-4DPOL5A7.js.map → dist-7YB7BMNG.js.map} +0 -0
- /package/dist/{dist-3IMQNTTV.js.map → dist-SL2QSMBE.js.map} +0 -0
- /package/dist/{dist-J2IHD5T7.js.map → dist-VHI5QOSQ.js.map} +0 -0
- /package/dist/{dist-57M6ZA7H.js.map → dist-XC47DSCR.js.map} +0 -0
- /package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js.map → prepared-state-MQHD3M5F-2LANTRL7.js.map} +0 -0
|
@@ -32,10 +32,13 @@ rm -rf /var/lib/apt/lists/* 2>/dev/null
|
|
|
32
32
|
find /tmp /var/tmp -mindepth 1 -maxdepth 1 ! -name 'claude-*' -exec rm -rf {} + 2>/dev/null
|
|
33
33
|
|
|
34
34
|
# Logs: truncate (don't delete) so the original file modes / ownerships stay
|
|
35
|
-
# intact for the next run. Targets common rotated archives too.
|
|
35
|
+
# intact for the next run. Targets common rotated archives too. Exclude the
|
|
36
|
+
# `state/` subdir — it holds the idempotent-task marker fallback (used when
|
|
37
|
+
# /var/lib/agentbox isn't writable) and must survive the checkpoint.
|
|
36
38
|
find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
|
|
39
|
+
-not -path '/var/log/agentbox/state/*' -exec truncate -s0 {} + 2>/dev/null
|
|
40
|
+
find /var/log/agentbox -type f -not -path '/var/log/agentbox/state/*' \
|
|
37
41
|
-exec truncate -s0 {} + 2>/dev/null
|
|
38
|
-
find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
|
|
39
42
|
|
|
40
43
|
# Bash history (root + vscode). Re-assert vscode ownership: `: >` run as root
|
|
41
44
|
# (re)creates the file root-owned 0644 when it didn't exist, which the uid-1000
|
|
@@ -46,35 +46,56 @@ Look at `/workspace`:
|
|
|
46
46
|
- **Tasks** = one-shot. `pnpm install`, DB migrations, codegen, fixture loaders, install apt packages. Wire dependent services with `needs:` so they wait for the task to finish successfully.
|
|
47
47
|
- Names: must match `[A-Za-z0-9_-]+`. Task names and service names share a namespace — no collisions.
|
|
48
48
|
- No cycles in `needs:`.
|
|
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
|
|
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**: `agentbox-ctl` re-runs pending tasks on every box stop/start (the daemon dies with the container and is relaunched), so an unguarded install would reinstall on every start. The clean way is the **`run_once: true`** field — the supervisor stores a marker keyed by a hash of the command and skips warm boots automatically (the marker lives at `/var/lib/agentbox/tasks/<name>`, on the box rootfs, captured by checkpoints, never polluting `/workspace`). Editing the command re-runs it. 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
52
|
### Stateful services: data persistence & re-seeding (read this for databases)
|
|
53
53
|
|
|
54
|
+
**Declare a containerized dependency with the `image:` service form** — AgentBox
|
|
55
|
+
generates the `docker start`-or-`run` shell (no hand-written `docker run … || docker
|
|
56
|
+
start …`). The container runs in the box's dockerd; a published port is reachable
|
|
57
|
+
from other in-box services at `127.0.0.1:<host port>`:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
services:
|
|
61
|
+
postgres:
|
|
62
|
+
image: # bare string (image: postgres:17-alpine) or a mapping:
|
|
63
|
+
name: postgres:17-alpine
|
|
64
|
+
ports: ["5432:5432"]
|
|
65
|
+
env:
|
|
66
|
+
POSTGRES_PASSWORD: postgres
|
|
67
|
+
POSTGRES_DB: app
|
|
68
|
+
args: "-c max_connections=200" # string or ["-c","max_connections=200"]
|
|
69
|
+
container_name: app_db # optional; default = service name
|
|
70
|
+
ready_when: { port: 5432 }
|
|
71
|
+
restart: always
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The container is reused by name across box stop/start. (Changing `image`/`env`
|
|
75
|
+
reuses the existing container as-is; `docker rm <container_name>` + `agentbox-ctl
|
|
76
|
+
reload` to apply.) Install the DB client the migrate/seed tasks need (e.g.
|
|
77
|
+
`postgresql-client`) in the `install` task and reach the DB over TCP — don't
|
|
78
|
+
`docker exec` the container (nested exec fails with a `setns` error in a box).
|
|
79
|
+
|
|
54
80
|
**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
81
|
|
|
56
|
-
**Consequence for migrate/seed tasks of a containerized DB: do
|
|
82
|
+
**Consequence for migrate/seed tasks of a containerized DB: do NOT use `run_once: true` (the marker form).** A command-hash marker 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 use the **`run_once: { check: <cmd> }`** form — the probe runs first and the seed runs unless the probe exits 0, and **no marker is written** (the DB is the source of truth). Gate on the actual data:
|
|
57
83
|
|
|
58
84
|
```yaml
|
|
59
85
|
seed:
|
|
60
|
-
# Re-seed when the DB is empty. The postgres data lives in the in-box
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
86
|
+
# Re-seed when the DB is empty. The postgres data lives in the in-box docker
|
|
87
|
+
# volume, which is NOT captured by `agentbox checkpoint` — so a box started
|
|
88
|
+
# from a checkpoint has the workspace warm but an empty DB. The marker form
|
|
89
|
+
# would be restored while the DB is blank and wrongly skip; the `check` probe
|
|
90
|
+
# gates on the data itself. Exit 0 = already seeded, skip. Fast no-op once
|
|
65
91
|
# 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
|
|
92
|
+
command: pnpm db:seed
|
|
77
93
|
needs: [install, migrate]
|
|
94
|
+
run_once:
|
|
95
|
+
check: |
|
|
96
|
+
export PGPASSWORD=postgres
|
|
97
|
+
psql -h 127.0.0.1 -p 5432 -U postgres -d app -tAc \
|
|
98
|
+
"SELECT EXISTS (SELECT 1 FROM users LIMIT 1)" 2>/dev/null | grep -q t
|
|
78
99
|
```
|
|
79
100
|
|
|
80
101
|
**Lifecycle nuance (this is why the data check, not a marker, is right):**
|
|
@@ -148,22 +169,19 @@ tasks:
|
|
|
148
169
|
# Idempotent install. /workspace is the container's writable filesystem, so
|
|
149
170
|
# node_modules persists across pause/stop/start and is captured by
|
|
150
171
|
# `agentbox checkpoint`. The host's node_modules is macOS-native and is
|
|
151
|
-
# never copied in, so
|
|
152
|
-
# on every subsequent box start (
|
|
153
|
-
#
|
|
154
|
-
# manager.
|
|
172
|
+
# never copied in, so the first Linux install runs; `run_once: true` then
|
|
173
|
+
# skips it on every subsequent box start (the supervisor stores a marker
|
|
174
|
+
# keyed by a hash of the command). Adjust the lockfile detection to the
|
|
175
|
+
# project's package manager.
|
|
155
176
|
install:
|
|
156
177
|
command: |
|
|
157
178
|
set -e
|
|
158
|
-
|
|
159
|
-
[ -f "$MARKER" ] && { echo "deps installed (marker present) — skip"; exit 0; }
|
|
160
|
-
apt-get update && apt-get install -y postgresql-client
|
|
161
|
-
rm -rf node_modules
|
|
179
|
+
sudo apt-get update && sudo apt-get install -y postgresql-client
|
|
162
180
|
if [ -f pnpm-lock.yaml ]; then
|
|
163
181
|
corepack enable >/dev/null 2>&1 || true
|
|
164
182
|
pnpm install --frozen-lockfile || pnpm install
|
|
165
183
|
fi
|
|
166
|
-
|
|
184
|
+
run_once: true
|
|
167
185
|
|
|
168
186
|
migrate:
|
|
169
187
|
command: pnpm db:migrate
|
|
@@ -192,6 +210,36 @@ services:
|
|
|
192
210
|
factor: 2
|
|
193
211
|
```
|
|
194
212
|
|
|
213
|
+
## 6b. Bringing extra host files/folders into the box
|
|
214
|
+
|
|
215
|
+
Two ways to copy host files in (both COPY — never a live mount, so the box can't
|
|
216
|
+
write back to the host):
|
|
217
|
+
|
|
218
|
+
- **`carry:` block** (declarative, in `agentbox.yaml`) — for files/dirs every box
|
|
219
|
+
should get at create time. Each entry is `{ src, dest }` with optional `mode`,
|
|
220
|
+
`user`, `optional`, and `exclude:` (a list of tar globs / bare dir names to drop
|
|
221
|
+
when copying a directory). Heavy regenerable dirs (`.git`, `node_modules`, `bin`,
|
|
222
|
+
`obj`, `packages`, `dist`, `.next`, `target`) are dropped by default; `exclude:`
|
|
223
|
+
is additive. Each carry entry is capped at `box.cpMaxBytes` (default 100 MiB
|
|
224
|
+
after excludes) — the same limit `agentbox cp` enforces.
|
|
225
|
+
- **`agentbox-ctl cp fromHost <hostPath> <boxPath>`** (ad-hoc, from inside the box)
|
|
226
|
+
— for a one-off copy. Prompts the user on the host to approve.
|
|
227
|
+
|
|
228
|
+
**The per-copy size limit (important for large/legacy folders).** A single copy is
|
|
229
|
+
blocked above `box.cpMaxBytes` (default **100 MB**) *after* default excludes, so it
|
|
230
|
+
fails loud instead of silently hanging. When blocked you get a `du`-style tree of
|
|
231
|
+
the biggest remaining folders/subfolders. To get under the limit, EITHER:
|
|
232
|
+
|
|
233
|
+
- **drop what the box can regenerate** (the default excludes already remove
|
|
234
|
+
`node_modules`/`.git`/build output; add more with `--exclude=<glob-or-name>`), OR
|
|
235
|
+
- **copy the heavy folders one at a time** so each copy is under the limit, OR
|
|
236
|
+
- pass `--yes` to copy the whole thing anyway (only when you really need it all).
|
|
237
|
+
|
|
238
|
+
Example: a 2.4 GB legacy folder is mostly `packages/` (NuGet) + `.git`; those are
|
|
239
|
+
excluded by default, and what's left can be split:
|
|
240
|
+
`agentbox-ctl cp fromHost ../legacy/src /workspace/legacy/src` then
|
|
241
|
+
`... cp fromHost ../legacy/Database /workspace/legacy/Database`.
|
|
242
|
+
|
|
195
243
|
## 7. Validate before handing off
|
|
196
244
|
|
|
197
245
|
- check with `agentbox-ctl reload` and then `agentbox-ctl status` that everything is running as expected.
|
|
@@ -228,6 +276,41 @@ On Vercel: this actually STOPS the sandbox, so warn the user about it. Also the
|
|
|
228
276
|
|
|
229
277
|
- For Nextjs/Vite/Tasnstack projects, makes sure to forward also websocket for hot reload.
|
|
230
278
|
|
|
231
|
-
- Service like flask, nextjs, BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL should use the
|
|
279
|
+
- Service like flask, nextjs, BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL should use the `<boxname>.localhost` url for the local development so that on the host it will use the same url as the box. Render this automatically instead of hand-writing `sed` — see section 6c.
|
|
280
|
+
|
|
281
|
+
- The `install` task above uses `run_once: true`, so it is a no-op on warm boots. Do **not** wrap it in a manual marker check too. To force a one-off rebuild, run `agentbox-ctl run-task install --force` (which bypasses the run_once marker), or edit the command (a changed command invalidates the hash and re-runs).
|
|
282
|
+
|
|
283
|
+
## 11. Pin URLs / render config files (env, secrets)
|
|
284
|
+
|
|
285
|
+
Many apps hard-code a hostname (e.g. `optima.localhost`) or read a gitignored `.env`. Instead of long `sed` commands in a task, use the built-ins:
|
|
286
|
+
|
|
287
|
+
- **`agentbox-ctl render <src>`** — a declarative `sed` for files already in the workspace. `--env` substitutes `{{AGENTBOX_*}}` placeholders; `--rules <name>` applies a named rule-set from the top-level `replacements:` block; `--rule 'from=>to'` / `--rule-regex 'pat=>repl'` are inline. Write to `--out <path>` (or `--in-place`). The whitelist placeholders are `{{AGENTBOX_BOX_NAME}}`, `{{AGENTBOX_BOX_HOST}}` (= `<boxname>.localhost`), `{{AGENTBOX_BOX_ID}}`, `{{AGENTBOX_BOX_KIND}}`, `{{AGENTBOX_HOST_WORKSPACE}}`, `{{AGENTBOX_PROJECT_ROOT}}`.
|
|
288
|
+
|
|
289
|
+
Render a gitignored `.env` from a committed `env.example` on every boot, pinning the URLs to this box:
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
replacements:
|
|
293
|
+
box-host:
|
|
294
|
+
- { from: 'optima\.localhost', to: '{{AGENTBOX_BOX_HOST}}', regex: true } # {{AGENTBOX_BOX_HOST}} = <box>.localhost
|
|
295
|
+
|
|
296
|
+
tasks:
|
|
297
|
+
env:
|
|
298
|
+
# The render is idempotent (the rules re-pin the same lines every boot), so
|
|
299
|
+
# no `run_once:` guard is needed — it self-corrects on a checkpoint-started
|
|
300
|
+
# box that carries a different box's host in .env.
|
|
301
|
+
command: agentbox-ctl render apps/saas/env.example --out apps/saas/.env --env --rules box-host
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Note: an `run_once: { check: <cmd> }` probe runs verbatim via `bash -c` with the box env — use shell vars like `$AGENTBOX_BOX_NAME`, NOT `{{…}}` placeholders (those are only expanded by `render`/carry, never by the supervisor).
|
|
305
|
+
|
|
306
|
+
**Generated secrets:** put `{{AGENTBOX_AUTO_SECRET}}` in the template for a value like `BETTER_AUTH_SECRET` instead of shelling out to `openssl rand`. Unnamed → a fresh 32-byte base64url secret each render (stable when you render the template→`.env` once). `{{AGENTBOX_AUTO_SECRET:better-auth}}` → generated once, persisted at `/var/lib/agentbox/secrets/<name>`, reused on every render (stable even if you render every boot). Example `env.example` line: `BETTER_AUTH_SECRET="{{AGENTBOX_AUTO_SECRET:better-auth}}"`.
|
|
307
|
+
|
|
308
|
+
- **`carry:` + `replaceEnvs`/`replace`/`rules`** — for a host-only file (e.g. a real `.env` with secrets that never lives in the repo), carry it in and render it host-side in one step (file entries only):
|
|
232
309
|
|
|
233
|
-
|
|
310
|
+
```yaml
|
|
311
|
+
carry:
|
|
312
|
+
- src: ~/secrets/optima.env
|
|
313
|
+
dest: /workspace/apps/saas/.env
|
|
314
|
+
replaceEnvs: true
|
|
315
|
+
rules: [box-host]
|
|
316
|
+
```
|