@madarco/agentbox 0.11.2 → 0.12.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 (35) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/{_cloud-attach-XWCVLO5V.js → _cloud-attach-XKO4SHR3.js} +3 -3
  3. package/dist/{chunk-ZGVMN54V.js → chunk-2LF5YILI.js} +21 -3
  4. package/dist/chunk-2LF5YILI.js.map +1 -0
  5. package/dist/{chunk-MXXXKJYS.js → chunk-DHJ7OMIP.js} +234 -83
  6. package/dist/chunk-DHJ7OMIP.js.map +1 -0
  7. package/dist/{chunk-GYJ62GFL.js → chunk-HFV6THYG.js} +6 -6
  8. package/dist/{chunk-ZJXTIH6C.js → chunk-IZXPJPPV.js} +1347 -852
  9. package/dist/chunk-IZXPJPPV.js.map +1 -0
  10. package/dist/{dist-RAZP76VX.js → dist-24PY2ZMO.js} +3 -3
  11. package/dist/{dist-PTJ6CEQY.js → dist-47LVLYUV.js} +4 -4
  12. package/dist/{dist-ASLPRUQR.js → dist-RZZSSUNB.js} +28 -2
  13. package/dist/{dist-WMQDMTWS.js → dist-SWUOU34W.js} +8 -5
  14. package/dist/dist-SWUOU34W.js.map +1 -0
  15. package/dist/index.js +1351 -731
  16. package/dist/index.js.map +1 -1
  17. package/package.json +6 -6
  18. package/runtime/docker/packages/ctl/dist/bin.cjs +335 -4
  19. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +86 -5
  20. package/runtime/hetzner/ctl.cjs +335 -4
  21. package/runtime/hetzner/gh-shim +86 -5
  22. package/runtime/relay/bin.cjs +285 -2
  23. package/runtime/vercel/ctl.cjs +335 -4
  24. package/runtime/vercel/gh-shim +86 -5
  25. package/share/host-skills/agentbox/SKILL.md +16 -5
  26. package/share/host-skills/agentbox-info/SKILL.md +29 -7
  27. package/dist/chunk-MXXXKJYS.js.map +0 -1
  28. package/dist/chunk-ZGVMN54V.js.map +0 -1
  29. package/dist/chunk-ZJXTIH6C.js.map +0 -1
  30. package/dist/dist-WMQDMTWS.js.map +0 -1
  31. /package/dist/{_cloud-attach-XWCVLO5V.js.map → _cloud-attach-XKO4SHR3.js.map} +0 -0
  32. /package/dist/{chunk-GYJ62GFL.js.map → chunk-HFV6THYG.js.map} +0 -0
  33. /package/dist/{dist-RAZP76VX.js.map → dist-24PY2ZMO.js.map} +0 -0
  34. /package/dist/{dist-PTJ6CEQY.js.map → dist-47LVLYUV.js.map} +0 -0
  35. /package/dist/{dist-ASLPRUQR.js.map → dist-RZZSSUNB.js.map} +0 -0
@@ -22,10 +22,10 @@ import {
22
22
  } from "./chunk-SNTHHWKY.js";
23
23
 
24
24
  // ../../packages/sandbox-docker/dist/index.js
25
- import { mkdir as mkdir7, stat as stat6 } from "fs/promises";
26
- import { homedir as homedir9 } from "os";
27
- import { basename as basename3, join as join10, resolve as resolve3 } from "path";
28
- import { execa as execa13 } from "execa";
25
+ import { mkdir as mkdir7, stat as stat8 } from "fs/promises";
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";
29
29
 
30
30
  // ../../packages/ctl/dist/index.js
31
31
  import { readFile } from "fs/promises";
