@madarco/agentbox 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +11 -8
  3. package/dist/{_cloud-attach-HJC672UR.js → _cloud-attach-R6TRWG5L.js} +4 -4
  4. package/dist/{chunk-QYRK5H6Q.js → chunk-43Q5GWP6.js} +108 -56
  5. package/dist/chunk-43Q5GWP6.js.map +1 -0
  6. package/dist/{chunk-ECLLV5JH.js → chunk-72CJTXN6.js} +156 -5
  7. package/dist/chunk-72CJTXN6.js.map +1 -0
  8. package/dist/{chunk-R5XIDQFR.js → chunk-BKU34KYY.js} +170 -6
  9. package/dist/chunk-BKU34KYY.js.map +1 -0
  10. package/dist/{chunk-4NQXNQ53.js → chunk-E7CHS7ZR.js} +168 -58
  11. package/dist/chunk-E7CHS7ZR.js.map +1 -0
  12. package/dist/chunk-MCOU6CZS.js +346 -0
  13. package/dist/chunk-MCOU6CZS.js.map +1 -0
  14. package/dist/{chunk-B4QG2MCW.js → chunk-MLMFNN4T.js} +762 -483
  15. package/dist/chunk-MLMFNN4T.js.map +1 -0
  16. package/dist/{chunk-2LF5YILI.js → chunk-RSKG7AFU.js} +80 -6
  17. package/dist/chunk-RSKG7AFU.js.map +1 -0
  18. package/dist/{chunk-SNTHHWKY.js → chunk-XKH7NTT7.js} +80 -22
  19. package/dist/chunk-XKH7NTT7.js.map +1 -0
  20. package/dist/{dist-7KVUIKJX.js → dist-AGTIA7AD.js} +37 -226
  21. package/dist/dist-AGTIA7AD.js.map +1 -0
  22. package/dist/{dist-OPIBZ7XM.js → dist-FIFEFKJ7.js} +14 -69
  23. package/dist/dist-FIFEFKJ7.js.map +1 -0
  24. package/dist/dist-JZ3XO6EB.js +662 -0
  25. package/dist/dist-JZ3XO6EB.js.map +1 -0
  26. package/dist/{dist-OG6NW6SM.js → dist-OGJGZETZ.js} +5 -3
  27. package/dist/{dist-JAN5VABY.js → dist-S4XR4ACV.js} +25 -177
  28. package/dist/dist-S4XR4ACV.js.map +1 -0
  29. package/dist/index.js +2229 -1314
  30. package/dist/index.js.map +1 -1
  31. package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
  32. package/package.json +6 -4
  33. package/runtime/docker/Dockerfile.box +21 -26
  34. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +67 -1
  35. package/runtime/docker/packages/ctl/dist/bin.cjs +361 -43
  36. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +17 -6
  37. package/runtime/docker/packages/sandbox-docker/scripts/chromium-resolver +57 -0
  38. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +2 -1
  39. package/runtime/e2b/agentbox-checkpoint-cleanup +52 -0
  40. package/runtime/e2b/agentbox-codex-hooks.json +68 -0
  41. package/runtime/e2b/agentbox-open +28 -0
  42. package/runtime/e2b/agentbox-setup-skill.md +263 -0
  43. package/runtime/e2b/agentbox-vnc-start +102 -0
  44. package/runtime/e2b/attach-helper.cjs +167 -0
  45. package/runtime/e2b/claude-managed-settings.json +116 -0
  46. package/runtime/e2b/ctl.cjs +24158 -0
  47. package/runtime/e2b/custom-system-CLAUDE.md +46 -0
  48. package/runtime/e2b/gh-shim +344 -0
  49. package/runtime/e2b/git-shim +131 -0
  50. package/runtime/e2b/scripts/build-template.sh +295 -0
  51. package/runtime/hetzner/agentbox-setup-skill.md +67 -1
  52. package/runtime/hetzner/agentbox-vnc-start +17 -6
  53. package/runtime/hetzner/claude-managed-settings.json +2 -1
  54. package/runtime/hetzner/ctl.cjs +361 -43
  55. package/runtime/relay/bin.cjs +380 -233
  56. package/runtime/vercel/agentbox-setup-skill.md +67 -1
  57. package/runtime/vercel/agentbox-vnc-start +17 -6
  58. package/runtime/vercel/claude-managed-settings.json +2 -1
  59. package/runtime/vercel/ctl.cjs +361 -43
  60. package/share/agentbox-setup/SKILL.md +67 -1
  61. package/share/host-skills/agentbox-info/SKILL.md +47 -35
  62. package/dist/chunk-2LF5YILI.js.map +0 -1
  63. package/dist/chunk-4NQXNQ53.js.map +0 -1
  64. package/dist/chunk-B4QG2MCW.js.map +0 -1
  65. package/dist/chunk-ECLLV5JH.js.map +0 -1
  66. package/dist/chunk-QYRK5H6Q.js.map +0 -1
  67. package/dist/chunk-R5XIDQFR.js.map +0 -1
  68. package/dist/chunk-SNTHHWKY.js.map +0 -1
  69. package/dist/dist-7KVUIKJX.js.map +0 -1
  70. package/dist/dist-JAN5VABY.js.map +0 -1
  71. package/dist/dist-OPIBZ7XM.js.map +0 -1
  72. /package/dist/{_cloud-attach-HJC672UR.js.map → _cloud-attach-R6TRWG5L.js.map} +0 -0
  73. /package/dist/{dist-OG6NW6SM.js.map → dist-OGJGZETZ.js.map} +0 -0
  74. /package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js.map → prepared-state-MQHD3M5F-Q27AZU53.js.map} +0 -0
@@ -4,7 +4,6 @@ import {
4
4
  GitWorktreeError,
5
5
  STATE_DIR,
6
6
  STATE_FILE,
7
- allocateProjectIndex,
8
7
  computeDockerContextFingerprint,
9
8
  detectGitRepos,
10
9
  ensureImage,
@@ -18,14 +17,15 @@ import {
18
17
  readPreparedDockerState,
19
18
  readState,
20
19
  recordBox,
21
- removeBoxRecord
22
- } from "./chunk-SNTHHWKY.js";
20
+ removeBoxRecord,
21
+ reserveProjectIndex
22
+ } from "./chunk-XKH7NTT7.js";
23
23
 
24
24
  // ../../packages/sandbox-docker/dist/index.js
25
25
  import { mkdir as mkdir7, stat as stat8 } from "fs/promises";
26
26
  import { homedir as homedir10 } from "os";
27
- import { basename as basename4, join as join11, resolve as resolve3 } from "path";
28
- import { execa as execa14 } from "execa";
27
+ import { basename as basename5, join as join12, resolve as resolve4 } from "path";
28
+ import { execa as execa15 } from "execa";
29
29
 
30
30
  // ../../packages/ctl/dist/index.js
31
31
  import { readFile } from "fs/promises";
@@ -517,7 +517,21 @@ var CarryConfigError = class extends Error {
517
517
  this.name = "CarryConfigError";
518
518
  }
519
519
  };
