@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/dist/{_cloud-attach-GUBB5RH2.js → _cloud-attach-5KJWOASL.js} +4 -4
  3. package/dist/{chunk-RSKG7AFU.js → chunk-3WCEB6RE.js} +2 -2
  4. package/dist/{chunk-XKH7NTT7.js → chunk-DBBUDKKB.js} +248 -5
  5. package/dist/chunk-DBBUDKKB.js.map +1 -0
  6. package/dist/{chunk-TCS5HXJX.js → chunk-GXJNJUEV.js} +1090 -527
  7. package/dist/chunk-GXJNJUEV.js.map +1 -0
  8. package/dist/{chunk-LDMYHWUS.js → chunk-NW2UZQV6.js} +10 -6
  9. package/dist/chunk-NW2UZQV6.js.map +1 -0
  10. package/dist/{chunk-TBSIJVSN.js → chunk-PIK47622.js} +37 -17
  11. package/dist/chunk-PIK47622.js.map +1 -0
  12. package/dist/{chunk-BKU34KYY.js → chunk-QXFNLKJJ.js} +9 -3
  13. package/dist/{chunk-BKU34KYY.js.map → chunk-QXFNLKJJ.js.map} +1 -1
  14. package/dist/{chunk-BYCLD6D6.js → chunk-SB4QTF2T.js} +98 -54
  15. package/dist/chunk-SB4QTF2T.js.map +1 -0
  16. package/dist/{chunk-VATTS2MR.js → chunk-SENASAU4.js} +10 -6
  17. package/dist/{chunk-VATTS2MR.js.map → chunk-SENASAU4.js.map} +1 -1
  18. package/dist/{dist-34RKQ74M.js → dist-4IQFJJQI.js} +5 -5
  19. package/dist/{dist-4DPOL5A7.js → dist-7YB7BMNG.js} +5 -5
  20. package/dist/{dist-3IMQNTTV.js → dist-SL2QSMBE.js} +5 -5
  21. package/dist/{dist-J2IHD5T7.js → dist-VHI5QOSQ.js} +6 -6
  22. package/dist/{dist-57M6ZA7H.js → dist-XC47DSCR.js} +5 -5
  23. package/dist/index.js +1043 -333
  24. package/dist/index.js.map +1 -1
  25. package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js → prepared-state-MQHD3M5F-2LANTRL7.js} +2 -2
  26. package/package.json +6 -5
  27. package/runtime/docker/Dockerfile.box +21 -2
  28. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +112 -29
  29. package/runtime/docker/packages/ctl/dist/bin.cjs +10353 -8575
  30. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +5 -2
  31. package/runtime/docker/packages/sandbox-docker/scripts/linear-shim +181 -0
  32. package/runtime/docker/packages/sandbox-docker/scripts/ntn-shim +95 -0
  33. package/runtime/e2b/agentbox-checkpoint-cleanup +5 -2
  34. package/runtime/e2b/agentbox-setup-skill.md +112 -29
  35. package/runtime/e2b/ctl.cjs +10353 -8575
  36. package/runtime/e2b/linear-shim +181 -0
  37. package/runtime/e2b/ntn-shim +95 -0
  38. package/runtime/e2b/scripts/build-template.sh +13 -7
  39. package/runtime/hetzner/agentbox-checkpoint-cleanup +5 -2
  40. package/runtime/hetzner/agentbox-setup-skill.md +112 -29
  41. package/runtime/hetzner/ctl.cjs +10353 -8575
  42. package/runtime/hetzner/linear-shim +181 -0
  43. package/runtime/hetzner/ntn-shim +95 -0
  44. package/runtime/hetzner/scripts/install-box.sh +19 -9
  45. package/runtime/relay/bin.cjs +3707 -2828
  46. package/runtime/vercel/agentbox-checkpoint-cleanup +5 -2
  47. package/runtime/vercel/agentbox-setup-skill.md +112 -29
  48. package/runtime/vercel/ctl.cjs +10353 -8575
  49. package/runtime/vercel/linear-shim +181 -0
  50. package/runtime/vercel/ntn-shim +95 -0
  51. package/runtime/vercel/scripts/provision.sh +13 -7
  52. package/share/agentbox-setup/SKILL.md +112 -29
  53. package/share/host-skills/agentbox-info/SKILL.md +22 -2
  54. package/dist/chunk-BYCLD6D6.js.map +0 -1
  55. package/dist/chunk-LDMYHWUS.js.map +0 -1
  56. package/dist/chunk-TBSIJVSN.js.map +0 -1
  57. package/dist/chunk-TCS5HXJX.js.map +0 -1
  58. package/dist/chunk-XKH7NTT7.js.map +0 -1
  59. /package/dist/{_cloud-attach-GUBB5RH2.js.map → _cloud-attach-5KJWOASL.js.map} +0 -0
  60. /package/dist/{chunk-RSKG7AFU.js.map → chunk-3WCEB6RE.js.map} +0 -0
  61. /package/dist/{dist-34RKQ74M.js.map → dist-4IQFJJQI.js.map} +0 -0
  62. /package/dist/{dist-4DPOL5A7.js.map → dist-7YB7BMNG.js.map} +0 -0
  63. /package/dist/{dist-3IMQNTTV.js.map → dist-SL2QSMBE.js.map} +0 -0
  64. /package/dist/{dist-J2IHD5T7.js.map → dist-VHI5QOSQ.js.map} +0 -0
  65. /package/dist/{dist-57M6ZA7H.js.map → dist-XC47DSCR.js.map} +0 -0
  66. /package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js.map → prepared-state-MQHD3M5F-2LANTRL7.js.map} +0 -0
@@ -1,15 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AmbiguousBoxError,
4
+ BoxNotFoundError,
3
5
  DEFAULT_BOX_IMAGE,
4
6
  GitWorktreeError,
7
+ ReplaceError,
5
8
  STATE_DIR,
6
9
  STATE_FILE,
7
10
  computeDockerContextFingerprint,
8
11
  detectGitRepos,
9
12
  ensureImage,
10
13
  findBox,
14
+ generateBoxId,
11
15
  hostOpenCommand,
12
16
  imageExists,
17
+ parseReplaceRules,
18
+ parseReplacements,
13
19
  pickFreshBranch,
14
20
  preparedMatches,
15
21
  pullOrBuild,
@@ -18,20 +24,22 @@ import {
18
24
  readState,
19
25
  recordBox,
20
26
  removeBoxRecord,
27
+ renderCarryEntries,
21
28
  reserveProjectIndex
22
- } from "./chunk-XKH7NTT7.js";
29
+ } from "./chunk-DBBUDKKB.js";
23
30
 
24
31
  // ../../packages/sandbox-docker/dist/index.js
25
32
  import { mkdir as mkdir7, stat as stat8 } from "fs/promises";
26
33
  import { homedir as homedir10 } from "os";
27
- import { basename as basename4, join as join12, resolve as resolve3 } from "path";
28
- import { execa as execa14 } from "execa";
34
+ import { basename as basename6, join as join12, resolve as resolve4 } from "path";
35
+ import { execa as execa15 } from "execa";
29
36
 
30
37
  // ../../packages/ctl/dist/index.js
31
- import { readFile } from "fs/promises";
32
- import { parse as parseYaml } from "yaml";
33
38
  import { readFile as readFile2 } from "fs/promises";
34
39
  import { parse as parseYaml2 } from "yaml";
40
+ import { parse as parseYaml } from "yaml";
41
+ import { readFile as readFile3 } from "fs/promises";
42
+ import { parse as parseYaml3 } from "yaml";
35
43
  var BOX_STATUS_EVENT = "box-status";
