@madarco/agentbox 0.1.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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-IDR4HVIC.js +106 -0
  3. package/dist/chunk-IDR4HVIC.js.map +1 -0
  4. package/dist/chunk-J35IH7W5.js +200 -0
  5. package/dist/chunk-J35IH7W5.js.map +1 -0
  6. package/dist/chunk-O5HS3QHW.js +2164 -0
  7. package/dist/chunk-O5HS3QHW.js.map +1 -0
  8. package/dist/chunk-OOOKFFR5.js +496 -0
  9. package/dist/chunk-OOOKFFR5.js.map +1 -0
  10. package/dist/chunk-RWJE6AER.js +515 -0
  11. package/dist/chunk-RWJE6AER.js.map +1 -0
  12. package/dist/chunk-SOMIKEN2.js +1651 -0
  13. package/dist/chunk-SOMIKEN2.js.map +1 -0
  14. package/dist/create-LSSO7H4I-GWNALUMF.js +15 -0
  15. package/dist/create-LSSO7H4I-GWNALUMF.js.map +1 -0
  16. package/dist/index.js +4067 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/lifecycle-P4FSKGR2-3466P54Y.js +38 -0
  19. package/dist/lifecycle-P4FSKGR2-3466P54Y.js.map +1 -0
  20. package/dist/state-ZSP3ORXW-WI6KOIG3.js +26 -0
  21. package/dist/state-ZSP3ORXW-WI6KOIG3.js.map +1 -0
  22. package/dist/stats-GZFLPYTU-DBJ2DVBJ.js +19 -0
  23. package/dist/stats-GZFLPYTU-DBJ2DVBJ.js.map +1 -0
  24. package/package.json +73 -0
  25. package/runtime/docker/Dockerfile.box +316 -0
  26. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +188 -0
  27. package/runtime/docker/packages/ctl/dist/bin.cjs +12770 -0
  28. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +52 -0
  29. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +77 -0
  30. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +54 -0
  31. package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +21 -0
  32. package/runtime/relay/bin.cjs +11467 -0
  33. package/share/agentbox-setup/SKILL.md +188 -0