520
- var ITEM_KEYS = /* @__PURE__ */ new Set(["src", "dest", "mode", "user", "optional"]);
520
+ var ITEM_KEYS = /* @__PURE__ */ new Set(["src", "dest", "mode", "user", "exclude", "optional"]);
521
+ function parseExclude(raw, where) {
522
+ if (raw === void 0 || raw === null) return void 0;
523
+ if (!Array.isArray(raw)) {
524
+ throw new CarryConfigError(`${where}.exclude must be a list of glob/name strings`);
525
+ }
526
+ const out = [];
527
+ for (const [i, v] of raw.entries()) {
528
+ if (typeof v !== "string" || v.trim().length === 0) {
529
+ throw new CarryConfigError(`${where}.exclude[${String(i)}] must be a non-empty string`);
530
+ }
531
+ out.push(v.trim());
532
+ }
533
+ return out.length > 0 ? out : void 0;
534
+ }
521
535
  function parseUser(raw, where) {
522
536
  if (raw === void 0 || raw === null) return void 0;
523
537
  let n;
@@ -646,6 +660,7 @@ function parseMapping(raw, where) {
646
660
  assertDestShape(dest, where);
647
661
  const mode = parseMode(raw.mode, where);
648
662
  const user = parseUser(raw.user, where);
663
+ const exclude = parseExclude(raw.exclude, where);
649
664
  let optional = false;
650
665
  if (raw.optional !== void 0 && raw.optional !== null) {
651
666
  if (typeof raw.optional !== "boolean") {
@@ -656,6 +671,7 @@ function parseMapping(raw, where) {
656
671
  const out = { src, dest, optional };
657
672
  if (mode !== void 0) out.mode = mode;
658
673
  if (user !== void 0) out.user = user;
674
+ if (exclude !== void 0) out.exclude = exclude;
659
675
  return out;
660
676
  }
661
677
  function parseCarryRaw(raw) {
@@ -702,20 +718,6 @@ async function loadCarrySection(path) {
702
718
  return parseCarrySection(text);
703
719
  }
704
720
 
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 readFile4, 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
721
  // ../../packages/config/dist/index.js
720
722
  import { parse as parseYaml3 } from "yaml";
721
723
  import { createHash } from "crypto";
@@ -737,15 +739,18 @@ var BUILT_IN_DEFAULTS = {
737
739
  defaultCheckpointDaytona: "",
738
740
  defaultCheckpointHetzner: "",
739
741
  defaultCheckpointVercel: "",
742
+ defaultCheckpointE2b: "",
740
743
  size: "",
741
744
  sizeDocker: "",
742
745
  sizeDaytona: "",
743
746
  sizeHetzner: "",
744
747
  sizeVercel: "",
748
+ sizeE2b: "",
745
749
  withPlaywright: false,
746
750
  withEnv: false,
747
751
  resyncOnStart: true,
748
752
  vnc: true,
753
+ autoApproveHostActions: false,
749
754
  isolateClaudeConfig: false,
750
755
  isolateCodexConfig: false,
751
756
  isolateOpencodeConfig: false,
@@ -754,6 +759,7 @@ var BUILT_IN_DEFAULTS = {
754
759
  imageDaytona: "",
755
760
  imageHetzner: "",
756
761
  imageVercel: "",
762
+ imageE2b: "",
757
763
  // Mirrors BOX_IMAGE_REGISTRY in @agentbox/sandbox-docker. Empty disables the
758
764
  // registry pull (always build the docker base image locally).
759
765
  imageRegistry: "ghcr.io/madarco/agentbox/box",
@@ -765,7 +771,8 @@ var BUILT_IN_DEFAULTS = {
765
771
  bundleDepth: void 0,
766
772
  vercelVcpus: 2,
767
773
  vercelTimeoutMs: 27e5,
768
- vercelNetworkPolicy: ""
774
+ vercelNetworkPolicy: "",
775
+ cpMaxBytes: 100 * 1024 * 1024
769
776
  },
770
777
  checkpoint: {
771
778
  maxLayers: 3
@@ -821,7 +828,8 @@ var BUILT_IN_DEFAULTS = {
821
828
  enabled: true,
822
829
  maxConcurrent: 5,
823
830
  maxWorking: 0,
824
- idleGraceSeconds: 15
831
+ idleGraceSeconds: 15,
832
+ openIn: "none"
825
833
  },
826
834
  cloud: {
827
835
  useCurrentBranch: false
@@ -835,8 +843,8 @@ var KEY_REGISTRY = [
835
843
  {
836
844
  key: "box.provider",
837
845
  type: "enum",
838
- enumValues: ["docker", "daytona", "hetzner", "vercel"],
839
- description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, or Vercel Sandboxes."
846
+ enumValues: ["docker", "daytona", "hetzner", "vercel", "e2b"],
847
+ description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, Vercel Sandboxes, or E2B microVMs."
840
848
  },
841
849
  {
842
850
  key: "box.hostSnapshot",
@@ -872,6 +880,12 @@ var KEY_REGISTRY = [
872
880
  description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
873
881
  advanced: true
874
882
  },
883
+ {
884
+ key: "box.defaultCheckpointE2b",
885
+ type: "string",
886
+ description: "Per-provider override of `box.defaultCheckpoint` for e2b. Wins over the global when set; set via `agentbox checkpoint set-default --provider e2b`.",
887
+ advanced: true
888
+ },
875
889
  {
876
890
  key: "box.size",
877
891
  type: "string",
@@ -901,6 +915,12 @@ var KEY_REGISTRY = [
901
915
  description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
902
916
  advanced: true
903
917
  },
918
+ {
919
+ key: "box.sizeE2b",
920
+ type: "string",
921
+ description: "Per-provider override of `box.size` for e2b. Reserved \u2014 e2b sizing is template-level (set at `agentbox prepare --provider e2b` time via --vcpus / --memory).",
922
+ advanced: true
923
+ },
904
924
  {
905
925
  key: "checkpoint.maxLayers",
906
926
  type: "int",
@@ -927,6 +947,11 @@ var KEY_REGISTRY = [
927
947
  type: "bool",
928
948
  description: "Run the per-box Xvnc + noVNC stack."
929
949
  },
950
+ {
951
+ key: "box.autoApproveHostActions",
952
+ type: "bool",
953
+ 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)."
954
+ },
930
955
  {
931
956
  key: "box.isolateClaudeConfig",
932
957
  type: "bool",
@@ -972,6 +997,12 @@ var KEY_REGISTRY = [
972
997
  description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
973
998
  advanced: true
974
999
  },
1000
+ {
1001
+ key: "box.imageE2b",
1002
+ type: "string",
1003
+ description: "Per-provider override of `box.image` for e2b (template id or `name:tag`, e.g. `agentbox-base:latest`). Written by `agentbox prepare --provider e2b`.",
1004
+ advanced: true
1005
+ },
975
1006
  {
976
1007
  key: "box.imageRegistry",
977
1008
  type: "string",
@@ -1019,6 +1050,12 @@ var KEY_REGISTRY = [
1019
1050
  type: "int",
1020
1051
  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."
1021
1052
  },
1053
+ {
1054
+ key: "box.cpMaxBytes",
1055
+ type: "int",
1056
+ 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).",
1057
+ advanced: true
1058
+ },
1022
1059
  {
1023
1060
  key: "box.vercelNetworkPolicy",
1024
1061
  type: "string",
@@ -1166,6 +1203,12 @@ var KEY_REGISTRY = [
1166
1203
  type: "int",
1167
1204
  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."
1168
1205
  },
1206
+ {
1207
+ key: "queue.openIn",
1208
+ type: "enum",
1209
+ enumValues: ["none", "split", "window", "tab"],
1210
+ 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."
1211
+ },
1169
1212
  {
1170
1213
  key: "cloud.useCurrentBranch",
1171
1214
  type: "bool",
@@ -1526,7 +1569,7 @@ function writeLeaf(obj, branch, leaf, value) {
1526
1569
  b[leaf] = value;
1527
1570
  }
1528
1571
  function resolveDefaultCheckpoint(cfg, provider) {
1529
- const perProvider = provider === "daytona" ? cfg.box.defaultCheckpointDaytona : provider === "hetzner" ? cfg.box.defaultCheckpointHetzner : provider === "vercel" ? cfg.box.defaultCheckpointVercel : cfg.box.defaultCheckpointDocker;
1572
+ 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;
1530
1573
  if (perProvider && perProvider.length > 0) return perProvider;
1531
1574
  return cfg.box.defaultCheckpoint;
1532
1575
  }
@@ -1535,15 +1578,16 @@ function defaultCheckpointConfigKey(provider) {
1535
1578
  if (provider === "daytona") return "box.defaultCheckpointDaytona";
1536
1579
  if (provider === "hetzner") return "box.defaultCheckpointHetzner";
1537
1580
  if (provider === "vercel") return "box.defaultCheckpointVercel";
1581
+ if (provider === "e2b") return "box.defaultCheckpointE2b";
1538
1582
  return "box.defaultCheckpoint";
1539
1583
  }
1540
1584
  function resolveBoxSize(cfg, provider) {
1541
- const perProvider = provider === "daytona" ? cfg.box.sizeDaytona : provider === "hetzner" ? cfg.box.sizeHetzner : provider === "vercel" ? cfg.box.sizeVercel : cfg.box.sizeDocker;
1585
+ const perProvider = provider === "daytona" ? cfg.box.sizeDaytona : provider === "hetzner" ? cfg.box.sizeHetzner : provider === "vercel" ? cfg.box.sizeVercel : provider === "e2b" ? cfg.box.sizeE2b : cfg.box.sizeDocker;
1542
1586
  if (perProvider && perProvider.length > 0) return perProvider;
1543
1587
  return cfg.box.size;
1544
1588
  }
1545
1589
  function resolveBoxImage(cfg, provider) {
1546
- const perProvider = provider === "daytona" ? cfg.box.imageDaytona : provider === "hetzner" ? cfg.box.imageHetzner : provider === "vercel" ? cfg.box.imageVercel : cfg.box.imageDocker;
1590
+ const perProvider = provider === "daytona" ? cfg.box.imageDaytona : provider === "hetzner" ? cfg.box.imageHetzner : provider === "vercel" ? cfg.box.imageVercel : provider === "e2b" ? cfg.box.imageE2b : cfg.box.imageDocker;
1547
1591
  if (perProvider && perProvider.length > 0) return perProvider;
1548
1592
  return cfg.box.image;
1549
1593
  }
@@ -1552,6 +1596,7 @@ function boxImageConfigKey(provider) {
1552
1596
  if (provider === "daytona") return "box.imageDaytona";
1553
1597
  if (provider === "hetzner") return "box.imageHetzner";
1554
1598
  if (provider === "vercel") return "box.imageVercel";
1599
+ if (provider === "e2b") return "box.imageE2b";
1555
1600
  return "box.image";
1556
1601
  }
1557
1602
  async function setConfigValue(scope, key, value, cwd, opts = {}) {
@@ -1748,30 +1793,48 @@ async function touchProjectMeta(absPath) {
1748
1793
  }
1749
1794
 
1750
1795
  // ../../packages/sandbox-docker/dist/index.js
1796
+ import { spawnSync } from "child_process";
1797
+ 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";
1798
+ import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
1799
+ import { isAbsolute as isAbsolute2, join as join4, relative as relative2 } from "path";
1800
+ import { setTimeout as delay } from "timers/promises";
1801
+ import { execa as execa6 } from "execa";
1802
+ import { execa as execa2 } from "execa";
1803
+ import { mkdir as mkdir4, readFile as readFile5, readdir as readdir4, stat as stat4 } from "fs/promises";
1804
+ import { createHash as createHash3 } from "crypto";
1805
+ import { homedir as homedir2 } from "os";
1806
+ import { join as join5 } from "path";
1807
+ import { execa as execa3 } from "execa";
1808
+ import { existsSync, mkdirSync, renameSync, statSync } from "fs";
1809
+ import { basename as basename2, dirname as dirname3, posix, resolve as resolve2 } from "path";
1810
+ import { execa as execa22 } from "execa";
1751
1811
  import { copyFile, mkdtemp, readdir as readdir22, readFile as readFile32, rm as rm3, stat as stat22, writeFile as writeFile3 } from "fs/promises";
1752
1812
  import { homedir as homedir22, tmpdir } from "os";
1753
- import { basename as basename2, join as join32, relative } from "path";
1813
+ import { basename as basename3, join as join32, relative } from "path";
1814
+ import { execa as execa5 } from "execa";
1815
+ import { chmod, mkdir as mkdir22, readFile as readFile23 } from "fs/promises";
1816
+ import { basename as basename22, join as join22 } from "path";
1754
1817
  import { execa as execa4 } from "execa";
1755
- import { chmod, mkdir as mkdir22, readFile as readFile24 } from "fs/promises";
1756
- import { basename as basename3, join as join22 } from "path";
1757
- import { execa as execa3 } from "execa";
1758
1818
  import { spawnSync as spawnSync2 } from "child_process";
1759
1819
  import { stat as stat42 } from "fs/promises";
1760
1820
  import { homedir as homedir4 } from "os";
1761
1821
  import { join as join52 } from "path";
1762
- import { execa as execa6 } from "execa";
1822
+ import { execa as execa7 } from "execa";
1763
1823
  import { spawnSync as spawnSync3 } from "child_process";
1764
1824
  import { stat as stat5 } from "fs/promises";
1765
1825
  import { homedir as homedir5 } from "os";
1766
1826
  import { join as join6 } from "path";
1767
- import { execa as execa7 } from "execa";
1768
- import { randomBytes as randomBytes3 } from "crypto";
1769
1827
  import { execa as execa8 } from "execa";
1770
- import { existsSync } from "fs";
1828
+ import { randomBytes as randomBytes3 } from "crypto";
1829
+ import { createHash as createHash22 } from "crypto";
1771
1830
  import { readFile as readFile52 } from "fs/promises";
1772
- import { homedir as homedir6 } from "os";
1773
1831
  import { join as join7 } from "path";
1774
1832
  import { execa as execa9 } from "execa";
1833
+ import { existsSync as existsSync2 } from "fs";
1834
+ import { readFile as readFile6 } from "fs/promises";
1835
+ import { homedir as homedir6 } from "os";
1836
+ import { join as join8 } from "path";
1837
+ import { execa as execa10 } from "execa";
1775
1838
 
1776
1839
  // ../../packages/core/dist/index.js
1777
1840
  import { randomBytes } from "crypto";
@@ -1805,6 +1868,12 @@ function resolveAgentLauncher(kind) {
1805
1868
  if (kind === "opencode") return opencodeLauncher;
1806
1869
  throw new Error(`unknown agent kind: ${String(kind)}`);
1807
1870
  }
1871
+ var UserFacingError = class extends Error {
1872
+ constructor(message) {
1873
+ super(message);
1874
+ this.name = "UserFacingError";
1875
+ }
1876
+ };
1808
1877
  var BoxNotFoundError = class extends Error {
1809
1878
  constructor(query) {
1810
1879
  super(`no agentbox matches "${query}"`);
@@ -1830,36 +1899,36 @@ function generateBoxId() {
1830
1899
  }
1831
1900
 
1832
1901
  // ../../packages/sandbox-docker/dist/index.js
1833
- import { execa as execa10 } from "execa";
1902
+ import { execa as execa11 } from "execa";
1834
1903
  import { mkdir as mkdir42, readdir as readdir42, rm as rm32, stat as stat6 } from "fs/promises";
1835
1904
  import { homedir as homedir7, platform } from "os";
1836
- import { join as join8, resolve as resolve2 } from "path";
1837
- import { mkdir as mkdir5, mkdtemp as mkdtemp3, readFile as readFile6, readdir as readdir5, rm as rm4, writeFile as writeFile32 } from "fs/promises";
1905
+ import { join as join9, resolve as resolve22 } from "path";
1906
+ import { mkdir as mkdir5, mkdtemp as mkdtemp3, readFile as readFile7, readdir as readdir5, rm as rm4, writeFile as writeFile32 } from "fs/promises";
1838
1907
  import { homedir as homedir8, tmpdir as tmpdir3 } from "os";
1839
- import { basename as basename32, join as join9 } from "path";
1840
- import { execa as execa11 } from "execa";
1841
- import { stat as stat7 } from "fs/promises";
1908
+ import { basename as basename4, join as join10 } from "path";
1842
1909
  import { execa as execa12 } from "execa";
1910
+ import { stat as stat7 } from "fs/promises";
1843
1911
  import { execa as execa13 } from "execa";
1912
+ import { execa as execa14 } from "execa";
1844
1913
  import { spawn } from "child_process";
1845
1914
  import { randomBytes as randomBytes22 } from "crypto";
1846
- import { existsSync as existsSync2, openSync } from "fs";
1847
- import { cp, mkdir as mkdir6, readFile as readFile7, readdir as readdir6, rename as rename3, rm as rm5, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
1915
+ import { existsSync as existsSync3, openSync } from "fs";
1916
+ 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";
1848
1917
  import { request as httpRequest } from "http";
1849
1918
  import { createRequire } from "module";
1850
1919
  import { homedir as homedir9 } from "os";
1851
- import { dirname as dirname3, join as join10, resolve as resolve22, sep } from "path";
1920
+ import { dirname as dirname22, join as join11, resolve as resolve3, sep } from "path";
1852
1921
  import { setTimeout as delay2 } from "timers/promises";
1853
1922
  import { fileURLToPath } from "url";
1854
1923
 
1855
1924
  // ../../packages/relay/dist/index.js
1856
1925
  import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
1857
1926
  import { execa } from "execa";
1858
- import { spawn as spawn4 } from "child_process";
1927
+ import { spawn as spawn3 } from "child_process";
1859
1928
  import {
1860
1929
  mkdir as mkdir2,
1861
1930
  readdir as readdir2,
1862
- readFile as readFile23,
1931
+ readFile as readFile4,
1863
1932
  rename as rename2,
1864
1933
  unlink,
1865
1934
  writeFile as writeFile2
@@ -1921,7 +1990,7 @@ async function loadQueueConfig() {
1921
1990
  const d = BUILT_IN_DEFAULTS.queue;
1922
1991
  let global = {};
1923
1992
  try {
1924
- global = parseUserConfig(await readFile23(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
1993
+ global = parseUserConfig(await readFile4(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
1925
1994
  } catch {
1926
1995
  }
1927
1996
  const q = global.queue ?? {};
@@ -1941,7 +2010,7 @@ async function writeJob(job) {
1941
2010
  }
1942
2011
  async function readJob(id) {
1943
2012
  try {
1944
- const raw = await readFile23(join3(QUEUE_DIR, `${id}.json`), "utf8");
2013
+ const raw = await readFile4(join3(QUEUE_DIR, `${id}.json`), "utf8");
1945
2014
  return JSON.parse(raw);
1946
2015
  } catch (err) {
1947
2016
  if (err.code === "ENOENT") return null;
@@ -1967,7 +2036,7 @@ async function loadQueue() {
1967
2036
  for (const name of entries) {
1968
2037
  if (!name.endsWith(".json")) continue;
1969
2038
  try {
1970
- const raw = await readFile23(join3(QUEUE_DIR, name), "utf8");
2039
+ const raw = await readFile4(join3(QUEUE_DIR, name), "utf8");
1971
2040
  out.push(JSON.parse(raw));
1972
2041
  } catch {
1973
2042
  }
@@ -1975,6 +2044,8 @@ async function loadQueue() {
1975
2044
  out.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0);
1976
2045
  return out;
1977
2046
  }
2047
+ var TERMINAL_RETENTION_MS = 60 * 60 * 1e3;
2048
+ var SWEEP_INTERVAL_MS = 60 * 1e3;
1978
2049
  function processAlive(pid) {
1979
2050
  try {
1980
2051
  process.kill(pid, 0);
@@ -2043,7 +2114,7 @@ async function uncachedBoxStateCount() {
2043
2114
  }
2044
2115
  function inspectDockerState(containerName) {
2045
2116
  return new Promise((resolveP) => {
2046
- const child = spawn4("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
2117
+ const child = spawn3("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
2047
2118
  stdio: ["ignore", "pipe", "pipe"]
2048
2119
  });
2049
2120
  let out = "";
@@ -2076,21 +2147,18 @@ function queueLogPath(id) {
2076
2147
  }
2077
2148
 
2078
2149
  // ../../packages/sandbox-docker/dist/index.js
2079
- import { execa as execa16 } from "execa";
2150
+ import { execa as execa17 } from "execa";
2080
2151
  import { readdir as readdir7, rm as rm6, stat as stat9 } from "fs/promises";
2152
+ import { join as join14 } from "path";
2153
+ import { execa as execa16 } from "execa";
2081
2154
  import { join as join13 } from "path";
2082
- import { execa as execa15 } from "execa";
2083
- import { join as join12 } from "path";
2084
2155
  import { homedir as homedir11 } from "os";
2085
- import { join as join14 } from "path";
2086
- import { execa as execa17 } from "execa";
2087
- import { existsSync as existsSync3, mkdirSync, renameSync, statSync } from "fs";
2088
- import { basename as basename5, dirname as dirname22, posix, resolve as resolve4 } from "path";
2156
+ import { join as join15 } from "path";
2089
2157
  import { execa as execa18 } from "execa";
2090
- import { createHash as createHash22 } from "crypto";
2091
- import { copyFile as copyFile2, mkdir as mkdir8, mkdtemp as mkdtemp4, readdir as readdir8, readFile as readFile8, rm as rm7, stat as stat10 } from "fs/promises";
2158
+ import { createHash as createHash32 } from "crypto";
2159
+ 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";
2092
2160
  import { homedir as homedir12, tmpdir as tmpdir4 } from "os";
2093
- import { basename as basename6, dirname as dirname32, join as join15 } from "path";
2161
+ import { basename as basename6, dirname as dirname32, join as join16 } from "path";
2094
2162
  import { execa as execa19 } from "execa";
2095
2163
  function isHostPathHookCommand(command, hostHome) {
2096
2164
  if (typeof command !== "string" || command.length === 0) return false;
@@ -2486,11 +2554,144 @@ async function listAgentboxVolumes() {
2486
2554
  if (result.exitCode !== 0) return [];
2487
2555
  return (result.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.startsWith(AGENTBOX_PREFIX));
2488
2556
  }
2557
+ function posixDirname(p) {
2558
+ return posix.dirname(p) || "/";
2559
+ }
2560
+ function asText(s) {
2561
+ if (s === void 0) return "";
2562
+ if (typeof s === "string") return s;
2563
+ return Buffer.from(s).toString("utf8");
2564
+ }
2565
+ function tarExcludeArgs(exclude) {
2566
+ return (exclude ?? []).map((p) => `--exclude=${p}`);
2567
+ }
2568
+ async function streamTarPipe(producerFile, producerArgs, consumerFile, consumerArgs, producerEnv) {
2569
+ const producer = execa22(producerFile, producerArgs, {
2570
+ reject: false,
2571
+ buffer: { stdout: false },
2572
+ ...producerEnv ? { env: producerEnv } : {}
2573
+ });
2574
+ const consumer = execa22(consumerFile, consumerArgs, {
2575
+ reject: false,
2576
+ buffer: { stdout: false }
2577
+ });
2578
+ producer.stdout?.pipe(consumer.stdin);
2579
+ const [p, c] = await Promise.all([producer, consumer]);
2580
+ return [
2581
+ { exitCode: p.exitCode, stderr: p.stderr },
2582
+ { exitCode: c.exitCode, stderr: c.stderr }
2583
+ ];
2584
+ }
2585
+ async function uploadToBox(box, hostSrc, boxDst, exclude) {
2586
+ const srcAbs = resolve2(hostSrc);
2587
+ if (!existsSync(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
2588
+ const srcBasename = basename2(srcAbs);
2589
+ const srcParent = dirname3(srcAbs);
2590
+ let boxParent;
2591
+ let finalName;
2592
+ if (boxDst.endsWith("/")) {
2593
+ boxParent = boxDst.replace(/\/+$/, "") || "/";
2594
+ finalName = srcBasename;
2595
+ } else {
2596
+ const isDir2 = await execa22(
2597
+ "docker",
2598
+ ["exec", box.container, "test", "-d", boxDst],
2599
+ { reject: false }
2600
+ );
2601
+ if (isDir2.exitCode === 0) {
2602
+ boxParent = boxDst.replace(/\/+$/, "") || "/";
2603
+ finalName = srcBasename;
2604
+ } else {
2605
+ boxParent = posixDirname(boxDst);
2606
+ finalName = posix.basename(boxDst);
2607
+ }
2608
+ }
2609
+ const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
2610
+ const mk = await execa22(
2611
+ "docker",
2612
+ ["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
2613
+ { reject: false }
2614
+ );
2615
+ if (mk.exitCode !== 0) {
2616
+ throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
2617
+ }
2618
+ const [packed, extracted] = await streamTarPipe(
2619
+ "tar",
2620
+ ["-C", srcParent, "-cf", "-", ...tarExcludeArgs(exclude), srcBasename],
2621
+ "docker",
2622
+ ["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
2623
+ { ...process.env, COPYFILE_DISABLE: "1" }
2624
+ );
2625
+ if (packed.exitCode !== 0) {
2626
+ throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
2627
+ }
2628
+ if (extracted.exitCode !== 0) {
2629
+ throw new Error(`tar extract in box failed: ${asText(extracted.stderr).slice(0, 300)}`);
2630
+ }
2631
+ if (finalName !== srcBasename) {
2632
+ const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
2633
+ const mv = await execa22(
2634
+ "docker",
2635
+ ["exec", "--user", "root", box.container, "mv", initial, finalPath],
2636
+ { reject: false }
2637
+ );
2638
+ if (mv.exitCode !== 0) {
2639
+ throw new Error(
2640
+ `rename ${initial} -> ${finalPath} in box failed: ${asText(mv.stderr).slice(0, 300)}`
2641
+ );
2642
+ }
2643
+ }
2644
+ const chown = await execa22(
2645
+ "docker",
2646
+ ["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
2647
+ { reject: false }
2648
+ );
2649
+ if (chown.exitCode !== 0) {
2650
+ return {
2651
+ finalPath,
2652
+ warn: `chown ${finalPath} to vscode (uid 1000) failed; ownership inside the box may be root.`
2653
+ };
2654
+ }
2655
+ return { finalPath };
2656
+ }
2657
+ async function downloadFromBox(box, boxSrc, hostDst, exclude) {
2658
+ const srcBasename = posix.basename(boxSrc);
2659
+ const srcParent = posixDirname(boxSrc);
2660
+ const dstAbs = resolve2(hostDst);
2661
+ let hostParent;
2662
+ let finalName;
2663
+ const dstExists = existsSync(dstAbs);
2664
+ if (hostDst.endsWith("/") || dstExists && statSync(dstAbs).isDirectory()) {
2665
+ hostParent = dstAbs;
2666
+ finalName = srcBasename;
2667
+ } else {
2668
+ hostParent = dirname3(dstAbs);
2669
+ finalName = basename2(dstAbs);
2670
+ }
2671
+ mkdirSync(hostParent, { recursive: true });
2672
+ const finalPath = posix.join(hostParent, finalName);
2673
+ const [packed, extracted] = await streamTarPipe(
2674
+ "docker",
2675
+ ["exec", box.container, "tar", "-C", srcParent, "-cf", "-", ...tarExcludeArgs(exclude), srcBasename],
2676
+ "tar",
2677
+ ["-xf", "-", "-C", hostParent]
2678
+ );
2679
+ if (packed.exitCode !== 0) {
2680
+ throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
2681
+ }
2682
+ if (extracted.exitCode !== 0) {
2683
+ throw new Error(`tar extract on host failed: ${asText(extracted.stderr).slice(0, 300)}`);
2684
+ }
2685
+ if (finalName !== srcBasename) {
2686
+ renameSync(posix.join(hostParent, srcBasename), finalPath);
2687
+ }
2688
+ return { finalPath };
2689
+ }
2489
2690
  var CONTAINER_EXPORT_MERGED = "/host-export";
2490
2691
  var cachedEngine = null;
2491
2692
  async function detectEngine() {
2492
2693
  if (cachedEngine !== null) return cachedEngine;
2493
- const result = await execa22("docker", ["info", "--format", "{{.OperatingSystem}}"], {
2694
+ const result = await execa3("docker", ["info", "--format", "{{.OperatingSystem}}"], {
2494
2695
  reject: false
2495
2696
  });
2496
2697
  const os = (result.stdout ?? "").trim().toLowerCase();
@@ -2503,7 +2704,7 @@ function setEngineOverride(engine) {
2503
2704
  cachedEngine = engine;
2504
2705
  }
2505
2706
  async function getDockerContext() {
2506
- const result = await execa22("docker", ["context", "show"], { reject: false });
2707
+ const result = await execa3("docker", ["context", "show"], { reject: false });
2507
2708
  if (result.exitCode !== 0) return void 0;
2508
2709
  const ctx = (result.stdout ?? "").trim();
2509
2710
  return ctx.length > 0 ? ctx : void 0;
@@ -2563,7 +2764,7 @@ async function refreshExport(record, opts = {}) {
2563
2764
  return { hostPath: paths.mergedExport, copied: true, usedFallback: false };
2564
2765
  }
2565
2766
  const excludes = excludeNodeModules ? ["--exclude=node_modules"] : [];
2566
- const result = await execa22(
2767
+ const result = await execa3(
2567
2768
  "docker",
2568
2769
  ["exec", "--user", "root", record.container, "tar", "-cf", "-", ...excludes, "-C", "/workspace", "."],
2569
2770
  { reject: false, encoding: "buffer" }
@@ -2575,7 +2776,7 @@ async function refreshExport(record, opts = {}) {
2575
2776
  typeof result.stderr === "string" ? result.stderr : result.stderr.toString("utf8")
2576
2777
  );
2577
2778
  }
2578
- const extract = await execa22("tar", ["-xf", "-", "-C", paths.mergedExport], {
2779
+ const extract = await execa3("tar", ["-xf", "-", "-C", paths.mergedExport], {
2579
2780
  input: result.stdout,
2580
2781
  reject: false
2581
2782
  });
@@ -2670,7 +2871,7 @@ function buildHostEnvFindArgs(patterns) {
2670
2871
  async function copyHostEnvFilesToBox(opts) {
2671
2872
  const log = opts.onLog ?? (() => {
2672
2873
  });
2673
- const found = await execa22("find", buildHostEnvFindArgs(opts.patterns).slice(1), {
2874
+ const found = await execa3("find", buildHostEnvFindArgs(opts.patterns).slice(1), {
2674
2875
  cwd: opts.workspaceDir,
2675
2876
  reject: false
2676
2877
  });
@@ -2680,7 +2881,7 @@ async function copyHostEnvFilesToBox(opts) {
2680
2881
  }
2681
2882
  const list = String(found.stdout).split("\0").filter((p) => p.length > 0);
2682
2883
  if (list.length === 0) return { copied: 0 };
2683
- const packed = await execa22("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
2884
+ const packed = await execa3("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
2684
2885
  input: list.join("\0"),
2685
2886
  encoding: "buffer",
2686
2887
  reject: false
@@ -2689,7 +2890,7 @@ async function copyHostEnvFilesToBox(opts) {
2689
2890
  log(`warning: env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
2690
2891
  return { copied: 0 };
2691
2892
  }
2692
- const extract = await execa22(
2893
+ const extract = await execa3(
2693
2894
  "docker",
2694
2895
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-xf", "-", "-C", "/workspace"],
2695
2896
  { input: packed.stdout, reject: false }
@@ -2702,7 +2903,7 @@ async function copyHostEnvFilesToBox(opts) {
2702
2903
  }
2703
2904
  async function scanHostEnvFiles(workspaceDir, patterns) {
2704
2905
  if (patterns.length === 0) return [];
2705
- const found = await execa22("find", buildHostEnvFindArgs(patterns).slice(1), {
2906
+ const found = await execa3("find", buildHostEnvFindArgs(patterns).slice(1), {
2706
2907
  cwd: workspaceDir,
2707
2908
  reject: false
2708
2909
  });
@@ -2714,7 +2915,7 @@ async function copyHostFilesToBox(opts) {
2714
2915
  });
2715
2916
  const list = opts.files.map((p) => p.replace(/^\.\//, "")).filter((p) => p.length > 0);
2716
2917
  if (list.length === 0) return { copied: 0 };
2717
- const packed = await execa22("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
2918
+ const packed = await execa3("tar", ["-C", opts.workspaceDir, "--null", "-T", "-", "-cf", "-"], {
2718
2919
  input: list.join("\0"),
2719
2920
  encoding: "buffer",
2720
2921
  reject: false
@@ -2723,7 +2924,7 @@ async function copyHostFilesToBox(opts) {
2723
2924
  log(`warning: env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
2724
2925
  return { copied: 0 };
2725
2926
  }
2726
- const extract = await execa22(
2927
+ const extract = await execa3(
2727
2928
  "docker",
2728
2929
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-xf", "-", "-C", "/workspace"],
2729
2930
  { input: packed.stdout, reject: false }
@@ -2797,7 +2998,7 @@ async function pullToHost(record, opts = {}) {
2797
2998
  }
2798
2999
  const src = `${scratchDir}/`;
2799
3000
  const dst = `${record.workspacePath}/`;
2800
- const dry = await execa22("rsync", [...baseArgs, "--dry-run", "-i", src, dst], {
3001
+ const dry = await execa3("rsync", [...baseArgs, "--dry-run", "-i", src, dst], {
2801
3002
  reject: false,
2802
3003
  input: fileList !== null ? fileList : void 0
2803
3004
  });
@@ -2808,7 +3009,7 @@ async function pullToHost(record, opts = {}) {
2808
3009
  if (opts.dryRun) {
2809
3010
  return { hostPath: record.workspacePath, changes, applied: false, usedGitignore };
2810
3011
  }
2811
- const real = await execa22("rsync", [...baseArgs, src, dst], {
3012
+ const real = await execa3("rsync", [...baseArgs, src, dst], {
2812
3013
  reject: false,
2813
3014
  input: fileList !== null ? fileList : void 0
2814
3015
  });
@@ -2833,7 +3034,7 @@ async function openInFinder(record, opts) {
2833
3034
  usedFallback = refreshed.usedFallback;
2834
3035
  }
2835
3036
  if (!opts.noOpen) {
2836
- const opened = await execa22(hostOpenCommand(), [hostPath], { reject: false });
3037
+ const opened = await execa3(hostOpenCommand(), [hostPath], { reject: false });
2837
3038
  if (opened.exitCode !== 0) {
2838
3039
  throw new ExportError(`open ${hostPath} failed`, opened.stdout, opened.stderr);
2839
3040
  }
@@ -2856,12 +3057,14 @@ async function carrySourceHash(entry) {
2856
3057
  if (entry.kind === "file") {
2857
3058
  return createHash3("sha256").update(await readFile5(entry.absSrc)).digest("hex");
2858
3059
  }
3060
+ const exclude = entry.exclude ?? [];
2859
3061
  const h = createHash3("sha256");
2860
3062
  const walk = async (dir, rel) => {
2861
3063
  const names = (await readdir4(dir)).sort();
2862
3064
  for (const name of names) {
2863
- const abs = join5(dir, name);
2864
3065
  const relPath = rel ? `${rel}/${name}` : name;
3066
+ if (carryRelExcluded(relPath, exclude)) continue;
3067
+ const abs = join5(dir, name);
2865
3068
  const st = await stat4(abs);
2866
3069
  if (st.isDirectory()) {
2867
3070
  h.update(`d\0${relPath}
@@ -2880,6 +3083,24 @@ async function carrySourceHash(entry) {
2880
3083
  return void 0;
2881
3084
  }
2882
3085
  }
3086
+ function carryRelExcluded(relPath, patterns) {
3087
+ if (patterns.length === 0) return false;
3088
+ const segs = relPath.split("/");
3089
+ for (const p of patterns) {
3090
+ const isGlob = p.includes("*") || p.includes("?");
3091
+ if (!isGlob) {
3092
+ if (segs.includes(p)) return true;
3093
+ } else if (p.startsWith("*/") && !/[*?/]/.test(p.slice(2))) {
3094
+ if (segs.includes(p.slice(2))) return true;
3095
+ } else {
3096
+ const re = new RegExp(
3097
+ `^${p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
3098
+ );
3099
+ if (re.test(relPath)) return true;
3100
+ }
3101
+ }
3102
+ return false;
3103
+ }
2883
3104
  async function copyCarryPathsToBox(opts) {
2884
3105
  const log = opts.onLog ?? (() => {
2885
3106
  });
@@ -2915,7 +3136,7 @@ async function copyOneEntry(container, entry) {
2915
3136
  const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
2916
3137
  const boxDestParent = boxDest.endsWith("/") ? boxDest.slice(0, -1) : boxDest;
2917
3138
  const parentDir = entry.kind === "dir" ? boxDestParent : dirnameUnix(boxDestParent);
2918
- const mkdir9 = await execa22(
3139
+ const mkdir9 = await execa3(
2919
3140
  "docker",
2920
3141
  ["exec", "--user", "0:0", container, "mkdir", "-p", parentDir],
2921
3142
  { reject: false }
@@ -2924,7 +3145,7 @@ async function copyOneEntry(container, entry) {
2924
3145
  throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir9.stderr).slice(0, 300)}`);
2925
3146
  }
2926
3147
  if (entry.kind === "file") {
2927
- const cp2 = await execa22(
3148
+ const cp2 = await execa3(
2928
3149
  "docker",
2929
3150
  ["cp", entry.absSrc, `${container}:${boxDest}`],
2930
3151
  { reject: false }
@@ -2933,15 +3154,10 @@ async function copyOneEntry(container, entry) {
2933
3154
  throw new Error(`docker cp failed: ${String(cp2.stderr).slice(0, 300)}`);
2934
3155
  }
2935
3156
  } else {
2936
- const packed = await execa22(
3157
+ const excludeArgs = (entry.exclude ?? []).map((p) => `--exclude=${p}`);
3158
+ const [packed, extract] = await streamTarPipe(
2937
3159
  "tar",
2938
- ["-C", entry.absSrc, "-cf", "-", "."],
2939
- { encoding: "buffer", reject: false }
2940
- );
2941
- if (packed.exitCode !== 0) {
2942
- throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
2943
- }
2944
- const extract = await execa22(
3160
+ ["-C", entry.absSrc, "-cf", "-", ...excludeArgs, "."],
2945
3161
  "docker",
2946
3162
  [
2947
3163
  "exec",
@@ -2957,16 +3173,18 @@ async function copyOneEntry(container, entry) {
2957
3173
  "--no-same-permissions",
2958
3174
  "--no-same-owner",
2959
3175
  "-m"
2960
- ],
2961
- { input: packed.stdout, reject: false }
3176
+ ]
2962
3177
  );
3178
+ if (packed.exitCode !== 0) {
3179
+ throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
3180
+ }
2963
3181
  if (extract.exitCode !== 0) {
2964
3182
  throw new Error(`tar extract failed: ${String(extract.stderr).slice(0, 300)}`);
2965
3183
  }
2966
3184
  }
2967
3185
  if (entry.mode !== void 0) {
2968
3186
  const modeStr = entry.mode.toString(8).padStart(4, "0");
2969
- const chmod2 = await execa22(
3187
+ const chmod2 = await execa3(
2970
3188
  "docker",
2971
3189
  ["exec", "--user", "0:0", container, "chmod", "-R", modeStr, boxDest],
2972
3190
  { reject: false }
@@ -2976,7 +3194,7 @@ async function copyOneEntry(container, entry) {
2976
3194
  }
2977
3195
  }
2978
3196
  const uid = entry.user ?? 1e3;
2979
- const chown = await execa22(
3197
+ const chown = await execa3(
2980
3198
  "docker",
2981
3199
  ["exec", "--user", "0:0", container, "chown", "-R", `${String(uid)}:${String(uid)}`, boxDest],
2982
3200
  { reject: false }
@@ -2987,7 +3205,7 @@ async function copyOneEntry(container, entry) {
2987
3205
  if (boxDest.startsWith(BOX_HOME + "/") && dirnameUnix(boxDest) !== BOX_HOME) {
2988
3206
  const safeDest = boxDest.replace(/'/g, `'\\''`);
2989
3207
  const script = `set -e; parent="$(dirname '${safeDest}')"; while [ "$parent" != "${BOX_HOME}" ] && [ "$parent" != "/" ]; do chown ${String(uid)}:${String(uid)} "$parent"; parent="$(dirname "$parent")"; done`;
2990
- const chownParents = await execa22(
3208
+ const chownParents = await execa3(
2991
3209
  "docker",
2992
3210
  ["exec", "--user", "0:0", container, "bash", "-c", script],
2993
3211
  { reject: false }
@@ -3021,7 +3239,7 @@ function isRealAgentCredential(agent, text) {
3021
3239
  }
3022
3240
  async function hostClaudeBackupExpired(path = CREDENTIALS_BACKUP_FILE, now = Date.now()) {
3023
3241
  try {
3024
- const parsed = JSON.parse(await readFile24(path, "utf8"));
3242
+ const parsed = JSON.parse(await readFile23(path, "utf8"));
3025
3243
  const exp = parsed?.claudeAiOauth?.expiresAt;
3026
3244
  return typeof exp === "number" && Number.isFinite(exp) && exp < now;
3027
3245
  } catch {
@@ -3035,7 +3253,7 @@ async function extractVolumeAuthToBackup(opts) {
3035
3253
  try {
3036
3254
  await mkdir22(STATE_DIR, { recursive: true });
3037
3255
  const script = 'COPIED=no; if [ -s /dst/auth.json ]; then cp -a /dst/auth.json "/host-state/$DEST" && COPIED=yes; fi; echo "COPIED=$COPIED"';
3038
- const { stdout } = await execa3("docker", [
3256
+ const { stdout } = await execa4("docker", [
3039
3257
  "run",
3040
3258
  "--rm",
3041
3259
  "--user",
@@ -3047,7 +3265,7 @@ async function extractVolumeAuthToBackup(opts) {
3047
3265
  "-e",
3048
3266
  // Pass the destination filename via env so the path isn't interpolated
3049
3267
  // into the script string (keeps the docker arg list static + injection-safe).
3050
- `DEST=${basename3(opts.backupFile)}`,
3268
+ `DEST=${basename22(opts.backupFile)}`,
3051
3269
  opts.image,
3052
3270
  "sh",
3053
3271
  "-c",
@@ -3069,7 +3287,7 @@ function extractOpencodeCredentials(volume, image) {
3069
3287
  }
3070
3288
  async function hostBackupHasCredentials(path = CREDENTIALS_BACKUP_FILE) {
3071
3289
  try {
3072
- const parsed = JSON.parse(await readFile24(path, "utf8"));
3290
+ const parsed = JSON.parse(await readFile23(path, "utf8"));
3073
3291
  const rt = parsed?.claudeAiOauth?.refreshToken;
3074
3292
  return typeof rt === "string" && rt.length > 0;
3075
3293
  } catch {
@@ -3099,7 +3317,7 @@ echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
3099
3317
  async function syncClaudeCredentials(spec, opts) {
3100
3318
  try {
3101
3319
  await mkdir22(STATE_DIR, { recursive: true });
3102
- const { stdout } = await execa3("docker", [
3320
+ const { stdout } = await execa4("docker", [
3103
3321
  "run",
3104
3322
  "--rm",
3105
3323
  "--user",
@@ -3167,8 +3385,8 @@ function emptyResult(warnings = []) {
3167
3385
  }, warnings };
3168
3386
  }
3169
3387
  async function tarballFromDir(stageDir, agent) {
3170
- const tarballPath = join32(tmpdir(), `agentbox-${agent}-${basename2(stageDir)}.tar.gz`);
3171
- await execa4("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
3388
+ const tarballPath = join32(tmpdir(), `agentbox-${agent}-${basename3(stageDir)}.tar.gz`);
3389
+ await execa5("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
3172
3390
  env: { ...process.env, COPYFILE_DISABLE: "1" }
3173
3391
  });
3174
3392
  return tarballPath;
@@ -3241,6 +3459,64 @@ async function resolveClaudeMemoryDir(hostWorkspace, hostHome = homedir22()) {
3241
3459
  }
3242
3460
  return memDir;
3243
3461
  }
3462
+ async function buildBoxClaudeJsonFromHost(opts) {
3463
+ const { hostHome, hostWorkspace } = opts;
3464
+ const hostClaudeJson = join32(hostHome, ".claude.json");
3465
+ let working;
3466
+ if (await pathExists(hostClaudeJson)) {
3467
+ try {
3468
+ working = JSON.parse(await readFile32(hostClaudeJson, "utf8"));
3469
+ } catch {
3470
+ working = null;
3471
+ }
3472
+ }
3473
+ if (working === void 0 || working === null) {
3474
+ working = {
3475
+ installMethod: "native",
3476
+ autoUpdates: false,
3477
+ autoUpdatesProtectedForNative: true,
3478
+ // Pre-accept onboarding so the in-box Claude doesn't show the theme
3479
+ // picker on first run. AgentBox installing implies the user has
3480
+ // already used Claude Code on the host.
3481
+ hasCompletedOnboarding: true,
3482
+ projects: { [CLOUD_WORKSPACE]: { hasTrustDialogAccepted: true } }
3483
+ };
3484
+ } else {
3485
+ working = filterHostHooks(working, hostHome).data;
3486
+ working = setInstallMethodNative(working).data;
3487
+ if (hostWorkspace) {
3488
+ working = addProjectAlias(working, hostWorkspace, CLOUD_WORKSPACE).data;
3489
+ }
3490
+ working = trustWorkspace(working, CLOUD_WORKSPACE).data;
3491
+ if (typeof working === "object" && working !== null) {
3492
+ const w = working;
3493
+ if (w["hasCompletedOnboarding"] !== true) w["hasCompletedOnboarding"] = true;
3494
+ }
3495
+ }
3496
+ return working;
3497
+ }
3498
+ async function stageClaudeJsonOnlyForUpload(opts = {}) {
3499
+ const hostHome = opts.hostHome ?? homedir22();
3500
+ const stageDir = await mkStageDir("claude-json-only");
3501
+ let tarballPath = null;
3502
+ try {
3503
+ const claudeJson = await buildBoxClaudeJsonFromHost({
3504
+ hostHome,
3505
+ hostWorkspace: opts.hostWorkspace
3506
+ });
3507
+ await writeFile3(join32(stageDir, "_claude.json"), JSON.stringify(claudeJson, null, 2));
3508
+ tarballPath = await tarballFromDir(stageDir, "claude-json-only");
3509
+ return {
3510
+ tarballPath,
3511
+ cleanup: makeCleanup([stageDir, tarballPath]),
3512
+ warnings: []
3513
+ };
3514
+ } catch (err) {
3515
+ await rm3(stageDir, { recursive: true, force: true });
3516
+ if (tarballPath) await rm3(tarballPath, { force: true });
3517
+ throw err;
3518
+ }
3519
+ }
3244
3520
  async function stageClaudeStaticForUpload(opts = {}) {
3245
3521
  const hostHome = opts.hostHome ?? homedir22();
3246
3522
  const hostClaude = join32(hostHome, ".claude");
@@ -3255,7 +3531,7 @@ async function stageClaudeStaticForUpload(opts = {}) {
3255
3531
  ...CLAUDE_RUNTIME_EXCLUDES.map((p) => `--exclude=${p}`),
3256
3532
  ...broken.map((r) => `--exclude=/${r}`)
3257
3533
  ];
3258
- await execa4("rsync", [
3534
+ await execa5("rsync", [
3259
3535
  "-a",
3260
3536
  "--copy-unsafe-links",
3261
3537
  ...excludes,
@@ -3273,31 +3549,11 @@ async function stageClaudeStaticForUpload(opts = {}) {
3273
3549
  } catch {
3274
3550
  }
3275
3551
  }
3276
- const hostClaudeJson = join32(hostHome, ".claude.json");
3277
- let working;
3278
- if (await pathExists(hostClaudeJson)) {
3279
- try {
3280
- working = JSON.parse(await readFile32(hostClaudeJson, "utf8"));
3281
- } catch {
3282
- working = null;
3283
- }
3284
- }
3285
- if (working === void 0 || working === null) {
3286
- working = {
3287
- installMethod: "native",
3288
- autoUpdates: false,
3289
- autoUpdatesProtectedForNative: true,
3290
- projects: { [CLOUD_WORKSPACE]: { hasTrustDialogAccepted: true } }
3291
- };
3292
- } else {
3293
- working = filterHostHooks(working, hostHome).data;
3294
- working = setInstallMethodNative(working).data;
3295
- if (opts.hostWorkspace) {
3296
- working = addProjectAlias(working, opts.hostWorkspace, CLOUD_WORKSPACE).data;
3297
- }
3298
- working = trustWorkspace(working, CLOUD_WORKSPACE).data;
3299
- }
3300
- await writeFile3(join32(stageDir, "_claude.json"), JSON.stringify(working, null, 2));
3552
+ const claudeJson = await buildBoxClaudeJsonFromHost({
3553
+ hostHome,
3554
+ hostWorkspace: opts.hostWorkspace
3555
+ });
3556
+ await writeFile3(join32(stageDir, "_claude.json"), JSON.stringify(claudeJson, null, 2));
3301
3557
  const pluginsDir = join32(stageDir, "plugins");
3302
3558
  if (await pathExists(pluginsDir)) {
3303
3559
  try {
@@ -3364,7 +3620,7 @@ async function stageCodexStaticForUpload(opts = {}) {
3364
3620
  let tarballPath = null;
3365
3621
  try {
3366
3622
  const codexBroken = await findBrokenSymlinks(hostCodex);
3367
- await execa4("rsync", [
3623
+ await execa5("rsync", [
3368
3624
  "-a",
3369
3625
  "-L",
3370
3626
  ...codexBroken.map((r) => `--exclude=/${r}`),
@@ -3420,7 +3676,7 @@ async function stageOpencodeStaticForUpload(opts = {}) {
3420
3676
  try {
3421
3677
  if (hasData) {
3422
3678
  const dataBroken = await findBrokenSymlinks(hostData);
3423
- await execa4("rsync", [
3679
+ await execa5("rsync", [
3424
3680
  "-a",
3425
3681
  "-L",
3426
3682
  ...dataBroken.map((r) => `--exclude=/${r}`),
@@ -3433,7 +3689,7 @@ async function stageOpencodeStaticForUpload(opts = {}) {
3433
3689
  if (hasConfig) {
3434
3690
  const configStage = join32(stageDir, "config");
3435
3691
  const cfgBroken = await findBrokenSymlinks(hostConfig);
3436
- await execa4("rsync", [
3692
+ await execa5("rsync", [
3437
3693
  "-a",
3438
3694
  "-L",
3439
3695
  ...cfgBroken.map((r) => `--exclude=/${r}`),
@@ -3491,7 +3747,7 @@ async function pathExists2(p) {
3491
3747
  }
3492
3748
  }
3493
3749
  async function volumeHasClaudeJson(volume, image) {
3494
- const res = await execa5(
3750
+ const res = await execa6(
3495
3751
  "docker",
3496
3752
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/_claude.json"],
3497
3753
  { reject: false }
@@ -3661,7 +3917,7 @@ async function ensureClaudeVolume(spec, opts) {
3661
3917
  // linux/amd64 node_modules on the next `agentbox claude`.
3662
3918
  `{ [ ! -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"
3663
3919
  );
3664
- await execa5("docker", args);
3920
+ await execa6("docker", args);
3665
3921
  } finally {
3666
3922
  await rm2(filterDir, { recursive: true, force: true });
3667
3923
  }
@@ -3676,7 +3932,7 @@ async function ensureClaudeVolume(spec, opts) {
3676
3932
  }
3677
3933
  async function seedSetupSkillIntoVolume(volume, image) {
3678
3934
  try {
3679
- const { stdout } = await execa5("docker", [
3935
+ const { stdout } = await execa6("docker", [
3680
3936
  "run",
3681
3937
  "--rm",
3682
3938
  "--user",
@@ -3705,7 +3961,7 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
3705
3961
  };
3706
3962
  let parsed;
3707
3963
  try {
3708
- parsed = JSON.parse(await readFile4(src, "utf8"));
3964
+ parsed = JSON.parse(await readFile42(src, "utf8"));
3709
3965
  } catch {
3710
3966
  return zero;
3711
3967
  }
@@ -3788,7 +4044,7 @@ async function isDir(p) {
3788
4044
  }
3789
4045
  async function readReferencedPluginKeys(installedPluginsJsonPath) {
3790
4046
  try {
3791
- const raw = await readFile4(installedPluginsJsonPath, "utf8");
4047
+ const raw = await readFile42(installedPluginsJsonPath, "utf8");
3792
4048
  return referencedPluginVersionKeys(JSON.parse(raw));
3793
4049
  } catch {
3794
4050
  return /* @__PURE__ */ new Set();
@@ -3841,7 +4097,7 @@ async function resolveClaudeCacheLiveOnHost(volume) {
3841
4097
  return orbstackVolumePath(volume, "plugins", "cache");
3842
4098
  }
3843
4099
  async function readBoxReferencedPluginKeys(container) {
3844
- const res = await execa5(
4100
+ const res = await execa6(
3845
4101
  "docker",
3846
4102
  [
3847
4103
  "exec",
@@ -3959,7 +4215,7 @@ while IFS= read -r dir; do
3959
4215
  done < "$WORK/dirs"
3960
4216
  rm -rf "$WORK"
3961
4217
  `;
3962
- const result = await execa5(
4218
+ const result = await execa6(
3963
4219
  "docker",
3964
4220
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", script],
3965
4221
  { reject: false }
@@ -4020,7 +4276,7 @@ async function startClaudeSession(opts) {
4020
4276
  const v = process.env[k];
4021
4277
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4022
4278
  }
4023
- const result = await execa5(
4279
+ const result = await execa6(
4024
4280
  "docker",
4025
4281
  [
4026
4282
  "exec",
@@ -4111,7 +4367,7 @@ function buildDashboardAttachArgv(container, sessionName) {
4111
4367
  async function waitForTmuxPaneContent(container, sessionName, timeoutMs = 2e4) {
4112
4368
  const deadline = Date.now() + timeoutMs;
4113
4369
  while (Date.now() < deadline) {
4114
- const res = await execa5(
4370
+ const res = await execa6(
4115
4371
  "docker",
4116
4372
  ["exec", "--user", CONTAINER_USER, container, "tmux", "capture-pane", "-p", "-t", sessionName],
4117
4373
  { reject: false }
@@ -4179,7 +4435,7 @@ async function warmUpClaudeCredentials(volume, image, opts = {}) {
4179
4435
  const SLEEP_MS = 5e3;
4180
4436
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
4181
4437
  opts.onProgress?.(`checking credentials... ${attempt}/${MAX_ATTEMPTS}`);
4182
- const res = await execa5(
4438
+ const res = await execa6(
4183
4439
  "docker",
4184
4440
  [
4185
4441
  "run",
@@ -4221,7 +4477,7 @@ function attachClaudeSession(container, sessionName, reattachRef) {
4221
4477
  }
4222
4478
  async function claudeSessionInfo(container, sessionName) {
4223
4479
  const name = sessionName ?? DEFAULT_CLAUDE_SESSION;
4224
- const has = await execa5(
4480
+ const has = await execa6(
4225
4481
  "docker",
4226
4482
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4227
4483
  { reject: false }
@@ -4229,7 +4485,7 @@ async function claudeSessionInfo(container, sessionName) {
4229
4485
  if (has.exitCode !== 0) {
4230
4486
  return { running: false, sessionName: name, startedAt: null };
4231
4487
  }
4232
- const ts = await execa5(
4488
+ const ts = await execa6(
4233
4489
  "docker",
4234
4490
  [
4235
4491
  "exec",
@@ -4263,7 +4519,7 @@ async function listChildDirs(dir) {
4263
4519
  }
4264
4520
  async function readJsonFile(path) {
4265
4521
  try {
4266
- return JSON.parse(await readFile4(path, "utf8"));
4522
+ return JSON.parse(await readFile42(path, "utf8"));
4267
4523
  } catch {
4268
4524
  return void 0;
4269
4525
  }
@@ -4295,7 +4551,7 @@ async function pullClaudeExtras(spec, opts) {
4295
4551
  ' printf "\\n";',
4296
4552
  "done"
4297
4553
  ].join(" ");
4298
- const inv = await execa5(
4554
+ const inv = await execa6(
4299
4555
  "docker",
4300
4556
  ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/src:ro`, opts.image, "sh", "-c", inventoryScript],
4301
4557
  { reject: false }
@@ -4368,7 +4624,7 @@ async function pullClaudeExtras(spec, opts) {
4368
4624
  const parent = dest.slice(0, dest.lastIndexOf("/"));
4369
4625
  return `mkdir -p '${parent}' && rsync -a --ignore-existing --exclude=node_modules '${src}/' '${dest}/'`;
4370
4626
  });
4371
- const apply = await execa5(
4627
+ const apply = await execa6(
4372
4628
  "docker",
4373
4629
  [
4374
4630
  "run",
@@ -4441,7 +4697,7 @@ async function ensureCodexVolume(spec, opts) {
4441
4697
  const hostCodex = join52(homedir4(), ".codex");
4442
4698
  const willSync = opts.syncFromHost && await pathExists3(hostCodex);
4443
4699
  if (willSync) {
4444
- await execa6("docker", [
4700
+ await execa7("docker", [
4445
4701
  "run",
4446
4702
  "--rm",
4447
4703
  "--user",
@@ -4459,7 +4715,7 @@ async function ensureCodexVolume(spec, opts) {
4459
4715
  ]);
4460
4716
  return { created, synced: true };
4461
4717
  }
4462
- await execa6(
4718
+ await execa7(
4463
4719
  "docker",
4464
4720
  [
4465
4721
  "run",
@@ -4479,7 +4735,7 @@ async function ensureCodexVolume(spec, opts) {
4479
4735
  }
4480
4736
  async function seedCodexHooks(volume, image) {
4481
4737
  try {
4482
- const { stdout } = await execa6("docker", [
4738
+ const { stdout } = await execa7("docker", [
4483
4739
  "run",
4484
4740
  "--rm",
4485
4741
  "--user",
@@ -4516,14 +4772,14 @@ var CodexSessionError = class extends Error {
4516
4772
  }
4517
4773
  };
4518
4774
  async function ensureCodexInstalled(container, opts = {}) {
4519
- const probe = await execa6(
4775
+ const probe = await execa7(
4520
4776
  "docker",
4521
4777
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v codex"],
4522
4778
  { reject: false }
4523
4779
  );
4524
4780
  if (probe.exitCode === 0) return { installed: false };
4525
4781
  opts.onProgress?.("installing codex (absent from this box image)");
4526
- const install = await execa6(
4782
+ const install = await execa7(
4527
4783
  "docker",
4528
4784
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g @openai/codex 2>&1"],
4529
4785
  { reject: false }
@@ -4546,7 +4802,7 @@ async function startCodexSession(opts) {
4546
4802
  const v = process.env[k];
4547
4803
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4548
4804
  }
4549
- const result = await execa6(
4805
+ const result = await execa7(
4550
4806
  "docker",
4551
4807
  [
4552
4808
  "exec",
@@ -4628,7 +4884,7 @@ function runInteractiveCodexLogin(dockerArgv) {
4628
4884
  return { exitCode: child.status ?? 1 };
4629
4885
  }
4630
4886
  async function volumeHasCodexAuth(volume, image) {
4631
- const res = await execa6(
4887
+ const res = await execa7(
4632
4888
  "docker",
4633
4889
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
4634
4890
  { reject: false }
@@ -4637,7 +4893,7 @@ async function volumeHasCodexAuth(volume, image) {
4637
4893
  }
4638
4894
  async function codexSessionInfo(container, sessionName) {
4639
4895
  const name = sessionName ?? DEFAULT_CODEX_SESSION;
4640
- const has = await execa6(
4896
+ const has = await execa7(
4641
4897
  "docker",
4642
4898
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4643
4899
  { reject: false }
@@ -4645,7 +4901,7 @@ async function codexSessionInfo(container, sessionName) {
4645
4901
  if (has.exitCode !== 0) {
4646
4902
  return { running: false, sessionName: name, startedAt: null };
4647
4903
  }
4648
- const ts = await execa6(
4904
+ const ts = await execa7(
4649
4905
  "docker",
4650
4906
  [
4651
4907
  "exec",
@@ -4671,7 +4927,7 @@ async function codexSessionInfo(container, sessionName) {
4671
4927
  var CODEX_PULL_ITEMS = ["config.toml", "auth.json", "prompts"];
4672
4928
  async function pullCodexConfig(spec, opts) {
4673
4929
  const hostCodex = join52(homedir4(), ".codex");
4674
- const inv = await execa6(
4930
+ const inv = await execa7(
4675
4931
  "docker",
4676
4932
  [
4677
4933
  "run",
@@ -4705,7 +4961,7 @@ async function pullCodexConfig(spec, opts) {
4705
4961
  const uid = process.getuid?.() ?? 0;
4706
4962
  const gid = process.getgid?.() ?? 0;
4707
4963
  const cmds = newItems.map((it) => `cp -a '/src/${it}' '/dst/${it}'`);
4708
- const apply = await execa6(
4964
+ const apply = await execa7(
4709
4965
  "docker",
4710
4966
  [
4711
4967
  "run",
@@ -4787,10 +5043,10 @@ async function ensureOpencodeVolume(spec, opts) {
4787
5043
  }
4788
5044
  steps.push("chown -R 1000:1000 /dst");
4789
5045
  args.push(opts.image, "sh", "-c", steps.join(" && "));
4790
- await execa7("docker", args);
5046
+ await execa8("docker", args);
4791
5047
  return { created, synced: true };
4792
5048
  }
4793
- await execa7(
5049
+ await execa8(
4794
5050
  "docker",
4795
5051
  [
4796
5052
  "run",
@@ -4810,7 +5066,7 @@ async function ensureOpencodeVolume(spec, opts) {
4810
5066
  }
4811
5067
  async function seedOpencodePlugin(volume, image) {
4812
5068
  try {
4813
- const { stdout } = await execa7("docker", [
5069
+ const { stdout } = await execa8("docker", [
4814
5070
  "run",
4815
5071
  "--rm",
4816
5072
  "--user",
@@ -4858,14 +5114,14 @@ var OpencodeSessionError = class extends Error {
4858
5114
  }
4859
5115
  };
4860
5116
  async function ensureOpencodeInstalled(container, opts = {}) {
4861
- const probe = await execa7(
5117
+ const probe = await execa8(
4862
5118
  "docker",
4863
5119
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v opencode"],
4864
5120
  { reject: false }
4865
5121
  );
4866
5122
  if (probe.exitCode === 0) return { installed: false };
4867
5123
  opts.onProgress?.("installing opencode (absent from this box image)");
4868
- const install = await execa7(
5124
+ const install = await execa8(
4869
5125
  "docker",
4870
5126
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g opencode-ai 2>&1"],
4871
5127
  { reject: false }
@@ -4887,7 +5143,7 @@ async function startOpencodeSession(opts) {
4887
5143
  const v = process.env[k];
4888
5144
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4889
5145
  }
4890
- const result = await execa7(
5146
+ const result = await execa8(
4891
5147
  "docker",
4892
5148
  [
4893
5149
  "exec",
@@ -4973,7 +5229,7 @@ function runInteractiveOpencodeLogin(dockerArgv) {
4973
5229
  return { exitCode: child.status ?? 1 };
4974
5230
  }
4975
5231
  async function volumeHasOpencodeAuth(volume, image) {
4976
- const res = await execa7(
5232
+ const res = await execa8(
4977
5233
  "docker",
4978
5234
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
4979
5235
  { reject: false }
@@ -4982,7 +5238,7 @@ async function volumeHasOpencodeAuth(volume, image) {
4982
5238
  }
4983
5239
  async function opencodeSessionInfo(container, sessionName) {
4984
5240
  const name = sessionName ?? DEFAULT_OPENCODE_SESSION;
4985
- const has = await execa7(
5241
+ const has = await execa8(
4986
5242
  "docker",
4987
5243
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4988
5244
  { reject: false }
@@ -4990,7 +5246,7 @@ async function opencodeSessionInfo(container, sessionName) {
4990
5246
  if (has.exitCode !== 0) {
4991
5247
  return { running: false, sessionName: name, startedAt: null };
4992
5248
  }
4993
- const ts = await execa7(
5249
+ const ts = await execa8(
4994
5250
  "docker",
4995
5251
  [
4996
5252
  "exec",
@@ -5028,7 +5284,7 @@ var OPENCODE_PULL_CONFIG_ITEMS = [
5028
5284
  async function pullOpencodeConfig(spec, opts) {
5029
5285
  const hostData = join6(homedir5(), ".local", "share", "opencode");
5030
5286
  const hostConfig = join6(homedir5(), ".config", "opencode");
5031
- const inv = await execa7(
5287
+ const inv = await execa8(
5032
5288
  "docker",
5033
5289
  [
5034
5290
  "run",
@@ -5070,7 +5326,7 @@ async function pullOpencodeConfig(spec, opts) {
5070
5326
  const cmds = newItems.map(
5071
5327
  (i) => `cp -a '${i.src}' '${i.hostDst === "data" ? "/dst-data" : "/dst-config"}/${i.name}'`
5072
5328
  );
5073
- const apply = await execa7(
5329
+ const apply = await execa8(
5074
5330
  "docker",
5075
5331
  [
5076
5332
  "run",
@@ -5181,9 +5437,9 @@ function gitWorktreePathFor(branch) {
5181
5437
  return `${WORKTREE_ROOT}/${fsSafeBranch(branch)}`;
5182
5438
  }
5183
5439
  async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath) {
5184
- const stash = await execa8("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
5440
+ const stash = await execa9("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
5185
5441
  const stashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
5186
- const untracked = await execa8(
5442
+ const untracked = await execa9(
5187
5443
  "git",
5188
5444
  ["-C", repo.hostMainRepo, "ls-files", "--others", "--exclude-standard", "-z"],
5189
5445
  { reject: false }
@@ -5200,7 +5456,7 @@ async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath
5200
5456
  };
5201
5457
  }
5202
5458
  async function dexec(container, argv, user = "vscode", cwd = "/") {
5203
- const r = await execa8(
5459
+ const r = await execa9(
5204
5460
  "docker",
5205
5461
  ["exec", "-w", cwd, "--user", user, container, ...argv],
5206
5462
  { reject: false }
@@ -5232,7 +5488,7 @@ async function bindWorktrees(container, binds, onLog) {
5232
5488
  (a, b) => a.kind === "root" && b.kind !== "root" ? -1 : a.kind !== "root" && b.kind === "root" ? 1 : 0
5233
5489
  );
5234
5490
  for (const b of ordered) {
5235
- await execa8(
5491
+ await execa9(
5236
5492
  "docker",
5237
5493
  ["exec", "-w", "/", "--user", "root", container, "sh", "-c", `mountpoint -q ${b.containerPath} && umount ${b.containerPath} || true`],
5238
5494
  { reject: false }
@@ -5254,7 +5510,7 @@ async function seedWorkspace(opts) {
5254
5510
  const wt = r.gitWorktreePath;
5255
5511
  const baseRef = r.repo.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
5256
5512
  const addArgs = r.reuseBranch ? ["worktree", "add", wt, r.branch] : ["worktree", "add", "-b", r.branch, wt, baseRef];
5257
- const add = await execa8(
5513
+ const add = await execa9(
5258
5514
  "docker",
5259
5515
  ["exec", "--user", "vscode", opts.container, "git", "-C", main, ...addArgs],
5260
5516
  { reject: false }
@@ -5265,7 +5521,7 @@ async function seedWorkspace(opts) {
5265
5521
  );
5266
5522
  }
5267
5523
  log(`worktree ${wt} on branch ${r.branch} (host main ${main})`);
5268
- await execa8(
5524
+ await execa9(
5269
5525
  "docker",
5270
5526
  [
5271
5527
  "exec",
@@ -5281,7 +5537,7 @@ async function seedWorkspace(opts) {
5281
5537
  ],
5282
5538
  { reject: false }
5283
5539
  );
5284
- await execa8(
5540
+ await execa9(
5285
5541
  "docker",
5286
5542
  [
5287
5543
  "exec",
@@ -5311,7 +5567,7 @@ async function seedWorkspace(opts) {
5311
5567
  for (const r of opts.repos) {
5312
5568
  const ct = r.containerPath;
5313
5569
  if (r.stashSha) {
5314
- const withIndex = await execa8(
5570
+ const withIndex = await execa9(
5315
5571
  "docker",
5316
5572
  [
5317
5573
  "exec",
@@ -5329,7 +5585,7 @@ async function seedWorkspace(opts) {
5329
5585
  { reject: false }
5330
5586
  );
5331
5587
  if (withIndex.exitCode !== 0) {
5332
- const noIndex = await execa8(
5588
+ const noIndex = await execa9(
5333
5589
  "docker",
5334
5590
  [
5335
5591
  "exec",
@@ -5357,7 +5613,7 @@ async function seedWorkspace(opts) {
5357
5613
  }
5358
5614
  }
5359
5615
  if (r.untrackedNul.length > 0) {
5360
- const tarOut = await execa8("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
5616
+ const tarOut = await execa9("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
5361
5617
  input: r.untrackedNul.replace(/\0$/, ""),
5362
5618
  encoding: "buffer",
5363
5619
  reject: false
@@ -5366,7 +5622,7 @@ async function seedWorkspace(opts) {
5366
5622
  log(`warning: tar of untracked files for ${r.repo.hostMainRepo} failed: ${tarOut.stderr}`);
5367
5623
  continue;
5368
5624
  }
5369
- const tarIn = await execa8(
5625
+ const tarIn = await execa9(
5370
5626
  "docker",
5371
5627
  ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
5372
5628
  { input: tarOut.stdout, reject: false }
@@ -5380,17 +5636,86 @@ async function seedWorkspace(opts) {
5380
5636
  }
5381
5637
  }
5382
5638
  }
5639
+ async function regenerateRestoredWorktrees(opts) {
5640
+ const log = opts.onLog ?? (() => {
5641
+ });
5642
+ const script = [
5643
+ "set -e",
5644
+ 'OLD="$1"; NEW="$2"; MAIN="$3"; BR="$4"; BASE="$5"; META="$6"',
5645
+ // Rename baked content to the fresh unique path (rename, not copy).
5646
+ '[ "$OLD" = "$NEW" ] || mv "$OLD" "$NEW"',
5647
+ // Fresh branch at the base ref; -f is safe because pickFreshBranch already
5648
+ // guaranteed BR is unused on the host.
5649
+ 'git -C "$MAIN" branch -f "$BR" "$BASE"',
5650
+ // Author worktree metadata under the bind-mounted host .git.
5651
+ 'mkdir -p "$META"',
5652
+ 'printf "../..\\n" > "$META/commondir"',
5653
+ 'printf "%s\\n" "$NEW/.git" > "$META/gitdir"',
5654
+ 'printf "ref: refs/heads/%s\\n" "$BR" > "$META/HEAD"',
5655
+ // Point the worktree's gitfile at the fresh metadata dir.
5656
+ 'printf "gitdir: %s\\n" "$META" > "$NEW/.git"',
5657
+ // Reset tracked files to the fork base so the box starts CLEAN at the host
5658
+ // base ref — same git state as a fresh create. The baked tree was captured
5659
+ // on the source box's (possibly divergent/older) branch, so its tracked
5660
+ // deviations are staleness, not work; resetting drops that noise. The
5661
+ // gitignored warm artifacts (node_modules, .next, build caches) are
5662
+ // untouched by reset --hard — that warm state is the checkpoint's value.
5663
+ 'git -C "$NEW" reset -q --hard HEAD',
5664
+ // Boxes carry no signing key; mirror seedWorkspace's per-worktree config so
5665
+ // host commit.gpgsign=true doesn't break in-box commits. extensions.
5666
+ // worktreeConfig writes the SHARED (bind-mounted) .git/config, so concurrent
5667
+ // boxes race on .git/config.lock — retry through the contention rather than
5668
+ // aborting (best-effort, like seedWorkspace). The per-worktree gpgsign write
5669
+ // needs the extension enabled first, so it follows the retry.
5670
+ "set +e",
5671
+ 'for i in 1 2 3 4 5 6 7 8; do git -C "$MAIN" config extensions.worktreeConfig true && break; sleep 0.25; done',
5672
+ 'git -C "$NEW" config --worktree commit.gpgsign false',
5673
+ "exit 0"
5674
+ ].join("\n");
5675
+ for (const p of opts.plans) {
5676
+ const baseRef = p.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
5677
+ const metaDir = `${p.hostMainRepo}/.git/worktrees/${fsSafeBranch(p.freshBranch)}`;
5678
+ const r = await execa9(
5679
+ "docker",
5680
+ [
5681
+ "exec",
5682
+ "--user",
5683
+ "vscode",
5684
+ opts.container,
5685
+ "bash",
5686
+ "-c",
5687
+ script,
5688
+ "bash",
5689
+ p.bakedGitWorktreePath,
5690
+ p.freshGitWorktreePath,
5691
+ p.hostMainRepo,
5692
+ p.freshBranch,
5693
+ baseRef,
5694
+ metaDir
5695
+ ],
5696
+ { reject: false }
5697
+ );
5698
+ if (r.exitCode !== 0) {
5699
+ throw new GitWorktreeError(
5700
+ `regenerate worktree for ${p.freshBranch} failed: ${r.stderr || r.stdout}`
5701
+ );
5702
+ }
5703
+ log(
5704
+ `restored worktree ${p.freshGitWorktreePath} on fresh branch ${p.freshBranch} (base ${baseRef})`
5705
+ );
5706
+ }
5707
+ }
5383
5708
  async function seedWorkspaceFromDir(opts) {
5384
5709
  const log = opts.onLog ?? (() => {
5385
5710
  });
5386
- const tarOut = await execa8("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
5711
+ const tarOut = await execa9("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
5387
5712
  encoding: "buffer",
5388
5713
  reject: false
5389
5714
  });
5390
5715
  if (tarOut.exitCode !== 0) {
5391
5716
  throw new GitWorktreeError(`tar of ${opts.hostSource} failed: ${tarOut.stderr}`);
5392
5717
  }
5393
- const tarIn = await execa8(
5718
+ const tarIn = await execa9(
5394
5719
  "docker",
5395
5720
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-C", "/workspace", "-xf", "-"],
5396
5721
  { input: tarOut.stdout, reject: false }
@@ -5401,13 +5726,13 @@ async function seedWorkspaceFromDir(opts) {
5401
5726
  log(`seeded /workspace from ${opts.hostSource}`);
5402
5727
  }
5403
5728
  async function removeInBoxWorktree(args) {
5404
- const remove = await execa8(
5729
+ const remove = await execa9(
5405
5730
  "git",
5406
5731
  ["-C", args.hostMainRepo, "worktree", "remove", "--force", args.gitWorktreePath],
5407
5732
  { reject: false }
5408
5733
  );
5409
5734
  if (remove.exitCode === 0) return;
5410
- await execa8("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
5735
+ await execa9("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
5411
5736
  }
5412
5737
  function ctParent(p) {
5413
5738
  const i = p.lastIndexOf("/");
@@ -5419,6 +5744,12 @@ async function gitIn(container, ct, args) {
5419
5744
  function splitNul(s) {
5420
5745
  return s.split("\0").filter((p) => p.length > 0);
5421
5746
  }
5747
+ var NON_REGULAR_TOKEN = "-";
5748
+ function classifyUntrackedOverlay(boxToken, hostHash) {
5749
+ if (boxToken === void 0) return "copy";
5750
+ if (boxToken === NON_REGULAR_TOKEN) return "conflict";
5751
+ return boxToken === hostHash ? "identical" : "conflict";
5752
+ }
5422
5753
  async function unmergedPaths(container, ct) {
5423
5754
  const r = await gitIn(container, ct, ["diff", "--name-only", "--diff-filter=U", "-z"]);
5424
5755
  return r.exitCode === 0 ? splitNul(r.stdout) : [];
@@ -5432,20 +5763,20 @@ async function resyncWorkspaceFromHost(opts) {
5432
5763
  const hostMain = w.hostMainRepo;
5433
5764
  const boxBranch = w.branch;
5434
5765
  const res = { containerPath: ct, mergeConflicts: [], overlaySkipped: [] };
5435
- const hostBranchProbe = await execa8(
5766
+ const hostBranchProbe = await execa9(
5436
5767
  "git",
5437
5768
  ["-C", hostMain, "symbolic-ref", "--short", "-q", "HEAD"],
5438
5769
  { reject: false }
5439
5770
  );
5440
- const hostRef = hostBranchProbe.exitCode === 0 && hostBranchProbe.stdout.trim() ? hostBranchProbe.stdout.trim() : (await execa8("git", ["-C", hostMain, "rev-parse", "HEAD"], { reject: false })).stdout.trim();
5771
+ const hostRef = hostBranchProbe.exitCode === 0 && hostBranchProbe.stdout.trim() ? hostBranchProbe.stdout.trim() : (await execa9("git", ["-C", hostMain, "rev-parse", "HEAD"], { reject: false })).stdout.trim();
5441
5772
  if (!hostRef) {
5442
5773
  log(`resync: ${ct}: could not resolve host ref; skipping`);
5443
5774
  results.push(res);
5444
5775
  continue;
5445
5776
  }
5446
- const stash = await execa8("git", ["-C", hostMain, "stash", "create"], { reject: false });
5777
+ const stash = await execa9("git", ["-C", hostMain, "stash", "create"], { reject: false });
5447
5778
  const hostStashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
5448
- const untracked = await execa8(
5779
+ const untracked = await execa9(
5449
5780
  "git",
5450
5781
  ["-C", hostMain, "ls-files", "--others", "--exclude-standard", "-z"],
5451
5782
  { reject: false }
@@ -5515,7 +5846,7 @@ async function resyncWorkspaceFromHost(opts) {
5515
5846
  }
5516
5847
  }
5517
5848
  if (hostUntracked.length > 0) {
5518
- const filter = await execa8(
5849
+ const probe = await execa9(
5519
5850
  "docker",
5520
5851
  [
5521
5852
  "exec",
@@ -5525,23 +5856,50 @@ async function resyncWorkspaceFromHost(opts) {
5525
5856
  opts.container,
5526
5857
  "bash",
5527
5858
  "-c",
5528
- 'cd "$1" && while IFS= read -r -d "" f; do [ -e "$f" ] && printf "%s\\0" "$f"; done',
5859
+ 'cd "$1" && while IFS= read -r -d "" f; do if [ -f "$f" ] && [ ! -L "$f" ]; then printf "%s\\0%s\\0" "$(sha256sum < "$f" | cut -d" " -f1)" "$f"; elif [ -e "$f" ]; then printf "%s\\0%s\\0" "-" "$f"; fi; done',
5529
5860
  "bash",
5530
5861
  ct
5531
5862
  ],
5532
5863
  { input: hostUntracked.join("\0"), reject: false }
5533
5864
  );
5534
- const existing = new Set(filter.exitCode === 0 ? splitNul(filter.stdout) : []);
5535
- const toCopy = hostUntracked.filter((p) => !existing.has(p));
5536
- for (const p of hostUntracked) if (existing.has(p)) res.overlaySkipped.push(p);
5865
+ const boxTokens = /* @__PURE__ */ new Map();
5866
+ if (probe.exitCode === 0) {
5867
+ const flat = splitNul(probe.stdout);
5868
+ for (let i = 0; i + 1 < flat.length; i += 2) {
5869
+ const token = flat[i];
5870
+ const path = flat[i + 1];
5871
+ if (token !== void 0 && path !== void 0) boxTokens.set(path, token);
5872
+ }
5873
+ }
5874
+ const toCopy = [];
5875
+ let identical = 0;
5876
+ for (const p of hostUntracked) {
5877
+ const boxToken = boxTokens.get(p);
5878
+ let hostHash = "";
5879
+ if (boxToken !== void 0 && boxToken !== NON_REGULAR_TOKEN) {
5880
+ try {
5881
+ hostHash = createHash22("sha256").update(await readFile52(join7(hostMain, p))).digest("hex");
5882
+ } catch {
5883
+ identical++;
5884
+ continue;
5885
+ }
5886
+ }
5887
+ const verdict = classifyUntrackedOverlay(boxToken, hostHash);
5888
+ if (verdict === "copy") toCopy.push(p);
5889
+ else if (verdict === "conflict") res.overlaySkipped.push(p);
5890
+ else identical++;
5891
+ }
5892
+ if (identical > 0) {
5893
+ log(`resync: ${ct}: ${String(identical)} untracked host file(s) already identical in box (no-op)`);
5894
+ }
5537
5895
  if (toCopy.length > 0) {
5538
- const tarOut = await execa8("tar", ["-C", hostMain, "--null", "-T", "-", "-cf", "-"], {
5896
+ const tarOut = await execa9("tar", ["-C", hostMain, "--null", "-T", "-", "-cf", "-"], {
5539
5897
  input: toCopy.join("\0"),
5540
5898
  encoding: "buffer",
5541
5899
  reject: false
5542
5900
  });
5543
5901
  if (tarOut.exitCode === 0) {
5544
- await execa8(
5902
+ await execa9(
5545
5903
  "docker",
5546
5904
  ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
5547
5905
  { input: tarOut.stdout, reject: false }
@@ -5564,7 +5922,7 @@ var cached = null;
5564
5922
  async function detectPortless() {
5565
5923
  if (cached !== null) return cached;
5566
5924
  try {
5567
- const ver = await execa9(PORTLESS_BIN, SUB_VERSION, { reject: false });
5925
+ const ver = await execa10(PORTLESS_BIN, SUB_VERSION, { reject: false });
5568
5926
  if (ver.exitCode !== 0) {
5569
5927
  cached = { installed: false, proxyRunning: false };
5570
5928
  return cached;
@@ -5584,7 +5942,7 @@ function resetPortlessCache() {
5584
5942
  }
5585
5943
  async function portlessAlias(name, port) {
5586
5944
  try {
5587
- const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
5945
+ const r = await execa10(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
5588
5946
  return r.exitCode === 0;
5589
5947
  } catch {
5590
5948
  return false;
@@ -5592,7 +5950,7 @@ async function portlessAlias(name, port) {
5592
5950
  }
5593
5951
  async function portlessUnalias(name) {
5594
5952
  try {
5595
- const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
5953
+ const r = await execa10(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
5596
5954
  return r.exitCode === 0;
5597
5955
  } catch {
5598
5956
  return false;
@@ -5601,7 +5959,7 @@ async function portlessUnalias(name) {
5601
5959
  async function portlessGetUrl(name) {
5602
5960
  const fallback = `https://${name}.localhost`;
5603
5961
  try {
5604
- const r = await execa9(PORTLESS_BIN, [SUB_GET, name], { reject: false });
5962
+ const r = await execa10(PORTLESS_BIN, [SUB_GET, name], { reject: false });
5605
5963
  const out = (r.stdout ?? "").trim();
5606
5964
  if (r.exitCode === 0 && /^https?:\/\//.test(out)) return out;
5607
5965
  } catch {
@@ -5622,7 +5980,7 @@ function portlessBrowserEnv(boxName, opts) {
5622
5980
  }
5623
5981
  async function installPortless() {
5624
5982
  try {
5625
- const r = await execa9("npm", ["install", "-g", "portless"], { reject: false });
5983
+ const r = await execa10("npm", ["install", "-g", "portless"], { reject: false });
5626
5984
  return r.exitCode === 0;
5627
5985
  } catch {
5628
5986
  return false;
@@ -5630,7 +5988,7 @@ async function installPortless() {
5630
5988
  }
5631
5989
  async function startPortlessProxy() {
5632
5990
  try {
5633
- const r = await execa9(
5991
+ const r = await execa10(
5634
5992
  PORTLESS_BIN,
5635
5993
  ["proxy", "start", "--no-tls", "-p", String(PORTLESS_PROXY_PORT)],
5636
5994
  { reject: false }
@@ -5643,7 +6001,7 @@ async function startPortlessProxy() {
5643
6001
  function portlessStateDirCandidates() {
5644
6002
  const env = process.env["PORTLESS_STATE_DIR"];
5645
6003
  if (env && env.trim().length > 0) return [env.trim()];
5646
- return ["/tmp/portless", join7(homedir6(), ".portless")];
6004
+ return ["/tmp/portless", join8(homedir6(), ".portless")];
5647
6005
  }
5648
6006
  function pidAlive(pid) {
5649
6007
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -5656,7 +6014,7 @@ function pidAlive(pid) {
5656
6014
  }
5657
6015
  async function readProxyPid(dir) {
5658
6016
  try {
5659
- const raw = await readFile52(join7(dir, "proxy.pid"), "utf8");
6017
+ const raw = await readFile6(join8(dir, "proxy.pid"), "utf8");
5660
6018
  const pid = Number.parseInt(raw.trim(), 10);
5661
6019
  return Number.isFinite(pid) && pid > 0 ? pid : null;
5662
6020
  } catch {
@@ -5676,15 +6034,15 @@ async function resolvePortlessHostStateDir(override) {
5676
6034
  if (env && env.trim().length > 0) return env.trim();
5677
6035
  const live = await findLivePortlessStateDir();
5678
6036
  if (live) return live;
5679
- const home = join7(homedir6(), ".portless");
5680
- if (existsSync(home)) return home;
5681
- if (existsSync("/tmp/portless")) return "/tmp/portless";
6037
+ const home = join8(homedir6(), ".portless");
6038
+ if (existsSync2(home)) return home;
6039
+ if (existsSync2("/tmp/portless")) return "/tmp/portless";
5682
6040
  return home;
5683
6041
  }
5684
6042
  async function isProxyRunning() {
5685
6043
  if (await findLivePortlessStateDir() !== null) return true;
5686
6044
  try {
5687
- const r = await execa9("pgrep", ["-f", "portless proxy"], { reject: false });
6045
+ const r = await execa10("pgrep", ["-f", "portless proxy"], { reject: false });
5688
6046
  return r.exitCode === 0 && (r.stdout ?? "").trim().length > 0;
5689
6047
  } catch {
5690
6048
  return false;
@@ -5705,12 +6063,12 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
5705
6063
  ".cache",
5706
6064
  ".parcel-cache"
5707
6065
  ]);
5708
- var SNAPSHOTS_ROOT = join8(homedir7(), ".agentbox", "snapshots");
6066
+ var SNAPSHOTS_ROOT = join9(homedir7(), ".agentbox", "snapshots");
5709
6067
  function snapshotPathFor(box) {
5710
6068
  const mnemonic = sanitizeMnemonic(box.name);
5711
6069
  const n = box.projectIndex;
5712
6070
  const segment = typeof n === "number" && Number.isFinite(n) && n > 0 ? `${box.id}-${String(n)}-${mnemonic}` : `${box.id}-${mnemonic}`;
5713
- return join8(SNAPSHOTS_ROOT, segment);
6071
+ return join9(SNAPSHOTS_ROOT, segment);
5714
6072
  }
5715
6073
  async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
5716
6074
  const matches = [];
@@ -5723,7 +6081,7 @@ async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
5723
6081
  }
5724
6082
  for (const entry of entries) {
5725
6083
  if (!entry.isDirectory()) continue;
5726
- const abs = join8(dir, entry.name);
6084
+ const abs = join9(dir, entry.name);
5727
6085
  if (excluded.has(entry.name)) {
5728
6086
  matches.push(abs);
5729
6087
  continue;
@@ -5735,31 +6093,31 @@ async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
5735
6093
  return matches;
5736
6094
  }
5737
6095
  async function createSnapshot(opts) {
5738
- const source = resolve2(opts.source);
5739
- const destination = resolve2(opts.destination);
6096
+ const source = resolve22(opts.source);
6097
+ const destination = resolve22(opts.destination);
5740
6098
  const excluded = opts.excluded ?? EXCLUDE_DIRS;
5741
6099
  await mkdir42(SNAPSHOTS_ROOT, { recursive: true });
5742
6100
  const cpArgs = platform() === "darwin" ? ["-cR"] : ["-R"];
5743
- await execa10("cp", [...cpArgs, `${source}/`, destination]);
6101
+ await execa11("cp", [...cpArgs, `${source}/`, destination]);
5744
6102
  const toPrune = await findExcludedDirs(destination, excluded);
5745
6103
  await Promise.all(toPrune.map((p) => rm32(p, { recursive: true, force: true })));
5746
6104
  return { destination, prunedPaths: toPrune };
5747
6105
  }
5748
- var CHECKPOINTS_ROOT = join9(homedir8(), ".agentbox", "checkpoints");
6106
+ var CHECKPOINTS_ROOT = join10(homedir8(), ".agentbox", "checkpoints");
5749
6107
  var CHECKPOINT_IMAGE_PREFIX = "agentbox-ckpt-";
5750
6108
  function checkpointImageTag(projectRoot, name) {
5751
- const mnemonic = sanitizeMnemonic(basename32(projectRoot));
6109
+ const mnemonic = sanitizeMnemonic(basename4(projectRoot));
5752
6110
  return `${CHECKPOINT_IMAGE_PREFIX}${hashProjectPath(projectRoot)}_${mnemonic}:${name}`;
5753
6111
  }
5754
6112
  function projectCheckpointsDir(projectRoot) {
5755
- return join9(CHECKPOINTS_ROOT, projectDirSegment(projectRoot));
6113
+ return join10(CHECKPOINTS_ROOT, projectDirSegment(projectRoot));
5756
6114
  }
5757
6115
  function checkpointDir(projectRoot, name) {
5758
- return join9(projectCheckpointsDir(projectRoot), name);
6116
+ return join10(projectCheckpointsDir(projectRoot), name);
5759
6117
  }
5760
6118
  async function readManifest(dir) {
5761
6119
  try {
5762
- const raw = await readFile6(join9(dir, "manifest.json"), "utf8");
6120
+ const raw = await readFile7(join10(dir, "manifest.json"), "utf8");
5763
6121
  const m = JSON.parse(raw);
5764
6122
  if (m.schema !== 2 && m.schema !== 3) return null;
5765
6123
  return m;
@@ -5776,7 +6134,7 @@ async function listCheckpointsInDir(root) {
5776
6134
  }
5777
6135
  const out = [];
5778
6136
  for (const name of entries) {
5779
- const dir = join9(root, name);
6137
+ const dir = join10(root, name);
5780
6138
  const manifest = await readManifest(dir);
5781
6139
  if (manifest) out.push({ name, dir, manifest });
5782
6140
  }
@@ -5795,7 +6153,7 @@ async function listAllCheckpoints() {
5795
6153
  }
5796
6154
  const out = [];
5797
6155
  for (const segment of segments) {
5798
- const items = await listCheckpointsInDir(join9(CHECKPOINTS_ROOT, segment));
6156
+ const items = await listCheckpointsInDir(join10(CHECKPOINTS_ROOT, segment));
5799
6157
  if (items.length > 0) out.push({ segment, items });
5800
6158
  }
5801
6159
  return out;
@@ -5815,7 +6173,7 @@ async function listAllCheckpointImages() {
5815
6173
  }
5816
6174
  const out = /* @__PURE__ */ new Set();
5817
6175
  for (const proj of projectDirs) {
5818
- const projPath = join9(CHECKPOINTS_ROOT, proj);
6176
+ const projPath = join10(CHECKPOINTS_ROOT, proj);
5819
6177
  let names;
5820
6178
  try {
5821
6179
  names = (await readdir5(projPath, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -5823,7 +6181,7 @@ async function listAllCheckpointImages() {
5823
6181
  continue;
5824
6182
  }
5825
6183
  for (const name of names) {
5826
- const manifest = await readManifest(join9(projPath, name));
6184
+ const manifest = await readManifest(join10(projPath, name));
5827
6185
  if (manifest) out.add(manifest.image);
5828
6186
  }
5829
6187
  }
@@ -5865,7 +6223,7 @@ async function runCleanup(container, log) {
5865
6223
  }
5866
6224
  }
5867
6225
  async function inspectImageConfig(imageRef) {
5868
- const r = await execa11("docker", ["image", "inspect", imageRef], { reject: false });
6226
+ const r = await execa12("docker", ["image", "inspect", imageRef], { reject: false });
5869
6227
  if (r.exitCode !== 0) {
5870
6228
  throw new CheckpointError(`docker image inspect ${imageRef} failed`, r.stdout, r.stderr);
5871
6229
  }
@@ -5941,14 +6299,14 @@ async function createCheckpoint(opts) {
5941
6299
  await runCleanup(box.container, log);
5942
6300
  if (type === "layered") {
5943
6301
  log(`docker commit ${box.container} -> ${tag} (layered)`);
5944
- const r = await execa11("docker", ["commit", box.container, tag], { reject: false });
6302
+ const r = await execa12("docker", ["commit", box.container, tag], { reject: false });
5945
6303
  if (r.exitCode !== 0) {
5946
6304
  throw new CheckpointError(`docker commit failed for ${box.container}`, r.stdout, r.stderr);
5947
6305
  }
5948
6306
  } else {
5949
6307
  log(`docker commit ${box.container} -> <intermediate> (flattened path)`);
5950
6308
  const intermediate = `${tag}-intermediate`;
5951
- const commit = await execa11("docker", ["commit", box.container, intermediate], {
6309
+ const commit = await execa12("docker", ["commit", box.container, intermediate], {
5952
6310
  reject: false
5953
6311
  });
5954
6312
  if (commit.exitCode !== 0) {
@@ -5984,7 +6342,7 @@ async function createCheckpoint(opts) {
5984
6342
  cliVersion: stamp.cliVersion,
5985
6343
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5986
6344
  };
5987
- await writeFile32(join9(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
6345
+ await writeFile32(join10(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
5988
6346
  if (opts.setDefault) {
5989
6347
  await setConfigValue("project", "box.defaultCheckpointDocker", name, opts.projectRoot);
5990
6348
  log(`set project default checkpoint (box.defaultCheckpointDocker) -> ${name}`);
@@ -5993,7 +6351,7 @@ async function createCheckpoint(opts) {
5993
6351
  }
5994
6352
  async function flattenImage(sourceTag, destTag, log) {
5995
6353
  const tmpName = `agentbox-flatten-${Date.now().toString(36)}`;
5996
- const create = await execa11(
6354
+ const create = await execa12(
5997
6355
  "docker",
5998
6356
  ["create", "--name", tmpName, sourceTag, "sleep", "0"],
5999
6357
  { reject: false }
@@ -6001,11 +6359,11 @@ async function flattenImage(sourceTag, destTag, log) {
6001
6359
  if (create.exitCode !== 0) {
6002
6360
  throw new CheckpointError(`docker create for flatten failed`, create.stdout, create.stderr);
6003
6361
  }
6004
- const scratch = await mkdtemp3(join9(tmpdir3(), "agentbox-flatten-"));
6362
+ const scratch = await mkdtemp3(join10(tmpdir3(), "agentbox-flatten-"));
6005
6363
  try {
6006
- const rootfsPath = join9(scratch, "rootfs.tar");
6364
+ const rootfsPath = join10(scratch, "rootfs.tar");
6007
6365
  log(`exporting rootfs of ${sourceTag} to ${rootfsPath}`);
6008
- const exp = await execa11("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
6366
+ const exp = await execa12("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
6009
6367
  if (exp.exitCode !== 0) {
6010
6368
  throw new CheckpointError(`docker export failed`, exp.stdout, exp.stderr);
6011
6369
  }
@@ -6016,18 +6374,18 @@ async function flattenImage(sourceTag, destTag, log) {
6016
6374
  "ADD rootfs.tar /",
6017
6375
  ...renderConfigDirectives(cfg)
6018
6376
  ];
6019
- await writeFile32(join9(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
6377
+ await writeFile32(join10(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
6020
6378
  log(`building flattened ${destTag} from rootfs.tar (FROM scratch)`);
6021
- const build = await execa11(
6379
+ const build = await execa12(
6022
6380
  "docker",
6023
- ["build", "-t", destTag, "-f", join9(scratch, "Dockerfile"), scratch],
6381
+ ["build", "-t", destTag, "-f", join10(scratch, "Dockerfile"), scratch],
6024
6382
  { reject: false }
6025
6383
  );
6026
6384
  if (build.exitCode !== 0) {
6027
6385
  throw new CheckpointError(`flatten docker build failed`, build.stdout, build.stderr);
6028
6386
  }
6029
6387
  } finally {
6030
- await execa11("docker", ["rm", "-f", tmpName], { reject: false });
6388
+ await execa12("docker", ["rm", "-f", tmpName], { reject: false });
6031
6389
  await rm4(scratch, { recursive: true, force: true });
6032
6390
  }
6033
6391
  }
@@ -6071,7 +6429,7 @@ async function pathExists5(p) {
6071
6429
  }
6072
6430
  async function writeBoxEnvFile(container, env) {
6073
6431
  const body = formatBoxEnvBody(env);
6074
- const result = await execa12(
6432
+ const result = await execa13(
6075
6433
  "docker",
6076
6434
  ["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
6077
6435
  { input: body, reject: false }
@@ -6095,7 +6453,7 @@ function shellSingleQuote(s) {
6095
6453
  return `'${s.replace(/'/g, `'\\''`)}'`;
6096
6454
  }
6097
6455
  async function ensureHomeOwnedByVscode(container) {
6098
- await execa13(
6456
+ await execa14(
6099
6457
  "docker",
6100
6458
  [
6101
6459
  "exec",
@@ -6111,10 +6469,10 @@ async function ensureHomeOwnedByVscode(container) {
6111
6469
  { reject: false }
6112
6470
  );
6113
6471
  }
6114
- var STATE_DIR22 = join10(homedir9(), ".agentbox");
6115
- var PID_FILE = join10(STATE_DIR22, "relay.pid");
6116
- var LOG_FILE = join10(STATE_DIR22, "relay.log");
6117
- var RELAY_HOME_DIR = join10(STATE_DIR22, "relay");
6472
+ var STATE_DIR22 = join11(homedir9(), ".agentbox");
6473
+ var PID_FILE = join11(STATE_DIR22, "relay.pid");
6474
+ var LOG_FILE = join11(STATE_DIR22, "relay.log");
6475
+ var RELAY_HOME_DIR = join11(STATE_DIR22, "relay");
6118
6476
  var PORT = DEFAULT_RELAY_PORT;
6119
6477
  var ENDPOINT = {
6120
6478
  // host.docker.internal is the Docker Desktop / OrbStack-supplied alias for
@@ -6244,16 +6602,16 @@ async function spawnRelay(relayBin, cliEntry, log) {
6244
6602
  }
6245
6603
  function resolveRelayBin() {
6246
6604
  const override = process.env.AGENTBOX_RELAY_BIN;
6247
- if (override && existsSync2(override)) return override;
6248
- const here = dirname3(fileURLToPath(import.meta.url));
6605
+ if (override && existsSync3(override)) return override;
6606
+ const here = dirname22(fileURLToPath(import.meta.url));
6249
6607
  const candidates = [
6250
- resolve22(here, "..", "runtime", "relay", "bin.cjs"),
6251
- resolve22(here, "..", "..", "relay", "dist", "bin.cjs"),
6252
- resolve22(here, "..", "..", "..", "@agentbox", "relay", "dist", "bin.cjs"),
6253
- resolve22(here, "..", "..", "node_modules", "@agentbox", "relay", "dist", "bin.cjs")
6608
+ resolve3(here, "..", "runtime", "relay", "bin.cjs"),
6609
+ resolve3(here, "..", "..", "relay", "dist", "bin.cjs"),
6610
+ resolve3(here, "..", "..", "..", "@agentbox", "relay", "dist", "bin.cjs"),
6611
+ resolve3(here, "..", "..", "node_modules", "@agentbox", "relay", "dist", "bin.cjs")
6254
6612
  ];
6255
6613
  for (const c of candidates) {
6256
- if (existsSync2(c)) return c;
6614
+ if (existsSync3(c)) return c;
6257
6615
  }
6258
6616
  throw new Error(
6259
6617
  `could not locate @agentbox/relay bin; tried:
@@ -6262,32 +6620,32 @@ function resolveRelayBin() {
6262
6620
  }
6263
6621
  function resolveCliEntry() {
6264
6622
  const override = process.env.AGENTBOX_CLI_ENTRY;
6265
- if (override && existsSync2(override)) return override;
6266
- const here = dirname3(fileURLToPath(import.meta.url));
6623
+ if (override && existsSync3(override)) return override;
6624
+ const here = dirname22(fileURLToPath(import.meta.url));
6267
6625
  const candidates = [
6268
6626
  // Bundled CLI (dev + published): this module IS bundled into the CLI
6269
6627
  // entry, so the entry is index.js next to this file.
6270
- resolve22(here, "index.js"),
6271
- resolve22(here, "..", "..", "..", "apps", "cli", "dist", "index.js"),
6272
- resolve22(here, "..", "..", "..", "..", "dist", "index.js")
6628
+ resolve3(here, "index.js"),
6629
+ resolve3(here, "..", "..", "..", "apps", "cli", "dist", "index.js"),
6630
+ resolve3(here, "..", "..", "..", "..", "dist", "index.js")
6273
6631
  ];
6274
6632
  for (const c of candidates) {
6275
- if (existsSync2(c)) return c;
6633
+ if (existsSync3(c)) return c;
6276
6634
  }
6277
6635
  return null;
6278
6636
  }
6279
6637
  async function stageRelayHome(version, log) {
6280
6638
  if (!version || version === "0.0.0-dev") return null;
6281
6639
  if (process.env.AGENTBOX_RELAY_BIN || process.env.AGENTBOX_CLI_ENTRY) return null;
6282
- const cliRoot = findCliRoot(dirname3(fileURLToPath(import.meta.url)));
6640
+ const cliRoot = findCliRoot(dirname22(fileURLToPath(import.meta.url)));
6283
6641
  if (cliRoot === null) return null;
6284
- const homeDir = join10(RELAY_HOME_DIR, version);
6285
- const stagedEntry = join10(homeDir, "dist", "index.js");
6286
- const stagedBin = join10(homeDir, "runtime", "relay", "bin.cjs");
6287
- if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
6642
+ const homeDir = join11(RELAY_HOME_DIR, version);
6643
+ const stagedEntry = join11(homeDir, "dist", "index.js");
6644
+ const stagedBin = join11(homeDir, "runtime", "relay", "bin.cjs");
6645
+ if (existsSync3(stagedEntry) && existsSync3(stagedBin)) {
6288
6646
  return { relayBin: stagedBin, cliEntry: stagedEntry };
6289
6647
  }
6290
- const nodeModules = resolveDepRoot(join10(cliRoot, "dist", "index.js"));
6648
+ const nodeModules = resolveDepRoot(join11(cliRoot, "dist", "index.js"));
6291
6649
  if (nodeModules === null) return null;
6292
6650
  const tmpDir = `${homeDir}.tmp-${String(process.pid)}`;
6293
6651
  try {
@@ -6295,16 +6653,16 @@ async function stageRelayHome(version, log) {
6295
6653
  await rm5(tmpDir, { recursive: true, force: true });
6296
6654
  await mkdir6(tmpDir, { recursive: true });
6297
6655
  for (const sub of ["dist", "runtime", "share"]) {
6298
- const src = join10(cliRoot, sub);
6299
- if (existsSync2(src)) await cp(src, join10(tmpDir, sub), { recursive: true });
6656
+ const src = join11(cliRoot, sub);
6657
+ if (existsSync3(src)) await cp(src, join11(tmpDir, sub), { recursive: true });
6300
6658
  }
6301
- await cp(nodeModules, join10(tmpDir, "node_modules"), { recursive: true, dereference: true });
6659
+ await cp(nodeModules, join11(tmpDir, "node_modules"), { recursive: true, dereference: true });
6302
6660
  await rm5(homeDir, { recursive: true, force: true });
6303
6661
  await rename3(tmpDir, homeDir);
6304
6662
  } catch (err) {
6305
6663
  await rm5(tmpDir, { recursive: true, force: true }).catch(() => {
6306
6664
  });
6307
- if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
6665
+ if (existsSync3(stagedEntry) && existsSync3(stagedBin)) {
6308
6666
  return { relayBin: stagedBin, cliEntry: stagedEntry };
6309
6667
  }
6310
6668
  log(`relay home staging failed (${err instanceof Error ? err.message : String(err)}); using bundle paths`);
@@ -6316,8 +6674,8 @@ async function stageRelayHome(version, log) {
6316
6674
  return { relayBin: stagedBin, cliEntry: stagedEntry };
6317
6675
  }
6318
6676
  function findCliRoot(moduleDir) {
6319
- for (const root of [resolve22(moduleDir, ".."), resolve22(moduleDir, "..", "..")]) {
6320
- if (existsSync2(join10(root, "dist", "index.js")) && existsSync2(join10(root, "runtime", "relay", "bin.cjs"))) {
6677
+ for (const root of [resolve3(moduleDir, ".."), resolve3(moduleDir, "..", "..")]) {
6678
+ if (existsSync3(join11(root, "dist", "index.js")) && existsSync3(join11(root, "runtime", "relay", "bin.cjs"))) {
6321
6679
  return root;
6322
6680
  }
6323
6681
  }
@@ -6332,7 +6690,7 @@ function resolveDepRoot(fromFile) {
6332
6690
  const idx = main.lastIndexOf(marker);
6333
6691
  if (idx === -1) return null;
6334
6692
  const nm = main.slice(0, idx + marker.length - 1);
6335
- return existsSync2(join10(nm, "commander")) ? nm : null;
6693
+ return existsSync3(join11(nm, "commander")) ? nm : null;
6336
6694
  } catch {
6337
6695
  return null;
6338
6696
  }
@@ -6346,7 +6704,7 @@ async function gcOldRelayHomes(keepVersion) {
6346
6704
  }
6347
6705
  for (const name of entries) {
6348
6706
  if (name === keepVersion) continue;
6349
- await rm5(join10(RELAY_HOME_DIR, name), { recursive: true, force: true }).catch(() => {
6707
+ await rm5(join11(RELAY_HOME_DIR, name), { recursive: true, force: true }).catch(() => {
6350
6708
  });
6351
6709
  }
6352
6710
  }
@@ -6457,7 +6815,7 @@ function fetchHealthz(timeoutMs) {
6457
6815
  }
6458
6816
  async function readPidFile() {
6459
6817
  try {
6460
- const text = await readFile7(PID_FILE, "utf8");
6818
+ const text = await readFile8(PID_FILE, "utf8");
6461
6819
  const pid = Number.parseInt(text.trim(), 10);
6462
6820
  return Number.isFinite(pid) && pid > 0 ? pid : null;
6463
6821
  } catch {
@@ -6493,7 +6851,8 @@ async function registerBoxWithRelay(args) {
6493
6851
  worktrees,
6494
6852
  previewUrl: args.previewUrl,
6495
6853
  previewToken: args.previewToken,
6496
- bridgeToken: args.bridgeToken
6854
+ bridgeToken: args.bridgeToken,
6855
+ autoApproveHostActions: args.autoApproveHostActions
6497
6856
  });
6498
6857
  }
6499
6858
  async function forgetBoxFromRelay(boxId) {
@@ -6634,7 +6993,8 @@ async function rehydrateRelayRegistry(boxes) {
6634
6993
  worktrees: b.gitWorktrees,
6635
6994
  previewUrl: b.relayPreviewUrl,
6636
6995
  previewToken: b.relayPreviewToken,
6637
- bridgeToken: b.bridgeToken
6996
+ bridgeToken: b.bridgeToken,
6997
+ autoApproveHostActions: b.autoApproveHostActions
6638
6998
  });
6639
6999
  } catch {
6640
7000
  }
@@ -6785,7 +7145,7 @@ function persistableLimits(lim) {
6785
7145
  return Object.keys(out).length > 0 ? out : void 0;
6786
7146
  }
6787
7147
  function sanitizeBasename(workspacePath) {
6788
- const raw = basename4(resolve3(workspacePath));
7148
+ const raw = basename5(resolve4(workspacePath));
6789
7149
  return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
6790
7150
  }
6791
7151
  function defaultBoxName(workspacePath, id) {
@@ -6803,7 +7163,7 @@ async function pathExists6(p) {
6803
7163
  async function buildIdentityMounts() {
6804
7164
  const home = homedir10();
6805
7165
  const candidates = [
6806
- { src: join11(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
7166
+ { src: join12(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
6807
7167
  ];
6808
7168
  const out = [];
6809
7169
  for (const c of candidates) {
@@ -6816,11 +7176,11 @@ async function buildIdentityMounts() {
6816
7176
  async function createBox(opts) {
6817
7177
  const log = opts.onLog ?? (() => {
6818
7178
  });
6819
- const workspace = resolve3(opts.workspacePath);
7179
+ const workspace = resolve4(opts.workspacePath);
6820
7180
  if (!await pathExists6(workspace)) {
6821
7181
  throw new Error(`workspace does not exist: ${workspace}`);
6822
7182
  }
6823
- const cfgPath = join11(workspace, "agentbox.yaml");
7183
+ const cfgPath = join12(workspace, "agentbox.yaml");
6824
7184
  if (await pathExists6(cfgPath)) {
6825
7185
  try {
6826
7186
  const cfg = await loadConfig(cfgPath);
@@ -6900,12 +7260,35 @@ async function createBox(opts) {
6900
7260
  }
6901
7261
  let projectIndex;
6902
7262
  if (opts.projectRoot) {
6903
- projectIndex = allocateProjectIndex(await readState(), opts.projectRoot);
7263
+ projectIndex = await reserveProjectIndex(
7264
+ { id, name, container: containerName, image: imageRef, workspacePath: workspace, createdAt },
7265
+ opts.projectRoot
7266
+ );
6904
7267
  }
6905
7268
  const repoCarryOvers = [];
6906
7269
  const gitWorktreeRecords = [];
7270
+ const restoreWorktreePlans = [];
6907
7271
  if (checkpointImage && restoredWorktrees && restoredWorktrees.length > 0) {
6908
- gitWorktreeRecords.push(...restoredWorktrees);
7272
+ for (const w of restoredWorktrees) {
7273
+ const branchBase = w.kind === "root" ? `agentbox/${name}` : `agentbox/${name}--${w.relPathFromWorkspace.replace(/[^A-Za-z0-9._-]+/g, "_")}`;
7274
+ const freshBranch = await pickFreshBranch(w.hostMainRepo, branchBase);
7275
+ const freshGitWorktreePath = gitWorktreePathFor(freshBranch);
7276
+ restoreWorktreePlans.push({
7277
+ hostMainRepo: w.hostMainRepo,
7278
+ kind: w.kind,
7279
+ bakedGitWorktreePath: w.gitWorktreePath,
7280
+ freshBranch,
7281
+ freshGitWorktreePath
7282
+ });
7283
+ gitWorktreeRecords.push({
7284
+ kind: w.kind,
7285
+ hostMainRepo: w.hostMainRepo,
7286
+ containerPath: w.containerPath,
7287
+ gitWorktreePath: freshGitWorktreePath,
7288
+ branch: freshBranch,
7289
+ relPathFromWorkspace: w.relPathFromWorkspace
7290
+ });
7291
+ }
6909
7292
  }
6910
7293
  if (!checkpointImage) {
6911
7294
  const repos = await detectGitRepos(workspace);
@@ -7013,7 +7396,7 @@ async function createBox(opts) {
7013
7396
  log(`seeded claude credentials into ${claudeSpec.volume} from host backup`);
7014
7397
  }
7015
7398
  const claudeMounts = buildClaudeMounts(claudeSpec, process.env);
7016
- const wantCodex = opts.codexConfig !== void 0 || await pathExists6(join11(homedir10(), ".codex"));
7399
+ const wantCodex = opts.codexConfig !== void 0 || await pathExists6(join12(homedir10(), ".codex"));
7017
7400
  let codexMounts;
7018
7401
  let codexConfigVolume;
7019
7402
  if (wantCodex) {
@@ -7033,7 +7416,7 @@ async function createBox(opts) {
7033
7416
  codexMounts = buildCodexMounts(codexSpec, process.env);
7034
7417
  codexConfigVolume = codexSpec.volume;
7035
7418
  }
7036
- const wantOpencode = opts.opencodeConfig !== void 0 || await pathExists6(join11(homedir10(), ".config", "opencode")) || await pathExists6(join11(homedir10(), ".local", "share", "opencode"));
7419
+ const wantOpencode = opts.opencodeConfig !== void 0 || await pathExists6(join12(homedir10(), ".config", "opencode")) || await pathExists6(join12(homedir10(), ".local", "share", "opencode"));
7037
7420
  let opencodeMounts;
7038
7421
  let opencodeConfigVolume;
7039
7422
  if (wantOpencode) {
@@ -7054,9 +7437,9 @@ async function createBox(opts) {
7054
7437
  opencodeConfigVolume = opencodeSpec.volume;
7055
7438
  }
7056
7439
  const boxDir = boxRunDirFor({ id, name, projectIndex });
7057
- const socketDir = join11(boxDir, "run");
7058
- const socketPath = join11(socketDir, "ctl.sock");
7059
- const mergedExportDir = join11(boxDir, "workspace");
7440
+ const socketDir = join12(boxDir, "run");
7441
+ const socketPath = join12(socketDir, "ctl.sock");
7442
+ const mergedExportDir = join12(boxDir, "workspace");
7060
7443
  await mkdir7(socketDir, { recursive: true });
7061
7444
  await mkdir7(mergedExportDir, { recursive: true });
7062
7445
  const extraVolumes = await buildIdentityMounts();
@@ -7086,6 +7469,7 @@ async function createBox(opts) {
7086
7469
  }
7087
7470
  }
7088
7471
  for (const v of extraVolumes) log(`mounting agent dir: ${v}`);
7472
+ const autoApproveHostActions = (await loadEffectiveConfig(opts.projectRoot ?? workspace)).effective.box.autoApproveHostActions;
7089
7473
  const relayToken = generateRelayToken();
7090
7474
  if (relayUp) {
7091
7475
  try {
@@ -7096,7 +7480,8 @@ async function createBox(opts) {
7096
7480
  containerName,
7097
7481
  createdAt,
7098
7482
  projectIndex,
7099
- worktrees: gitWorktreeRecords
7483
+ worktrees: gitWorktreeRecords,
7484
+ autoApproveHostActions
7100
7485
  });
7101
7486
  log(`registered box token with relay`);
7102
7487
  } catch (err) {
@@ -7145,6 +7530,37 @@ async function createBox(opts) {
7145
7530
  effectiveLimits = { ...appliedLimits, disk: null };
7146
7531
  }
7147
7532
  }
7533
+ const baseRecord = {
7534
+ id,
7535
+ name,
7536
+ container: containerName,
7537
+ image: imageRef,
7538
+ workspacePath: workspace,
7539
+ snapshotDir,
7540
+ socketPath,
7541
+ claudeConfigVolume: claudeSpec.volume,
7542
+ codexConfigVolume,
7543
+ opencodeConfigVolume,
7544
+ vscodeServerVolume: vscodeServerVolumeName(id),
7545
+ cursorServerVolume: cursorServerVolumeName(id),
7546
+ relayToken: relayUp ? relayToken : void 0,
7547
+ gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : void 0,
7548
+ withPlaywright: opts.withPlaywright ? true : void 0,
7549
+ withEnv: opts.withEnv ? true : void 0,
7550
+ autoApproveHostActions: autoApproveHostActions ? true : void 0,
7551
+ vncEnabled: vncEnabled ? true : void 0,
7552
+ vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : void 0,
7553
+ vncPassword,
7554
+ webContainerPort: WEB_CONTAINER_PORT,
7555
+ dockerVolume,
7556
+ dockerCacheShared: dockerCacheShared || void 0,
7557
+ projectRoot: opts.projectRoot,
7558
+ projectIndex,
7559
+ checkpointImage,
7560
+ checkpointSource,
7561
+ resourceLimits: persistableLimits(effectiveLimits),
7562
+ createdAt
7563
+ };
7148
7564
  await runBox({
7149
7565
  name: containerName,
7150
7566
  image: imageRef,
@@ -7164,6 +7580,7 @@ async function createBox(opts) {
7164
7580
  }
7165
7581
  });
7166
7582
  log(`container ${containerName} started`);
7583
+ await recordBox(baseRecord);
7167
7584
  if (gitWorktreeRecords.length > 0) {
7168
7585
  await chownGitBindParents({
7169
7586
  container: containerName,
@@ -7188,7 +7605,8 @@ async function createBox(opts) {
7188
7605
  } catch (err) {
7189
7606
  if (opts.useBranch !== void 0) {
7190
7607
  log(`seedWorkspace failed for --use-branch ${opts.useBranch}; cleaning up the box`);
7191
- await execa14("docker", ["rm", "-f", containerName], { reject: false });
7608
+ await execa15("docker", ["rm", "-f", containerName], { reject: false });
7609
+ await removeBoxRecord(id);
7192
7610
  for (const w of gitWorktreeRecords) {
7193
7611
  await removeInBoxWorktree({
7194
7612
  hostMainRepo: w.hostMainRepo,
@@ -7204,21 +7622,27 @@ async function createBox(opts) {
7204
7622
  const source = snapshotDir ?? workspace;
7205
7623
  await seedWorkspaceFromDir({ container: containerName, hostSource: source, onLog: log });
7206
7624
  }
7207
- } else if (restoredWorktrees && restoredWorktrees.length > 0) {
7625
+ } else if (restoreWorktreePlans.length > 0) {
7626
+ await regenerateRestoredWorktrees({
7627
+ container: containerName,
7628
+ plans: restoreWorktreePlans,
7629
+ fromBranch: opts.fromBranch,
7630
+ onLog: log
7631
+ });
7208
7632
  await bindWorktrees(
7209
7633
  containerName,
7210
- restoredWorktrees.map((w) => ({
7634
+ gitWorktreeRecords.map((w) => ({
7211
7635
  kind: w.kind,
7212
7636
  containerPath: w.containerPath,
7213
7637
  gitWorktreePath: w.gitWorktreePath
7214
7638
  })),
7215
7639
  log
7216
7640
  );
7217
- log("re-bound /workspace from checkpoint image");
7641
+ log("re-bound /workspace from checkpoint image (fresh per-box worktree)");
7218
7642
  if (opts.resyncOnStart !== false) {
7219
7643
  const repos = await resyncWorkspaceFromHost({
7220
7644
  container: containerName,
7221
- worktrees: restoredWorktrees,
7645
+ worktrees: gitWorktreeRecords,
7222
7646
  onLog: log
7223
7647
  });
7224
7648
  resyncResult = {
@@ -7233,18 +7657,18 @@ async function createBox(opts) {
7233
7657
  }
7234
7658
  await repairIdeOwnership(containerName);
7235
7659
  log(".vscode-server + .cursor-server ownership verified");
7236
- const ctl = await launchCtlDaemon(containerName, socketPath);
7237
- if (ctl.up) log("agentbox-ctl daemon up");
7238
- else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);
7239
7660
  const dockerd = await launchDockerdDaemon(containerName);
7240
7661
  if (dockerd.up) {
7241
7662
  log(`dockerd up (data root=${dockerVolume})`);
7242
7663
  } else {
7243
7664
  log(`dockerd did not become ready: ${dockerd.reason}`);
7244
7665
  }
7666
+ const ctl = await launchCtlDaemon(containerName, socketPath);
7667
+ if (ctl.up) log("agentbox-ctl daemon up");
7668
+ else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);
7245
7669
  if (opts.withPlaywright) {
7246
7670
  log("installing @playwright/cli@latest (--with-playwright)");
7247
- const result = await execa14(
7671
+ const result = await execa15(
7248
7672
  "docker",
7249
7673
  [
7250
7674
  "exec",
@@ -7360,41 +7784,14 @@ async function createBox(opts) {
7360
7784
  }
7361
7785
  }
7362
7786
  const record = {
7363
- id,
7364
- name,
7365
- container: containerName,
7366
- image: imageRef,
7367
- workspacePath: workspace,
7368
- snapshotDir,
7369
- socketPath,
7370
- claudeConfigVolume: claudeSpec.volume,
7371
- codexConfigVolume,
7372
- opencodeConfigVolume,
7373
- vscodeServerVolume: vscodeServerVolumeName(id),
7374
- cursorServerVolume: cursorServerVolumeName(id),
7375
- relayToken: relayUp ? relayToken : void 0,
7376
- gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : void 0,
7377
- withPlaywright: opts.withPlaywright ? true : void 0,
7378
- withEnv: opts.withEnv ? true : void 0,
7787
+ ...baseRecord,
7379
7788
  carry: carrySummary,
7380
- vncEnabled: vncEnabled ? true : void 0,
7381
- vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : void 0,
7382
7789
  vncHostPort: vncHostPort ?? void 0,
7383
- vncPassword,
7384
- webContainerPort: WEB_CONTAINER_PORT,
7385
7790
  webHostPort: webHostPort ?? void 0,
7386
7791
  portlessAlias: portlessAliasName,
7387
7792
  portlessUrl,
7388
7793
  portlessVncAlias: portlessVncAliasName,
7389
- portlessVncUrl,
7390
- dockerVolume,
7391
- dockerCacheShared: dockerCacheShared || void 0,
7392
- projectRoot: opts.projectRoot,
7393
- projectIndex,
7394
- checkpointImage,
7395
- checkpointSource,
7396
- resourceLimits: persistableLimits(effectiveLimits),
7397
- createdAt
7794
+ portlessVncUrl
7398
7795
  };
7399
7796
  await recordBox(record);
7400
7797
  return { record, imageBuilt: built, resync: resyncResult };
@@ -7445,7 +7842,7 @@ function parseShellSessionList(stdout) {
7445
7842
  return out;
7446
7843
  }
7447
7844
  async function listShellSessions(container, user) {
7448
- const res = await execa15(
7845
+ const res = await execa16(
7449
7846
  "docker",
7450
7847
  [
7451
7848
  "exec",
@@ -7469,7 +7866,7 @@ async function startShellSession(opts) {
7469
7866
  const login = opts.login !== false;
7470
7867
  const term = process.env["TERM"] ?? "xterm-256color";
7471
7868
  const cmd = login ? "bash -l" : "bash";
7472
- const result = await execa15(
7869
+ const result = await execa16(
7473
7870
  "docker",
7474
7871
  [
7475
7872
  "exec",
@@ -7518,7 +7915,7 @@ function buildShellSessionAttachArgv(container, sessionName, user) {
7518
7915
  }
7519
7916
  async function shellSessionInfo(container, sessionName, user) {
7520
7917
  const name = sessionName ?? DEFAULT_SHELL_SESSION;
7521
- const has = await execa15(
7918
+ const has = await execa16(
7522
7919
  "docker",
7523
7920
  ["exec", "--user", user ?? CONTAINER_USER, container, "tmux", "has-session", "-t", name],
7524
7921
  { reject: false }
@@ -7526,7 +7923,7 @@ async function shellSessionInfo(container, sessionName, user) {
7526
7923
  return { running: has.exitCode === 0, sessionName: name };
7527
7924
  }
7528
7925
  async function killShellSession(container, sessionName, user) {
7529
- const res = await execa15(
7926
+ const res = await execa16(
7530
7927
  "docker",
7531
7928
  [
7532
7929
  "exec",
@@ -7580,7 +7977,7 @@ async function getBoxEndpoints(record, engine, persisted) {
7580
7977
  for (const svc of persistedServices) pushService(svc.name, svc.port);
7581
7978
  } else {
7582
7979
  try {
7583
- const cfg = await loadConfig(join12(record.workspacePath, "agentbox.yaml"));
7980
+ const cfg = await loadConfig(join13(record.workspacePath, "agentbox.yaml"));
7584
7981
  if (!webServiceName) {
7585
7982
  webServiceName = cfg.services.find((s) => s.expose)?.name ?? null;
7586
7983
  }
@@ -7731,9 +8128,9 @@ async function resyncBox(idOrName, onLog) {
7731
8128
  async function startBox(idOrName) {
7732
8129
  const box = await resolveBox(idOrName);
7733
8130
  for (const w of box.gitWorktrees ?? []) {
7734
- if (!await pathExists7(join13(w.hostMainRepo, ".git"))) {
8131
+ if (!await pathExists7(join14(w.hostMainRepo, ".git"))) {
7735
8132
  throw new Error(
7736
- `main repo for box worktree missing: ${join13(w.hostMainRepo, ".git")} (recreate the box)`
8133
+ `main repo for box worktree missing: ${join14(w.hostMainRepo, ".git")} (recreate the box)`
7737
8134
  );
7738
8135
  }
7739
8136
  }
@@ -7749,12 +8146,12 @@ async function startBox(idOrName) {
7749
8146
  );
7750
8147
  }
7751
8148
  await ensureHomeOwnedByVscode(box.container);
7752
- if (box.socketPath) {
7753
- await launchCtlDaemon(box.container, box.socketPath);
7754
- }
7755
8149
  if (box.dockerVolume) {
7756
8150
  await launchDockerdDaemon(box.container);
7757
8151
  }
8152
+ if (box.socketPath) {
8153
+ await launchCtlDaemon(box.container, box.socketPath);
8154
+ }
7758
8155
  if (box.vncEnabled) {
7759
8156
  await launchVncDaemon(box.container);
7760
8157
  const freshHostPort = await publishedHostPort(box.container, VNC_CONTAINER_PORT);
@@ -7809,7 +8206,8 @@ async function startBox(idOrName) {
7809
8206
  containerName: box.container,
7810
8207
  createdAt: box.createdAt,
7811
8208
  projectIndex: box.projectIndex,
7812
- worktrees: box.gitWorktrees
8209
+ worktrees: box.gitWorktrees,
8210
+ autoApproveHostActions: box.autoApproveHostActions
7813
8211
  });
7814
8212
  } catch {
7815
8213
  }
@@ -7828,7 +8226,7 @@ async function getBoxHostPaths(idOrName) {
7828
8226
  }
7829
8227
  async function dirSizeBytes(path) {
7830
8228
  try {
7831
- const result = await execa16("du", ["-sk", path], { reject: false });
8229
+ const result = await execa17("du", ["-sk", path], { reject: false });
7832
8230
  if (result.exitCode !== 0) return null;
7833
8231
  const sizeKb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
7834
8232
  if (Number.isNaN(sizeKb)) return null;
@@ -7902,16 +8300,13 @@ async function destroyBox(idOrName, opts = {}) {
7902
8300
  } catch {
7903
8301
  }
7904
8302
  }
7905
- const ownsWorktrees = !box.checkpointImage;
7906
- if (ownsWorktrees) {
7907
- for (const w of box.gitWorktrees ?? []) {
7908
- try {
7909
- await removeInBoxWorktree({
7910
- hostMainRepo: w.hostMainRepo,
7911
- gitWorktreePath: w.gitWorktreePath
7912
- });
7913
- } catch {
7914
- }
8303
+ for (const w of box.gitWorktrees ?? []) {
8304
+ try {
8305
+ await removeInBoxWorktree({
8306
+ hostMainRepo: w.hostMainRepo,
8307
+ gitWorktreePath: w.gitWorktreePath
8308
+ });
8309
+ } catch {
7915
8310
  }
7916
8311
  }
7917
8312
  const beforeContainer = await inspectContainerStatus(box.container);
@@ -7962,7 +8357,7 @@ async function destroyBox(idOrName, opts = {}) {
7962
8357
  async function listSnapshotDirs() {
7963
8358
  try {
7964
8359
  const entries = await readdir7(SNAPSHOTS_ROOT, { withFileTypes: true });
7965
- return entries.filter((e) => e.isDirectory()).map((e) => join13(SNAPSHOTS_ROOT, e.name));
8360
+ return entries.filter((e) => e.isDirectory()).map((e) => join14(SNAPSHOTS_ROOT, e.name));
7966
8361
  } catch {
7967
8362
  return [];
7968
8363
  }
@@ -7970,13 +8365,13 @@ async function listSnapshotDirs() {
7970
8365
  async function listBoxDirs() {
7971
8366
  try {
7972
8367
  const entries = await readdir7(BOXES_ROOT, { withFileTypes: true });
7973
- return entries.filter((e) => e.isDirectory()).map((e) => join13(BOXES_ROOT, e.name));
8368
+ return entries.filter((e) => e.isDirectory()).map((e) => join14(BOXES_ROOT, e.name));
7974
8369
  } catch {
7975
8370
  return [];
7976
8371
  }
7977
8372
  }
7978
8373
  async function listCheckpointImageTags() {
7979
- const r = await execa16(
8374
+ const r = await execa17(
7980
8375
  "docker",
7981
8376
  ["image", "ls", "--format", "{{.Repository}}:{{.Tag}}", `${CHECKPOINT_IMAGE_PREFIX}*`],
7982
8377
  { reject: false }
@@ -8084,7 +8479,7 @@ async function pruneBoxes(opts = {}) {
8084
8479
  } catch {
8085
8480
  }
8086
8481
  try {
8087
- await execa16("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
8482
+ await execa17("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
8088
8483
  } catch {
8089
8484
  }
8090
8485
  try {
@@ -8146,7 +8541,7 @@ function splitPair(raw) {
8146
8541
  return [parts[0].trim(), parts[1].trim()];
8147
8542
  }
8148
8543
  async function duBytes(path) {
8149
- const result = await execa17("du", ["-sk", path], { reject: false });
8544
+ const result = await execa18("du", ["-sk", path], { reject: false });
8150
8545
  if (result.exitCode !== 0) return null;
8151
8546
  const kb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
8152
8547
  return Number.isNaN(kb) ? null : kb * 1024;
@@ -8155,11 +8550,11 @@ async function volumeSizeBytes(name) {
8155
8550
  if (!name) return null;
8156
8551
  const engine = await detectEngine();
8157
8552
  if (engine === "orbstack") {
8158
- const live = join14(homedir11(), "OrbStack", "docker", "volumes", name);
8553
+ const live = join15(homedir11(), "OrbStack", "docker", "volumes", name);
8159
8554
  const sz = await duBytes(live);
8160
8555
  if (sz !== null) return sz;
8161
8556
  }
8162
- const df = await execa17(
8557
+ const df = await execa18(
8163
8558
  "docker",
8164
8559
  ["system", "df", "-v", "--format", "{{json .Volumes}}"],
8165
8560
  { reject: false }
@@ -8180,7 +8575,7 @@ async function volumeSizeBytes(name) {
8180
8575
  return null;
8181
8576
  }
8182
8577
  async function imageBytes(tag) {
8183
- const r = await execa17("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
8578
+ const r = await execa18("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
8184
8579
  reject: false
8185
8580
  });
8186
8581
  if (r.exitCode !== 0) return null;
@@ -8191,7 +8586,7 @@ async function projectCheckpointImageBytes(projectRoot, name) {
8191
8586
  return imageBytes(checkpointImageTag(projectRoot, name));
8192
8587
  }
8193
8588
  async function allCheckpointImagesBytes() {
8194
- const r = await execa17(
8589
+ const r = await execa18(
8195
8590
  "docker",
8196
8591
  [
8197
8592
  "image",
@@ -8218,7 +8613,7 @@ async function allCheckpointImagesBytes() {
8218
8613
  return any ? total : null;
8219
8614
  }
8220
8615
  async function agentboxHomeBytes() {
8221
- return duBytes(join14(homedir11(), ".agentbox"));
8616
+ return duBytes(join15(homedir11(), ".agentbox"));
8222
8617
  }
8223
8618
  function limitsFromRecord(record) {
8224
8619
  const r = record.resourceLimits;
@@ -8243,7 +8638,7 @@ function reconcileLimits(persisted, dockerJson) {
8243
8638
  };
8244
8639
  }
8245
8640
  async function containerWritableBytes(container) {
8246
- const r = await execa17(
8641
+ const r = await execa18(
8247
8642
  "docker",
8248
8643
  ["ps", "-a", "--filter", `name=^${container}$`, "--format", "{{.Size}}", "--size"],
8249
8644
  { reject: false }
@@ -8290,7 +8685,7 @@ async function boxResourceStats(record) {
8290
8685
  if (await inspectContainerStatus(record.container) !== "running") {
8291
8686
  return base;
8292
8687
  }
8293
- const proc = await execa17(
8688
+ const proc = await execa18(
8294
8689
  "docker",
8295
8690
  ["stats", "--no-stream", "--format", "{{json .}}", record.container],
8296
8691
  { reject: false }
@@ -8325,125 +8720,6 @@ async function boxResourceStats(record) {
8325
8720
  blockWriteBytes: blkPair ? parseDockerSize(blkPair[1]) : null
8326
8721
  };
8327
8722
  }
8328
- function posixDirname(p) {
8329
- return posix.dirname(p) || "/";
8330
- }
8331
- function asText(s) {
8332
- if (s === void 0) return "";
8333
- if (typeof s === "string") return s;
8334
- return Buffer.from(s).toString("utf8");
8335
- }
8336
- async function uploadToBox(box, hostSrc, boxDst) {
8337
- const srcAbs = resolve4(hostSrc);
8338
- if (!existsSync3(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
8339
- const srcBasename = basename5(srcAbs);
8340
- const srcParent = dirname22(srcAbs);
8341
- let boxParent;
8342
- let finalName;
8343
- if (boxDst.endsWith("/")) {
8344
- boxParent = boxDst.replace(/\/+$/, "") || "/";
8345
- finalName = srcBasename;
8346
- } else {
8347
- const isDir2 = await execa18(
8348
- "docker",
8349
- ["exec", box.container, "test", "-d", boxDst],
8350
- { reject: false }
8351
- );
8352
- if (isDir2.exitCode === 0) {
8353
- boxParent = boxDst.replace(/\/+$/, "") || "/";
8354
- finalName = srcBasename;
8355
- } else {
8356
- boxParent = posixDirname(boxDst);
8357
- finalName = posix.basename(boxDst);
8358
- }
8359
- }
8360
- const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
8361
- const mk = await execa18(
8362
- "docker",
8363
- ["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
8364
- { reject: false }
8365
- );
8366
- if (mk.exitCode !== 0) {
8367
- throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
8368
- }
8369
- const packed = await execa18("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
8370
- encoding: "buffer",
8371
- reject: false,
8372
- env: { ...process.env, COPYFILE_DISABLE: "1" }
8373
- });
8374
- if (packed.exitCode !== 0) {
8375
- throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
8376
- }
8377
- const extract = await execa18(
8378
- "docker",
8379
- ["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
8380
- { input: packed.stdout, reject: false }
8381
- );
8382
- if (extract.exitCode !== 0) {
8383
- throw new Error(`tar extract in box failed: ${asText(extract.stderr).slice(0, 300)}`);
8384
- }
8385
- if (finalName !== srcBasename) {
8386
- const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
8387
- const mv = await execa18(
8388
- "docker",
8389
- ["exec", "--user", "root", box.container, "mv", initial, finalPath],
8390
- { reject: false }
8391
- );
8392
- if (mv.exitCode !== 0) {
8393
- throw new Error(
8394
- `rename ${initial} -> ${finalPath} in box failed: ${asText(mv.stderr).slice(0, 300)}`
8395
- );
8396
- }
8397
- }
8398
- const chown = await execa18(
8399
- "docker",
8400
- ["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
8401
- { reject: false }
8402
- );
8403
- if (chown.exitCode !== 0) {
8404
- return {
8405
- finalPath,
8406
- warn: `chown ${finalPath} to vscode (uid 1000) failed; ownership inside the box may be root.`
8407
- };
8408
- }
8409
- return { finalPath };
8410
- }
8411
- async function downloadFromBox(box, boxSrc, hostDst) {
8412
- const srcBasename = posix.basename(boxSrc);
8413
- const srcParent = posixDirname(boxSrc);
8414
- const dstAbs = resolve4(hostDst);
8415
- let hostParent;
8416
- let finalName;
8417
- const dstExists = existsSync3(dstAbs);
8418
- if (hostDst.endsWith("/") || dstExists && statSync(dstAbs).isDirectory()) {
8419
- hostParent = dstAbs;
8420
- finalName = srcBasename;
8421
- } else {
8422
- hostParent = dirname22(dstAbs);
8423
- finalName = basename5(dstAbs);
8424
- }
8425
- mkdirSync(hostParent, { recursive: true });
8426
- const finalPath = posix.join(hostParent, finalName);
8427
- const packed = await execa18(
8428
- "docker",
8429
- ["exec", box.container, "tar", "-C", srcParent, "-cf", "-", srcBasename],
8430
- { encoding: "buffer", reject: false }
8431
- );
8432
- if (packed.exitCode !== 0) {
8433
- throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
8434
- }
8435
- const extract = await execa18("tar", ["-xf", "-", "-C", hostParent], {
8436
- input: packed.stdout,
8437
- reject: false
8438
- });
8439
- if (extract.exitCode !== 0) {
8440
- throw new Error(`tar extract on host failed: ${asText(extract.stderr).slice(0, 300)}`);
8441
- }
8442
- if (finalName !== srcBasename) {
8443
- renameSync(posix.join(hostParent, srcBasename), finalPath);
8444
- }
8445
- return { finalPath };
8446
- }
8447
8723
  var dockerProvider = {
8448
8724
  name: "docker",
8449
8725
  async create(req) {
@@ -8519,12 +8795,12 @@ var dockerProvider = {
8519
8795
  const r = await execInBox(box.container, argv, opts?.user ? { user: opts.user } : {});
8520
8796
  return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
8521
8797
  },
8522
- async uploadPath(box, hostSrc, boxDst) {
8523
- const r = await uploadToBox(box, hostSrc, boxDst);
8798
+ async uploadPath(box, hostSrc, boxDst, exclude) {
8799
+ const r = await uploadToBox(box, hostSrc, boxDst, exclude);
8524
8800
  return { finalPath: r.finalPath };
8525
8801
  },
8526
- async downloadPath(box, boxSrc, hostDst) {
8527
- const r = await downloadFromBox(box, boxSrc, hostDst);
8802
+ async downloadPath(box, boxSrc, hostDst, exclude) {
8803
+ const r = await downloadFromBox(box, boxSrc, hostDst, exclude);
8528
8804
  return { finalPath: r.finalPath };
8529
8805
  },
8530
8806
  async resolveUrl(box, opts) {
@@ -8604,7 +8880,7 @@ async function walkFiles(root, prefix = "") {
8604
8880
  const out = [];
8605
8881
  for (const ent of entries) {
8606
8882
  const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
8607
- const full = join15(root, ent.name);
8883
+ const full = join16(root, ent.name);
8608
8884
  if (ent.isDirectory()) {
8609
8885
  out.push(...await walkFiles(full, rel));
8610
8886
  } else if (ent.isFile()) {
@@ -8620,20 +8896,20 @@ async function walkFiles(root, prefix = "") {
8620
8896
  return out;
8621
8897
  }
8622
8898
  async function hashFile(absPath) {
8623
- const buf = await readFile8(absPath);
8624
- return createHash22("sha256").update(buf).digest("hex");
8899
+ const buf = await readFile9(absPath);
8900
+ return createHash32("sha256").update(buf).digest("hex");
8625
8901
  }
8626
8902
  async function buildHostSet(name, hostDir, boxDst) {
8627
8903
  if (hostDir === null) return { dst: boxDst, files: {}, hostDir: null };
8628
8904
  const rels = await walkFiles(hostDir);
8629
8905
  const files = {};
8630
8906
  for (const rel of rels) {
8631
- files[rel] = await hashFile(join15(hostDir, rel));
8907
+ files[rel] = await hashFile(join16(hostDir, rel));
8632
8908
  }
8633
8909
  return { dst: boxDst, files, hostDir };
8634
8910
  }
8635
8911
  async function buildHostSyncManifest(workspacePath, hostHome = homedir12()) {
8636
- const workflowsDir = join15(hostHome, ".claude", "workflows");
8912
+ const workflowsDir = join16(hostHome, ".claude", "workflows");
8637
8913
  const workflowsHost = await pathExists8(workflowsDir) ? workflowsDir : null;
8638
8914
  const memoryHost = await resolveClaudeMemoryDir(workspacePath, hostHome);
8639
8915
  const [workflows, memory] = await Promise.all([
@@ -8655,7 +8931,7 @@ function computeSyncDelta(host, box) {
8655
8931
  uploads.push({
8656
8932
  set: name,
8657
8933
  rel,
8658
- absSrc: join15(hostSet.hostDir, rel),
8934
+ absSrc: join16(hostSet.hostDir, rel),
8659
8935
  dst: `${hostSet.dst}/${rel}`
8660
8936
  });
8661
8937
  }
@@ -8674,15 +8950,15 @@ async function stageDynamicSyncTarball(uploads) {
8674
8950
  return { tarballPath: null, cleanup: async () => {
8675
8951
  } };
8676
8952
  }
8677
- const stageDir = await mkdtemp4(join15(tmpdir4(), "agentbox-dynsync-stage-"));
8953
+ const stageDir = await mkdtemp4(join16(tmpdir4(), "agentbox-dynsync-stage-"));
8678
8954
  let tarballPath = null;
8679
8955
  try {
8680
8956
  for (const up of uploads) {
8681
- const target = join15(stageDir, up.set, up.rel);
8957
+ const target = join16(stageDir, up.set, up.rel);
8682
8958
  await mkdir8(dirname32(target), { recursive: true });
8683
8959
  await copyFile2(up.absSrc, target);
8684
8960
  }
8685
- tarballPath = join15(tmpdir4(), `agentbox-dynsync-${basename6(stageDir)}.tar.gz`);
8961
+ tarballPath = join16(tmpdir4(), `agentbox-dynsync-${basename6(stageDir)}.tar.gz`);
8686
8962
  await execa19("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
8687
8963
  env: { ...process.env, COPYFILE_DISABLE: "1" }
8688
8964
  });
@@ -8723,6 +8999,7 @@ async function ensureBoxBrowser(container, timeoutMs = 8e3, targetUrl = "about:b
8723
8999
  }
8724
9000
 
8725
9001
  export {
9002
+ BUILT_IN_DEFAULTS,
8726
9003
  KEY_REGISTRY,
8727
9004
  lookupKey,
8728
9005
  UserConfigError,
@@ -8748,6 +9025,7 @@ export {
8748
9025
  renderPortsTable,
8749
9026
  loadCarrySection,
8750
9027
  resolveAgentLauncher,
9028
+ UserFacingError,
8751
9029
  BoxNotFoundError,
8752
9030
  AmbiguousBoxError,
8753
9031
  generateBoxId,
@@ -8768,6 +9046,8 @@ export {
8768
9046
  execInBox,
8769
9047
  removeImage,
8770
9048
  volumeExists,
9049
+ uploadToBox,
9050
+ downloadFromBox,
8771
9051
  CONTAINER_EXPORT_MERGED,
8772
9052
  detectEngine,
8773
9053
  setEngineOverride,
@@ -8804,6 +9084,7 @@ export {
8804
9084
  encodeClaudeProjectsKey,
8805
9085
  BOX_CLAUDE_PROJECT_DIR,
8806
9086
  resolveClaudeMemoryDir,
9087
+ stageClaudeJsonOnlyForUpload,
8807
9088
  stageClaudeStaticForUpload,
8808
9089
  stageClaudeCredentialsForUpload,
8809
9090
  stageCodexStaticForUpload,
@@ -8967,8 +9248,6 @@ export {
8967
9248
  allCheckpointImagesBytes,
8968
9249
  agentboxHomeBytes,
8969
9250
  boxResourceStats,
8970
- uploadToBox,
8971
- downloadFromBox,
8972
9251
  dockerProvider,
8973
9252
  BOX_WORKFLOWS_DIR,
8974
9253
  BOX_DYNAMIC_SYNC_MANIFEST,
@@ -8979,4 +9258,4 @@ export {
8979
9258
  browserSessionActive,
8980
9259
  ensureBoxBrowser
8981
9260
  };
8982
- //# sourceMappingURL=chunk-B4QG2MCW.js.map
9261
+ //# sourceMappingURL=chunk-MLMFNN4T.js.map