@@ -704,15 +704,16 @@ async function loadCarrySection(path) {
704
704
 
705
705
  // ../../packages/sandbox-docker/dist/index.js
706
706
  import { spawnSync } from "child_process";
707
- import { mkdir as mkdir22, mkdtemp, readdir as readdir3, readFile as readFile24, rm as rm2, stat as stat3, writeFile as writeFile3 } from "fs/promises";
708
- import { homedir as homedir2, tmpdir } from "os";
709
- import { join as join22, relative } from "path";
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
710
  import { setTimeout as delay } from "timers/promises";
711
- import { execa as execa3 } from "execa";
711
+ import { execa as execa5 } from "execa";
712
712
  import { execa as execa2 } from "execa";
713
- import { mkdir as mkdir3, readFile as readFile4 } from "fs/promises";
714
- import { homedir as homedir3 } from "os";
715
- import { join as join4 } from "path";
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";
716
717
  import { execa as execa22 } from "execa";
717
718
 
718
719
  // ../../packages/config/dist/index.js
@@ -736,13 +737,23 @@ var BUILT_IN_DEFAULTS = {
736
737
  defaultCheckpointDaytona: "",
737
738
  defaultCheckpointHetzner: "",
738
739
  defaultCheckpointVercel: "",
740
+ size: "",
741
+ sizeDocker: "",
742
+ sizeDaytona: "",
743
+ sizeHetzner: "",
744
+ sizeVercel: "",
739
745
  withPlaywright: false,
740
746
  withEnv: false,
747
+ resyncOnStart: true,
741
748
  vnc: true,
742
749
  isolateClaudeConfig: false,
743
750
  isolateCodexConfig: false,
744
751
  isolateOpencodeConfig: false,
745
752
  image: "agentbox/box:dev",
753
+ imageDocker: "",
754
+ imageDaytona: "",
755
+ imageHetzner: "",
756
+ imageVercel: "",
746
757
  // Mirrors BOX_IMAGE_REGISTRY in @agentbox/sandbox-docker. Empty disables the
747
758
  // registry pull (always build the docker base image locally).
748
759
  imageRegistry: "ghcr.io/madarco/agentbox/box",
@@ -860,6 +871,35 @@ var KEY_REGISTRY = [
860
871
  description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
861
872
  advanced: true
862
873
  },
874
+ {
875
+ key: "box.size",
876
+ type: "string",
877
+ description: "Default VM size for cloud providers. Provider-interpreted: hetzner = server type (e.g. `cx33`); daytona = `cpu-memory-disk` GB (e.g. `4-8-20`). Used as fallback when no per-provider override is set. Docker/Vercel ignore it."
878
+ },
879
+ {
880
+ key: "box.sizeDocker",
881
+ type: "string",
882
+ description: "Per-provider override of `box.size` for docker. Reserved \u2014 docker sizing is controlled via `box.memory` / `box.cpus` / `box.disk`.",
883
+ advanced: true
884
+ },
885
+ {
886
+ key: "box.sizeDaytona",
887
+ type: "string",
888
+ description: "Per-provider override of `box.size` for daytona. `cpu-memory-disk` GB spec (e.g. `4-8-20`). Only honored on the image/Dockerfile create path; Daytona rejects custom resources on snapshot-resume.",
889
+ advanced: true
890
+ },
891
+ {
892
+ key: "box.sizeHetzner",
893
+ type: "string",
894
+ description: "Per-provider override of `box.size` for hetzner. Server type string (e.g. `cx23`, `cx33`, `cx43`).",
895
+ advanced: true
896
+ },
897
+ {
898
+ key: "box.sizeVercel",
899
+ type: "string",
900
+ description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
901
+ advanced: true
902
+ },
863
903
  {
864
904
  key: "checkpoint.maxLayers",
865
905
  type: "int",
@@ -876,6 +916,11 @@ var KEY_REGISTRY = [
876
916
  type: "bool",
877
917
  description: "Copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at box create time (gitignore-bypassing)."
878
918
  },
919
+ {
920
+ key: "box.resyncOnStart",
921
+ type: "bool",
922
+ description: "Merge the host's current branch into the box and overlay the host's uncommitted/untracked changes when starting an agent session (keeps the box's version on conflict and warns the agent)."
923
+ },
879
924
  {
880
925
  key: "box.vnc",
881
926
  type: "bool",
@@ -899,7 +944,31 @@ var KEY_REGISTRY = [
899
944
  {
900
945
  key: "box.image",
901
946
  type: "string",
902
- description: "Box image ref (advanced).",
947
+ description: "Generic box image ref (fallback). Used as fallback when no per-provider override is set; the default `agentbox/box:dev` is treated as a sentinel by cloud backends (boot from their prepared base snapshot instead).",
948
+ advanced: true
949
+ },
950
+ {
951
+ key: "box.imageDocker",
952
+ type: "string",
953
+ description: "Per-provider override of `box.image` for docker (local docker image ref, e.g. `agentbox/box:dev`). Wins over the generic when set.",
954
+ advanced: true
955
+ },
956
+ {
957
+ key: "box.imageDaytona",
958
+ type: "string",
959
+ description: "Per-provider override of `box.image` for daytona (named snapshot, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider daytona`.",
960
+ advanced: true
961
+ },
962
+ {
963
+ key: "box.imageHetzner",
964
+ type: "string",
965
+ description: "Per-provider override of `box.image` for hetzner (image description, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider hetzner`.",
966
+ advanced: true
967
+ },
968
+ {
969
+ key: "box.imageVercel",
970
+ type: "string",
971
+ description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
903
972
  advanced: true
904
973
  },
905
974
  {
@@ -1462,6 +1531,23 @@ function defaultCheckpointConfigKey(provider) {
1462
1531
  if (provider === "vercel") return "box.defaultCheckpointVercel";
1463
1532
  return "box.defaultCheckpoint";
1464
1533
  }
1534
+ function resolveBoxSize(cfg, provider) {
1535
+ const perProvider = provider === "daytona" ? cfg.box.sizeDaytona : provider === "hetzner" ? cfg.box.sizeHetzner : provider === "vercel" ? cfg.box.sizeVercel : cfg.box.sizeDocker;
1536
+ if (perProvider && perProvider.length > 0) return perProvider;
1537
+ return cfg.box.size;
1538
+ }
1539
+ function resolveBoxImage(cfg, provider) {
1540
+ const perProvider = provider === "daytona" ? cfg.box.imageDaytona : provider === "hetzner" ? cfg.box.imageHetzner : provider === "vercel" ? cfg.box.imageVercel : cfg.box.imageDocker;
1541
+ if (perProvider && perProvider.length > 0) return perProvider;
1542
+ return cfg.box.image;
1543
+ }
1544
+ function boxImageConfigKey(provider) {
1545
+ if (provider === "docker") return "box.imageDocker";
1546
+ if (provider === "daytona") return "box.imageDaytona";
1547
+ if (provider === "hetzner") return "box.imageHetzner";
1548
+ if (provider === "vercel") return "box.imageVercel";
1549
+ return "box.image";
1550
+ }
1465
1551
  async function setConfigValue(scope, key, value, cwd, opts = {}) {
1466
1552
  if (!lookupKey(key)) {
1467
1553
  throw new UserConfigError(`unknown key "${key}"`);
@@ -1656,26 +1742,30 @@ async function touchProjectMeta(absPath) {
1656
1742
  }
1657
1743
 
1658
1744
  // ../../packages/sandbox-docker/dist/index.js
1659
- import { chmod, mkdir as mkdir32, readFile as readFile32 } from "fs/promises";
1660
- import { basename as basename2, join as join32 } from "path";
1745
+ import { copyFile, mkdtemp, readdir as readdir22, readFile as readFile32, rm as rm3, stat as stat22, writeFile as writeFile3 } from "fs/promises";
1746
+ import { homedir as homedir22, tmpdir } from "os";
1747
+ import { basename as basename2, join as join32, relative } from "path";
1661
1748
  import { execa as execa4 } from "execa";
1749
+ import { chmod, mkdir as mkdir22, readFile as readFile24 } from "fs/promises";
1750
+ import { basename as basename3, join as join22 } from "path";
1751
+ import { execa as execa3 } from "execa";
1662
1752
  import { spawnSync as spawnSync2 } from "child_process";
1663
- import { stat as stat22 } from "fs/promises";
1664
- import { homedir as homedir32 } from "os";
1665
- import { join as join42 } from "path";
1666
- import { execa as execa5 } from "execa";
1667
- import { spawnSync as spawnSync3 } from "child_process";
1668
- import { stat as stat32 } from "fs/promises";
1753
+ import { stat as stat42 } from "fs/promises";
1669
1754
  import { homedir as homedir4 } from "os";
1670
- import { join as join5 } from "path";
1755
+ import { join as join52 } from "path";
1671
1756
  import { execa as execa6 } from "execa";
1672
- import { randomBytes as randomBytes3 } from "crypto";
1673
- import { execa as execa7 } from "execa";
1674
- import { existsSync } from "fs";
1675
- import { readFile as readFile42 } from "fs/promises";
1757
+ import { spawnSync as spawnSync3 } from "child_process";
1758
+ import { stat as stat5 } from "fs/promises";
1676
1759
  import { homedir as homedir5 } from "os";
1677
1760
  import { join as join6 } from "path";
1761
+ import { execa as execa7 } from "execa";
1762
+ import { randomBytes as randomBytes3 } from "crypto";
1678
1763
  import { execa as execa8 } from "execa";
1764
+ import { existsSync } from "fs";
1765
+ import { readFile as readFile52 } from "fs/promises";
1766
+ import { homedir as homedir6 } from "os";
1767
+ import { join as join7 } from "path";
1768
+ import { execa as execa9 } from "execa";
1679
1769
 
1680
1770
  // ../../packages/core/dist/index.js
1681
1771
  import { randomBytes } from "crypto";
@@ -1734,25 +1824,25 @@ function generateBoxId() {
1734
1824
  }
1735
1825
 
1736
1826
  // ../../packages/sandbox-docker/dist/index.js
1737
- import { execa as execa9 } from "execa";
1738
- import { mkdir as mkdir4, readdir as readdir22, rm as rm22, stat as stat4 } from "fs/promises";
1739
- import { homedir as homedir6, platform } from "os";
1740
- import { join as join7, resolve as resolve2 } from "path";
1741
- import { mkdir as mkdir5, mkdtemp as mkdtemp2, readFile as readFile5, readdir as readdir32, rm as rm3, writeFile as writeFile22 } from "fs/promises";
1742
- import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
1743
- import { basename as basename22, join as join8 } from "path";
1744
1827
  import { execa as execa10 } from "execa";
1745
- import { stat as stat5 } from "fs/promises";
1828
+ import { mkdir as mkdir42, readdir as readdir42, rm as rm32, stat as stat6 } from "fs/promises";
1829
+ import { homedir as homedir7, platform } from "os";
1830
+ import { join as join8, resolve as resolve2 } from "path";
1831
+ import { mkdir as mkdir5, mkdtemp as mkdtemp3, readFile as readFile6, readdir as readdir5, rm as rm4, writeFile as writeFile32 } from "fs/promises";
1832
+ import { homedir as homedir8, tmpdir as tmpdir3 } from "os";
1833
+ import { basename as basename32, join as join9 } from "path";
1746
1834
  import { execa as execa11 } from "execa";
1835
+ import { stat as stat7 } from "fs/promises";
1747
1836
  import { execa as execa12 } from "execa";
1837
+ import { execa as execa13 } from "execa";
1748
1838
  import { spawn } from "child_process";
1749
1839
  import { randomBytes as randomBytes22 } from "crypto";
1750
1840
  import { existsSync as existsSync2, openSync } from "fs";
1751
- import { cp, mkdir as mkdir6, readFile as readFile6, readdir as readdir4, rename as rename3, rm as rm4, unlink as unlink2, writeFile as writeFile32 } from "fs/promises";
1841
+ 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";
1752
1842
  import { request as httpRequest } from "http";
1753
1843
  import { createRequire } from "module";
1754
- import { homedir as homedir8 } from "os";
1755
- import { dirname as dirname3, join as join9, resolve as resolve22, sep } from "path";
1844
+ import { homedir as homedir9 } from "os";
1845
+ import { dirname as dirname3, join as join10, resolve as resolve22, sep } from "path";
1756
1846
  import { setTimeout as delay2 } from "timers/promises";
1757
1847
  import { fileURLToPath } from "url";
1758
1848
 
@@ -1794,6 +1884,8 @@ var GH_PR_OPS = [
1794
1884
  "create",
1795
1885
  "view",
1796
1886
  "list",
1887
+ "diff",
1888
+ "checks",
1797
1889
  "comment",
1798
1890
  "review",
1799
1891
  "merge",
@@ -1801,6 +1893,13 @@ var GH_PR_OPS = [
1801
1893
  "close",
1802
1894
  "reopen"
1803
1895
  ];
1896
+ var PR_REVIEW_COMMENT = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments(\?.*)?$/;
1897
+ var PR_REVIEW_COMMENT_REPLY = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments\/\d+\/replies(\?.*)?$/;
1898
+ var GH_API_WRITE_ALLOWED_ENDPOINTS = [
1899
+ PR_REVIEW_COMMENT,
1900
+ PR_REVIEW_COMMENT_REPLY
1901
+ ];
1902
+ var GH_API_ALLOWED_ENDPOINTS = [...GH_API_WRITE_ALLOWED_ENDPOINTS];
1804
1903
  function injectPrCreateHead(op, branch, args) {
1805
1904
  if (op !== "create") return args;
1806
1905
  if (!branch || branch === "HEAD") return args;
@@ -1971,21 +2070,22 @@ function queueLogPath(id) {
1971
2070
  }
1972
2071
 
1973
2072
  // ../../packages/sandbox-docker/dist/index.js
2073
+ import { execa as execa16 } from "execa";
2074
+ import { readdir as readdir7, rm as rm6, stat as stat9 } from "fs/promises";
2075
+ import { join as join13 } from "path";
1974
2076
  import { execa as execa15 } from "execa";
1975
- import { readdir as readdir5, rm as rm5, stat as stat7 } from "fs/promises";
1976
2077
  import { join as join12 } from "path";
1977
- import { execa as execa14 } from "execa";
1978
- import { join as join11 } from "path";
1979
- import { homedir as homedir10 } from "os";
1980
- import { join as join13 } from "path";
1981
- import { execa as execa16 } from "execa";
1982
- import { existsSync as existsSync3, mkdirSync, renameSync, statSync } from "fs";
1983
- import { basename as basename4, dirname as dirname22, posix, resolve as resolve4 } from "path";
2078
+ import { homedir as homedir11 } from "os";
2079
+ import { join as join14 } from "path";
1984
2080
  import { execa as execa17 } from "execa";
1985
- import { copyFile, mkdtemp as mkdtemp3, readdir as readdir6, readFile as readFile7, rm as rm6, stat as stat8, writeFile as writeFile4 } from "fs/promises";
1986
- import { homedir as homedir11, tmpdir as tmpdir3 } from "os";
1987
- import { basename as basename5, join as join14, relative as relative2 } from "path";
2081
+ import { existsSync as existsSync3, mkdirSync, renameSync, statSync } from "fs";
2082
+ import { basename as basename5, dirname as dirname22, posix, resolve as resolve4 } from "path";
1988
2083
  import { execa as execa18 } from "execa";
2084
+ import { createHash as createHash22 } from "crypto";
2085
+ 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";
2086
+ import { homedir as homedir12, tmpdir as tmpdir4 } from "os";
2087
+ import { basename as basename6, dirname as dirname32, join as join15 } from "path";
2088
+ import { execa as execa19 } from "execa";
1989
2089
  function isHostPathHookCommand(command, hostHome) {
1990
2090
  if (typeof command !== "string" || command.length === 0) return false;
1991
2091
  if (hostHome.length === 0) return false;
@@ -2402,7 +2502,7 @@ async function getDockerContext() {
2402
2502
  const ctx = (result.stdout ?? "").trim();
2403
2503
  return ctx.length > 0 ? ctx : void 0;
2404
2504
  }
2405
- var BOXES_ROOT = join4(homedir3(), ".agentbox", "boxes");
2505
+ var BOXES_ROOT = join5(homedir2(), ".agentbox", "boxes");
2406
2506
  function boxDirSegment(box) {
2407
2507
  const mnemonic = sanitizeMnemonic(box.name);
2408
2508
  const n = box.projectIndex;
@@ -2412,14 +2512,14 @@ function boxDirSegment(box) {
2412
2512
  return `${box.id}-${mnemonic}`;
2413
2513
  }
2414
2514
  function boxRunDirFor(box) {
2415
- return join4(BOXES_ROOT, boxDirSegment(box));
2515
+ return join5(BOXES_ROOT, boxDirSegment(box));
2416
2516
  }
2417
2517
  function boxStatusPathFor(box) {
2418
- return join4(boxRunDirFor(box), "status.json");
2518
+ return join5(boxRunDirFor(box), "status.json");
2419
2519
  }
2420
2520
  async function readBoxStatus(box) {
2421
2521
  try {
2422
- const raw = await readFile4(boxStatusPathFor(box), "utf8");
2522
+ const raw = await readFile5(boxStatusPathFor(box), "utf8");
2423
2523
  const parsed = JSON.parse(raw);
2424
2524
  if (parsed.schema !== 1) return null;
2425
2525
  return parsed;
@@ -2428,13 +2528,13 @@ async function readBoxStatus(box) {
2428
2528
  }
2429
2529
  }
2430
2530
  function orbstackVolumePath(volume, ...sub) {
2431
- return join4(homedir3(), "OrbStack", "docker", "volumes", volume, ...sub);
2531
+ return join5(homedir2(), "OrbStack", "docker", "volumes", volume, ...sub);
2432
2532
  }
2433
2533
  async function getHostPaths(record) {
2434
2534
  const boxDir = boxRunDirFor(record);
2435
2535
  return {
2436
2536
  boxDir,
2437
- mergedExport: join4(boxDir, "workspace")
2537
+ mergedExport: join5(boxDir, "workspace")
2438
2538
  };
2439
2539
  }
2440
2540
  async function hasContainerPath(container, path) {
@@ -2444,7 +2544,7 @@ async function hasContainerPath(container, path) {
2444
2544
  async function refreshExport(record, opts = {}) {
2445
2545
  const paths = await getHostPaths(record);
2446
2546
  const excludeNodeModules = !opts.includeNodeModules;
2447
- await mkdir3(paths.mergedExport, { recursive: true });
2547
+ await mkdir4(paths.mergedExport, { recursive: true });
2448
2548
  const bindAvailable = await hasContainerPath(record.container, CONTAINER_EXPORT_MERGED);
2449
2549
  if (bindAvailable) {
2450
2550
  const args = ["rsync", "-a", "--delete"];
@@ -2640,7 +2740,7 @@ async function pullToHost(record, opts = {}) {
2640
2740
  let scratchDir;
2641
2741
  if (opts.noRefresh) {
2642
2742
  scratchDir = paths.mergedExport;
2643
- await mkdir3(scratchDir, { recursive: true });
2743
+ await mkdir4(scratchDir, { recursive: true });
2644
2744
  } else {
2645
2745
  const refreshed = await refreshExport(record, {
2646
2746
  includeNodeModules: opts.includeNodeModules
@@ -2719,7 +2819,7 @@ async function openInFinder(record, opts) {
2719
2819
  if (opts.noRefresh) {
2720
2820
  const paths = await getHostPaths(record);
2721
2821
  hostPath = paths.mergedExport;
2722
- await mkdir3(hostPath, { recursive: true });
2822
+ await mkdir4(hostPath, { recursive: true });
2723
2823
  } else {
2724
2824
  const refreshed = await refreshExport(record, opts);
2725
2825
  hostPath = refreshed.hostPath;
@@ -2744,6 +2844,36 @@ var ExportError = class extends Error {
2744
2844
  stdout;
2745
2845
  stderr;
2746
2846
  };
2847
+ async function carrySourceHash(entry) {
2848
+ if (entry.kind === "missing") return void 0;
2849
+ try {
2850
+ if (entry.kind === "file") {
2851
+ return createHash3("sha256").update(await readFile5(entry.absSrc)).digest("hex");
2852
+ }
2853
+ const h = createHash3("sha256");
2854
+ const walk = async (dir, rel) => {
2855
+ const names = (await readdir4(dir)).sort();
2856
+ for (const name of names) {
2857
+ const abs = join5(dir, name);
2858
+ const relPath = rel ? `${rel}/${name}` : name;
2859
+ const st = await stat4(abs);
2860
+ if (st.isDirectory()) {
2861
+ h.update(`d\0${relPath}
2862
+ `);
2863
+ await walk(abs, relPath);
2864
+ } else {
2865
+ h.update(`f\0${relPath}\0`);
2866
+ h.update(await readFile5(abs));
2867
+ h.update("\n");
2868
+ }
2869
+ }
2870
+ };
2871
+ await walk(entry.absSrc, "");
2872
+ return h.digest("hex");
2873
+ } catch {
2874
+ return void 0;
2875
+ }
2876
+ }
2747
2877
  async function copyCarryPathsToBox(opts) {
2748
2878
  const log = opts.onLog ?? (() => {
2749
2879
  });
@@ -2759,7 +2889,12 @@ async function copyCarryPathsToBox(opts) {
2759
2889
  try {
2760
2890
  await copyOneEntry(opts.container, entry);
2761
2891
  copied += 1;
2762
- applied.push({ src: entry.absSrc, dest: entry.absDest, bytes: entry.bytes ?? 0 });
2892
+ applied.push({
2893
+ src: entry.absSrc,
2894
+ dest: entry.absDest,
2895
+ bytes: entry.bytes ?? 0,
2896
+ hash: await carrySourceHash(entry)
2897
+ });
2763
2898
  } catch (err) {
2764
2899
  const msg = err instanceof Error ? err.message : String(err);
2765
2900
  errors.push(`${where}: ${msg}`);
@@ -2774,13 +2909,13 @@ async function copyOneEntry(container, entry) {
2774
2909
  const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
2775
2910
  const boxDestParent = boxDest.endsWith("/") ? boxDest.slice(0, -1) : boxDest;
2776
2911
  const parentDir = entry.kind === "dir" ? boxDestParent : dirnameUnix(boxDestParent);
2777
- const mkdir8 = await execa22(
2912
+ const mkdir9 = await execa22(
2778
2913
  "docker",
2779
2914
  ["exec", "--user", "0:0", container, "mkdir", "-p", parentDir],
2780
2915
  { reject: false }
2781
2916
  );
2782
- if (mkdir8.exitCode !== 0) {
2783
- throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir8.stderr).slice(0, 300)}`);
2917
+ if (mkdir9.exitCode !== 0) {
2918
+ throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir9.stderr).slice(0, 300)}`);
2784
2919
  }
2785
2920
  if (entry.kind === "file") {
2786
2921
  const cp2 = await execa22(
@@ -2861,49 +2996,152 @@ function dirnameUnix(p) {
2861
2996
  if (i <= 0) return "/";
2862
2997
  return p.slice(0, i);
2863
2998
  }
2864
- var SHARED_CLAUDE_VOLUME = "agentbox-claude-config";
2865
- var DEFAULT_CLAUDE_SESSION = "claude";
2866
- var CONTAINER_CLAUDE_DIR = "/home/vscode/.claude";
2867
- var CONTAINER_USER = "vscode";
2868
- var CONTAINER_WORKSPACE = "/workspace";
2869
- var IN_BOX_SETUP_GUIDE_PATH = "/usr/local/share/agentbox/setup-guide.md";
2870
- var SETUP_SKILL_DST = "/dst/skills/agentbox-setup/SKILL.md";
2871
- function resolveClaudeVolume(opts) {
2872
- if (opts.isolate) {
2873
- return { volume: `${SHARED_CLAUDE_VOLUME}-${opts.boxId}` };
2999
+ var CREDENTIALS_BACKUP_FILE = join22(STATE_DIR, "claude-credentials.json");
3000
+ var CODEX_CREDENTIALS_BACKUP_FILE = join22(STATE_DIR, "codex-credentials.json");
3001
+ var OPENCODE_CREDENTIALS_BACKUP_FILE = join22(STATE_DIR, "opencode-credentials.json");
3002
+ function isRealAgentCredential(agent, text) {
3003
+ let parsed;
3004
+ try {
3005
+ parsed = JSON.parse(text);
3006
+ } catch {
3007
+ return false;
3008
+ }
3009
+ if (typeof parsed !== "object" || parsed === null) return false;
3010
+ if (agent === "claude") {
3011
+ const rt = parsed.claudeAiOauth?.refreshToken;
3012
+ return typeof rt === "string" && rt.length > 0;
3013
+ }
3014
+ return Object.keys(parsed).length > 0;
3015
+ }
3016
+ async function hostClaudeBackupExpired(path = CREDENTIALS_BACKUP_FILE, now = Date.now()) {
3017
+ try {
3018
+ const parsed = JSON.parse(await readFile24(path, "utf8"));
3019
+ const exp = parsed?.claudeAiOauth?.expiresAt;
3020
+ return typeof exp === "number" && Number.isFinite(exp) && exp < now;
3021
+ } catch {
3022
+ return false;
3023
+ }
3024
+ }
3025
+ function parseExtractResult(stdout) {
3026
+ return { copied: /\bCOPIED=yes\b/.test(stdout) };
3027
+ }
3028
+ async function extractVolumeAuthToBackup(opts) {
3029
+ try {
3030
+ await mkdir22(STATE_DIR, { recursive: true });
3031
+ const script = 'COPIED=no; if [ -s /dst/auth.json ]; then cp -a /dst/auth.json "/host-state/$DEST" && COPIED=yes; fi; echo "COPIED=$COPIED"';
3032
+ const { stdout } = await execa3("docker", [
3033
+ "run",
3034
+ "--rm",
3035
+ "--user",
3036
+ "0",
3037
+ "-v",
3038
+ `${opts.volume}:/dst`,
3039
+ "-v",
3040
+ `${STATE_DIR}:/host-state`,
3041
+ "-e",
3042
+ // Pass the destination filename via env so the path isn't interpolated
3043
+ // into the script string (keeps the docker arg list static + injection-safe).
3044
+ `DEST=${basename3(opts.backupFile)}`,
3045
+ opts.image,
3046
+ "sh",
3047
+ "-c",
3048
+ script
3049
+ ]);
3050
+ const result = parseExtractResult(stdout);
3051
+ if (result.copied) await chmod(opts.backupFile, 384).catch(() => {
3052
+ });
3053
+ return result;
3054
+ } catch {
3055
+ return { copied: false };
3056
+ }
3057
+ }
3058
+ function extractCodexCredentials(volume, image) {
3059
+ return extractVolumeAuthToBackup({ volume, image, backupFile: CODEX_CREDENTIALS_BACKUP_FILE });
3060
+ }
3061
+ function extractOpencodeCredentials(volume, image) {
3062
+ return extractVolumeAuthToBackup({ volume, image, backupFile: OPENCODE_CREDENTIALS_BACKUP_FILE });
3063
+ }
3064
+ async function hostBackupHasCredentials(path = CREDENTIALS_BACKUP_FILE) {
3065
+ try {
3066
+ const parsed = JSON.parse(await readFile24(path, "utf8"));
3067
+ const rt = parsed?.claudeAiOauth?.refreshToken;
3068
+ return typeof rt === "string" && rt.length > 0;
3069
+ } catch {
3070
+ return false;
3071
+ }
3072
+ }
3073
+ function parseSyncResult(stdout) {
3074
+ const volumeHasCredentials = /\bVOLREAL=yes\b/.test(stdout);
3075
+ if (/\bEXTRACTED=yes\b/.test(stdout)) return { direction: "extracted", volumeHasCredentials };
3076
+ if (/\bSEEDED=yes\b/.test(stdout)) return { direction: "seeded", volumeHasCredentials };
3077
+ return { direction: "noop", volumeHasCredentials };
3078
+ }
3079
+ var SYNC_SCRIPT = `
3080
+ EXTRACTED=no
3081
+ SEEDED=no
3082
+ VOL=/dst/.credentials.json
3083
+ HOST=/host-state/claude-credentials.json
3084
+ if [ -f "$VOL" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$VOL" >/dev/null 2>&1; then VOL_REAL=yes; else VOL_REAL=no; fi
3085
+ if [ -f "$HOST" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$HOST" >/dev/null 2>&1; then HOST_REAL=yes; else HOST_REAL=no; fi
3086
+ if [ "$VOL_REAL" = yes ] && [ "$ISOLATE" != yes ]; then
3087
+ cp -a "$VOL" "$HOST" && chmod 600 "$HOST" && EXTRACTED=yes
3088
+ elif [ "$VOL_REAL" = no ] && [ "$HOST_REAL" = yes ]; then
3089
+ cp -a "$HOST" "$VOL" && chown 1000:1000 "$VOL" && chmod 600 "$VOL" && SEEDED=yes && VOL_REAL=yes
3090
+ fi
3091
+ echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
3092
+ `;
3093
+ async function syncClaudeCredentials(spec, opts) {
3094
+ try {
3095
+ await mkdir22(STATE_DIR, { recursive: true });
3096
+ const { stdout } = await execa3("docker", [
3097
+ "run",
3098
+ "--rm",
3099
+ "--user",
3100
+ "0",
3101
+ "-v",
3102
+ `${spec.volume}:/dst`,
3103
+ "-v",
3104
+ `${STATE_DIR}:/host-state`,
3105
+ "-e",
3106
+ `ISOLATE=${opts.isolate ? "yes" : "no"}`,
3107
+ opts.image,
3108
+ "sh",
3109
+ "-c",
3110
+ SYNC_SCRIPT
3111
+ ]);
3112
+ const result = parseSyncResult(stdout);
3113
+ if (result.direction === "extracted") {
3114
+ await chmod(CREDENTIALS_BACKUP_FILE, 384).catch(() => {
3115
+ });
3116
+ }
3117
+ return result;
3118
+ } catch {
3119
+ return { direction: "noop", volumeHasCredentials: false };
2874
3120
  }
2875
- return { volume: SHARED_CLAUDE_VOLUME };
2876
3121
  }
3122
+ var CLOUD_WORKSPACE = "/workspace";
2877
3123
  async function pathExists(p) {
2878
3124
  try {
2879
- await stat3(p);
3125
+ await stat22(p);
2880
3126
  return true;
2881
3127
  } catch {
2882
3128
  return false;
2883
3129
  }
2884
3130
  }
2885
- async function volumeHasClaudeJson(volume, image) {
2886
- const res = await execa3(
2887
- "docker",
2888
- ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/_claude.json"],
2889
- { reject: false }
2890
- );
2891
- return res.exitCode === 0;
2892
- }
2893
3131
  async function findBrokenSymlinks(root) {
2894
3132
  const broken = [];
2895
3133
  async function walk(dir) {
2896
3134
  let entries;
2897
3135
  try {
2898
- entries = await readdir3(dir, { withFileTypes: true });
3136
+ entries = await readdir22(dir, { withFileTypes: true });
2899
3137
  } catch {
2900
3138
  return;
2901
3139
  }
2902
3140
  for (const ent of entries) {
2903
- const full = join22(dir, ent.name);
3141
+ const full = join32(dir, ent.name);
2904
3142
  if (ent.isSymbolicLink()) {
2905
3143
  try {
2906
- await stat3(full);
3144
+ await stat22(full);
2907
3145
  } catch {
2908
3146
  broken.push(relative(root, full));
2909
3147
  }
@@ -2915,52 +3153,434 @@ async function findBrokenSymlinks(root) {
2915
3153
  await walk(root);
2916
3154
  return broken;
2917
3155
  }
2918
- async function ensureClaudeVolume(spec, opts) {
2919
- const existed = await volumeExists(spec.volume);
2920
- await ensureVolume(spec.volume);
2921
- const created = !existed;
2922
- if (!opts.syncFromHost) return { created, synced: false };
2923
- const hostClaude = join22(homedir2(), ".claude");
2924
- if (!await pathExists(hostClaude)) return { created, synced: false };
2925
- const hostClaudeJson = join22(homedir2(), ".claude.json");
2926
- const hasJson = await pathExists(hostClaudeJson);
2927
- const seedClaudeJson = !await volumeHasClaudeJson(spec.volume, opts.image);
2928
- const hostHome = homedir2();
2929
- const hostAgents = join22(homedir2(), ".agents");
2930
- const hasAgents = await pathExists(hostAgents);
2931
- const args = [
2932
- "run",
2933
- "--rm",
2934
- "--user",
2935
- "0",
2936
- // HOST_HOME used inside the shell script to rewrite host-absolute
2937
- // installPath values in plugins/installed_plugins.json.
2938
- "-e",
2939
- `HOST_HOME=${hostHome}`,
2940
- "-v",
2941
- `${spec.volume}:/dst`,
2942
- "-v",
2943
- `${hostClaude}:/src-claude:ro`
2944
- ];
2945
- if (hasJson && seedClaudeJson) args.push("-v", `${hostClaudeJson}:/src-claude-json:ro`);
2946
- if (hasAgents) args.push("-v", `${hostAgents}:/.agents:ro`);
2947
- const filterDir = await mkdtemp(join22(tmpdir(), "agentbox-claude-filter-"));
2948
- let filteredHookCount = 0;
2949
- let installMethodFixed = false;
2950
- let aliasedProjectKey = false;
2951
- let workspaceTrusted = false;
2952
- try {
2953
- const settingsResult = await maybeFilterTo(
2954
- join22(hostClaude, "settings.json"),
2955
- join22(filterDir, "settings.json"),
2956
- hostHome
2957
- );
2958
- filteredHookCount += settingsResult.removedHooks;
2959
- if (!seedClaudeJson) {
2960
- } else if (hasJson) {
2961
- const jsonResult = await maybeFilterTo(
3156
+ async function mkStageDir(prefix) {
3157
+ return mkdtemp(join32(tmpdir(), `agentbox-${prefix}-stage-`));
3158
+ }
3159
+ function emptyResult(warnings = []) {
3160
+ return { tarballPath: null, cleanup: async () => {
3161
+ }, warnings };
3162
+ }
3163
+ async function tarballFromDir(stageDir, agent) {
3164
+ const tarballPath = join32(tmpdir(), `agentbox-${agent}-${basename2(stageDir)}.tar.gz`);
3165
+ await execa4("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
3166
+ env: { ...process.env, COPYFILE_DISABLE: "1" }
3167
+ });
3168
+ return tarballPath;
3169
+ }
3170
+ function makeCleanup(paths) {
3171
+ return async () => {
3172
+ for (const p of paths) {
3173
+ await rm3(p, { recursive: true, force: true });
3174
+ }
3175
+ };
3176
+ }
3177
+ async function stageSingleFileTarball(agent, sourcePath, tarballEntryName) {
3178
+ const stageDir = await mkStageDir(agent);
3179
+ let tarballPath = null;
3180
+ try {
3181
+ await copyFile(sourcePath, join32(stageDir, tarballEntryName));
3182
+ tarballPath = await tarballFromDir(stageDir, agent);
3183
+ return {
3184
+ tarballPath,
3185
+ cleanup: makeCleanup([stageDir, tarballPath]),
3186
+ warnings: []
3187
+ };
3188
+ } catch (err) {
3189
+ await rm3(stageDir, { recursive: true, force: true });
3190
+ if (tarballPath) await rm3(tarballPath, { force: true });
3191
+ throw err;
3192
+ }
3193
+ }
3194
+ var CLAUDE_RUNTIME_EXCLUDES = [
3195
+ "projects",
3196
+ // workflows/ are seeded per-box at create time (incremental, like memory),
3197
+ // not frozen into the prepare-time snapshot — see seedDynamicConfig.
3198
+ "workflows",
3199
+ "sessions",
3200
+ "history.jsonl",
3201
+ "file-history",
3202
+ "shell-snapshots",
3203
+ "backups",
3204
+ "session-env",
3205
+ "paste-cache",
3206
+ "cache",
3207
+ "telemetry",
3208
+ "tasks",
3209
+ "downloads",
3210
+ "chrome",
3211
+ "ide",
3212
+ "debug",
3213
+ "mcp-needs-auth-cache.json",
3214
+ "stats-cache.json"
3215
+ ];
3216
+ function encodeClaudeProjectsKey(absPath) {
3217
+ return absPath.replace(/[^a-zA-Z0-9]/g, "-");
3218
+ }
3219
+ var BOX_CLAUDE_PROJECT_DIR = "/home/vscode/.claude/projects/-workspace";
3220
+ async function resolveClaudeMemoryDir(hostWorkspace, hostHome = homedir22()) {
3221
+ if (hostWorkspace.length === 0) return null;
3222
+ const memDir = join32(
3223
+ hostHome,
3224
+ ".claude",
3225
+ "projects",
3226
+ encodeClaudeProjectsKey(hostWorkspace),
3227
+ "memory"
3228
+ );
3229
+ if (!await pathExists(memDir)) return null;
3230
+ try {
3231
+ const entries = await readdir22(memDir);
3232
+ if (entries.length === 0) return null;
3233
+ } catch {
3234
+ return null;
3235
+ }
3236
+ return memDir;
3237
+ }
3238
+ async function stageClaudeStaticForUpload(opts = {}) {
3239
+ const hostHome = opts.hostHome ?? homedir22();
3240
+ const hostClaude = join32(hostHome, ".claude");
3241
+ if (!await pathExists(hostClaude)) return emptyResult();
3242
+ const stageDir = await mkStageDir("claude-static");
3243
+ let tarballPath = null;
3244
+ try {
3245
+ const broken = await findBrokenSymlinks(hostClaude);
3246
+ const excludes = [
3247
+ "--exclude=node_modules",
3248
+ "--exclude=.credentials.json",
3249
+ ...CLAUDE_RUNTIME_EXCLUDES.map((p) => `--exclude=${p}`),
3250
+ ...broken.map((r) => `--exclude=/${r}`)
3251
+ ];
3252
+ await execa4("rsync", [
3253
+ "-a",
3254
+ "--copy-unsafe-links",
3255
+ ...excludes,
3256
+ `${hostClaude}/`,
3257
+ `${stageDir}/`
3258
+ ]);
3259
+ const settingsPath = join32(stageDir, "settings.json");
3260
+ if (await pathExists(settingsPath)) {
3261
+ try {
3262
+ const parsed = JSON.parse(await readFile32(settingsPath, "utf8"));
3263
+ const filtered = filterHostHooks(parsed, hostHome);
3264
+ if (filtered.removedCommands.length > 0) {
3265
+ await writeFile3(settingsPath, JSON.stringify(filtered.data, null, 2));
3266
+ }
3267
+ } catch {
3268
+ }
3269
+ }
3270
+ const hostClaudeJson = join32(hostHome, ".claude.json");
3271
+ let working;
3272
+ if (await pathExists(hostClaudeJson)) {
3273
+ try {
3274
+ working = JSON.parse(await readFile32(hostClaudeJson, "utf8"));
3275
+ } catch {
3276
+ working = null;
3277
+ }
3278
+ }
3279
+ if (working === void 0 || working === null) {
3280
+ working = {
3281
+ installMethod: "native",
3282
+ autoUpdates: false,
3283
+ autoUpdatesProtectedForNative: true,
3284
+ projects: { [CLOUD_WORKSPACE]: { hasTrustDialogAccepted: true } }
3285
+ };
3286
+ } else {
3287
+ working = filterHostHooks(working, hostHome).data;
3288
+ working = setInstallMethodNative(working).data;
3289
+ if (opts.hostWorkspace) {
3290
+ working = addProjectAlias(working, opts.hostWorkspace, CLOUD_WORKSPACE).data;
3291
+ }
3292
+ working = trustWorkspace(working, CLOUD_WORKSPACE).data;
3293
+ }
3294
+ await writeFile3(join32(stageDir, "_claude.json"), JSON.stringify(working, null, 2));
3295
+ const pluginsDir = join32(stageDir, "plugins");
3296
+ if (await pathExists(pluginsDir)) {
3297
+ try {
3298
+ const entries = await readdir22(pluginsDir, { withFileTypes: true });
3299
+ for (const ent of entries) {
3300
+ if (!ent.isFile() || !ent.name.endsWith(".json")) continue;
3301
+ const file = join32(pluginsDir, ent.name);
3302
+ const raw = await readFile32(file, "utf8");
3303
+ const replaced = raw.split(`${hostHome}/.claude/plugins/`).join("/home/vscode/.claude/plugins/");
3304
+ if (replaced !== raw) await writeFile3(file, replaced);
3305
+ }
3306
+ } catch {
3307
+ }
3308
+ }
3309
+ tarballPath = await tarballFromDir(stageDir, "claude-static");
3310
+ return {
3311
+ tarballPath,
3312
+ cleanup: makeCleanup([stageDir, tarballPath]),
3313
+ warnings: []
3314
+ };
3315
+ } catch (err) {
3316
+ await rm3(stageDir, { recursive: true, force: true });
3317
+ if (tarballPath) await rm3(tarballPath, { force: true });
3318
+ throw err;
3319
+ }
3320
+ }
3321
+ async function stageClaudeCredentialsForUpload() {
3322
+ if (!await pathExists(CREDENTIALS_BACKUP_FILE)) return emptyResult();
3323
+ return stageSingleFileTarball("claude-creds", CREDENTIALS_BACKUP_FILE, ".credentials.json");
3324
+ }
3325
+ var CODEX_RSYNC_EXCLUDES = [
3326
+ "--exclude=sessions",
3327
+ "--exclude=log",
3328
+ "--exclude=history.jsonl",
3329
+ "--exclude=hooks.json",
3330
+ "--exclude=logs_2.sqlite",
3331
+ "--exclude=logs_2.sqlite-shm",
3332
+ "--exclude=logs_2.sqlite-wal",
3333
+ "--exclude=state_5.sqlite",
3334
+ "--exclude=state_5.sqlite-shm",
3335
+ "--exclude=state_5.sqlite-wal",
3336
+ "--exclude=sqlite",
3337
+ "--exclude=cache",
3338
+ "--exclude=vendor_imports",
3339
+ "--exclude=tmp",
3340
+ // .tmp holds codex plugin sync state — ~100 MB of marketplace cache. Not
3341
+ // the same as `tmp/`; both can exist side by side on a long-running host.
3342
+ "--exclude=.tmp",
3343
+ "--exclude=.codex-global-state.json",
3344
+ "--exclude=.codex-global-state.json.bak",
3345
+ "--exclude=.personality_migration",
3346
+ "--exclude=shell_snapshots",
3347
+ "--exclude=session_index.jsonl",
3348
+ "--exclude=models_cache.json",
3349
+ "--exclude=installation_id",
3350
+ "--exclude=version.json"
3351
+ ];
3352
+ var CODEX_KEYCHAIN_WARNING = 'codex: ~/.codex/auth.json missing. On macOS the codex CLI defaults to storing the OAuth token in the system Keychain, which isn\'t reachable from a remote sandbox. To share creds with cloud boxes either:\n - add `cli_auth_credentials_store = "file"` to ~/.codex/config.toml then re-run `codex login`, or\n - set OPENAI_API_KEY in your environment, or\n - run `codex login --with-api-key` for a file-backed login.\nSkipping codex seed; in-box codex will prompt for sign-in.';
3353
+ async function stageCodexStaticForUpload(opts = {}) {
3354
+ const hostHome = opts.hostHome ?? homedir22();
3355
+ const hostCodex = join32(hostHome, ".codex");
3356
+ if (!await pathExists(hostCodex)) return emptyResult();
3357
+ const stageDir = await mkStageDir("codex-static");
3358
+ let tarballPath = null;
3359
+ try {
3360
+ const codexBroken = await findBrokenSymlinks(hostCodex);
3361
+ await execa4("rsync", [
3362
+ "-a",
3363
+ "-L",
3364
+ ...codexBroken.map((r) => `--exclude=/${r}`),
3365
+ "--exclude=auth.json",
3366
+ ...CODEX_RSYNC_EXCLUDES,
3367
+ `${hostCodex}/`,
3368
+ `${stageDir}/`
3369
+ ]);
3370
+ tarballPath = await tarballFromDir(stageDir, "codex-static");
3371
+ return {
3372
+ tarballPath,
3373
+ cleanup: makeCleanup([stageDir, tarballPath]),
3374
+ warnings: []
3375
+ };
3376
+ } catch (err) {
3377
+ await rm3(stageDir, { recursive: true, force: true });
3378
+ if (tarballPath) await rm3(tarballPath, { force: true });
3379
+ throw err;
3380
+ }
3381
+ }
3382
+ async function stageCodexCredentialsForUpload(opts = {}) {
3383
+ const hostHome = opts.hostHome ?? homedir22();
3384
+ const cloudBackup = join32(hostHome, ".agentbox", "codex-credentials.json");
3385
+ if (await pathExists(cloudBackup)) {
3386
+ return stageSingleFileTarball("codex-creds", cloudBackup, "auth.json");
3387
+ }
3388
+ const hostAuth = join32(hostHome, ".codex", "auth.json");
3389
+ if (!await pathExists(hostAuth)) return emptyResult([CODEX_KEYCHAIN_WARNING]);
3390
+ return stageSingleFileTarball("codex-creds", hostAuth, "auth.json");
3391
+ }
3392
+ var OPENCODE_DATA_EXCLUDES = [
3393
+ "--exclude=storage",
3394
+ "--exclude=log",
3395
+ "--exclude=project",
3396
+ "--exclude=cache",
3397
+ "--exclude=bin",
3398
+ "--exclude=repos",
3399
+ "--exclude=snapshot",
3400
+ "--exclude=config",
3401
+ "--exclude=opencode.db",
3402
+ "--exclude=opencode.db-shm",
3403
+ "--exclude=opencode.db-wal"
3404
+ ];
3405
+ async function stageOpencodeStaticForUpload(opts = {}) {
3406
+ const hostHome = opts.hostHome ?? homedir22();
3407
+ const hostData = join32(hostHome, ".local", "share", "opencode");
3408
+ const hostConfig = join32(hostHome, ".config", "opencode");
3409
+ const hasData = await pathExists(hostData);
3410
+ const hasConfig = await pathExists(hostConfig);
3411
+ if (!hasData && !hasConfig) return emptyResult();
3412
+ const stageDir = await mkStageDir("opencode-static");
3413
+ let tarballPath = null;
3414
+ try {
3415
+ if (hasData) {
3416
+ const dataBroken = await findBrokenSymlinks(hostData);
3417
+ await execa4("rsync", [
3418
+ "-a",
3419
+ "-L",
3420
+ ...dataBroken.map((r) => `--exclude=/${r}`),
3421
+ "--exclude=auth.json",
3422
+ ...OPENCODE_DATA_EXCLUDES,
3423
+ `${hostData}/`,
3424
+ `${stageDir}/`
3425
+ ]);
3426
+ }
3427
+ if (hasConfig) {
3428
+ const configStage = join32(stageDir, "config");
3429
+ const cfgBroken = await findBrokenSymlinks(hostConfig);
3430
+ await execa4("rsync", [
3431
+ "-a",
3432
+ "-L",
3433
+ ...cfgBroken.map((r) => `--exclude=/${r}`),
3434
+ `${hostConfig}/`,
3435
+ `${configStage}/`
3436
+ ]);
3437
+ }
3438
+ tarballPath = await tarballFromDir(stageDir, "opencode-static");
3439
+ return {
3440
+ tarballPath,
3441
+ cleanup: makeCleanup([stageDir, tarballPath]),
3442
+ warnings: []
3443
+ };
3444
+ } catch (err) {
3445
+ await rm3(stageDir, { recursive: true, force: true });
3446
+ if (tarballPath) await rm3(tarballPath, { force: true });
3447
+ throw err;
3448
+ }
3449
+ }
3450
+ async function stageOpencodeCredentialsForUpload(opts = {}) {
3451
+ const hostHome = opts.hostHome ?? homedir22();
3452
+ const cloudBackup = join32(hostHome, ".agentbox", "opencode-credentials.json");
3453
+ if (await pathExists(cloudBackup)) {
3454
+ return stageSingleFileTarball("opencode-creds", cloudBackup, "auth.json");
3455
+ }
3456
+ const hostAuth = join32(hostHome, ".local", "share", "opencode", "auth.json");
3457
+ if (!await pathExists(hostAuth)) return emptyResult();
3458
+ return stageSingleFileTarball("opencode-creds", hostAuth, "auth.json");
3459
+ }
3460
+ async function stageOpencodeStateForUpload(opts = {}) {
3461
+ const hostHome = opts.hostHome ?? homedir22();
3462
+ const hostModel = join32(hostHome, ".local", "state", "opencode", "model.json");
3463
+ if (!await pathExists(hostModel)) return emptyResult();
3464
+ return stageSingleFileTarball("opencode-state", hostModel, "model.json");
3465
+ }
3466
+ var SHARED_CLAUDE_VOLUME = "agentbox-claude-config";
3467
+ var DEFAULT_CLAUDE_SESSION = "claude";
3468
+ var CONTAINER_CLAUDE_DIR = "/home/vscode/.claude";
3469
+ var CONTAINER_USER = "vscode";
3470
+ var CONTAINER_WORKSPACE = "/workspace";
3471
+ var IN_BOX_SETUP_GUIDE_PATH = "/usr/local/share/agentbox/setup-guide.md";
3472
+ var SETUP_SKILL_DST = "/dst/skills/agentbox-setup/SKILL.md";
3473
+ function resolveClaudeVolume(opts) {
3474
+ if (opts.isolate) {
3475
+ return { volume: `${SHARED_CLAUDE_VOLUME}-${opts.boxId}` };
3476
+ }
3477
+ return { volume: SHARED_CLAUDE_VOLUME };
3478
+ }
3479
+ async function pathExists2(p) {
3480
+ try {
3481
+ await stat3(p);
3482
+ return true;
3483
+ } catch {
3484
+ return false;
3485
+ }
3486
+ }
3487
+ async function volumeHasClaudeJson(volume, image) {
3488
+ const res = await execa5(
3489
+ "docker",
3490
+ ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/_claude.json"],
3491
+ { reject: false }
3492
+ );
3493
+ return res.exitCode === 0;
3494
+ }
3495
+ function isUnder(parent, child) {
3496
+ const rel = relative2(parent, child);
3497
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
3498
+ }
3499
+ async function findUnsyncableSymlinks(root, reachableRoots) {
3500
+ const reachable = await Promise.all(
3501
+ reachableRoots.map(async (r) => {
3502
+ try {
3503
+ return await realpath2(r);
3504
+ } catch {
3505
+ return r;
3506
+ }
3507
+ })
3508
+ );
3509
+ const unsyncable = [];
3510
+ async function walk(dir) {
3511
+ let entries;
3512
+ try {
3513
+ entries = await readdir3(dir, { withFileTypes: true });
3514
+ } catch {
3515
+ return;
3516
+ }
3517
+ for (const ent of entries) {
3518
+ const full = join4(dir, ent.name);
3519
+ if (ent.isSymbolicLink()) {
3520
+ let real;
3521
+ try {
3522
+ real = await realpath2(full);
3523
+ } catch {
3524
+ unsyncable.push(relative2(root, full));
3525
+ continue;
3526
+ }
3527
+ if (!reachable.some((r) => isUnder(r, real))) {
3528
+ unsyncable.push(relative2(root, full));
3529
+ }
3530
+ } else if (ent.isDirectory()) {
3531
+ await walk(full);
3532
+ }
3533
+ }
3534
+ }
3535
+ await walk(root);
3536
+ return unsyncable;
3537
+ }
3538
+ async function ensureClaudeVolume(spec, opts) {
3539
+ const existed = await volumeExists(spec.volume);
3540
+ await ensureVolume(spec.volume);
3541
+ const created = !existed;
3542
+ if (!opts.syncFromHost) return { created, synced: false };
3543
+ const hostClaude = join4(homedir3(), ".claude");
3544
+ if (!await pathExists2(hostClaude)) return { created, synced: false };
3545
+ const hostClaudeJson = join4(homedir3(), ".claude.json");
3546
+ const hasJson = await pathExists2(hostClaudeJson);
3547
+ const seedClaudeJson = !await volumeHasClaudeJson(spec.volume, opts.image);
3548
+ const hostHome = homedir3();
3549
+ const hostAgents = join4(homedir3(), ".agents");
3550
+ const hasAgents = await pathExists2(hostAgents);
3551
+ const args = [
3552
+ "run",
3553
+ "--rm",
3554
+ "--user",
3555
+ "0",
3556
+ // HOST_HOME used inside the shell script to rewrite host-absolute
3557
+ // installPath values in plugins/installed_plugins.json.
3558
+ "-e",
3559
+ `HOST_HOME=${hostHome}`,
3560
+ "-v",
3561
+ `${spec.volume}:/dst`,
3562
+ "-v",
3563
+ `${hostClaude}:/src-claude:ro`
3564
+ ];
3565
+ if (hasJson && seedClaudeJson) args.push("-v", `${hostClaudeJson}:/src-claude-json:ro`);
3566
+ if (hasAgents) args.push("-v", `${hostAgents}:/.agents:ro`);
3567
+ const filterDir = await mkdtemp2(join4(tmpdir2(), "agentbox-claude-filter-"));
3568
+ let filteredHookCount = 0;
3569
+ let installMethodFixed = false;
3570
+ let aliasedProjectKey = false;
3571
+ let workspaceTrusted = false;
3572
+ try {
3573
+ const settingsResult = await maybeFilterTo(
3574
+ join4(hostClaude, "settings.json"),
3575
+ join4(filterDir, "settings.json"),
3576
+ hostHome
3577
+ );
3578
+ filteredHookCount += settingsResult.removedHooks;
3579
+ if (!seedClaudeJson) {
3580
+ } else if (hasJson) {
3581
+ const jsonResult = await maybeFilterTo(
2962
3582
  hostClaudeJson,
2963
- join22(filterDir, "_claude.json"),
3583
+ join4(filterDir, "_claude.json"),
2964
3584
  hostHome,
2965
3585
  {
2966
3586
  setInstallMethodNative: true,
@@ -2973,8 +3593,8 @@ async function ensureClaudeVolume(spec, opts) {
2973
3593
  aliasedProjectKey = jsonResult.aliasedProjectKey;
2974
3594
  workspaceTrusted = jsonResult.workspaceTrusted;
2975
3595
  } else {
2976
- await writeFile3(
2977
- join22(filterDir, "_claude.json"),
3596
+ await writeFile22(
3597
+ join4(filterDir, "_claude.json"),
2978
3598
  JSON.stringify(
2979
3599
  {
2980
3600
  installMethod: "native",
@@ -2992,10 +3612,13 @@ async function ensureClaudeVolume(spec, opts) {
2992
3612
  if (filteredHookCount > 0 || installMethodFixed || aliasedProjectKey || workspaceTrusted) {
2993
3613
  args.push("-v", `${filterDir}:/src-filter:ro`);
2994
3614
  }
2995
- const brokenSymlinks = await findBrokenSymlinks(hostClaude);
2996
- const rsyncExcludes = ["--exclude=node_modules"];
3615
+ const reachableRoots = hasAgents ? [hostClaude, hostAgents] : [hostClaude];
3616
+ const brokenSymlinks = await findUnsyncableSymlinks(hostClaude, reachableRoots);
3617
+ const rsyncExcludes = ["--exclude=node_modules", "--exclude=/projects"];
2997
3618
  for (const rel of brokenSymlinks) rsyncExcludes.push(`--exclude=/${rel}`);
2998
3619
  const rsyncFlags = `-a --copy-unsafe-links ${rsyncExcludes.join(" ")}`;
3620
+ const memoryKey = opts.hostWorkspace ? encodeClaudeProjectsKey(opts.hostWorkspace) : null;
3621
+ const memoryRekeyStep = memoryKey ? ` && { [ -d "/src-claude/projects/${memoryKey}/memory" ] && mkdir -p /dst/projects/-workspace && rm -rf /dst/projects/-workspace/memory && cp -a "/src-claude/projects/${memoryKey}/memory" /dst/projects/-workspace/memory; true; }` : "";
2999
3622
  args.push(
3000
3623
  opts.image,
3001
3624
  "sh",
@@ -3030,9 +3653,9 @@ async function ensureClaudeVolume(spec, opts) {
3030
3653
  // remove them). The `.agentbox-cleaned-nm-v1` sentinel makes the wipe
3031
3654
  // a no-op after the first run; rebuildPluginNativeDeps repopulates
3032
3655
  // linux/amd64 node_modules on the next `agentbox claude`.
3033
- `{ [ ! -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; } && chown -R 1000:1000 /dst`
3656
+ `{ [ ! -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"
3034
3657
  );
3035
- await execa3("docker", args);
3658
+ await execa5("docker", args);
3036
3659
  } finally {
3037
3660
  await rm2(filterDir, { recursive: true, force: true });
3038
3661
  }
@@ -3047,7 +3670,7 @@ async function ensureClaudeVolume(spec, opts) {
3047
3670
  }
3048
3671
  async function seedSetupSkillIntoVolume(volume, image) {
3049
3672
  try {
3050
- const { stdout } = await execa3("docker", [
3673
+ const { stdout } = await execa5("docker", [
3051
3674
  "run",
3052
3675
  "--rm",
3053
3676
  "--user",
@@ -3076,7 +3699,7 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
3076
3699
  };
3077
3700
  let parsed;
3078
3701
  try {
3079
- parsed = JSON.parse(await readFile24(src, "utf8"));
3702
+ parsed = JSON.parse(await readFile4(src, "utf8"));
3080
3703
  } catch {
3081
3704
  return zero;
3082
3705
  }
@@ -3103,7 +3726,7 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
3103
3726
  if (filtered.removedCommands.length === 0 && !installFixed && !aliased && !trusted) {
3104
3727
  return zero;
3105
3728
  }
3106
- await writeFile3(dest, JSON.stringify(working, null, 2));
3729
+ await writeFile22(dest, JSON.stringify(working, null, 2));
3107
3730
  return {
3108
3731
  removedHooks: filtered.removedCommands.length,
3109
3732
  installMethodFixed: installFixed,
@@ -3159,7 +3782,7 @@ async function isDir(p) {
3159
3782
  }
3160
3783
  async function readReferencedPluginKeys(installedPluginsJsonPath) {
3161
3784
  try {
3162
- const raw = await readFile24(installedPluginsJsonPath, "utf8");
3785
+ const raw = await readFile4(installedPluginsJsonPath, "utf8");
3163
3786
  return referencedPluginVersionKeys(JSON.parse(raw));
3164
3787
  } catch {
3165
3788
  return /* @__PURE__ */ new Set();
@@ -3167,7 +3790,7 @@ async function readReferencedPluginKeys(installedPluginsJsonPath) {
3167
3790
  }
3168
3791
  async function scanPluginCacheForRebuild(cacheRoot) {
3169
3792
  const referenced = await readReferencedPluginKeys(
3170
- join22(cacheRoot, "..", "installed_plugins.json")
3793
+ join4(cacheRoot, "..", "installed_plugins.json")
3171
3794
  );
3172
3795
  let marketplaces;
3173
3796
  try {
@@ -3177,7 +3800,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
3177
3800
  }
3178
3801
  for (const m of marketplaces) {
3179
3802
  if (!m.isDirectory()) continue;
3180
- const mPath = join22(cacheRoot, m.name);
3803
+ const mPath = join4(cacheRoot, m.name);
3181
3804
  let plugins;
3182
3805
  try {
3183
3806
  plugins = await readdir3(mPath, { withFileTypes: true });
@@ -3186,7 +3809,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
3186
3809
  }
3187
3810
  for (const p of plugins) {
3188
3811
  if (!p.isDirectory()) continue;
3189
- const pPath = join22(mPath, p.name);
3812
+ const pPath = join4(mPath, p.name);
3190
3813
  let versions;
3191
3814
  try {
3192
3815
  versions = await readdir3(pPath, { withFileTypes: true });
@@ -3196,10 +3819,10 @@ async function scanPluginCacheForRebuild(cacheRoot) {
3196
3819
  for (const v of versions) {
3197
3820
  if (!v.isDirectory()) continue;
3198
3821
  if (referenced.size > 0 && !referenced.has(`${m.name}/${p.name}/${v.name}`)) continue;
3199
- const vPath = join22(pPath, v.name);
3200
- if (!await isFile(join22(vPath, "package.json"))) continue;
3201
- if (await isFile(join22(vPath, PLUGIN_INSTALLED_MARKER))) continue;
3202
- if (await isRecentFailMarker(join22(vPath, PLUGIN_FAILED_MARKER))) continue;
3822
+ const vPath = join4(pPath, v.name);
3823
+ if (!await isFile(join4(vPath, "package.json"))) continue;
3824
+ if (await isFile(join4(vPath, PLUGIN_INSTALLED_MARKER))) continue;
3825
+ if (await isRecentFailMarker(join4(vPath, PLUGIN_FAILED_MARKER))) continue;
3203
3826
  return true;
3204
3827
  }
3205
3828
  }
@@ -3212,7 +3835,7 @@ async function resolveClaudeCacheLiveOnHost(volume) {
3212
3835
  return orbstackVolumePath(volume, "plugins", "cache");
3213
3836
  }
3214
3837
  async function readBoxReferencedPluginKeys(container) {
3215
- const res = await execa3(
3838
+ const res = await execa5(
3216
3839
  "docker",
3217
3840
  [
3218
3841
  "exec",
@@ -3330,7 +3953,7 @@ while IFS= read -r dir; do
3330
3953
  done < "$WORK/dirs"
3331
3954
  rm -rf "$WORK"
3332
3955
  `;
3333
- const result = await execa3(
3956
+ const result = await execa5(
3334
3957
  "docker",
3335
3958
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", script],
3336
3959
  { reject: false }
@@ -3391,7 +4014,7 @@ async function startClaudeSession(opts) {
3391
4014
  const v = process.env[k];
3392
4015
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
3393
4016
  }
3394
- const result = await execa3(
4017
+ const result = await execa5(
3395
4018
  "docker",
3396
4019
  [
3397
4020
  "exec",
@@ -3482,7 +4105,7 @@ function buildDashboardAttachArgv(container, sessionName) {
3482
4105
  async function waitForTmuxPaneContent(container, sessionName, timeoutMs = 2e4) {
3483
4106
  const deadline = Date.now() + timeoutMs;
3484
4107
  while (Date.now() < deadline) {
3485
- const res = await execa3(
4108
+ const res = await execa5(
3486
4109
  "docker",
3487
4110
  ["exec", "--user", CONTAINER_USER, container, "tmux", "capture-pane", "-p", "-t", sessionName],
3488
4111
  { reject: false }
@@ -3550,7 +4173,7 @@ async function warmUpClaudeCredentials(volume, image, opts = {}) {
3550
4173
  const SLEEP_MS = 5e3;
3551
4174
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
3552
4175
  opts.onProgress?.(`checking credentials... ${attempt}/${MAX_ATTEMPTS}`);
3553
- const res = await execa3(
4176
+ const res = await execa5(
3554
4177
  "docker",
3555
4178
  [
3556
4179
  "run",
@@ -3592,7 +4215,7 @@ function attachClaudeSession(container, sessionName, reattachRef) {
3592
4215
  }
3593
4216
  async function claudeSessionInfo(container, sessionName) {
3594
4217
  const name = sessionName ?? DEFAULT_CLAUDE_SESSION;
3595
- const has = await execa3(
4218
+ const has = await execa5(
3596
4219
  "docker",
3597
4220
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
3598
4221
  { reject: false }
@@ -3600,7 +4223,7 @@ async function claudeSessionInfo(container, sessionName) {
3600
4223
  if (has.exitCode !== 0) {
3601
4224
  return { running: false, sessionName: name, startedAt: null };
3602
4225
  }
3603
- const ts = await execa3(
4226
+ const ts = await execa5(
3604
4227
  "docker",
3605
4228
  [
3606
4229
  "exec",
@@ -3634,14 +4257,14 @@ async function listChildDirs(dir) {
3634
4257
  }
3635
4258
  async function readJsonFile(path) {
3636
4259
  try {
3637
- return JSON.parse(await readFile24(path, "utf8"));
4260
+ return JSON.parse(await readFile4(path, "utf8"));
3638
4261
  } catch {
3639
4262
  return void 0;
3640
4263
  }
3641
4264
  }
3642
4265
  async function pullClaudeExtras(spec, opts) {
3643
- const hostHome = homedir2();
3644
- const hostClaude = join22(hostHome, ".claude");
4266
+ const hostHome = homedir3();
4267
+ const hostClaude = join4(hostHome, ".claude");
3645
4268
  const inventoryScript = [
3646
4269
  "for cat in skills agents commands; do",
3647
4270
  ' [ -d "/src/$cat" ] || continue;',
@@ -3666,7 +4289,7 @@ async function pullClaudeExtras(spec, opts) {
3666
4289
  ' printf "\\n";',
3667
4290
  "done"
3668
4291
  ].join(" ");
3669
- const inv = await execa3(
4292
+ const inv = await execa5(
3670
4293
  "docker",
3671
4294
  ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/src:ro`, opts.image, "sh", "-c", inventoryScript],
3672
4295
  { reject: false }
@@ -3703,7 +4326,7 @@ async function pullClaudeExtras(spec, opts) {
3703
4326
  const newItems = [];
3704
4327
  const applyPaths = [];
3705
4328
  for (const cat of PULL_DIR_CATEGORIES) {
3706
- const hostNames = await listChildDirs(join22(hostClaude, cat));
4329
+ const hostNames = await listChildDirs(join4(hostClaude, cat));
3707
4330
  const excludes = cat === "skills" ? SKILL_EXCLUDE_PREFIXES : [];
3708
4331
  for (const name of pickNewItems(boxDirs[cat] ?? [], hostNames, excludes)) {
3709
4332
  newItems.push({ category: cat, name });
@@ -3711,8 +4334,8 @@ async function pullClaudeExtras(spec, opts) {
3711
4334
  }
3712
4335
  }
3713
4336
  const hostPluginKeys = [];
3714
- for (const m of await listChildDirs(join22(hostClaude, "plugins", "cache"))) {
3715
- for (const p of await listChildDirs(join22(hostClaude, "plugins", "cache", m))) {
4337
+ for (const m of await listChildDirs(join4(hostClaude, "plugins", "cache"))) {
4338
+ for (const p of await listChildDirs(join4(hostClaude, "plugins", "cache", m))) {
3716
4339
  hostPluginKeys.push(`${m}/${p}`);
3717
4340
  }
3718
4341
  }
@@ -3720,190 +4343,67 @@ async function pullClaudeExtras(spec, opts) {
3720
4343
  newItems.push({ category: "plugins", name: key });
3721
4344
  applyPaths.push({ src: `/src/plugins/cache/${key}`, dest: `/dst/plugins/cache/${key}` });
3722
4345
  }
3723
- const hostInstalled = await readJsonFile(join22(hostClaude, "plugins", "installed_plugins.json"));
3724
- const hostMarkets = await readJsonFile(join22(hostClaude, "plugins", "known_marketplaces.json"));
4346
+ const hostInstalled = await readJsonFile(join4(hostClaude, "plugins", "installed_plugins.json"));
4347
+ const hostMarkets = await readJsonFile(join4(hostClaude, "plugins", "known_marketplaces.json"));
3725
4348
  const mergedInstalled = mergeInstalledPlugins(hostInstalled, boxJson["installed_plugins"], {
3726
4349
  hostHome
3727
4350
  });
3728
- const mergedMarkets = mergeKnownMarketplaces(hostMarkets, boxJson["known_marketplaces"], {
3729
- hostHome
3730
- });
3731
- const mergedRegistries = [];
3732
- if (mergedInstalled.changed) mergedRegistries.push("installed_plugins.json");
3733
- if (mergedMarkets.changed) mergedRegistries.push("known_marketplaces.json");
3734
- if (opts.dryRun || newItems.length === 0 && mergedRegistries.length === 0) {
3735
- return { newItems, mergedRegistries };
3736
- }
3737
- if (applyPaths.length > 0) {
3738
- const cmds = applyPaths.map(({ src, dest }) => {
3739
- const parent = dest.slice(0, dest.lastIndexOf("/"));
3740
- return `mkdir -p '${parent}' && rsync -a --ignore-existing --exclude=node_modules '${src}/' '${dest}/'`;
3741
- });
3742
- const apply = await execa3(
3743
- "docker",
3744
- [
3745
- "run",
3746
- "--rm",
3747
- "--user",
3748
- "0",
3749
- "-v",
3750
- `${spec.volume}:/src:ro`,
3751
- "-v",
3752
- `${hostClaude}:/dst`,
3753
- opts.image,
3754
- "sh",
3755
- "-c",
3756
- cmds.join(" && ")
3757
- ],
3758
- { reject: false }
3759
- );
3760
- if (apply.exitCode !== 0) {
3761
- throw new ClaudeSessionError(
3762
- `failed to copy extensions from ${spec.volume}: ${(apply.stderr ?? "").toString().trim() || `exit ${String(apply.exitCode)}`}`
3763
- );
3764
- }
3765
- }
3766
- if (mergedMarkets.changed || mergedInstalled.changed) {
3767
- await mkdir22(join22(hostClaude, "plugins"), { recursive: true });
3768
- if (mergedMarkets.changed) {
3769
- await writeFile3(
3770
- join22(hostClaude, "plugins", "known_marketplaces.json"),
3771
- `${JSON.stringify(mergedMarkets.data, null, 2)}
3772
- `
3773
- );
3774
- }
3775
- if (mergedInstalled.changed) {
3776
- await writeFile3(
3777
- join22(hostClaude, "plugins", "installed_plugins.json"),
3778
- `${JSON.stringify(mergedInstalled.data, null, 2)}
3779
- `
3780
- );
3781
- }
3782
- }
3783
- return { newItems, mergedRegistries };
3784
- }
3785
- var CREDENTIALS_BACKUP_FILE = join32(STATE_DIR, "claude-credentials.json");
3786
- var CODEX_CREDENTIALS_BACKUP_FILE = join32(STATE_DIR, "codex-credentials.json");
3787
- var OPENCODE_CREDENTIALS_BACKUP_FILE = join32(STATE_DIR, "opencode-credentials.json");
3788
- function isRealAgentCredential(agent, text) {
3789
- let parsed;
3790
- try {
3791
- parsed = JSON.parse(text);
3792
- } catch {
3793
- return false;
3794
- }
3795
- if (typeof parsed !== "object" || parsed === null) return false;
3796
- if (agent === "claude") {
3797
- const rt = parsed.claudeAiOauth?.refreshToken;
3798
- return typeof rt === "string" && rt.length > 0;
3799
- }
3800
- return Object.keys(parsed).length > 0;
3801
- }
3802
- async function hostClaudeBackupExpired(path = CREDENTIALS_BACKUP_FILE, now = Date.now()) {
3803
- try {
3804
- const parsed = JSON.parse(await readFile32(path, "utf8"));
3805
- const exp = parsed?.claudeAiOauth?.expiresAt;
3806
- return typeof exp === "number" && Number.isFinite(exp) && exp < now;
3807
- } catch {
3808
- return false;
3809
- }
3810
- }
3811
- function parseExtractResult(stdout) {
3812
- return { copied: /\bCOPIED=yes\b/.test(stdout) };
3813
- }
3814
- async function extractVolumeAuthToBackup(opts) {
3815
- try {
3816
- await mkdir32(STATE_DIR, { recursive: true });
3817
- const script = 'COPIED=no; if [ -s /dst/auth.json ]; then cp -a /dst/auth.json "/host-state/$DEST" && COPIED=yes; fi; echo "COPIED=$COPIED"';
3818
- const { stdout } = await execa4("docker", [
3819
- "run",
3820
- "--rm",
3821
- "--user",
3822
- "0",
3823
- "-v",
3824
- `${opts.volume}:/dst`,
3825
- "-v",
3826
- `${STATE_DIR}:/host-state`,
3827
- "-e",
3828
- // Pass the destination filename via env so the path isn't interpolated
3829
- // into the script string (keeps the docker arg list static + injection-safe).
3830
- `DEST=${basename2(opts.backupFile)}`,
3831
- opts.image,
3832
- "sh",
3833
- "-c",
3834
- script
3835
- ]);
3836
- const result = parseExtractResult(stdout);
3837
- if (result.copied) await chmod(opts.backupFile, 384).catch(() => {
3838
- });
3839
- return result;
3840
- } catch {
3841
- return { copied: false };
3842
- }
3843
- }
3844
- function extractCodexCredentials(volume, image) {
3845
- return extractVolumeAuthToBackup({ volume, image, backupFile: CODEX_CREDENTIALS_BACKUP_FILE });
3846
- }
3847
- function extractOpencodeCredentials(volume, image) {
3848
- return extractVolumeAuthToBackup({ volume, image, backupFile: OPENCODE_CREDENTIALS_BACKUP_FILE });
3849
- }
3850
- async function hostBackupHasCredentials(path = CREDENTIALS_BACKUP_FILE) {
3851
- try {
3852
- const parsed = JSON.parse(await readFile32(path, "utf8"));
3853
- const rt = parsed?.claudeAiOauth?.refreshToken;
3854
- return typeof rt === "string" && rt.length > 0;
3855
- } catch {
3856
- return false;
4351
+ const mergedMarkets = mergeKnownMarketplaces(hostMarkets, boxJson["known_marketplaces"], {
4352
+ hostHome
4353
+ });
4354
+ const mergedRegistries = [];
4355
+ if (mergedInstalled.changed) mergedRegistries.push("installed_plugins.json");
4356
+ if (mergedMarkets.changed) mergedRegistries.push("known_marketplaces.json");
4357
+ if (opts.dryRun || newItems.length === 0 && mergedRegistries.length === 0) {
4358
+ return { newItems, mergedRegistries };
3857
4359
  }
3858
- }
3859
- function parseSyncResult(stdout) {
3860
- const volumeHasCredentials = /\bVOLREAL=yes\b/.test(stdout);
3861
- if (/\bEXTRACTED=yes\b/.test(stdout)) return { direction: "extracted", volumeHasCredentials };
3862
- if (/\bSEEDED=yes\b/.test(stdout)) return { direction: "seeded", volumeHasCredentials };
3863
- return { direction: "noop", volumeHasCredentials };
3864
- }
3865
- var SYNC_SCRIPT = `
3866
- EXTRACTED=no
3867
- SEEDED=no
3868
- VOL=/dst/.credentials.json
3869
- HOST=/host-state/claude-credentials.json
3870
- if [ -f "$VOL" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$VOL" >/dev/null 2>&1; then VOL_REAL=yes; else VOL_REAL=no; fi
3871
- if [ -f "$HOST" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$HOST" >/dev/null 2>&1; then HOST_REAL=yes; else HOST_REAL=no; fi
3872
- if [ "$VOL_REAL" = yes ] && [ "$ISOLATE" != yes ]; then
3873
- cp -a "$VOL" "$HOST" && chmod 600 "$HOST" && EXTRACTED=yes
3874
- elif [ "$VOL_REAL" = no ] && [ "$HOST_REAL" = yes ]; then
3875
- cp -a "$HOST" "$VOL" && chown 1000:1000 "$VOL" && chmod 600 "$VOL" && SEEDED=yes && VOL_REAL=yes
3876
- fi
3877
- echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
3878
- `;
3879
- async function syncClaudeCredentials(spec, opts) {
3880
- try {
3881
- await mkdir32(STATE_DIR, { recursive: true });
3882
- const { stdout } = await execa4("docker", [
3883
- "run",
3884
- "--rm",
3885
- "--user",
3886
- "0",
3887
- "-v",
3888
- `${spec.volume}:/dst`,
3889
- "-v",
3890
- `${STATE_DIR}:/host-state`,
3891
- "-e",
3892
- `ISOLATE=${opts.isolate ? "yes" : "no"}`,
3893
- opts.image,
3894
- "sh",
3895
- "-c",
3896
- SYNC_SCRIPT
3897
- ]);
3898
- const result = parseSyncResult(stdout);
3899
- if (result.direction === "extracted") {
3900
- await chmod(CREDENTIALS_BACKUP_FILE, 384).catch(() => {
3901
- });
4360
+ if (applyPaths.length > 0) {
4361
+ const cmds = applyPaths.map(({ src, dest }) => {
4362
+ const parent = dest.slice(0, dest.lastIndexOf("/"));
4363
+ return `mkdir -p '${parent}' && rsync -a --ignore-existing --exclude=node_modules '${src}/' '${dest}/'`;
4364
+ });
4365
+ const apply = await execa5(
4366
+ "docker",
4367
+ [
4368
+ "run",
4369
+ "--rm",
4370
+ "--user",
4371
+ "0",
4372
+ "-v",
4373
+ `${spec.volume}:/src:ro`,
4374
+ "-v",
4375
+ `${hostClaude}:/dst`,
4376
+ opts.image,
4377
+ "sh",
4378
+ "-c",
4379
+ cmds.join(" && ")
4380
+ ],
4381
+ { reject: false }
4382
+ );
4383
+ if (apply.exitCode !== 0) {
4384
+ throw new ClaudeSessionError(
4385
+ `failed to copy extensions from ${spec.volume}: ${(apply.stderr ?? "").toString().trim() || `exit ${String(apply.exitCode)}`}`
4386
+ );
4387
+ }
4388
+ }
4389
+ if (mergedMarkets.changed || mergedInstalled.changed) {
4390
+ await mkdir3(join4(hostClaude, "plugins"), { recursive: true });
4391
+ if (mergedMarkets.changed) {
4392
+ await writeFile22(
4393
+ join4(hostClaude, "plugins", "known_marketplaces.json"),
4394
+ `${JSON.stringify(mergedMarkets.data, null, 2)}
4395
+ `
4396
+ );
4397
+ }
4398
+ if (mergedInstalled.changed) {
4399
+ await writeFile22(
4400
+ join4(hostClaude, "plugins", "installed_plugins.json"),
4401
+ `${JSON.stringify(mergedInstalled.data, null, 2)}
4402
+ `
4403
+ );
3902
4404
  }
3903
- return result;
3904
- } catch {
3905
- return { direction: "noop", volumeHasCredentials: false };
3906
4405
  }
4406
+ return { newItems, mergedRegistries };
3907
4407
  }
3908
4408
  var SHARED_CODEX_VOLUME = "agentbox-codex-config";
3909
4409
  var DEFAULT_CODEX_SESSION = "codex";
@@ -3915,9 +4415,9 @@ function resolveCodexVolume(opts) {
3915
4415
  }
3916
4416
  return { volume: SHARED_CODEX_VOLUME };
3917
4417
  }
3918
- async function pathExists2(p) {
4418
+ async function pathExists3(p) {
3919
4419
  try {
3920
- await stat22(p);
4420
+ await stat42(p);
3921
4421
  return true;
3922
4422
  } catch {
3923
4423
  return false;
@@ -3932,10 +4432,10 @@ async function ensureCodexVolume(spec, opts) {
3932
4432
  const existed = await volumeExists(spec.volume);
3933
4433
  await ensureVolume(spec.volume);
3934
4434
  const created = !existed;
3935
- const hostCodex = join42(homedir32(), ".codex");
3936
- const willSync = opts.syncFromHost && await pathExists2(hostCodex);
4435
+ const hostCodex = join52(homedir4(), ".codex");
4436
+ const willSync = opts.syncFromHost && await pathExists3(hostCodex);
3937
4437
  if (willSync) {
3938
- await execa5("docker", [
4438
+ await execa6("docker", [
3939
4439
  "run",
3940
4440
  "--rm",
3941
4441
  "--user",
@@ -3953,7 +4453,7 @@ async function ensureCodexVolume(spec, opts) {
3953
4453
  ]);
3954
4454
  return { created, synced: true };
3955
4455
  }
3956
- await execa5(
4456
+ await execa6(
3957
4457
  "docker",
3958
4458
  [
3959
4459
  "run",
@@ -3973,7 +4473,7 @@ async function ensureCodexVolume(spec, opts) {
3973
4473
  }
3974
4474
  async function seedCodexHooks(volume, image) {
3975
4475
  try {
3976
- const { stdout } = await execa5("docker", [
4476
+ const { stdout } = await execa6("docker", [
3977
4477
  "run",
3978
4478
  "--rm",
3979
4479
  "--user",
@@ -4010,14 +4510,14 @@ var CodexSessionError = class extends Error {
4010
4510
  }
4011
4511
  };
4012
4512
  async function ensureCodexInstalled(container, opts = {}) {
4013
- const probe = await execa5(
4513
+ const probe = await execa6(
4014
4514
  "docker",
4015
4515
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v codex"],
4016
4516
  { reject: false }
4017
4517
  );
4018
4518
  if (probe.exitCode === 0) return { installed: false };
4019
4519
  opts.onProgress?.("installing codex (absent from this box image)");
4020
- const install = await execa5(
4520
+ const install = await execa6(
4021
4521
  "docker",
4022
4522
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g @openai/codex 2>&1"],
4023
4523
  { reject: false }
@@ -4040,7 +4540,7 @@ async function startCodexSession(opts) {
4040
4540
  const v = process.env[k];
4041
4541
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4042
4542
  }
4043
- const result = await execa5(
4543
+ const result = await execa6(
4044
4544
  "docker",
4045
4545
  [
4046
4546
  "exec",
@@ -4122,7 +4622,7 @@ function runInteractiveCodexLogin(dockerArgv) {
4122
4622
  return { exitCode: child.status ?? 1 };
4123
4623
  }
4124
4624
  async function volumeHasCodexAuth(volume, image) {
4125
- const res = await execa5(
4625
+ const res = await execa6(
4126
4626
  "docker",
4127
4627
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
4128
4628
  { reject: false }
@@ -4131,7 +4631,7 @@ async function volumeHasCodexAuth(volume, image) {
4131
4631
  }
4132
4632
  async function codexSessionInfo(container, sessionName) {
4133
4633
  const name = sessionName ?? DEFAULT_CODEX_SESSION;
4134
- const has = await execa5(
4634
+ const has = await execa6(
4135
4635
  "docker",
4136
4636
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4137
4637
  { reject: false }
@@ -4139,7 +4639,7 @@ async function codexSessionInfo(container, sessionName) {
4139
4639
  if (has.exitCode !== 0) {
4140
4640
  return { running: false, sessionName: name, startedAt: null };
4141
4641
  }
4142
- const ts = await execa5(
4642
+ const ts = await execa6(
4143
4643
  "docker",
4144
4644
  [
4145
4645
  "exec",
@@ -4164,8 +4664,8 @@ async function codexSessionInfo(container, sessionName) {
4164
4664
  }
4165
4665
  var CODEX_PULL_ITEMS = ["config.toml", "auth.json", "prompts"];
4166
4666
  async function pullCodexConfig(spec, opts) {
4167
- const hostCodex = join42(homedir32(), ".codex");
4168
- const inv = await execa5(
4667
+ const hostCodex = join52(homedir4(), ".codex");
4668
+ const inv = await execa6(
4169
4669
  "docker",
4170
4670
  [
4171
4671
  "run",
@@ -4192,14 +4692,14 @@ async function pullCodexConfig(spec, opts) {
4192
4692
  const newItems = [];
4193
4693
  for (const item of CODEX_PULL_ITEMS) {
4194
4694
  if (!present.has(item)) continue;
4195
- if (await pathExists2(join42(hostCodex, item))) continue;
4695
+ if (await pathExists3(join52(hostCodex, item))) continue;
4196
4696
  newItems.push(item);
4197
4697
  }
4198
4698
  if (opts.dryRun || newItems.length === 0) return { newItems };
4199
4699
  const uid = process.getuid?.() ?? 0;
4200
4700
  const gid = process.getgid?.() ?? 0;
4201
4701
  const cmds = newItems.map((it) => `cp -a '/src/${it}' '/dst/${it}'`);
4202
- const apply = await execa5(
4702
+ const apply = await execa6(
4203
4703
  "docker",
4204
4704
  [
4205
4705
  "run",
@@ -4236,9 +4736,9 @@ function resolveOpencodeVolume(opts) {
4236
4736
  }
4237
4737
  return { volume: SHARED_OPENCODE_VOLUME };
4238
4738
  }
4239
- async function pathExists3(p) {
4739
+ async function pathExists4(p) {
4240
4740
  try {
4241
- await stat32(p);
4741
+ await stat5(p);
4242
4742
  return true;
4243
4743
  } catch {
4244
4744
  return false;
@@ -4253,12 +4753,12 @@ async function ensureOpencodeVolume(spec, opts) {
4253
4753
  const existed = await volumeExists(spec.volume);
4254
4754
  await ensureVolume(spec.volume);
4255
4755
  const created = !existed;
4256
- const hostData = join5(homedir4(), ".local", "share", "opencode");
4257
- const hostConfig = join5(homedir4(), ".config", "opencode");
4258
- const hostState = join5(homedir4(), ".local", "state", "opencode");
4259
- const hasData = await pathExists3(hostData);
4260
- const hasConfig = await pathExists3(hostConfig);
4261
- const hasState = await pathExists3(hostState);
4756
+ const hostData = join6(homedir5(), ".local", "share", "opencode");
4757
+ const hostConfig = join6(homedir5(), ".config", "opencode");
4758
+ const hostState = join6(homedir5(), ".local", "state", "opencode");
4759
+ const hasData = await pathExists4(hostData);
4760
+ const hasConfig = await pathExists4(hostConfig);
4761
+ const hasState = await pathExists4(hostState);
4262
4762
  const willSync = opts.syncFromHost && (hasData || hasConfig || hasState);
4263
4763
  if (willSync) {
4264
4764
  const args = ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/dst`];
@@ -4281,10 +4781,10 @@ async function ensureOpencodeVolume(spec, opts) {
4281
4781
  }
4282
4782
  steps.push("chown -R 1000:1000 /dst");
4283
4783
  args.push(opts.image, "sh", "-c", steps.join(" && "));
4284
- await execa6("docker", args);
4784
+ await execa7("docker", args);
4285
4785
  return { created, synced: true };
4286
4786
  }
4287
- await execa6(
4787
+ await execa7(
4288
4788
  "docker",
4289
4789
  [
4290
4790
  "run",
@@ -4304,7 +4804,7 @@ async function ensureOpencodeVolume(spec, opts) {
4304
4804
  }
4305
4805
  async function seedOpencodePlugin(volume, image) {
4306
4806
  try {
4307
- const { stdout } = await execa6("docker", [
4807
+ const { stdout } = await execa7("docker", [
4308
4808
  "run",
4309
4809
  "--rm",
4310
4810
  "--user",
@@ -4352,14 +4852,14 @@ var OpencodeSessionError = class extends Error {
4352
4852
  }
4353
4853
  };
4354
4854
  async function ensureOpencodeInstalled(container, opts = {}) {
4355
- const probe = await execa6(
4855
+ const probe = await execa7(
4356
4856
  "docker",
4357
4857
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v opencode"],
4358
4858
  { reject: false }
4359
4859
  );
4360
4860
  if (probe.exitCode === 0) return { installed: false };
4361
4861
  opts.onProgress?.("installing opencode (absent from this box image)");
4362
- const install = await execa6(
4862
+ const install = await execa7(
4363
4863
  "docker",
4364
4864
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g opencode-ai 2>&1"],
4365
4865
  { reject: false }
@@ -4381,7 +4881,7 @@ async function startOpencodeSession(opts) {
4381
4881
  const v = process.env[k];
4382
4882
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4383
4883
  }
4384
- const result = await execa6(
4884
+ const result = await execa7(
4385
4885
  "docker",
4386
4886
  [
4387
4887
  "exec",
@@ -4467,7 +4967,7 @@ function runInteractiveOpencodeLogin(dockerArgv) {
4467
4967
  return { exitCode: child.status ?? 1 };
4468
4968
  }
4469
4969
  async function volumeHasOpencodeAuth(volume, image) {
4470
- const res = await execa6(
4970
+ const res = await execa7(
4471
4971
  "docker",
4472
4972
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
4473
4973
  { reject: false }
@@ -4476,7 +4976,7 @@ async function volumeHasOpencodeAuth(volume, image) {
4476
4976
  }
4477
4977
  async function opencodeSessionInfo(container, sessionName) {
4478
4978
  const name = sessionName ?? DEFAULT_OPENCODE_SESSION;
4479
- const has = await execa6(
4979
+ const has = await execa7(
4480
4980
  "docker",
4481
4981
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4482
4982
  { reject: false }
@@ -4484,7 +4984,7 @@ async function opencodeSessionInfo(container, sessionName) {
4484
4984
  if (has.exitCode !== 0) {
4485
4985
  return { running: false, sessionName: name, startedAt: null };
4486
4986
  }
4487
- const ts = await execa6(
4987
+ const ts = await execa7(
4488
4988
  "docker",
4489
4989
  [
4490
4990
  "exec",
@@ -4520,9 +5020,9 @@ var OPENCODE_PULL_CONFIG_ITEMS = [
4520
5020
  "themes"
4521
5021
  ];
4522
5022
  async function pullOpencodeConfig(spec, opts) {
4523
- const hostData = join5(homedir4(), ".local", "share", "opencode");
4524
- const hostConfig = join5(homedir4(), ".config", "opencode");
4525
- const inv = await execa6(
5023
+ const hostData = join6(homedir5(), ".local", "share", "opencode");
5024
+ const hostConfig = join6(homedir5(), ".config", "opencode");
5025
+ const inv = await execa7(
4526
5026
  "docker",
4527
5027
  [
4528
5028
  "run",
@@ -4548,7 +5048,7 @@ async function pullOpencodeConfig(spec, opts) {
4548
5048
  const [group, name] = line.trim().split(/\s+/, 2);
4549
5049
  if (!name || group !== "data" && group !== "config") continue;
4550
5050
  const hostBase = group === "data" ? hostData : hostConfig;
4551
- if (await pathExists3(join5(hostBase, name))) continue;
5051
+ if (await pathExists4(join6(hostBase, name))) continue;
4552
5052
  newItems.push({
4553
5053
  label: group === "data" ? name : `config/${name}`,
4554
5054
  src: group === "data" ? `/src/${name}` : `/src/config/${name}`,
@@ -4564,7 +5064,7 @@ async function pullOpencodeConfig(spec, opts) {
4564
5064
  const cmds = newItems.map(
4565
5065
  (i) => `cp -a '${i.src}' '${i.hostDst === "data" ? "/dst-data" : "/dst-config"}/${i.name}'`
4566
5066
  );
4567
- const apply = await execa6(
5067
+ const apply = await execa7(
4568
5068
  "docker",
4569
5069
  [
4570
5070
  "run",
@@ -4675,9 +5175,9 @@ function gitWorktreePathFor(branch) {
4675
5175
  return `${WORKTREE_ROOT}/${fsSafeBranch(branch)}`;
4676
5176
  }
4677
5177
  async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath) {
4678
- const stash = await execa7("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
5178
+ const stash = await execa8("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
4679
5179
  const stashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
4680
- const untracked = await execa7(
5180
+ const untracked = await execa8(
4681
5181
  "git",
4682
5182
  ["-C", repo.hostMainRepo, "ls-files", "--others", "--exclude-standard", "-z"],
4683
5183
  { reject: false }
@@ -4694,7 +5194,7 @@ async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath
4694
5194
  };
4695
5195
  }
4696
5196
  async function dexec(container, argv, user = "vscode", cwd = "/") {
4697
- const r = await execa7(
5197
+ const r = await execa8(
4698
5198
  "docker",
4699
5199
  ["exec", "-w", cwd, "--user", user, container, ...argv],
4700
5200
  { reject: false }
@@ -4726,7 +5226,7 @@ async function bindWorktrees(container, binds, onLog) {
4726
5226
  (a, b) => a.kind === "root" && b.kind !== "root" ? -1 : a.kind !== "root" && b.kind === "root" ? 1 : 0
4727
5227
  );
4728
5228
  for (const b of ordered) {
4729
- await execa7(
5229
+ await execa8(
4730
5230
  "docker",
4731
5231
  ["exec", "-w", "/", "--user", "root", container, "sh", "-c", `mountpoint -q ${b.containerPath} && umount ${b.containerPath} || true`],
4732
5232
  { reject: false }
@@ -4748,7 +5248,7 @@ async function seedWorkspace(opts) {
4748
5248
  const wt = r.gitWorktreePath;
4749
5249
  const baseRef = r.repo.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
4750
5250
  const addArgs = r.reuseBranch ? ["worktree", "add", wt, r.branch] : ["worktree", "add", "-b", r.branch, wt, baseRef];
4751
- const add = await execa7(
5251
+ const add = await execa8(
4752
5252
  "docker",
4753
5253
  ["exec", "--user", "vscode", opts.container, "git", "-C", main, ...addArgs],
4754
5254
  { reject: false }
@@ -4759,7 +5259,7 @@ async function seedWorkspace(opts) {
4759
5259
  );
4760
5260
  }
4761
5261
  log(`worktree ${wt} on branch ${r.branch} (host main ${main})`);
4762
- await execa7(
5262
+ await execa8(
4763
5263
  "docker",
4764
5264
  [
4765
5265
  "exec",
@@ -4775,7 +5275,7 @@ async function seedWorkspace(opts) {
4775
5275
  ],
4776
5276
  { reject: false }
4777
5277
  );
4778
- await execa7(
5278
+ await execa8(
4779
5279
  "docker",
4780
5280
  [
4781
5281
  "exec",
@@ -4805,7 +5305,7 @@ async function seedWorkspace(opts) {
4805
5305
  for (const r of opts.repos) {
4806
5306
  const ct = r.containerPath;
4807
5307
  if (r.stashSha) {
4808
- const withIndex = await execa7(
5308
+ const withIndex = await execa8(
4809
5309
  "docker",
4810
5310
  [
4811
5311
  "exec",
@@ -4823,7 +5323,7 @@ async function seedWorkspace(opts) {
4823
5323
  { reject: false }
4824
5324
  );
4825
5325
  if (withIndex.exitCode !== 0) {
4826
- const noIndex = await execa7(
5326
+ const noIndex = await execa8(
4827
5327
  "docker",
4828
5328
  [
4829
5329
  "exec",
@@ -4851,7 +5351,7 @@ async function seedWorkspace(opts) {
4851
5351
  }
4852
5352
  }
4853
5353
  if (r.untrackedNul.length > 0) {
4854
- const tarOut = await execa7("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
5354
+ const tarOut = await execa8("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
4855
5355
  input: r.untrackedNul.replace(/\0$/, ""),
4856
5356
  encoding: "buffer",
4857
5357
  reject: false
@@ -4860,7 +5360,7 @@ async function seedWorkspace(opts) {
4860
5360
  log(`warning: tar of untracked files for ${r.repo.hostMainRepo} failed: ${tarOut.stderr}`);
4861
5361
  continue;
4862
5362
  }
4863
- const tarIn = await execa7(
5363
+ const tarIn = await execa8(
4864
5364
  "docker",
4865
5365
  ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
4866
5366
  { input: tarOut.stdout, reject: false }
@@ -4877,14 +5377,14 @@ async function seedWorkspace(opts) {
4877
5377
  async function seedWorkspaceFromDir(opts) {
4878
5378
  const log = opts.onLog ?? (() => {
4879
5379
  });
4880
- const tarOut = await execa7("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
5380
+ const tarOut = await execa8("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
4881
5381
  encoding: "buffer",
4882
5382
  reject: false
4883
5383
  });
4884
5384
  if (tarOut.exitCode !== 0) {
4885
5385
  throw new GitWorktreeError(`tar of ${opts.hostSource} failed: ${tarOut.stderr}`);
4886
5386
  }
4887
- const tarIn = await execa7(
5387
+ const tarIn = await execa8(
4888
5388
  "docker",
4889
5389
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-C", "/workspace", "-xf", "-"],
4890
5390
  { input: tarOut.stdout, reject: false }
@@ -4895,18 +5395,159 @@ async function seedWorkspaceFromDir(opts) {
4895
5395
  log(`seeded /workspace from ${opts.hostSource}`);
4896
5396
  }
4897
5397
  async function removeInBoxWorktree(args) {
4898
- const remove = await execa7(
5398
+ const remove = await execa8(
4899
5399
  "git",
4900
5400
  ["-C", args.hostMainRepo, "worktree", "remove", "--force", args.gitWorktreePath],
4901
5401
  { reject: false }
4902
5402
  );
4903
5403
  if (remove.exitCode === 0) return;
4904
- await execa7("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
5404
+ await execa8("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
4905
5405
  }
4906
5406
  function ctParent(p) {
4907
5407
  const i = p.lastIndexOf("/");
4908
5408
  return i <= 0 ? "/" : p.slice(0, i);
4909
5409
  }
5410
+ async function gitIn(container, ct, args) {
5411
+ return execInBox(container, ["git", "-C", ct, ...args], { user: "vscode" });
5412
+ }
5413
+ function splitNul(s) {
5414
+ return s.split("\0").filter((p) => p.length > 0);
5415
+ }
5416
+ async function unmergedPaths(container, ct) {
5417
+ const r = await gitIn(container, ct, ["diff", "--name-only", "--diff-filter=U", "-z"]);
5418
+ return r.exitCode === 0 ? splitNul(r.stdout) : [];
5419
+ }
5420
+ async function resyncWorkspaceFromHost(opts) {
5421
+ const log = opts.onLog ?? (() => {
5422
+ });
5423
+ const results = [];
5424
+ for (const w of opts.worktrees) {
5425
+ const ct = w.containerPath;
5426
+ const hostMain = w.hostMainRepo;
5427
+ const boxBranch = w.branch;
5428
+ const res = { containerPath: ct, mergeConflicts: [], overlaySkipped: [] };
5429
+ const hostBranchProbe = await execa8(
5430
+ "git",
5431
+ ["-C", hostMain, "symbolic-ref", "--short", "-q", "HEAD"],
5432
+ { reject: false }
5433
+ );
5434
+ const hostRef = hostBranchProbe.exitCode === 0 && hostBranchProbe.stdout.trim() ? hostBranchProbe.stdout.trim() : (await execa8("git", ["-C", hostMain, "rev-parse", "HEAD"], { reject: false })).stdout.trim();
5435
+ if (!hostRef) {
5436
+ log(`resync: ${ct}: could not resolve host ref; skipping`);
5437
+ results.push(res);
5438
+ continue;
5439
+ }
5440
+ const stash = await execa8("git", ["-C", hostMain, "stash", "create"], { reject: false });
5441
+ const hostStashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
5442
+ const untracked = await execa8(
5443
+ "git",
5444
+ ["-C", hostMain, "ls-files", "--others", "--exclude-standard", "-z"],
5445
+ { reject: false }
5446
+ );
5447
+ const hostUntracked = untracked.exitCode === 0 ? splitNul(untracked.stdout) : [];
5448
+ if (hostRef !== boxBranch) {
5449
+ const status = await gitIn(opts.container, ct, ["status", "--porcelain"]);
5450
+ const boxDirty = status.stdout.split("\n").some((line) => line.length > 0 && !line.startsWith("??"));
5451
+ let boxStashed = false;
5452
+ if (boxDirty) {
5453
+ const push = await gitIn(opts.container, ct, ["stash", "push", "-m", "agentbox-resync"]);
5454
+ boxStashed = push.exitCode === 0;
5455
+ }
5456
+ const newCommits = await gitIn(opts.container, ct, [
5457
+ "rev-list",
5458
+ "--count",
5459
+ `${boxBranch}..${hostRef}`
5460
+ ]);
5461
+ const n = newCommits.exitCode === 0 ? newCommits.stdout.trim() : "?";
5462
+ const merge = await gitIn(opts.container, ct, ["merge", "--no-commit", hostRef]);
5463
+ const mergeInProgress = (await gitIn(opts.container, ct, ["rev-parse", "-q", "--verify", "MERGE_HEAD"])).exitCode === 0;
5464
+ const conflicts = await unmergedPaths(opts.container, ct);
5465
+ if (conflicts.length > 0) {
5466
+ await gitIn(opts.container, ct, ["checkout", "--ours", "--", ...conflicts]);
5467
+ await gitIn(opts.container, ct, ["add", "--", ...conflicts]);
5468
+ res.mergeConflicts.push(...conflicts);
5469
+ }
5470
+ if (mergeInProgress) {
5471
+ await gitIn(opts.container, ct, [
5472
+ "-c",
5473
+ "user.name=agentbox",
5474
+ "-c",
5475
+ "user.email=agentbox@users.noreply.github.com",
5476
+ "commit",
5477
+ "--no-edit"
5478
+ ]);
5479
+ log(
5480
+ `resync: ${ct}: merged ${n} new host commit(s) from ${hostRef}` + (conflicts.length > 0 ? ` (${String(conflicts.length)} conflict(s) kept box version)` : "")
5481
+ );
5482
+ } else if (merge.exitCode === 0) {
5483
+ log(`resync: ${ct}: ${n === "0" ? "already up to date" : `fast-forwarded to ${hostRef}`}`);
5484
+ } else {
5485
+ await gitIn(opts.container, ct, ["merge", "--abort"]);
5486
+ log(`resync: ${ct}: merge skipped (${(merge.stderr || merge.stdout).trim().split("\n")[0]})`);
5487
+ }
5488
+ if (boxStashed) {
5489
+ const pop = await gitIn(opts.container, ct, ["stash", "pop"]);
5490
+ if (pop.exitCode !== 0) {
5491
+ const popConflicts = await unmergedPaths(opts.container, ct);
5492
+ for (const p of popConflicts) {
5493
+ await gitIn(opts.container, ct, ["checkout", "--theirs", "--", p]);
5494
+ await gitIn(opts.container, ct, ["reset", "-q", "--", p]);
5495
+ }
5496
+ await gitIn(opts.container, ct, ["stash", "drop"]);
5497
+ }
5498
+ }
5499
+ }
5500
+ if (hostStashSha) {
5501
+ const apply = await gitIn(opts.container, ct, ["stash", "apply", hostStashSha]);
5502
+ if (apply.exitCode !== 0) {
5503
+ const conflicts = await unmergedPaths(opts.container, ct);
5504
+ for (const p of conflicts) {
5505
+ await gitIn(opts.container, ct, ["checkout", "--ours", "--", p]);
5506
+ await gitIn(opts.container, ct, ["reset", "-q", "--", p]);
5507
+ }
5508
+ if (conflicts.length > 0) res.overlaySkipped.push(...conflicts);
5509
+ }
5510
+ }
5511
+ if (hostUntracked.length > 0) {
5512
+ const filter = await execa8(
5513
+ "docker",
5514
+ [
5515
+ "exec",
5516
+ "-i",
5517
+ "--user",
5518
+ "vscode",
5519
+ opts.container,
5520
+ "bash",
5521
+ "-c",
5522
+ 'cd "$1" && while IFS= read -r -d "" f; do [ -e "$f" ] && printf "%s\\0" "$f"; done',
5523
+ "bash",
5524
+ ct
5525
+ ],
5526
+ { input: hostUntracked.join("\0"), reject: false }
5527
+ );
5528
+ const existing = new Set(filter.exitCode === 0 ? splitNul(filter.stdout) : []);
5529
+ const toCopy = hostUntracked.filter((p) => !existing.has(p));
5530
+ for (const p of hostUntracked) if (existing.has(p)) res.overlaySkipped.push(p);
5531
+ if (toCopy.length > 0) {
5532
+ const tarOut = await execa8("tar", ["-C", hostMain, "--null", "-T", "-", "-cf", "-"], {
5533
+ input: toCopy.join("\0"),
5534
+ encoding: "buffer",
5535
+ reject: false
5536
+ });
5537
+ if (tarOut.exitCode === 0) {
5538
+ await execa8(
5539
+ "docker",
5540
+ ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
5541
+ { input: tarOut.stdout, reject: false }
5542
+ );
5543
+ log(`resync: ${ct}: copied ${String(toCopy.length)} untracked host file(s)`);
5544
+ }
5545
+ }
5546
+ }
5547
+ results.push(res);
5548
+ }
5549
+ return results;
5550
+ }
4910
5551
  var PORTLESS_BIN = "portless";
4911
5552
  var SUB_VERSION = ["--version"];
4912
5553
  var SUB_ALIAS = "alias";
@@ -4917,7 +5558,7 @@ var cached = null;
4917
5558
  async function detectPortless() {
4918
5559
  if (cached !== null) return cached;
4919
5560
  try {
4920
- const ver = await execa8(PORTLESS_BIN, SUB_VERSION, { reject: false });
5561
+ const ver = await execa9(PORTLESS_BIN, SUB_VERSION, { reject: false });
4921
5562
  if (ver.exitCode !== 0) {
4922
5563
  cached = { installed: false, proxyRunning: false };
4923
5564
  return cached;
@@ -4937,7 +5578,7 @@ function resetPortlessCache() {
4937
5578
  }
4938
5579
  async function portlessAlias(name, port) {
4939
5580
  try {
4940
- const r = await execa8(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
5581
+ const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
4941
5582
  return r.exitCode === 0;
4942
5583
  } catch {
4943
5584
  return false;
@@ -4945,7 +5586,7 @@ async function portlessAlias(name, port) {
4945
5586
  }
4946
5587
  async function portlessUnalias(name) {
4947
5588
  try {
4948
- const r = await execa8(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
5589
+ const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
4949
5590
  return r.exitCode === 0;
4950
5591
  } catch {
4951
5592
  return false;
@@ -4954,7 +5595,7 @@ async function portlessUnalias(name) {
4954
5595
  async function portlessGetUrl(name) {
4955
5596
  const fallback = `https://${name}.localhost`;
4956
5597
  try {
4957
- const r = await execa8(PORTLESS_BIN, [SUB_GET, name], { reject: false });
5598
+ const r = await execa9(PORTLESS_BIN, [SUB_GET, name], { reject: false });
4958
5599
  const out = (r.stdout ?? "").trim();
4959
5600
  if (r.exitCode === 0 && /^https?:\/\//.test(out)) return out;
4960
5601
  } catch {
@@ -4975,7 +5616,7 @@ function portlessBrowserEnv(boxName, opts) {
4975
5616
  }
4976
5617
  async function installPortless() {
4977
5618
  try {
4978
- const r = await execa8("npm", ["install", "-g", "portless"], { reject: false });
5619
+ const r = await execa9("npm", ["install", "-g", "portless"], { reject: false });
4979
5620
  return r.exitCode === 0;
4980
5621
  } catch {
4981
5622
  return false;
@@ -4983,7 +5624,7 @@ async function installPortless() {
4983
5624
  }
4984
5625
  async function startPortlessProxy() {
4985
5626
  try {
4986
- const r = await execa8(
5627
+ const r = await execa9(
4987
5628
  PORTLESS_BIN,
4988
5629
  ["proxy", "start", "--no-tls", "-p", String(PORTLESS_PROXY_PORT)],
4989
5630
  { reject: false }
@@ -4996,7 +5637,7 @@ async function startPortlessProxy() {
4996
5637
  function portlessStateDirCandidates() {
4997
5638
  const env = process.env["PORTLESS_STATE_DIR"];
4998
5639
  if (env && env.trim().length > 0) return [env.trim()];
4999
- return ["/tmp/portless", join6(homedir5(), ".portless")];
5640
+ return ["/tmp/portless", join7(homedir6(), ".portless")];
5000
5641
  }
5001
5642
  function pidAlive(pid) {
5002
5643
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -5009,7 +5650,7 @@ function pidAlive(pid) {
5009
5650
  }
5010
5651
  async function readProxyPid(dir) {
5011
5652
  try {
5012
- const raw = await readFile42(join6(dir, "proxy.pid"), "utf8");
5653
+ const raw = await readFile52(join7(dir, "proxy.pid"), "utf8");
5013
5654
  const pid = Number.parseInt(raw.trim(), 10);
5014
5655
  return Number.isFinite(pid) && pid > 0 ? pid : null;
5015
5656
  } catch {
@@ -5029,7 +5670,7 @@ async function resolvePortlessHostStateDir(override) {
5029
5670
  if (env && env.trim().length > 0) return env.trim();
5030
5671
  const live = await findLivePortlessStateDir();
5031
5672
  if (live) return live;
5032
- const home = join6(homedir5(), ".portless");
5673
+ const home = join7(homedir6(), ".portless");
5033
5674
  if (existsSync(home)) return home;
5034
5675
  if (existsSync("/tmp/portless")) return "/tmp/portless";
5035
5676
  return home;
@@ -5037,7 +5678,7 @@ async function resolvePortlessHostStateDir(override) {
5037
5678
  async function isProxyRunning() {
5038
5679
  if (await findLivePortlessStateDir() !== null) return true;
5039
5680
  try {
5040
- const r = await execa8("pgrep", ["-f", "portless proxy"], { reject: false });
5681
+ const r = await execa9("pgrep", ["-f", "portless proxy"], { reject: false });
5041
5682
  return r.exitCode === 0 && (r.stdout ?? "").trim().length > 0;
5042
5683
  } catch {
5043
5684
  return false;
@@ -5058,25 +5699,25 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
5058
5699
  ".cache",
5059
5700
  ".parcel-cache"
5060
5701
  ]);
5061
- var SNAPSHOTS_ROOT = join7(homedir6(), ".agentbox", "snapshots");
5702
+ var SNAPSHOTS_ROOT = join8(homedir7(), ".agentbox", "snapshots");
5062
5703
  function snapshotPathFor(box) {
5063
5704
  const mnemonic = sanitizeMnemonic(box.name);
5064
5705
  const n = box.projectIndex;
5065
5706
  const segment = typeof n === "number" && Number.isFinite(n) && n > 0 ? `${box.id}-${String(n)}-${mnemonic}` : `${box.id}-${mnemonic}`;
5066
- return join7(SNAPSHOTS_ROOT, segment);
5707
+ return join8(SNAPSHOTS_ROOT, segment);
5067
5708
  }
5068
5709
  async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
5069
5710
  const matches = [];
5070
5711
  const walk = async (dir) => {
5071
5712
  let entries;
5072
5713
  try {
5073
- entries = await readdir22(dir, { withFileTypes: true });
5714
+ entries = await readdir42(dir, { withFileTypes: true });
5074
5715
  } catch {
5075
5716
  return;
5076
5717
  }
5077
5718
  for (const entry of entries) {
5078
5719
  if (!entry.isDirectory()) continue;
5079
- const abs = join7(dir, entry.name);
5720
+ const abs = join8(dir, entry.name);
5080
5721
  if (excluded.has(entry.name)) {
5081
5722
  matches.push(abs);
5082
5723
  continue;
@@ -5091,28 +5732,28 @@ async function createSnapshot(opts) {
5091
5732
  const source = resolve2(opts.source);
5092
5733
  const destination = resolve2(opts.destination);
5093
5734
  const excluded = opts.excluded ?? EXCLUDE_DIRS;
5094
- await mkdir4(SNAPSHOTS_ROOT, { recursive: true });
5735
+ await mkdir42(SNAPSHOTS_ROOT, { recursive: true });
5095
5736
  const cpArgs = platform() === "darwin" ? ["-cR"] : ["-R"];
5096
- await execa9("cp", [...cpArgs, `${source}/`, destination]);
5737
+ await execa10("cp", [...cpArgs, `${source}/`, destination]);
5097
5738
  const toPrune = await findExcludedDirs(destination, excluded);
5098
- await Promise.all(toPrune.map((p) => rm22(p, { recursive: true, force: true })));
5739
+ await Promise.all(toPrune.map((p) => rm32(p, { recursive: true, force: true })));
5099
5740
  return { destination, prunedPaths: toPrune };
5100
5741
  }
5101
- var CHECKPOINTS_ROOT = join8(homedir7(), ".agentbox", "checkpoints");
5742
+ var CHECKPOINTS_ROOT = join9(homedir8(), ".agentbox", "checkpoints");
5102
5743
  var CHECKPOINT_IMAGE_PREFIX = "agentbox-ckpt-";
5103
5744
  function checkpointImageTag(projectRoot, name) {
5104
- const mnemonic = sanitizeMnemonic(basename22(projectRoot));
5745
+ const mnemonic = sanitizeMnemonic(basename32(projectRoot));
5105
5746
  return `${CHECKPOINT_IMAGE_PREFIX}${hashProjectPath(projectRoot)}_${mnemonic}:${name}`;
5106
5747
  }
5107
5748
  function projectCheckpointsDir(projectRoot) {
5108
- return join8(CHECKPOINTS_ROOT, projectDirSegment(projectRoot));
5749
+ return join9(CHECKPOINTS_ROOT, projectDirSegment(projectRoot));
5109
5750
  }
5110
5751
  function checkpointDir(projectRoot, name) {
5111
- return join8(projectCheckpointsDir(projectRoot), name);
5752
+ return join9(projectCheckpointsDir(projectRoot), name);
5112
5753
  }
5113
5754
  async function readManifest(dir) {
5114
5755
  try {
5115
- const raw = await readFile5(join8(dir, "manifest.json"), "utf8");
5756
+ const raw = await readFile6(join9(dir, "manifest.json"), "utf8");
5116
5757
  const m = JSON.parse(raw);
5117
5758
  if (m.schema !== 2 && m.schema !== 3) return null;
5118
5759
  return m;
@@ -5120,23 +5761,39 @@ async function readManifest(dir) {
5120
5761
  return null;
5121
5762
  }
5122
5763
  }
5123
- async function listCheckpoints(projectRoot) {
5124
- const root = projectCheckpointsDir(projectRoot);
5764
+ async function listCheckpointsInDir(root) {
5125
5765
  let entries;
5126
5766
  try {
5127
- entries = (await readdir32(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5767
+ entries = (await readdir5(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5128
5768
  } catch {
5129
5769
  return [];
5130
5770
  }
5131
5771
  const out = [];
5132
5772
  for (const name of entries) {
5133
- const dir = join8(root, name);
5773
+ const dir = join9(root, name);
5134
5774
  const manifest = await readManifest(dir);
5135
5775
  if (manifest) out.push({ name, dir, manifest });
5136
5776
  }
5137
5777
  out.sort((a, b) => a.manifest.createdAt.localeCompare(b.manifest.createdAt));
5138
5778
  return out;
5139
5779
  }
5780
+ async function listCheckpoints(projectRoot) {
5781
+ return listCheckpointsInDir(projectCheckpointsDir(projectRoot));
5782
+ }
5783
+ async function listAllCheckpoints() {
5784
+ let segments;
5785
+ try {
5786
+ segments = (await readdir5(CHECKPOINTS_ROOT, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5787
+ } catch {
5788
+ return [];
5789
+ }
5790
+ const out = [];
5791
+ for (const segment of segments) {
5792
+ const items = await listCheckpointsInDir(join9(CHECKPOINTS_ROOT, segment));
5793
+ if (items.length > 0) out.push({ segment, items });
5794
+ }
5795
+ return out;
5796
+ }
5140
5797
  async function resolveCheckpoint(projectRoot, ref) {
5141
5798
  const dir = checkpointDir(projectRoot, ref);
5142
5799
  const manifest = await readManifest(dir);
@@ -5146,21 +5803,21 @@ async function resolveCheckpoint(projectRoot, ref) {
5146
5803
  async function listAllCheckpointImages() {
5147
5804
  let projectDirs;
5148
5805
  try {
5149
- projectDirs = (await readdir32(CHECKPOINTS_ROOT, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5806
+ projectDirs = (await readdir5(CHECKPOINTS_ROOT, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5150
5807
  } catch {
5151
5808
  return [];
5152
5809
  }
5153
5810
  const out = /* @__PURE__ */ new Set();
5154
5811
  for (const proj of projectDirs) {
5155
- const projPath = join8(CHECKPOINTS_ROOT, proj);
5812
+ const projPath = join9(CHECKPOINTS_ROOT, proj);
5156
5813
  let names;
5157
5814
  try {
5158
- names = (await readdir32(projPath, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5815
+ names = (await readdir5(projPath, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5159
5816
  } catch {
5160
5817
  continue;
5161
5818
  }
5162
5819
  for (const name of names) {
5163
- const manifest = await readManifest(join8(projPath, name));
5820
+ const manifest = await readManifest(join9(projPath, name));
5164
5821
  if (manifest) out.add(manifest.image);
5165
5822
  }
5166
5823
  }
@@ -5170,7 +5827,7 @@ async function removeCheckpoint(projectRoot, ref) {
5170
5827
  const dir = checkpointDir(projectRoot, ref);
5171
5828
  const manifest = await readManifest(dir);
5172
5829
  if (!manifest) return false;
5173
- await rm3(dir, { recursive: true, force: true });
5830
+ await rm4(dir, { recursive: true, force: true });
5174
5831
  await removeImage(manifest.image, { force: true });
5175
5832
  return true;
5176
5833
  }
@@ -5202,7 +5859,7 @@ async function runCleanup(container, log) {
5202
5859
  }
5203
5860
  }
5204
5861
  async function inspectImageConfig(imageRef) {
5205
- const r = await execa10("docker", ["image", "inspect", imageRef], { reject: false });
5862
+ const r = await execa11("docker", ["image", "inspect", imageRef], { reject: false });
5206
5863
  if (r.exitCode !== 0) {
5207
5864
  throw new CheckpointError(`docker image inspect ${imageRef} failed`, r.stdout, r.stderr);
5208
5865
  }
@@ -5278,14 +5935,14 @@ async function createCheckpoint(opts) {
5278
5935
  await runCleanup(box.container, log);
5279
5936
  if (type === "layered") {
5280
5937
  log(`docker commit ${box.container} -> ${tag} (layered)`);
5281
- const r = await execa10("docker", ["commit", box.container, tag], { reject: false });
5938
+ const r = await execa11("docker", ["commit", box.container, tag], { reject: false });
5282
5939
  if (r.exitCode !== 0) {
5283
5940
  throw new CheckpointError(`docker commit failed for ${box.container}`, r.stdout, r.stderr);
5284
5941
  }
5285
5942
  } else {
5286
5943
  log(`docker commit ${box.container} -> <intermediate> (flattened path)`);
5287
5944
  const intermediate = `${tag}-intermediate`;
5288
- const commit = await execa10("docker", ["commit", box.container, intermediate], {
5945
+ const commit = await execa11("docker", ["commit", box.container, intermediate], {
5289
5946
  reject: false
5290
5947
  });
5291
5948
  if (commit.exitCode !== 0) {
@@ -5321,7 +5978,7 @@ async function createCheckpoint(opts) {
5321
5978
  cliVersion: stamp.cliVersion,
5322
5979
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5323
5980
  };
5324
- await writeFile22(join8(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
5981
+ await writeFile32(join9(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
5325
5982
  if (opts.setDefault) {
5326
5983
  await setConfigValue("project", "box.defaultCheckpointDocker", name, opts.projectRoot);
5327
5984
  log(`set project default checkpoint (box.defaultCheckpointDocker) -> ${name}`);
@@ -5330,7 +5987,7 @@ async function createCheckpoint(opts) {
5330
5987
  }
5331
5988
  async function flattenImage(sourceTag, destTag, log) {
5332
5989
  const tmpName = `agentbox-flatten-${Date.now().toString(36)}`;
5333
- const create = await execa10(
5990
+ const create = await execa11(
5334
5991
  "docker",
5335
5992
  ["create", "--name", tmpName, sourceTag, "sleep", "0"],
5336
5993
  { reject: false }
@@ -5338,11 +5995,11 @@ async function flattenImage(sourceTag, destTag, log) {
5338
5995
  if (create.exitCode !== 0) {
5339
5996
  throw new CheckpointError(`docker create for flatten failed`, create.stdout, create.stderr);
5340
5997
  }
5341
- const scratch = await mkdtemp2(join8(tmpdir2(), "agentbox-flatten-"));
5998
+ const scratch = await mkdtemp3(join9(tmpdir3(), "agentbox-flatten-"));
5342
5999
  try {
5343
- const rootfsPath = join8(scratch, "rootfs.tar");
6000
+ const rootfsPath = join9(scratch, "rootfs.tar");
5344
6001
  log(`exporting rootfs of ${sourceTag} to ${rootfsPath}`);
5345
- const exp = await execa10("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
6002
+ const exp = await execa11("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
5346
6003
  if (exp.exitCode !== 0) {
5347
6004
  throw new CheckpointError(`docker export failed`, exp.stdout, exp.stderr);
5348
6005
  }
@@ -5353,19 +6010,19 @@ async function flattenImage(sourceTag, destTag, log) {
5353
6010
  "ADD rootfs.tar /",
5354
6011
  ...renderConfigDirectives(cfg)
5355
6012
  ];
5356
- await writeFile22(join8(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
6013
+ await writeFile32(join9(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
5357
6014
  log(`building flattened ${destTag} from rootfs.tar (FROM scratch)`);
5358
- const build = await execa10(
6015
+ const build = await execa11(
5359
6016
  "docker",
5360
- ["build", "-t", destTag, "-f", join8(scratch, "Dockerfile"), scratch],
6017
+ ["build", "-t", destTag, "-f", join9(scratch, "Dockerfile"), scratch],
5361
6018
  { reject: false }
5362
6019
  );
5363
6020
  if (build.exitCode !== 0) {
5364
6021
  throw new CheckpointError(`flatten docker build failed`, build.stdout, build.stderr);
5365
6022
  }
5366
6023
  } finally {
5367
- await execa10("docker", ["rm", "-f", tmpName], { reject: false });
5368
- await rm3(scratch, { recursive: true, force: true });
6024
+ await execa11("docker", ["rm", "-f", tmpName], { reject: false });
6025
+ await rm4(scratch, { recursive: true, force: true });
5369
6026
  }
5370
6027
  }
5371
6028
  var CheckpointError = class extends Error {
@@ -5390,7 +6047,7 @@ async function launchCtlDaemon(container, hostSocketPath, timeoutMs = 3e3) {
5390
6047
  }
5391
6048
  const deadline = Date.now() + timeoutMs;
5392
6049
  while (Date.now() < deadline) {
5393
- if (await pathExists4(hostSocketPath)) return { up: true };
6050
+ if (await pathExists5(hostSocketPath)) return { up: true };
5394
6051
  await new Promise((r) => setTimeout(r, 100));
5395
6052
  }
5396
6053
  return {
@@ -5398,9 +6055,9 @@ async function launchCtlDaemon(container, hostSocketPath, timeoutMs = 3e3) {
5398
6055
  reason: `socket ${hostSocketPath} did not appear within ${String(timeoutMs)}ms`
5399
6056
  };
5400
6057
  }
5401
- async function pathExists4(p) {
6058
+ async function pathExists5(p) {
5402
6059
  try {
5403
- await stat5(p);
6060
+ await stat7(p);
5404
6061
  return true;
5405
6062
  } catch {
5406
6063
  return false;
@@ -5408,7 +6065,7 @@ async function pathExists4(p) {
5408
6065
  }
5409
6066
  async function writeBoxEnvFile(container, env) {
5410
6067
  const body = formatBoxEnvBody(env);
5411
- const result = await execa11(
6068
+ const result = await execa12(
5412
6069
  "docker",
5413
6070
  ["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
5414
6071
  { input: body, reject: false }
@@ -5432,7 +6089,7 @@ function shellSingleQuote(s) {
5432
6089
  return `'${s.replace(/'/g, `'\\''`)}'`;
5433
6090
  }
5434
6091
  async function ensureHomeOwnedByVscode(container) {
5435
- await execa12(
6092
+ await execa13(
5436
6093
  "docker",
5437
6094
  [
5438
6095
  "exec",
@@ -5448,10 +6105,10 @@ async function ensureHomeOwnedByVscode(container) {
5448
6105
  { reject: false }
5449
6106
  );
5450
6107
  }
5451
- var STATE_DIR22 = join9(homedir8(), ".agentbox");
5452
- var PID_FILE = join9(STATE_DIR22, "relay.pid");
5453
- var LOG_FILE = join9(STATE_DIR22, "relay.log");
5454
- var RELAY_HOME_DIR = join9(STATE_DIR22, "relay");
6108
+ var STATE_DIR22 = join10(homedir9(), ".agentbox");
6109
+ var PID_FILE = join10(STATE_DIR22, "relay.pid");
6110
+ var LOG_FILE = join10(STATE_DIR22, "relay.log");
6111
+ var RELAY_HOME_DIR = join10(STATE_DIR22, "relay");
5455
6112
  var PORT = DEFAULT_RELAY_PORT;
5456
6113
  var ENDPOINT = {
5457
6114
  // host.docker.internal is the Docker Desktop / OrbStack-supplied alias for
@@ -5565,7 +6222,7 @@ async function spawnRelay(relayBin, cliEntry, log) {
5565
6222
  );
5566
6223
  child.unref();
5567
6224
  if (typeof child.pid === "number") {
5568
- await writeFile32(PID_FILE, String(child.pid), "utf8");
6225
+ await writeFile4(PID_FILE, String(child.pid), "utf8");
5569
6226
  log(`spawned relay host process (pid ${String(child.pid)}, port ${String(PORT)})`);
5570
6227
  }
5571
6228
  for (let i = 0; i < 25; i++) {
@@ -5618,28 +6275,28 @@ async function stageRelayHome(version, log) {
5618
6275
  if (process.env.AGENTBOX_RELAY_BIN || process.env.AGENTBOX_CLI_ENTRY) return null;
5619
6276
  const cliRoot = findCliRoot(dirname3(fileURLToPath(import.meta.url)));
5620
6277
  if (cliRoot === null) return null;
5621
- const homeDir = join9(RELAY_HOME_DIR, version);
5622
- const stagedEntry = join9(homeDir, "dist", "index.js");
5623
- const stagedBin = join9(homeDir, "runtime", "relay", "bin.cjs");
6278
+ const homeDir = join10(RELAY_HOME_DIR, version);
6279
+ const stagedEntry = join10(homeDir, "dist", "index.js");
6280
+ const stagedBin = join10(homeDir, "runtime", "relay", "bin.cjs");
5624
6281
  if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
5625
6282
  return { relayBin: stagedBin, cliEntry: stagedEntry };
5626
6283
  }
5627
- const nodeModules = resolveDepRoot(join9(cliRoot, "dist", "index.js"));
6284
+ const nodeModules = resolveDepRoot(join10(cliRoot, "dist", "index.js"));
5628
6285
  if (nodeModules === null) return null;
5629
6286
  const tmpDir = `${homeDir}.tmp-${String(process.pid)}`;
5630
6287
  try {
5631
6288
  await mkdir6(RELAY_HOME_DIR, { recursive: true });
5632
- await rm4(tmpDir, { recursive: true, force: true });
6289
+ await rm5(tmpDir, { recursive: true, force: true });
5633
6290
  await mkdir6(tmpDir, { recursive: true });
5634
6291
  for (const sub of ["dist", "runtime", "share"]) {
5635
- const src = join9(cliRoot, sub);
5636
- if (existsSync2(src)) await cp(src, join9(tmpDir, sub), { recursive: true });
6292
+ const src = join10(cliRoot, sub);
6293
+ if (existsSync2(src)) await cp(src, join10(tmpDir, sub), { recursive: true });
5637
6294
  }
5638
- await cp(nodeModules, join9(tmpDir, "node_modules"), { recursive: true, dereference: true });
5639
- await rm4(homeDir, { recursive: true, force: true });
6295
+ await cp(nodeModules, join10(tmpDir, "node_modules"), { recursive: true, dereference: true });
6296
+ await rm5(homeDir, { recursive: true, force: true });
5640
6297
  await rename3(tmpDir, homeDir);
5641
6298
  } catch (err) {
5642
- await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
6299
+ await rm5(tmpDir, { recursive: true, force: true }).catch(() => {
5643
6300
  });
5644
6301
  if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
5645
6302
  return { relayBin: stagedBin, cliEntry: stagedEntry };
@@ -5654,7 +6311,7 @@ async function stageRelayHome(version, log) {
5654
6311
  }
5655
6312
  function findCliRoot(moduleDir) {
5656
6313
  for (const root of [resolve22(moduleDir, ".."), resolve22(moduleDir, "..", "..")]) {
5657
- if (existsSync2(join9(root, "dist", "index.js")) && existsSync2(join9(root, "runtime", "relay", "bin.cjs"))) {
6314
+ if (existsSync2(join10(root, "dist", "index.js")) && existsSync2(join10(root, "runtime", "relay", "bin.cjs"))) {
5658
6315
  return root;
5659
6316
  }
5660
6317
  }
@@ -5669,7 +6326,7 @@ function resolveDepRoot(fromFile) {
5669
6326
  const idx = main.lastIndexOf(marker);
5670
6327
  if (idx === -1) return null;
5671
6328
  const nm = main.slice(0, idx + marker.length - 1);
5672
- return existsSync2(join9(nm, "commander")) ? nm : null;
6329
+ return existsSync2(join10(nm, "commander")) ? nm : null;
5673
6330
  } catch {
5674
6331
  return null;
5675
6332
  }
@@ -5677,13 +6334,13 @@ function resolveDepRoot(fromFile) {
5677
6334
  async function gcOldRelayHomes(keepVersion) {
5678
6335
  let entries;
5679
6336
  try {
5680
- entries = await readdir4(RELAY_HOME_DIR);
6337
+ entries = await readdir6(RELAY_HOME_DIR);
5681
6338
  } catch {
5682
6339
  return;
5683
6340
  }
5684
6341
  for (const name of entries) {
5685
6342
  if (name === keepVersion) continue;
5686
- await rm4(join9(RELAY_HOME_DIR, name), { recursive: true, force: true }).catch(() => {
6343
+ await rm5(join10(RELAY_HOME_DIR, name), { recursive: true, force: true }).catch(() => {
5687
6344
  });
5688
6345
  }
5689
6346
  }
@@ -5794,7 +6451,7 @@ function fetchHealthz(timeoutMs) {
5794
6451
  }
5795
6452
  async function readPidFile() {
5796
6453
  try {
5797
- const text = await readFile6(PID_FILE, "utf8");
6454
+ const text = await readFile7(PID_FILE, "utf8");
5798
6455
  const pid = Number.parseInt(text.trim(), 10);
5799
6456
  return Number.isFinite(pid) && pid > 0 ? pid : null;
5800
6457
  } catch {
@@ -6097,8 +6754,8 @@ async function ensureAgentboxTasksFile(container, services, opts = {}) {
6097
6754
  return { status: "wrote" };
6098
6755
  }
6099
6756
  async function writeFileInBox(container, path, content) {
6100
- const { execa: execa19 } = await import("execa");
6101
- const result = await execa19(
6757
+ const { execa: execa20 } = await import("execa");
6758
+ const result = await execa20(
6102
6759
  "docker",
6103
6760
  ["exec", "-i", "--user", "vscode", container, "sh", "-c", `cat > ${shellQuote(path)}`],
6104
6761
  { input: content, reject: false }
@@ -6122,29 +6779,29 @@ function persistableLimits(lim) {
6122
6779
  return Object.keys(out).length > 0 ? out : void 0;
6123
6780
  }
6124
6781
  function sanitizeBasename(workspacePath) {
6125
- const raw = basename3(resolve3(workspacePath));
6782
+ const raw = basename4(resolve3(workspacePath));
6126
6783
  return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
6127
6784
  }
6128
6785
  function defaultBoxName(workspacePath, id) {
6129
6786
  const base = sanitizeBasename(workspacePath);
6130
6787
  return base.length > 0 ? `${base}-${id}` : id;
6131
6788
  }
6132
- async function pathExists5(p) {
6789
+ async function pathExists6(p) {
6133
6790
  try {
6134
- await stat6(p);
6791
+ await stat8(p);
6135
6792
  return true;
6136
6793
  } catch {
6137
6794
  return false;
6138
6795
  }
6139
6796
  }
6140
6797
  async function buildIdentityMounts() {
6141
- const home = homedir9();
6798
+ const home = homedir10();
6142
6799
  const candidates = [
6143
- { src: join10(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
6800
+ { src: join11(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
6144
6801
  ];
6145
6802
  const out = [];
6146
6803
  for (const c of candidates) {
6147
- if (await pathExists5(c.src)) {
6804
+ if (await pathExists6(c.src)) {
6148
6805
  out.push(`${c.src}:${c.dst}${c.readOnly ? ":ro" : ""}`);
6149
6806
  }
6150
6807
  }
@@ -6154,11 +6811,11 @@ async function createBox(opts) {
6154
6811
  const log = opts.onLog ?? (() => {
6155
6812
  });
6156
6813
  const workspace = resolve3(opts.workspacePath);
6157
- if (!await pathExists5(workspace)) {
6814
+ if (!await pathExists6(workspace)) {
6158
6815
  throw new Error(`workspace does not exist: ${workspace}`);
6159
6816
  }
6160
- const cfgPath = join10(workspace, "agentbox.yaml");
6161
- if (await pathExists5(cfgPath)) {
6817
+ const cfgPath = join11(workspace, "agentbox.yaml");
6818
+ if (await pathExists6(cfgPath)) {
6162
6819
  try {
6163
6820
  const cfg = await loadConfig(cfgPath);
6164
6821
  log(`agentbox.yaml validated (${String(cfg.services.length)} service(s))`);
@@ -6175,6 +6832,7 @@ async function createBox(opts) {
6175
6832
  let checkpointImage;
6176
6833
  let checkpointSource;
6177
6834
  let restoredWorktrees;
6835
+ let resyncResult;
6178
6836
  if (opts.checkpointRef) {
6179
6837
  const projectRootForCkpt = opts.projectRoot ?? workspace;
6180
6838
  const head = await resolveCheckpoint(projectRootForCkpt, opts.checkpointRef);
@@ -6349,7 +7007,7 @@ async function createBox(opts) {
6349
7007
  log(`seeded claude credentials into ${claudeSpec.volume} from host backup`);
6350
7008
  }
6351
7009
  const claudeMounts = buildClaudeMounts(claudeSpec, process.env);
6352
- const wantCodex = opts.codexConfig !== void 0 || await pathExists5(join10(homedir9(), ".codex"));
7010
+ const wantCodex = opts.codexConfig !== void 0 || await pathExists6(join11(homedir10(), ".codex"));
6353
7011
  let codexMounts;
6354
7012
  let codexConfigVolume;
6355
7013
  if (wantCodex) {
@@ -6369,7 +7027,7 @@ async function createBox(opts) {
6369
7027
  codexMounts = buildCodexMounts(codexSpec, process.env);
6370
7028
  codexConfigVolume = codexSpec.volume;
6371
7029
  }
6372
- const wantOpencode = opts.opencodeConfig !== void 0 || await pathExists5(join10(homedir9(), ".config", "opencode")) || await pathExists5(join10(homedir9(), ".local", "share", "opencode"));
7030
+ const wantOpencode = opts.opencodeConfig !== void 0 || await pathExists6(join11(homedir10(), ".config", "opencode")) || await pathExists6(join11(homedir10(), ".local", "share", "opencode"));
6373
7031
  let opencodeMounts;
6374
7032
  let opencodeConfigVolume;
6375
7033
  if (wantOpencode) {
@@ -6390,9 +7048,9 @@ async function createBox(opts) {
6390
7048
  opencodeConfigVolume = opencodeSpec.volume;
6391
7049
  }
6392
7050
  const boxDir = boxRunDirFor({ id, name, projectIndex });
6393
- const socketDir = join10(boxDir, "run");
6394
- const socketPath = join10(socketDir, "ctl.sock");
6395
- const mergedExportDir = join10(boxDir, "workspace");
7051
+ const socketDir = join11(boxDir, "run");
7052
+ const socketPath = join11(socketDir, "ctl.sock");
7053
+ const mergedExportDir = join11(boxDir, "workspace");
6396
7054
  await mkdir7(socketDir, { recursive: true });
6397
7055
  await mkdir7(mergedExportDir, { recursive: true });
6398
7056
  const extraVolumes = await buildIdentityMounts();
@@ -6524,7 +7182,7 @@ async function createBox(opts) {
6524
7182
  } catch (err) {
6525
7183
  if (opts.useBranch !== void 0) {
6526
7184
  log(`seedWorkspace failed for --use-branch ${opts.useBranch}; cleaning up the box`);
6527
- await execa13("docker", ["rm", "-f", containerName], { reject: false });
7185
+ await execa14("docker", ["rm", "-f", containerName], { reject: false });
6528
7186
  for (const w of gitWorktreeRecords) {
6529
7187
  await removeInBoxWorktree({
6530
7188
  hostMainRepo: w.hostMainRepo,
@@ -6551,6 +7209,19 @@ async function createBox(opts) {
6551
7209
  log
6552
7210
  );
6553
7211
  log("re-bound /workspace from checkpoint image");
7212
+ if (opts.resyncOnStart !== false) {
7213
+ const repos = await resyncWorkspaceFromHost({
7214
+ container: containerName,
7215
+ worktrees: restoredWorktrees,
7216
+ onLog: log
7217
+ });
7218
+ resyncResult = {
7219
+ repos,
7220
+ hadConflicts: repos.some(
7221
+ (r) => r.mergeConflicts.length > 0 || r.overlaySkipped.length > 0
7222
+ )
7223
+ };
7224
+ }
6554
7225
  } else {
6555
7226
  log("using /workspace from checkpoint image (no worktrees recorded; no rebind)");
6556
7227
  }
@@ -6567,7 +7238,7 @@ async function createBox(opts) {
6567
7238
  }
6568
7239
  if (opts.withPlaywright) {
6569
7240
  log("installing @playwright/cli@latest (--with-playwright)");
6570
- const result = await execa13(
7241
+ const result = await execa14(
6571
7242
  "docker",
6572
7243
  [
6573
7244
  "exec",
@@ -6720,7 +7391,7 @@ async function createBox(opts) {
6720
7391
  createdAt
6721
7392
  };
6722
7393
  await recordBox(record);
6723
- return { record, imageBuilt: built };
7394
+ return { record, imageBuilt: built, resync: resyncResult };
6724
7395
  }
6725
7396
  var DEFAULT_SHELL_SESSION = "shell";
6726
7397
  var SHELL_SESSION_PREFIX = `${DEFAULT_SHELL_SESSION}-`;
@@ -6768,7 +7439,7 @@ function parseShellSessionList(stdout) {
6768
7439
  return out;
6769
7440
  }
6770
7441
  async function listShellSessions(container, user) {
6771
- const res = await execa14(
7442
+ const res = await execa15(
6772
7443
  "docker",
6773
7444
  [
6774
7445
  "exec",
@@ -6792,7 +7463,7 @@ async function startShellSession(opts) {
6792
7463
  const login = opts.login !== false;
6793
7464
  const term = process.env["TERM"] ?? "xterm-256color";
6794
7465
  const cmd = login ? "bash -l" : "bash";
6795
- const result = await execa14(
7466
+ const result = await execa15(
6796
7467
  "docker",
6797
7468
  [
6798
7469
  "exec",
@@ -6841,7 +7512,7 @@ function buildShellSessionAttachArgv(container, sessionName, user) {
6841
7512
  }
6842
7513
  async function shellSessionInfo(container, sessionName, user) {
6843
7514
  const name = sessionName ?? DEFAULT_SHELL_SESSION;
6844
- const has = await execa14(
7515
+ const has = await execa15(
6845
7516
  "docker",
6846
7517
  ["exec", "--user", user ?? CONTAINER_USER, container, "tmux", "has-session", "-t", name],
6847
7518
  { reject: false }
@@ -6849,7 +7520,7 @@ async function shellSessionInfo(container, sessionName, user) {
6849
7520
  return { running: has.exitCode === 0, sessionName: name };
6850
7521
  }
6851
7522
  async function killShellSession(container, sessionName, user) {
6852
- const res = await execa14(
7523
+ const res = await execa15(
6853
7524
  "docker",
6854
7525
  [
6855
7526
  "exec",
@@ -6903,7 +7574,7 @@ async function getBoxEndpoints(record, engine, persisted) {
6903
7574
  for (const svc of persistedServices) pushService(svc.name, svc.port);
6904
7575
  } else {
6905
7576
  try {
6906
- const cfg = await loadConfig(join11(record.workspacePath, "agentbox.yaml"));
7577
+ const cfg = await loadConfig(join12(record.workspacePath, "agentbox.yaml"));
6907
7578
  if (!webServiceName) {
6908
7579
  webServiceName = cfg.services.find((s) => s.expose)?.name ?? null;
6909
7580
  }
@@ -7008,9 +7679,9 @@ function safeHost(url) {
7008
7679
  return "";
7009
7680
  }
7010
7681
  }
7011
- async function pathExists6(p) {
7682
+ async function pathExists7(p) {
7012
7683
  try {
7013
- await stat7(p);
7684
+ await stat9(p);
7014
7685
  return true;
7015
7686
  } catch {
7016
7687
  return false;
@@ -7043,12 +7714,20 @@ async function stopBox(idOrName) {
7043
7714
  await stopContainer(box.container);
7044
7715
  return box;
7045
7716
  }
7717
+ async function resyncBox(idOrName, onLog) {
7718
+ const box = await resolveBox(idOrName);
7719
+ const worktrees = box.gitWorktrees ?? [];
7720
+ if (worktrees.length === 0) return { repos: [], hadConflicts: false };
7721
+ const repos = await resyncWorkspaceFromHost({ container: box.container, worktrees, onLog });
7722
+ const hadConflicts = repos.some((r) => r.mergeConflicts.length > 0 || r.overlaySkipped.length > 0);
7723
+ return { repos, hadConflicts };
7724
+ }
7046
7725
  async function startBox(idOrName) {
7047
7726
  const box = await resolveBox(idOrName);
7048
7727
  for (const w of box.gitWorktrees ?? []) {
7049
- if (!await pathExists6(join12(w.hostMainRepo, ".git"))) {
7728
+ if (!await pathExists7(join13(w.hostMainRepo, ".git"))) {
7050
7729
  throw new Error(
7051
- `main repo for box worktree missing: ${join12(w.hostMainRepo, ".git")} (recreate the box)`
7730
+ `main repo for box worktree missing: ${join13(w.hostMainRepo, ".git")} (recreate the box)`
7052
7731
  );
7053
7732
  }
7054
7733
  }
@@ -7143,7 +7822,7 @@ async function getBoxHostPaths(idOrName) {
7143
7822
  }
7144
7823
  async function dirSizeBytes(path) {
7145
7824
  try {
7146
- const result = await execa15("du", ["-sk", path], { reject: false });
7825
+ const result = await execa16("du", ["-sk", path], { reject: false });
7147
7826
  if (result.exitCode !== 0) return null;
7148
7827
  const sizeKb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
7149
7828
  if (Number.isNaN(sizeKb)) return null;
@@ -7261,14 +7940,14 @@ async function destroyBox(idOrName, opts = {}) {
7261
7940
  let removedSnapshot = null;
7262
7941
  if (box.snapshotDir && !opts.keepSnapshot) {
7263
7942
  try {
7264
- await rm5(box.snapshotDir, { recursive: true, force: true });
7943
+ await rm6(box.snapshotDir, { recursive: true, force: true });
7265
7944
  removedSnapshot = box.snapshotDir;
7266
7945
  } catch {
7267
7946
  removedSnapshot = null;
7268
7947
  }
7269
7948
  }
7270
7949
  try {
7271
- await rm5(boxRunDirFor(box), { recursive: true, force: true });
7950
+ await rm6(boxRunDirFor(box), { recursive: true, force: true });
7272
7951
  } catch {
7273
7952
  }
7274
7953
  await removeBoxRecord(box.id);
@@ -7276,22 +7955,22 @@ async function destroyBox(idOrName, opts = {}) {
7276
7955
  }
7277
7956
  async function listSnapshotDirs() {
7278
7957
  try {
7279
- const entries = await readdir5(SNAPSHOTS_ROOT, { withFileTypes: true });
7280
- return entries.filter((e) => e.isDirectory()).map((e) => join12(SNAPSHOTS_ROOT, e.name));
7958
+ const entries = await readdir7(SNAPSHOTS_ROOT, { withFileTypes: true });
7959
+ return entries.filter((e) => e.isDirectory()).map((e) => join13(SNAPSHOTS_ROOT, e.name));
7281
7960
  } catch {
7282
7961
  return [];
7283
7962
  }
7284
7963
  }
7285
7964
  async function listBoxDirs() {
7286
7965
  try {
7287
- const entries = await readdir5(BOXES_ROOT, { withFileTypes: true });
7288
- return entries.filter((e) => e.isDirectory()).map((e) => join12(BOXES_ROOT, e.name));
7966
+ const entries = await readdir7(BOXES_ROOT, { withFileTypes: true });
7967
+ return entries.filter((e) => e.isDirectory()).map((e) => join13(BOXES_ROOT, e.name));
7289
7968
  } catch {
7290
7969
  return [];
7291
7970
  }
7292
7971
  }
7293
7972
  async function listCheckpointImageTags() {
7294
- const r = await execa15(
7973
+ const r = await execa16(
7295
7974
  "docker",
7296
7975
  ["image", "ls", "--format", "{{.Repository}}:{{.Tag}}", `${CHECKPOINT_IMAGE_PREFIX}*`],
7297
7976
  { reject: false }
@@ -7380,13 +8059,13 @@ async function pruneBoxes(opts = {}) {
7380
8059
  for (const v of orphanVolumes) await removeVolume(v);
7381
8060
  for (const d of orphanSnapshots) {
7382
8061
  try {
7383
- await rm5(d, { recursive: true, force: true });
8062
+ await rm6(d, { recursive: true, force: true });
7384
8063
  } catch {
7385
8064
  }
7386
8065
  }
7387
8066
  for (const d of orphanBoxDirs) {
7388
8067
  try {
7389
- await rm5(d, { recursive: true, force: true });
8068
+ await rm6(d, { recursive: true, force: true });
7390
8069
  } catch {
7391
8070
  }
7392
8071
  }
@@ -7399,7 +8078,7 @@ async function pruneBoxes(opts = {}) {
7399
8078
  } catch {
7400
8079
  }
7401
8080
  try {
7402
- await execa15("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
8081
+ await execa16("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
7403
8082
  } catch {
7404
8083
  }
7405
8084
  try {
@@ -7420,7 +8099,7 @@ async function pruneBoxes(opts = {}) {
7420
8099
  async function snapshotPresent(path) {
7421
8100
  if (!path) return false;
7422
8101
  try {
7423
- const s = await stat7(path);
8102
+ const s = await stat9(path);
7424
8103
  return s.isDirectory();
7425
8104
  } catch {
7426
8105
  return false;
@@ -7461,7 +8140,7 @@ function splitPair(raw) {
7461
8140
  return [parts[0].trim(), parts[1].trim()];
7462
8141
  }
7463
8142
  async function duBytes(path) {
7464
- const result = await execa16("du", ["-sk", path], { reject: false });
8143
+ const result = await execa17("du", ["-sk", path], { reject: false });
7465
8144
  if (result.exitCode !== 0) return null;
7466
8145
  const kb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
7467
8146
  return Number.isNaN(kb) ? null : kb * 1024;
@@ -7470,11 +8149,11 @@ async function volumeSizeBytes(name) {
7470
8149
  if (!name) return null;
7471
8150
  const engine = await detectEngine();
7472
8151
  if (engine === "orbstack") {
7473
- const live = join13(homedir10(), "OrbStack", "docker", "volumes", name);
8152
+ const live = join14(homedir11(), "OrbStack", "docker", "volumes", name);
7474
8153
  const sz = await duBytes(live);
7475
8154
  if (sz !== null) return sz;
7476
8155
  }
7477
- const df = await execa16(
8156
+ const df = await execa17(
7478
8157
  "docker",
7479
8158
  ["system", "df", "-v", "--format", "{{json .Volumes}}"],
7480
8159
  { reject: false }
@@ -7495,7 +8174,7 @@ async function volumeSizeBytes(name) {
7495
8174
  return null;
7496
8175
  }
7497
8176
  async function imageBytes(tag) {
7498
- const r = await execa16("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
8177
+ const r = await execa17("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
7499
8178
  reject: false
7500
8179
  });
7501
8180
  if (r.exitCode !== 0) return null;
@@ -7506,7 +8185,7 @@ async function projectCheckpointImageBytes(projectRoot, name) {
7506
8185
  return imageBytes(checkpointImageTag(projectRoot, name));
7507
8186
  }
7508
8187
  async function allCheckpointImagesBytes() {
7509
- const r = await execa16(
8188
+ const r = await execa17(
7510
8189
  "docker",
7511
8190
  [
7512
8191
  "image",
@@ -7533,7 +8212,7 @@ async function allCheckpointImagesBytes() {
7533
8212
  return any ? total : null;
7534
8213
  }
7535
8214
  async function agentboxHomeBytes() {
7536
- return duBytes(join13(homedir10(), ".agentbox"));
8215
+ return duBytes(join14(homedir11(), ".agentbox"));
7537
8216
  }
7538
8217
  function limitsFromRecord(record) {
7539
8218
  const r = record.resourceLimits;
@@ -7558,7 +8237,7 @@ function reconcileLimits(persisted, dockerJson) {
7558
8237
  };
7559
8238
  }
7560
8239
  async function containerWritableBytes(container) {
7561
- const r = await execa16(
8240
+ const r = await execa17(
7562
8241
  "docker",
7563
8242
  ["ps", "-a", "--filter", `name=^${container}$`, "--format", "{{.Size}}", "--size"],
7564
8243
  { reject: false }
@@ -7605,7 +8284,7 @@ async function boxResourceStats(record) {
7605
8284
  if (await inspectContainerStatus(record.container) !== "running") {
7606
8285
  return base;
7607
8286
  }
7608
- const proc = await execa16(
8287
+ const proc = await execa17(
7609
8288
  "docker",
7610
8289
  ["stats", "--no-stream", "--format", "{{json .}}", record.container],
7611
8290
  { reject: false }
@@ -7651,7 +8330,7 @@ function asText(s) {
7651
8330
  async function uploadToBox(box, hostSrc, boxDst) {
7652
8331
  const srcAbs = resolve4(hostSrc);
7653
8332
  if (!existsSync3(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
7654
- const srcBasename = basename4(srcAbs);
8333
+ const srcBasename = basename5(srcAbs);
7655
8334
  const srcParent = dirname22(srcAbs);
7656
8335
  let boxParent;
7657
8336
  let finalName;
@@ -7659,7 +8338,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7659
8338
  boxParent = boxDst.replace(/\/+$/, "") || "/";
7660
8339
  finalName = srcBasename;
7661
8340
  } else {
7662
- const isDir2 = await execa17(
8341
+ const isDir2 = await execa18(
7663
8342
  "docker",
7664
8343
  ["exec", box.container, "test", "-d", boxDst],
7665
8344
  { reject: false }
@@ -7673,7 +8352,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7673
8352
  }
7674
8353
  }
7675
8354
  const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
7676
- const mk = await execa17(
8355
+ const mk = await execa18(
7677
8356
  "docker",
7678
8357
  ["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
7679
8358
  { reject: false }
@@ -7681,7 +8360,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7681
8360
  if (mk.exitCode !== 0) {
7682
8361
  throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
7683
8362
  }
7684
- const packed = await execa17("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
8363
+ const packed = await execa18("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
7685
8364
  encoding: "buffer",
7686
8365
  reject: false,
7687
8366
  env: { ...process.env, COPYFILE_DISABLE: "1" }
@@ -7689,7 +8368,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7689
8368
  if (packed.exitCode !== 0) {
7690
8369
  throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
7691
8370
  }
7692
- const extract = await execa17(
8371
+ const extract = await execa18(
7693
8372
  "docker",
7694
8373
  ["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
7695
8374
  { input: packed.stdout, reject: false }
@@ -7699,7 +8378,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7699
8378
  }
7700
8379
  if (finalName !== srcBasename) {
7701
8380
  const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
7702
- const mv = await execa17(
8381
+ const mv = await execa18(
7703
8382
  "docker",
7704
8383
  ["exec", "--user", "root", box.container, "mv", initial, finalPath],
7705
8384
  { reject: false }
@@ -7710,7 +8389,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7710
8389
  );
7711
8390
  }
7712
8391
  }
7713
- const chown = await execa17(
8392
+ const chown = await execa18(
7714
8393
  "docker",
7715
8394
  ["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
7716
8395
  { reject: false }
@@ -7735,11 +8414,11 @@ async function downloadFromBox(box, boxSrc, hostDst) {
7735
8414
  finalName = srcBasename;
7736
8415
  } else {
7737
8416
  hostParent = dirname22(dstAbs);
7738
- finalName = basename4(dstAbs);
8417
+ finalName = basename5(dstAbs);
7739
8418
  }
7740
8419
  mkdirSync(hostParent, { recursive: true });
7741
8420
  const finalPath = posix.join(hostParent, finalName);
7742
- const packed = await execa17(
8421
+ const packed = await execa18(
7743
8422
  "docker",
7744
8423
  ["exec", box.container, "tar", "-C", srcParent, "-cf", "-", srcBasename],
7745
8424
  { encoding: "buffer", reject: false }
@@ -7747,7 +8426,7 @@ async function downloadFromBox(box, boxSrc, hostDst) {
7747
8426
  if (packed.exitCode !== 0) {
7748
8427
  throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
7749
8428
  }
7750
- const extract = await execa17("tar", ["-xf", "-", "-C", hostParent], {
8429
+ const extract = await execa18("tar", ["-xf", "-", "-C", hostParent], {
7751
8430
  input: packed.stdout,
7752
8431
  reject: false
7753
8432
  });
@@ -7770,6 +8449,7 @@ var dockerProvider = {
7770
8449
  checkpointRef: req.checkpointRef,
7771
8450
  fromBranch: req.fromBranch,
7772
8451
  useBranch: req.useBranch,
8452
+ resyncOnStart: req.resyncOnStart,
7773
8453
  image: req.image,
7774
8454
  allowPull: req.allowPull,
7775
8455
  imageRegistry: req.imageRegistry,
@@ -7792,7 +8472,8 @@ var dockerProvider = {
7792
8472
  const result = await createBox(opts);
7793
8473
  return {
7794
8474
  record: { ...result.record, provider: "docker" },
7795
- imageBuilt: result.imageBuilt
8475
+ imageBuilt: result.imageBuilt,
8476
+ resync: result.resync
7796
8477
  };
7797
8478
  },
7798
8479
  async start(box) {
@@ -7879,342 +8560,140 @@ var dockerProvider = {
7879
8560
  return {};
7880
8561
  }
7881
8562
  }
7882
- const { source } = await pullOrBuild(ref, fingerprint, {
7883
- onProgress: opts.onLog,
7884
- allowPull: opts.force ? false : opts.allowPull,
7885
- registry: opts.registry
7886
- });
7887
- if (fingerprint) {
7888
- opts.onLog?.(
7889
- `docker image ${ref} ${source}; recorded fingerprint ${fingerprint.contextSha256.slice(0, 12)}`
7890
- );
7891
- } else {
7892
- opts.onLog?.(
7893
- `docker image ${ref} ${source} (fingerprint unavailable, prepared state not written)`
7894
- );
7895
- }
7896
- return {};
7897
- }
7898
- };
7899
- var CLOUD_WORKSPACE = "/workspace";
7900
- async function pathExists7(p) {
7901
- try {
7902
- await stat8(p);
7903
- return true;
7904
- } catch {
7905
- return false;
7906
- }
7907
- }
7908
- async function findBrokenSymlinks2(root) {
7909
- const broken = [];
7910
- async function walk(dir) {
7911
- let entries;
7912
- try {
7913
- entries = await readdir6(dir, { withFileTypes: true });
7914
- } catch {
7915
- return;
7916
- }
7917
- for (const ent of entries) {
7918
- const full = join14(dir, ent.name);
7919
- if (ent.isSymbolicLink()) {
7920
- try {
7921
- await stat8(full);
7922
- } catch {
7923
- broken.push(relative2(root, full));
7924
- }
7925
- } else if (ent.isDirectory()) {
7926
- await walk(full);
7927
- }
7928
- }
7929
- }
7930
- await walk(root);
7931
- return broken;
7932
- }
7933
- async function mkStageDir(prefix) {
7934
- return mkdtemp3(join14(tmpdir3(), `agentbox-${prefix}-stage-`));
7935
- }
7936
- function emptyResult(warnings = []) {
7937
- return { tarballPath: null, cleanup: async () => {
7938
- }, warnings };
7939
- }
7940
- async function tarballFromDir(stageDir, agent) {
7941
- const tarballPath = join14(tmpdir3(), `agentbox-${agent}-${basename5(stageDir)}.tar.gz`);
7942
- await execa18("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
7943
- env: { ...process.env, COPYFILE_DISABLE: "1" }
7944
- });
7945
- return tarballPath;
7946
- }
7947
- function makeCleanup(paths) {
7948
- return async () => {
7949
- for (const p of paths) {
7950
- await rm6(p, { recursive: true, force: true });
7951
- }
7952
- };
7953
- }
7954
- async function stageSingleFileTarball(agent, sourcePath, tarballEntryName) {
7955
- const stageDir = await mkStageDir(agent);
7956
- let tarballPath = null;
8563
+ const { source } = await pullOrBuild(ref, fingerprint, {
8564
+ onProgress: opts.onLog,
8565
+ allowPull: opts.force ? false : opts.allowPull,
8566
+ registry: opts.registry
8567
+ });
8568
+ if (fingerprint) {
8569
+ opts.onLog?.(
8570
+ `docker image ${ref} ${source}; recorded fingerprint ${fingerprint.contextSha256.slice(0, 12)}`
8571
+ );
8572
+ } else {
8573
+ opts.onLog?.(
8574
+ `docker image ${ref} ${source} (fingerprint unavailable, prepared state not written)`
8575
+ );
8576
+ }
8577
+ return {};
8578
+ }
8579
+ };
8580
+ var BOX_WORKFLOWS_DIR = "/home/vscode/.claude/workflows";
8581
+ var BOX_DYNAMIC_SYNC_MANIFEST = "/home/vscode/.agentbox/dynamic-sync.json";
8582
+ var BOX_MEMORY_DIR = `${BOX_CLAUDE_PROJECT_DIR}/memory`;
8583
+ async function pathExists8(p) {
7957
8584
  try {
7958
- await copyFile(sourcePath, join14(stageDir, tarballEntryName));
7959
- tarballPath = await tarballFromDir(stageDir, agent);
7960
- return {
7961
- tarballPath,
7962
- cleanup: makeCleanup([stageDir, tarballPath]),
7963
- warnings: []
7964
- };
7965
- } catch (err) {
7966
- await rm6(stageDir, { recursive: true, force: true });
7967
- if (tarballPath) await rm6(tarballPath, { force: true });
7968
- throw err;
8585
+ await stat10(p);
8586
+ return true;
8587
+ } catch {
8588
+ return false;
7969
8589
  }
7970
8590
  }
7971
- var CLAUDE_RUNTIME_EXCLUDES = [
7972
- "projects",
7973
- "sessions",
7974
- "history.jsonl",
7975
- "file-history",
7976
- "shell-snapshots",
7977
- "backups",
7978
- "session-env",
7979
- "paste-cache",
7980
- "cache",
7981
- "telemetry",
7982
- "tasks",
7983
- "downloads",
7984
- "chrome",
7985
- "ide",
7986
- "debug",
7987
- "mcp-needs-auth-cache.json",
7988
- "stats-cache.json"
7989
- ];
7990
- async function stageClaudeStaticForUpload(opts = {}) {
7991
- const hostHome = opts.hostHome ?? homedir11();
7992
- const hostClaude = join14(hostHome, ".claude");
7993
- if (!await pathExists7(hostClaude)) return emptyResult();
7994
- const stageDir = await mkStageDir("claude-static");
7995
- let tarballPath = null;
8591
+ async function walkFiles(root, prefix = "") {
8592
+ let entries;
7996
8593
  try {
7997
- const broken = await findBrokenSymlinks2(hostClaude);
7998
- const excludes = [
7999
- "--exclude=node_modules",
8000
- "--exclude=.credentials.json",
8001
- ...CLAUDE_RUNTIME_EXCLUDES.map((p) => `--exclude=${p}`),
8002
- ...broken.map((r) => `--exclude=/${r}`)
8003
- ];
8004
- await execa18("rsync", [
8005
- "-a",
8006
- "--copy-unsafe-links",
8007
- ...excludes,
8008
- `${hostClaude}/`,
8009
- `${stageDir}/`
8010
- ]);
8011
- const settingsPath = join14(stageDir, "settings.json");
8012
- if (await pathExists7(settingsPath)) {
8013
- try {
8014
- const parsed = JSON.parse(await readFile7(settingsPath, "utf8"));
8015
- const filtered = filterHostHooks(parsed, hostHome);
8016
- if (filtered.removedCommands.length > 0) {
8017
- await writeFile4(settingsPath, JSON.stringify(filtered.data, null, 2));
8018
- }
8019
- } catch {
8020
- }
8021
- }
8022
- const hostClaudeJson = join14(hostHome, ".claude.json");
8023
- let working;
8024
- if (await pathExists7(hostClaudeJson)) {
8594
+ entries = await readdir8(root, { withFileTypes: true });
8595
+ } catch {
8596
+ return [];
8597
+ }
8598
+ const out = [];
8599
+ for (const ent of entries) {
8600
+ const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
8601
+ const full = join15(root, ent.name);
8602
+ if (ent.isDirectory()) {
8603
+ out.push(...await walkFiles(full, rel));
8604
+ } else if (ent.isFile()) {
8605
+ out.push(rel);
8606
+ } else if (ent.isSymbolicLink()) {
8025
8607
  try {
8026
- working = JSON.parse(await readFile7(hostClaudeJson, "utf8"));
8608
+ const s = await stat10(full);
8609
+ if (s.isFile()) out.push(rel);
8027
8610
  } catch {
8028
- working = null;
8029
8611
  }
8030
8612
  }
8031
- if (working === void 0 || working === null) {
8032
- working = {
8033
- installMethod: "native",
8034
- autoUpdates: false,
8035
- autoUpdatesProtectedForNative: true,
8036
- projects: { [CLOUD_WORKSPACE]: { hasTrustDialogAccepted: true } }
8037
- };
8038
- } else {
8039
- working = filterHostHooks(working, hostHome).data;
8040
- working = setInstallMethodNative(working).data;
8041
- if (opts.hostWorkspace) {
8042
- working = addProjectAlias(working, opts.hostWorkspace, CLOUD_WORKSPACE).data;
8613
+ }
8614
+ return out;
8615
+ }
8616
+ async function hashFile(absPath) {
8617
+ const buf = await readFile8(absPath);
8618
+ return createHash22("sha256").update(buf).digest("hex");
8619
+ }
8620
+ async function buildHostSet(name, hostDir, boxDst) {
8621
+ if (hostDir === null) return { dst: boxDst, files: {}, hostDir: null };
8622
+ const rels = await walkFiles(hostDir);
8623
+ const files = {};
8624
+ for (const rel of rels) {
8625
+ files[rel] = await hashFile(join15(hostDir, rel));
8626
+ }
8627
+ return { dst: boxDst, files, hostDir };
8628
+ }
8629
+ async function buildHostSyncManifest(workspacePath, hostHome = homedir12()) {
8630
+ const workflowsDir = join15(hostHome, ".claude", "workflows");
8631
+ const workflowsHost = await pathExists8(workflowsDir) ? workflowsDir : null;
8632
+ const memoryHost = await resolveClaudeMemoryDir(workspacePath, hostHome);
8633
+ const [workflows, memory] = await Promise.all([
8634
+ buildHostSet("workflows", workflowsHost, BOX_WORKFLOWS_DIR),
8635
+ buildHostSet("memory", memoryHost, BOX_MEMORY_DIR)
8636
+ ]);
8637
+ return { sets: { workflows, memory } };
8638
+ }
8639
+ var SET_NAMES = ["workflows", "memory"];
8640
+ function computeSyncDelta(host, box) {
8641
+ const uploads = [];
8642
+ const deletions = [];
8643
+ const nextSets = {};
8644
+ for (const name of SET_NAMES) {
8645
+ const hostSet = host.sets[name];
8646
+ const boxFiles = box?.sets?.[name]?.files ?? {};
8647
+ for (const [rel, hash] of Object.entries(hostSet.files)) {
8648
+ if (boxFiles[rel] !== hash) {
8649
+ uploads.push({
8650
+ set: name,
8651
+ rel,
8652
+ absSrc: join15(hostSet.hostDir, rel),
8653
+ dst: `${hostSet.dst}/${rel}`
8654
+ });
8043
8655
  }
8044
- working = trustWorkspace(working, CLOUD_WORKSPACE).data;
8045
8656
  }
8046
- await writeFile4(join14(stageDir, "_claude.json"), JSON.stringify(working, null, 2));
8047
- const pluginsDir = join14(stageDir, "plugins");
8048
- if (await pathExists7(pluginsDir)) {
8049
- try {
8050
- const entries = await readdir6(pluginsDir, { withFileTypes: true });
8051
- for (const ent of entries) {
8052
- if (!ent.isFile() || !ent.name.endsWith(".json")) continue;
8053
- const file = join14(pluginsDir, ent.name);
8054
- const raw = await readFile7(file, "utf8");
8055
- const replaced = raw.split(`${hostHome}/.claude/plugins/`).join("/home/vscode/.claude/plugins/");
8056
- if (replaced !== raw) await writeFile4(file, replaced);
8057
- }
8058
- } catch {
8657
+ for (const rel of Object.keys(boxFiles)) {
8658
+ if (!(rel in hostSet.files)) {
8659
+ deletions.push({ set: name, rel, dst: `${hostSet.dst}/${rel}` });
8059
8660
  }
8060
8661
  }
8061
- tarballPath = await tarballFromDir(stageDir, "claude-static");
8062
- return {
8063
- tarballPath,
8064
- cleanup: makeCleanup([stageDir, tarballPath]),
8065
- warnings: []
8066
- };
8067
- } catch (err) {
8068
- await rm6(stageDir, { recursive: true, force: true });
8069
- if (tarballPath) await rm6(tarballPath, { force: true });
8070
- throw err;
8071
- }
8072
- }
8073
- async function stageClaudeCredentialsForUpload() {
8074
- if (!await pathExists7(CREDENTIALS_BACKUP_FILE)) return emptyResult();
8075
- return stageSingleFileTarball("claude-creds", CREDENTIALS_BACKUP_FILE, ".credentials.json");
8076
- }
8077
- var CODEX_RSYNC_EXCLUDES = [
8078
- "--exclude=sessions",
8079
- "--exclude=log",
8080
- "--exclude=history.jsonl",
8081
- "--exclude=hooks.json",
8082
- "--exclude=logs_2.sqlite",
8083
- "--exclude=logs_2.sqlite-shm",
8084
- "--exclude=logs_2.sqlite-wal",
8085
- "--exclude=state_5.sqlite",
8086
- "--exclude=state_5.sqlite-shm",
8087
- "--exclude=state_5.sqlite-wal",
8088
- "--exclude=sqlite",
8089
- "--exclude=cache",
8090
- "--exclude=vendor_imports",
8091
- "--exclude=tmp",
8092
- // .tmp holds codex plugin sync state — ~100 MB of marketplace cache. Not
8093
- // the same as `tmp/`; both can exist side by side on a long-running host.
8094
- "--exclude=.tmp",
8095
- "--exclude=.codex-global-state.json",
8096
- "--exclude=.codex-global-state.json.bak",
8097
- "--exclude=.personality_migration",
8098
- "--exclude=shell_snapshots",
8099
- "--exclude=session_index.jsonl",
8100
- "--exclude=models_cache.json",
8101
- "--exclude=installation_id",
8102
- "--exclude=version.json"
8103
- ];
8104
- var CODEX_KEYCHAIN_WARNING = 'codex: ~/.codex/auth.json missing. On macOS the codex CLI defaults to storing the OAuth token in the system Keychain, which isn\'t reachable from a remote sandbox. To share creds with cloud boxes either:\n - add `cli_auth_credentials_store = "file"` to ~/.codex/config.toml then re-run `codex login`, or\n - set OPENAI_API_KEY in your environment, or\n - run `codex login --with-api-key` for a file-backed login.\nSkipping codex seed; in-box codex will prompt for sign-in.';
8105
- async function stageCodexStaticForUpload(opts = {}) {
8106
- const hostHome = opts.hostHome ?? homedir11();
8107
- const hostCodex = join14(hostHome, ".codex");
8108
- if (!await pathExists7(hostCodex)) return emptyResult();
8109
- const stageDir = await mkStageDir("codex-static");
8110
- let tarballPath = null;
8111
- try {
8112
- const codexBroken = await findBrokenSymlinks2(hostCodex);
8113
- await execa18("rsync", [
8114
- "-a",
8115
- "-L",
8116
- ...codexBroken.map((r) => `--exclude=/${r}`),
8117
- "--exclude=auth.json",
8118
- ...CODEX_RSYNC_EXCLUDES,
8119
- `${hostCodex}/`,
8120
- `${stageDir}/`
8121
- ]);
8122
- tarballPath = await tarballFromDir(stageDir, "codex-static");
8123
- return {
8124
- tarballPath,
8125
- cleanup: makeCleanup([stageDir, tarballPath]),
8126
- warnings: []
8127
- };
8128
- } catch (err) {
8129
- await rm6(stageDir, { recursive: true, force: true });
8130
- if (tarballPath) await rm6(tarballPath, { force: true });
8131
- throw err;
8662
+ nextSets[name] = { dst: hostSet.dst, files: hostSet.files };
8132
8663
  }
8664
+ return { uploads, deletions, nextManifest: { version: 1, sets: nextSets } };
8133
8665
  }
8134
- async function stageCodexCredentialsForUpload(opts = {}) {
8135
- const hostHome = opts.hostHome ?? homedir11();
8136
- const cloudBackup = join14(hostHome, ".agentbox", "codex-credentials.json");
8137
- if (await pathExists7(cloudBackup)) {
8138
- return stageSingleFileTarball("codex-creds", cloudBackup, "auth.json");
8666
+ async function stageDynamicSyncTarball(uploads) {
8667
+ if (uploads.length === 0) {
8668
+ return { tarballPath: null, cleanup: async () => {
8669
+ } };
8139
8670
  }
8140
- const hostAuth = join14(hostHome, ".codex", "auth.json");
8141
- if (!await pathExists7(hostAuth)) return emptyResult([CODEX_KEYCHAIN_WARNING]);
8142
- return stageSingleFileTarball("codex-creds", hostAuth, "auth.json");
8143
- }
8144
- var OPENCODE_DATA_EXCLUDES = [
8145
- "--exclude=storage",
8146
- "--exclude=log",
8147
- "--exclude=project",
8148
- "--exclude=cache",
8149
- "--exclude=bin",
8150
- "--exclude=repos",
8151
- "--exclude=snapshot",
8152
- "--exclude=config",
8153
- "--exclude=opencode.db",
8154
- "--exclude=opencode.db-shm",
8155
- "--exclude=opencode.db-wal"
8156
- ];
8157
- async function stageOpencodeStaticForUpload(opts = {}) {
8158
- const hostHome = opts.hostHome ?? homedir11();
8159
- const hostData = join14(hostHome, ".local", "share", "opencode");
8160
- const hostConfig = join14(hostHome, ".config", "opencode");
8161
- const hasData = await pathExists7(hostData);
8162
- const hasConfig = await pathExists7(hostConfig);
8163
- if (!hasData && !hasConfig) return emptyResult();
8164
- const stageDir = await mkStageDir("opencode-static");
8671
+ const stageDir = await mkdtemp4(join15(tmpdir4(), "agentbox-dynsync-stage-"));
8165
8672
  let tarballPath = null;
8166
8673
  try {
8167
- if (hasData) {
8168
- const dataBroken = await findBrokenSymlinks2(hostData);
8169
- await execa18("rsync", [
8170
- "-a",
8171
- "-L",
8172
- ...dataBroken.map((r) => `--exclude=/${r}`),
8173
- "--exclude=auth.json",
8174
- ...OPENCODE_DATA_EXCLUDES,
8175
- `${hostData}/`,
8176
- `${stageDir}/`
8177
- ]);
8178
- }
8179
- if (hasConfig) {
8180
- const configStage = join14(stageDir, "config");
8181
- const cfgBroken = await findBrokenSymlinks2(hostConfig);
8182
- await execa18("rsync", [
8183
- "-a",
8184
- "-L",
8185
- ...cfgBroken.map((r) => `--exclude=/${r}`),
8186
- `${hostConfig}/`,
8187
- `${configStage}/`
8188
- ]);
8189
- }
8190
- tarballPath = await tarballFromDir(stageDir, "opencode-static");
8674
+ for (const up of uploads) {
8675
+ const target = join15(stageDir, up.set, up.rel);
8676
+ await mkdir8(dirname32(target), { recursive: true });
8677
+ await copyFile2(up.absSrc, target);
8678
+ }
8679
+ tarballPath = join15(tmpdir4(), `agentbox-dynsync-${basename6(stageDir)}.tar.gz`);
8680
+ await execa19("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
8681
+ env: { ...process.env, COPYFILE_DISABLE: "1" }
8682
+ });
8683
+ const tp = tarballPath;
8191
8684
  return {
8192
- tarballPath,
8193
- cleanup: makeCleanup([stageDir, tarballPath]),
8194
- warnings: []
8685
+ tarballPath: tp,
8686
+ cleanup: async () => {
8687
+ await rm7(stageDir, { recursive: true, force: true });
8688
+ await rm7(tp, { force: true });
8689
+ }
8195
8690
  };
8196
8691
  } catch (err) {
8197
- await rm6(stageDir, { recursive: true, force: true });
8198
- if (tarballPath) await rm6(tarballPath, { force: true });
8692
+ await rm7(stageDir, { recursive: true, force: true });
8693
+ if (tarballPath) await rm7(tarballPath, { force: true });
8199
8694
  throw err;
8200
8695
  }
8201
8696
  }
8202
- async function stageOpencodeCredentialsForUpload(opts = {}) {
8203
- const hostHome = opts.hostHome ?? homedir11();
8204
- const cloudBackup = join14(hostHome, ".agentbox", "opencode-credentials.json");
8205
- if (await pathExists7(cloudBackup)) {
8206
- return stageSingleFileTarball("opencode-creds", cloudBackup, "auth.json");
8207
- }
8208
- const hostAuth = join14(hostHome, ".local", "share", "opencode", "auth.json");
8209
- if (!await pathExists7(hostAuth)) return emptyResult();
8210
- return stageSingleFileTarball("opencode-creds", hostAuth, "auth.json");
8211
- }
8212
- async function stageOpencodeStateForUpload(opts = {}) {
8213
- const hostHome = opts.hostHome ?? homedir11();
8214
- const hostModel = join14(hostHome, ".local", "state", "opencode", "model.json");
8215
- if (!await pathExists7(hostModel)) return emptyResult();
8216
- return stageSingleFileTarball("opencode-state", hostModel, "model.json");
8217
- }
8218
8697
  function browserSessionActive(stdout, exitCode) {
8219
8698
  return exitCode === 0 && !/no active sessions/i.test(stdout);
8220
8699
  }
@@ -8249,6 +8728,9 @@ export {
8249
8728
  loadEffectiveConfig,
8250
8729
  resolveDefaultCheckpoint,
8251
8730
  defaultCheckpointConfigKey,
8731
+ resolveBoxSize,
8732
+ resolveBoxImage,
8733
+ boxImageConfigKey,
8252
8734
  setConfigValue,
8253
8735
  unsetConfigValue,
8254
8736
  listProjectsConfigured,
@@ -8299,6 +8781,30 @@ export {
8299
8781
  pullToHost,
8300
8782
  openInFinder,
8301
8783
  ExportError,
8784
+ carrySourceHash,
8785
+ copyCarryPathsToBox,
8786
+ CREDENTIALS_BACKUP_FILE,
8787
+ CODEX_CREDENTIALS_BACKUP_FILE,
8788
+ OPENCODE_CREDENTIALS_BACKUP_FILE,
8789
+ isRealAgentCredential,
8790
+ hostClaudeBackupExpired,
8791
+ parseExtractResult,
8792
+ extractVolumeAuthToBackup,
8793
+ extractCodexCredentials,
8794
+ extractOpencodeCredentials,
8795
+ hostBackupHasCredentials,
8796
+ parseSyncResult,
8797
+ syncClaudeCredentials,
8798
+ encodeClaudeProjectsKey,
8799
+ BOX_CLAUDE_PROJECT_DIR,
8800
+ resolveClaudeMemoryDir,
8801
+ stageClaudeStaticForUpload,
8802
+ stageClaudeCredentialsForUpload,
8803
+ stageCodexStaticForUpload,
8804
+ stageCodexCredentialsForUpload,
8805
+ stageOpencodeStaticForUpload,
8806
+ stageOpencodeCredentialsForUpload,
8807
+ stageOpencodeStateForUpload,
8302
8808
  SHARED_CLAUDE_VOLUME,
8303
8809
  DEFAULT_CLAUDE_SESSION,
8304
8810
  CONTAINER_USER,
@@ -8324,18 +8830,6 @@ export {
8324
8830
  attachClaudeSession,
8325
8831
  claudeSessionInfo,
8326
8832
  pullClaudeExtras,
8327
- CREDENTIALS_BACKUP_FILE,
8328
- CODEX_CREDENTIALS_BACKUP_FILE,
8329
- OPENCODE_CREDENTIALS_BACKUP_FILE,
8330
- isRealAgentCredential,
8331
- hostClaudeBackupExpired,
8332
- parseExtractResult,
8333
- extractVolumeAuthToBackup,
8334
- extractCodexCredentials,
8335
- extractOpencodeCredentials,
8336
- hostBackupHasCredentials,
8337
- parseSyncResult,
8338
- syncClaudeCredentials,
8339
8833
  SHARED_CODEX_VOLUME,
8340
8834
  DEFAULT_CODEX_SESSION,
8341
8835
  resolveCodexVolume,
@@ -8402,6 +8896,7 @@ export {
8402
8896
  checkpointImageTag,
8403
8897
  projectCheckpointsDir,
8404
8898
  listCheckpoints,
8899
+ listAllCheckpoints,
8405
8900
  resolveCheckpoint,
8406
8901
  listAllCheckpointImages,
8407
8902
  removeCheckpoint,
@@ -8452,6 +8947,7 @@ export {
8452
8947
  pauseBox,
8453
8948
  unpauseBox,
8454
8949
  stopBox,
8950
+ resyncBox,
8455
8951
  startBox,
8456
8952
  openBoxInFinder,
8457
8953
  getBoxHostPaths,
@@ -8468,14 +8964,13 @@ export {
8468
8964
  uploadToBox,
8469
8965
  downloadFromBox,
8470
8966
  dockerProvider,
8471
- stageClaudeStaticForUpload,
8472
- stageClaudeCredentialsForUpload,
8473
- stageCodexStaticForUpload,
8474
- stageCodexCredentialsForUpload,
8475
- stageOpencodeStaticForUpload,
8476
- stageOpencodeCredentialsForUpload,
8477
- stageOpencodeStateForUpload,
8967
+ BOX_WORKFLOWS_DIR,
8968
+ BOX_DYNAMIC_SYNC_MANIFEST,
8969
+ BOX_MEMORY_DIR,
8970
+ buildHostSyncManifest,
8971
+ computeSyncDelta,
8972
+ stageDynamicSyncTarball,
8478
8973
  browserSessionActive,
8479
8974
  ensureBoxBrowser
8480
8975
  };
8481
- //# sourceMappingURL=chunk-ZJXTIH6C.js.map
8976
+ //# sourceMappingURL=chunk-IZXPJPPV.js.map