@madarco/agentbox 0.8.0 → 0.10.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 +89 -0
- package/README.md +161 -0
- package/dist/{_cloud-attach-T727ZPRV.js → _cloud-attach-O6NYTLES.js} +4 -4
- package/dist/{chunk-67N47KUS.js → chunk-2GPORKYF.js} +349 -182
- package/dist/chunk-2GPORKYF.js.map +1 -0
- package/dist/{chunk-6OZDFNBF.js → chunk-7UIAO7PC.js} +401 -82
- package/dist/chunk-7UIAO7PC.js.map +1 -0
- package/dist/{chunk-BGK32PZE.js → chunk-KL36BRN4.js} +2 -2
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-FODMEHD3.js → chunk-R4O5WPHW.js} +705 -77
- package/dist/chunk-R4O5WPHW.js.map +1 -0
- package/dist/{dist-ZODPD2I6.js → dist-5FQGYRW5.js} +20 -10
- package/dist/dist-5FQGYRW5.js.map +1 -0
- package/dist/{dist-LOZBWMBF.js → dist-BQNX7RQE.js} +19 -3
- package/dist/dist-PZW3GWWU.js +874 -0
- package/dist/dist-PZW3GWWU.js.map +1 -0
- package/dist/{dist-L4LCG5SJ.js → dist-TMHSUVTP.js} +4 -4
- package/dist/index.js +2385 -842
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js → prepared-state-CL4CWXQA-H5THETIM.js} +2 -2
- package/package.json +11 -7
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +9 -8
- package/runtime/docker/packages/ctl/dist/bin.cjs +129 -31
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- package/runtime/hetzner/agentbox-setup-skill.md +9 -8
- package/runtime/hetzner/agentbox-vnc-start +15 -1
- package/runtime/hetzner/ctl.cjs +129 -31
- package/runtime/relay/bin.cjs +260 -39
- package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
- package/runtime/vercel/agentbox-codex-hooks.json +68 -0
- package/runtime/vercel/agentbox-open +28 -0
- package/runtime/vercel/agentbox-setup-skill.md +197 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23495 -0
- package/runtime/vercel/custom-system-CLAUDE.md +47 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +314 -0
- package/share/agentbox-setup/SKILL.md +9 -8
- package/dist/chunk-67N47KUS.js.map +0 -1
- package/dist/chunk-6OZDFNBF.js.map +0 -1
- package/dist/chunk-BGK32PZE.js.map +0 -1
- package/dist/chunk-FODMEHD3.js.map +0 -1
- package/dist/dist-ZODPD2I6.js.map +0 -1
- /package/dist/{_cloud-attach-T727ZPRV.js.map → _cloud-attach-O6NYTLES.js.map} +0 -0
- /package/dist/{dist-LOZBWMBF.js.map → dist-BQNX7RQE.js.map} +0 -0
- /package/dist/{dist-L4LCG5SJ.js.map → dist-TMHSUVTP.js.map} +0 -0
- /package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js.map → prepared-state-CL4CWXQA-H5THETIM.js.map} +0 -0
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
readPreparedDockerState,
|
|
7
7
|
resolveContextFiles,
|
|
8
8
|
writePreparedDockerState
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KL36BRN4.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-CL4CWXQA-
|
|
18
|
+
//# sourceMappingURL=prepared-state-CL4CWXQA-H5THETIM.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madarco/agentbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -37,11 +37,13 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"dist",
|
|
39
39
|
"share",
|
|
40
|
-
"runtime"
|
|
40
|
+
"runtime",
|
|
41
|
+
"CHANGELOG.md"
|
|
41
42
|
],
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@clack/prompts": "^0.9.0",
|
|
44
45
|
"@daytonaio/sdk": "^0.179.0",
|
|
46
|
+
"@vercel/sandbox": "^2.0.1",
|
|
45
47
|
"@xterm/headless": "^5.5.0",
|
|
46
48
|
"commander": "^12.1.0",
|
|
47
49
|
"execa": "^9.5.2",
|
|
@@ -57,14 +59,15 @@
|
|
|
57
59
|
"typescript": "^5.7.2",
|
|
58
60
|
"vitest": "^2.1.8",
|
|
59
61
|
"@agentbox/config": "0.0.0",
|
|
60
|
-
"@agentbox/relay": "0.0.0",
|
|
61
62
|
"@agentbox/ctl": "0.0.0",
|
|
62
|
-
"@agentbox/core": "0.0.0",
|
|
63
|
-
"@agentbox/sandbox-cloud": "0.0.0",
|
|
64
63
|
"@agentbox/sandbox-core": "0.0.0",
|
|
65
|
-
"@agentbox/
|
|
64
|
+
"@agentbox/core": "0.0.0",
|
|
65
|
+
"@agentbox/relay": "0.0.0",
|
|
66
66
|
"@agentbox/sandbox-daytona": "0.0.0",
|
|
67
|
-
"@agentbox/sandbox-
|
|
67
|
+
"@agentbox/sandbox-docker": "0.0.0",
|
|
68
|
+
"@agentbox/sandbox-cloud": "0.0.0",
|
|
69
|
+
"@agentbox/sandbox-hetzner": "0.0.0",
|
|
70
|
+
"@agentbox/sandbox-vercel": "0.0.0"
|
|
68
71
|
},
|
|
69
72
|
"scripts": {
|
|
70
73
|
"build": "tsup",
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
"test": "vitest run",
|
|
74
77
|
"typecheck": "tsc --noEmit",
|
|
75
78
|
"clean": "rm -rf dist runtime .turbo",
|
|
79
|
+
"version": "git add CHANGELOG.md",
|
|
76
80
|
"publish:patch": "npm version patch && git push --follow-tags",
|
|
77
81
|
"publish:minor": "npm version minor && git push --follow-tags"
|
|
78
82
|
}
|
|
@@ -164,14 +164,7 @@ services:
|
|
|
164
164
|
- A service with `restart: never` and an autostart dependency will block the dependent forever after one failed run — usually a mistake.
|
|
165
165
|
- `command:` is either a shell string (run via `bash -c`) or an argv array. Use the argv form if you need to avoid shell quoting.
|
|
166
166
|
|
|
167
|
-
## 8.
|
|
168
|
-
|
|
169
|
-
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.
|
|
170
|
-
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.
|
|
171
|
-
This doesn't need to be confirmed by the user.
|
|
172
|
-
It will pause the container for several seconds so warn the user about it and write Done when it's done.
|
|
173
|
-
|
|
174
|
-
## 9. Hand-off
|
|
167
|
+
## 8. Hand-off
|
|
175
168
|
|
|
176
169
|
Tell the user (verbatim):
|
|
177
170
|
|
|
@@ -187,6 +180,14 @@ Tell the user (verbatim):
|
|
|
187
180
|
your box is ready, you can start more sessions with `agentbox claude`
|
|
188
181
|
you can access the web app at https://<boxname>.localhost
|
|
189
182
|
|
|
183
|
+
|
|
184
|
+
## 9. Checkpoint the warm state - DON't SKIP THIS STEP
|
|
185
|
+
|
|
186
|
+
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.
|
|
187
|
+
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
|
+
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
|
+
On Vercel: this actually STOPS the sandbox, so warn the user about it. Also the system will ask confirmation.
|
|
190
|
+
|
|
190
191
|
## 10. Known issues
|
|
191
192
|
|
|
192
193
|
- For Nextjs/Vite/Tasnstack projects, makes sure to forward also websocket for hot reload.
|
|
@@ -18469,8 +18469,8 @@ var KEY_REGISTRY = [
|
|
|
18469
18469
|
{
|
|
18470
18470
|
key: "box.provider",
|
|
18471
18471
|
type: "enum",
|
|
18472
|
-
enumValues: ["docker", "daytona", "hetzner"],
|
|
18473
|
-
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes,
|
|
18472
|
+
enumValues: ["docker", "daytona", "hetzner", "vercel"],
|
|
18473
|
+
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, or Vercel Sandboxes."
|
|
18474
18474
|
},
|
|
18475
18475
|
{
|
|
18476
18476
|
key: "box.hostSnapshot",
|
|
@@ -18500,6 +18500,12 @@ var KEY_REGISTRY = [
|
|
|
18500
18500
|
description: "Per-provider override of `box.defaultCheckpoint` for hetzner. Wins over the global when set; set via `agentbox checkpoint set-default --provider hetzner`.",
|
|
18501
18501
|
advanced: true
|
|
18502
18502
|
},
|
|
18503
|
+
{
|
|
18504
|
+
key: "box.defaultCheckpointVercel",
|
|
18505
|
+
type: "string",
|
|
18506
|
+
description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
|
|
18507
|
+
advanced: true
|
|
18508
|
+
},
|
|
18503
18509
|
{
|
|
18504
18510
|
key: "checkpoint.maxLayers",
|
|
18505
18511
|
type: "int",
|
|
@@ -18573,16 +18579,41 @@ var KEY_REGISTRY = [
|
|
|
18573
18579
|
type: "int",
|
|
18574
18580
|
description: "Cap git bundle history shipped to cloud sandboxes (daytona, hetzner). 0 = full history. Unset = adaptive default (last 200 commits; re-bundle at 100 if the bundle exceeds 20 MB). Ignored for docker (which bind-mounts .git/)."
|
|
18575
18581
|
},
|
|
18582
|
+
{
|
|
18583
|
+
key: "box.vercelVcpus",
|
|
18584
|
+
type: "int",
|
|
18585
|
+
description: "vCPUs for new --provider vercel boxes (Vercel couples RAM at 2048 MB/vCPU). Default 2. Vercel only accepts specific counts (e.g. 1, 2, 4, 8) \u2014 an unsupported value fails create with a 400. Vercel-only; ignored by other providers."
|
|
18586
|
+
},
|
|
18587
|
+
{
|
|
18588
|
+
key: "box.vercelTimeoutMs",
|
|
18589
|
+
type: "int",
|
|
18590
|
+
description: "Max session length (ms) for new --provider vercel boxes before the VM auto-snapshots; persistent mode auto-resumes on the next call. Default 2700000 (45 min, the Hobby ceiling). Vercel-only."
|
|
18591
|
+
},
|
|
18592
|
+
{
|
|
18593
|
+
key: "box.vercelNetworkPolicy",
|
|
18594
|
+
type: "string",
|
|
18595
|
+
description: "Egress lock for new --provider vercel boxes: 'allow-all' (default, unset), 'deny-all', or a comma-separated domain allowlist (e.g. 'github.com,*.npmjs.org') that denies everything else. Vercel-only; ignored by other providers."
|
|
18596
|
+
},
|
|
18576
18597
|
{
|
|
18577
18598
|
key: "claude.sessionName",
|
|
18578
18599
|
type: "string",
|
|
18579
18600
|
description: "tmux session name for `agentbox claude`."
|
|
18580
18601
|
},
|
|
18602
|
+
{
|
|
18603
|
+
key: "claude.dangerouslySkipPermissions",
|
|
18604
|
+
type: "bool",
|
|
18605
|
+
description: "Launch claude in new boxes with --dangerously-skip-permissions (auto-accept tool use). Safe because boxes are isolated; on by default. Override per-box with --no-dangerously-skip-permissions."
|
|
18606
|
+
},
|
|
18581
18607
|
{
|
|
18582
18608
|
key: "codex.sessionName",
|
|
18583
18609
|
type: "string",
|
|
18584
18610
|
description: "tmux session name for `agentbox codex`."
|
|
18585
18611
|
},
|
|
18612
|
+
{
|
|
18613
|
+
key: "codex.dangerouslySkipPermissions",
|
|
18614
|
+
type: "bool",
|
|
18615
|
+
description: "Launch codex in new boxes with --dangerously-bypass-approvals-and-sandbox (never prompt for approval). Safe because boxes are isolated; on by default. Override per-box with --no-dangerously-skip-permissions."
|
|
18616
|
+
},
|
|
18586
18617
|
{
|
|
18587
18618
|
key: "opencode.sessionName",
|
|
18588
18619
|
type: "string",
|
|
@@ -18690,6 +18721,21 @@ var KEY_REGISTRY = [
|
|
|
18690
18721
|
type: "int",
|
|
18691
18722
|
description: "Max number of simultaneously-running boxes (across providers) before background `-i` jobs queue up instead of starting immediately. Per-invocation override: `--max-running <n>`."
|
|
18692
18723
|
},
|
|
18724
|
+
{
|
|
18725
|
+
key: "queue.maxWorking",
|
|
18726
|
+
type: "int",
|
|
18727
|
+
description: "Max agents actively working/thinking (quota-consuming) at once before background `-i` jobs queue. 0 = disabled (use the queue.maxConcurrent running-box gate). Counts all boxes, foreground + queued. Per-invocation override: `--max-working <n>`."
|
|
18728
|
+
},
|
|
18729
|
+
{
|
|
18730
|
+
key: "queue.idleGraceSeconds",
|
|
18731
|
+
type: "int",
|
|
18732
|
+
description: "Seconds an agent must stay non-working before it frees its working slot (debounce against brief idle flaps between turns). Only used when queue.maxWorking > 0."
|
|
18733
|
+
},
|
|
18734
|
+
{
|
|
18735
|
+
key: "cloud.useCurrentBranch",
|
|
18736
|
+
type: "bool",
|
|
18737
|
+
description: "On cloud providers (daytona/hetzner), start new boxes on the host's current branch instead of forking a new agentbox/<box-name> branch. Overridden by an explicit --use-branch / --from-branch."
|
|
18738
|
+
},
|
|
18693
18739
|
{
|
|
18694
18740
|
key: "maintenance.pruneProjectConfigs",
|
|
18695
18741
|
type: "bool",
|
|
@@ -19119,6 +19165,23 @@ function isGhPrOp(value) {
|
|
|
19119
19165
|
return GH_PR_OPS.includes(value);
|
|
19120
19166
|
}
|
|
19121
19167
|
var GH_PR_READ_ONLY_OPS = /* @__PURE__ */ new Set(["view", "list"]);
|
|
19168
|
+
function injectPrCreateHead(op, branch, args) {
|
|
19169
|
+
if (op !== "create") return args;
|
|
19170
|
+
if (!branch || branch === "HEAD") return args;
|
|
19171
|
+
if (hasHeadArg(args)) return args;
|
|
19172
|
+
return ["--head", branch, ...args];
|
|
19173
|
+
}
|
|
19174
|
+
function hasHeadArg(args) {
|
|
19175
|
+
return args.some((a2) => a2 === "--head" || a2.startsWith("--head=") || a2.startsWith("-H"));
|
|
19176
|
+
}
|
|
19177
|
+
function prCreateNeedsHead(op, args) {
|
|
19178
|
+
return op === "create" && !hasHeadArg(args);
|
|
19179
|
+
}
|
|
19180
|
+
var PR_CREATE_NO_HEAD_REFUSAL = {
|
|
19181
|
+
exitCode: 65,
|
|
19182
|
+
stdout: "",
|
|
19183
|
+
stderr: "gh pr create: refusing to run without --head \u2014 could not resolve this box's branch, and falling back to the host repo's checked-out branch would open a PR for the wrong branch. Ensure the box branch is pushed, or pass --head <branch> explicitly.\n"
|
|
19184
|
+
};
|
|
19122
19185
|
var GH_RPC_TIMEOUT_MS = 12e4;
|
|
19123
19186
|
var GH_READY_CACHE_TTL_MS = 6e4;
|
|
19124
19187
|
var ghReadyCache;
|
|
@@ -19320,36 +19383,31 @@ var BoxStatusStore = class {
|
|
|
19320
19383
|
async function resolveCloudBackend(name) {
|
|
19321
19384
|
if (name === "daytona") {
|
|
19322
19385
|
const pkg = "@agentbox/sandbox-daytona";
|
|
19323
|
-
|
|
19324
|
-
const mod = await import(pkg);
|
|
19325
|
-
return mod.daytonaBackend;
|
|
19326
|
-
} catch (err) {
|
|
19327
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19328
|
-
if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {
|
|
19329
|
-
throw new Error(
|
|
19330
|
-
`relay: cannot load '${pkg}' at runtime \u2014 install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`
|
|
19331
|
-
);
|
|
19332
|
-
}
|
|
19333
|
-
throw err;
|
|
19334
|
-
}
|
|
19386
|
+
return loadCloudBackend(pkg, async () => (await import(pkg)).daytonaBackend);
|
|
19335
19387
|
}
|
|
19336
19388
|
if (name === "hetzner") {
|
|
19337
19389
|
const pkg = "@agentbox/sandbox-hetzner";
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {
|
|
19344
|
-
throw new Error(
|
|
19345
|
-
`relay: cannot load '${pkg}' at runtime \u2014 install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`
|
|
19346
|
-
);
|
|
19347
|
-
}
|
|
19348
|
-
throw err;
|
|
19349
|
-
}
|
|
19390
|
+
return loadCloudBackend(pkg, async () => (await import(pkg)).hetznerBackend);
|
|
19391
|
+
}
|
|
19392
|
+
if (name === "vercel") {
|
|
19393
|
+
const pkg = "@agentbox/sandbox-vercel";
|
|
19394
|
+
return loadCloudBackend(pkg, async () => (await import(pkg)).vercelBackend);
|
|
19350
19395
|
}
|
|
19351
19396
|
throw new Error(`no host executor for cloud backend '${name}'`);
|
|
19352
19397
|
}
|
|
19398
|
+
async function loadCloudBackend(pkg, load2) {
|
|
19399
|
+
try {
|
|
19400
|
+
return await load2();
|
|
19401
|
+
} catch (err) {
|
|
19402
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19403
|
+
if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {
|
|
19404
|
+
throw new Error(
|
|
19405
|
+
`relay: cannot load '${pkg}' at runtime \u2014 install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`
|
|
19406
|
+
);
|
|
19407
|
+
}
|
|
19408
|
+
throw err;
|
|
19409
|
+
}
|
|
19410
|
+
}
|
|
19353
19411
|
async function refreshCloudPreviewUrl(backendName, boxId, port) {
|
|
19354
19412
|
try {
|
|
19355
19413
|
const backend = await resolveCloudBackend(backendName);
|
|
@@ -19477,7 +19535,20 @@ async function runGhPrRpc(action, deps) {
|
|
|
19477
19535
|
}
|
|
19478
19536
|
}
|
|
19479
19537
|
}
|
|
19480
|
-
|
|
19538
|
+
let finalArgs = args;
|
|
19539
|
+
if (op === "create" && !args.some((a2) => a2 === "--head" || a2.startsWith("--head="))) {
|
|
19540
|
+
const backend = await resolveCloudBackend(deps.backendName);
|
|
19541
|
+
const handle = { sandboxId: lookup.cloudSandboxId };
|
|
19542
|
+
const containerPath = params.path ?? "/workspace";
|
|
19543
|
+
const branchProbe = await backend.exec(
|
|
19544
|
+
handle,
|
|
19545
|
+
`git -C ${shellQuote(containerPath)} rev-parse --abbrev-ref HEAD`
|
|
19546
|
+
);
|
|
19547
|
+
const branch = branchProbe.exitCode === 0 ? (branchProbe.stdout ?? "").trim() : "";
|
|
19548
|
+
finalArgs = injectPrCreateHead(op, branch, args);
|
|
19549
|
+
}
|
|
19550
|
+
if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
|
|
19551
|
+
return runHostGh(["pr", op, ...finalArgs], lookup.workspacePath);
|
|
19481
19552
|
}
|
|
19482
19553
|
async function runBrowserOpenMirror(action, deps) {
|
|
19483
19554
|
const params = action.params ?? {};
|
|
@@ -19596,6 +19667,18 @@ async function runCheckpointRpc(action, deps) {
|
|
|
19596
19667
|
stderr: "relay: AGENTBOX_CLI_ENTRY not set; cannot run checkpoint host-side\n"
|
|
19597
19668
|
};
|
|
19598
19669
|
}
|
|
19670
|
+
if (deps.backendName === "vercel" && deps.prompts && deps.subscribers && deps.subscribers.forBox(deps.boxId).length > 0) {
|
|
19671
|
+
const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, {
|
|
19672
|
+
kind: "confirm",
|
|
19673
|
+
message: `Create checkpoint on ${deps.boxName ?? deps.boxId}? The vercel box will stop and reboot.`,
|
|
19674
|
+
detail: params.name ? `checkpoint: ${params.name}` : "(auto-named)",
|
|
19675
|
+
defaultAnswer: "n",
|
|
19676
|
+
context: { command: "checkpoint create", argv: params.name ? [params.name] : [] }
|
|
19677
|
+
});
|
|
19678
|
+
if (verdict.answer !== "y") {
|
|
19679
|
+
return { exitCode: 10, stdout: "", stderr: "checkpoint denied by user\n" };
|
|
19680
|
+
}
|
|
19681
|
+
}
|
|
19599
19682
|
const argv = [process.execPath, entry, "checkpoint", "create", deps.boxId];
|
|
19600
19683
|
if (params.name) argv.push("--name", params.name);
|
|
19601
19684
|
if (params.merged === true) argv.push("--merged");
|
|
@@ -19947,7 +20030,18 @@ function createRelayServer(opts) {
|
|
|
19947
20030
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "relay"}`);
|
|
19948
20031
|
const route = `${req.method ?? "GET"} ${url.pathname}`;
|
|
19949
20032
|
if (route === "GET /healthz") {
|
|
19950
|
-
send(res, 200, {
|
|
20033
|
+
send(res, 200, {
|
|
20034
|
+
ok: true,
|
|
20035
|
+
boxes: registry.size(),
|
|
20036
|
+
events: events.size(),
|
|
20037
|
+
pid: process.pid,
|
|
20038
|
+
cliEntry: Boolean(process.env.AGENTBOX_CLI_ENTRY),
|
|
20039
|
+
// The spawning CLI's version/commit (inherited via env at spawn time).
|
|
20040
|
+
// `version` lets host-side ensureRelay reclaim a relay left over from a
|
|
20041
|
+
// different agentbox version; `commit` is observability-only.
|
|
20042
|
+
version: process.env.AGENTBOX_CLI_VERSION || void 0,
|
|
20043
|
+
commit: process.env.AGENTBOX_CLI_COMMIT || void 0
|
|
20044
|
+
});
|
|
19951
20045
|
return;
|
|
19952
20046
|
}
|
|
19953
20047
|
if (url.pathname.startsWith("/bridge/")) {
|
|
@@ -20605,7 +20699,9 @@ async function handleGhPrRpc(op, reg, params, prompts, subscribers, hostInitiate
|
|
|
20605
20699
|
return { exitCode: 10, stdout: "", stderr: "denied by user\n" };
|
|
20606
20700
|
}
|
|
20607
20701
|
}
|
|
20608
|
-
|
|
20702
|
+
const finalArgs = injectPrCreateHead(op, worktree.branch, args);
|
|
20703
|
+
if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
|
|
20704
|
+
return runHostGh(["pr", op, ...finalArgs], worktree.hostMainRepo);
|
|
20609
20705
|
}
|
|
20610
20706
|
async function handleCpRpc(reg, method, params) {
|
|
20611
20707
|
const entry = process.env.AGENTBOX_CLI_ENTRY;
|
|
@@ -21888,6 +21984,7 @@ var Supervisor = class extends import_node_events15.EventEmitter {
|
|
|
21888
21984
|
super();
|
|
21889
21985
|
this.opts = opts;
|
|
21890
21986
|
this.relay = new RelayClient();
|
|
21987
|
+
this.webProxy = new WebProxy(opts.webProxyPort);
|
|
21891
21988
|
}
|
|
21892
21989
|
opts;
|
|
21893
21990
|
units = /* @__PURE__ */ new Map();
|
|
@@ -21897,7 +21994,7 @@ var Supervisor = class extends import_node_events15.EventEmitter {
|
|
|
21897
21994
|
scheduling = false;
|
|
21898
21995
|
rescheduleDirty = false;
|
|
21899
21996
|
relay;
|
|
21900
|
-
webProxy
|
|
21997
|
+
webProxy;
|
|
21901
21998
|
/** The relay client the supervisor pushes state events on. Shared with the
|
|
21902
21999
|
* status reporter so both use the same fire-and-forget channel. */
|
|
21903
22000
|
get relayClient() {
|
|
@@ -22824,7 +22921,8 @@ function resolveBoxRelayPort() {
|
|
|
22824
22921
|
}
|
|
22825
22922
|
var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supervisor in the foreground").option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).option("--config <path>", "path to agentbox.yaml", DEFAULT_CONFIG_PATH).option("--log-dir <path>", "where per-service log files are written", DEFAULT_LOG_DIR).option("--workspace <path>", "cwd for service processes", "/workspace").action(async (opts) => {
|
|
22826
22923
|
const cfg = await loadConfig(opts.config);
|
|
22827
|
-
const
|
|
22924
|
+
const webProxyPort = Number(process.env.AGENTBOX_WEB_PROXY_PORT) || void 0;
|
|
22925
|
+
const sup = new Supervisor({ workspace: opts.workspace, logDir: opts.logDir, webProxyPort });
|
|
22828
22926
|
await sup.init(cfg);
|
|
22829
22927
|
const reporter = new StatusReporter({
|
|
22830
22928
|
supervisor: sup,
|
|
@@ -65,11 +65,25 @@ if ! pgrep -u "$(id -u)" -x autocutsel >/dev/null; then
|
|
|
65
65
|
DISPLAY=:1 autocutsel -selection PRIMARY -fork >/dev/null 2>&1 || true
|
|
66
66
|
fi
|
|
67
67
|
|
|
68
|
+
# noVNC's static assets live at different paths per base image: Debian/Ubuntu
|
|
69
|
+
# (docker, hetzner) ship them at /usr/share/novnc via apt; the AL2023 bake
|
|
70
|
+
# (vercel) git-clones them to /usr/local/share/novnc. websockify runs
|
|
71
|
+
# os.chdir(--web) at startup, so a wrong path makes it FileNotFoundError and
|
|
72
|
+
# never bind 6080 — pick the first dir that exists.
|
|
73
|
+
NOVNC_WEB=""
|
|
74
|
+
for _d in /usr/share/novnc /usr/local/share/novnc; do
|
|
75
|
+
if [[ -d "$_d" ]]; then NOVNC_WEB="$_d"; break; fi
|
|
76
|
+
done
|
|
77
|
+
if [[ -z "$NOVNC_WEB" ]]; then
|
|
78
|
+
echo "agentbox-vnc-start: noVNC assets not found (looked in /usr/share/novnc, /usr/local/share/novnc)" >&2
|
|
79
|
+
exit 65
|
|
80
|
+
fi
|
|
81
|
+
|
|
68
82
|
# websockify serves noVNC at /vnc.html (--web) and tunnels WS frames to Xvnc's
|
|
69
83
|
# RFB. Bind 0.0.0.0:6080 so both Docker `-p` mappings and OrbStack's
|
|
70
84
|
# <name>.orb.local routing reach it.
|
|
71
85
|
websockify \
|
|
72
|
-
--web
|
|
86
|
+
--web="$NOVNC_WEB" \
|
|
73
87
|
0.0.0.0:6080 \
|
|
74
88
|
127.0.0.1:5901 \
|
|
75
89
|
>/var/log/agentbox/websockify.log 2>&1 &
|
|
@@ -164,14 +164,7 @@ services:
|
|
|
164
164
|
- A service with `restart: never` and an autostart dependency will block the dependent forever after one failed run — usually a mistake.
|
|
165
165
|
- `command:` is either a shell string (run via `bash -c`) or an argv array. Use the argv form if you need to avoid shell quoting.
|
|
166
166
|
|
|
167
|
-
## 8.
|
|
168
|
-
|
|
169
|
-
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.
|
|
170
|
-
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.
|
|
171
|
-
This doesn't need to be confirmed by the user.
|
|
172
|
-
It will pause the container for several seconds so warn the user about it and write Done when it's done.
|
|
173
|
-
|
|
174
|
-
## 9. Hand-off
|
|
167
|
+
## 8. Hand-off
|
|
175
168
|
|
|
176
169
|
Tell the user (verbatim):
|
|
177
170
|
|
|
@@ -187,6 +180,14 @@ Tell the user (verbatim):
|
|
|
187
180
|
your box is ready, you can start more sessions with `agentbox claude`
|
|
188
181
|
you can access the web app at https://<boxname>.localhost
|
|
189
182
|
|
|
183
|
+
|
|
184
|
+
## 9. Checkpoint the warm state - DON't SKIP THIS STEP
|
|
185
|
+
|
|
186
|
+
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.
|
|
187
|
+
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
|
+
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
|
+
On Vercel: this actually STOPS the sandbox, so warn the user about it. Also the system will ask confirmation.
|
|
190
|
+
|
|
190
191
|
## 10. Known issues
|
|
191
192
|
|
|
192
193
|
- For Nextjs/Vite/Tasnstack projects, makes sure to forward also websocket for hot reload.
|
|
@@ -65,11 +65,25 @@ if ! pgrep -u "$(id -u)" -x autocutsel >/dev/null; then
|
|
|
65
65
|
DISPLAY=:1 autocutsel -selection PRIMARY -fork >/dev/null 2>&1 || true
|
|
66
66
|
fi
|
|
67
67
|
|
|
68
|
+
# noVNC's static assets live at different paths per base image: Debian/Ubuntu
|
|
69
|
+
# (docker, hetzner) ship them at /usr/share/novnc via apt; the AL2023 bake
|
|
70
|
+
# (vercel) git-clones them to /usr/local/share/novnc. websockify runs
|
|
71
|
+
# os.chdir(--web) at startup, so a wrong path makes it FileNotFoundError and
|
|
72
|
+
# never bind 6080 — pick the first dir that exists.
|
|
73
|
+
NOVNC_WEB=""
|
|
74
|
+
for _d in /usr/share/novnc /usr/local/share/novnc; do
|
|
75
|
+
if [[ -d "$_d" ]]; then NOVNC_WEB="$_d"; break; fi
|
|
76
|
+
done
|
|
77
|
+
if [[ -z "$NOVNC_WEB" ]]; then
|
|
78
|
+
echo "agentbox-vnc-start: noVNC assets not found (looked in /usr/share/novnc, /usr/local/share/novnc)" >&2
|
|
79
|
+
exit 65
|
|
80
|
+
fi
|
|
81
|
+
|
|
68
82
|
# websockify serves noVNC at /vnc.html (--web) and tunnels WS frames to Xvnc's
|
|
69
83
|
# RFB. Bind 0.0.0.0:6080 so both Docker `-p` mappings and OrbStack's
|
|
70
84
|
# <name>.orb.local routing reach it.
|
|
71
85
|
websockify \
|
|
72
|
-
--web
|
|
86
|
+
--web="$NOVNC_WEB" \
|
|
73
87
|
0.0.0.0:6080 \
|
|
74
88
|
127.0.0.1:5901 \
|
|
75
89
|
>/var/log/agentbox/websockify.log 2>&1 &
|