36
44
  function renderStatusTable(rows) {
37
45
  if (rows.length === 0) return "(no services configured)";
@@ -89,6 +97,20 @@ function truncate(s, n) {
89
97
  if (s.length <= n) return s;
90
98
  return s.slice(0, n - 1) + "\u2026";
91
99
  }
100
+ function isPlainObject(v) {
101
+ return typeof v === "object" && v !== null && !Array.isArray(v);
102
+ }
103
+ function parseReplacementsSection(text) {
104
+ let doc;
105
+ try {
106
+ doc = parseYaml(text);
107
+ } catch (err) {
108
+ throw new ReplaceError(`yaml parse error: ${err instanceof Error ? err.message : String(err)}`);
109
+ }
110
+ if (doc === null || doc === void 0) return {};
111
+ if (!isPlainObject(doc)) throw new ReplaceError("top-level config must be a mapping");
112
+ return parseReplacements(doc.replacements);
113
+ }
92
114
  var DEFAULT_BACKOFF = {
93
115
  initialMs: 500,
94
116
  maxMs: 3e4,
@@ -105,12 +127,12 @@ var ConfigError = class extends Error {
105
127
  this.name = "ConfigError";
106
128
  }
107
129
  };
108
- function isPlainObject(v) {
130
+ function isPlainObject2(v) {
109
131
  return typeof v === "object" && v !== null && !Array.isArray(v);
110
132
  }
111
133
  function parseEnv(raw, where) {
112
134
  if (raw === void 0 || raw === null) return void 0;
113
- if (!isPlainObject(raw)) {
135
+ if (!isPlainObject2(raw)) {
114
136
  throw new ConfigError(`${where}.env must be a mapping of string \u2192 string`);
115
137
  }
116
138
  const out = {};
@@ -152,7 +174,7 @@ function parseRestart(raw, where) {
152
174
  var BACKOFF_KEYS = /* @__PURE__ */ new Set(["initial_ms", "max_ms", "factor"]);
153
175
  function parseBackoff(raw, where) {
154
176
  if (raw === void 0) return { ...DEFAULT_BACKOFF };
155
- if (!isPlainObject(raw)) {
177
+ if (!isPlainObject2(raw)) {
156
178
  throw new ConfigError(`${where}.backoff must be a mapping`);
157
179
  }
158
180
  rejectUnknownKeys(raw, BACKOFF_KEYS, `${where}.backoff`);
@@ -234,7 +256,7 @@ var PROBE_KEYS = /* @__PURE__ */ new Set([
234
256
  ]);
235
257
  function parseReadyWhen(raw, where) {
236
258
  if (raw === void 0 || raw === null) return void 0;
237
- if (!isPlainObject(raw)) {
259
+ if (!isPlainObject2(raw)) {
238
260
  throw new ConfigError(`${where}.ready_when must be a mapping`);
239
261
  }
240
262
  rejectUnknownKeys(raw, PROBE_KEYS, `${where}.ready_when`);
@@ -330,12 +352,94 @@ var SERVICE_KEYS = /* @__PURE__ */ new Set([
330
352
  "needs",
331
353
  "ready_when",
332
354
  "expose",
333
- "ide"
355
+ "ide",
356
+ "image"
334
357
  ]);
358
+ var IMAGE_KEYS = /* @__PURE__ */ new Set(["name", "ports", "env", "args", "container_name"]);
359
+ function shQuote(s) {
360
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s)) return s;
361
+ return `'${s.replace(/'/g, `'\\''`)}'`;
362
+ }
363
+ function parsePorts(raw, where) {
364
+ if (raw === void 0 || raw === null) return void 0;
365
+ if (!Array.isArray(raw)) {
366
+ throw new ConfigError(`${where}.ports must be a list of "<host>:<container>" strings`);
367
+ }
368
+ const out = [];
369
+ for (const [i, v] of raw.entries()) {
370
+ const s = typeof v === "number" ? String(v) : v;
371
+ if (typeof s !== "string" || !/^\d+(:\d+)?$/.test(s.trim())) {
372
+ throw new ConfigError(
373
+ `${where}.ports[${String(i)}] must be "<host>" or "<host>:<container>" (got ${JSON.stringify(v)})`
374
+ );
375
+ }
376
+ out.push(s.trim());
377
+ }
378
+ return out.length > 0 ? out : void 0;
379
+ }
380
+ function parseArgs(raw, where) {
381
+ if (raw === void 0 || raw === null) return void 0;
382
+ if (typeof raw === "string") return raw.trim().length > 0 ? raw : void 0;
383
+ if (Array.isArray(raw)) {
384
+ const parts = [];
385
+ for (const [i, v] of raw.entries()) {
386
+ if (typeof v !== "string") throw new ConfigError(`${where}.args[${String(i)}] must be a string`);
387
+ parts.push(v);
388
+ }
389
+ const joined = parts.join(" ").trim();
390
+ return joined.length > 0 ? joined : void 0;
391
+ }
392
+ throw new ConfigError(`${where}.args must be a string or a list of strings`);
393
+ }
394
+ function synthesizeImageCommand(opts) {
395
+ const name = shQuote(opts.name);
396
+ const run = ["docker", "run", "--name", name];
397
+ for (const p of opts.ports ?? []) run.push("-p", shQuote(p));
398
+ for (const [k, v] of Object.entries(opts.env ?? {})) run.push("-e", `${k}=${shQuote(v)}`);
399
+ run.push(shQuote(opts.image));
400
+ let runLine = run.join(" ");
401
+ if (opts.args) runLine += ` ${opts.args}`;
402
+ return [
403
+ "set -e",
404
+ `if docker container inspect ${name} >/dev/null 2>&1; then`,
405
+ ` docker start ${name} >/dev/null`,
406
+ ` exec docker logs -f ${name}`,
407
+ "else",
408
+ ` exec ${runLine}`,
409
+ "fi"
410
+ ].join("\n");
411
+ }
412
+ function parseImage(raw, where, defaultName) {
413
+ if (typeof raw === "string") {
414
+ const name2 = raw.trim();
415
+ if (name2.length === 0) throw new ConfigError(`${where}.image must not be empty`);
416
+ return { name: name2, containerName: defaultName };
417
+ }
418
+ if (!isPlainObject2(raw)) {
419
+ throw new ConfigError(`${where}.image must be an image ref string or a mapping`);
420
+ }
421
+ rejectUnknownKeys(raw, IMAGE_KEYS, `${where}.image`);
422
+ const name = assertString(raw.name, `${where}.image.name`).trim();
423
+ if (name.length === 0) throw new ConfigError(`${where}.image.name must not be empty`);
424
+ const ports = parsePorts(raw.ports, `${where}.image`);
425
+ const args = parseArgs(raw.args, `${where}.image`);
426
+ const env = parseEnv(raw.env, `${where}.image`);
427
+ const containerName = raw.container_name === void 0 ? defaultName : assertString(raw.container_name, `${where}.image.container_name`).trim();
428
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(containerName)) {
429
+ throw new ConfigError(
430
+ `${where}.image.container_name "${containerName}" is not a valid docker container name`
431
+ );
432
+ }
433
+ const out = { name, containerName };
434
+ if (ports !== void 0) out.ports = ports;
435
+ if (env !== void 0) out.env = env;
436
+ if (args !== void 0) out.args = args;
437
+ return out;
438
+ }
335
439
  var EXPOSE_KEYS = /* @__PURE__ */ new Set(["port", "as"]);
336
440
  function parseExpose(raw, where) {
337
441
  if (raw === void 0 || raw === null) return void 0;
338
- if (!isPlainObject(raw)) {
442
+ if (!isPlainObject2(raw)) {
339
443
  throw new ConfigError(`${where}.expose must be a mapping`);
340
444
  }
341
445
  rejectUnknownKeys(raw, EXPOSE_KEYS, `${where}.expose`);
@@ -359,25 +463,78 @@ function parsePortNumber(raw, where) {
359
463
  }
360
464
  function parseService(name, raw) {
361
465
  const where = `services.${name}`;
362
- if (!isPlainObject(raw)) {
466
+ if (!isPlainObject2(raw)) {
363
467
  throw new ConfigError(`${where} must be a mapping`);
364
468
  }
365
469
  rejectUnknownKeys(raw, SERVICE_KEYS, where);
366
- const command = parseCommand(raw.command, where);
470
+ const hasImage = raw.image !== void 0 && raw.image !== null;
471
+ const hasCommand = raw.command !== void 0 && raw.command !== null;
472
+ if (hasImage && hasCommand) {
473
+ throw new ConfigError(`${where} sets both command and image \u2014 use exactly one`);
474
+ }
475
+ if (!hasImage && !hasCommand) {
476
+ throw new ConfigError(`${where} must set either command or image`);
477
+ }
367
478
  const cwd = raw.cwd === void 0 ? void 0 : assertString(raw.cwd, `${where}.cwd`);
368
- const env = parseEnv(raw.env, where);
369
479
  const autostart = raw.autostart === void 0 ? true : assertBool(raw.autostart, `${where}.autostart`);
370
480
  const restart2 = parseRestart(raw.restart, where);
371
481
  const backoff = parseBackoff(raw.backoff, where);
372
482
  const needs = parseNeeds(raw.needs, `${where}.needs`);
373
483
  const readyWhen = parseReadyWhen(raw.ready_when, where);
374
484
  const expose = parseExpose(raw.expose, where);
485
+ if (hasImage) {
486
+ if (raw.env !== void 0) {
487
+ throw new ConfigError(`${where}.env is not valid for an image service \u2014 use image.env`);
488
+ }
489
+ const img = parseImage(raw.image, where, name);
490
+ const command2 = synthesizeImageCommand({
491
+ image: img.name,
492
+ name: img.containerName,
493
+ ports: img.ports,
494
+ env: img.env,
495
+ args: img.args
496
+ });
497
+ const spec = {
498
+ name,
499
+ command: command2,
500
+ cwd,
501
+ autostart,
502
+ restart: restart2,
503
+ backoff,
504
+ needs,
505
+ readyWhen,
506
+ expose,
507
+ image: img.name,
508
+ containerName: img.containerName
509
+ };
510
+ if (img.ports !== void 0) spec.ports = img.ports;
511
+ if (img.args !== void 0) spec.args = img.args;
512
+ return spec;
513
+ }
514
+ const command = parseCommand(raw.command, where);
515
+ const env = parseEnv(raw.env, where);
375
516
  return { name, command, cwd, env, autostart, restart: restart2, backoff, needs, readyWhen, expose };
376
517
  }
377
- var TASK_KEYS = /* @__PURE__ */ new Set(["command", "cwd", "env", "needs"]);
518
+ var TASK_KEYS = /* @__PURE__ */ new Set(["command", "cwd", "env", "needs", "run_once"]);
519
+ function parseRunOnce(raw, where) {
520
+ if (raw === void 0 || raw === null || raw === false) return void 0;
521
+ if (raw === true) return { kind: "marker" };
522
+ if (isPlainObject2(raw)) {
523
+ const keys = Object.keys(raw);
524
+ if (keys.length !== 1 || keys[0] !== "check") {
525
+ throw new ConfigError(`${where}.run_once object form must be exactly { check: <command> }`);
526
+ }
527
+ const check = raw.check;
528
+ if (typeof check !== "string" || check.trim().length === 0) {
529
+ throw new ConfigError(`${where}.run_once.check must be a non-empty command string`);
530
+ }
531
+ return { kind: "check", command: check };
532
+ }
533
+ throw new ConfigError(`${where}.run_once must be true or { check: <command> }`);
534
+ }
378
535
  function parseTask(name, raw) {
379
536
  const where = `tasks.${name}`;
380
- if (!isPlainObject(raw)) {
537
+ if (!isPlainObject2(raw)) {
381
538
  throw new ConfigError(`${where} must be a mapping`);
382
539
  }
383
540
  rejectUnknownKeys(raw, TASK_KEYS, where);
@@ -385,7 +542,10 @@ function parseTask(name, raw) {
385
542
  const cwd = raw.cwd === void 0 ? void 0 : assertString(raw.cwd, `${where}.cwd`);
386
543
  const env = parseEnv(raw.env, where);
387
544
  const needs = parseNeeds(raw.needs, `${where}.needs`);
388
- return { name, command, cwd, env, needs };
545
+ const runOnce = parseRunOnce(raw.run_once, where);
546
+ const spec = { name, command, cwd, env, needs };
547
+ if (runOnce !== void 0) spec.runOnce = runOnce;
548
+ return spec;
389
549
  }
390
550
  function assertString(raw, where) {
391
551
  if (typeof raw !== "string") throw new ConfigError(`${where} must be a string`);
@@ -395,7 +555,7 @@ function assertBool(raw, where) {
395
555
  if (typeof raw !== "boolean") throw new ConfigError(`${where} must be a boolean`);
396
556
  return raw;
397
557
  }
398
- var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["services", "tasks", "ide", "defaults", "carry"]);
558
+ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["services", "tasks", "ide", "defaults", "carry", "replacements"]);
399
559
  function validateUnitGraph(tasks, services) {
400
560
  const names = /* @__PURE__ */ new Set();
401
561
  for (const t of tasks) {
@@ -449,19 +609,19 @@ function validateUnitGraph(tasks, services) {
449
609
  function parseConfig(text) {
450
610
  let doc;
451
611
  try {
452
- doc = parseYaml(text);
612
+ doc = parseYaml2(text);
453
613
  } catch (err) {
454
614
  throw new ConfigError(`yaml parse error: ${err instanceof Error ? err.message : String(err)}`);
455
615
  }
456
- if (doc === null || doc === void 0) return { services: [], tasks: [] };
457
- if (!isPlainObject(doc)) {
616
+ if (doc === null || doc === void 0) return { services: [], tasks: [], replacements: {} };
617
+ if (!isPlainObject2(doc)) {
458
618
  throw new ConfigError("top-level config must be a mapping");
459
619
  }
460
620
  rejectUnknownKeys(doc, TOP_LEVEL_KEYS, "(root)");
461
621
  const services = [];
462
622
  const servicesRaw = doc.services;
463
623
  if (servicesRaw !== void 0 && servicesRaw !== null) {
464
- if (!isPlainObject(servicesRaw)) {
624
+ if (!isPlainObject2(servicesRaw)) {
465
625
  throw new ConfigError("services must be a mapping of name \u2192 service");
466
626
  }
467
627
  for (const [name, raw] of Object.entries(servicesRaw)) {
@@ -474,7 +634,7 @@ function parseConfig(text) {
474
634
  const tasks = [];
475
635
  const tasksRaw = doc.tasks;
476
636
  if (tasksRaw !== void 0 && tasksRaw !== null) {
477
- if (!isPlainObject(tasksRaw)) {
637
+ if (!isPlainObject2(tasksRaw)) {
478
638
  throw new ConfigError("tasks must be a mapping of name \u2192 task");
479
639
  }
480
640
  for (const [name, raw] of Object.entries(tasksRaw)) {
@@ -484,10 +644,10 @@ function parseConfig(text) {
484
644
  tasks.push(parseTask(name, raw));
485
645
  }
486
646
  }
487
- if (doc.ide !== void 0 && doc.ide !== null && !isPlainObject(doc.ide)) {
647
+ if (doc.ide !== void 0 && doc.ide !== null && !isPlainObject2(doc.ide)) {
488
648
  throw new ConfigError("ide must be a mapping");
489
649
  }
490
- if (doc.defaults !== void 0 && doc.defaults !== null && !isPlainObject(doc.defaults)) {
650
+ if (doc.defaults !== void 0 && doc.defaults !== null && !isPlainObject2(doc.defaults)) {
491
651
  throw new ConfigError("defaults must be a mapping");
492
652
  }
493
653
  validateUnitGraph(tasks, services);
@@ -497,15 +657,21 @@ function parseConfig(text) {
497
657
  `at most one service may set expose: (got: ${exposed.map((s) => s.name).join(", ")})`
498
658
  );
499
659
  }
500
- return { services, tasks };
660
+ let replacements;
661
+ try {
662
+ replacements = parseReplacements(doc.replacements);
663
+ } catch (err) {
664
+ throw new ConfigError(err instanceof Error ? err.message : String(err));
665
+ }
666
+ return { services, tasks, replacements };
501
667
  }
502
668
  async function loadConfig(path) {
503
669
  let text;
504
670
  try {
505
- text = await readFile(path, "utf8");
671
+ text = await readFile2(path, "utf8");
506
672
  } catch (err) {
507
673
  if (err.code === "ENOENT") {
508
- return { services: [], tasks: [] };
674
+ return { services: [], tasks: [], replacements: {} };
509
675
  }
510
676
  throw err;
511
677
  }
@@ -517,7 +683,31 @@ var CarryConfigError = class extends Error {
517
683
  this.name = "CarryConfigError";
518
684
  }
519
685
  };
520
- var ITEM_KEYS = /* @__PURE__ */ new Set(["src", "dest", "mode", "user", "optional"]);
686
+ var ITEM_KEYS = /* @__PURE__ */ new Set([
687
+ "src",
688
+ "dest",
689
+ "mode",
690
+ "user",
691
+ "exclude",
692
+ "optional",
693
+ "replaceEnvs",
694
+ "replace",
695
+ "rules"
696
+ ]);
697
+ function parseStringList(raw, where, desc) {
698
+ if (raw === void 0 || raw === null) return void 0;
699
+ if (!Array.isArray(raw)) {
700
+ throw new CarryConfigError(`${where} must be a list of ${desc}`);
701
+ }
702
+ const out = [];
703
+ for (const [i, v] of raw.entries()) {
704
+ if (typeof v !== "string" || v.trim().length === 0) {
705
+ throw new CarryConfigError(`${where}[${String(i)}] must be a non-empty string`);
706
+ }
707
+ out.push(v.trim());
708
+ }
709
+ return out.length > 0 ? out : void 0;
710
+ }
521
711
  function parseUser(raw, where) {
522
712
  if (raw === void 0 || raw === null) return void 0;
523
713
  let n;
@@ -542,7 +732,7 @@ function parseUser(raw, where) {
542
732
  }
543
733
  return n;
544
734
  }
545
- function isPlainObject2(v) {
735
+ function isPlainObject3(v) {
546
736
  return typeof v === "object" && v !== null && !Array.isArray(v);
547
737
  }
548
738
  function assertSrcShape(src, where) {
@@ -646,6 +836,7 @@ function parseMapping(raw, where) {
646
836
  assertDestShape(dest, where);
647
837
  const mode = parseMode(raw.mode, where);
648
838
  const user = parseUser(raw.user, where);
839
+ const exclude = parseStringList(raw.exclude, `${where}.exclude`, "glob/name strings");
649
840
  let optional = false;
650
841
  if (raw.optional !== void 0 && raw.optional !== null) {
651
842
  if (typeof raw.optional !== "boolean") {
@@ -653,9 +844,30 @@ function parseMapping(raw, where) {
653
844
  }
654
845
  optional = raw.optional;
655
846
  }
847
+ let replaceEnvs;
848
+ if (raw.replaceEnvs !== void 0 && raw.replaceEnvs !== null) {
849
+ if (typeof raw.replaceEnvs !== "boolean") {
850
+ throw new CarryConfigError(`${where}.replaceEnvs must be a boolean`);
851
+ }
852
+ replaceEnvs = raw.replaceEnvs;
853
+ }
854
+ let replace;
855
+ if (raw.replace !== void 0 && raw.replace !== null) {
856
+ try {
857
+ const rules2 = parseReplaceRules(raw.replace, `${where}.replace`);
858
+ if (rules2.length > 0) replace = rules2;
859
+ } catch (err) {
860
+ throw new CarryConfigError(err instanceof Error ? err.message : String(err));
861
+ }
862
+ }
863
+ const rules = parseStringList(raw.rules, `${where}.rules`, "replacements rule-set names");
656
864
  const out = { src, dest, optional };
657
865
  if (mode !== void 0) out.mode = mode;
658
866
  if (user !== void 0) out.user = user;
867
+ if (exclude !== void 0) out.exclude = exclude;
868
+ if (replaceEnvs !== void 0) out.replaceEnvs = replaceEnvs;
869
+ if (replace !== void 0) out.replace = replace;
870
+ if (rules !== void 0) out.rules = rules;
659
871
  return out;
660
872
  }
661
873
  function parseCarryRaw(raw) {
@@ -668,7 +880,7 @@ function parseCarryRaw(raw) {
668
880
  const where = `carry[${String(i)}]`;
669
881
  if (typeof item === "string") {
670
882
  out.push(parseShorthand(item, where));
671
- } else if (isPlainObject2(item)) {
883
+ } else if (isPlainObject3(item)) {
672
884
  out.push(parseMapping(item, where));
673
885
  } else {
674
886
  throw new CarryConfigError(`${where} must be a string or mapping`);
@@ -679,14 +891,14 @@ function parseCarryRaw(raw) {
679
891
  function parseCarrySection(text) {
680
892
  let doc;
681
893
  try {
682
- doc = parseYaml2(text);
894
+ doc = parseYaml3(text);
683
895
  } catch (err) {
684
896
  throw new CarryConfigError(
685
897
  `yaml parse error: ${err instanceof Error ? err.message : String(err)}`
686
898
  );
687
899
  }
688
900
  if (doc === null || doc === void 0) return [];
689
- if (!isPlainObject2(doc)) {
901
+ if (!isPlainObject3(doc)) {
690
902
  throw new CarryConfigError("top-level config must be a mapping");
691
903
  }
692
904
  return parseCarryRaw(doc.carry);
@@ -694,7 +906,7 @@ function parseCarrySection(text) {
694
906
  async function loadCarrySection(path) {
695
907
  let text;
696
908
  try {
697
- text = await readFile2(path, "utf8");
909
+ text = await readFile3(path, "utf8");
698
910
  } catch (err) {
699
911
  if (err.code === "ENOENT") return [];
700
912
  throw err;
@@ -702,27 +914,13 @@ async function loadCarrySection(path) {
702
914
  return parseCarrySection(text);
703
915
  }
704
916
 
705
- // ../../packages/sandbox-docker/dist/index.js
706
- import { spawnSync } from "child_process";
707
- import { mkdir as mkdir3, mkdtemp as mkdtemp2, readdir as readdir3, readFile as readFile42, realpath as realpath2, rm as rm2, stat as stat3, writeFile as writeFile22 } from "fs/promises";
708
- import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
709
- import { isAbsolute as isAbsolute2, join as join4, relative as relative2 } from "path";
710
- import { setTimeout as delay } from "timers/promises";
711
- import { execa as execa5 } from "execa";
712
- import { execa as execa2 } from "execa";
713
- import { mkdir as mkdir4, readFile as readFile5, readdir as readdir4, stat as stat4 } from "fs/promises";
714
- import { createHash as createHash3 } from "crypto";
715
- import { homedir as homedir2 } from "os";
716
- import { join as join5 } from "path";
717
- import { execa as execa22 } from "execa";
718
-
719
917
  // ../../packages/config/dist/index.js
720
- import { parse as parseYaml3 } from "yaml";
918
+ import { parse as parseYaml4 } from "yaml";
721
919
  import { createHash } from "crypto";
722
920
  import { realpath, stat } from "fs/promises";
723
921
  import { homedir } from "os";
724
922
  import { basename, dirname, join, resolve } from "path";
725
- import { readFile as readFile3 } from "fs/promises";
923
+ import { readFile } from "fs/promises";
726
924
  import { parse as parseYaml22 } from "yaml";
727
925
  import { mkdir, readFile as readFile22, rename, rm, stat as stat2, writeFile } from "fs/promises";
728
926
  import { dirname as dirname2, isAbsolute, join as join2 } from "path";
@@ -748,6 +946,7 @@ var BUILT_IN_DEFAULTS = {
748
946
  withEnv: false,
749
947
  resyncOnStart: true,
750
948
  vnc: true,
949
+ autoApproveHostActions: false,
751
950
  isolateClaudeConfig: false,
752
951
  isolateCodexConfig: false,
753
952
  isolateOpencodeConfig: false,
@@ -768,7 +967,8 @@ var BUILT_IN_DEFAULTS = {
768
967
  bundleDepth: void 0,
769
968
  vercelVcpus: 2,
770
969
  vercelTimeoutMs: 27e5,
771
- vercelNetworkPolicy: ""
970
+ vercelNetworkPolicy: "",
971
+ cpMaxBytes: 100 * 1024 * 1024
772
972
  },
773
973
  checkpoint: {
774
974
  maxLayers: 3
@@ -824,7 +1024,8 @@ var BUILT_IN_DEFAULTS = {
824
1024
  enabled: true,
825
1025
  maxConcurrent: 5,
826
1026
  maxWorking: 0,
827
- idleGraceSeconds: 15
1027
+ idleGraceSeconds: 15,
1028
+ openIn: "none"
828
1029
  },
829
1030
  cloud: {
830
1031
  useCurrentBranch: false
@@ -832,6 +1033,10 @@ var BUILT_IN_DEFAULTS = {
832
1033
  maintenance: {
833
1034
  pruneProjectConfigs: true,
834
1035
  pruneProjectConfigsEvery: 50
1036
+ },
1037
+ integrations: {
1038
+ notion: { enabled: false },
1039
+ linear: { enabled: false }
835
1040
  }
836
1041
  };
837
1042
  var KEY_REGISTRY = [
@@ -942,6 +1147,11 @@ var KEY_REGISTRY = [
942
1147
  type: "bool",
943
1148
  description: "Run the per-box Xvnc + noVNC stack."
944
1149
  },
1150
+ {
1151
+ key: "box.autoApproveHostActions",
1152
+ type: "bool",
1153
+ description: "Auto-approve host-action confirmations (git push, cp host<->box, gh PR writes, checkpoint) for this box without an interactive prompt. Off by default; intended for unattended orchestration of trusted boxes. Each auto-approval is recorded as a relay event (visible in `agentbox agent` / the dashboard)."
1154
+ },
945
1155
  {
946
1156
  key: "box.isolateClaudeConfig",
947
1157
  type: "bool",
@@ -1040,6 +1250,12 @@ var KEY_REGISTRY = [
1040
1250
  type: "int",
1041
1251
  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."
1042
1252
  },
1253
+ {
1254
+ key: "box.cpMaxBytes",
1255
+ type: "int",
1256
+ description: "Max bytes a single host\u2192box copy may transfer after excludes, shared by `agentbox cp` (blocked with a size breakdown unless --yes) and each `carry:` entry (rejected at resolve time). Default 104857600 (100 MiB).",
1257
+ advanced: true
1258
+ },
1043
1259
  {
1044
1260
  key: "box.vercelNetworkPolicy",
1045
1261
  type: "string",
@@ -1187,6 +1403,12 @@ var KEY_REGISTRY = [
1187
1403
  type: "int",
1188
1404
  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."
1189
1405
  },
1406
+ {
1407
+ key: "queue.openIn",
1408
+ type: "enum",
1409
+ enumValues: ["none", "split", "window", "tab"],
1410
+ description: "When a background `-i` job finishes creating its box, where the host relay opens an attached terminal onto it: `none` (default \u2014 open nothing, just queue), `split`, `window`, or `tab`. Honored only when the submitting shell runs inside tmux, cmux, or iTerm2 (the targeting is captured at submit time). Under cmux, `split` splits the pane you submitted from (falling back to the parent workspace, then a new workspace), `tab` adds a tab in the parent workspace, and `window` opens a separate workspace; iTerm2 opens relative to the frontmost window. Unlike `attach.openIn` there is no `same` mode \u2014 the box is created asynchronously, so it is always a fresh terminal."
1411
+ },
1190
1412
  {
1191
1413
  key: "cloud.useCurrentBranch",
1192
1414
  type: "bool",
@@ -1201,6 +1423,16 @@ var KEY_REGISTRY = [
1201
1423
  key: "maintenance.pruneProjectConfigsEvery",
1202
1424
  type: "int",
1203
1425
  description: "Run the orphan project-config sweep every N successful `agentbox create`."
1426
+ },
1427
+ {
1428
+ key: "integrations.notion.enabled",
1429
+ type: "bool",
1430
+ description: 'Enable the in-box Notion integration shim (`ntn`/`notion` commands routed via the host relay). When false (default), the relay refuses dispatch with a clear "disabled" error and no host process is touched.'
1431
+ },
1432
+ {
1433
+ key: "integrations.linear.enabled",
1434
+ type: "bool",
1435
+ description: 'Enable the in-box Linear integration shim (`linear` commands routed via the host relay; backed by `@schpet/linear-cli`). When false (default), the relay refuses dispatch with a clear "disabled" error and no host process is touched.'
1204
1436
  }
1205
1437
  ];
1206
1438
  var REGISTRY_BY_KEY = new Map(KEY_REGISTRY.map((d) => [d.key, d]));
@@ -1213,7 +1445,7 @@ var UserConfigError = class extends Error {
1213
1445
  this.name = "UserConfigError";
1214
1446
  }
1215
1447
  };
1216
- function isPlainObject3(v) {
1448
+ function isPlainObject4(v) {
1217
1449
  return typeof v === "object" && v !== null && !Array.isArray(v);
1218
1450
  }
1219
1451
  var RENAMED_KEYS = /* @__PURE__ */ new Map([["box.snapshot", "box.hostSnapshot"]]);
@@ -1270,21 +1502,21 @@ function coerceTypedValue(raw, desc, where) {
1270
1502
  function parseUserConfig(text, where) {
1271
1503
  let doc;
1272
1504
  try {
1273
- doc = parseYaml3(text);
1505
+ doc = parseYaml4(text);
1274
1506
  } catch (err) {
1275
1507
  throw new UserConfigError(
1276
1508
  `${where}: yaml parse error: ${err instanceof Error ? err.message : String(err)}`
1277
1509
  );
1278
1510
  }
1279
1511
  if (doc === null || doc === void 0) return {};
1280
- if (!isPlainObject3(doc)) {
1512
+ if (!isPlainObject4(doc)) {
1281
1513
  throw new UserConfigError(`${where}: top-level must be a mapping`);
1282
1514
  }
1283
1515
  return parseUserConfigObject(doc, where);
1284
1516
  }
1285
1517
  function parseUserConfigObject(doc, where) {
1286
1518
  if (doc === null || doc === void 0) return {};
1287
- if (!isPlainObject3(doc)) {
1519
+ if (!isPlainObject4(doc)) {
1288
1520
  throw new UserConfigError(`${where}: must be a mapping`);
1289
1521
  }
1290
1522
  const out = {};
@@ -1305,32 +1537,50 @@ function parseUserConfigObject(doc, where) {
1305
1537
  );
1306
1538
  }
1307
1539
  if (branchRaw === null || branchRaw === void 0) continue;
1308
- if (!isPlainObject3(branchRaw)) {
1540
+ if (!isPlainObject4(branchRaw)) {
1309
1541
  throw new UserConfigError(`${where}.${branchName}: must be a mapping`);
1310
1542
  }
1311
- const branchOut = {};
1312
- for (const [leafName, leafRaw] of Object.entries(branchRaw)) {
1313
- const desc = branchSpec.leaves.get(leafName);
1314
- if (!desc) {
1315
- const renamedTo = RENAMED_KEYS.get(`${branchName}.${leafName}`);
1316
- if (renamedTo) {
1317
- throw new UserConfigError(
1318
- `${where}.${branchName}.${leafName} was renamed to ${renamedTo} \u2014 update your config`
1319
- );
1320
- }
1321
- throw new UserConfigError(
1322
- `${where}.${branchName}: unknown key "${leafName}" (known: ${[...branchSpec.leaves.keys()].join(", ")})`
1323
- );
1324
- }
1325
- if (leafRaw === void 0) continue;
1326
- branchOut[leafName] = coerceTypedValue(leafRaw, desc, `${where}.${desc.key}`);
1327
- }
1543
+ const branchOut = parseBranchObject(branchSpec, branchName, branchRaw, "", where);
1328
1544
  if (Object.keys(branchOut).length > 0) {
1329
1545
  out[branchName] = branchOut;
1330
1546
  }
1331
1547
  }
1332
1548
  return out;
1333
1549
  }
1550
+ function parseBranchObject(branchSpec, branchName, raw, qualifiedPrefix, where) {
1551
+ const out = {};
1552
+ for (const [name, value] of Object.entries(raw)) {
1553
+ if (value === void 0) continue;
1554
+ const qualified = qualifiedPrefix ? `${qualifiedPrefix}.${name}` : name;
1555
+ const desc = branchSpec.leaves.get(qualified);
1556
+ if (desc) {
1557
+ out[name] = coerceTypedValue(value, desc, `${where}.${desc.key}`);
1558
+ continue;
1559
+ }
1560
+ if (isPlainObject4(value) && branchHasLeafBelow(branchSpec, qualified)) {
1561
+ const sub = parseBranchObject(branchSpec, branchName, value, qualified, where);
1562
+ if (Object.keys(sub).length > 0) out[name] = sub;
1563
+ continue;
1564
+ }
1565
+ const renamedTo = RENAMED_KEYS.get(`${branchName}.${qualified}`);
1566
+ if (renamedTo) {
1567
+ throw new UserConfigError(
1568
+ `${where}.${branchName}.${qualified} was renamed to ${renamedTo} \u2014 update your config`
1569
+ );
1570
+ }
1571
+ throw new UserConfigError(
1572
+ `${where}.${branchName}: unknown key "${qualified}" (known: ${[...branchSpec.leaves.keys()].join(", ")})`
1573
+ );
1574
+ }
1575
+ return out;
1576
+ }
1577
+ function branchHasLeafBelow(branchSpec, prefix) {
1578
+ const needle = `${prefix}.`;
1579
+ for (const leaf of branchSpec.leaves.keys()) {
1580
+ if (leaf.startsWith(needle)) return true;
1581
+ }
1582
+ return false;
1583
+ }
1334
1584
  function coerceFromString(key, raw) {
1335
1585
  const desc = lookupKeyOrThrow(key);
1336
1586
  switch (desc.type) {
@@ -1446,7 +1696,7 @@ async function configPathFor(scope, cwd) {
1446
1696
  async function loadOptionalUserConfig(path) {
1447
1697
  let text;
1448
1698
  try {
1449
- text = await readFile3(path, "utf8");
1699
+ text = await readFile(path, "utf8");
1450
1700
  } catch (err) {
1451
1701
  if (err.code === "ENOENT") return {};
1452
1702
  throw err;
@@ -1457,7 +1707,7 @@ async function loadProjectAgentboxDefaults(workspacePath) {
1457
1707
  const path = workspaceConfigFile(workspacePath);
1458
1708
  let text;
1459
1709
  try {
1460
- text = await readFile3(path, "utf8");
1710
+ text = await readFile(path, "utf8");
1461
1711
  } catch (err) {
1462
1712
  if (err.code === "ENOENT") return {};
1463
1713
  throw err;
@@ -1537,14 +1787,26 @@ function mergeLayers(input) {
1537
1787
  return { effective, sources };
1538
1788
  }
1539
1789
  function readLeaf(obj, branch, leaf) {
1540
- const b = obj[branch];
1541
- if (b === void 0 || b === null || typeof b !== "object") return void 0;
1542
- return b[leaf];
1790
+ let cur = obj[branch];
1791
+ for (const seg of leaf.split(".")) {
1792
+ if (cur === void 0 || cur === null || typeof cur !== "object") return void 0;
1793
+ cur = cur[seg];
1794
+ }
1795
+ return cur;
1543
1796
  }
1544
1797
  function writeLeaf(obj, branch, leaf, value) {
1545
- const b = obj[branch];
1546
- if (!b) return;
1547
- b[leaf] = value;
1798
+ let cur = obj[branch];
1799
+ if (!cur) return;
1800
+ const segs = leaf.split(".");
1801
+ for (let i = 0; i < segs.length - 1; i++) {
1802
+ const seg = segs[i];
1803
+ const next = cur[seg];
1804
+ if (next === void 0 || next === null || typeof next !== "object") {
1805
+ cur[seg] = {};
1806
+ }
1807
+ cur = cur[seg];
1808
+ }
1809
+ cur[segs[segs.length - 1]] = value;
1548
1810
  }
1549
1811
  function resolveDefaultCheckpoint(cfg, provider) {
1550
1812
  const perProvider = provider === "daytona" ? cfg.box.defaultCheckpointDaytona : provider === "hetzner" ? cfg.box.defaultCheckpointHetzner : provider === "vercel" ? cfg.box.defaultCheckpointVercel : provider === "e2b" ? cfg.box.defaultCheckpointE2b : cfg.box.defaultCheckpointDocker;
@@ -1720,25 +1982,34 @@ function stampSchema(doc) {
1720
1982
  }
1721
1983
  }
1722
1984
  function setLeaf(doc, key, value) {
1723
- const idx = key.indexOf(".");
1724
- const branch = key.slice(0, idx);
1725
- const leaf = key.slice(idx + 1);
1726
- const root = doc;
1727
- if (!root[branch] || typeof root[branch] !== "object") {
1728
- root[branch] = {};
1985
+ const segs = key.split(".");
1986
+ let cur = doc;
1987
+ for (let i = 0; i < segs.length - 1; i++) {
1988
+ const seg = segs[i];
1989
+ const next = cur[seg];
1990
+ if (!next || typeof next !== "object") {
1991
+ cur[seg] = {};
1992
+ }
1993
+ cur = cur[seg];
1729
1994
  }
1730
- root[branch][leaf] = value;
1995
+ cur[segs[segs.length - 1]] = value;
1731
1996
  }
1732
1997
  function unsetLeaf(doc, key) {
1733
- const idx = key.indexOf(".");
1734
- const branch = key.slice(0, idx);
1735
- const leaf = key.slice(idx + 1);
1736
- const root = doc;
1737
- const b = root[branch];
1738
- if (!b || typeof b !== "object" || !(leaf in b)) return false;
1739
- delete b[leaf];
1740
- if (Object.keys(b).length === 0) {
1741
- delete root[branch];
1998
+ const segs = key.split(".");
1999
+ const path = [doc];
2000
+ for (let i = 0; i < segs.length - 1; i++) {
2001
+ const seg = segs[i];
2002
+ const next = path[path.length - 1][seg];
2003
+ if (!next || typeof next !== "object") return false;
2004
+ path.push(next);
2005
+ }
2006
+ const leafSeg = segs[segs.length - 1];
2007
+ const leafContainer = path[path.length - 1];
2008
+ if (!(leafSeg in leafContainer)) return false;
2009
+ delete leafContainer[leafSeg];
2010
+ for (let i = path.length - 1; i > 0; i--) {
2011
+ if (Object.keys(path[i]).length > 0) break;
2012
+ delete path[i - 1][segs[i - 1]];
1742
2013
  }
1743
2014
  return true;
1744
2015
  }
@@ -1771,123 +2042,346 @@ async function touchProjectMeta(absPath) {
1771
2042
  }
1772
2043
 
1773
2044
  // ../../packages/sandbox-docker/dist/index.js
2045
+ import { spawnSync } from "child_process";
2046
+ import { mkdir as mkdir3, mkdtemp as mkdtemp2, readdir as readdir3, readFile as readFile42, realpath as realpath2, rm as rm2, stat as stat3, writeFile as writeFile22 } from "fs/promises";
2047
+ import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
2048
+ import { isAbsolute as isAbsolute2, join as join4, relative as relative2 } from "path";
2049
+ import { setTimeout as delay } from "timers/promises";
2050
+ import { execa as execa6 } from "execa";
2051
+ import { execa as execa2 } from "execa";
2052
+ import { mkdir as mkdir4, readFile as readFile5, readdir as readdir4, stat as stat4 } from "fs/promises";
2053
+ import { createHash as createHash3 } from "crypto";
2054
+ import { homedir as homedir2 } from "os";
2055
+ import { basename as basename2, dirname as dirname22, join as join5 } from "path";
2056
+ import { execa as execa3 } from "execa";
2057
+ import { existsSync, mkdirSync, renameSync, statSync } from "fs";
2058
+ import { basename as basename3, dirname as dirname3, posix, resolve as resolve2 } from "path";
2059
+ import { execa as execa22 } from "execa";
1774
2060
  import { copyFile, mkdtemp, readdir as readdir22, readFile as readFile32, rm as rm3, stat as stat22, writeFile as writeFile3 } from "fs/promises";
1775
2061
  import { homedir as homedir22, tmpdir } from "os";
1776
- import { basename as basename2, join as join32, relative } from "path";
1777
- import { execa as execa4 } from "execa";
2062
+ import { basename as basename4, join as join32, relative } from "path";
2063
+ import { execa as execa5 } from "execa";
1778
2064
  import { chmod, mkdir as mkdir22, readFile as readFile23 } from "fs/promises";
1779
- import { basename as basename3, join as join22 } from "path";
1780
- import { execa as execa3 } from "execa";
2065
+ import { basename as basename32, join as join22 } from "path";
2066
+ import { execa as execa4 } from "execa";
1781
2067
  import { spawnSync as spawnSync2 } from "child_process";
1782
2068
  import { stat as stat42 } from "fs/promises";
1783
2069
  import { homedir as homedir4 } from "os";
1784
2070
  import { join as join52 } from "path";
1785
- import { execa as execa6 } from "execa";
2071
+ import { execa as execa7 } from "execa";
1786
2072
  import { spawnSync as spawnSync3 } from "child_process";
1787
2073
  import { stat as stat5 } from "fs/promises";
1788
2074
  import { homedir as homedir5 } from "os";
1789
2075
  import { join as join6 } from "path";
1790
- import { execa as execa7 } from "execa";
1791
- import { randomBytes as randomBytes3 } from "crypto";
2076
+ import { execa as execa8 } from "execa";
2077
+ import { randomBytes as randomBytes2 } from "crypto";
1792
2078
  import { createHash as createHash22 } from "crypto";
1793
2079
  import { readFile as readFile52 } from "fs/promises";
1794
2080
  import { join as join7 } from "path";
1795
- import { execa as execa8 } from "execa";
1796
- import { existsSync } from "fs";
2081
+ import { execa as execa9 } from "execa";
2082
+ import { existsSync as existsSync2 } from "fs";
1797
2083
  import { readFile as readFile6 } from "fs/promises";
1798
2084
  import { homedir as homedir6 } from "os";
1799
2085
  import { join as join8 } from "path";
1800
- import { execa as execa9 } from "execa";
1801
-
1802
- // ../../packages/core/dist/index.js
1803
- import { randomBytes } from "crypto";
1804
- var claudeCodeLauncher = {
1805
- kind: "claude-code",
1806
- // claude treats its first positional argument as the seed user turn in
1807
- // interactive mode (`claude "<message>"`), so we slot the initial message
1808
- // ahead of any user-passed flags.
1809
- buildArgs(initialMessage, userArgs) {
1810
- if (!initialMessage) return [...userArgs];
1811
- return [initialMessage, ...userArgs];
1812
- }
1813
- };
1814
- var codexLauncher = {
1815
- kind: "codex",
1816
- buildArgs(initialMessage, userArgs) {
1817
- if (!initialMessage) return [...userArgs];
1818
- return [initialMessage, ...userArgs];
1819
- }
1820
- };
1821
- var opencodeLauncher = {
1822
- kind: "opencode",
1823
- buildArgs(initialMessage, userArgs) {
1824
- if (!initialMessage) return [...userArgs];
1825
- return [initialMessage, ...userArgs];
1826
- }
1827
- };
1828
- function resolveAgentLauncher(kind) {
1829
- if (kind === "claude-code") return claudeCodeLauncher;
1830
- if (kind === "codex") return codexLauncher;
1831
- if (kind === "opencode") return opencodeLauncher;
1832
- throw new Error(`unknown agent kind: ${String(kind)}`);
1833
- }
1834
- var UserFacingError = class extends Error {
1835
- constructor(message) {
1836
- super(message);
1837
- this.name = "UserFacingError";
1838
- }
1839
- };
1840
- var BoxNotFoundError = class extends Error {
1841
- constructor(query) {
1842
- super(`no agentbox matches "${query}"`);
1843
- this.query = query;
1844
- this.name = "BoxNotFoundError";
1845
- }
1846
- query;
1847
- };
1848
- var AmbiguousBoxError = class extends Error {
1849
- constructor(query, matches) {
1850
- const ids = matches.map((m) => m.id).join(", ");
1851
- super(`"${query}" matches multiple boxes: ${ids}`);
1852
- this.query = query;
1853
- this.matches = matches;
1854
- this.name = "AmbiguousBoxError";
1855
- }
1856
- query;
1857
- matches;
1858
- };
1859
- var BOX_ID_PREFIX = "b";
1860
- function generateBoxId() {
1861
- return `${BOX_ID_PREFIX}${randomBytes(4).toString("hex")}`;
1862
- }
1863
-
1864
- // ../../packages/sandbox-docker/dist/index.js
1865
2086
  import { execa as execa10 } from "execa";
2087
+ import { execa as execa11 } from "execa";
1866
2088
  import { mkdir as mkdir42, readdir as readdir42, rm as rm32, stat as stat6 } from "fs/promises";
1867
2089
  import { homedir as homedir7, platform } from "os";
1868
- import { join as join9, resolve as resolve2 } from "path";
2090
+ import { join as join9, resolve as resolve22 } from "path";
1869
2091
  import { mkdir as mkdir5, mkdtemp as mkdtemp3, readFile as readFile7, readdir as readdir5, rm as rm4, writeFile as writeFile32 } from "fs/promises";
1870
2092
  import { homedir as homedir8, tmpdir as tmpdir3 } from "os";
1871
- import { basename as basename32, join as join10 } from "path";
1872
- import { execa as execa11 } from "execa";
1873
- import { stat as stat7 } from "fs/promises";
2093
+ import { basename as basename5, join as join10 } from "path";
1874
2094
  import { execa as execa12 } from "execa";
2095
+ import { stat as stat7 } from "fs/promises";
1875
2096
  import { execa as execa13 } from "execa";
2097
+ import { execa as execa14 } from "execa";
1876
2098
  import { spawn } from "child_process";
1877
2099
  import { randomBytes as randomBytes22 } from "crypto";
1878
- import { existsSync as existsSync2, openSync } from "fs";
2100
+ import { existsSync as existsSync3, openSync } from "fs";
1879
2101
  import { cp, mkdir as mkdir6, readFile as readFile8, readdir as readdir6, rename as rename3, rm as rm5, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
1880
2102
  import { request as httpRequest } from "http";
1881
2103
  import { createRequire } from "module";
1882
2104
  import { homedir as homedir9 } from "os";
1883
- import { dirname as dirname3, join as join11, resolve as resolve22, sep } from "path";
2105
+ import { dirname as dirname32, join as join11, resolve as resolve3, sep } from "path";
1884
2106
  import { setTimeout as delay2 } from "timers/promises";
1885
2107
  import { fileURLToPath } from "url";
1886
2108
 
1887
2109
  // ../../packages/relay/dist/index.js
1888
- import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
2110
+ import { createHash as createHash2, randomBytes } from "crypto";
1889
2111
  import { execa } from "execa";
1890
- import { spawn as spawn3 } from "child_process";
2112
+
2113
+ // ../../packages/integrations/dist/index.js
2114
+ var linearConnector = {
2115
+ service: "linear",
2116
+ hostBin: "linear",
2117
+ detect: {
2118
+ versionArgs: ["--version"],
2119
+ authArgs: ["auth", "whoami"],
2120
+ installHint: "install @schpet/linear-cli: npm i -g @schpet/linear-cli",
2121
+ loginHint: "linear auth login"
2122
+ },
2123
+ ops: {
2124
+ whoami: {
2125
+ write: false,
2126
+ buildArgv: (args) => ["auth", "whoami", ...args]
2127
+ },
2128
+ "issue.list": {
2129
+ write: false,
2130
+ buildArgv: (args) => ["issue", "list", ...args]
2131
+ },
2132
+ "issue.mine": {
2133
+ // The v2-native read for "issues assigned to me" — the README directs
2134
+ // users here in place of the older `issue list --me`. Listed as a
2135
+ // separate op so the shim doesn't reject the canonical form.
2136
+ write: false,
2137
+ buildArgv: (args) => ["issue", "mine", ...args]
2138
+ },
2139
+ "issue.view": {
2140
+ write: false,
2141
+ buildArgv: (args) => ["issue", "view", ...args]
2142
+ },
2143
+ "issue.query": {
2144
+ write: false,
2145
+ buildArgv: (args) => ["issue", "query", ...args]
2146
+ },
2147
+ "team.list": {
2148
+ write: false,
2149
+ buildArgv: (args) => ["team", "list", ...args]
2150
+ },
2151
+ api: {
2152
+ write: false,
2153
+ buildArgv: (args) => ["api", ...args],
2154
+ refuseCall: refuseGraphqlNonQuery
2155
+ },
2156
+ "issue.create": {
2157
+ write: true,
2158
+ buildArgv: (args) => ["issue", "create", ...args]
2159
+ },
2160
+ "issue.update": {
2161
+ write: true,
2162
+ buildArgv: (args) => ["issue", "update", ...args]
2163
+ },
2164
+ "issue.comment": {
2165
+ // Maps to `linear issue comment add` — `@schpet/linear-cli` v2 uses
2166
+ // `add` (not `create`); `add`'s sibling subcommands are `list`,
2167
+ // `update`, `delete`.
2168
+ write: true,
2169
+ buildArgv: (args) => ["issue", "comment", "add", ...args]
2170
+ }
2171
+ }
2172
+ };
2173
+ function refuseGraphqlNonQuery(args) {
2174
+ const refuse = (reason) => ({
2175
+ exitCode: 65,
2176
+ stderr: `linear api: ${reason}
2177
+ `
2178
+ });
2179
+ for (let i = 0; i < args.length; i++) {
2180
+ const arg = args[i] ?? "";
2181
+ if (arg === "--input" || arg.startsWith("--input=")) {
2182
+ return refuse("'--input' (stdin/file body) isn't supported through the relay");
2183
+ }
2184
+ if (arg === "--variable") {
2185
+ const next = args[i + 1] ?? "";
2186
+ if (variableValueIsFileLoad(next)) {
2187
+ return refuse(
2188
+ "'--variable key=@<path>' (host-file load) isn't supported through the relay"
2189
+ );
2190
+ }
2191
+ if (!next.startsWith("--")) i++;
2192
+ continue;
2193
+ }
2194
+ if (arg.startsWith("--variable=")) {
2195
+ if (variableValueIsFileLoad(arg.slice("--variable=".length))) {
2196
+ return refuse(
2197
+ "'--variable=key=@<path>' (host-file load) isn't supported through the relay"
2198
+ );
2199
+ }
2200
+ continue;
2201
+ }
2202
+ if (arg === "--variables-json") {
2203
+ const next = args[i + 1] ?? "";
2204
+ if (!next.startsWith("--")) i++;
2205
+ continue;
2206
+ }
2207
+ if (arg.startsWith("--variables-json=")) {
2208
+ continue;
2209
+ }
2210
+ if (arg.startsWith("--")) continue;
2211
+ const op = firstGraphqlOperationKeyword(arg);
2212
+ if (op === "mutation" || op === "subscription") {
2213
+ return refuse(
2214
+ `only GraphQL queries are proxied (use issue.create / issue.update / issue.comment for writes); detected operation '${op}'`
2215
+ );
2216
+ }
2217
+ if (op === "unparseable") {
2218
+ return refuse(
2219
+ `couldn't classify positional argv ${JSON.stringify(arg)} as a GraphQL operation (expected 'query', 'mutation', 'subscription', or '{')`
2220
+ );
2221
+ }
2222
+ }
2223
+ return null;
2224
+ }
2225
+ function variableValueIsFileLoad(value) {
2226
+ if (value.startsWith("@")) return true;
2227
+ return value.includes("=@");
2228
+ }
2229
+ function firstGraphqlOperationKeyword(source) {
2230
+ let i = 0;
2231
+ const n = source.length;
2232
+ while (i < n) {
2233
+ const c = source[i];
2234
+ if (/\s/.test(c) || c === "," || c === "\uFEFF") {
2235
+ i++;
2236
+ continue;
2237
+ }
2238
+ if (c === "#") {
2239
+ while (i < n && source[i] !== "\n") i++;
2240
+ continue;
2241
+ }
2242
+ break;
2243
+ }
2244
+ if (i >= n) return null;
2245
+ if (source[i] === "{") return "anonymous";
2246
+ let j = i;
2247
+ while (j < n) {
2248
+ const c = source[j];
2249
+ if (c >= "a" && c <= "z" || c >= "A" && c <= "Z") {
2250
+ j++;
2251
+ } else {
2252
+ break;
2253
+ }
2254
+ }
2255
+ if (j === i) return "unparseable";
2256
+ return source.slice(i, j).toLowerCase();
2257
+ }
2258
+ var notionConnector = {
2259
+ service: "notion",
2260
+ hostBin: "ntn",
2261
+ detect: {
2262
+ versionArgs: ["--version"],
2263
+ authArgs: ["api", "v1/users/me"],
2264
+ installHint: "install ntn: https://developers.notion.com/reference/notion-cli",
2265
+ loginHint: "ntn login"
2266
+ },
2267
+ ops: {
2268
+ whoami: {
2269
+ write: false,
2270
+ buildArgv: (args) => ["whoami", ...args]
2271
+ },
2272
+ api: {
2273
+ write: false,
2274
+ buildArgv: (args) => ["api", ...args],
2275
+ refuseCall: refuseUnsafeApiCall
2276
+ },
2277
+ "page.create": {
2278
+ write: true,
2279
+ buildArgv: (args) => ["pages", "create", ...args]
2280
+ },
2281
+ "page.update": {
2282
+ write: true,
2283
+ buildArgv: (args) => ["pages", "update", ...args]
2284
+ }
2285
+ }
2286
+ };
2287
+ var READ_POST_ENDPOINTS = [
2288
+ /^\/?v1\/search$/,
2289
+ /^\/?v1\/databases\/[^/?]+\/query$/,
2290
+ /^\/?v1\/data_sources\/[^/?]+\/query$/
2291
+ ];
2292
+ var SAFE_API_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
2293
+ "--spec",
2294
+ "--docs",
2295
+ "-h",
2296
+ "--help",
2297
+ "-v",
2298
+ "--verbose",
2299
+ "-V",
2300
+ "--version"
2301
+ ]);
2302
+ function refuseUnsafeApiCall(args) {
2303
+ const refuse = (reason) => ({
2304
+ exitCode: 65,
2305
+ stderr: `notion api: ${reason}
2306
+ `
2307
+ });
2308
+ let explicitMethod = null;
2309
+ let hasBody = false;
2310
+ let endpoint = null;
2311
+ for (let i = 0; i < args.length; i++) {
2312
+ const arg = args[i] ?? "";
2313
+ if (arg === "-X" || arg === "--method") {
2314
+ explicitMethod = args[i + 1] ?? "";
2315
+ i++;
2316
+ continue;
2317
+ }
2318
+ if (arg.startsWith("--method=")) {
2319
+ explicitMethod = arg.slice("--method=".length);
2320
+ continue;
2321
+ }
2322
+ if (arg.startsWith("-X") && arg.length > 2) {
2323
+ explicitMethod = arg.slice(2).replace(/^=/, "");
2324
+ continue;
2325
+ }
2326
+ if (arg === "-d" || arg === "--data") {
2327
+ hasBody = true;
2328
+ i++;
2329
+ continue;
2330
+ }
2331
+ if (arg.startsWith("--data=") || arg.startsWith("-d") && arg.length > 2) {
2332
+ hasBody = true;
2333
+ continue;
2334
+ }
2335
+ if (arg === "--file" || arg.startsWith("--file=")) {
2336
+ return refuse("'--file' (host-file upload) isn't supported through the relay");
2337
+ }
2338
+ if (arg === "--input" || arg.startsWith("--input=")) {
2339
+ return refuse("'--input' (stdin/file body) isn't supported through the relay; use -d <JSON>");
2340
+ }
2341
+ if (arg === "--notion-version") {
2342
+ i++;
2343
+ continue;
2344
+ }
2345
+ if (arg.startsWith("--notion-version=")) continue;
2346
+ if (arg.startsWith("-")) {
2347
+ if (SAFE_API_BOOLEAN_FLAGS.has(arg)) continue;
2348
+ return refuse(
2349
+ `unsupported option '${arg}' (read-only api accepts a path, inline inputs, -d <JSON>, -X GET/POST, --notion-version, --spec, --docs)`
2350
+ );
2351
+ }
2352
+ if (endpoint === null) {
2353
+ endpoint = arg;
2354
+ continue;
2355
+ }
2356
+ if (isBodyAssignment(arg)) hasBody = true;
2357
+ }
2358
+ const method = (explicitMethod && explicitMethod.length > 0 ? explicitMethod : hasBody ? "POST" : "GET").toUpperCase();
2359
+ if (method === "GET") return null;
2360
+ if (method !== "POST") {
2361
+ return refuse(
2362
+ `only GET and read-only POST are proxied (use page.create / page.update for writes); detected method '${method}'`
2363
+ );
2364
+ }
2365
+ if (endpoint === null) {
2366
+ return refuse("could not determine the API endpoint for a non-GET call");
2367
+ }
2368
+ if (endpoint.split("/").some((seg) => seg === "." || seg === "..")) {
2369
+ return refuse(`path traversal segments ('.' / '..') are not allowed in '${endpoint}'`);
2370
+ }
2371
+ if (READ_POST_ENDPOINTS.some((re) => re.test(endpoint))) return null;
2372
+ return refuse(
2373
+ `POST is only proxied for read endpoints (v1/search, v1/databases/{id}/query, v1/data_sources/{id}/query); '${endpoint}' is not one (use page.create / page.update for writes)`
2374
+ );
2375
+ }
2376
+ function isBodyAssignment(token) {
2377
+ if (token.includes(":=")) return true;
2378
+ if (token.includes("==")) return false;
2379
+ return token.includes("=");
2380
+ }
2381
+ var ALL_CONNECTORS = [notionConnector, linearConnector];
2382
+
2383
+ // ../../packages/relay/dist/index.js
2384
+ import { spawn as spawn4 } from "child_process";
1891
2385
  import {
1892
2386
  mkdir as mkdir2,
1893
2387
  readdir as readdir2,
@@ -2077,7 +2571,7 @@ async function uncachedBoxStateCount() {
2077
2571
  }
2078
2572
  function inspectDockerState(containerName) {
2079
2573
  return new Promise((resolveP) => {
2080
- const child = spawn3("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
2574
+ const child = spawn4("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
2081
2575
  stdio: ["ignore", "pipe", "pipe"]
2082
2576
  });
2083
2577
  let out = "";
@@ -2110,21 +2604,18 @@ function queueLogPath(id) {
2110
2604
  }
2111
2605
 
2112
2606
  // ../../packages/sandbox-docker/dist/index.js
2113
- import { execa as execa16 } from "execa";
2607
+ import { execa as execa17 } from "execa";
2114
2608
  import { readdir as readdir7, rm as rm6, stat as stat9 } from "fs/promises";
2115
2609
  import { join as join14 } from "path";
2116
- import { execa as execa15 } from "execa";
2610
+ import { execa as execa16 } from "execa";
2117
2611
  import { join as join13 } from "path";
2118
2612
  import { homedir as homedir11 } from "os";
2119
2613
  import { join as join15 } from "path";
2120
- import { execa as execa17 } from "execa";
2121
- import { existsSync as existsSync3, mkdirSync, renameSync, statSync } from "fs";
2122
- import { basename as basename5, dirname as dirname22, posix, resolve as resolve4 } from "path";
2123
2614
  import { execa as execa18 } from "execa";
2124
2615
  import { createHash as createHash32 } from "crypto";
2125
2616
  import { copyFile as copyFile2, mkdir as mkdir8, mkdtemp as mkdtemp4, readdir as readdir8, readFile as readFile9, rm as rm7, stat as stat10 } from "fs/promises";
2126
2617
  import { homedir as homedir12, tmpdir as tmpdir4 } from "os";
2127
- import { basename as basename6, dirname as dirname32, join as join16 } from "path";
2618
+ import { basename as basename7, dirname as dirname4, join as join16 } from "path";
2128
2619
  import { execa as execa19 } from "execa";
2129
2620
  function isHostPathHookCommand(command, hostHome) {
2130
2621
  if (typeof command !== "string" || command.length === 0) return false;
@@ -2251,16 +2742,16 @@ function rewritePluginPaths(value, hostPluginsPrefix) {
2251
2742
  }
2252
2743
  return value;
2253
2744
  }
2254
- function isPlainObject4(v) {
2745
+ function isPlainObject5(v) {
2255
2746
  return v !== null && typeof v === "object" && !Array.isArray(v);
2256
2747
  }
2257
2748
  function additiveMerge(hostRoot, boxRoot, hostPluginsPrefix, selectMap, withMap) {
2258
2749
  const hostMap = selectMap(hostRoot);
2259
2750
  const boxMap = selectMap(boxRoot);
2260
- if (!isPlainObject4(boxMap)) {
2751
+ if (!isPlainObject5(boxMap)) {
2261
2752
  return { data: hostRoot, changed: false, addedKeys: [] };
2262
2753
  }
2263
- const base = isPlainObject4(hostMap) ? { ...hostMap } : {};
2754
+ const base = isPlainObject5(hostMap) ? { ...hostMap } : {};
2264
2755
  const addedKeys = [];
2265
2756
  for (const [key, value] of Object.entries(boxMap)) {
2266
2757
  if (Object.prototype.hasOwnProperty.call(base, key)) continue;
@@ -2275,7 +2766,7 @@ function additiveMerge(hostRoot, boxRoot, hostPluginsPrefix, selectMap, withMap)
2275
2766
  function mergeKnownMarketplaces(hostJson, boxJson, opts) {
2276
2767
  const prefix = `${opts.hostHome}/.claude/plugins/`;
2277
2768
  return additiveMerge(
2278
- isPlainObject4(hostJson) ? hostJson : {},
2769
+ isPlainObject5(hostJson) ? hostJson : {},
2279
2770
  boxJson,
2280
2771
  prefix,
2281
2772
  (root) => root,
@@ -2284,24 +2775,24 @@ function mergeKnownMarketplaces(hostJson, boxJson, opts) {
2284
2775
  }
2285
2776
  function mergeInstalledPlugins(hostJson, boxJson, opts) {
2286
2777
  const prefix = `${opts.hostHome}/.claude/plugins/`;
2287
- const hostRoot = isPlainObject4(hostJson) ? hostJson : { plugins: {} };
2778
+ const hostRoot = isPlainObject5(hostJson) ? hostJson : { plugins: {} };
2288
2779
  return additiveMerge(
2289
2780
  hostRoot,
2290
2781
  boxJson,
2291
2782
  prefix,
2292
- (root) => isPlainObject4(root) ? root["plugins"] : void 0,
2783
+ (root) => isPlainObject5(root) ? root["plugins"] : void 0,
2293
2784
  (host, merged) => ({ ...host, plugins: merged })
2294
2785
  );
2295
2786
  }
2296
2787
  function referencedPluginVersionKeys(installedPluginsJson) {
2297
2788
  const keys = /* @__PURE__ */ new Set();
2298
- if (!isPlainObject4(installedPluginsJson)) return keys;
2789
+ if (!isPlainObject5(installedPluginsJson)) return keys;
2299
2790
  const plugins = installedPluginsJson["plugins"];
2300
- if (!isPlainObject4(plugins)) return keys;
2791
+ if (!isPlainObject5(plugins)) return keys;
2301
2792
  for (const entries of Object.values(plugins)) {
2302
2793
  if (!Array.isArray(entries)) continue;
2303
2794
  for (const entry of entries) {
2304
- if (!isPlainObject4(entry)) continue;
2795
+ if (!isPlainObject5(entry)) continue;
2305
2796
  const installPath = entry["installPath"];
2306
2797
  if (typeof installPath !== "string") continue;
2307
2798
  const segments = installPath.split("/").filter((s) => s.length > 0);
@@ -2483,48 +2974,181 @@ async function inspectContainer(name) {
2483
2974
  } catch {
2484
2975
  return null;
2485
2976
  }
2486
- }
2487
- async function inspectVolumeMountpoint(name) {
2488
- const result = await execa2("docker", ["volume", "inspect", "--format", "{{.Mountpoint}}", name], {
2489
- reject: false
2490
- });
2491
- if (result.exitCode !== 0) return null;
2492
- return (result.stdout ?? "").trim() || null;
2493
- }
2494
- var AGENTBOX_PREFIX = "agentbox-";
2495
- async function listAgentboxContainers() {
2496
- const result = await execa2(
2977
+ }
2978
+ async function inspectVolumeMountpoint(name) {
2979
+ const result = await execa2("docker", ["volume", "inspect", "--format", "{{.Mountpoint}}", name], {
2980
+ reject: false
2981
+ });
2982
+ if (result.exitCode !== 0) return null;
2983
+ return (result.stdout ?? "").trim() || null;
2984
+ }
2985
+ var AGENTBOX_PREFIX = "agentbox-";
2986
+ async function listAgentboxContainers() {
2987
+ const result = await execa2(
2988
+ "docker",
2989
+ ["ps", "-a", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Names}}"],
2990
+ { reject: false }
2991
+ );
2992
+ if (result.exitCode !== 0) return [];
2993
+ return (result.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.startsWith(AGENTBOX_PREFIX));
2994
+ }
2995
+ async function publishedHostPort(container, containerPort) {
2996
+ const result = await execa2("docker", ["port", container, `${String(containerPort)}/tcp`], {
2997
+ reject: false
2998
+ });
2999
+ if (result.exitCode !== 0) return null;
3000
+ const first = (result.stdout ?? "").split("\n")[0]?.trim();
3001
+ if (!first) return null;
3002
+ const m = /:(\d+)$/.exec(first);
3003
+ return m ? Number(m[1]) : null;
3004
+ }
3005
+ async function listAgentboxVolumes() {
3006
+ const result = await execa2(
3007
+ "docker",
3008
+ ["volume", "ls", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Name}}"],
3009
+ { reject: false }
3010
+ );
3011
+ if (result.exitCode !== 0) return [];
3012
+ return (result.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.startsWith(AGENTBOX_PREFIX));
3013
+ }
3014
+ function posixDirname(p) {
3015
+ return posix.dirname(p) || "/";
3016
+ }
3017
+ function asText(s) {
3018
+ if (s === void 0) return "";
3019
+ if (typeof s === "string") return s;
3020
+ return Buffer.from(s).toString("utf8");
3021
+ }
3022
+ function tarExcludeArgs(exclude) {
3023
+ return (exclude ?? []).map((p) => `--exclude=${p}`);
3024
+ }
3025
+ async function streamTarPipe(producerFile, producerArgs, consumerFile, consumerArgs, producerEnv) {
3026
+ const producer = execa22(producerFile, producerArgs, {
3027
+ reject: false,
3028
+ buffer: { stdout: false },
3029
+ ...producerEnv ? { env: producerEnv } : {}
3030
+ });
3031
+ const consumer = execa22(consumerFile, consumerArgs, {
3032
+ reject: false,
3033
+ buffer: { stdout: false }
3034
+ });
3035
+ producer.stdout?.pipe(consumer.stdin);
3036
+ const [p, c] = await Promise.all([producer, consumer]);
3037
+ return [
3038
+ { exitCode: p.exitCode, stderr: p.stderr },
3039
+ { exitCode: c.exitCode, stderr: c.stderr }
3040
+ ];
3041
+ }
3042
+ async function uploadToBox(box, hostSrc, boxDst, exclude) {
3043
+ const srcAbs = resolve2(hostSrc);
3044
+ if (!existsSync(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
3045
+ const srcBasename = basename3(srcAbs);
3046
+ const srcParent = dirname3(srcAbs);
3047
+ let boxParent;
3048
+ let finalName;
3049
+ if (boxDst.endsWith("/")) {
3050
+ boxParent = boxDst.replace(/\/+$/, "") || "/";
3051
+ finalName = srcBasename;
3052
+ } else {
3053
+ const isDir2 = await execa22(
3054
+ "docker",
3055
+ ["exec", box.container, "test", "-d", boxDst],
3056
+ { reject: false }
3057
+ );
3058
+ if (isDir2.exitCode === 0) {
3059
+ boxParent = boxDst.replace(/\/+$/, "") || "/";
3060
+ finalName = srcBasename;
3061
+ } else {
3062
+ boxParent = posixDirname(boxDst);
3063
+ finalName = posix.basename(boxDst);
3064
+ }
3065
+ }
3066
+ const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
3067
+ const mk = await execa22(
3068
+ "docker",
3069
+ ["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
3070
+ { reject: false }
3071
+ );
3072
+ if (mk.exitCode !== 0) {
3073
+ throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
3074
+ }
3075
+ const [packed, extracted] = await streamTarPipe(
3076
+ "tar",
3077
+ ["-C", srcParent, "-cf", "-", ...tarExcludeArgs(exclude), srcBasename],
3078
+ "docker",
3079
+ ["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
3080
+ { ...process.env, COPYFILE_DISABLE: "1" }
3081
+ );
3082
+ if (packed.exitCode !== 0) {
3083
+ throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
3084
+ }
3085
+ if (extracted.exitCode !== 0) {
3086
+ throw new Error(`tar extract in box failed: ${asText(extracted.stderr).slice(0, 300)}`);
3087
+ }
3088
+ if (finalName !== srcBasename) {
3089
+ const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
3090
+ const mv = await execa22(
3091
+ "docker",
3092
+ ["exec", "--user", "root", box.container, "mv", initial, finalPath],
3093
+ { reject: false }
3094
+ );
3095
+ if (mv.exitCode !== 0) {
3096
+ throw new Error(
3097
+ `rename ${initial} -> ${finalPath} in box failed: ${asText(mv.stderr).slice(0, 300)}`
3098
+ );
3099
+ }
3100
+ }
3101
+ const chown = await execa22(
2497
3102
  "docker",
2498
- ["ps", "-a", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Names}}"],
3103
+ ["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
2499
3104
  { reject: false }
2500
3105
  );
2501
- if (result.exitCode !== 0) return [];
2502
- return (result.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.startsWith(AGENTBOX_PREFIX));
2503
- }
2504
- async function publishedHostPort(container, containerPort) {
2505
- const result = await execa2("docker", ["port", container, `${String(containerPort)}/tcp`], {
2506
- reject: false
2507
- });
2508
- if (result.exitCode !== 0) return null;
2509
- const first = (result.stdout ?? "").split("\n")[0]?.trim();
2510
- if (!first) return null;
2511
- const m = /:(\d+)$/.exec(first);
2512
- return m ? Number(m[1]) : null;
3106
+ if (chown.exitCode !== 0) {
3107
+ return {
3108
+ finalPath,
3109
+ warn: `chown ${finalPath} to vscode (uid 1000) failed; ownership inside the box may be root.`
3110
+ };
3111
+ }
3112
+ return { finalPath };
2513
3113
  }
2514
- async function listAgentboxVolumes() {
2515
- const result = await execa2(
3114
+ async function downloadFromBox(box, boxSrc, hostDst, exclude) {
3115
+ const srcBasename = posix.basename(boxSrc);
3116
+ const srcParent = posixDirname(boxSrc);
3117
+ const dstAbs = resolve2(hostDst);
3118
+ let hostParent;
3119
+ let finalName;
3120
+ const dstExists = existsSync(dstAbs);
3121
+ if (hostDst.endsWith("/") || dstExists && statSync(dstAbs).isDirectory()) {
3122
+ hostParent = dstAbs;
3123
+ finalName = srcBasename;
3124
+ } else {
3125
+ hostParent = dirname3(dstAbs);
3126
+ finalName = basename3(dstAbs);
3127
+ }
3128
+ mkdirSync(hostParent, { recursive: true });
3129
+ const finalPath = posix.join(hostParent, finalName);
3130
+ const [packed, extracted] = await streamTarPipe(
2516
3131
  "docker",
2517
- ["volume", "ls", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Name}}"],
2518
- { reject: false }
3132
+ ["exec", box.container, "tar", "-C", srcParent, "-cf", "-", ...tarExcludeArgs(exclude), srcBasename],
3133
+ "tar",
3134
+ ["-xf", "-", "-C", hostParent]
2519
3135
  );
2520
- if (result.exitCode !== 0) return [];
2521
- return (result.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.startsWith(AGENTBOX_PREFIX));
3136
+ if (packed.exitCode !== 0) {
3137
+ throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
3138
+ }
3139
+ if (extracted.exitCode !== 0) {
3140
+ throw new Error(`tar extract on host failed: ${asText(extracted.stderr).slice(0, 300)}`);
3141
+ }
3142
+ if (finalName !== srcBasename) {
3143
+ renameSync(posix.join(hostParent, srcBasename), finalPath);
3144
+ }
3145
+ return { finalPath };
2522
3146
  }
2523
3147
  var CONTAINER_EXPORT_MERGED = "/host-export";
2524
3148
  var cachedEngine = null;
2525
3149
  async function detectEngine() {
2526
3150
  if (cachedEngine !== null) return cachedEngine;
2527
- const result = await execa22("docker", ["info", "--format", "{{.OperatingSystem}}"], {
3151
+ const result = await execa3("docker", ["info", "--format", "{{.OperatingSystem}}"], {
2528
3152
  reject: false
2529
3153
  });
2530
3154
  const os = (result.stdout ?? "").trim().toLowerCase();
@@ -2537,7 +3161,7 @@ function setEngineOverride(engine) {
2537
3161
  cachedEngine = engine;
2538
3162
  }
2539
3163
  async function getDockerContext() {
2540
- const result = await execa22("docker", ["context", "show"], { reject: false });
3164
+ const result = await execa3("docker", ["context", "show"], { reject: false });
2541
3165
  if (result.exitCode !== 0) return void 0;
2542
3166
  const ctx = (result.stdout ?? "").trim();
2543
3167
  return ctx.length > 0 ? ctx : void 0;
@@ -2597,7 +3221,7 @@ async function refreshExport(record, opts = {}) {
2597
3221
  return { hostPath: paths.mergedExport, copied: true, usedFallback: false };
2598
3222
  }
2599
3223
  const excludes = excludeNodeModules ? ["--exclude=node_modules"] : [];
2600
- const result = await execa22(
3224
+ const result = await execa3(
2601
3225
  "docker",
2602
3226
  ["exec", "--user", "root", record.container, "tar", "-cf", "-", ...excludes, "-C", "/workspace", "."],
2603
3227
  { reject: false, encoding: "buffer" }
@@ -2609,7 +3233,7 @@ async function refreshExport(record, opts = {}) {
2609
3233
  typeof result.stderr === "string" ? result.stderr : result.stderr.toString("utf8")
2610
3234
  );
2611
3235
  }
2612
- const extract = await execa22("tar", ["-xf", "-", "-C", paths.mergedExport], {
3236
+ const extract = await execa3("tar", ["-xf", "-", "-C", paths.mergedExport], {
2613
3237
  input: result.stdout,
2614
3238
  reject: false
2615
3239
  });
@@ -2704,7 +3328,7 @@ function buildHostEnvFindArgs(patterns) {
2704
3328
  async function copyHostEnvFilesToBox(opts) {
2705
3329
  const log = opts.onLog ?? (() => {
2706
3330
  });
2707
- const found = await execa22("find", buildHostEnvFindArgs(opts.patterns).slice(1), {
3331
+ const found = await execa3("find", buildHostEnvFindArgs(opts.patterns).slice(1), {
2708
3332
  cwd: opts.workspaceDir,
2709
3333
  reject: false
2710
3334
  });
@@ -2714,7 +3338,7 @@ async function copyHostEnvFilesToBox(opts) {
2714
3338
  }
2715
3339
  const list = String(found.stdout).split("\0").filter((p) => p.length > 0);
2716
3340
  if (list.length === 0) return { copied: 0 };
2717
- const packed = await execa22("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
3341
+ const packed = await execa3("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
2718
3342
  input: list.join("\0"),
2719
3343
  encoding: "buffer",
2720
3344
  reject: false
@@ -2723,7 +3347,7 @@ async function copyHostEnvFilesToBox(opts) {
2723
3347
  log(`warning: env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
2724
3348
  return { copied: 0 };
2725
3349
  }
2726
- const extract = await execa22(
3350
+ const extract = await execa3(
2727
3351
  "docker",
2728
3352
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-xf", "-", "-C", "/workspace"],
2729
3353
  { input: packed.stdout, reject: false }
@@ -2736,7 +3360,7 @@ async function copyHostEnvFilesToBox(opts) {
2736
3360
  }
2737
3361
  async function scanHostEnvFiles(workspaceDir, patterns) {
2738
3362
  if (patterns.length === 0) return [];
2739
- const found = await execa22("find", buildHostEnvFindArgs(patterns).slice(1), {
3363
+ const found = await execa3("find", buildHostEnvFindArgs(patterns).slice(1), {
2740
3364
  cwd: workspaceDir,
2741
3365
  reject: false
2742
3366
  });
@@ -2748,7 +3372,7 @@ async function copyHostFilesToBox(opts) {
2748
3372
  });
2749
3373
  const list = opts.files.map((p) => p.replace(/^\.\//, "")).filter((p) => p.length > 0);
2750
3374
  if (list.length === 0) return { copied: 0 };
2751
- const packed = await execa22("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
3375
+ const packed = await execa3("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
2752
3376
  input: list.join("\0"),
2753
3377
  encoding: "buffer",
2754
3378
  reject: false
@@ -2757,7 +3381,7 @@ async function copyHostFilesToBox(opts) {
2757
3381
  log(`warning: env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
2758
3382
  return { copied: 0 };
2759
3383
  }
2760
- const extract = await execa22(
3384
+ const extract = await execa3(
2761
3385
  "docker",
2762
3386
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-xf", "-", "-C", "/workspace"],
2763
3387
  { input: packed.stdout, reject: false }
@@ -2831,7 +3455,7 @@ async function pullToHost(record, opts = {}) {
2831
3455
  }
2832
3456
  const src = `${scratchDir}/`;
2833
3457
  const dst = `${record.workspacePath}/`;
2834
- const dry = await execa22("rsync", [...baseArgs, "--dry-run", "-i", src, dst], {
3458
+ const dry = await execa3("rsync", [...baseArgs, "--dry-run", "-i", src, dst], {
2835
3459
  reject: false,
2836
3460
  input: fileList !== null ? fileList : void 0
2837
3461
  });
@@ -2842,7 +3466,7 @@ async function pullToHost(record, opts = {}) {
2842
3466
  if (opts.dryRun) {
2843
3467
  return { hostPath: record.workspacePath, changes, applied: false, usedGitignore };
2844
3468
  }
2845
- const real = await execa22("rsync", [...baseArgs, src, dst], {
3469
+ const real = await execa3("rsync", [...baseArgs, src, dst], {
2846
3470
  reject: false,
2847
3471
  input: fileList !== null ? fileList : void 0
2848
3472
  });
@@ -2867,7 +3491,7 @@ async function openInFinder(record, opts) {
2867
3491
  usedFallback = refreshed.usedFallback;
2868
3492
  }
2869
3493
  if (!opts.noOpen) {
2870
- const opened = await execa22(hostOpenCommand(), [hostPath], { reject: false });
3494
+ const opened = await execa3(hostOpenCommand(), [hostPath], { reject: false });
2871
3495
  if (opened.exitCode !== 0) {
2872
3496
  throw new ExportError(`open ${hostPath} failed`, opened.stdout, opened.stderr);
2873
3497
  }
@@ -2890,12 +3514,14 @@ async function carrySourceHash(entry) {
2890
3514
  if (entry.kind === "file") {
2891
3515
  return createHash3("sha256").update(await readFile5(entry.absSrc)).digest("hex");
2892
3516
  }
3517
+ const exclude = entry.exclude ?? [];
2893
3518
  const h = createHash3("sha256");
2894
3519
  const walk = async (dir, rel) => {
2895
3520
  const names = (await readdir4(dir)).sort();
2896
3521
  for (const name of names) {
2897
- const abs = join5(dir, name);
2898
3522
  const relPath = rel ? `${rel}/${name}` : name;
3523
+ if (carryRelExcluded(relPath, exclude)) continue;
3524
+ const abs = join5(dir, name);
2899
3525
  const st = await stat4(abs);
2900
3526
  if (st.isDirectory()) {
2901
3527
  h.update(`d\0${relPath}
@@ -2914,6 +3540,24 @@ async function carrySourceHash(entry) {
2914
3540
  return void 0;
2915
3541
  }
2916
3542
  }
3543
+ function carryRelExcluded(relPath, patterns) {
3544
+ if (patterns.length === 0) return false;
3545
+ const segs = relPath.split("/");
3546
+ for (const p of patterns) {
3547
+ const isGlob = p.includes("*") || p.includes("?");
3548
+ if (!isGlob) {
3549
+ if (segs.includes(p)) return true;
3550
+ } else if (p.startsWith("*/") && !/[*?/]/.test(p.slice(2))) {
3551
+ if (segs.includes(p.slice(2))) return true;
3552
+ } else {
3553
+ const re = new RegExp(
3554
+ `^${p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
3555
+ );
3556
+ if (re.test(relPath)) return true;
3557
+ }
3558
+ }
3559
+ return false;
3560
+ }
2917
3561
  async function copyCarryPathsToBox(opts) {
2918
3562
  const log = opts.onLog ?? (() => {
2919
3563
  });
@@ -2949,7 +3593,7 @@ async function copyOneEntry(container, entry) {
2949
3593
  const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
2950
3594
  const boxDestParent = boxDest.endsWith("/") ? boxDest.slice(0, -1) : boxDest;
2951
3595
  const parentDir = entry.kind === "dir" ? boxDestParent : dirnameUnix(boxDestParent);
2952
- const mkdir9 = await execa22(
3596
+ const mkdir9 = await execa3(
2953
3597
  "docker",
2954
3598
  ["exec", "--user", "0:0", container, "mkdir", "-p", parentDir],
2955
3599
  { reject: false }
@@ -2958,24 +3602,50 @@ async function copyOneEntry(container, entry) {
2958
3602
  throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir9.stderr).slice(0, 300)}`);
2959
3603
  }
2960
3604
  if (entry.kind === "file") {
2961
- const cp2 = await execa22(
2962
- "docker",
2963
- ["cp", entry.absSrc, `${container}:${boxDest}`],
2964
- { reject: false }
2965
- );
2966
- if (cp2.exitCode !== 0) {
2967
- throw new Error(`docker cp failed: ${String(cp2.stderr).slice(0, 300)}`);
2968
- }
2969
- } else {
2970
- const packed = await execa22(
3605
+ const srcDir = dirname22(entry.absSrc);
3606
+ const srcBase = basename2(entry.absSrc);
3607
+ const destBase = boxDest.slice(boxDest.lastIndexOf("/") + 1);
3608
+ const [packed, extract] = await streamTarPipe(
2971
3609
  "tar",
2972
- ["-C", entry.absSrc, "-cf", "-", "."],
2973
- { encoding: "buffer", reject: false }
3610
+ ["-C", srcDir, "-cf", "-", srcBase],
3611
+ "docker",
3612
+ [
3613
+ "exec",
3614
+ "-i",
3615
+ "--user",
3616
+ "0:0",
3617
+ container,
3618
+ "tar",
3619
+ "-xf",
3620
+ "-",
3621
+ "-C",
3622
+ parentDir,
3623
+ "--no-same-permissions",
3624
+ "--no-same-owner",
3625
+ "-m"
3626
+ ]
2974
3627
  );
2975
3628
  if (packed.exitCode !== 0) {
2976
3629
  throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
2977
3630
  }
2978
- const extract = await execa22(
3631
+ if (extract.exitCode !== 0) {
3632
+ throw new Error(`tar extract failed: ${String(extract.stderr).slice(0, 300)}`);
3633
+ }
3634
+ if (srcBase !== destBase) {
3635
+ const mv = await execa3(
3636
+ "docker",
3637
+ ["exec", "--user", "0:0", container, "mv", "-f", `${parentDir}/${srcBase}`, boxDest],
3638
+ { reject: false }
3639
+ );
3640
+ if (mv.exitCode !== 0) {
3641
+ throw new Error(`rename to ${boxDest} failed: ${String(mv.stderr).slice(0, 300)}`);
3642
+ }
3643
+ }
3644
+ } else {
3645
+ const excludeArgs = (entry.exclude ?? []).map((p) => `--exclude=${p}`);
3646
+ const [packed, extract] = await streamTarPipe(
3647
+ "tar",
3648
+ ["-C", entry.absSrc, "-cf", "-", ...excludeArgs, "."],
2979
3649
  "docker",
2980
3650
  [
2981
3651
  "exec",
@@ -2991,16 +3661,18 @@ async function copyOneEntry(container, entry) {
2991
3661
  "--no-same-permissions",
2992
3662
  "--no-same-owner",
2993
3663
  "-m"
2994
- ],
2995
- { input: packed.stdout, reject: false }
3664
+ ]
2996
3665
  );
3666
+ if (packed.exitCode !== 0) {
3667
+ throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
3668
+ }
2997
3669
  if (extract.exitCode !== 0) {
2998
3670
  throw new Error(`tar extract failed: ${String(extract.stderr).slice(0, 300)}`);
2999
3671
  }
3000
3672
  }
3001
3673
  if (entry.mode !== void 0) {
3002
3674
  const modeStr = entry.mode.toString(8).padStart(4, "0");
3003
- const chmod2 = await execa22(
3675
+ const chmod2 = await execa3(
3004
3676
  "docker",
3005
3677
  ["exec", "--user", "0:0", container, "chmod", "-R", modeStr, boxDest],
3006
3678
  { reject: false }
@@ -3010,7 +3682,7 @@ async function copyOneEntry(container, entry) {
3010
3682
  }
3011
3683
  }
3012
3684
  const uid = entry.user ?? 1e3;
3013
- const chown = await execa22(
3685
+ const chown = await execa3(
3014
3686
  "docker",
3015
3687
  ["exec", "--user", "0:0", container, "chown", "-R", `${String(uid)}:${String(uid)}`, boxDest],
3016
3688
  { reject: false }
@@ -3021,7 +3693,7 @@ async function copyOneEntry(container, entry) {
3021
3693
  if (boxDest.startsWith(BOX_HOME + "/") && dirnameUnix(boxDest) !== BOX_HOME) {
3022
3694
  const safeDest = boxDest.replace(/'/g, `'\\''`);
3023
3695
  const script = `set -e; parent="$(dirname '${safeDest}')"; while [ "$parent" != "${BOX_HOME}" ] && [ "$parent" != "/" ]; do chown ${String(uid)}:${String(uid)} "$parent"; parent="$(dirname "$parent")"; done`;
3024
- const chownParents = await execa22(
3696
+ const chownParents = await execa3(
3025
3697
  "docker",
3026
3698
  ["exec", "--user", "0:0", container, "bash", "-c", script],
3027
3699
  { reject: false }
@@ -3069,7 +3741,7 @@ async function extractVolumeAuthToBackup(opts) {
3069
3741
  try {
3070
3742
  await mkdir22(STATE_DIR, { recursive: true });
3071
3743
  const script = 'COPIED=no; if [ -s /dst/auth.json ]; then cp -a /dst/auth.json "/host-state/$DEST" && COPIED=yes; fi; echo "COPIED=$COPIED"';
3072
- const { stdout } = await execa3("docker", [
3744
+ const { stdout } = await execa4("docker", [
3073
3745
  "run",
3074
3746
  "--rm",
3075
3747
  "--user",
@@ -3081,7 +3753,7 @@ async function extractVolumeAuthToBackup(opts) {
3081
3753
  "-e",
3082
3754
  // Pass the destination filename via env so the path isn't interpolated
3083
3755
  // into the script string (keeps the docker arg list static + injection-safe).
3084
- `DEST=${basename3(opts.backupFile)}`,
3756
+ `DEST=${basename32(opts.backupFile)}`,
3085
3757
  opts.image,
3086
3758
  "sh",
3087
3759
  "-c",
@@ -3133,7 +3805,7 @@ echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
3133
3805
  async function syncClaudeCredentials(spec, opts) {
3134
3806
  try {
3135
3807
  await mkdir22(STATE_DIR, { recursive: true });
3136
- const { stdout } = await execa3("docker", [
3808
+ const { stdout } = await execa4("docker", [
3137
3809
  "run",
3138
3810
  "--rm",
3139
3811
  "--user",
@@ -3201,8 +3873,8 @@ function emptyResult(warnings = []) {
3201
3873
  }, warnings };
3202
3874
  }
3203
3875
  async function tarballFromDir(stageDir, agent) {
3204
- const tarballPath = join32(tmpdir(), `agentbox-${agent}-${basename2(stageDir)}.tar.gz`);
3205
- await execa4("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
3876
+ const tarballPath = join32(tmpdir(), `agentbox-${agent}-${basename4(stageDir)}.tar.gz`);
3877
+ await execa5("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
3206
3878
  env: { ...process.env, COPYFILE_DISABLE: "1" }
3207
3879
  });
3208
3880
  return tarballPath;
@@ -3347,7 +4019,7 @@ async function stageClaudeStaticForUpload(opts = {}) {
3347
4019
  ...CLAUDE_RUNTIME_EXCLUDES.map((p) => `--exclude=${p}`),
3348
4020
  ...broken.map((r) => `--exclude=/${r}`)
3349
4021
  ];
3350
- await execa4("rsync", [
4022
+ await execa5("rsync", [
3351
4023
  "-a",
3352
4024
  "--copy-unsafe-links",
3353
4025
  ...excludes,
@@ -3436,7 +4108,7 @@ async function stageCodexStaticForUpload(opts = {}) {
3436
4108
  let tarballPath = null;
3437
4109
  try {
3438
4110
  const codexBroken = await findBrokenSymlinks(hostCodex);
3439
- await execa4("rsync", [
4111
+ await execa5("rsync", [
3440
4112
  "-a",
3441
4113
  "-L",
3442
4114
  ...codexBroken.map((r) => `--exclude=/${r}`),
@@ -3492,7 +4164,7 @@ async function stageOpencodeStaticForUpload(opts = {}) {
3492
4164
  try {
3493
4165
  if (hasData) {
3494
4166
  const dataBroken = await findBrokenSymlinks(hostData);
3495
- await execa4("rsync", [
4167
+ await execa5("rsync", [
3496
4168
  "-a",
3497
4169
  "-L",
3498
4170
  ...dataBroken.map((r) => `--exclude=/${r}`),
@@ -3505,7 +4177,7 @@ async function stageOpencodeStaticForUpload(opts = {}) {
3505
4177
  if (hasConfig) {
3506
4178
  const configStage = join32(stageDir, "config");
3507
4179
  const cfgBroken = await findBrokenSymlinks(hostConfig);
3508
- await execa4("rsync", [
4180
+ await execa5("rsync", [
3509
4181
  "-a",
3510
4182
  "-L",
3511
4183
  ...cfgBroken.map((r) => `--exclude=/${r}`),
@@ -3563,7 +4235,7 @@ async function pathExists2(p) {
3563
4235
  }
3564
4236
  }
3565
4237
  async function volumeHasClaudeJson(volume, image) {
3566
- const res = await execa5(
4238
+ const res = await execa6(
3567
4239
  "docker",
3568
4240
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/_claude.json"],
3569
4241
  { reject: false }
@@ -3733,7 +4405,7 @@ async function ensureClaudeVolume(spec, opts) {
3733
4405
  // linux/amd64 node_modules on the next `agentbox claude`.
3734
4406
  `{ [ ! -f /dst/.agentbox-cleaned-nm-v1 ] && find /dst -name node_modules -type d -prune -exec rm -rf {} + && touch /dst/.agentbox-cleaned-nm-v1; true; } && rsync ${rsyncFlags} /src-claude/ /dst/ && { [ -f /src-claude-json ] && cp -a /src-claude-json /dst/_claude.json; true; } && { [ -f /src-filter/settings.json ] && cp -a /src-filter/settings.json /dst/settings.json; true; } && { [ -f /src-filter/_claude.json ] && cp -a /src-filter/_claude.json /dst/_claude.json; true; } && { [ -d /dst/plugins ] && [ -n "$HOST_HOME" ] && find /dst/plugins -maxdepth 1 -type f -name "*.json" -exec sed -i "s|$HOST_HOME/.claude/plugins/|/home/vscode/.claude/plugins/|g" {} +; true; }` + memoryRekeyStep + " && chown -R 1000:1000 /dst"
3735
4407
  );
3736
- await execa5("docker", args);
4408
+ await execa6("docker", args);
3737
4409
  } finally {
3738
4410
  await rm2(filterDir, { recursive: true, force: true });
3739
4411
  }
@@ -3748,7 +4420,7 @@ async function ensureClaudeVolume(spec, opts) {
3748
4420
  }
3749
4421
  async function seedSetupSkillIntoVolume(volume, image) {
3750
4422
  try {
3751
- const { stdout } = await execa5("docker", [
4423
+ const { stdout } = await execa6("docker", [
3752
4424
  "run",
3753
4425
  "--rm",
3754
4426
  "--user",
@@ -3913,7 +4585,7 @@ async function resolveClaudeCacheLiveOnHost(volume) {
3913
4585
  return orbstackVolumePath(volume, "plugins", "cache");
3914
4586
  }
3915
4587
  async function readBoxReferencedPluginKeys(container) {
3916
- const res = await execa5(
4588
+ const res = await execa6(
3917
4589
  "docker",
3918
4590
  [
3919
4591
  "exec",
@@ -4031,7 +4703,7 @@ while IFS= read -r dir; do
4031
4703
  done < "$WORK/dirs"
4032
4704
  rm -rf "$WORK"
4033
4705
  `;
4034
- const result = await execa5(
4706
+ const result = await execa6(
4035
4707
  "docker",
4036
4708
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", script],
4037
4709
  { reject: false }
@@ -4078,21 +4750,21 @@ var ClaudeSessionError = class extends Error {
4078
4750
  this.name = "ClaudeSessionError";
4079
4751
  }
4080
4752
  };
4081
- function shQuote(arg) {
4753
+ function shQuote2(arg) {
4082
4754
  if (arg.length === 0) return `''`;
4083
4755
  if (/^[A-Za-z0-9_\-./=:@%+,]+$/.test(arg)) return arg;
4084
4756
  return `'${arg.replace(/'/g, `'\\''`)}'`;
4085
4757
  }
4086
4758
  async function startClaudeSession(opts) {
4087
4759
  const sessionName = opts.sessionName ?? DEFAULT_CLAUDE_SESSION;
4088
- const cmd = ["claude", ...opts.claudeArgs].map(shQuote).join(" ");
4760
+ const cmd = ["claude", ...opts.claudeArgs].map(shQuote2).join(" ");
4089
4761
  const term = process.env["TERM"] ?? "xterm-256color";
4090
4762
  const envFlags = ["-e", `TERM=${term}`];
4091
4763
  for (const k of FORWARDED_ENV_KEYS) {
4092
4764
  const v = process.env[k];
4093
4765
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4094
4766
  }
4095
- const result = await execa5(
4767
+ const result = await execa6(
4096
4768
  "docker",
4097
4769
  [
4098
4770
  "exec",
@@ -4183,7 +4855,7 @@ function buildDashboardAttachArgv(container, sessionName) {
4183
4855
  async function waitForTmuxPaneContent(container, sessionName, timeoutMs = 2e4) {
4184
4856
  const deadline = Date.now() + timeoutMs;
4185
4857
  while (Date.now() < deadline) {
4186
- const res = await execa5(
4858
+ const res = await execa6(
4187
4859
  "docker",
4188
4860
  ["exec", "--user", CONTAINER_USER, container, "tmux", "capture-pane", "-p", "-t", sessionName],
4189
4861
  { reject: false }
@@ -4251,7 +4923,7 @@ async function warmUpClaudeCredentials(volume, image, opts = {}) {
4251
4923
  const SLEEP_MS = 5e3;
4252
4924
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
4253
4925
  opts.onProgress?.(`checking credentials... ${attempt}/${MAX_ATTEMPTS}`);
4254
- const res = await execa5(
4926
+ const res = await execa6(
4255
4927
  "docker",
4256
4928
  [
4257
4929
  "run",
@@ -4293,7 +4965,7 @@ function attachClaudeSession(container, sessionName, reattachRef) {
4293
4965
  }
4294
4966
  async function claudeSessionInfo(container, sessionName) {
4295
4967
  const name = sessionName ?? DEFAULT_CLAUDE_SESSION;
4296
- const has = await execa5(
4968
+ const has = await execa6(
4297
4969
  "docker",
4298
4970
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4299
4971
  { reject: false }
@@ -4301,7 +4973,7 @@ async function claudeSessionInfo(container, sessionName) {
4301
4973
  if (has.exitCode !== 0) {
4302
4974
  return { running: false, sessionName: name, startedAt: null };
4303
4975
  }
4304
- const ts = await execa5(
4976
+ const ts = await execa6(
4305
4977
  "docker",
4306
4978
  [
4307
4979
  "exec",
@@ -4367,7 +5039,7 @@ async function pullClaudeExtras(spec, opts) {
4367
5039
  ' printf "\\n";',
4368
5040
  "done"
4369
5041
  ].join(" ");
4370
- const inv = await execa5(
5042
+ const inv = await execa6(
4371
5043
  "docker",
4372
5044
  ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/src:ro`, opts.image, "sh", "-c", inventoryScript],
4373
5045
  { reject: false }
@@ -4440,7 +5112,7 @@ async function pullClaudeExtras(spec, opts) {
4440
5112
  const parent = dest.slice(0, dest.lastIndexOf("/"));
4441
5113
  return `mkdir -p '${parent}' && rsync -a --ignore-existing --exclude=node_modules '${src}/' '${dest}/'`;
4442
5114
  });
4443
- const apply = await execa5(
5115
+ const apply = await execa6(
4444
5116
  "docker",
4445
5117
  [
4446
5118
  "run",
@@ -4501,7 +5173,7 @@ async function pathExists3(p) {
4501
5173
  return false;
4502
5174
  }
4503
5175
  }
4504
- function shQuote2(arg) {
5176
+ function shQuote22(arg) {
4505
5177
  if (arg.length === 0) return `''`;
4506
5178
  if (/^[A-Za-z0-9_\-./=:@%+,]+$/.test(arg)) return arg;
4507
5179
  return `'${arg.replace(/'/g, `'\\''`)}'`;
@@ -4513,7 +5185,7 @@ async function ensureCodexVolume(spec, opts) {
4513
5185
  const hostCodex = join52(homedir4(), ".codex");
4514
5186
  const willSync = opts.syncFromHost && await pathExists3(hostCodex);
4515
5187
  if (willSync) {
4516
- await execa6("docker", [
5188
+ await execa7("docker", [
4517
5189
  "run",
4518
5190
  "--rm",
4519
5191
  "--user",
@@ -4531,7 +5203,7 @@ async function ensureCodexVolume(spec, opts) {
4531
5203
  ]);
4532
5204
  return { created, synced: true };
4533
5205
  }
4534
- await execa6(
5206
+ await execa7(
4535
5207
  "docker",
4536
5208
  [
4537
5209
  "run",
@@ -4551,7 +5223,7 @@ async function ensureCodexVolume(spec, opts) {
4551
5223
  }
4552
5224
  async function seedCodexHooks(volume, image) {
4553
5225
  try {
4554
- const { stdout } = await execa6("docker", [
5226
+ const { stdout } = await execa7("docker", [
4555
5227
  "run",
4556
5228
  "--rm",
4557
5229
  "--user",
@@ -4588,14 +5260,14 @@ var CodexSessionError = class extends Error {
4588
5260
  }
4589
5261
  };
4590
5262
  async function ensureCodexInstalled(container, opts = {}) {
4591
- const probe = await execa6(
5263
+ const probe = await execa7(
4592
5264
  "docker",
4593
5265
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v codex"],
4594
5266
  { reject: false }
4595
5267
  );
4596
5268
  if (probe.exitCode === 0) return { installed: false };
4597
5269
  opts.onProgress?.("installing codex (absent from this box image)");
4598
- const install = await execa6(
5270
+ const install = await execa7(
4599
5271
  "docker",
4600
5272
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g @openai/codex 2>&1"],
4601
5273
  { reject: false }
@@ -4611,14 +5283,14 @@ ${(install.stdout ?? "").toString().slice(-600)}`
4611
5283
  var CODEX_AGENTBOX_FLAGS = ["--enable", "hooks", "--dangerously-bypass-hook-trust"];
4612
5284
  async function startCodexSession(opts) {
4613
5285
  const sessionName = opts.sessionName ?? DEFAULT_CODEX_SESSION;
4614
- const cmd = ["codex", ...CODEX_AGENTBOX_FLAGS, ...opts.codexArgs].map(shQuote2).join(" ");
5286
+ const cmd = ["codex", ...CODEX_AGENTBOX_FLAGS, ...opts.codexArgs].map(shQuote22).join(" ");
4615
5287
  const term = process.env["TERM"] ?? "xterm-256color";
4616
5288
  const envFlags = ["-e", `TERM=${term}`];
4617
5289
  for (const k of CODEX_FORWARDED_ENV_KEYS) {
4618
5290
  const v = process.env[k];
4619
5291
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4620
5292
  }
4621
- const result = await execa6(
5293
+ const result = await execa7(
4622
5294
  "docker",
4623
5295
  [
4624
5296
  "exec",
@@ -4700,7 +5372,7 @@ function runInteractiveCodexLogin(dockerArgv) {
4700
5372
  return { exitCode: child.status ?? 1 };
4701
5373
  }
4702
5374
  async function volumeHasCodexAuth(volume, image) {
4703
- const res = await execa6(
5375
+ const res = await execa7(
4704
5376
  "docker",
4705
5377
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
4706
5378
  { reject: false }
@@ -4709,7 +5381,7 @@ async function volumeHasCodexAuth(volume, image) {
4709
5381
  }
4710
5382
  async function codexSessionInfo(container, sessionName) {
4711
5383
  const name = sessionName ?? DEFAULT_CODEX_SESSION;
4712
- const has = await execa6(
5384
+ const has = await execa7(
4713
5385
  "docker",
4714
5386
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4715
5387
  { reject: false }
@@ -4717,7 +5389,7 @@ async function codexSessionInfo(container, sessionName) {
4717
5389
  if (has.exitCode !== 0) {
4718
5390
  return { running: false, sessionName: name, startedAt: null };
4719
5391
  }
4720
- const ts = await execa6(
5392
+ const ts = await execa7(
4721
5393
  "docker",
4722
5394
  [
4723
5395
  "exec",
@@ -4743,7 +5415,7 @@ async function codexSessionInfo(container, sessionName) {
4743
5415
  var CODEX_PULL_ITEMS = ["config.toml", "auth.json", "prompts"];
4744
5416
  async function pullCodexConfig(spec, opts) {
4745
5417
  const hostCodex = join52(homedir4(), ".codex");
4746
- const inv = await execa6(
5418
+ const inv = await execa7(
4747
5419
  "docker",
4748
5420
  [
4749
5421
  "run",
@@ -4777,7 +5449,7 @@ async function pullCodexConfig(spec, opts) {
4777
5449
  const uid = process.getuid?.() ?? 0;
4778
5450
  const gid = process.getgid?.() ?? 0;
4779
5451
  const cmds = newItems.map((it) => `cp -a '/src/${it}' '/dst/${it}'`);
4780
- const apply = await execa6(
5452
+ const apply = await execa7(
4781
5453
  "docker",
4782
5454
  [
4783
5455
  "run",
@@ -4859,10 +5531,10 @@ async function ensureOpencodeVolume(spec, opts) {
4859
5531
  }
4860
5532
  steps.push("chown -R 1000:1000 /dst");
4861
5533
  args.push(opts.image, "sh", "-c", steps.join(" && "));
4862
- await execa7("docker", args);
5534
+ await execa8("docker", args);
4863
5535
  return { created, synced: true };
4864
5536
  }
4865
- await execa7(
5537
+ await execa8(
4866
5538
  "docker",
4867
5539
  [
4868
5540
  "run",
@@ -4882,7 +5554,7 @@ async function ensureOpencodeVolume(spec, opts) {
4882
5554
  }
4883
5555
  async function seedOpencodePlugin(volume, image) {
4884
5556
  try {
4885
- const { stdout } = await execa7("docker", [
5557
+ const { stdout } = await execa8("docker", [
4886
5558
  "run",
4887
5559
  "--rm",
4888
5560
  "--user",
@@ -4930,14 +5602,14 @@ var OpencodeSessionError = class extends Error {
4930
5602
  }
4931
5603
  };
4932
5604
  async function ensureOpencodeInstalled(container, opts = {}) {
4933
- const probe = await execa7(
5605
+ const probe = await execa8(
4934
5606
  "docker",
4935
5607
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v opencode"],
4936
5608
  { reject: false }
4937
5609
  );
4938
5610
  if (probe.exitCode === 0) return { installed: false };
4939
5611
  opts.onProgress?.("installing opencode (absent from this box image)");
4940
- const install = await execa7(
5612
+ const install = await execa8(
4941
5613
  "docker",
4942
5614
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g opencode-ai 2>&1"],
4943
5615
  { reject: false }
@@ -4959,7 +5631,7 @@ async function startOpencodeSession(opts) {
4959
5631
  const v = process.env[k];
4960
5632
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4961
5633
  }
4962
- const result = await execa7(
5634
+ const result = await execa8(
4963
5635
  "docker",
4964
5636
  [
4965
5637
  "exec",
@@ -5045,7 +5717,7 @@ function runInteractiveOpencodeLogin(dockerArgv) {
5045
5717
  return { exitCode: child.status ?? 1 };
5046
5718
  }
5047
5719
  async function volumeHasOpencodeAuth(volume, image) {
5048
- const res = await execa7(
5720
+ const res = await execa8(
5049
5721
  "docker",
5050
5722
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
5051
5723
  { reject: false }
@@ -5054,7 +5726,7 @@ async function volumeHasOpencodeAuth(volume, image) {
5054
5726
  }
5055
5727
  async function opencodeSessionInfo(container, sessionName) {
5056
5728
  const name = sessionName ?? DEFAULT_OPENCODE_SESSION;
5057
- const has = await execa7(
5729
+ const has = await execa8(
5058
5730
  "docker",
5059
5731
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
5060
5732
  { reject: false }
@@ -5062,7 +5734,7 @@ async function opencodeSessionInfo(container, sessionName) {
5062
5734
  if (has.exitCode !== 0) {
5063
5735
  return { running: false, sessionName: name, startedAt: null };
5064
5736
  }
5065
- const ts = await execa7(
5737
+ const ts = await execa8(
5066
5738
  "docker",
5067
5739
  [
5068
5740
  "exec",
@@ -5100,7 +5772,7 @@ var OPENCODE_PULL_CONFIG_ITEMS = [
5100
5772
  async function pullOpencodeConfig(spec, opts) {
5101
5773
  const hostData = join6(homedir5(), ".local", "share", "opencode");
5102
5774
  const hostConfig = join6(homedir5(), ".config", "opencode");
5103
- const inv = await execa7(
5775
+ const inv = await execa8(
5104
5776
  "docker",
5105
5777
  [
5106
5778
  "run",
@@ -5142,7 +5814,7 @@ async function pullOpencodeConfig(spec, opts) {
5142
5814
  const cmds = newItems.map(
5143
5815
  (i) => `cp -a '${i.src}' '${i.hostDst === "data" ? "/dst-data" : "/dst-config"}/${i.name}'`
5144
5816
  );
5145
- const apply = await execa7(
5817
+ const apply = await execa8(
5146
5818
  "docker",
5147
5819
  [
5148
5820
  "run",
@@ -5219,7 +5891,7 @@ async function launchVncDaemon(container, timeoutMs = 5e3) {
5219
5891
  }
5220
5892
  var VNC_PASSWORD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
5221
5893
  function generateVncPassword() {
5222
- const bytes = randomBytes3(8);
5894
+ const bytes = randomBytes2(8);
5223
5895
  let out = "";
5224
5896
  for (let i = 0; i < 8; i++) {
5225
5897
  out += VNC_PASSWORD_ALPHABET[bytes[i] % VNC_PASSWORD_ALPHABET.length];
@@ -5253,9 +5925,9 @@ function gitWorktreePathFor(branch) {
5253
5925
  return `${WORKTREE_ROOT}/${fsSafeBranch(branch)}`;
5254
5926
  }
5255
5927
  async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath) {
5256
- const stash = await execa8("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
5928
+ const stash = await execa9("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
5257
5929
  const stashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
5258
- const untracked = await execa8(
5930
+ const untracked = await execa9(
5259
5931
  "git",
5260
5932
  ["-C", repo.hostMainRepo, "ls-files", "--others", "--exclude-standard", "-z"],
5261
5933
  { reject: false }
@@ -5272,7 +5944,7 @@ async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath
5272
5944
  };
5273
5945
  }
5274
5946
  async function dexec(container, argv, user = "vscode", cwd = "/") {
5275
- const r = await execa8(
5947
+ const r = await execa9(
5276
5948
  "docker",
5277
5949
  ["exec", "-w", cwd, "--user", user, container, ...argv],
5278
5950
  { reject: false }
@@ -5304,7 +5976,7 @@ async function bindWorktrees(container, binds, onLog) {
5304
5976
  (a, b) => a.kind === "root" && b.kind !== "root" ? -1 : a.kind !== "root" && b.kind === "root" ? 1 : 0
5305
5977
  );
5306
5978
  for (const b of ordered) {
5307
- await execa8(
5979
+ await execa9(
5308
5980
  "docker",
5309
5981
  ["exec", "-w", "/", "--user", "root", container, "sh", "-c", `mountpoint -q ${b.containerPath} && umount ${b.containerPath} || true`],
5310
5982
  { reject: false }
@@ -5326,7 +5998,7 @@ async function seedWorkspace(opts) {
5326
5998
  const wt = r.gitWorktreePath;
5327
5999
  const baseRef = r.repo.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
5328
6000
  const addArgs = r.reuseBranch ? ["worktree", "add", wt, r.branch] : ["worktree", "add", "-b", r.branch, wt, baseRef];
5329
- const add = await execa8(
6001
+ const add = await execa9(
5330
6002
  "docker",
5331
6003
  ["exec", "--user", "vscode", opts.container, "git", "-C", main, ...addArgs],
5332
6004
  { reject: false }
@@ -5337,7 +6009,7 @@ async function seedWorkspace(opts) {
5337
6009
  );
5338
6010
  }
5339
6011
  log(`worktree ${wt} on branch ${r.branch} (host main ${main})`);
5340
- await execa8(
6012
+ await execa9(
5341
6013
  "docker",
5342
6014
  [
5343
6015
  "exec",
@@ -5353,7 +6025,7 @@ async function seedWorkspace(opts) {
5353
6025
  ],
5354
6026
  { reject: false }
5355
6027
  );
5356
- await execa8(
6028
+ await execa9(
5357
6029
  "docker",
5358
6030
  [
5359
6031
  "exec",
@@ -5383,7 +6055,7 @@ async function seedWorkspace(opts) {
5383
6055
  for (const r of opts.repos) {
5384
6056
  const ct = r.containerPath;
5385
6057
  if (r.stashSha) {
5386
- const withIndex = await execa8(
6058
+ const withIndex = await execa9(
5387
6059
  "docker",
5388
6060
  [
5389
6061
  "exec",
@@ -5401,7 +6073,7 @@ async function seedWorkspace(opts) {
5401
6073
  { reject: false }
5402
6074
  );
5403
6075
  if (withIndex.exitCode !== 0) {
5404
- const noIndex = await execa8(
6076
+ const noIndex = await execa9(
5405
6077
  "docker",
5406
6078
  [
5407
6079
  "exec",
@@ -5429,7 +6101,7 @@ async function seedWorkspace(opts) {
5429
6101
  }
5430
6102
  }
5431
6103
  if (r.untrackedNul.length > 0) {
5432
- const tarOut = await execa8("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
6104
+ const tarOut = await execa9("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
5433
6105
  input: r.untrackedNul.replace(/\0$/, ""),
5434
6106
  encoding: "buffer",
5435
6107
  reject: false
@@ -5438,7 +6110,7 @@ async function seedWorkspace(opts) {
5438
6110
  log(`warning: tar of untracked files for ${r.repo.hostMainRepo} failed: ${tarOut.stderr}`);
5439
6111
  continue;
5440
6112
  }
5441
- const tarIn = await execa8(
6113
+ const tarIn = await execa9(
5442
6114
  "docker",
5443
6115
  ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
5444
6116
  { input: tarOut.stdout, reject: false }
@@ -5491,7 +6163,7 @@ async function regenerateRestoredWorktrees(opts) {
5491
6163
  for (const p of opts.plans) {
5492
6164
  const baseRef = p.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
5493
6165
  const metaDir = `${p.hostMainRepo}/.git/worktrees/${fsSafeBranch(p.freshBranch)}`;
5494
- const r = await execa8(
6166
+ const r = await execa9(
5495
6167
  "docker",
5496
6168
  [
5497
6169
  "exec",
@@ -5524,14 +6196,14 @@ async function regenerateRestoredWorktrees(opts) {
5524
6196
  async function seedWorkspaceFromDir(opts) {
5525
6197
  const log = opts.onLog ?? (() => {
5526
6198
  });
5527
- const tarOut = await execa8("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
6199
+ const tarOut = await execa9("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
5528
6200
  encoding: "buffer",
5529
6201
  reject: false
5530
6202
  });
5531
6203
  if (tarOut.exitCode !== 0) {
5532
6204
  throw new GitWorktreeError(`tar of ${opts.hostSource} failed: ${tarOut.stderr}`);
5533
6205
  }
5534
- const tarIn = await execa8(
6206
+ const tarIn = await execa9(
5535
6207
  "docker",
5536
6208
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-C", "/workspace", "-xf", "-"],
5537
6209
  { input: tarOut.stdout, reject: false }
@@ -5542,13 +6214,13 @@ async function seedWorkspaceFromDir(opts) {
5542
6214
  log(`seeded /workspace from ${opts.hostSource}`);
5543
6215
  }
5544
6216
  async function removeInBoxWorktree(args) {
5545
- const remove = await execa8(
6217
+ const remove = await execa9(
5546
6218
  "git",
5547
6219
  ["-C", args.hostMainRepo, "worktree", "remove", "--force", args.gitWorktreePath],
5548
6220
  { reject: false }
5549
6221
  );
5550
6222
  if (remove.exitCode === 0) return;
5551
- await execa8("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
6223
+ await execa9("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
5552
6224
  }
5553
6225
  function ctParent(p) {
5554
6226
  const i = p.lastIndexOf("/");
@@ -5579,20 +6251,20 @@ async function resyncWorkspaceFromHost(opts) {
5579
6251
  const hostMain = w.hostMainRepo;
5580
6252
  const boxBranch = w.branch;
5581
6253
  const res = { containerPath: ct, mergeConflicts: [], overlaySkipped: [] };
5582
- const hostBranchProbe = await execa8(
6254
+ const hostBranchProbe = await execa9(
5583
6255
  "git",
5584
6256
  ["-C", hostMain, "symbolic-ref", "--short", "-q", "HEAD"],
5585
6257
  { reject: false }
5586
6258
  );
5587
- const hostRef = hostBranchProbe.exitCode === 0 && hostBranchProbe.stdout.trim() ? hostBranchProbe.stdout.trim() : (await execa8("git", ["-C", hostMain, "rev-parse", "HEAD"], { reject: false })).stdout.trim();
6259
+ const hostRef = hostBranchProbe.exitCode === 0 && hostBranchProbe.stdout.trim() ? hostBranchProbe.stdout.trim() : (await execa9("git", ["-C", hostMain, "rev-parse", "HEAD"], { reject: false })).stdout.trim();
5588
6260
  if (!hostRef) {
5589
6261
  log(`resync: ${ct}: could not resolve host ref; skipping`);
5590
6262
  results.push(res);
5591
6263
  continue;
5592
6264
  }
5593
- const stash = await execa8("git", ["-C", hostMain, "stash", "create"], { reject: false });
6265
+ const stash = await execa9("git", ["-C", hostMain, "stash", "create"], { reject: false });
5594
6266
  const hostStashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
5595
- const untracked = await execa8(
6267
+ const untracked = await execa9(
5596
6268
  "git",
5597
6269
  ["-C", hostMain, "ls-files", "--others", "--exclude-standard", "-z"],
5598
6270
  { reject: false }
@@ -5662,7 +6334,7 @@ async function resyncWorkspaceFromHost(opts) {
5662
6334
  }
5663
6335
  }
5664
6336
  if (hostUntracked.length > 0) {
5665
- const probe = await execa8(
6337
+ const probe = await execa9(
5666
6338
  "docker",
5667
6339
  [
5668
6340
  "exec",
@@ -5709,13 +6381,13 @@ async function resyncWorkspaceFromHost(opts) {
5709
6381
  log(`resync: ${ct}: ${String(identical)} untracked host file(s) already identical in box (no-op)`);
5710
6382
  }
5711
6383
  if (toCopy.length > 0) {
5712
- const tarOut = await execa8("tar", ["-C", hostMain, "--null", "-T", "-", "-cf", "-"], {
6384
+ const tarOut = await execa9("tar", ["-C", hostMain, "--null", "-T", "-", "-cf", "-"], {
5713
6385
  input: toCopy.join("\0"),
5714
6386
  encoding: "buffer",
5715
6387
  reject: false
5716
6388
  });
5717
6389
  if (tarOut.exitCode === 0) {
5718
- await execa8(
6390
+ await execa9(
5719
6391
  "docker",
5720
6392
  ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
5721
6393
  { input: tarOut.stdout, reject: false }
@@ -5738,7 +6410,7 @@ var cached = null;
5738
6410
  async function detectPortless() {
5739
6411
  if (cached !== null) return cached;
5740
6412
  try {
5741
- const ver = await execa9(PORTLESS_BIN, SUB_VERSION, { reject: false });
6413
+ const ver = await execa10(PORTLESS_BIN, SUB_VERSION, { reject: false });
5742
6414
  if (ver.exitCode !== 0) {
5743
6415
  cached = { installed: false, proxyRunning: false };
5744
6416
  return cached;
@@ -5758,7 +6430,7 @@ function resetPortlessCache() {
5758
6430
  }
5759
6431
  async function portlessAlias(name, port) {
5760
6432
  try {
5761
- const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
6433
+ const r = await execa10(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
5762
6434
  return r.exitCode === 0;
5763
6435
  } catch {
5764
6436
  return false;
@@ -5766,7 +6438,7 @@ async function portlessAlias(name, port) {
5766
6438
  }
5767
6439
  async function portlessUnalias(name) {
5768
6440
  try {
5769
- const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
6441
+ const r = await execa10(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
5770
6442
  return r.exitCode === 0;
5771
6443
  } catch {
5772
6444
  return false;
@@ -5775,7 +6447,7 @@ async function portlessUnalias(name) {
5775
6447
  async function portlessGetUrl(name) {
5776
6448
  const fallback = `https://${name}.localhost`;
5777
6449
  try {
5778
- const r = await execa9(PORTLESS_BIN, [SUB_GET, name], { reject: false });
6450
+ const r = await execa10(PORTLESS_BIN, [SUB_GET, name], { reject: false });
5779
6451
  const out = (r.stdout ?? "").trim();
5780
6452
  if (r.exitCode === 0 && /^https?:\/\//.test(out)) return out;
5781
6453
  } catch {
@@ -5796,7 +6468,7 @@ function portlessBrowserEnv(boxName, opts) {
5796
6468
  }
5797
6469
  async function installPortless() {
5798
6470
  try {
5799
- const r = await execa9("npm", ["install", "-g", "portless"], { reject: false });
6471
+ const r = await execa10("npm", ["install", "-g", "portless"], { reject: false });
5800
6472
  return r.exitCode === 0;
5801
6473
  } catch {
5802
6474
  return false;
@@ -5804,7 +6476,7 @@ async function installPortless() {
5804
6476
  }
5805
6477
  async function startPortlessProxy() {
5806
6478
  try {
5807
- const r = await execa9(
6479
+ const r = await execa10(
5808
6480
  PORTLESS_BIN,
5809
6481
  ["proxy", "start", "--no-tls", "-p", String(PORTLESS_PROXY_PORT)],
5810
6482
  { reject: false }
@@ -5851,14 +6523,14 @@ async function resolvePortlessHostStateDir(override) {
5851
6523
  const live = await findLivePortlessStateDir();
5852
6524
  if (live) return live;
5853
6525
  const home = join8(homedir6(), ".portless");
5854
- if (existsSync(home)) return home;
5855
- if (existsSync("/tmp/portless")) return "/tmp/portless";
6526
+ if (existsSync2(home)) return home;
6527
+ if (existsSync2("/tmp/portless")) return "/tmp/portless";
5856
6528
  return home;
5857
6529
  }
5858
6530
  async function isProxyRunning() {
5859
6531
  if (await findLivePortlessStateDir() !== null) return true;
5860
6532
  try {
5861
- const r = await execa9("pgrep", ["-f", "portless proxy"], { reject: false });
6533
+ const r = await execa10("pgrep", ["-f", "portless proxy"], { reject: false });
5862
6534
  return r.exitCode === 0 && (r.stdout ?? "").trim().length > 0;
5863
6535
  } catch {
5864
6536
  return false;
@@ -5909,12 +6581,12 @@ async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
5909
6581
  return matches;
5910
6582
  }
5911
6583
  async function createSnapshot(opts) {
5912
- const source = resolve2(opts.source);
5913
- const destination = resolve2(opts.destination);
6584
+ const source = resolve22(opts.source);
6585
+ const destination = resolve22(opts.destination);
5914
6586
  const excluded = opts.excluded ?? EXCLUDE_DIRS;
5915
6587
  await mkdir42(SNAPSHOTS_ROOT, { recursive: true });
5916
6588
  const cpArgs = platform() === "darwin" ? ["-cR"] : ["-R"];
5917
- await execa10("cp", [...cpArgs, `${source}/`, destination]);
6589
+ await execa11("cp", [...cpArgs, `${source}/`, destination]);
5918
6590
  const toPrune = await findExcludedDirs(destination, excluded);
5919
6591
  await Promise.all(toPrune.map((p) => rm32(p, { recursive: true, force: true })));
5920
6592
  return { destination, prunedPaths: toPrune };
@@ -5922,7 +6594,7 @@ async function createSnapshot(opts) {
5922
6594
  var CHECKPOINTS_ROOT = join10(homedir8(), ".agentbox", "checkpoints");
5923
6595
  var CHECKPOINT_IMAGE_PREFIX = "agentbox-ckpt-";
5924
6596
  function checkpointImageTag(projectRoot, name) {
5925
- const mnemonic = sanitizeMnemonic(basename32(projectRoot));
6597
+ const mnemonic = sanitizeMnemonic(basename5(projectRoot));
5926
6598
  return `${CHECKPOINT_IMAGE_PREFIX}${hashProjectPath(projectRoot)}_${mnemonic}:${name}`;
5927
6599
  }
5928
6600
  function projectCheckpointsDir(projectRoot) {
@@ -6039,7 +6711,7 @@ async function runCleanup(container, log) {
6039
6711
  }
6040
6712
  }
6041
6713
  async function inspectImageConfig(imageRef) {
6042
- const r = await execa11("docker", ["image", "inspect", imageRef], { reject: false });
6714
+ const r = await execa12("docker", ["image", "inspect", imageRef], { reject: false });
6043
6715
  if (r.exitCode !== 0) {
6044
6716
  throw new CheckpointError(`docker image inspect ${imageRef} failed`, r.stdout, r.stderr);
6045
6717
  }
@@ -6115,14 +6787,14 @@ async function createCheckpoint(opts) {
6115
6787
  await runCleanup(box.container, log);
6116
6788
  if (type === "layered") {
6117
6789
  log(`docker commit ${box.container} -> ${tag} (layered)`);
6118
- const r = await execa11("docker", ["commit", box.container, tag], { reject: false });
6790
+ const r = await execa12("docker", ["commit", box.container, tag], { reject: false });
6119
6791
  if (r.exitCode !== 0) {
6120
6792
  throw new CheckpointError(`docker commit failed for ${box.container}`, r.stdout, r.stderr);
6121
6793
  }
6122
6794
  } else {
6123
6795
  log(`docker commit ${box.container} -> <intermediate> (flattened path)`);
6124
6796
  const intermediate = `${tag}-intermediate`;
6125
- const commit = await execa11("docker", ["commit", box.container, intermediate], {
6797
+ const commit = await execa12("docker", ["commit", box.container, intermediate], {
6126
6798
  reject: false
6127
6799
  });
6128
6800
  if (commit.exitCode !== 0) {
@@ -6167,7 +6839,7 @@ async function createCheckpoint(opts) {
6167
6839
  }
6168
6840
  async function flattenImage(sourceTag, destTag, log) {
6169
6841
  const tmpName = `agentbox-flatten-${Date.now().toString(36)}`;
6170
- const create = await execa11(
6842
+ const create = await execa12(
6171
6843
  "docker",
6172
6844
  ["create", "--name", tmpName, sourceTag, "sleep", "0"],
6173
6845
  { reject: false }
@@ -6179,7 +6851,7 @@ async function flattenImage(sourceTag, destTag, log) {
6179
6851
  try {
6180
6852
  const rootfsPath = join10(scratch, "rootfs.tar");
6181
6853
  log(`exporting rootfs of ${sourceTag} to ${rootfsPath}`);
6182
- const exp = await execa11("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
6854
+ const exp = await execa12("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
6183
6855
  if (exp.exitCode !== 0) {
6184
6856
  throw new CheckpointError(`docker export failed`, exp.stdout, exp.stderr);
6185
6857
  }
@@ -6192,7 +6864,7 @@ async function flattenImage(sourceTag, destTag, log) {
6192
6864
  ];
6193
6865
  await writeFile32(join10(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
6194
6866
  log(`building flattened ${destTag} from rootfs.tar (FROM scratch)`);
6195
- const build = await execa11(
6867
+ const build = await execa12(
6196
6868
  "docker",
6197
6869
  ["build", "-t", destTag, "-f", join10(scratch, "Dockerfile"), scratch],
6198
6870
  { reject: false }
@@ -6201,7 +6873,7 @@ async function flattenImage(sourceTag, destTag, log) {
6201
6873
  throw new CheckpointError(`flatten docker build failed`, build.stdout, build.stderr);
6202
6874
  }
6203
6875
  } finally {
6204
- await execa11("docker", ["rm", "-f", tmpName], { reject: false });
6876
+ await execa12("docker", ["rm", "-f", tmpName], { reject: false });
6205
6877
  await rm4(scratch, { recursive: true, force: true });
6206
6878
  }
6207
6879
  }
@@ -6245,7 +6917,7 @@ async function pathExists5(p) {
6245
6917
  }
6246
6918
  async function writeBoxEnvFile(container, env) {
6247
6919
  const body = formatBoxEnvBody(env);
6248
- const result = await execa12(
6920
+ const result = await execa13(
6249
6921
  "docker",
6250
6922
  ["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
6251
6923
  { input: body, reject: false }
@@ -6269,7 +6941,7 @@ function shellSingleQuote(s) {
6269
6941
  return `'${s.replace(/'/g, `'\\''`)}'`;
6270
6942
  }
6271
6943
  async function ensureHomeOwnedByVscode(container) {
6272
- await execa13(
6944
+ await execa14(
6273
6945
  "docker",
6274
6946
  [
6275
6947
  "exec",
@@ -6418,16 +7090,16 @@ async function spawnRelay(relayBin, cliEntry, log) {
6418
7090
  }
6419
7091
  function resolveRelayBin() {
6420
7092
  const override = process.env.AGENTBOX_RELAY_BIN;
6421
- if (override && existsSync2(override)) return override;
6422
- const here = dirname3(fileURLToPath(import.meta.url));
7093
+ if (override && existsSync3(override)) return override;
7094
+ const here = dirname32(fileURLToPath(import.meta.url));
6423
7095
  const candidates = [
6424
- resolve22(here, "..", "runtime", "relay", "bin.cjs"),
6425
- resolve22(here, "..", "..", "relay", "dist", "bin.cjs"),
6426
- resolve22(here, "..", "..", "..", "@agentbox", "relay", "dist", "bin.cjs"),
6427
- resolve22(here, "..", "..", "node_modules", "@agentbox", "relay", "dist", "bin.cjs")
7096
+ resolve3(here, "..", "runtime", "relay", "bin.cjs"),
7097
+ resolve3(here, "..", "..", "relay", "dist", "bin.cjs"),
7098
+ resolve3(here, "..", "..", "..", "@agentbox", "relay", "dist", "bin.cjs"),
7099
+ resolve3(here, "..", "..", "node_modules", "@agentbox", "relay", "dist", "bin.cjs")
6428
7100
  ];
6429
7101
  for (const c of candidates) {
6430
- if (existsSync2(c)) return c;
7102
+ if (existsSync3(c)) return c;
6431
7103
  }
6432
7104
  throw new Error(
6433
7105
  `could not locate @agentbox/relay bin; tried:
@@ -6436,29 +7108,29 @@ function resolveRelayBin() {
6436
7108
  }
6437
7109
  function resolveCliEntry() {
6438
7110
  const override = process.env.AGENTBOX_CLI_ENTRY;
6439
- if (override && existsSync2(override)) return override;
6440
- const here = dirname3(fileURLToPath(import.meta.url));
7111
+ if (override && existsSync3(override)) return override;
7112
+ const here = dirname32(fileURLToPath(import.meta.url));
6441
7113
  const candidates = [
6442
7114
  // Bundled CLI (dev + published): this module IS bundled into the CLI
6443
7115
  // entry, so the entry is index.js next to this file.
6444
- resolve22(here, "index.js"),
6445
- resolve22(here, "..", "..", "..", "apps", "cli", "dist", "index.js"),
6446
- resolve22(here, "..", "..", "..", "..", "dist", "index.js")
7116
+ resolve3(here, "index.js"),
7117
+ resolve3(here, "..", "..", "..", "apps", "cli", "dist", "index.js"),
7118
+ resolve3(here, "..", "..", "..", "..", "dist", "index.js")
6447
7119
  ];
6448
7120
  for (const c of candidates) {
6449
- if (existsSync2(c)) return c;
7121
+ if (existsSync3(c)) return c;
6450
7122
  }
6451
7123
  return null;
6452
7124
  }
6453
7125
  async function stageRelayHome(version, log) {
6454
7126
  if (!version || version === "0.0.0-dev") return null;
6455
7127
  if (process.env.AGENTBOX_RELAY_BIN || process.env.AGENTBOX_CLI_ENTRY) return null;
6456
- const cliRoot = findCliRoot(dirname3(fileURLToPath(import.meta.url)));
7128
+ const cliRoot = findCliRoot(dirname32(fileURLToPath(import.meta.url)));
6457
7129
  if (cliRoot === null) return null;
6458
7130
  const homeDir = join11(RELAY_HOME_DIR, version);
6459
7131
  const stagedEntry = join11(homeDir, "dist", "index.js");
6460
7132
  const stagedBin = join11(homeDir, "runtime", "relay", "bin.cjs");
6461
- if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
7133
+ if (existsSync3(stagedEntry) && existsSync3(stagedBin)) {
6462
7134
  return { relayBin: stagedBin, cliEntry: stagedEntry };
6463
7135
  }
6464
7136
  const nodeModules = resolveDepRoot(join11(cliRoot, "dist", "index.js"));
@@ -6470,7 +7142,7 @@ async function stageRelayHome(version, log) {
6470
7142
  await mkdir6(tmpDir, { recursive: true });
6471
7143
  for (const sub of ["dist", "runtime", "share"]) {
6472
7144
  const src = join11(cliRoot, sub);
6473
- if (existsSync2(src)) await cp(src, join11(tmpDir, sub), { recursive: true });
7145
+ if (existsSync3(src)) await cp(src, join11(tmpDir, sub), { recursive: true });
6474
7146
  }
6475
7147
  await cp(nodeModules, join11(tmpDir, "node_modules"), { recursive: true, dereference: true });
6476
7148
  await rm5(homeDir, { recursive: true, force: true });
@@ -6478,7 +7150,7 @@ async function stageRelayHome(version, log) {
6478
7150
  } catch (err) {
6479
7151
  await rm5(tmpDir, { recursive: true, force: true }).catch(() => {
6480
7152
  });
6481
- if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
7153
+ if (existsSync3(stagedEntry) && existsSync3(stagedBin)) {
6482
7154
  return { relayBin: stagedBin, cliEntry: stagedEntry };
6483
7155
  }
6484
7156
  log(`relay home staging failed (${err instanceof Error ? err.message : String(err)}); using bundle paths`);
@@ -6490,8 +7162,8 @@ async function stageRelayHome(version, log) {
6490
7162
  return { relayBin: stagedBin, cliEntry: stagedEntry };
6491
7163
  }
6492
7164
  function findCliRoot(moduleDir) {
6493
- for (const root of [resolve22(moduleDir, ".."), resolve22(moduleDir, "..", "..")]) {
6494
- if (existsSync2(join11(root, "dist", "index.js")) && existsSync2(join11(root, "runtime", "relay", "bin.cjs"))) {
7165
+ for (const root of [resolve3(moduleDir, ".."), resolve3(moduleDir, "..", "..")]) {
7166
+ if (existsSync3(join11(root, "dist", "index.js")) && existsSync3(join11(root, "runtime", "relay", "bin.cjs"))) {
6495
7167
  return root;
6496
7168
  }
6497
7169
  }
@@ -6506,7 +7178,7 @@ function resolveDepRoot(fromFile) {
6506
7178
  const idx = main.lastIndexOf(marker);
6507
7179
  if (idx === -1) return null;
6508
7180
  const nm = main.slice(0, idx + marker.length - 1);
6509
- return existsSync2(join11(nm, "commander")) ? nm : null;
7181
+ return existsSync3(join11(nm, "commander")) ? nm : null;
6510
7182
  } catch {
6511
7183
  return null;
6512
7184
  }
@@ -6667,7 +7339,8 @@ async function registerBoxWithRelay(args) {
6667
7339
  worktrees,
6668
7340
  previewUrl: args.previewUrl,
6669
7341
  previewToken: args.previewToken,
6670
- bridgeToken: args.bridgeToken
7342
+ bridgeToken: args.bridgeToken,
7343
+ autoApproveHostActions: args.autoApproveHostActions
6671
7344
  });
6672
7345
  }
6673
7346
  async function forgetBoxFromRelay(boxId) {
@@ -6808,7 +7481,8 @@ async function rehydrateRelayRegistry(boxes) {
6808
7481
  worktrees: b.gitWorktrees,
6809
7482
  previewUrl: b.relayPreviewUrl,
6810
7483
  previewToken: b.relayPreviewToken,
6811
- bridgeToken: b.bridgeToken
7484
+ bridgeToken: b.bridgeToken,
7485
+ autoApproveHostActions: b.autoApproveHostActions
6812
7486
  });
6813
7487
  } catch {
6814
7488
  }
@@ -6959,7 +7633,7 @@ function persistableLimits(lim) {
6959
7633
  return Object.keys(out).length > 0 ? out : void 0;
6960
7634
  }
6961
7635
  function sanitizeBasename(workspacePath) {
6962
- const raw = basename4(resolve3(workspacePath));
7636
+ const raw = basename6(resolve4(workspacePath));
6963
7637
  return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
6964
7638
  }
6965
7639
  function defaultBoxName(workspacePath, id) {
@@ -6990,7 +7664,7 @@ async function buildIdentityMounts() {
6990
7664
  async function createBox(opts) {
6991
7665
  const log = opts.onLog ?? (() => {
6992
7666
  });
6993
- const workspace = resolve3(opts.workspacePath);
7667
+ const workspace = resolve4(opts.workspacePath);
6994
7668
  if (!await pathExists6(workspace)) {
6995
7669
  throw new Error(`workspace does not exist: ${workspace}`);
6996
7670
  }
@@ -7283,6 +7957,7 @@ async function createBox(opts) {
7283
7957
  }
7284
7958
  }
7285
7959
  for (const v of extraVolumes) log(`mounting agent dir: ${v}`);
7960
+ const autoApproveHostActions = (await loadEffectiveConfig(opts.projectRoot ?? workspace)).effective.box.autoApproveHostActions;
7286
7961
  const relayToken = generateRelayToken();
7287
7962
  if (relayUp) {
7288
7963
  try {
@@ -7293,7 +7968,8 @@ async function createBox(opts) {
7293
7968
  containerName,
7294
7969
  createdAt,
7295
7970
  projectIndex,
7296
- worktrees: gitWorktreeRecords
7971
+ worktrees: gitWorktreeRecords,
7972
+ autoApproveHostActions
7297
7973
  });
7298
7974
  log(`registered box token with relay`);
7299
7975
  } catch (err) {
@@ -7359,6 +8035,7 @@ async function createBox(opts) {
7359
8035
  gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : void 0,
7360
8036
  withPlaywright: opts.withPlaywright ? true : void 0,
7361
8037
  withEnv: opts.withEnv ? true : void 0,
8038
+ autoApproveHostActions: autoApproveHostActions ? true : void 0,
7362
8039
  vncEnabled: vncEnabled ? true : void 0,
7363
8040
  vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : void 0,
7364
8041
  vncPassword,
@@ -7416,7 +8093,7 @@ async function createBox(opts) {
7416
8093
  } catch (err) {
7417
8094
  if (opts.useBranch !== void 0) {
7418
8095
  log(`seedWorkspace failed for --use-branch ${opts.useBranch}; cleaning up the box`);
7419
- await execa14("docker", ["rm", "-f", containerName], { reject: false });
8096
+ await execa15("docker", ["rm", "-f", containerName], { reject: false });
7420
8097
  await removeBoxRecord(id);
7421
8098
  for (const w of gitWorktreeRecords) {
7422
8099
  await removeInBoxWorktree({
@@ -7479,7 +8156,7 @@ async function createBox(opts) {
7479
8156
  else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);
7480
8157
  if (opts.withPlaywright) {
7481
8158
  log("installing @playwright/cli@latest (--with-playwright)");
7482
- const result = await execa14(
8159
+ const result = await execa15(
7483
8160
  "docker",
7484
8161
  [
7485
8162
  "exec",
@@ -7527,9 +8204,14 @@ async function createBox(opts) {
7527
8204
  let carrySummary;
7528
8205
  if (opts.carry && opts.carry.length > 0) {
7529
8206
  log(`carry: copying ${String(opts.carry.length)} host path(s) into the box`);
8207
+ const entries = await renderCarryEntries(
8208
+ opts.carry,
8209
+ { name, id, kind: "docker", hostWorkspace: workspace, projectRoot: opts.projectRoot },
8210
+ log
8211
+ );
7530
8212
  const result = await copyCarryPathsToBox({
7531
8213
  container: containerName,
7532
- entries: opts.carry,
8214
+ entries,
7533
8215
  onLog: log
7534
8216
  });
7535
8217
  log(`carry: copied ${String(result.copied)}/${String(opts.carry.length)} entry/entries`);
@@ -7653,7 +8335,7 @@ function parseShellSessionList(stdout) {
7653
8335
  return out;
7654
8336
  }
7655
8337
  async function listShellSessions(container, user) {
7656
- const res = await execa15(
8338
+ const res = await execa16(
7657
8339
  "docker",
7658
8340
  [
7659
8341
  "exec",
@@ -7677,7 +8359,7 @@ async function startShellSession(opts) {
7677
8359
  const login = opts.login !== false;
7678
8360
  const term = process.env["TERM"] ?? "xterm-256color";
7679
8361
  const cmd = login ? "bash -l" : "bash";
7680
- const result = await execa15(
8362
+ const result = await execa16(
7681
8363
  "docker",
7682
8364
  [
7683
8365
  "exec",
@@ -7726,7 +8408,7 @@ function buildShellSessionAttachArgv(container, sessionName, user) {
7726
8408
  }
7727
8409
  async function shellSessionInfo(container, sessionName, user) {
7728
8410
  const name = sessionName ?? DEFAULT_SHELL_SESSION;
7729
- const has = await execa15(
8411
+ const has = await execa16(
7730
8412
  "docker",
7731
8413
  ["exec", "--user", user ?? CONTAINER_USER, container, "tmux", "has-session", "-t", name],
7732
8414
  { reject: false }
@@ -7734,7 +8416,7 @@ async function shellSessionInfo(container, sessionName, user) {
7734
8416
  return { running: has.exitCode === 0, sessionName: name };
7735
8417
  }
7736
8418
  async function killShellSession(container, sessionName, user) {
7737
- const res = await execa15(
8419
+ const res = await execa16(
7738
8420
  "docker",
7739
8421
  [
7740
8422
  "exec",
@@ -8017,7 +8699,8 @@ async function startBox(idOrName) {
8017
8699
  containerName: box.container,
8018
8700
  createdAt: box.createdAt,
8019
8701
  projectIndex: box.projectIndex,
8020
- worktrees: box.gitWorktrees
8702
+ worktrees: box.gitWorktrees,
8703
+ autoApproveHostActions: box.autoApproveHostActions
8021
8704
  });
8022
8705
  } catch {
8023
8706
  }
@@ -8036,7 +8719,7 @@ async function getBoxHostPaths(idOrName) {
8036
8719
  }
8037
8720
  async function dirSizeBytes(path) {
8038
8721
  try {
8039
- const result = await execa16("du", ["-sk", path], { reject: false });
8722
+ const result = await execa17("du", ["-sk", path], { reject: false });
8040
8723
  if (result.exitCode !== 0) return null;
8041
8724
  const sizeKb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
8042
8725
  if (Number.isNaN(sizeKb)) return null;
@@ -8181,7 +8864,7 @@ async function listBoxDirs() {
8181
8864
  }
8182
8865
  }
8183
8866
  async function listCheckpointImageTags() {
8184
- const r = await execa16(
8867
+ const r = await execa17(
8185
8868
  "docker",
8186
8869
  ["image", "ls", "--format", "{{.Repository}}:{{.Tag}}", `${CHECKPOINT_IMAGE_PREFIX}*`],
8187
8870
  { reject: false }
@@ -8289,7 +8972,7 @@ async function pruneBoxes(opts = {}) {
8289
8972
  } catch {
8290
8973
  }
8291
8974
  try {
8292
- await execa16("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
8975
+ await execa17("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
8293
8976
  } catch {
8294
8977
  }
8295
8978
  try {
@@ -8351,7 +9034,7 @@ function splitPair(raw) {
8351
9034
  return [parts[0].trim(), parts[1].trim()];
8352
9035
  }
8353
9036
  async function duBytes(path) {
8354
- const result = await execa17("du", ["-sk", path], { reject: false });
9037
+ const result = await execa18("du", ["-sk", path], { reject: false });
8355
9038
  if (result.exitCode !== 0) return null;
8356
9039
  const kb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
8357
9040
  return Number.isNaN(kb) ? null : kb * 1024;
@@ -8364,7 +9047,7 @@ async function volumeSizeBytes(name) {
8364
9047
  const sz = await duBytes(live);
8365
9048
  if (sz !== null) return sz;
8366
9049
  }
8367
- const df = await execa17(
9050
+ const df = await execa18(
8368
9051
  "docker",
8369
9052
  ["system", "df", "-v", "--format", "{{json .Volumes}}"],
8370
9053
  { reject: false }
@@ -8385,7 +9068,7 @@ async function volumeSizeBytes(name) {
8385
9068
  return null;
8386
9069
  }
8387
9070
  async function imageBytes(tag) {
8388
- const r = await execa17("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
9071
+ const r = await execa18("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
8389
9072
  reject: false
8390
9073
  });
8391
9074
  if (r.exitCode !== 0) return null;
@@ -8396,7 +9079,7 @@ async function projectCheckpointImageBytes(projectRoot, name) {
8396
9079
  return imageBytes(checkpointImageTag(projectRoot, name));
8397
9080
  }
8398
9081
  async function allCheckpointImagesBytes() {
8399
- const r = await execa17(
9082
+ const r = await execa18(
8400
9083
  "docker",
8401
9084
  [
8402
9085
  "image",
@@ -8448,7 +9131,7 @@ function reconcileLimits(persisted, dockerJson) {
8448
9131
  };
8449
9132
  }
8450
9133
  async function containerWritableBytes(container) {
8451
- const r = await execa17(
9134
+ const r = await execa18(
8452
9135
  "docker",
8453
9136
  ["ps", "-a", "--filter", `name=^${container}$`, "--format", "{{.Size}}", "--size"],
8454
9137
  { reject: false }
@@ -8495,7 +9178,7 @@ async function boxResourceStats(record) {
8495
9178
  if (await inspectContainerStatus(record.container) !== "running") {
8496
9179
  return base;
8497
9180
  }
8498
- const proc = await execa17(
9181
+ const proc = await execa18(
8499
9182
  "docker",
8500
9183
  ["stats", "--no-stream", "--format", "{{json .}}", record.container],
8501
9184
  { reject: false }
@@ -8530,125 +9213,6 @@ async function boxResourceStats(record) {
8530
9213
  blockWriteBytes: blkPair ? parseDockerSize(blkPair[1]) : null
8531
9214
  };
8532
9215
  }
8533
- function posixDirname(p) {
8534
- return posix.dirname(p) || "/";
8535
- }
8536
- function asText(s) {
8537
- if (s === void 0) return "";
8538
- if (typeof s === "string") return s;
8539
- return Buffer.from(s).toString("utf8");
8540
- }
8541
- async function uploadToBox(box, hostSrc, boxDst) {
8542
- const srcAbs = resolve4(hostSrc);
8543
- if (!existsSync3(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
8544
- const srcBasename = basename5(srcAbs);
8545
- const srcParent = dirname22(srcAbs);
8546
- let boxParent;
8547
- let finalName;
8548
- if (boxDst.endsWith("/")) {
8549
- boxParent = boxDst.replace(/\/+$/, "") || "/";
8550
- finalName = srcBasename;
8551
- } else {
8552
- const isDir2 = await execa18(
8553
- "docker",
8554
- ["exec", box.container, "test", "-d", boxDst],
8555
- { reject: false }
8556
- );
8557
- if (isDir2.exitCode === 0) {
8558
- boxParent = boxDst.replace(/\/+$/, "") || "/";
8559
- finalName = srcBasename;
8560
- } else {
8561
- boxParent = posixDirname(boxDst);
8562
- finalName = posix.basename(boxDst);
8563
- }
8564
- }
8565
- const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
8566
- const mk = await execa18(
8567
- "docker",
8568
- ["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
8569
- { reject: false }
8570
- );
8571
- if (mk.exitCode !== 0) {
8572
- throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
8573
- }
8574
- const packed = await execa18("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
8575
- encoding: "buffer",
8576
- reject: false,
8577
- env: { ...process.env, COPYFILE_DISABLE: "1" }
8578
- });
8579
- if (packed.exitCode !== 0) {
8580
- throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
8581
- }
8582
- const extract = await execa18(
8583
- "docker",
8584
- ["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
8585
- { input: packed.stdout, reject: false }
8586
- );
8587
- if (extract.exitCode !== 0) {
8588
- throw new Error(`tar extract in box failed: ${asText(extract.stderr).slice(0, 300)}`);
8589
- }
8590
- if (finalName !== srcBasename) {
8591
- const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
8592
- const mv = await execa18(
8593
- "docker",
8594
- ["exec", "--user", "root", box.container, "mv", initial, finalPath],
8595
- { reject: false }
8596
- );
8597
- if (mv.exitCode !== 0) {
8598
- throw new Error(
8599
- `rename ${initial} -> ${finalPath} in box failed: ${asText(mv.stderr).slice(0, 300)}`
8600
- );
8601
- }
8602
- }
8603
- const chown = await execa18(
8604
- "docker",
8605
- ["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
8606
- { reject: false }
8607
- );
8608
- if (chown.exitCode !== 0) {
8609
- return {
8610
- finalPath,
8611
- warn: `chown ${finalPath} to vscode (uid 1000) failed; ownership inside the box may be root.`
8612
- };
8613
- }
8614
- return { finalPath };
8615
- }
8616
- async function downloadFromBox(box, boxSrc, hostDst) {
8617
- const srcBasename = posix.basename(boxSrc);
8618
- const srcParent = posixDirname(boxSrc);
8619
- const dstAbs = resolve4(hostDst);
8620
- let hostParent;
8621
- let finalName;
8622
- const dstExists = existsSync3(dstAbs);
8623
- if (hostDst.endsWith("/") || dstExists && statSync(dstAbs).isDirectory()) {
8624
- hostParent = dstAbs;
8625
- finalName = srcBasename;
8626
- } else {
8627
- hostParent = dirname22(dstAbs);
8628
- finalName = basename5(dstAbs);
8629
- }
8630
- mkdirSync(hostParent, { recursive: true });
8631
- const finalPath = posix.join(hostParent, finalName);
8632
- const packed = await execa18(
8633
- "docker",
8634
- ["exec", box.container, "tar", "-C", srcParent, "-cf", "-", srcBasename],
8635
- { encoding: "buffer", reject: false }
8636
- );
8637
- if (packed.exitCode !== 0) {
8638
- throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
8639
- }
8640
- const extract = await execa18("tar", ["-xf", "-", "-C", hostParent], {
8641
- input: packed.stdout,
8642
- reject: false
8643
- });
8644
- if (extract.exitCode !== 0) {
8645
- throw new Error(`tar extract on host failed: ${asText(extract.stderr).slice(0, 300)}`);
8646
- }
8647
- if (finalName !== srcBasename) {
8648
- renameSync(posix.join(hostParent, srcBasename), finalPath);
8649
- }
8650
- return { finalPath };
8651
- }
8652
9216
  var dockerProvider = {
8653
9217
  name: "docker",
8654
9218
  async create(req) {
@@ -8724,12 +9288,12 @@ var dockerProvider = {
8724
9288
  const r = await execInBox(box.container, argv, opts?.user ? { user: opts.user } : {});
8725
9289
  return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
8726
9290
  },
8727
- async uploadPath(box, hostSrc, boxDst) {
8728
- const r = await uploadToBox(box, hostSrc, boxDst);
9291
+ async uploadPath(box, hostSrc, boxDst, exclude) {
9292
+ const r = await uploadToBox(box, hostSrc, boxDst, exclude);
8729
9293
  return { finalPath: r.finalPath };
8730
9294
  },
8731
- async downloadPath(box, boxSrc, hostDst) {
8732
- const r = await downloadFromBox(box, boxSrc, hostDst);
9295
+ async downloadPath(box, boxSrc, hostDst, exclude) {
9296
+ const r = await downloadFromBox(box, boxSrc, hostDst, exclude);
8733
9297
  return { finalPath: r.finalPath };
8734
9298
  },
8735
9299
  async resolveUrl(box, opts) {
@@ -8884,10 +9448,10 @@ async function stageDynamicSyncTarball(uploads) {
8884
9448
  try {
8885
9449
  for (const up of uploads) {
8886
9450
  const target = join16(stageDir, up.set, up.rel);
8887
- await mkdir8(dirname32(target), { recursive: true });
9451
+ await mkdir8(dirname4(target), { recursive: true });
8888
9452
  await copyFile2(up.absSrc, target);
8889
9453
  }
8890
- tarballPath = join16(tmpdir4(), `agentbox-dynsync-${basename6(stageDir)}.tar.gz`);
9454
+ tarballPath = join16(tmpdir4(), `agentbox-dynsync-${basename7(stageDir)}.tar.gz`);
8891
9455
  await execa19("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
8892
9456
  env: { ...process.env, COPYFILE_DISABLE: "1" }
8893
9457
  });
@@ -8928,6 +9492,7 @@ async function ensureBoxBrowser(container, timeoutMs = 8e3, targetUrl = "about:b
8928
9492
  }
8929
9493
 
8930
9494
  export {
9495
+ BUILT_IN_DEFAULTS,
8931
9496
  KEY_REGISTRY,
8932
9497
  lookupKey,
8933
9498
  UserConfigError,
@@ -8951,12 +9516,10 @@ export {
8951
9516
  renderStatusTable,
8952
9517
  renderTaskTable,
8953
9518
  renderPortsTable,
9519
+ parseReplacementsSection,
9520
+ parseCarrySection,
8954
9521
  loadCarrySection,
8955
- resolveAgentLauncher,
8956
- UserFacingError,
8957
- BoxNotFoundError,
8958
- AmbiguousBoxError,
8959
- generateBoxId,
9522
+ ALL_CONNECTORS,
8960
9523
  DEFAULT_RELAY_PORT,
8961
9524
  RELAY_CONTAINER_NAME,
8962
9525
  RELAY_NETWORK_NAME,
@@ -8974,6 +9537,8 @@ export {
8974
9537
  execInBox,
8975
9538
  removeImage,
8976
9539
  volumeExists,
9540
+ uploadToBox,
9541
+ downloadFromBox,
8977
9542
  CONTAINER_EXPORT_MERGED,
8978
9543
  detectEngine,
8979
9544
  setEngineOverride,
@@ -9174,8 +9739,6 @@ export {
9174
9739
  allCheckpointImagesBytes,
9175
9740
  agentboxHomeBytes,
9176
9741
  boxResourceStats,
9177
- uploadToBox,
9178
- downloadFromBox,
9179
9742
  dockerProvider,
9180
9743
  BOX_WORKFLOWS_DIR,
9181
9744
  BOX_DYNAMIC_SYNC_MANIFEST,
@@ -9186,4 +9749,4 @@ export {
9186
9749
  browserSessionActive,
9187
9750
  ensureBoxBrowser
9188
9751
  };
9189
- //# sourceMappingURL=chunk-TCS5HXJX.js.map
9752
+ //# sourceMappingURL=chunk-GXJNJUEV.js.map