@@ -0,0 +1,496 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ConfigError,
4
+ VNC_CONTAINER_PORT,
5
+ WEB_CONTAINER_PORT,
6
+ buildClaudeMounts,
7
+ buildIdeMounts,
8
+ createBoxWorktree,
9
+ createSnapshot,
10
+ cursorServerVolumeName,
11
+ detectGitRepos,
12
+ dockerVolumeName,
13
+ ensureClaudeVolume,
14
+ ensureIdeVolumes,
15
+ ensureRelay,
16
+ generateRelayToken,
17
+ generateVncPassword,
18
+ launchCtlDaemon,
19
+ launchDockerdDaemon,
20
+ launchVncDaemon,
21
+ loadConfig,
22
+ mountOverlay,
23
+ registerBoxWithRelay,
24
+ rehydrateRelayRegistry,
25
+ repairIdeOwnership,
26
+ resolveClaudeVolume,
27
+ snapshotPathFor,
28
+ verifyOverlay,
29
+ vscodeServerVolumeName
30
+ } from "./chunk-O5HS3QHW.js";
31
+ import {
32
+ allocateProjectIndex,
33
+ readState,
34
+ recordBox
35
+ } from "./chunk-IDR4HVIC.js";
36
+ import {
37
+ CHECKPOINT_MOUNT,
38
+ CONTAINER_EXPORT_MERGED,
39
+ CONTAINER_EXPORT_UPPER,
40
+ DEFAULT_BOX_IMAGE,
41
+ DEFAULT_ENV_PATTERNS,
42
+ boxRunDirFor,
43
+ containerExists,
44
+ copyHostEnvFilesToBox,
45
+ dockerInfo,
46
+ dockerStorageDriver,
47
+ ensureImage,
48
+ ensureVolume,
49
+ publishedHostPort,
50
+ resolveCheckpointLower,
51
+ runBox
52
+ } from "./chunk-SOMIKEN2.js";
53
+
54
+ // ../../packages/sandbox-docker/dist/chunk-IGNL6VQG.js
55
+ import { randomBytes } from "crypto";
56
+ import { mkdir, stat } from "fs/promises";
57
+ import { homedir } from "os";
58
+ import { basename, join, resolve } from "path";
59
+ import { execa as execa2 } from "execa";
60
+ import { execa } from "execa";
61
+ async function writeBoxEnvFile(container, env) {
62
+ const body = formatBoxEnvBody(env);
63
+ const result = await execa(
64
+ "docker",
65
+ ["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
66
+ { input: body, reject: false }
67
+ );
68
+ if (result.exitCode !== 0) {
69
+ return {
70
+ ok: false,
71
+ reason: `docker exec failed (exit ${String(result.exitCode)}): ${(result.stderr ?? "").toString().slice(0, 400)}`
72
+ };
73
+ }
74
+ return { ok: true };
75
+ }
76
+ function formatBoxEnvBody(env) {
77
+ const lines = [];
78
+ for (const [k, v] of Object.entries(env)) {
79
+ lines.push(`${k}=${shellSingleQuote(v)}`);
80
+ }
81
+ return lines.join("\n") + "\n";
82
+ }
83
+ function shellSingleQuote(s) {
84
+ return `'${s.replace(/'/g, `'\\''`)}'`;
85
+ }
86
+ function persistableLimits(lim) {
87
+ if (!lim) return void 0;
88
+ const out = {};
89
+ if (lim.memoryBytes && lim.memoryBytes > 0) out.memoryBytes = Math.floor(lim.memoryBytes);
90
+ if (lim.cpus && lim.cpus > 0) out.cpus = lim.cpus;
91
+ if (lim.pidsLimit && lim.pidsLimit > 0) out.pidsLimit = Math.floor(lim.pidsLimit);
92
+ if (lim.disk) out.disk = lim.disk;
93
+ return Object.keys(out).length > 0 ? out : void 0;
94
+ }
95
+ function generateBoxId() {
96
+ return randomBytes(4).toString("hex");
97
+ }
98
+ function sanitizeBasename(workspacePath) {
99
+ const raw = basename(resolve(workspacePath));
100
+ return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
101
+ }
102
+ function defaultBoxName(workspacePath, id) {
103
+ const base = sanitizeBasename(workspacePath);
104
+ return base.length > 0 ? `${base}-${id}` : id;
105
+ }
106
+ async function pathExists(p) {
107
+ try {
108
+ await stat(p);
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+ async function buildIdentityMounts() {
115
+ const home = homedir();
116
+ const candidates = [
117
+ { src: join(home, ".codex"), dst: "/home/vscode/.codex", readOnly: false },
118
+ { src: join(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
119
+ ];
120
+ const out = [];
121
+ for (const c of candidates) {
122
+ if (await pathExists(c.src)) {
123
+ out.push(`${c.src}:${c.dst}${c.readOnly ? ":ro" : ""}`);
124
+ }
125
+ }
126
+ return out;
127
+ }
128
+ async function createBox(opts) {
129
+ const log = opts.onLog ?? (() => {
130
+ });
131
+ const workspace = resolve(opts.workspacePath);
132
+ if (!await pathExists(workspace)) {
133
+ throw new Error(`workspace does not exist: ${workspace}`);
134
+ }
135
+ const cfgPath = join(workspace, "agentbox.yaml");
136
+ if (await pathExists(cfgPath)) {
137
+ try {
138
+ const cfg = await loadConfig(cfgPath);
139
+ log(`agentbox.yaml validated (${String(cfg.services.length)} service(s))`);
140
+ } catch (err) {
141
+ if (err instanceof ConfigError) {
142
+ throw new Error(`agentbox.yaml validation failed:
143
+ ${err.message}`);
144
+ }
145
+ throw err;
146
+ }
147
+ }
148
+ await dockerInfo();
149
+ log("docker daemon reachable");
150
+ const imageRef = opts.image ?? DEFAULT_BOX_IMAGE;
151
+ const { built } = await ensureImage(imageRef, {
152
+ onProgress: (line) => log(`[image] ${line}`)
153
+ });
154
+ log(built ? `built image ${imageRef}` : `using cached image ${imageRef}`);
155
+ let relayUp = false;
156
+ try {
157
+ await ensureRelay({ onLog: log });
158
+ const existing = await readState();
159
+ await rehydrateRelayRegistry(existing.boxes);
160
+ relayUp = true;
161
+ } catch (err) {
162
+ log(`relay unavailable: ${err instanceof Error ? err.message : String(err)}`);
163
+ }
164
+ const id = generateBoxId();
165
+ const name = opts.name ?? defaultBoxName(workspace, id);
166
+ const containerName = `agentbox-${name}`;
167
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
168
+ if (await containerExists(containerName)) {
169
+ throw new Error(`container ${containerName} already exists; remove it first`);
170
+ }
171
+ const worktreesRoot = join(boxRunDirFor(id), "worktrees");
172
+ await mkdir(worktreesRoot, { recursive: true });
173
+ const gitWorktreeRecords = [];
174
+ const nestedWorktreeBinds = [];
175
+ const repos = await detectGitRepos(workspace);
176
+ if (repos.length > 0) {
177
+ log(
178
+ `detected ${String(repos.length)} git repo(s): ` + repos.map((r) => `${r.kind}${r.relPathFromWorkspace ? "@" + r.relPathFromWorkspace : ""}`).join(", ")
179
+ );
180
+ }
181
+ for (const r of repos) {
182
+ const worktreeDir = join(worktreesRoot, r.relPathFromWorkspace || "root");
183
+ const branchBase = r.kind === "root" ? `agentbox/${name}` : `agentbox/${name}--${r.relPathFromWorkspace.replace(/[^A-Za-z0-9._-]+/g, "_")}`;
184
+ const result = await createBoxWorktree({
185
+ hostMainRepo: r.hostMainRepo,
186
+ branchName: branchBase,
187
+ worktreeDir,
188
+ onLog: log
189
+ });
190
+ const containerPath = r.kind === "root" ? "/workspace" : `/workspace/${r.relPathFromWorkspace}`;
191
+ gitWorktreeRecords.push({
192
+ kind: r.kind,
193
+ hostMainRepo: r.hostMainRepo,
194
+ hostWorktreeDir: worktreeDir,
195
+ containerPath,
196
+ branch: result.branchName,
197
+ relPathFromWorkspace: r.relPathFromWorkspace
198
+ });
199
+ if (r.kind === "nested") {
200
+ nestedWorktreeBinds.push({
201
+ containerPath,
202
+ mountFromPath: `/agentbox-worktrees/${r.relPathFromWorkspace}`
203
+ });
204
+ }
205
+ }
206
+ let lowerPath = workspace;
207
+ const rootWorktree = gitWorktreeRecords.find((w) => w.kind === "root");
208
+ if (rootWorktree) {
209
+ lowerPath = rootWorktree.hostWorktreeDir;
210
+ log(`using worktree as overlay lower: ${lowerPath}`);
211
+ }
212
+ let snapshotDir = null;
213
+ if (opts.useSnapshot) {
214
+ snapshotDir = snapshotPathFor(id);
215
+ log(`cloning workspace to ${snapshotDir} (APFS clone where available)`);
216
+ const snap = await createSnapshot({ source: lowerPath, destination: snapshotDir });
217
+ log(`pruned ${snap.prunedPaths.length} platform-dependent dirs from snapshot`);
218
+ lowerPath = snapshotDir;
219
+ }
220
+ let lowerDirs;
221
+ let checkpointVolume;
222
+ let checkpointSource;
223
+ if (opts.checkpointRef) {
224
+ const projectRootForCkpt = opts.projectRoot ?? workspace;
225
+ const spec = await resolveCheckpointLower(projectRootForCkpt, opts.checkpointRef);
226
+ checkpointVolume = spec.volume;
227
+ const layerDirs = spec.subpaths.map((s) => `${CHECKPOINT_MOUNT}/${s}`);
228
+ lowerDirs = spec.type === "merged" ? layerDirs : [...layerDirs, "/host-src"];
229
+ checkpointSource = { ref: opts.checkpointRef, type: spec.type, chain: spec.chain };
230
+ log(
231
+ `starting from checkpoint ${opts.checkpointRef} (${spec.type}, ${String(spec.subpaths.length)} layer(s), volume ${spec.volume})`
232
+ );
233
+ }
234
+ const upperVolume = `agentbox-upper-${id}`;
235
+ await ensureVolume(upperVolume);
236
+ await ensureIdeVolumes(id);
237
+ const dockerCacheShared = opts.docker?.sharedCache === true;
238
+ const dockerVolume = dockerVolumeName(id, dockerCacheShared);
239
+ await ensureVolume(dockerVolume);
240
+ log(
241
+ `prepared volumes ${upperVolume}, ${vscodeServerVolumeName(id)}, ${cursorServerVolumeName(id)}, ${dockerVolume}`
242
+ );
243
+ const ide = buildIdeMounts(id);
244
+ const claudeSpec = resolveClaudeVolume({
245
+ isolate: opts.claudeConfig?.isolate ?? false,
246
+ boxId: id
247
+ });
248
+ const claudeEnsured = await ensureClaudeVolume(claudeSpec, {
249
+ syncFromHost: true,
250
+ image: imageRef,
251
+ hostWorkspace: workspace
252
+ });
253
+ if (claudeEnsured.synced) {
254
+ log(`synced ${claudeSpec.volume} from ~/.claude`);
255
+ if ((claudeEnsured.filteredHookCount ?? 0) > 0) {
256
+ log(
257
+ `filtered ${String(claudeEnsured.filteredHookCount)} host-path hook(s) (paths under ~/)`
258
+ );
259
+ }
260
+ if (claudeEnsured.clearedInstallMethod) {
261
+ log("cleared host's installMethod from synced .claude.json (box uses the native installer)");
262
+ }
263
+ if (claudeEnsured.aliasedProjectKey) {
264
+ log(`aliased project state for ${workspace} -> /workspace in synced .claude.json`);
265
+ }
266
+ } else if (claudeEnsured.created) {
267
+ log(`created empty volume ${claudeSpec.volume} (no host ~/.claude to sync)`);
268
+ } else {
269
+ log(`reusing volume ${claudeSpec.volume} (no host ~/.claude to sync)`);
270
+ }
271
+ const claudeMounts = buildClaudeMounts(claudeSpec, process.env);
272
+ const boxDir = boxRunDirFor(id);
273
+ const socketDir = join(boxDir, "run");
274
+ const socketPath = join(socketDir, "ctl.sock");
275
+ const mergedExportDir = join(boxDir, "workspace");
276
+ const upperExportDir = join(boxDir, "upper");
277
+ await mkdir(socketDir, { recursive: true });
278
+ await mkdir(mergedExportDir, { recursive: true });
279
+ await mkdir(upperExportDir, { recursive: true });
280
+ const extraVolumes = await buildIdentityMounts();
281
+ extraVolumes.push(...claudeMounts.extraVolumes);
282
+ extraVolumes.push(...ide.extraVolumes);
283
+ extraVolumes.push(`${socketDir}:/run/agentbox`);
284
+ extraVolumes.push(`${mergedExportDir}:${CONTAINER_EXPORT_MERGED}`);
285
+ extraVolumes.push(`${upperExportDir}:${CONTAINER_EXPORT_UPPER}`);
286
+ extraVolumes.push(`${dockerVolume}:/var/lib/docker`);
287
+ for (const w of gitWorktreeRecords) {
288
+ extraVolumes.push(`${w.hostMainRepo}/.git:${w.hostMainRepo}/.git`);
289
+ }
290
+ for (const w of gitWorktreeRecords) {
291
+ if (w.kind === "nested") {
292
+ extraVolumes.push(`${w.hostWorktreeDir}:/agentbox-worktrees/${w.relPathFromWorkspace}`);
293
+ }
294
+ }
295
+ if (checkpointVolume) {
296
+ extraVolumes.push(`${checkpointVolume}:${CHECKPOINT_MOUNT}:ro`);
297
+ }
298
+ for (const v of extraVolumes) log(`mounting agent dir: ${v}`);
299
+ const relayToken = generateRelayToken();
300
+ if (relayUp) {
301
+ try {
302
+ await registerBoxWithRelay({
303
+ boxId: id,
304
+ token: relayToken,
305
+ name,
306
+ containerName,
307
+ createdAt,
308
+ worktrees: gitWorktreeRecords
309
+ });
310
+ log(`registered box token with relay`);
311
+ } catch (err) {
312
+ log(`relay register failed: ${err instanceof Error ? err.message : String(err)}`);
313
+ relayUp = false;
314
+ }
315
+ }
316
+ const relayEnv = relayUp ? {
317
+ // host.docker.internal resolves to the host (where the relay node
318
+ // process is running). The matching `--add-host` is set in runBox.
319
+ AGENTBOX_RELAY_URL: `http://host.docker.internal:8787`,
320
+ AGENTBOX_RELAY_TOKEN: relayToken
321
+ } : {};
322
+ const vncEnabled = opts.vnc?.enabled !== false;
323
+ const vncPassword = vncEnabled ? generateVncPassword() : void 0;
324
+ const vncEnv = vncEnabled && vncPassword ? { AGENTBOX_VNC_PASSWORD: vncPassword } : {};
325
+ const vncPortMappings = vncEnabled ? [{ hostPort: 0, containerPort: VNC_CONTAINER_PORT, hostIp: "127.0.0.1" }] : [];
326
+ const webPortMappings = [
327
+ { hostPort: 0, containerPort: WEB_CONTAINER_PORT, hostIp: "127.0.0.1" }
328
+ ];
329
+ let projectIndex;
330
+ if (opts.projectRoot) {
331
+ projectIndex = allocateProjectIndex(await readState(), opts.projectRoot);
332
+ }
333
+ const agentboxEnv = {
334
+ AGENTBOX: "1",
335
+ AGENTBOX_BOX_NAME: name,
336
+ AGENTBOX_HOST_WORKSPACE: workspace,
337
+ ...opts.projectRoot ? { AGENTBOX_PROJECT_ROOT: opts.projectRoot } : {},
338
+ ...projectIndex !== void 0 ? { AGENTBOX_PROJECT_INDEX: String(projectIndex) } : {}
339
+ };
340
+ const boxEnvForFile = {
341
+ AGENTBOX_BOX_ID: id,
342
+ ...agentboxEnv
343
+ };
344
+ const appliedLimits = opts.limits;
345
+ let effectiveLimits = appliedLimits;
346
+ if (appliedLimits?.disk) {
347
+ const driver = await dockerStorageDriver();
348
+ if (!/^(devicemapper|btrfs|zfs|windowsfilter)$/.test(driver)) {
349
+ log(
350
+ `warning: --disk/box.disk is a no-op on this engine (storage-driver=${driver || "unknown"}); ignoring`
351
+ );
352
+ effectiveLimits = { ...appliedLimits, disk: null };
353
+ }
354
+ }
355
+ await runBox({
356
+ name: containerName,
357
+ image: imageRef,
358
+ lowerPath,
359
+ upperVolume,
360
+ extraVolumes,
361
+ limits: effectiveLimits,
362
+ portMappings: [...vncPortMappings, ...webPortMappings],
363
+ env: {
364
+ AGENTBOX_BOX_ID: id,
365
+ ...agentboxEnv,
366
+ ...claudeMounts.env,
367
+ ...relayEnv,
368
+ ...vncEnv,
369
+ ...opts.claudeEnv ?? {}
370
+ }
371
+ });
372
+ log(`container ${containerName} started`);
373
+ const boxEnv = await writeBoxEnvFile(containerName, boxEnvForFile);
374
+ if (boxEnv.ok) log("wrote /etc/agentbox/box.env");
375
+ else log(`writing /etc/agentbox/box.env failed: ${boxEnv.reason}`);
376
+ try {
377
+ await mountOverlay(containerName, { lowerDirs, nestedWorktrees: nestedWorktreeBinds });
378
+ log("fuse-overlayfs mounted at /workspace");
379
+ if (nestedWorktreeBinds.length > 0) {
380
+ log(`bind-mounted ${String(nestedWorktreeBinds.length)} nested worktree(s) over /workspace`);
381
+ }
382
+ } catch (err) {
383
+ log(`overlay mount failed; leaving container ${containerName} running so you can inspect it`);
384
+ throw err;
385
+ }
386
+ const overlayChecks = await verifyOverlay(containerName, lowerDirs ?? ["/host-src"]);
387
+ const failed = overlayChecks.filter((c) => !c.ok);
388
+ if (failed.length > 0) {
389
+ const detail = failed.map((c) => ` - ${c.name}: ${c.detail}`).join("\n");
390
+ throw new Error(`overlay verification failed:
391
+ ${detail}`);
392
+ }
393
+ log("overlay verified");
394
+ await repairIdeOwnership(containerName);
395
+ log(".vscode-server + .cursor-server ownership verified");
396
+ const ctl = await launchCtlDaemon(containerName, socketPath);
397
+ if (ctl.up) log("agentbox-ctl daemon up");
398
+ else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);
399
+ const dockerd = await launchDockerdDaemon(containerName);
400
+ if (dockerd.up) {
401
+ log(`dockerd up (storage-driver=fuse-overlayfs, data root=${dockerVolume})`);
402
+ } else {
403
+ log(`dockerd did not become ready: ${dockerd.reason}`);
404
+ }
405
+ if (opts.withPlaywright) {
406
+ log("installing @playwright/cli@latest (--with-playwright)");
407
+ const result = await execa2(
408
+ "docker",
409
+ [
410
+ "exec",
411
+ "--user",
412
+ "root",
413
+ containerName,
414
+ "bash",
415
+ "-lc",
416
+ "npm install -g @playwright/cli@latest 2>&1"
417
+ ],
418
+ { reject: false }
419
+ );
420
+ for (const line of (result.stdout ?? "").split("\n")) {
421
+ if (line.trim().length > 0) log(`[playwright] ${line}`);
422
+ }
423
+ if (result.exitCode !== 0) {
424
+ throw new Error(
425
+ `failed to install @playwright/cli (exit ${String(result.exitCode)}): ${(result.stderr ?? "").toString().slice(0, 400)}`
426
+ );
427
+ }
428
+ log("@playwright/cli installed");
429
+ }
430
+ if (opts.withEnv) {
431
+ log("copying host env/config files into /workspace (--with-env)");
432
+ const { copied } = await copyHostEnvFilesToBox({
433
+ container: containerName,
434
+ workspaceDir: workspace,
435
+ patterns: DEFAULT_ENV_PATTERNS,
436
+ onLog: log
437
+ });
438
+ log(copied > 0 ? `copied ${String(copied)} env/config file(s)` : "no env/config files found");
439
+ }
440
+ let vncHostPort = null;
441
+ if (vncEnabled) {
442
+ const vnc = await launchVncDaemon(containerName);
443
+ if (vnc.up) log("vnc stack up (Xvnc + websockify + noVNC)");
444
+ else log(`vnc stack did not become reachable: ${vnc.reason}`);
445
+ vncHostPort = await publishedHostPort(containerName, VNC_CONTAINER_PORT);
446
+ if (vncHostPort) log(`vnc web on host 127.0.0.1:${String(vncHostPort)}`);
447
+ }
448
+ const webHostPort = await publishedHostPort(containerName, WEB_CONTAINER_PORT);
449
+ if (webHostPort) {
450
+ log(
451
+ `web port reserved on host 127.0.0.1:${String(webHostPort)} (forwards to the web service once agentbox.yaml sets a service expose:)`
452
+ );
453
+ }
454
+ const record = {
455
+ id,
456
+ name,
457
+ container: containerName,
458
+ image: imageRef,
459
+ workspacePath: workspace,
460
+ lowerPath,
461
+ upperVolume,
462
+ snapshotDir,
463
+ socketPath,
464
+ claudeConfigVolume: claudeSpec.volume,
465
+ vscodeServerVolume: vscodeServerVolumeName(id),
466
+ cursorServerVolume: cursorServerVolumeName(id),
467
+ relayToken: relayUp ? relayToken : void 0,
468
+ gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : void 0,
469
+ withPlaywright: opts.withPlaywright ? true : void 0,
470
+ withEnv: opts.withEnv ? true : void 0,
471
+ vncEnabled: vncEnabled ? true : void 0,
472
+ vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : void 0,
473
+ vncHostPort: vncHostPort ?? void 0,
474
+ vncPassword,
475
+ webContainerPort: WEB_CONTAINER_PORT,
476
+ webHostPort: webHostPort ?? void 0,
477
+ dockerVolume,
478
+ dockerCacheShared: dockerCacheShared || void 0,
479
+ projectRoot: opts.projectRoot,
480
+ projectIndex,
481
+ lowerDirs,
482
+ checkpointVolume,
483
+ checkpointSource,
484
+ resourceLimits: persistableLimits(effectiveLimits),
485
+ createdAt
486
+ };
487
+ await recordBox(record);
488
+ return { record, overlayChecks, imageBuilt: built };
489
+ }
490
+
491
+ export {
492
+ sanitizeBasename,
493
+ defaultBoxName,
494
+ createBox
495
+ };
496
+ //# sourceMappingURL=chunk-OOOKFFR5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../packages/sandbox-docker/src/create.ts","../../../packages/sandbox-docker/src/box-env.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { mkdir, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, join, resolve } from 'node:path';\nimport { execa } from 'execa';\nimport { ConfigError, loadConfig } from '@agentbox/ctl';\nimport { buildClaudeMounts, ensureClaudeVolume, resolveClaudeVolume } from './claude.js';\nimport {\n type BoxLimitSpec,\n containerExists,\n dockerInfo,\n dockerStorageDriver,\n ensureVolume,\n publishedHostPort,\n runBox,\n} from './docker.js';\nimport { dockerVolumeName, launchDockerdDaemon } from './dockerd.js';\nimport { generateVncPassword, launchVncDaemon, VNC_CONTAINER_PORT } from './vnc.js';\nimport { WEB_CONTAINER_PORT } from './web.js';\nimport { createBoxWorktree, detectGitRepos } from './git-worktree.js';\nimport {\n CONTAINER_EXPORT_MERGED,\n CONTAINER_EXPORT_UPPER,\n DEFAULT_ENV_PATTERNS,\n boxRunDirFor,\n copyHostEnvFilesToBox,\n} from './host-export.js';\nimport { DEFAULT_BOX_IMAGE, ensureImage } from './image.js';\nimport {\n mountOverlay,\n verifyOverlay,\n type NestedWorktreeBind,\n type OverlayCheck,\n} from './overlay.js';\nimport {\n allocateProjectIndex,\n readState,\n recordBox,\n type BoxRecord,\n type GitWorktreeRecord,\n} from './state.js';\nimport { createSnapshot, snapshotPathFor } from './snapshot.js';\nimport { CHECKPOINT_MOUNT, resolveCheckpointLower } from './checkpoint.js';\nimport { launchCtlDaemon } from './ctl.js';\nimport { writeBoxEnvFile } from './box-env.js';\nimport {\n ensureRelay,\n generateRelayToken,\n registerBoxWithRelay,\n rehydrateRelayRegistry,\n} from './relay.js';\nimport {\n buildIdeMounts,\n cursorServerVolumeName,\n ensureIdeVolumes,\n repairIdeOwnership,\n vscodeServerVolumeName,\n} from './vscode.js';\n\nexport interface CreateBoxOptions {\n workspacePath: string;\n name?: string;\n /** Frozen APFS clone of the host workspace as the overlay lower (the `--host-snapshot` path). */\n useSnapshot: boolean;\n /**\n * Start the box from a project checkpoint (the `--snapshot <ref>` path).\n * Resolved against `projectRoot` (or `workspacePath` when unset). A\n * `layered` checkpoint stacks its captured delta(s) over the normal base\n * lower; a `merged` checkpoint is the sole, frozen lower.\n */\n checkpointRef?: string;\n image?: string;\n onLog?: (line: string) => void;\n /**\n * Claude Code config volume. When omitted, defaults to `{ isolate: false }` —\n * every box mounts the shared `agentbox-claude-config` volume at\n * /home/vscode/.claude so auth / skills / plugins persist across boxes.\n */\n claudeConfig?: { isolate: boolean };\n /** Extra env vars forwarded to the container (merged on top of claude env forwarding). */\n claudeEnv?: Record<string, string>;\n /**\n * When true, run `npm install -g @playwright/cli@latest` inside the box after\n * the overlay is mounted. agent-browser is always installed in the image; this\n * flag adds the Playwright CLI on top for boxes that need it.\n */\n withPlaywright?: boolean;\n /**\n * When true, copy the host's env/config files (DEFAULT_ENV_PATTERNS basename\n * globs — `.env*`, `secrets.toml`, `agentbox.yaml`, ...) into the box's\n * /workspace after the overlay is mounted, bypassing gitignore. The reverse\n * of `pull env`. One-shot at create time; the files persist in the writable\n * upper layer across pause/stop/start.\n */\n withEnv?: boolean;\n /**\n * VNC stack (Xvnc on :1 + websockify serving noVNC on container :6080).\n * Defaults to enabled. The CLI exposes `--no-vnc` for opt-out. Disabling\n * skips port mapping + password generation + the in-container supervisor\n * launch; the apt-installed binaries stay in the image but are unused.\n */\n vnc?: { enabled: boolean };\n /**\n * Docker-in-Docker. Always-on (the in-box dockerd is part of the box\n * surface). When `sharedCache` is true the per-box `agentbox-docker-<id>`\n * volume is replaced with the shared `agentbox-docker-cache` volume — image\n * layers persist across boxes (and `destroy`/`prune` won't remove it).\n */\n docker?: { sharedCache: boolean };\n /**\n * Absolute host path of the cwd's project at create time. When provided,\n * `createBox` stamps `projectRoot` + an allocated `projectIndex` on the\n * BoxRecord so the CLI can auto-pick / resolve by index. The CLI computes\n * this via `findProjectRoot(workspacePath)` from `@agentbox/config`; this\n * package stays free of the config dep. Omit for unowned boxes created\n * directly via the programmatic API.\n */\n projectRoot?: string;\n /**\n * Container resource ceilings (engine-agnostic: bytes / fractional cpus /\n * pid count / raw disk size string). Absent fields = unlimited. `disk` is\n * best-effort: dropped (with a warning via `onLog`) when the engine's\n * storage driver can't enforce `--storage-opt size=` (overlay2 / macOS).\n */\n limits?: BoxLimitSpec;\n}\n\nexport interface CreatedBox {\n record: BoxRecord;\n overlayChecks: OverlayCheck[];\n imageBuilt: boolean;\n}\n\n/**\n * Compact the engine-applied limits into the BoxRecord shape: only fields that\n * actually constrain the box (>0 / non-empty). Returns undefined when nothing\n * was applied so legacy/unlimited boxes stay free of the field.\n */\nfunction persistableLimits(\n lim: BoxLimitSpec | undefined,\n): BoxRecord['resourceLimits'] | undefined {\n if (!lim) return undefined;\n const out: NonNullable<BoxRecord['resourceLimits']> = {};\n if (lim.memoryBytes && lim.memoryBytes > 0) out.memoryBytes = Math.floor(lim.memoryBytes);\n if (lim.cpus && lim.cpus > 0) out.cpus = lim.cpus;\n if (lim.pidsLimit && lim.pidsLimit > 0) out.pidsLimit = Math.floor(lim.pidsLimit);\n if (lim.disk) out.disk = lim.disk;\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nfunction generateBoxId(): string {\n return randomBytes(4).toString('hex');\n}\n\nexport function sanitizeBasename(workspacePath: string): string {\n const raw = basename(resolve(workspacePath));\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^[-._]+|[-._]+$/g, '')\n .slice(0, 30)\n .replace(/[-._]+$/, '');\n}\n\nexport function defaultBoxName(workspacePath: string, id: string): string {\n const base = sanitizeBasename(workspacePath);\n return base.length > 0 ? `${base}-${id}` : id;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\n// ~/.claude is intentionally NOT in this list: it lives in the named volume\n// `agentbox-claude-config` (see resolveClaudeVolume / ensureClaudeVolume) so\n// auth persists inside the container without leaking host state. Only\n// non-claude identity files are bind-mounted from the host.\nasync function buildIdentityMounts(): Promise<string[]> {\n const home = homedir();\n const candidates: Array<{ src: string; dst: string; readOnly: boolean }> = [\n { src: join(home, '.codex'), dst: '/home/vscode/.codex', readOnly: false },\n { src: join(home, '.gitconfig'), dst: '/home/vscode/.gitconfig', readOnly: true },\n ];\n const out: string[] = [];\n for (const c of candidates) {\n if (await pathExists(c.src)) {\n out.push(`${c.src}:${c.dst}${c.readOnly ? ':ro' : ''}`);\n }\n }\n return out;\n}\n\nexport async function createBox(opts: CreateBoxOptions): Promise<CreatedBox> {\n const log = opts.onLog ?? (() => {});\n const workspace = resolve(opts.workspacePath);\n if (!(await pathExists(workspace))) {\n throw new Error(`workspace does not exist: ${workspace}`);\n }\n\n // Pre-flight agentbox.yaml validation on the host so the user sees the real\n // ConfigError instead of an opaque \"socket did not appear\" timeout from the\n // detached daemon exec later. The daemon re-validates inside the box anyway\n // — defence in depth, and necessary because the file lives in the overlay\n // and can be edited after create.\n const cfgPath = join(workspace, 'agentbox.yaml');\n if (await pathExists(cfgPath)) {\n try {\n const cfg = await loadConfig(cfgPath);\n log(`agentbox.yaml validated (${String(cfg.services.length)} service(s))`);\n } catch (err) {\n if (err instanceof ConfigError) {\n throw new Error(`agentbox.yaml validation failed:\\n ${err.message}`);\n }\n throw err;\n }\n }\n\n await dockerInfo();\n log('docker daemon reachable');\n\n const imageRef = opts.image ?? DEFAULT_BOX_IMAGE;\n const { built } = await ensureImage(imageRef, {\n onProgress: (line) => log(`[image] ${line}`),\n });\n log(built ? `built image ${imageRef}` : `using cached image ${imageRef}`);\n\n // Bring up the host relay before the box so the box can post events\n // immediately on boot. Best-effort — a relay outage shouldn't block create.\n // Always re-push known box tokens after ensure: the relay's registry is\n // in-memory, so a daemon restart or `docker restart agentbox-relay` between\n // CLI invocations leaves it empty. Repushing is idempotent and cheap.\n let relayUp = false;\n try {\n await ensureRelay({ onLog: log });\n const existing = await readState();\n await rehydrateRelayRegistry(existing.boxes);\n relayUp = true;\n } catch (err) {\n log(`relay unavailable: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n const id = generateBoxId();\n const name = opts.name ?? defaultBoxName(workspace, id);\n const containerName = `agentbox-${name}`;\n const createdAt = new Date().toISOString();\n if (await containerExists(containerName)) {\n throw new Error(`container ${containerName} already exists; remove it first`);\n }\n\n // Detect host git repos at workspace root + 1st-level subdirs and create a\n // dedicated worktree per repo on a fresh `agentbox/<box-name>` branch. The\n // host's working tree stays untouched; uncommitted (tracked + untracked)\n // state is carried into the worktree so the agent picks up where the user\n // left off. The root worktree (if any) replaces the box's overlay lower so\n // /workspace is the agent's editable working tree; nested worktrees are\n // staged on a side path and bind-mounted on top of /workspace/<subpath>\n // after the FUSE overlay is up (see mountOverlay).\n const worktreesRoot = join(boxRunDirFor(id), 'worktrees');\n await mkdir(worktreesRoot, { recursive: true });\n const gitWorktreeRecords: GitWorktreeRecord[] = [];\n const nestedWorktreeBinds: NestedWorktreeBind[] = [];\n const repos = await detectGitRepos(workspace);\n if (repos.length > 0) {\n log(\n `detected ${String(repos.length)} git repo(s): ` +\n repos.map((r) => `${r.kind}${r.relPathFromWorkspace ? '@' + r.relPathFromWorkspace : ''}`).join(', '),\n );\n }\n for (const r of repos) {\n const worktreeDir = join(worktreesRoot, r.relPathFromWorkspace || 'root');\n const branchBase =\n r.kind === 'root'\n ? `agentbox/${name}`\n : `agentbox/${name}--${r.relPathFromWorkspace.replace(/[^A-Za-z0-9._-]+/g, '_')}`;\n const result = await createBoxWorktree({\n hostMainRepo: r.hostMainRepo,\n branchName: branchBase,\n worktreeDir,\n onLog: log,\n });\n const containerPath = r.kind === 'root' ? '/workspace' : `/workspace/${r.relPathFromWorkspace}`;\n gitWorktreeRecords.push({\n kind: r.kind,\n hostMainRepo: r.hostMainRepo,\n hostWorktreeDir: worktreeDir,\n containerPath,\n branch: result.branchName,\n relPathFromWorkspace: r.relPathFromWorkspace,\n });\n if (r.kind === 'nested') {\n nestedWorktreeBinds.push({\n containerPath,\n mountFromPath: `/agentbox-worktrees/${r.relPathFromWorkspace}`,\n });\n }\n }\n\n let lowerPath = workspace;\n const rootWorktree = gitWorktreeRecords.find((w) => w.kind === 'root');\n if (rootWorktree) {\n lowerPath = rootWorktree.hostWorktreeDir;\n log(`using worktree as overlay lower: ${lowerPath}`);\n }\n\n let snapshotDir: string | null = null;\n if (opts.useSnapshot) {\n snapshotDir = snapshotPathFor(id);\n log(`cloning workspace to ${snapshotDir} (APFS clone where available)`);\n const snap = await createSnapshot({ source: lowerPath, destination: snapshotDir });\n log(`pruned ${snap.prunedPaths.length} platform-dependent dirs from snapshot`);\n lowerPath = snapshotDir;\n }\n\n // Checkpoint restore: mount the per-project checkpoint volume read-only\n // ONCE at /agentbox-checkpoints; the overlay uses its `<name>` subdirs as\n // lowerdirs (layered, over the /host-src base) or the sole lower (merged,\n // code frozen). The base bind is always `${lowerPath}:/host-src`.\n let lowerDirs: string[] | undefined;\n let checkpointVolume: string | undefined;\n let checkpointSource: BoxRecord['checkpointSource'];\n if (opts.checkpointRef) {\n const projectRootForCkpt = opts.projectRoot ?? workspace;\n const spec = await resolveCheckpointLower(projectRootForCkpt, opts.checkpointRef);\n checkpointVolume = spec.volume;\n const layerDirs = spec.subpaths.map((s) => `${CHECKPOINT_MOUNT}/${s}`);\n lowerDirs = spec.type === 'merged' ? layerDirs : [...layerDirs, '/host-src'];\n checkpointSource = { ref: opts.checkpointRef, type: spec.type, chain: spec.chain };\n log(\n `starting from checkpoint ${opts.checkpointRef} (${spec.type}, ${String(spec.subpaths.length)} layer(s), volume ${spec.volume})`,\n );\n }\n\n const upperVolume = `agentbox-upper-${id}`;\n await ensureVolume(upperVolume);\n await ensureIdeVolumes(id);\n const dockerCacheShared = opts.docker?.sharedCache === true;\n const dockerVolume = dockerVolumeName(id, dockerCacheShared);\n await ensureVolume(dockerVolume);\n log(\n `prepared volumes ${upperVolume}, ${vscodeServerVolumeName(id)}, ${cursorServerVolumeName(id)}, ${dockerVolume}`,\n );\n const ide = buildIdeMounts(id);\n\n // Claude Code config volume. Shared by default so users sign in once across\n // every box; --isolate-claude-config opts into a per-box volume. Either way,\n // the host's ~/.claude is the authoritative source: we rsync host -> volume\n // on every create so updates on the host (new login, new skills, new MCP)\n // flow into the next box. Sync is additive — box-only state (session logs,\n // etc.) is preserved.\n const claudeSpec = resolveClaudeVolume({\n isolate: opts.claudeConfig?.isolate ?? false,\n boxId: id,\n });\n const claudeEnsured = await ensureClaudeVolume(claudeSpec, {\n syncFromHost: true,\n image: imageRef,\n hostWorkspace: workspace,\n });\n if (claudeEnsured.synced) {\n log(`synced ${claudeSpec.volume} from ~/.claude`);\n if ((claudeEnsured.filteredHookCount ?? 0) > 0) {\n log(\n `filtered ${String(claudeEnsured.filteredHookCount)} host-path hook(s) (paths under ~/)`,\n );\n }\n if (claudeEnsured.clearedInstallMethod) {\n log(\"cleared host's installMethod from synced .claude.json (box uses the native installer)\");\n }\n if (claudeEnsured.aliasedProjectKey) {\n log(`aliased project state for ${workspace} -> /workspace in synced .claude.json`);\n }\n } else if (claudeEnsured.created) {\n log(`created empty volume ${claudeSpec.volume} (no host ~/.claude to sync)`);\n } else {\n log(`reusing volume ${claudeSpec.volume} (no host ~/.claude to sync)`);\n }\n const claudeMounts = buildClaudeMounts(claudeSpec, process.env);\n\n const boxDir = boxRunDirFor(id);\n const socketDir = join(boxDir, 'run');\n const socketPath = join(socketDir, 'ctl.sock');\n // Per-box host dirs that `agentbox open` / `agentbox path` refresh into.\n // We bind these in at create time so a later `docker exec rsync` can write\n // straight to the host filesystem — no container restart needed.\n const mergedExportDir = join(boxDir, 'workspace');\n const upperExportDir = join(boxDir, 'upper');\n await mkdir(socketDir, { recursive: true });\n await mkdir(mergedExportDir, { recursive: true });\n await mkdir(upperExportDir, { recursive: true });\n\n const extraVolumes = await buildIdentityMounts();\n extraVolumes.push(...claudeMounts.extraVolumes);\n extraVolumes.push(...ide.extraVolumes);\n extraVolumes.push(`${socketDir}:/run/agentbox`);\n extraVolumes.push(`${mergedExportDir}:${CONTAINER_EXPORT_MERGED}`);\n extraVolumes.push(`${upperExportDir}:${CONTAINER_EXPORT_UPPER}`);\n // In-box dockerd's data root. Per-box (`agentbox-docker-<id>`, wiped on\n // destroy) by default; shared (`agentbox-docker-cache`, preserved) when\n // `box.dockerCacheShared` is set.\n extraVolumes.push(`${dockerVolume}:/var/lib/docker`);\n // Bind-mount each main repo's `.git/` at its identical absolute host path,\n // RW. Worktree pointer files (`<worktree>/.git`) and the back-reference at\n // `<main>/.git/worktrees/<name>/gitdir` contain absolute paths; both must\n // resolve to the same path on host and inside the container or git breaks\n // on one side.\n for (const w of gitWorktreeRecords) {\n extraVolumes.push(`${w.hostMainRepo}/.git:${w.hostMainRepo}/.git`);\n }\n // Stage nested worktrees on a side path so mountOverlay() can bind-mount\n // them on top of /workspace/<subpath> after the FUSE overlay is up.\n for (const w of gitWorktreeRecords) {\n if (w.kind === 'nested') {\n extraVolumes.push(`${w.hostWorktreeDir}:/agentbox-worktrees/${w.relPathFromWorkspace}`);\n }\n }\n // Per-project checkpoint volume, mounted read-only once; the overlay's\n // lowerdirs are its `<name>` subdirs (see mountOverlay).\n if (checkpointVolume) {\n extraVolumes.push(`${checkpointVolume}:${CHECKPOINT_MOUNT}:ro`);\n }\n for (const v of extraVolumes) log(`mounting agent dir: ${v}`);\n\n // Per-box bearer token for the host relay. Register *before* runBox so the\n // box's supervisor can post on boot. Skip if the relay isn't reachable —\n // the box still works, it just won't deliver events to the host.\n const relayToken = generateRelayToken();\n if (relayUp) {\n try {\n await registerBoxWithRelay({\n boxId: id,\n token: relayToken,\n name,\n containerName,\n createdAt,\n worktrees: gitWorktreeRecords,\n });\n log(`registered box token with relay`);\n } catch (err) {\n log(`relay register failed: ${err instanceof Error ? err.message : String(err)}`);\n relayUp = false;\n }\n }\n const relayEnv: Record<string, string> = relayUp\n ? {\n // host.docker.internal resolves to the host (where the relay node\n // process is running). The matching `--add-host` is set in runBox.\n AGENTBOX_RELAY_URL: `http://host.docker.internal:8787`,\n AGENTBOX_RELAY_TOKEN: relayToken,\n }\n : {};\n\n // VNC stack defaults on; the CLI surfaces `--no-vnc` for opt-out. Generate\n // the password and the port mapping up front so they're baked into the\n // container's env + `-p` flags before `docker run` — both must be set at\n // create time (env survives stop/start; port mappings are immutable).\n const vncEnabled = opts.vnc?.enabled !== false;\n const vncPassword = vncEnabled ? generateVncPassword() : undefined;\n const vncEnv: Record<string, string> = vncEnabled && vncPassword\n ? { AGENTBOX_VNC_PASSWORD: vncPassword }\n : {};\n const vncPortMappings = vncEnabled\n ? [{ hostPort: 0, containerPort: VNC_CONTAINER_PORT, hostIp: '127.0.0.1' }]\n : [];\n\n // Reserve the web port unconditionally: `docker run -p` is immutable, but the\n // `expose:`-flagged service is usually only known after the in-box wizard\n // writes agentbox.yaml. The supervisor forwards :80 to it later; here we just\n // guarantee a published host port exists for whenever that happens.\n const webPortMappings = [\n { hostPort: 0, containerPort: WEB_CONTAINER_PORT, hostIp: '127.0.0.1' },\n ];\n\n // Per-project monotonic index. Allocated *before* runBox so it can be\n // injected as AGENTBOX_PROJECT_INDEX in the container env. Re-reads state\n // each time so concurrent creates from the same project see each other's\n // assignments (last-write-wins is fine — `recordBox` upserts by id, and\n // index collisions are harmless since each box's id is unique).\n let projectIndex: number | undefined;\n if (opts.projectRoot) {\n projectIndex = allocateProjectIndex(await readState(), opts.projectRoot);\n }\n\n // Identity vars that make the box self-aware. `AGENTBOX=1` is the sentinel\n // `[ -n \"$AGENTBOX\" ]` checks key off. The rest are metadata for in-box\n // agents — `AGENTBOX_HOST_WORKSPACE` is intentionally the absolute host\n // path (not a mount), so an agent can explain to the user what host dir\n // they are looking at.\n const agentboxEnv: Record<string, string> = {\n AGENTBOX: '1',\n AGENTBOX_BOX_NAME: name,\n AGENTBOX_HOST_WORKSPACE: workspace,\n ...(opts.projectRoot ? { AGENTBOX_PROJECT_ROOT: opts.projectRoot } : {}),\n ...(projectIndex !== undefined\n ? { AGENTBOX_PROJECT_INDEX: String(projectIndex) }\n : {}),\n };\n const boxEnvForFile: Record<string, string> = {\n AGENTBOX_BOX_ID: id,\n ...agentboxEnv,\n };\n\n // `--storage-opt size=` is only enforced by devicemapper/btrfs/zfs — a hard\n // error on overlay2 / fuse-overlayfs (every macOS engine). Drop it + warn so\n // create doesn't blow up; the other limits are universal.\n const appliedLimits: BoxLimitSpec | undefined = opts.limits;\n let effectiveLimits = appliedLimits;\n if (appliedLimits?.disk) {\n const driver = await dockerStorageDriver();\n if (!/^(devicemapper|btrfs|zfs|windowsfilter)$/.test(driver)) {\n log(\n `warning: --disk/box.disk is a no-op on this engine (storage-driver=${driver || 'unknown'}); ignoring`,\n );\n effectiveLimits = { ...appliedLimits, disk: null };\n }\n }\n\n await runBox({\n name: containerName,\n image: imageRef,\n lowerPath,\n upperVolume,\n extraVolumes,\n limits: effectiveLimits,\n portMappings: [...vncPortMappings, ...webPortMappings],\n env: {\n AGENTBOX_BOX_ID: id,\n ...agentboxEnv,\n ...claudeMounts.env,\n ...relayEnv,\n ...vncEnv,\n ...(opts.claudeEnv ?? {}),\n },\n });\n log(`container ${containerName} started`);\n\n // /etc/agentbox/box.env: sourced by /etc/profile.d/agentbox.sh in login\n // shells (the docker-run env doesn't reach `agentbox shell <box>` cleanly\n // without it). Best-effort — env vars on the container are the primary\n // path; this file is for shells launched via tools that strip env.\n const boxEnv = await writeBoxEnvFile(containerName, boxEnvForFile);\n if (boxEnv.ok) log('wrote /etc/agentbox/box.env');\n else log(`writing /etc/agentbox/box.env failed: ${boxEnv.reason}`);\n\n try {\n await mountOverlay(containerName, { lowerDirs, nestedWorktrees: nestedWorktreeBinds });\n log('fuse-overlayfs mounted at /workspace');\n if (nestedWorktreeBinds.length > 0) {\n log(`bind-mounted ${String(nestedWorktreeBinds.length)} nested worktree(s) over /workspace`);\n }\n } catch (err) {\n log(`overlay mount failed; leaving container ${containerName} running so you can inspect it`);\n throw err;\n }\n\n const overlayChecks = await verifyOverlay(containerName, lowerDirs ?? ['/host-src']);\n const failed = overlayChecks.filter((c) => !c.ok);\n if (failed.length > 0) {\n const detail = failed.map((c) => ` - ${c.name}: ${c.detail}`).join('\\n');\n throw new Error(`overlay verification failed:\\n${detail}`);\n }\n log('overlay verified');\n\n await repairIdeOwnership(containerName);\n log('.vscode-server + .cursor-server ownership verified');\n\n const ctl = await launchCtlDaemon(containerName, socketPath);\n if (ctl.up) log('agentbox-ctl daemon up');\n else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);\n\n // dockerd: always-on, mirrors launchVncDaemon. Best-effort — a slow start\n // shouldn't fail box creation; `agentbox start` will relaunch on restart\n // (the daemon dies with the container). Storage driver is fuse-overlayfs,\n // pinned in /etc/docker/daemon.json baked into the image.\n const dockerd = await launchDockerdDaemon(containerName);\n if (dockerd.up) {\n log(`dockerd up (storage-driver=fuse-overlayfs, data root=${dockerVolume})`);\n } else {\n log(`dockerd did not become ready: ${dockerd.reason}`);\n }\n\n if (opts.withPlaywright) {\n log('installing @playwright/cli@latest (--with-playwright)');\n // npm-global writes to /usr/lib/node_modules/, so we need root. The\n // resulting binary lives in /usr/bin and persists in the container's\n // writable layer across pause/stop/start — no need to reinstall on start.\n const result = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'root',\n containerName,\n 'bash',\n '-lc',\n 'npm install -g @playwright/cli@latest 2>&1',\n ],\n { reject: false },\n );\n for (const line of (result.stdout ?? '').split('\\n')) {\n if (line.trim().length > 0) log(`[playwright] ${line}`);\n }\n if (result.exitCode !== 0) {\n throw new Error(\n `failed to install @playwright/cli (exit ${String(result.exitCode)}): ${(result.stderr ?? '').toString().slice(0, 400)}`,\n );\n }\n log('@playwright/cli installed');\n }\n\n if (opts.withEnv) {\n log('copying host env/config files into /workspace (--with-env)');\n const { copied } = await copyHostEnvFilesToBox({\n container: containerName,\n workspaceDir: workspace,\n patterns: DEFAULT_ENV_PATTERNS,\n onLog: log,\n });\n log(copied > 0 ? `copied ${String(copied)} env/config file(s)` : 'no env/config files found');\n }\n\n // VNC daemon (Xvnc + websockify). Best-effort, like launchCtlDaemon. The\n // host port mapping was wired into runBox above (hostPort=0 → random); we\n // resolve the assigned port here for storage. If the daemon fails to come\n // up we still record vncEnabled so `agentbox start` will retry the launch\n // — the failure is usually transient (apt-running, fs slow).\n let vncHostPort: number | null = null;\n if (vncEnabled) {\n const vnc = await launchVncDaemon(containerName);\n if (vnc.up) log('vnc stack up (Xvnc + websockify + noVNC)');\n else log(`vnc stack did not become reachable: ${vnc.reason}`);\n vncHostPort = await publishedHostPort(containerName, VNC_CONTAINER_PORT);\n if (vncHostPort) log(`vnc web on host 127.0.0.1:${String(vncHostPort)}`);\n }\n\n const webHostPort = await publishedHostPort(containerName, WEB_CONTAINER_PORT);\n if (webHostPort) {\n log(\n `web port reserved on host 127.0.0.1:${String(webHostPort)} ` +\n `(forwards to the web service once agentbox.yaml sets a service expose:)`,\n );\n }\n\n const record: BoxRecord = {\n id,\n name,\n container: containerName,\n image: imageRef,\n workspacePath: workspace,\n lowerPath,\n upperVolume,\n snapshotDir,\n socketPath,\n claudeConfigVolume: claudeSpec.volume,\n vscodeServerVolume: vscodeServerVolumeName(id),\n cursorServerVolume: cursorServerVolumeName(id),\n relayToken: relayUp ? relayToken : undefined,\n gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : undefined,\n withPlaywright: opts.withPlaywright ? true : undefined,\n withEnv: opts.withEnv ? true : undefined,\n vncEnabled: vncEnabled ? true : undefined,\n vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : undefined,\n vncHostPort: vncHostPort ?? undefined,\n vncPassword: vncPassword,\n webContainerPort: WEB_CONTAINER_PORT,\n webHostPort: webHostPort ?? undefined,\n dockerVolume,\n dockerCacheShared: dockerCacheShared || undefined,\n projectRoot: opts.projectRoot,\n projectIndex,\n lowerDirs,\n checkpointVolume,\n checkpointSource,\n resourceLimits: persistableLimits(effectiveLimits),\n createdAt,\n };\n await recordBox(record);\n\n return { record, overlayChecks, imageBuilt: built };\n}\n","import { execa } from 'execa';\n\n/**\n * Writes /etc/agentbox/box.env inside the container as a POSIX-sourceable\n * key='value' file. Paired with /etc/profile.d/agentbox.sh (baked in the\n * image), which `set -a; . /etc/agentbox/box.env; set +a`s it on login.\n *\n * Best-effort: failure is logged by the caller; an unwritable file just\n * means interactive shells lose the AGENTBOX_* vars (the env vars baked\n * into docker run still survive).\n */\nexport async function writeBoxEnvFile(\n container: string,\n env: Record<string, string>,\n): Promise<{ ok: true } | { ok: false; reason: string }> {\n const body = formatBoxEnvBody(env);\n const result = await execa(\n 'docker',\n ['exec', '--user', 'root', '-i', container, 'sh', '-c', 'umask 022 && cat > /etc/agentbox/box.env'],\n { input: body, reject: false },\n );\n if (result.exitCode !== 0) {\n return {\n ok: false,\n reason: `docker exec failed (exit ${String(result.exitCode)}): ${(result.stderr ?? '').toString().slice(0, 400)}`,\n };\n }\n return { ok: true };\n}\n\n// Single-quote each value and escape embedded single quotes as '\\''. Avoids\n// double-quoted form because `. ` would expand $foo / `cmd` at source time.\nexport function formatBoxEnvBody(env: Record<string, string>): string {\n const lines: string[] = [];\n for (const [k, v] of Object.entries(env)) {\n lines.push(`${k}=${shellSingleQuote(v)}`);\n }\n return lines.join('\\n') + '\\n';\n}\n\nfunction shellSingleQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n"],"mappings":"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","names":["execa","execa"]}