@madarco/agentbox 0.11.3 → 0.13.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 (37) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +11 -0
  3. package/dist/{_cloud-attach-XWCVLO5V.js → _cloud-attach-HJC672UR.js} +3 -3
  4. package/dist/{chunk-ZGVMN54V.js → chunk-2LF5YILI.js} +21 -3
  5. package/dist/chunk-2LF5YILI.js.map +1 -0
  6. package/dist/{chunk-MXXXKJYS.js → chunk-4NQXNQ53.js} +234 -83
  7. package/dist/chunk-4NQXNQ53.js.map +1 -0
  8. package/dist/{chunk-ZJXTIH6C.js → chunk-B4QG2MCW.js} +1352 -851
  9. package/dist/chunk-B4QG2MCW.js.map +1 -0
  10. package/dist/{chunk-GYJ62GFL.js → chunk-QYRK5H6Q.js} +297 -33
  11. package/dist/chunk-QYRK5H6Q.js.map +1 -0
  12. package/dist/{dist-WMQDMTWS.js → dist-7KVUIKJX.js} +8 -5
  13. package/dist/dist-7KVUIKJX.js.map +1 -0
  14. package/dist/{dist-RAZP76VX.js → dist-JAN5VABY.js} +3 -3
  15. package/dist/{dist-ASLPRUQR.js → dist-OG6NW6SM.js} +28 -2
  16. package/dist/{dist-PTJ6CEQY.js → dist-OPIBZ7XM.js} +4 -4
  17. package/dist/index.js +1720 -876
  18. package/dist/index.js.map +1 -1
  19. package/package.json +4 -4
  20. package/runtime/docker/packages/ctl/dist/bin.cjs +341 -5
  21. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +86 -5
  22. package/runtime/hetzner/ctl.cjs +341 -5
  23. package/runtime/hetzner/gh-shim +86 -5
  24. package/runtime/relay/bin.cjs +293 -4
  25. package/runtime/vercel/ctl.cjs +341 -5
  26. package/runtime/vercel/gh-shim +86 -5
  27. package/share/host-skills/agentbox/SKILL.md +16 -5
  28. package/share/host-skills/agentbox-info/SKILL.md +3 -1
  29. package/dist/chunk-GYJ62GFL.js.map +0 -1
  30. package/dist/chunk-MXXXKJYS.js.map +0 -1
  31. package/dist/chunk-ZGVMN54V.js.map +0 -1
  32. package/dist/chunk-ZJXTIH6C.js.map +0 -1
  33. package/dist/dist-WMQDMTWS.js.map +0 -1
  34. /package/dist/{_cloud-attach-XWCVLO5V.js.map → _cloud-attach-HJC672UR.js.map} +0 -0
  35. /package/dist/{dist-RAZP76VX.js.map → dist-JAN5VABY.js.map} +0 -0
  36. /package/dist/{dist-ASLPRUQR.js.map → dist-OG6NW6SM.js.map} +0 -0
  37. /package/dist/{dist-PTJ6CEQY.js.map → dist-OPIBZ7XM.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",
@@ -771,7 +782,8 @@ var BUILT_IN_DEFAULTS = {
771
782
  sessionName: "opencode"
772
783
  },
773
784
  attach: {
774
- openIn: "split"
785
+ openIn: "split",
786
+ cmuxStatus: true
775
787
  },
776
788
  code: {
777
789
  ide: "auto",
@@ -860,6 +872,35 @@ var KEY_REGISTRY = [
860
872
  description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
861
873
  advanced: true
862
874
  },
875
+ {
876
+ key: "box.size",
877
+ type: "string",
878
+ 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."
879
+ },
880
+ {
881
+ key: "box.sizeDocker",
882
+ type: "string",
883
+ description: "Per-provider override of `box.size` for docker. Reserved \u2014 docker sizing is controlled via `box.memory` / `box.cpus` / `box.disk`.",
884
+ advanced: true
885
+ },
886
+ {
887
+ key: "box.sizeDaytona",
888
+ type: "string",
889
+ 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.",
890
+ advanced: true
891
+ },
892
+ {
893
+ key: "box.sizeHetzner",
894
+ type: "string",
895
+ description: "Per-provider override of `box.size` for hetzner. Server type string (e.g. `cx23`, `cx33`, `cx43`).",
896
+ advanced: true
897
+ },
898
+ {
899
+ key: "box.sizeVercel",
900
+ type: "string",
901
+ description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
902
+ advanced: true
903
+ },
863
904
  {
864
905
  key: "checkpoint.maxLayers",
865
906
  type: "int",
@@ -876,6 +917,11 @@ var KEY_REGISTRY = [
876
917
  type: "bool",
877
918
  description: "Copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at box create time (gitignore-bypassing)."
878
919
  },
920
+ {
921
+ key: "box.resyncOnStart",
922
+ type: "bool",
923
+ 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)."
924
+ },
879
925
  {
880
926
  key: "box.vnc",
881
927
  type: "bool",
@@ -899,7 +945,31 @@ var KEY_REGISTRY = [
899
945
  {
900
946
  key: "box.image",
901
947
  type: "string",
902
- description: "Box image ref (advanced).",
948
+ 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).",
949
+ advanced: true
950
+ },
951
+ {
952
+ key: "box.imageDocker",
953
+ type: "string",
954
+ description: "Per-provider override of `box.image` for docker (local docker image ref, e.g. `agentbox/box:dev`). Wins over the generic when set.",
955
+ advanced: true
956
+ },
957
+ {
958
+ key: "box.imageDaytona",
959
+ type: "string",
960
+ description: "Per-provider override of `box.image` for daytona (named snapshot, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider daytona`.",
961
+ advanced: true
962
+ },
963
+ {
964
+ key: "box.imageHetzner",
965
+ type: "string",
966
+ description: "Per-provider override of `box.image` for hetzner (image description, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider hetzner`.",
967
+ advanced: true
968
+ },
969
+ {
970
+ key: "box.imageVercel",
971
+ type: "string",
972
+ description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
903
973
  advanced: true
904
974
  },
905
975
  {
@@ -983,7 +1053,12 @@ var KEY_REGISTRY = [
983
1053
  key: "attach.openIn",
984
1054
  type: "enum",
985
1055
  enumValues: ["split", "window", "tab", "same"],
986
- description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux or iTerm2: `split` (tmux split-window / iTerm2 vertical split, default), `window` (tmux new-window / new iTerm2 window), `tab` (tmux new-window / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/iTerm2 every value behaves like `same`."
1056
+ description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux, cmux, or iTerm2: `split` (tmux split-window / cmux new-split / iTerm2 vertical split, default \u2014 same workspace), `window` (tmux new-window / cmux new-workspace / new iTerm2 window), `tab` (tmux new-window / cmux new-surface tab in the current pane, same workspace / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/cmux/iTerm2 every value behaves like `same`."
1057
+ },
1058
+ {
1059
+ key: "attach.cmuxStatus",
1060
+ type: "bool",
1061
+ description: "When attached inside cmux, reflect the box agent's live activity on its cmux workspace (colour + description: blue=working, amber=needs input, idle clears; restored on detach) and, when the agent needs input, flag the box's own tab via a cmux notification (tab badge + reorder + desktop notification) so it stands out among sibling tabs. cmux only; no-op in other terminals."
987
1062
  },
988
1063
  {
989
1064
  key: "code.ide",
@@ -1462,6 +1537,23 @@ function defaultCheckpointConfigKey(provider) {
1462
1537
  if (provider === "vercel") return "box.defaultCheckpointVercel";
1463
1538
  return "box.defaultCheckpoint";
1464
1539
  }
1540
+ function resolveBoxSize(cfg, provider) {
1541
+ const perProvider = provider === "daytona" ? cfg.box.sizeDaytona : provider === "hetzner" ? cfg.box.sizeHetzner : provider === "vercel" ? cfg.box.sizeVercel : cfg.box.sizeDocker;
1542
+ if (perProvider && perProvider.length > 0) return perProvider;
1543
+ return cfg.box.size;
1544
+ }
1545
+ function resolveBoxImage(cfg, provider) {
1546
+ const perProvider = provider === "daytona" ? cfg.box.imageDaytona : provider === "hetzner" ? cfg.box.imageHetzner : provider === "vercel" ? cfg.box.imageVercel : cfg.box.imageDocker;
1547
+ if (perProvider && perProvider.length > 0) return perProvider;
1548
+ return cfg.box.image;
1549
+ }
1550
+ function boxImageConfigKey(provider) {
1551
+ if (provider === "docker") return "box.imageDocker";
1552
+ if (provider === "daytona") return "box.imageDaytona";
1553
+ if (provider === "hetzner") return "box.imageHetzner";
1554
+ if (provider === "vercel") return "box.imageVercel";
1555
+ return "box.image";
1556
+ }
1465
1557
  async function setConfigValue(scope, key, value, cwd, opts = {}) {
1466
1558
  if (!lookupKey(key)) {
1467
1559
  throw new UserConfigError(`unknown key "${key}"`);
@@ -1656,26 +1748,30 @@ async function touchProjectMeta(absPath) {
1656
1748
  }
1657
1749
 
1658
1750
  // ../../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";
1751
+ import { copyFile, mkdtemp, readdir as readdir22, readFile as readFile32, rm as rm3, stat as stat22, writeFile as writeFile3 } from "fs/promises";
1752
+ import { homedir as homedir22, tmpdir } from "os";
1753
+ import { basename as basename2, join as join32, relative } from "path";
1661
1754
  import { execa as execa4 } from "execa";
1755
+ import { chmod, mkdir as mkdir22, readFile as readFile24 } from "fs/promises";
1756
+ import { basename as basename3, join as join22 } from "path";
1757
+ import { execa as execa3 } from "execa";
1662
1758
  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";
1759
+ import { stat as stat42 } from "fs/promises";
1669
1760
  import { homedir as homedir4 } from "os";
1670
- import { join as join5 } from "path";
1761
+ import { join as join52 } from "path";
1671
1762
  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";
1763
+ import { spawnSync as spawnSync3 } from "child_process";
1764
+ import { stat as stat5 } from "fs/promises";
1676
1765
  import { homedir as homedir5 } from "os";
1677
1766
  import { join as join6 } from "path";
1767
+ import { execa as execa7 } from "execa";
1768
+ import { randomBytes as randomBytes3 } from "crypto";
1678
1769
  import { execa as execa8 } from "execa";
1770
+ import { existsSync } from "fs";
1771
+ import { readFile as readFile52 } from "fs/promises";
1772
+ import { homedir as homedir6 } from "os";
1773
+ import { join as join7 } from "path";
1774
+ import { execa as execa9 } from "execa";
1679
1775
 
1680
1776
  // ../../packages/core/dist/index.js
1681
1777
  import { randomBytes } from "crypto";
@@ -1734,25 +1830,25 @@ function generateBoxId() {
1734
1830
  }
1735
1831
 
1736
1832
  // ../../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
1833
  import { execa as execa10 } from "execa";
1745
- import { stat as stat5 } from "fs/promises";
1834
+ import { mkdir as mkdir42, readdir as readdir42, rm as rm32, stat as stat6 } from "fs/promises";
1835
+ import { homedir as homedir7, platform } from "os";
1836
+ import { join as join8, resolve as resolve2 } from "path";
1837
+ import { mkdir as mkdir5, mkdtemp as mkdtemp3, readFile as readFile6, readdir as readdir5, rm as rm4, writeFile as writeFile32 } from "fs/promises";
1838
+ import { homedir as homedir8, tmpdir as tmpdir3 } from "os";
1839
+ import { basename as basename32, join as join9 } from "path";
1746
1840
  import { execa as execa11 } from "execa";
1841
+ import { stat as stat7 } from "fs/promises";
1747
1842
  import { execa as execa12 } from "execa";
1843
+ import { execa as execa13 } from "execa";
1748
1844
  import { spawn } from "child_process";
1749
1845
  import { randomBytes as randomBytes22 } from "crypto";
1750
1846
  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";
1847
+ import { cp, mkdir as mkdir6, readFile as readFile7, readdir as readdir6, rename as rename3, rm as rm5, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
1752
1848
  import { request as httpRequest } from "http";
1753
1849
  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";
1850
+ import { homedir as homedir9 } from "os";
1851
+ import { dirname as dirname3, join as join10, resolve as resolve22, sep } from "path";
1756
1852
  import { setTimeout as delay2 } from "timers/promises";
1757
1853
  import { fileURLToPath } from "url";
1758
1854
 
@@ -1794,6 +1890,8 @@ var GH_PR_OPS = [
1794
1890
  "create",
1795
1891
  "view",
1796
1892
  "list",
1893
+ "diff",
1894
+ "checks",
1797
1895
  "comment",
1798
1896
  "review",
1799
1897
  "merge",
@@ -1801,6 +1899,13 @@ var GH_PR_OPS = [
1801
1899
  "close",
1802
1900
  "reopen"
1803
1901
  ];
1902
+ var PR_REVIEW_COMMENT = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments(\?.*)?$/;
1903
+ var PR_REVIEW_COMMENT_REPLY = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments\/\d+\/replies(\?.*)?$/;
1904
+ var GH_API_WRITE_ALLOWED_ENDPOINTS = [
1905
+ PR_REVIEW_COMMENT,
1906
+ PR_REVIEW_COMMENT_REPLY
1907
+ ];
1908
+ var GH_API_ALLOWED_ENDPOINTS = [...GH_API_WRITE_ALLOWED_ENDPOINTS];
1804
1909
  function injectPrCreateHead(op, branch, args) {
1805
1910
  if (op !== "create") return args;
1806
1911
  if (!branch || branch === "HEAD") return args;
@@ -1971,21 +2076,22 @@ function queueLogPath(id) {
1971
2076
  }
1972
2077
 
1973
2078
  // ../../packages/sandbox-docker/dist/index.js
2079
+ import { execa as execa16 } from "execa";
2080
+ import { readdir as readdir7, rm as rm6, stat as stat9 } from "fs/promises";
2081
+ import { join as join13 } from "path";
1974
2082
  import { execa as execa15 } from "execa";
1975
- import { readdir as readdir5, rm as rm5, stat as stat7 } from "fs/promises";
1976
2083
  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";
2084
+ import { homedir as homedir11 } from "os";
2085
+ import { join as join14 } from "path";
1984
2086
  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";
2087
+ import { existsSync as existsSync3, mkdirSync, renameSync, statSync } from "fs";
2088
+ import { basename as basename5, dirname as dirname22, posix, resolve as resolve4 } from "path";
1988
2089
  import { execa as execa18 } from "execa";
2090
+ import { createHash as createHash22 } from "crypto";
2091
+ import { copyFile as copyFile2, mkdir as mkdir8, mkdtemp as mkdtemp4, readdir as readdir8, readFile as readFile8, rm as rm7, stat as stat10 } from "fs/promises";
2092
+ import { homedir as homedir12, tmpdir as tmpdir4 } from "os";
2093
+ import { basename as basename6, dirname as dirname32, join as join15 } from "path";
2094
+ import { execa as execa19 } from "execa";
1989
2095
  function isHostPathHookCommand(command, hostHome) {
1990
2096
  if (typeof command !== "string" || command.length === 0) return false;
1991
2097
  if (hostHome.length === 0) return false;
@@ -2402,7 +2508,7 @@ async function getDockerContext() {
2402
2508
  const ctx = (result.stdout ?? "").trim();
2403
2509
  return ctx.length > 0 ? ctx : void 0;
2404
2510
  }
2405
- var BOXES_ROOT = join4(homedir3(), ".agentbox", "boxes");
2511
+ var BOXES_ROOT = join5(homedir2(), ".agentbox", "boxes");
2406
2512
  function boxDirSegment(box) {
2407
2513
  const mnemonic = sanitizeMnemonic(box.name);
2408
2514
  const n = box.projectIndex;
@@ -2412,14 +2518,14 @@ function boxDirSegment(box) {
2412
2518
  return `${box.id}-${mnemonic}`;
2413
2519
  }
2414
2520
  function boxRunDirFor(box) {
2415
- return join4(BOXES_ROOT, boxDirSegment(box));
2521
+ return join5(BOXES_ROOT, boxDirSegment(box));
2416
2522
  }
2417
2523
  function boxStatusPathFor(box) {
2418
- return join4(boxRunDirFor(box), "status.json");
2524
+ return join5(boxRunDirFor(box), "status.json");
2419
2525
  }
2420
2526
  async function readBoxStatus(box) {
2421
2527
  try {
2422
- const raw = await readFile4(boxStatusPathFor(box), "utf8");
2528
+ const raw = await readFile5(boxStatusPathFor(box), "utf8");
2423
2529
  const parsed = JSON.parse(raw);
2424
2530
  if (parsed.schema !== 1) return null;
2425
2531
  return parsed;
@@ -2428,13 +2534,13 @@ async function readBoxStatus(box) {
2428
2534
  }
2429
2535
  }
2430
2536
  function orbstackVolumePath(volume, ...sub) {
2431
- return join4(homedir3(), "OrbStack", "docker", "volumes", volume, ...sub);
2537
+ return join5(homedir2(), "OrbStack", "docker", "volumes", volume, ...sub);
2432
2538
  }
2433
2539
  async function getHostPaths(record) {
2434
2540
  const boxDir = boxRunDirFor(record);
2435
2541
  return {
2436
2542
  boxDir,
2437
- mergedExport: join4(boxDir, "workspace")
2543
+ mergedExport: join5(boxDir, "workspace")
2438
2544
  };
2439
2545
  }
2440
2546
  async function hasContainerPath(container, path) {
@@ -2444,7 +2550,7 @@ async function hasContainerPath(container, path) {
2444
2550
  async function refreshExport(record, opts = {}) {
2445
2551
  const paths = await getHostPaths(record);
2446
2552
  const excludeNodeModules = !opts.includeNodeModules;
2447
- await mkdir3(paths.mergedExport, { recursive: true });
2553
+ await mkdir4(paths.mergedExport, { recursive: true });
2448
2554
  const bindAvailable = await hasContainerPath(record.container, CONTAINER_EXPORT_MERGED);
2449
2555
  if (bindAvailable) {
2450
2556
  const args = ["rsync", "-a", "--delete"];
@@ -2640,7 +2746,7 @@ async function pullToHost(record, opts = {}) {
2640
2746
  let scratchDir;
2641
2747
  if (opts.noRefresh) {
2642
2748
  scratchDir = paths.mergedExport;
2643
- await mkdir3(scratchDir, { recursive: true });
2749
+ await mkdir4(scratchDir, { recursive: true });
2644
2750
  } else {
2645
2751
  const refreshed = await refreshExport(record, {
2646
2752
  includeNodeModules: opts.includeNodeModules
@@ -2719,7 +2825,7 @@ async function openInFinder(record, opts) {
2719
2825
  if (opts.noRefresh) {
2720
2826
  const paths = await getHostPaths(record);
2721
2827
  hostPath = paths.mergedExport;
2722
- await mkdir3(hostPath, { recursive: true });
2828
+ await mkdir4(hostPath, { recursive: true });
2723
2829
  } else {
2724
2830
  const refreshed = await refreshExport(record, opts);
2725
2831
  hostPath = refreshed.hostPath;
@@ -2744,6 +2850,36 @@ var ExportError = class extends Error {
2744
2850
  stdout;
2745
2851
  stderr;
2746
2852
  };
2853
+ async function carrySourceHash(entry) {
2854
+ if (entry.kind === "missing") return void 0;
2855
+ try {
2856
+ if (entry.kind === "file") {
2857
+ return createHash3("sha256").update(await readFile5(entry.absSrc)).digest("hex");
2858
+ }
2859
+ const h = createHash3("sha256");
2860
+ const walk = async (dir, rel) => {
2861
+ const names = (await readdir4(dir)).sort();
2862
+ for (const name of names) {
2863
+ const abs = join5(dir, name);
2864
+ const relPath = rel ? `${rel}/${name}` : name;
2865
+ const st = await stat4(abs);
2866
+ if (st.isDirectory()) {
2867
+ h.update(`d\0${relPath}
2868
+ `);
2869
+ await walk(abs, relPath);
2870
+ } else {
2871
+ h.update(`f\0${relPath}\0`);
2872
+ h.update(await readFile5(abs));
2873
+ h.update("\n");
2874
+ }
2875
+ }
2876
+ };
2877
+ await walk(entry.absSrc, "");
2878
+ return h.digest("hex");
2879
+ } catch {
2880
+ return void 0;
2881
+ }
2882
+ }
2747
2883
  async function copyCarryPathsToBox(opts) {
2748
2884
  const log = opts.onLog ?? (() => {
2749
2885
  });
@@ -2759,7 +2895,12 @@ async function copyCarryPathsToBox(opts) {
2759
2895
  try {
2760
2896
  await copyOneEntry(opts.container, entry);
2761
2897
  copied += 1;
2762
- applied.push({ src: entry.absSrc, dest: entry.absDest, bytes: entry.bytes ?? 0 });
2898
+ applied.push({
2899
+ src: entry.absSrc,
2900
+ dest: entry.absDest,
2901
+ bytes: entry.bytes ?? 0,
2902
+ hash: await carrySourceHash(entry)
2903
+ });
2763
2904
  } catch (err) {
2764
2905
  const msg = err instanceof Error ? err.message : String(err);
2765
2906
  errors.push(`${where}: ${msg}`);
@@ -2774,13 +2915,13 @@ async function copyOneEntry(container, entry) {
2774
2915
  const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
2775
2916
  const boxDestParent = boxDest.endsWith("/") ? boxDest.slice(0, -1) : boxDest;
2776
2917
  const parentDir = entry.kind === "dir" ? boxDestParent : dirnameUnix(boxDestParent);
2777
- const mkdir8 = await execa22(
2918
+ const mkdir9 = await execa22(
2778
2919
  "docker",
2779
2920
  ["exec", "--user", "0:0", container, "mkdir", "-p", parentDir],
2780
2921
  { reject: false }
2781
2922
  );
2782
- if (mkdir8.exitCode !== 0) {
2783
- throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir8.stderr).slice(0, 300)}`);
2923
+ if (mkdir9.exitCode !== 0) {
2924
+ throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir9.stderr).slice(0, 300)}`);
2784
2925
  }
2785
2926
  if (entry.kind === "file") {
2786
2927
  const cp2 = await execa22(
@@ -2861,49 +3002,152 @@ function dirnameUnix(p) {
2861
3002
  if (i <= 0) return "/";
2862
3003
  return p.slice(0, i);
2863
3004
  }
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}` };
3005
+ var CREDENTIALS_BACKUP_FILE = join22(STATE_DIR, "claude-credentials.json");
3006
+ var CODEX_CREDENTIALS_BACKUP_FILE = join22(STATE_DIR, "codex-credentials.json");
3007
+ var OPENCODE_CREDENTIALS_BACKUP_FILE = join22(STATE_DIR, "opencode-credentials.json");
3008
+ function isRealAgentCredential(agent, text) {
3009
+ let parsed;
3010
+ try {
3011
+ parsed = JSON.parse(text);
3012
+ } catch {
3013
+ return false;
3014
+ }
3015
+ if (typeof parsed !== "object" || parsed === null) return false;
3016
+ if (agent === "claude") {
3017
+ const rt = parsed.claudeAiOauth?.refreshToken;
3018
+ return typeof rt === "string" && rt.length > 0;
3019
+ }
3020
+ return Object.keys(parsed).length > 0;
3021
+ }
3022
+ async function hostClaudeBackupExpired(path = CREDENTIALS_BACKUP_FILE, now = Date.now()) {
3023
+ try {
3024
+ const parsed = JSON.parse(await readFile24(path, "utf8"));
3025
+ const exp = parsed?.claudeAiOauth?.expiresAt;
3026
+ return typeof exp === "number" && Number.isFinite(exp) && exp < now;
3027
+ } catch {
3028
+ return false;
3029
+ }
3030
+ }
3031
+ function parseExtractResult(stdout) {
3032
+ return { copied: /\bCOPIED=yes\b/.test(stdout) };
3033
+ }
3034
+ async function extractVolumeAuthToBackup(opts) {
3035
+ try {
3036
+ await mkdir22(STATE_DIR, { recursive: true });
3037
+ const script = 'COPIED=no; if [ -s /dst/auth.json ]; then cp -a /dst/auth.json "/host-state/$DEST" && COPIED=yes; fi; echo "COPIED=$COPIED"';
3038
+ const { stdout } = await execa3("docker", [
3039
+ "run",
3040
+ "--rm",
3041
+ "--user",
3042
+ "0",
3043
+ "-v",
3044
+ `${opts.volume}:/dst`,
3045
+ "-v",
3046
+ `${STATE_DIR}:/host-state`,
3047
+ "-e",
3048
+ // Pass the destination filename via env so the path isn't interpolated
3049
+ // into the script string (keeps the docker arg list static + injection-safe).
3050
+ `DEST=${basename3(opts.backupFile)}`,
3051
+ opts.image,
3052
+ "sh",
3053
+ "-c",
3054
+ script
3055
+ ]);
3056
+ const result = parseExtractResult(stdout);
3057
+ if (result.copied) await chmod(opts.backupFile, 384).catch(() => {
3058
+ });
3059
+ return result;
3060
+ } catch {
3061
+ return { copied: false };
3062
+ }
3063
+ }
3064
+ function extractCodexCredentials(volume, image) {
3065
+ return extractVolumeAuthToBackup({ volume, image, backupFile: CODEX_CREDENTIALS_BACKUP_FILE });
3066
+ }
3067
+ function extractOpencodeCredentials(volume, image) {
3068
+ return extractVolumeAuthToBackup({ volume, image, backupFile: OPENCODE_CREDENTIALS_BACKUP_FILE });
3069
+ }
3070
+ async function hostBackupHasCredentials(path = CREDENTIALS_BACKUP_FILE) {
3071
+ try {
3072
+ const parsed = JSON.parse(await readFile24(path, "utf8"));
3073
+ const rt = parsed?.claudeAiOauth?.refreshToken;
3074
+ return typeof rt === "string" && rt.length > 0;
3075
+ } catch {
3076
+ return false;
3077
+ }
3078
+ }
3079
+ function parseSyncResult(stdout) {
3080
+ const volumeHasCredentials = /\bVOLREAL=yes\b/.test(stdout);
3081
+ if (/\bEXTRACTED=yes\b/.test(stdout)) return { direction: "extracted", volumeHasCredentials };
3082
+ if (/\bSEEDED=yes\b/.test(stdout)) return { direction: "seeded", volumeHasCredentials };
3083
+ return { direction: "noop", volumeHasCredentials };
3084
+ }
3085
+ var SYNC_SCRIPT = `
3086
+ EXTRACTED=no
3087
+ SEEDED=no
3088
+ VOL=/dst/.credentials.json
3089
+ HOST=/host-state/claude-credentials.json
3090
+ if [ -f "$VOL" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$VOL" >/dev/null 2>&1; then VOL_REAL=yes; else VOL_REAL=no; fi
3091
+ if [ -f "$HOST" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$HOST" >/dev/null 2>&1; then HOST_REAL=yes; else HOST_REAL=no; fi
3092
+ if [ "$VOL_REAL" = yes ] && [ "$ISOLATE" != yes ]; then
3093
+ cp -a "$VOL" "$HOST" && chmod 600 "$HOST" && EXTRACTED=yes
3094
+ elif [ "$VOL_REAL" = no ] && [ "$HOST_REAL" = yes ]; then
3095
+ cp -a "$HOST" "$VOL" && chown 1000:1000 "$VOL" && chmod 600 "$VOL" && SEEDED=yes && VOL_REAL=yes
3096
+ fi
3097
+ echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
3098
+ `;
3099
+ async function syncClaudeCredentials(spec, opts) {
3100
+ try {
3101
+ await mkdir22(STATE_DIR, { recursive: true });
3102
+ const { stdout } = await execa3("docker", [
3103
+ "run",
3104
+ "--rm",
3105
+ "--user",
3106
+ "0",
3107
+ "-v",
3108
+ `${spec.volume}:/dst`,
3109
+ "-v",
3110
+ `${STATE_DIR}:/host-state`,
3111
+ "-e",
3112
+ `ISOLATE=${opts.isolate ? "yes" : "no"}`,
3113
+ opts.image,
3114
+ "sh",
3115
+ "-c",
3116
+ SYNC_SCRIPT
3117
+ ]);
3118
+ const result = parseSyncResult(stdout);
3119
+ if (result.direction === "extracted") {
3120
+ await chmod(CREDENTIALS_BACKUP_FILE, 384).catch(() => {
3121
+ });
3122
+ }
3123
+ return result;
3124
+ } catch {
3125
+ return { direction: "noop", volumeHasCredentials: false };
2874
3126
  }
2875
- return { volume: SHARED_CLAUDE_VOLUME };
2876
3127
  }
3128
+ var CLOUD_WORKSPACE = "/workspace";
2877
3129
  async function pathExists(p) {
2878
3130
  try {
2879
- await stat3(p);
3131
+ await stat22(p);
2880
3132
  return true;
2881
3133
  } catch {
2882
3134
  return false;
2883
3135
  }
2884
3136
  }
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
3137
  async function findBrokenSymlinks(root) {
2894
3138
  const broken = [];
2895
3139
  async function walk(dir) {
2896
3140
  let entries;
2897
3141
  try {
2898
- entries = await readdir3(dir, { withFileTypes: true });
3142
+ entries = await readdir22(dir, { withFileTypes: true });
2899
3143
  } catch {
2900
3144
  return;
2901
3145
  }
2902
3146
  for (const ent of entries) {
2903
- const full = join22(dir, ent.name);
3147
+ const full = join32(dir, ent.name);
2904
3148
  if (ent.isSymbolicLink()) {
2905
3149
  try {
2906
- await stat3(full);
3150
+ await stat22(full);
2907
3151
  } catch {
2908
3152
  broken.push(relative(root, full));
2909
3153
  }
@@ -2915,52 +3159,434 @@ async function findBrokenSymlinks(root) {
2915
3159
  await walk(root);
2916
3160
  return broken;
2917
3161
  }
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;
3162
+ async function mkStageDir(prefix) {
3163
+ return mkdtemp(join32(tmpdir(), `agentbox-${prefix}-stage-`));
3164
+ }
3165
+ function emptyResult(warnings = []) {
3166
+ return { tarballPath: null, cleanup: async () => {
3167
+ }, warnings };
3168
+ }
3169
+ async function tarballFromDir(stageDir, agent) {
3170
+ const tarballPath = join32(tmpdir(), `agentbox-${agent}-${basename2(stageDir)}.tar.gz`);
3171
+ await execa4("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
3172
+ env: { ...process.env, COPYFILE_DISABLE: "1" }
3173
+ });
3174
+ return tarballPath;
3175
+ }
3176
+ function makeCleanup(paths) {
3177
+ return async () => {
3178
+ for (const p of paths) {
3179
+ await rm3(p, { recursive: true, force: true });
3180
+ }
3181
+ };
3182
+ }
3183
+ async function stageSingleFileTarball(agent, sourcePath, tarballEntryName) {
3184
+ const stageDir = await mkStageDir(agent);
3185
+ let tarballPath = null;
3186
+ try {
3187
+ await copyFile(sourcePath, join32(stageDir, tarballEntryName));
3188
+ tarballPath = await tarballFromDir(stageDir, agent);
3189
+ return {
3190
+ tarballPath,
3191
+ cleanup: makeCleanup([stageDir, tarballPath]),
3192
+ warnings: []
3193
+ };
3194
+ } catch (err) {
3195
+ await rm3(stageDir, { recursive: true, force: true });
3196
+ if (tarballPath) await rm3(tarballPath, { force: true });
3197
+ throw err;
3198
+ }
3199
+ }
3200
+ var CLAUDE_RUNTIME_EXCLUDES = [
3201
+ "projects",
3202
+ // workflows/ are seeded per-box at create time (incremental, like memory),
3203
+ // not frozen into the prepare-time snapshot — see seedDynamicConfig.
3204
+ "workflows",
3205
+ "sessions",
3206
+ "history.jsonl",
3207
+ "file-history",
3208
+ "shell-snapshots",
3209
+ "backups",
3210
+ "session-env",
3211
+ "paste-cache",
3212
+ "cache",
3213
+ "telemetry",
3214
+ "tasks",
3215
+ "downloads",
3216
+ "chrome",
3217
+ "ide",
3218
+ "debug",
3219
+ "mcp-needs-auth-cache.json",
3220
+ "stats-cache.json"
3221
+ ];
3222
+ function encodeClaudeProjectsKey(absPath) {
3223
+ return absPath.replace(/[^a-zA-Z0-9]/g, "-");
3224
+ }
3225
+ var BOX_CLAUDE_PROJECT_DIR = "/home/vscode/.claude/projects/-workspace";
3226
+ async function resolveClaudeMemoryDir(hostWorkspace, hostHome = homedir22()) {
3227
+ if (hostWorkspace.length === 0) return null;
3228
+ const memDir = join32(
3229
+ hostHome,
3230
+ ".claude",
3231
+ "projects",
3232
+ encodeClaudeProjectsKey(hostWorkspace),
3233
+ "memory"
3234
+ );
3235
+ if (!await pathExists(memDir)) return null;
3236
+ try {
3237
+ const entries = await readdir22(memDir);
3238
+ if (entries.length === 0) return null;
3239
+ } catch {
3240
+ return null;
3241
+ }
3242
+ return memDir;
3243
+ }
3244
+ async function stageClaudeStaticForUpload(opts = {}) {
3245
+ const hostHome = opts.hostHome ?? homedir22();
3246
+ const hostClaude = join32(hostHome, ".claude");
3247
+ if (!await pathExists(hostClaude)) return emptyResult();
3248
+ const stageDir = await mkStageDir("claude-static");
3249
+ let tarballPath = null;
3250
+ try {
3251
+ const broken = await findBrokenSymlinks(hostClaude);
3252
+ const excludes = [
3253
+ "--exclude=node_modules",
3254
+ "--exclude=.credentials.json",
3255
+ ...CLAUDE_RUNTIME_EXCLUDES.map((p) => `--exclude=${p}`),
3256
+ ...broken.map((r) => `--exclude=/${r}`)
3257
+ ];
3258
+ await execa4("rsync", [
3259
+ "-a",
3260
+ "--copy-unsafe-links",
3261
+ ...excludes,
3262
+ `${hostClaude}/`,
3263
+ `${stageDir}/`
3264
+ ]);
3265
+ const settingsPath = join32(stageDir, "settings.json");
3266
+ if (await pathExists(settingsPath)) {
3267
+ try {
3268
+ const parsed = JSON.parse(await readFile32(settingsPath, "utf8"));
3269
+ const filtered = filterHostHooks(parsed, hostHome);
3270
+ if (filtered.removedCommands.length > 0) {
3271
+ await writeFile3(settingsPath, JSON.stringify(filtered.data, null, 2));
3272
+ }
3273
+ } catch {
3274
+ }
3275
+ }
3276
+ const hostClaudeJson = join32(hostHome, ".claude.json");
3277
+ let working;
3278
+ if (await pathExists(hostClaudeJson)) {
3279
+ try {
3280
+ working = JSON.parse(await readFile32(hostClaudeJson, "utf8"));
3281
+ } catch {
3282
+ working = null;
3283
+ }
3284
+ }
3285
+ if (working === void 0 || working === null) {
3286
+ working = {
3287
+ installMethod: "native",
3288
+ autoUpdates: false,
3289
+ autoUpdatesProtectedForNative: true,
3290
+ projects: { [CLOUD_WORKSPACE]: { hasTrustDialogAccepted: true } }
3291
+ };
3292
+ } else {
3293
+ working = filterHostHooks(working, hostHome).data;
3294
+ working = setInstallMethodNative(working).data;
3295
+ if (opts.hostWorkspace) {
3296
+ working = addProjectAlias(working, opts.hostWorkspace, CLOUD_WORKSPACE).data;
3297
+ }
3298
+ working = trustWorkspace(working, CLOUD_WORKSPACE).data;
3299
+ }
3300
+ await writeFile3(join32(stageDir, "_claude.json"), JSON.stringify(working, null, 2));
3301
+ const pluginsDir = join32(stageDir, "plugins");
3302
+ if (await pathExists(pluginsDir)) {
3303
+ try {
3304
+ const entries = await readdir22(pluginsDir, { withFileTypes: true });
3305
+ for (const ent of entries) {
3306
+ if (!ent.isFile() || !ent.name.endsWith(".json")) continue;
3307
+ const file = join32(pluginsDir, ent.name);
3308
+ const raw = await readFile32(file, "utf8");
3309
+ const replaced = raw.split(`${hostHome}/.claude/plugins/`).join("/home/vscode/.claude/plugins/");
3310
+ if (replaced !== raw) await writeFile3(file, replaced);
3311
+ }
3312
+ } catch {
3313
+ }
3314
+ }
3315
+ tarballPath = await tarballFromDir(stageDir, "claude-static");
3316
+ return {
3317
+ tarballPath,
3318
+ cleanup: makeCleanup([stageDir, tarballPath]),
3319
+ warnings: []
3320
+ };
3321
+ } catch (err) {
3322
+ await rm3(stageDir, { recursive: true, force: true });
3323
+ if (tarballPath) await rm3(tarballPath, { force: true });
3324
+ throw err;
3325
+ }
3326
+ }
3327
+ async function stageClaudeCredentialsForUpload() {
3328
+ if (!await pathExists(CREDENTIALS_BACKUP_FILE)) return emptyResult();
3329
+ return stageSingleFileTarball("claude-creds", CREDENTIALS_BACKUP_FILE, ".credentials.json");
3330
+ }
3331
+ var CODEX_RSYNC_EXCLUDES = [
3332
+ "--exclude=sessions",
3333
+ "--exclude=log",
3334
+ "--exclude=history.jsonl",
3335
+ "--exclude=hooks.json",
3336
+ "--exclude=logs_2.sqlite",
3337
+ "--exclude=logs_2.sqlite-shm",
3338
+ "--exclude=logs_2.sqlite-wal",
3339
+ "--exclude=state_5.sqlite",
3340
+ "--exclude=state_5.sqlite-shm",
3341
+ "--exclude=state_5.sqlite-wal",
3342
+ "--exclude=sqlite",
3343
+ "--exclude=cache",
3344
+ "--exclude=vendor_imports",
3345
+ "--exclude=tmp",
3346
+ // .tmp holds codex plugin sync state — ~100 MB of marketplace cache. Not
3347
+ // the same as `tmp/`; both can exist side by side on a long-running host.
3348
+ "--exclude=.tmp",
3349
+ "--exclude=.codex-global-state.json",
3350
+ "--exclude=.codex-global-state.json.bak",
3351
+ "--exclude=.personality_migration",
3352
+ "--exclude=shell_snapshots",
3353
+ "--exclude=session_index.jsonl",
3354
+ "--exclude=models_cache.json",
3355
+ "--exclude=installation_id",
3356
+ "--exclude=version.json"
3357
+ ];
3358
+ 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.';
3359
+ async function stageCodexStaticForUpload(opts = {}) {
3360
+ const hostHome = opts.hostHome ?? homedir22();
3361
+ const hostCodex = join32(hostHome, ".codex");
3362
+ if (!await pathExists(hostCodex)) return emptyResult();
3363
+ const stageDir = await mkStageDir("codex-static");
3364
+ let tarballPath = null;
3365
+ try {
3366
+ const codexBroken = await findBrokenSymlinks(hostCodex);
3367
+ await execa4("rsync", [
3368
+ "-a",
3369
+ "-L",
3370
+ ...codexBroken.map((r) => `--exclude=/${r}`),
3371
+ "--exclude=auth.json",
3372
+ ...CODEX_RSYNC_EXCLUDES,
3373
+ `${hostCodex}/`,
3374
+ `${stageDir}/`
3375
+ ]);
3376
+ tarballPath = await tarballFromDir(stageDir, "codex-static");
3377
+ return {
3378
+ tarballPath,
3379
+ cleanup: makeCleanup([stageDir, tarballPath]),
3380
+ warnings: []
3381
+ };
3382
+ } catch (err) {
3383
+ await rm3(stageDir, { recursive: true, force: true });
3384
+ if (tarballPath) await rm3(tarballPath, { force: true });
3385
+ throw err;
3386
+ }
3387
+ }
3388
+ async function stageCodexCredentialsForUpload(opts = {}) {
3389
+ const hostHome = opts.hostHome ?? homedir22();
3390
+ const cloudBackup = join32(hostHome, ".agentbox", "codex-credentials.json");
3391
+ if (await pathExists(cloudBackup)) {
3392
+ return stageSingleFileTarball("codex-creds", cloudBackup, "auth.json");
3393
+ }
3394
+ const hostAuth = join32(hostHome, ".codex", "auth.json");
3395
+ if (!await pathExists(hostAuth)) return emptyResult([CODEX_KEYCHAIN_WARNING]);
3396
+ return stageSingleFileTarball("codex-creds", hostAuth, "auth.json");
3397
+ }
3398
+ var OPENCODE_DATA_EXCLUDES = [
3399
+ "--exclude=storage",
3400
+ "--exclude=log",
3401
+ "--exclude=project",
3402
+ "--exclude=cache",
3403
+ "--exclude=bin",
3404
+ "--exclude=repos",
3405
+ "--exclude=snapshot",
3406
+ "--exclude=config",
3407
+ "--exclude=opencode.db",
3408
+ "--exclude=opencode.db-shm",
3409
+ "--exclude=opencode.db-wal"
3410
+ ];
3411
+ async function stageOpencodeStaticForUpload(opts = {}) {
3412
+ const hostHome = opts.hostHome ?? homedir22();
3413
+ const hostData = join32(hostHome, ".local", "share", "opencode");
3414
+ const hostConfig = join32(hostHome, ".config", "opencode");
3415
+ const hasData = await pathExists(hostData);
3416
+ const hasConfig = await pathExists(hostConfig);
3417
+ if (!hasData && !hasConfig) return emptyResult();
3418
+ const stageDir = await mkStageDir("opencode-static");
3419
+ let tarballPath = null;
3420
+ try {
3421
+ if (hasData) {
3422
+ const dataBroken = await findBrokenSymlinks(hostData);
3423
+ await execa4("rsync", [
3424
+ "-a",
3425
+ "-L",
3426
+ ...dataBroken.map((r) => `--exclude=/${r}`),
3427
+ "--exclude=auth.json",
3428
+ ...OPENCODE_DATA_EXCLUDES,
3429
+ `${hostData}/`,
3430
+ `${stageDir}/`
3431
+ ]);
3432
+ }
3433
+ if (hasConfig) {
3434
+ const configStage = join32(stageDir, "config");
3435
+ const cfgBroken = await findBrokenSymlinks(hostConfig);
3436
+ await execa4("rsync", [
3437
+ "-a",
3438
+ "-L",
3439
+ ...cfgBroken.map((r) => `--exclude=/${r}`),
3440
+ `${hostConfig}/`,
3441
+ `${configStage}/`
3442
+ ]);
3443
+ }
3444
+ tarballPath = await tarballFromDir(stageDir, "opencode-static");
3445
+ return {
3446
+ tarballPath,
3447
+ cleanup: makeCleanup([stageDir, tarballPath]),
3448
+ warnings: []
3449
+ };
3450
+ } catch (err) {
3451
+ await rm3(stageDir, { recursive: true, force: true });
3452
+ if (tarballPath) await rm3(tarballPath, { force: true });
3453
+ throw err;
3454
+ }
3455
+ }
3456
+ async function stageOpencodeCredentialsForUpload(opts = {}) {
3457
+ const hostHome = opts.hostHome ?? homedir22();
3458
+ const cloudBackup = join32(hostHome, ".agentbox", "opencode-credentials.json");
3459
+ if (await pathExists(cloudBackup)) {
3460
+ return stageSingleFileTarball("opencode-creds", cloudBackup, "auth.json");
3461
+ }
3462
+ const hostAuth = join32(hostHome, ".local", "share", "opencode", "auth.json");
3463
+ if (!await pathExists(hostAuth)) return emptyResult();
3464
+ return stageSingleFileTarball("opencode-creds", hostAuth, "auth.json");
3465
+ }
3466
+ async function stageOpencodeStateForUpload(opts = {}) {
3467
+ const hostHome = opts.hostHome ?? homedir22();
3468
+ const hostModel = join32(hostHome, ".local", "state", "opencode", "model.json");
3469
+ if (!await pathExists(hostModel)) return emptyResult();
3470
+ return stageSingleFileTarball("opencode-state", hostModel, "model.json");
3471
+ }
3472
+ var SHARED_CLAUDE_VOLUME = "agentbox-claude-config";
3473
+ var DEFAULT_CLAUDE_SESSION = "claude";
3474
+ var CONTAINER_CLAUDE_DIR = "/home/vscode/.claude";
3475
+ var CONTAINER_USER = "vscode";
3476
+ var CONTAINER_WORKSPACE = "/workspace";
3477
+ var IN_BOX_SETUP_GUIDE_PATH = "/usr/local/share/agentbox/setup-guide.md";
3478
+ var SETUP_SKILL_DST = "/dst/skills/agentbox-setup/SKILL.md";
3479
+ function resolveClaudeVolume(opts) {
3480
+ if (opts.isolate) {
3481
+ return { volume: `${SHARED_CLAUDE_VOLUME}-${opts.boxId}` };
3482
+ }
3483
+ return { volume: SHARED_CLAUDE_VOLUME };
3484
+ }
3485
+ async function pathExists2(p) {
3486
+ try {
3487
+ await stat3(p);
3488
+ return true;
3489
+ } catch {
3490
+ return false;
3491
+ }
3492
+ }
3493
+ async function volumeHasClaudeJson(volume, image) {
3494
+ const res = await execa5(
3495
+ "docker",
3496
+ ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/_claude.json"],
3497
+ { reject: false }
3498
+ );
3499
+ return res.exitCode === 0;
3500
+ }
3501
+ function isUnder(parent, child) {
3502
+ const rel = relative2(parent, child);
3503
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
3504
+ }
3505
+ async function findUnsyncableSymlinks(root, reachableRoots) {
3506
+ const reachable = await Promise.all(
3507
+ reachableRoots.map(async (r) => {
3508
+ try {
3509
+ return await realpath2(r);
3510
+ } catch {
3511
+ return r;
3512
+ }
3513
+ })
3514
+ );
3515
+ const unsyncable = [];
3516
+ async function walk(dir) {
3517
+ let entries;
3518
+ try {
3519
+ entries = await readdir3(dir, { withFileTypes: true });
3520
+ } catch {
3521
+ return;
3522
+ }
3523
+ for (const ent of entries) {
3524
+ const full = join4(dir, ent.name);
3525
+ if (ent.isSymbolicLink()) {
3526
+ let real;
3527
+ try {
3528
+ real = await realpath2(full);
3529
+ } catch {
3530
+ unsyncable.push(relative2(root, full));
3531
+ continue;
3532
+ }
3533
+ if (!reachable.some((r) => isUnder(r, real))) {
3534
+ unsyncable.push(relative2(root, full));
3535
+ }
3536
+ } else if (ent.isDirectory()) {
3537
+ await walk(full);
3538
+ }
3539
+ }
3540
+ }
3541
+ await walk(root);
3542
+ return unsyncable;
3543
+ }
3544
+ async function ensureClaudeVolume(spec, opts) {
3545
+ const existed = await volumeExists(spec.volume);
3546
+ await ensureVolume(spec.volume);
3547
+ const created = !existed;
3548
+ if (!opts.syncFromHost) return { created, synced: false };
3549
+ const hostClaude = join4(homedir3(), ".claude");
3550
+ if (!await pathExists2(hostClaude)) return { created, synced: false };
3551
+ const hostClaudeJson = join4(homedir3(), ".claude.json");
3552
+ const hasJson = await pathExists2(hostClaudeJson);
3553
+ const seedClaudeJson = !await volumeHasClaudeJson(spec.volume, opts.image);
3554
+ const hostHome = homedir3();
3555
+ const hostAgents = join4(homedir3(), ".agents");
3556
+ const hasAgents = await pathExists2(hostAgents);
3557
+ const args = [
3558
+ "run",
3559
+ "--rm",
3560
+ "--user",
3561
+ "0",
3562
+ // HOST_HOME used inside the shell script to rewrite host-absolute
3563
+ // installPath values in plugins/installed_plugins.json.
3564
+ "-e",
3565
+ `HOST_HOME=${hostHome}`,
3566
+ "-v",
3567
+ `${spec.volume}:/dst`,
3568
+ "-v",
3569
+ `${hostClaude}:/src-claude:ro`
3570
+ ];
3571
+ if (hasJson && seedClaudeJson) args.push("-v", `${hostClaudeJson}:/src-claude-json:ro`);
3572
+ if (hasAgents) args.push("-v", `${hostAgents}:/.agents:ro`);
3573
+ const filterDir = await mkdtemp2(join4(tmpdir2(), "agentbox-claude-filter-"));
3574
+ let filteredHookCount = 0;
3575
+ let installMethodFixed = false;
3576
+ let aliasedProjectKey = false;
3577
+ let workspaceTrusted = false;
3578
+ try {
3579
+ const settingsResult = await maybeFilterTo(
3580
+ join4(hostClaude, "settings.json"),
3581
+ join4(filterDir, "settings.json"),
3582
+ hostHome
3583
+ );
3584
+ filteredHookCount += settingsResult.removedHooks;
2959
3585
  if (!seedClaudeJson) {
2960
3586
  } else if (hasJson) {
2961
3587
  const jsonResult = await maybeFilterTo(
2962
3588
  hostClaudeJson,
2963
- join22(filterDir, "_claude.json"),
3589
+ join4(filterDir, "_claude.json"),
2964
3590
  hostHome,
2965
3591
  {
2966
3592
  setInstallMethodNative: true,
@@ -2973,8 +3599,8 @@ async function ensureClaudeVolume(spec, opts) {
2973
3599
  aliasedProjectKey = jsonResult.aliasedProjectKey;
2974
3600
  workspaceTrusted = jsonResult.workspaceTrusted;
2975
3601
  } else {
2976
- await writeFile3(
2977
- join22(filterDir, "_claude.json"),
3602
+ await writeFile22(
3603
+ join4(filterDir, "_claude.json"),
2978
3604
  JSON.stringify(
2979
3605
  {
2980
3606
  installMethod: "native",
@@ -2992,10 +3618,13 @@ async function ensureClaudeVolume(spec, opts) {
2992
3618
  if (filteredHookCount > 0 || installMethodFixed || aliasedProjectKey || workspaceTrusted) {
2993
3619
  args.push("-v", `${filterDir}:/src-filter:ro`);
2994
3620
  }
2995
- const brokenSymlinks = await findBrokenSymlinks(hostClaude);
2996
- const rsyncExcludes = ["--exclude=node_modules"];
3621
+ const reachableRoots = hasAgents ? [hostClaude, hostAgents] : [hostClaude];
3622
+ const brokenSymlinks = await findUnsyncableSymlinks(hostClaude, reachableRoots);
3623
+ const rsyncExcludes = ["--exclude=node_modules", "--exclude=/projects"];
2997
3624
  for (const rel of brokenSymlinks) rsyncExcludes.push(`--exclude=/${rel}`);
2998
3625
  const rsyncFlags = `-a --copy-unsafe-links ${rsyncExcludes.join(" ")}`;
3626
+ const memoryKey = opts.hostWorkspace ? encodeClaudeProjectsKey(opts.hostWorkspace) : null;
3627
+ 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
3628
  args.push(
3000
3629
  opts.image,
3001
3630
  "sh",
@@ -3030,9 +3659,9 @@ async function ensureClaudeVolume(spec, opts) {
3030
3659
  // remove them). The `.agentbox-cleaned-nm-v1` sentinel makes the wipe
3031
3660
  // a no-op after the first run; rebuildPluginNativeDeps repopulates
3032
3661
  // 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`
3662
+ `{ [ ! -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
3663
  );
3035
- await execa3("docker", args);
3664
+ await execa5("docker", args);
3036
3665
  } finally {
3037
3666
  await rm2(filterDir, { recursive: true, force: true });
3038
3667
  }
@@ -3047,7 +3676,7 @@ async function ensureClaudeVolume(spec, opts) {
3047
3676
  }
3048
3677
  async function seedSetupSkillIntoVolume(volume, image) {
3049
3678
  try {
3050
- const { stdout } = await execa3("docker", [
3679
+ const { stdout } = await execa5("docker", [
3051
3680
  "run",
3052
3681
  "--rm",
3053
3682
  "--user",
@@ -3076,7 +3705,7 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
3076
3705
  };
3077
3706
  let parsed;
3078
3707
  try {
3079
- parsed = JSON.parse(await readFile24(src, "utf8"));
3708
+ parsed = JSON.parse(await readFile4(src, "utf8"));
3080
3709
  } catch {
3081
3710
  return zero;
3082
3711
  }
@@ -3103,7 +3732,7 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
3103
3732
  if (filtered.removedCommands.length === 0 && !installFixed && !aliased && !trusted) {
3104
3733
  return zero;
3105
3734
  }
3106
- await writeFile3(dest, JSON.stringify(working, null, 2));
3735
+ await writeFile22(dest, JSON.stringify(working, null, 2));
3107
3736
  return {
3108
3737
  removedHooks: filtered.removedCommands.length,
3109
3738
  installMethodFixed: installFixed,
@@ -3159,7 +3788,7 @@ async function isDir(p) {
3159
3788
  }
3160
3789
  async function readReferencedPluginKeys(installedPluginsJsonPath) {
3161
3790
  try {
3162
- const raw = await readFile24(installedPluginsJsonPath, "utf8");
3791
+ const raw = await readFile4(installedPluginsJsonPath, "utf8");
3163
3792
  return referencedPluginVersionKeys(JSON.parse(raw));
3164
3793
  } catch {
3165
3794
  return /* @__PURE__ */ new Set();
@@ -3167,7 +3796,7 @@ async function readReferencedPluginKeys(installedPluginsJsonPath) {
3167
3796
  }
3168
3797
  async function scanPluginCacheForRebuild(cacheRoot) {
3169
3798
  const referenced = await readReferencedPluginKeys(
3170
- join22(cacheRoot, "..", "installed_plugins.json")
3799
+ join4(cacheRoot, "..", "installed_plugins.json")
3171
3800
  );
3172
3801
  let marketplaces;
3173
3802
  try {
@@ -3177,7 +3806,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
3177
3806
  }
3178
3807
  for (const m of marketplaces) {
3179
3808
  if (!m.isDirectory()) continue;
3180
- const mPath = join22(cacheRoot, m.name);
3809
+ const mPath = join4(cacheRoot, m.name);
3181
3810
  let plugins;
3182
3811
  try {
3183
3812
  plugins = await readdir3(mPath, { withFileTypes: true });
@@ -3186,7 +3815,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
3186
3815
  }
3187
3816
  for (const p of plugins) {
3188
3817
  if (!p.isDirectory()) continue;
3189
- const pPath = join22(mPath, p.name);
3818
+ const pPath = join4(mPath, p.name);
3190
3819
  let versions;
3191
3820
  try {
3192
3821
  versions = await readdir3(pPath, { withFileTypes: true });
@@ -3196,10 +3825,10 @@ async function scanPluginCacheForRebuild(cacheRoot) {
3196
3825
  for (const v of versions) {
3197
3826
  if (!v.isDirectory()) continue;
3198
3827
  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;
3828
+ const vPath = join4(pPath, v.name);
3829
+ if (!await isFile(join4(vPath, "package.json"))) continue;
3830
+ if (await isFile(join4(vPath, PLUGIN_INSTALLED_MARKER))) continue;
3831
+ if (await isRecentFailMarker(join4(vPath, PLUGIN_FAILED_MARKER))) continue;
3203
3832
  return true;
3204
3833
  }
3205
3834
  }
@@ -3212,7 +3841,7 @@ async function resolveClaudeCacheLiveOnHost(volume) {
3212
3841
  return orbstackVolumePath(volume, "plugins", "cache");
3213
3842
  }
3214
3843
  async function readBoxReferencedPluginKeys(container) {
3215
- const res = await execa3(
3844
+ const res = await execa5(
3216
3845
  "docker",
3217
3846
  [
3218
3847
  "exec",
@@ -3330,7 +3959,7 @@ while IFS= read -r dir; do
3330
3959
  done < "$WORK/dirs"
3331
3960
  rm -rf "$WORK"
3332
3961
  `;
3333
- const result = await execa3(
3962
+ const result = await execa5(
3334
3963
  "docker",
3335
3964
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", script],
3336
3965
  { reject: false }
@@ -3391,7 +4020,7 @@ async function startClaudeSession(opts) {
3391
4020
  const v = process.env[k];
3392
4021
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
3393
4022
  }
3394
- const result = await execa3(
4023
+ const result = await execa5(
3395
4024
  "docker",
3396
4025
  [
3397
4026
  "exec",
@@ -3482,7 +4111,7 @@ function buildDashboardAttachArgv(container, sessionName) {
3482
4111
  async function waitForTmuxPaneContent(container, sessionName, timeoutMs = 2e4) {
3483
4112
  const deadline = Date.now() + timeoutMs;
3484
4113
  while (Date.now() < deadline) {
3485
- const res = await execa3(
4114
+ const res = await execa5(
3486
4115
  "docker",
3487
4116
  ["exec", "--user", CONTAINER_USER, container, "tmux", "capture-pane", "-p", "-t", sessionName],
3488
4117
  { reject: false }
@@ -3550,7 +4179,7 @@ async function warmUpClaudeCredentials(volume, image, opts = {}) {
3550
4179
  const SLEEP_MS = 5e3;
3551
4180
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
3552
4181
  opts.onProgress?.(`checking credentials... ${attempt}/${MAX_ATTEMPTS}`);
3553
- const res = await execa3(
4182
+ const res = await execa5(
3554
4183
  "docker",
3555
4184
  [
3556
4185
  "run",
@@ -3592,7 +4221,7 @@ function attachClaudeSession(container, sessionName, reattachRef) {
3592
4221
  }
3593
4222
  async function claudeSessionInfo(container, sessionName) {
3594
4223
  const name = sessionName ?? DEFAULT_CLAUDE_SESSION;
3595
- const has = await execa3(
4224
+ const has = await execa5(
3596
4225
  "docker",
3597
4226
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
3598
4227
  { reject: false }
@@ -3600,7 +4229,7 @@ async function claudeSessionInfo(container, sessionName) {
3600
4229
  if (has.exitCode !== 0) {
3601
4230
  return { running: false, sessionName: name, startedAt: null };
3602
4231
  }
3603
- const ts = await execa3(
4232
+ const ts = await execa5(
3604
4233
  "docker",
3605
4234
  [
3606
4235
  "exec",
@@ -3634,14 +4263,14 @@ async function listChildDirs(dir) {
3634
4263
  }
3635
4264
  async function readJsonFile(path) {
3636
4265
  try {
3637
- return JSON.parse(await readFile24(path, "utf8"));
4266
+ return JSON.parse(await readFile4(path, "utf8"));
3638
4267
  } catch {
3639
4268
  return void 0;
3640
4269
  }
3641
4270
  }
3642
4271
  async function pullClaudeExtras(spec, opts) {
3643
- const hostHome = homedir2();
3644
- const hostClaude = join22(hostHome, ".claude");
4272
+ const hostHome = homedir3();
4273
+ const hostClaude = join4(hostHome, ".claude");
3645
4274
  const inventoryScript = [
3646
4275
  "for cat in skills agents commands; do",
3647
4276
  ' [ -d "/src/$cat" ] || continue;',
@@ -3666,7 +4295,7 @@ async function pullClaudeExtras(spec, opts) {
3666
4295
  ' printf "\\n";',
3667
4296
  "done"
3668
4297
  ].join(" ");
3669
- const inv = await execa3(
4298
+ const inv = await execa5(
3670
4299
  "docker",
3671
4300
  ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/src:ro`, opts.image, "sh", "-c", inventoryScript],
3672
4301
  { reject: false }
@@ -3703,7 +4332,7 @@ async function pullClaudeExtras(spec, opts) {
3703
4332
  const newItems = [];
3704
4333
  const applyPaths = [];
3705
4334
  for (const cat of PULL_DIR_CATEGORIES) {
3706
- const hostNames = await listChildDirs(join22(hostClaude, cat));
4335
+ const hostNames = await listChildDirs(join4(hostClaude, cat));
3707
4336
  const excludes = cat === "skills" ? SKILL_EXCLUDE_PREFIXES : [];
3708
4337
  for (const name of pickNewItems(boxDirs[cat] ?? [], hostNames, excludes)) {
3709
4338
  newItems.push({ category: cat, name });
@@ -3711,8 +4340,8 @@ async function pullClaudeExtras(spec, opts) {
3711
4340
  }
3712
4341
  }
3713
4342
  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))) {
4343
+ for (const m of await listChildDirs(join4(hostClaude, "plugins", "cache"))) {
4344
+ for (const p of await listChildDirs(join4(hostClaude, "plugins", "cache", m))) {
3716
4345
  hostPluginKeys.push(`${m}/${p}`);
3717
4346
  }
3718
4347
  }
@@ -3720,190 +4349,67 @@ async function pullClaudeExtras(spec, opts) {
3720
4349
  newItems.push({ category: "plugins", name: key });
3721
4350
  applyPaths.push({ src: `/src/plugins/cache/${key}`, dest: `/dst/plugins/cache/${key}` });
3722
4351
  }
3723
- const hostInstalled = await readJsonFile(join22(hostClaude, "plugins", "installed_plugins.json"));
3724
- const hostMarkets = await readJsonFile(join22(hostClaude, "plugins", "known_marketplaces.json"));
4352
+ const hostInstalled = await readJsonFile(join4(hostClaude, "plugins", "installed_plugins.json"));
4353
+ const hostMarkets = await readJsonFile(join4(hostClaude, "plugins", "known_marketplaces.json"));
3725
4354
  const mergedInstalled = mergeInstalledPlugins(hostInstalled, boxJson["installed_plugins"], {
3726
4355
  hostHome
3727
4356
  });
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;
4357
+ const mergedMarkets = mergeKnownMarketplaces(hostMarkets, boxJson["known_marketplaces"], {
4358
+ hostHome
4359
+ });
4360
+ const mergedRegistries = [];
4361
+ if (mergedInstalled.changed) mergedRegistries.push("installed_plugins.json");
4362
+ if (mergedMarkets.changed) mergedRegistries.push("known_marketplaces.json");
4363
+ if (opts.dryRun || newItems.length === 0 && mergedRegistries.length === 0) {
4364
+ return { newItems, mergedRegistries };
3857
4365
  }
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
- });
4366
+ if (applyPaths.length > 0) {
4367
+ const cmds = applyPaths.map(({ src, dest }) => {
4368
+ const parent = dest.slice(0, dest.lastIndexOf("/"));
4369
+ return `mkdir -p '${parent}' && rsync -a --ignore-existing --exclude=node_modules '${src}/' '${dest}/'`;
4370
+ });
4371
+ const apply = await execa5(
4372
+ "docker",
4373
+ [
4374
+ "run",
4375
+ "--rm",
4376
+ "--user",
4377
+ "0",
4378
+ "-v",
4379
+ `${spec.volume}:/src:ro`,
4380
+ "-v",
4381
+ `${hostClaude}:/dst`,
4382
+ opts.image,
4383
+ "sh",
4384
+ "-c",
4385
+ cmds.join(" && ")
4386
+ ],
4387
+ { reject: false }
4388
+ );
4389
+ if (apply.exitCode !== 0) {
4390
+ throw new ClaudeSessionError(
4391
+ `failed to copy extensions from ${spec.volume}: ${(apply.stderr ?? "").toString().trim() || `exit ${String(apply.exitCode)}`}`
4392
+ );
4393
+ }
4394
+ }
4395
+ if (mergedMarkets.changed || mergedInstalled.changed) {
4396
+ await mkdir3(join4(hostClaude, "plugins"), { recursive: true });
4397
+ if (mergedMarkets.changed) {
4398
+ await writeFile22(
4399
+ join4(hostClaude, "plugins", "known_marketplaces.json"),
4400
+ `${JSON.stringify(mergedMarkets.data, null, 2)}
4401
+ `
4402
+ );
4403
+ }
4404
+ if (mergedInstalled.changed) {
4405
+ await writeFile22(
4406
+ join4(hostClaude, "plugins", "installed_plugins.json"),
4407
+ `${JSON.stringify(mergedInstalled.data, null, 2)}
4408
+ `
4409
+ );
3902
4410
  }
3903
- return result;
3904
- } catch {
3905
- return { direction: "noop", volumeHasCredentials: false };
3906
4411
  }
4412
+ return { newItems, mergedRegistries };
3907
4413
  }
3908
4414
  var SHARED_CODEX_VOLUME = "agentbox-codex-config";
3909
4415
  var DEFAULT_CODEX_SESSION = "codex";
@@ -3915,9 +4421,9 @@ function resolveCodexVolume(opts) {
3915
4421
  }
3916
4422
  return { volume: SHARED_CODEX_VOLUME };
3917
4423
  }
3918
- async function pathExists2(p) {
4424
+ async function pathExists3(p) {
3919
4425
  try {
3920
- await stat22(p);
4426
+ await stat42(p);
3921
4427
  return true;
3922
4428
  } catch {
3923
4429
  return false;
@@ -3932,10 +4438,10 @@ async function ensureCodexVolume(spec, opts) {
3932
4438
  const existed = await volumeExists(spec.volume);
3933
4439
  await ensureVolume(spec.volume);
3934
4440
  const created = !existed;
3935
- const hostCodex = join42(homedir32(), ".codex");
3936
- const willSync = opts.syncFromHost && await pathExists2(hostCodex);
4441
+ const hostCodex = join52(homedir4(), ".codex");
4442
+ const willSync = opts.syncFromHost && await pathExists3(hostCodex);
3937
4443
  if (willSync) {
3938
- await execa5("docker", [
4444
+ await execa6("docker", [
3939
4445
  "run",
3940
4446
  "--rm",
3941
4447
  "--user",
@@ -3953,7 +4459,7 @@ async function ensureCodexVolume(spec, opts) {
3953
4459
  ]);
3954
4460
  return { created, synced: true };
3955
4461
  }
3956
- await execa5(
4462
+ await execa6(
3957
4463
  "docker",
3958
4464
  [
3959
4465
  "run",
@@ -3973,7 +4479,7 @@ async function ensureCodexVolume(spec, opts) {
3973
4479
  }
3974
4480
  async function seedCodexHooks(volume, image) {
3975
4481
  try {
3976
- const { stdout } = await execa5("docker", [
4482
+ const { stdout } = await execa6("docker", [
3977
4483
  "run",
3978
4484
  "--rm",
3979
4485
  "--user",
@@ -4010,14 +4516,14 @@ var CodexSessionError = class extends Error {
4010
4516
  }
4011
4517
  };
4012
4518
  async function ensureCodexInstalled(container, opts = {}) {
4013
- const probe = await execa5(
4519
+ const probe = await execa6(
4014
4520
  "docker",
4015
4521
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v codex"],
4016
4522
  { reject: false }
4017
4523
  );
4018
4524
  if (probe.exitCode === 0) return { installed: false };
4019
4525
  opts.onProgress?.("installing codex (absent from this box image)");
4020
- const install = await execa5(
4526
+ const install = await execa6(
4021
4527
  "docker",
4022
4528
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g @openai/codex 2>&1"],
4023
4529
  { reject: false }
@@ -4040,7 +4546,7 @@ async function startCodexSession(opts) {
4040
4546
  const v = process.env[k];
4041
4547
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4042
4548
  }
4043
- const result = await execa5(
4549
+ const result = await execa6(
4044
4550
  "docker",
4045
4551
  [
4046
4552
  "exec",
@@ -4122,7 +4628,7 @@ function runInteractiveCodexLogin(dockerArgv) {
4122
4628
  return { exitCode: child.status ?? 1 };
4123
4629
  }
4124
4630
  async function volumeHasCodexAuth(volume, image) {
4125
- const res = await execa5(
4631
+ const res = await execa6(
4126
4632
  "docker",
4127
4633
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
4128
4634
  { reject: false }
@@ -4131,7 +4637,7 @@ async function volumeHasCodexAuth(volume, image) {
4131
4637
  }
4132
4638
  async function codexSessionInfo(container, sessionName) {
4133
4639
  const name = sessionName ?? DEFAULT_CODEX_SESSION;
4134
- const has = await execa5(
4640
+ const has = await execa6(
4135
4641
  "docker",
4136
4642
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4137
4643
  { reject: false }
@@ -4139,7 +4645,7 @@ async function codexSessionInfo(container, sessionName) {
4139
4645
  if (has.exitCode !== 0) {
4140
4646
  return { running: false, sessionName: name, startedAt: null };
4141
4647
  }
4142
- const ts = await execa5(
4648
+ const ts = await execa6(
4143
4649
  "docker",
4144
4650
  [
4145
4651
  "exec",
@@ -4164,8 +4670,8 @@ async function codexSessionInfo(container, sessionName) {
4164
4670
  }
4165
4671
  var CODEX_PULL_ITEMS = ["config.toml", "auth.json", "prompts"];
4166
4672
  async function pullCodexConfig(spec, opts) {
4167
- const hostCodex = join42(homedir32(), ".codex");
4168
- const inv = await execa5(
4673
+ const hostCodex = join52(homedir4(), ".codex");
4674
+ const inv = await execa6(
4169
4675
  "docker",
4170
4676
  [
4171
4677
  "run",
@@ -4192,14 +4698,14 @@ async function pullCodexConfig(spec, opts) {
4192
4698
  const newItems = [];
4193
4699
  for (const item of CODEX_PULL_ITEMS) {
4194
4700
  if (!present.has(item)) continue;
4195
- if (await pathExists2(join42(hostCodex, item))) continue;
4701
+ if (await pathExists3(join52(hostCodex, item))) continue;
4196
4702
  newItems.push(item);
4197
4703
  }
4198
4704
  if (opts.dryRun || newItems.length === 0) return { newItems };
4199
4705
  const uid = process.getuid?.() ?? 0;
4200
4706
  const gid = process.getgid?.() ?? 0;
4201
4707
  const cmds = newItems.map((it) => `cp -a '/src/${it}' '/dst/${it}'`);
4202
- const apply = await execa5(
4708
+ const apply = await execa6(
4203
4709
  "docker",
4204
4710
  [
4205
4711
  "run",
@@ -4236,9 +4742,9 @@ function resolveOpencodeVolume(opts) {
4236
4742
  }
4237
4743
  return { volume: SHARED_OPENCODE_VOLUME };
4238
4744
  }
4239
- async function pathExists3(p) {
4745
+ async function pathExists4(p) {
4240
4746
  try {
4241
- await stat32(p);
4747
+ await stat5(p);
4242
4748
  return true;
4243
4749
  } catch {
4244
4750
  return false;
@@ -4253,12 +4759,12 @@ async function ensureOpencodeVolume(spec, opts) {
4253
4759
  const existed = await volumeExists(spec.volume);
4254
4760
  await ensureVolume(spec.volume);
4255
4761
  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);
4762
+ const hostData = join6(homedir5(), ".local", "share", "opencode");
4763
+ const hostConfig = join6(homedir5(), ".config", "opencode");
4764
+ const hostState = join6(homedir5(), ".local", "state", "opencode");
4765
+ const hasData = await pathExists4(hostData);
4766
+ const hasConfig = await pathExists4(hostConfig);
4767
+ const hasState = await pathExists4(hostState);
4262
4768
  const willSync = opts.syncFromHost && (hasData || hasConfig || hasState);
4263
4769
  if (willSync) {
4264
4770
  const args = ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/dst`];
@@ -4281,10 +4787,10 @@ async function ensureOpencodeVolume(spec, opts) {
4281
4787
  }
4282
4788
  steps.push("chown -R 1000:1000 /dst");
4283
4789
  args.push(opts.image, "sh", "-c", steps.join(" && "));
4284
- await execa6("docker", args);
4790
+ await execa7("docker", args);
4285
4791
  return { created, synced: true };
4286
4792
  }
4287
- await execa6(
4793
+ await execa7(
4288
4794
  "docker",
4289
4795
  [
4290
4796
  "run",
@@ -4304,7 +4810,7 @@ async function ensureOpencodeVolume(spec, opts) {
4304
4810
  }
4305
4811
  async function seedOpencodePlugin(volume, image) {
4306
4812
  try {
4307
- const { stdout } = await execa6("docker", [
4813
+ const { stdout } = await execa7("docker", [
4308
4814
  "run",
4309
4815
  "--rm",
4310
4816
  "--user",
@@ -4352,14 +4858,14 @@ var OpencodeSessionError = class extends Error {
4352
4858
  }
4353
4859
  };
4354
4860
  async function ensureOpencodeInstalled(container, opts = {}) {
4355
- const probe = await execa6(
4861
+ const probe = await execa7(
4356
4862
  "docker",
4357
4863
  ["exec", "--user", CONTAINER_USER, container, "sh", "-c", "command -v opencode"],
4358
4864
  { reject: false }
4359
4865
  );
4360
4866
  if (probe.exitCode === 0) return { installed: false };
4361
4867
  opts.onProgress?.("installing opencode (absent from this box image)");
4362
- const install = await execa6(
4868
+ const install = await execa7(
4363
4869
  "docker",
4364
4870
  ["exec", "--user", "root", container, "bash", "-lc", "npm install -g opencode-ai 2>&1"],
4365
4871
  { reject: false }
@@ -4381,7 +4887,7 @@ async function startOpencodeSession(opts) {
4381
4887
  const v = process.env[k];
4382
4888
  if (typeof v === "string" && v.length > 0) envFlags.push("-e", `${k}=${v}`);
4383
4889
  }
4384
- const result = await execa6(
4890
+ const result = await execa7(
4385
4891
  "docker",
4386
4892
  [
4387
4893
  "exec",
@@ -4467,7 +4973,7 @@ function runInteractiveOpencodeLogin(dockerArgv) {
4467
4973
  return { exitCode: child.status ?? 1 };
4468
4974
  }
4469
4975
  async function volumeHasOpencodeAuth(volume, image) {
4470
- const res = await execa6(
4976
+ const res = await execa7(
4471
4977
  "docker",
4472
4978
  ["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/auth.json"],
4473
4979
  { reject: false }
@@ -4476,7 +4982,7 @@ async function volumeHasOpencodeAuth(volume, image) {
4476
4982
  }
4477
4983
  async function opencodeSessionInfo(container, sessionName) {
4478
4984
  const name = sessionName ?? DEFAULT_OPENCODE_SESSION;
4479
- const has = await execa6(
4985
+ const has = await execa7(
4480
4986
  "docker",
4481
4987
  ["exec", "--user", CONTAINER_USER, container, "tmux", "has-session", "-t", name],
4482
4988
  { reject: false }
@@ -4484,7 +4990,7 @@ async function opencodeSessionInfo(container, sessionName) {
4484
4990
  if (has.exitCode !== 0) {
4485
4991
  return { running: false, sessionName: name, startedAt: null };
4486
4992
  }
4487
- const ts = await execa6(
4993
+ const ts = await execa7(
4488
4994
  "docker",
4489
4995
  [
4490
4996
  "exec",
@@ -4520,9 +5026,9 @@ var OPENCODE_PULL_CONFIG_ITEMS = [
4520
5026
  "themes"
4521
5027
  ];
4522
5028
  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(
5029
+ const hostData = join6(homedir5(), ".local", "share", "opencode");
5030
+ const hostConfig = join6(homedir5(), ".config", "opencode");
5031
+ const inv = await execa7(
4526
5032
  "docker",
4527
5033
  [
4528
5034
  "run",
@@ -4548,7 +5054,7 @@ async function pullOpencodeConfig(spec, opts) {
4548
5054
  const [group, name] = line.trim().split(/\s+/, 2);
4549
5055
  if (!name || group !== "data" && group !== "config") continue;
4550
5056
  const hostBase = group === "data" ? hostData : hostConfig;
4551
- if (await pathExists3(join5(hostBase, name))) continue;
5057
+ if (await pathExists4(join6(hostBase, name))) continue;
4552
5058
  newItems.push({
4553
5059
  label: group === "data" ? name : `config/${name}`,
4554
5060
  src: group === "data" ? `/src/${name}` : `/src/config/${name}`,
@@ -4564,7 +5070,7 @@ async function pullOpencodeConfig(spec, opts) {
4564
5070
  const cmds = newItems.map(
4565
5071
  (i) => `cp -a '${i.src}' '${i.hostDst === "data" ? "/dst-data" : "/dst-config"}/${i.name}'`
4566
5072
  );
4567
- const apply = await execa6(
5073
+ const apply = await execa7(
4568
5074
  "docker",
4569
5075
  [
4570
5076
  "run",
@@ -4675,9 +5181,9 @@ function gitWorktreePathFor(branch) {
4675
5181
  return `${WORKTREE_ROOT}/${fsSafeBranch(branch)}`;
4676
5182
  }
4677
5183
  async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath) {
4678
- const stash = await execa7("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
5184
+ const stash = await execa8("git", ["-C", repo.hostMainRepo, "stash", "create"], { reject: false });
4679
5185
  const stashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
4680
- const untracked = await execa7(
5186
+ const untracked = await execa8(
4681
5187
  "git",
4682
5188
  ["-C", repo.hostMainRepo, "ls-files", "--others", "--exclude-standard", "-z"],
4683
5189
  { reject: false }
@@ -4694,7 +5200,7 @@ async function collectRepoCarryOver(repo, branch, containerPath, gitWorktreePath
4694
5200
  };
4695
5201
  }
4696
5202
  async function dexec(container, argv, user = "vscode", cwd = "/") {
4697
- const r = await execa7(
5203
+ const r = await execa8(
4698
5204
  "docker",
4699
5205
  ["exec", "-w", cwd, "--user", user, container, ...argv],
4700
5206
  { reject: false }
@@ -4726,7 +5232,7 @@ async function bindWorktrees(container, binds, onLog) {
4726
5232
  (a, b) => a.kind === "root" && b.kind !== "root" ? -1 : a.kind !== "root" && b.kind === "root" ? 1 : 0
4727
5233
  );
4728
5234
  for (const b of ordered) {
4729
- await execa7(
5235
+ await execa8(
4730
5236
  "docker",
4731
5237
  ["exec", "-w", "/", "--user", "root", container, "sh", "-c", `mountpoint -q ${b.containerPath} && umount ${b.containerPath} || true`],
4732
5238
  { reject: false }
@@ -4748,7 +5254,7 @@ async function seedWorkspace(opts) {
4748
5254
  const wt = r.gitWorktreePath;
4749
5255
  const baseRef = r.repo.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
4750
5256
  const addArgs = r.reuseBranch ? ["worktree", "add", wt, r.branch] : ["worktree", "add", "-b", r.branch, wt, baseRef];
4751
- const add = await execa7(
5257
+ const add = await execa8(
4752
5258
  "docker",
4753
5259
  ["exec", "--user", "vscode", opts.container, "git", "-C", main, ...addArgs],
4754
5260
  { reject: false }
@@ -4759,7 +5265,7 @@ async function seedWorkspace(opts) {
4759
5265
  );
4760
5266
  }
4761
5267
  log(`worktree ${wt} on branch ${r.branch} (host main ${main})`);
4762
- await execa7(
5268
+ await execa8(
4763
5269
  "docker",
4764
5270
  [
4765
5271
  "exec",
@@ -4775,7 +5281,7 @@ async function seedWorkspace(opts) {
4775
5281
  ],
4776
5282
  { reject: false }
4777
5283
  );
4778
- await execa7(
5284
+ await execa8(
4779
5285
  "docker",
4780
5286
  [
4781
5287
  "exec",
@@ -4805,7 +5311,7 @@ async function seedWorkspace(opts) {
4805
5311
  for (const r of opts.repos) {
4806
5312
  const ct = r.containerPath;
4807
5313
  if (r.stashSha) {
4808
- const withIndex = await execa7(
5314
+ const withIndex = await execa8(
4809
5315
  "docker",
4810
5316
  [
4811
5317
  "exec",
@@ -4823,7 +5329,7 @@ async function seedWorkspace(opts) {
4823
5329
  { reject: false }
4824
5330
  );
4825
5331
  if (withIndex.exitCode !== 0) {
4826
- const noIndex = await execa7(
5332
+ const noIndex = await execa8(
4827
5333
  "docker",
4828
5334
  [
4829
5335
  "exec",
@@ -4851,7 +5357,7 @@ async function seedWorkspace(opts) {
4851
5357
  }
4852
5358
  }
4853
5359
  if (r.untrackedNul.length > 0) {
4854
- const tarOut = await execa7("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
5360
+ const tarOut = await execa8("tar", ["-C", r.hostSource, "--null", "-T", "-", "-cf", "-"], {
4855
5361
  input: r.untrackedNul.replace(/\0$/, ""),
4856
5362
  encoding: "buffer",
4857
5363
  reject: false
@@ -4860,7 +5366,7 @@ async function seedWorkspace(opts) {
4860
5366
  log(`warning: tar of untracked files for ${r.repo.hostMainRepo} failed: ${tarOut.stderr}`);
4861
5367
  continue;
4862
5368
  }
4863
- const tarIn = await execa7(
5369
+ const tarIn = await execa8(
4864
5370
  "docker",
4865
5371
  ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
4866
5372
  { input: tarOut.stdout, reject: false }
@@ -4877,14 +5383,14 @@ async function seedWorkspace(opts) {
4877
5383
  async function seedWorkspaceFromDir(opts) {
4878
5384
  const log = opts.onLog ?? (() => {
4879
5385
  });
4880
- const tarOut = await execa7("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
5386
+ const tarOut = await execa8("tar", ["-C", opts.hostSource, "-cf", "-", "."], {
4881
5387
  encoding: "buffer",
4882
5388
  reject: false
4883
5389
  });
4884
5390
  if (tarOut.exitCode !== 0) {
4885
5391
  throw new GitWorktreeError(`tar of ${opts.hostSource} failed: ${tarOut.stderr}`);
4886
5392
  }
4887
- const tarIn = await execa7(
5393
+ const tarIn = await execa8(
4888
5394
  "docker",
4889
5395
  ["exec", "-i", "--user", "1000:1000", opts.container, "tar", "-C", "/workspace", "-xf", "-"],
4890
5396
  { input: tarOut.stdout, reject: false }
@@ -4895,18 +5401,159 @@ async function seedWorkspaceFromDir(opts) {
4895
5401
  log(`seeded /workspace from ${opts.hostSource}`);
4896
5402
  }
4897
5403
  async function removeInBoxWorktree(args) {
4898
- const remove = await execa7(
5404
+ const remove = await execa8(
4899
5405
  "git",
4900
5406
  ["-C", args.hostMainRepo, "worktree", "remove", "--force", args.gitWorktreePath],
4901
5407
  { reject: false }
4902
5408
  );
4903
5409
  if (remove.exitCode === 0) return;
4904
- await execa7("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
5410
+ await execa8("git", ["-C", args.hostMainRepo, "worktree", "prune"], { reject: false });
4905
5411
  }
4906
5412
  function ctParent(p) {
4907
5413
  const i = p.lastIndexOf("/");
4908
5414
  return i <= 0 ? "/" : p.slice(0, i);
4909
5415
  }
5416
+ async function gitIn(container, ct, args) {
5417
+ return execInBox(container, ["git", "-C", ct, ...args], { user: "vscode" });
5418
+ }
5419
+ function splitNul(s) {
5420
+ return s.split("\0").filter((p) => p.length > 0);
5421
+ }
5422
+ async function unmergedPaths(container, ct) {
5423
+ const r = await gitIn(container, ct, ["diff", "--name-only", "--diff-filter=U", "-z"]);
5424
+ return r.exitCode === 0 ? splitNul(r.stdout) : [];
5425
+ }
5426
+ async function resyncWorkspaceFromHost(opts) {
5427
+ const log = opts.onLog ?? (() => {
5428
+ });
5429
+ const results = [];
5430
+ for (const w of opts.worktrees) {
5431
+ const ct = w.containerPath;
5432
+ const hostMain = w.hostMainRepo;
5433
+ const boxBranch = w.branch;
5434
+ const res = { containerPath: ct, mergeConflicts: [], overlaySkipped: [] };
5435
+ const hostBranchProbe = await execa8(
5436
+ "git",
5437
+ ["-C", hostMain, "symbolic-ref", "--short", "-q", "HEAD"],
5438
+ { reject: false }
5439
+ );
5440
+ const hostRef = hostBranchProbe.exitCode === 0 && hostBranchProbe.stdout.trim() ? hostBranchProbe.stdout.trim() : (await execa8("git", ["-C", hostMain, "rev-parse", "HEAD"], { reject: false })).stdout.trim();
5441
+ if (!hostRef) {
5442
+ log(`resync: ${ct}: could not resolve host ref; skipping`);
5443
+ results.push(res);
5444
+ continue;
5445
+ }
5446
+ const stash = await execa8("git", ["-C", hostMain, "stash", "create"], { reject: false });
5447
+ const hostStashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;
5448
+ const untracked = await execa8(
5449
+ "git",
5450
+ ["-C", hostMain, "ls-files", "--others", "--exclude-standard", "-z"],
5451
+ { reject: false }
5452
+ );
5453
+ const hostUntracked = untracked.exitCode === 0 ? splitNul(untracked.stdout) : [];
5454
+ if (hostRef !== boxBranch) {
5455
+ const status = await gitIn(opts.container, ct, ["status", "--porcelain"]);
5456
+ const boxDirty = status.stdout.split("\n").some((line) => line.length > 0 && !line.startsWith("??"));
5457
+ let boxStashed = false;
5458
+ if (boxDirty) {
5459
+ const push = await gitIn(opts.container, ct, ["stash", "push", "-m", "agentbox-resync"]);
5460
+ boxStashed = push.exitCode === 0;
5461
+ }
5462
+ const newCommits = await gitIn(opts.container, ct, [
5463
+ "rev-list",
5464
+ "--count",
5465
+ `${boxBranch}..${hostRef}`
5466
+ ]);
5467
+ const n = newCommits.exitCode === 0 ? newCommits.stdout.trim() : "?";
5468
+ const merge = await gitIn(opts.container, ct, ["merge", "--no-commit", hostRef]);
5469
+ const mergeInProgress = (await gitIn(opts.container, ct, ["rev-parse", "-q", "--verify", "MERGE_HEAD"])).exitCode === 0;
5470
+ const conflicts = await unmergedPaths(opts.container, ct);
5471
+ if (conflicts.length > 0) {
5472
+ await gitIn(opts.container, ct, ["checkout", "--ours", "--", ...conflicts]);
5473
+ await gitIn(opts.container, ct, ["add", "--", ...conflicts]);
5474
+ res.mergeConflicts.push(...conflicts);
5475
+ }
5476
+ if (mergeInProgress) {
5477
+ await gitIn(opts.container, ct, [
5478
+ "-c",
5479
+ "user.name=agentbox",
5480
+ "-c",
5481
+ "user.email=agentbox@users.noreply.github.com",
5482
+ "commit",
5483
+ "--no-edit"
5484
+ ]);
5485
+ log(
5486
+ `resync: ${ct}: merged ${n} new host commit(s) from ${hostRef}` + (conflicts.length > 0 ? ` (${String(conflicts.length)} conflict(s) kept box version)` : "")
5487
+ );
5488
+ } else if (merge.exitCode === 0) {
5489
+ log(`resync: ${ct}: ${n === "0" ? "already up to date" : `fast-forwarded to ${hostRef}`}`);
5490
+ } else {
5491
+ await gitIn(opts.container, ct, ["merge", "--abort"]);
5492
+ log(`resync: ${ct}: merge skipped (${(merge.stderr || merge.stdout).trim().split("\n")[0]})`);
5493
+ }
5494
+ if (boxStashed) {
5495
+ const pop = await gitIn(opts.container, ct, ["stash", "pop"]);
5496
+ if (pop.exitCode !== 0) {
5497
+ const popConflicts = await unmergedPaths(opts.container, ct);
5498
+ for (const p of popConflicts) {
5499
+ await gitIn(opts.container, ct, ["checkout", "--theirs", "--", p]);
5500
+ await gitIn(opts.container, ct, ["reset", "-q", "--", p]);
5501
+ }
5502
+ await gitIn(opts.container, ct, ["stash", "drop"]);
5503
+ }
5504
+ }
5505
+ }
5506
+ if (hostStashSha) {
5507
+ const apply = await gitIn(opts.container, ct, ["stash", "apply", hostStashSha]);
5508
+ if (apply.exitCode !== 0) {
5509
+ const conflicts = await unmergedPaths(opts.container, ct);
5510
+ for (const p of conflicts) {
5511
+ await gitIn(opts.container, ct, ["checkout", "--ours", "--", p]);
5512
+ await gitIn(opts.container, ct, ["reset", "-q", "--", p]);
5513
+ }
5514
+ if (conflicts.length > 0) res.overlaySkipped.push(...conflicts);
5515
+ }
5516
+ }
5517
+ if (hostUntracked.length > 0) {
5518
+ const filter = await execa8(
5519
+ "docker",
5520
+ [
5521
+ "exec",
5522
+ "-i",
5523
+ "--user",
5524
+ "vscode",
5525
+ opts.container,
5526
+ "bash",
5527
+ "-c",
5528
+ 'cd "$1" && while IFS= read -r -d "" f; do [ -e "$f" ] && printf "%s\\0" "$f"; done',
5529
+ "bash",
5530
+ ct
5531
+ ],
5532
+ { input: hostUntracked.join("\0"), reject: false }
5533
+ );
5534
+ const existing = new Set(filter.exitCode === 0 ? splitNul(filter.stdout) : []);
5535
+ const toCopy = hostUntracked.filter((p) => !existing.has(p));
5536
+ for (const p of hostUntracked) if (existing.has(p)) res.overlaySkipped.push(p);
5537
+ if (toCopy.length > 0) {
5538
+ const tarOut = await execa8("tar", ["-C", hostMain, "--null", "-T", "-", "-cf", "-"], {
5539
+ input: toCopy.join("\0"),
5540
+ encoding: "buffer",
5541
+ reject: false
5542
+ });
5543
+ if (tarOut.exitCode === 0) {
5544
+ await execa8(
5545
+ "docker",
5546
+ ["exec", "-i", "--user", "vscode", opts.container, "tar", "-C", ct, "-xf", "-"],
5547
+ { input: tarOut.stdout, reject: false }
5548
+ );
5549
+ log(`resync: ${ct}: copied ${String(toCopy.length)} untracked host file(s)`);
5550
+ }
5551
+ }
5552
+ }
5553
+ results.push(res);
5554
+ }
5555
+ return results;
5556
+ }
4910
5557
  var PORTLESS_BIN = "portless";
4911
5558
  var SUB_VERSION = ["--version"];
4912
5559
  var SUB_ALIAS = "alias";
@@ -4917,7 +5564,7 @@ var cached = null;
4917
5564
  async function detectPortless() {
4918
5565
  if (cached !== null) return cached;
4919
5566
  try {
4920
- const ver = await execa8(PORTLESS_BIN, SUB_VERSION, { reject: false });
5567
+ const ver = await execa9(PORTLESS_BIN, SUB_VERSION, { reject: false });
4921
5568
  if (ver.exitCode !== 0) {
4922
5569
  cached = { installed: false, proxyRunning: false };
4923
5570
  return cached;
@@ -4937,7 +5584,7 @@ function resetPortlessCache() {
4937
5584
  }
4938
5585
  async function portlessAlias(name, port) {
4939
5586
  try {
4940
- const r = await execa8(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
5587
+ const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });
4941
5588
  return r.exitCode === 0;
4942
5589
  } catch {
4943
5590
  return false;
@@ -4945,7 +5592,7 @@ async function portlessAlias(name, port) {
4945
5592
  }
4946
5593
  async function portlessUnalias(name) {
4947
5594
  try {
4948
- const r = await execa8(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
5595
+ const r = await execa9(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });
4949
5596
  return r.exitCode === 0;
4950
5597
  } catch {
4951
5598
  return false;
@@ -4954,7 +5601,7 @@ async function portlessUnalias(name) {
4954
5601
  async function portlessGetUrl(name) {
4955
5602
  const fallback = `https://${name}.localhost`;
4956
5603
  try {
4957
- const r = await execa8(PORTLESS_BIN, [SUB_GET, name], { reject: false });
5604
+ const r = await execa9(PORTLESS_BIN, [SUB_GET, name], { reject: false });
4958
5605
  const out = (r.stdout ?? "").trim();
4959
5606
  if (r.exitCode === 0 && /^https?:\/\//.test(out)) return out;
4960
5607
  } catch {
@@ -4975,7 +5622,7 @@ function portlessBrowserEnv(boxName, opts) {
4975
5622
  }
4976
5623
  async function installPortless() {
4977
5624
  try {
4978
- const r = await execa8("npm", ["install", "-g", "portless"], { reject: false });
5625
+ const r = await execa9("npm", ["install", "-g", "portless"], { reject: false });
4979
5626
  return r.exitCode === 0;
4980
5627
  } catch {
4981
5628
  return false;
@@ -4983,7 +5630,7 @@ async function installPortless() {
4983
5630
  }
4984
5631
  async function startPortlessProxy() {
4985
5632
  try {
4986
- const r = await execa8(
5633
+ const r = await execa9(
4987
5634
  PORTLESS_BIN,
4988
5635
  ["proxy", "start", "--no-tls", "-p", String(PORTLESS_PROXY_PORT)],
4989
5636
  { reject: false }
@@ -4996,7 +5643,7 @@ async function startPortlessProxy() {
4996
5643
  function portlessStateDirCandidates() {
4997
5644
  const env = process.env["PORTLESS_STATE_DIR"];
4998
5645
  if (env && env.trim().length > 0) return [env.trim()];
4999
- return ["/tmp/portless", join6(homedir5(), ".portless")];
5646
+ return ["/tmp/portless", join7(homedir6(), ".portless")];
5000
5647
  }
5001
5648
  function pidAlive(pid) {
5002
5649
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -5009,7 +5656,7 @@ function pidAlive(pid) {
5009
5656
  }
5010
5657
  async function readProxyPid(dir) {
5011
5658
  try {
5012
- const raw = await readFile42(join6(dir, "proxy.pid"), "utf8");
5659
+ const raw = await readFile52(join7(dir, "proxy.pid"), "utf8");
5013
5660
  const pid = Number.parseInt(raw.trim(), 10);
5014
5661
  return Number.isFinite(pid) && pid > 0 ? pid : null;
5015
5662
  } catch {
@@ -5029,7 +5676,7 @@ async function resolvePortlessHostStateDir(override) {
5029
5676
  if (env && env.trim().length > 0) return env.trim();
5030
5677
  const live = await findLivePortlessStateDir();
5031
5678
  if (live) return live;
5032
- const home = join6(homedir5(), ".portless");
5679
+ const home = join7(homedir6(), ".portless");
5033
5680
  if (existsSync(home)) return home;
5034
5681
  if (existsSync("/tmp/portless")) return "/tmp/portless";
5035
5682
  return home;
@@ -5037,7 +5684,7 @@ async function resolvePortlessHostStateDir(override) {
5037
5684
  async function isProxyRunning() {
5038
5685
  if (await findLivePortlessStateDir() !== null) return true;
5039
5686
  try {
5040
- const r = await execa8("pgrep", ["-f", "portless proxy"], { reject: false });
5687
+ const r = await execa9("pgrep", ["-f", "portless proxy"], { reject: false });
5041
5688
  return r.exitCode === 0 && (r.stdout ?? "").trim().length > 0;
5042
5689
  } catch {
5043
5690
  return false;
@@ -5058,25 +5705,25 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
5058
5705
  ".cache",
5059
5706
  ".parcel-cache"
5060
5707
  ]);
5061
- var SNAPSHOTS_ROOT = join7(homedir6(), ".agentbox", "snapshots");
5708
+ var SNAPSHOTS_ROOT = join8(homedir7(), ".agentbox", "snapshots");
5062
5709
  function snapshotPathFor(box) {
5063
5710
  const mnemonic = sanitizeMnemonic(box.name);
5064
5711
  const n = box.projectIndex;
5065
5712
  const segment = typeof n === "number" && Number.isFinite(n) && n > 0 ? `${box.id}-${String(n)}-${mnemonic}` : `${box.id}-${mnemonic}`;
5066
- return join7(SNAPSHOTS_ROOT, segment);
5713
+ return join8(SNAPSHOTS_ROOT, segment);
5067
5714
  }
5068
5715
  async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
5069
5716
  const matches = [];
5070
5717
  const walk = async (dir) => {
5071
5718
  let entries;
5072
5719
  try {
5073
- entries = await readdir22(dir, { withFileTypes: true });
5720
+ entries = await readdir42(dir, { withFileTypes: true });
5074
5721
  } catch {
5075
5722
  return;
5076
5723
  }
5077
5724
  for (const entry of entries) {
5078
5725
  if (!entry.isDirectory()) continue;
5079
- const abs = join7(dir, entry.name);
5726
+ const abs = join8(dir, entry.name);
5080
5727
  if (excluded.has(entry.name)) {
5081
5728
  matches.push(abs);
5082
5729
  continue;
@@ -5091,28 +5738,28 @@ async function createSnapshot(opts) {
5091
5738
  const source = resolve2(opts.source);
5092
5739
  const destination = resolve2(opts.destination);
5093
5740
  const excluded = opts.excluded ?? EXCLUDE_DIRS;
5094
- await mkdir4(SNAPSHOTS_ROOT, { recursive: true });
5741
+ await mkdir42(SNAPSHOTS_ROOT, { recursive: true });
5095
5742
  const cpArgs = platform() === "darwin" ? ["-cR"] : ["-R"];
5096
- await execa9("cp", [...cpArgs, `${source}/`, destination]);
5743
+ await execa10("cp", [...cpArgs, `${source}/`, destination]);
5097
5744
  const toPrune = await findExcludedDirs(destination, excluded);
5098
- await Promise.all(toPrune.map((p) => rm22(p, { recursive: true, force: true })));
5745
+ await Promise.all(toPrune.map((p) => rm32(p, { recursive: true, force: true })));
5099
5746
  return { destination, prunedPaths: toPrune };
5100
5747
  }
5101
- var CHECKPOINTS_ROOT = join8(homedir7(), ".agentbox", "checkpoints");
5748
+ var CHECKPOINTS_ROOT = join9(homedir8(), ".agentbox", "checkpoints");
5102
5749
  var CHECKPOINT_IMAGE_PREFIX = "agentbox-ckpt-";
5103
5750
  function checkpointImageTag(projectRoot, name) {
5104
- const mnemonic = sanitizeMnemonic(basename22(projectRoot));
5751
+ const mnemonic = sanitizeMnemonic(basename32(projectRoot));
5105
5752
  return `${CHECKPOINT_IMAGE_PREFIX}${hashProjectPath(projectRoot)}_${mnemonic}:${name}`;
5106
5753
  }
5107
5754
  function projectCheckpointsDir(projectRoot) {
5108
- return join8(CHECKPOINTS_ROOT, projectDirSegment(projectRoot));
5755
+ return join9(CHECKPOINTS_ROOT, projectDirSegment(projectRoot));
5109
5756
  }
5110
5757
  function checkpointDir(projectRoot, name) {
5111
- return join8(projectCheckpointsDir(projectRoot), name);
5758
+ return join9(projectCheckpointsDir(projectRoot), name);
5112
5759
  }
5113
5760
  async function readManifest(dir) {
5114
5761
  try {
5115
- const raw = await readFile5(join8(dir, "manifest.json"), "utf8");
5762
+ const raw = await readFile6(join9(dir, "manifest.json"), "utf8");
5116
5763
  const m = JSON.parse(raw);
5117
5764
  if (m.schema !== 2 && m.schema !== 3) return null;
5118
5765
  return m;
@@ -5120,23 +5767,39 @@ async function readManifest(dir) {
5120
5767
  return null;
5121
5768
  }
5122
5769
  }
5123
- async function listCheckpoints(projectRoot) {
5124
- const root = projectCheckpointsDir(projectRoot);
5770
+ async function listCheckpointsInDir(root) {
5125
5771
  let entries;
5126
5772
  try {
5127
- entries = (await readdir32(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5773
+ entries = (await readdir5(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5128
5774
  } catch {
5129
5775
  return [];
5130
5776
  }
5131
5777
  const out = [];
5132
5778
  for (const name of entries) {
5133
- const dir = join8(root, name);
5779
+ const dir = join9(root, name);
5134
5780
  const manifest = await readManifest(dir);
5135
5781
  if (manifest) out.push({ name, dir, manifest });
5136
5782
  }
5137
5783
  out.sort((a, b) => a.manifest.createdAt.localeCompare(b.manifest.createdAt));
5138
5784
  return out;
5139
5785
  }
5786
+ async function listCheckpoints(projectRoot) {
5787
+ return listCheckpointsInDir(projectCheckpointsDir(projectRoot));
5788
+ }
5789
+ async function listAllCheckpoints() {
5790
+ let segments;
5791
+ try {
5792
+ segments = (await readdir5(CHECKPOINTS_ROOT, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5793
+ } catch {
5794
+ return [];
5795
+ }
5796
+ const out = [];
5797
+ for (const segment of segments) {
5798
+ const items = await listCheckpointsInDir(join9(CHECKPOINTS_ROOT, segment));
5799
+ if (items.length > 0) out.push({ segment, items });
5800
+ }
5801
+ return out;
5802
+ }
5140
5803
  async function resolveCheckpoint(projectRoot, ref) {
5141
5804
  const dir = checkpointDir(projectRoot, ref);
5142
5805
  const manifest = await readManifest(dir);
@@ -5146,21 +5809,21 @@ async function resolveCheckpoint(projectRoot, ref) {
5146
5809
  async function listAllCheckpointImages() {
5147
5810
  let projectDirs;
5148
5811
  try {
5149
- projectDirs = (await readdir32(CHECKPOINTS_ROOT, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5812
+ projectDirs = (await readdir5(CHECKPOINTS_ROOT, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5150
5813
  } catch {
5151
5814
  return [];
5152
5815
  }
5153
5816
  const out = /* @__PURE__ */ new Set();
5154
5817
  for (const proj of projectDirs) {
5155
- const projPath = join8(CHECKPOINTS_ROOT, proj);
5818
+ const projPath = join9(CHECKPOINTS_ROOT, proj);
5156
5819
  let names;
5157
5820
  try {
5158
- names = (await readdir32(projPath, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5821
+ names = (await readdir5(projPath, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
5159
5822
  } catch {
5160
5823
  continue;
5161
5824
  }
5162
5825
  for (const name of names) {
5163
- const manifest = await readManifest(join8(projPath, name));
5826
+ const manifest = await readManifest(join9(projPath, name));
5164
5827
  if (manifest) out.add(manifest.image);
5165
5828
  }
5166
5829
  }
@@ -5170,7 +5833,7 @@ async function removeCheckpoint(projectRoot, ref) {
5170
5833
  const dir = checkpointDir(projectRoot, ref);
5171
5834
  const manifest = await readManifest(dir);
5172
5835
  if (!manifest) return false;
5173
- await rm3(dir, { recursive: true, force: true });
5836
+ await rm4(dir, { recursive: true, force: true });
5174
5837
  await removeImage(manifest.image, { force: true });
5175
5838
  return true;
5176
5839
  }
@@ -5202,7 +5865,7 @@ async function runCleanup(container, log) {
5202
5865
  }
5203
5866
  }
5204
5867
  async function inspectImageConfig(imageRef) {
5205
- const r = await execa10("docker", ["image", "inspect", imageRef], { reject: false });
5868
+ const r = await execa11("docker", ["image", "inspect", imageRef], { reject: false });
5206
5869
  if (r.exitCode !== 0) {
5207
5870
  throw new CheckpointError(`docker image inspect ${imageRef} failed`, r.stdout, r.stderr);
5208
5871
  }
@@ -5278,14 +5941,14 @@ async function createCheckpoint(opts) {
5278
5941
  await runCleanup(box.container, log);
5279
5942
  if (type === "layered") {
5280
5943
  log(`docker commit ${box.container} -> ${tag} (layered)`);
5281
- const r = await execa10("docker", ["commit", box.container, tag], { reject: false });
5944
+ const r = await execa11("docker", ["commit", box.container, tag], { reject: false });
5282
5945
  if (r.exitCode !== 0) {
5283
5946
  throw new CheckpointError(`docker commit failed for ${box.container}`, r.stdout, r.stderr);
5284
5947
  }
5285
5948
  } else {
5286
5949
  log(`docker commit ${box.container} -> <intermediate> (flattened path)`);
5287
5950
  const intermediate = `${tag}-intermediate`;
5288
- const commit = await execa10("docker", ["commit", box.container, intermediate], {
5951
+ const commit = await execa11("docker", ["commit", box.container, intermediate], {
5289
5952
  reject: false
5290
5953
  });
5291
5954
  if (commit.exitCode !== 0) {
@@ -5321,7 +5984,7 @@ async function createCheckpoint(opts) {
5321
5984
  cliVersion: stamp.cliVersion,
5322
5985
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
5323
5986
  };
5324
- await writeFile22(join8(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
5987
+ await writeFile32(join9(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
5325
5988
  if (opts.setDefault) {
5326
5989
  await setConfigValue("project", "box.defaultCheckpointDocker", name, opts.projectRoot);
5327
5990
  log(`set project default checkpoint (box.defaultCheckpointDocker) -> ${name}`);
@@ -5330,7 +5993,7 @@ async function createCheckpoint(opts) {
5330
5993
  }
5331
5994
  async function flattenImage(sourceTag, destTag, log) {
5332
5995
  const tmpName = `agentbox-flatten-${Date.now().toString(36)}`;
5333
- const create = await execa10(
5996
+ const create = await execa11(
5334
5997
  "docker",
5335
5998
  ["create", "--name", tmpName, sourceTag, "sleep", "0"],
5336
5999
  { reject: false }
@@ -5338,11 +6001,11 @@ async function flattenImage(sourceTag, destTag, log) {
5338
6001
  if (create.exitCode !== 0) {
5339
6002
  throw new CheckpointError(`docker create for flatten failed`, create.stdout, create.stderr);
5340
6003
  }
5341
- const scratch = await mkdtemp2(join8(tmpdir2(), "agentbox-flatten-"));
6004
+ const scratch = await mkdtemp3(join9(tmpdir3(), "agentbox-flatten-"));
5342
6005
  try {
5343
- const rootfsPath = join8(scratch, "rootfs.tar");
6006
+ const rootfsPath = join9(scratch, "rootfs.tar");
5344
6007
  log(`exporting rootfs of ${sourceTag} to ${rootfsPath}`);
5345
- const exp = await execa10("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
6008
+ const exp = await execa11("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
5346
6009
  if (exp.exitCode !== 0) {
5347
6010
  throw new CheckpointError(`docker export failed`, exp.stdout, exp.stderr);
5348
6011
  }
@@ -5353,19 +6016,19 @@ async function flattenImage(sourceTag, destTag, log) {
5353
6016
  "ADD rootfs.tar /",
5354
6017
  ...renderConfigDirectives(cfg)
5355
6018
  ];
5356
- await writeFile22(join8(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
6019
+ await writeFile32(join9(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
5357
6020
  log(`building flattened ${destTag} from rootfs.tar (FROM scratch)`);
5358
- const build = await execa10(
6021
+ const build = await execa11(
5359
6022
  "docker",
5360
- ["build", "-t", destTag, "-f", join8(scratch, "Dockerfile"), scratch],
6023
+ ["build", "-t", destTag, "-f", join9(scratch, "Dockerfile"), scratch],
5361
6024
  { reject: false }
5362
6025
  );
5363
6026
  if (build.exitCode !== 0) {
5364
6027
  throw new CheckpointError(`flatten docker build failed`, build.stdout, build.stderr);
5365
6028
  }
5366
6029
  } finally {
5367
- await execa10("docker", ["rm", "-f", tmpName], { reject: false });
5368
- await rm3(scratch, { recursive: true, force: true });
6030
+ await execa11("docker", ["rm", "-f", tmpName], { reject: false });
6031
+ await rm4(scratch, { recursive: true, force: true });
5369
6032
  }
5370
6033
  }
5371
6034
  var CheckpointError = class extends Error {
@@ -5390,7 +6053,7 @@ async function launchCtlDaemon(container, hostSocketPath, timeoutMs = 3e3) {
5390
6053
  }
5391
6054
  const deadline = Date.now() + timeoutMs;
5392
6055
  while (Date.now() < deadline) {
5393
- if (await pathExists4(hostSocketPath)) return { up: true };
6056
+ if (await pathExists5(hostSocketPath)) return { up: true };
5394
6057
  await new Promise((r) => setTimeout(r, 100));
5395
6058
  }
5396
6059
  return {
@@ -5398,9 +6061,9 @@ async function launchCtlDaemon(container, hostSocketPath, timeoutMs = 3e3) {
5398
6061
  reason: `socket ${hostSocketPath} did not appear within ${String(timeoutMs)}ms`
5399
6062
  };
5400
6063
  }
5401
- async function pathExists4(p) {
6064
+ async function pathExists5(p) {
5402
6065
  try {
5403
- await stat5(p);
6066
+ await stat7(p);
5404
6067
  return true;
5405
6068
  } catch {
5406
6069
  return false;
@@ -5408,7 +6071,7 @@ async function pathExists4(p) {
5408
6071
  }
5409
6072
  async function writeBoxEnvFile(container, env) {
5410
6073
  const body = formatBoxEnvBody(env);
5411
- const result = await execa11(
6074
+ const result = await execa12(
5412
6075
  "docker",
5413
6076
  ["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
5414
6077
  { input: body, reject: false }
@@ -5432,7 +6095,7 @@ function shellSingleQuote(s) {
5432
6095
  return `'${s.replace(/'/g, `'\\''`)}'`;
5433
6096
  }
5434
6097
  async function ensureHomeOwnedByVscode(container) {
5435
- await execa12(
6098
+ await execa13(
5436
6099
  "docker",
5437
6100
  [
5438
6101
  "exec",
@@ -5448,10 +6111,10 @@ async function ensureHomeOwnedByVscode(container) {
5448
6111
  { reject: false }
5449
6112
  );
5450
6113
  }
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");
6114
+ var STATE_DIR22 = join10(homedir9(), ".agentbox");
6115
+ var PID_FILE = join10(STATE_DIR22, "relay.pid");
6116
+ var LOG_FILE = join10(STATE_DIR22, "relay.log");
6117
+ var RELAY_HOME_DIR = join10(STATE_DIR22, "relay");
5455
6118
  var PORT = DEFAULT_RELAY_PORT;
5456
6119
  var ENDPOINT = {
5457
6120
  // host.docker.internal is the Docker Desktop / OrbStack-supplied alias for
@@ -5565,7 +6228,7 @@ async function spawnRelay(relayBin, cliEntry, log) {
5565
6228
  );
5566
6229
  child.unref();
5567
6230
  if (typeof child.pid === "number") {
5568
- await writeFile32(PID_FILE, String(child.pid), "utf8");
6231
+ await writeFile4(PID_FILE, String(child.pid), "utf8");
5569
6232
  log(`spawned relay host process (pid ${String(child.pid)}, port ${String(PORT)})`);
5570
6233
  }
5571
6234
  for (let i = 0; i < 25; i++) {
@@ -5618,28 +6281,28 @@ async function stageRelayHome(version, log) {
5618
6281
  if (process.env.AGENTBOX_RELAY_BIN || process.env.AGENTBOX_CLI_ENTRY) return null;
5619
6282
  const cliRoot = findCliRoot(dirname3(fileURLToPath(import.meta.url)));
5620
6283
  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");
6284
+ const homeDir = join10(RELAY_HOME_DIR, version);
6285
+ const stagedEntry = join10(homeDir, "dist", "index.js");
6286
+ const stagedBin = join10(homeDir, "runtime", "relay", "bin.cjs");
5624
6287
  if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
5625
6288
  return { relayBin: stagedBin, cliEntry: stagedEntry };
5626
6289
  }
5627
- const nodeModules = resolveDepRoot(join9(cliRoot, "dist", "index.js"));
6290
+ const nodeModules = resolveDepRoot(join10(cliRoot, "dist", "index.js"));
5628
6291
  if (nodeModules === null) return null;
5629
6292
  const tmpDir = `${homeDir}.tmp-${String(process.pid)}`;
5630
6293
  try {
5631
6294
  await mkdir6(RELAY_HOME_DIR, { recursive: true });
5632
- await rm4(tmpDir, { recursive: true, force: true });
6295
+ await rm5(tmpDir, { recursive: true, force: true });
5633
6296
  await mkdir6(tmpDir, { recursive: true });
5634
6297
  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 });
6298
+ const src = join10(cliRoot, sub);
6299
+ if (existsSync2(src)) await cp(src, join10(tmpDir, sub), { recursive: true });
5637
6300
  }
5638
- await cp(nodeModules, join9(tmpDir, "node_modules"), { recursive: true, dereference: true });
5639
- await rm4(homeDir, { recursive: true, force: true });
6301
+ await cp(nodeModules, join10(tmpDir, "node_modules"), { recursive: true, dereference: true });
6302
+ await rm5(homeDir, { recursive: true, force: true });
5640
6303
  await rename3(tmpDir, homeDir);
5641
6304
  } catch (err) {
5642
- await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
6305
+ await rm5(tmpDir, { recursive: true, force: true }).catch(() => {
5643
6306
  });
5644
6307
  if (existsSync2(stagedEntry) && existsSync2(stagedBin)) {
5645
6308
  return { relayBin: stagedBin, cliEntry: stagedEntry };
@@ -5654,7 +6317,7 @@ async function stageRelayHome(version, log) {
5654
6317
  }
5655
6318
  function findCliRoot(moduleDir) {
5656
6319
  for (const root of [resolve22(moduleDir, ".."), resolve22(moduleDir, "..", "..")]) {
5657
- if (existsSync2(join9(root, "dist", "index.js")) && existsSync2(join9(root, "runtime", "relay", "bin.cjs"))) {
6320
+ if (existsSync2(join10(root, "dist", "index.js")) && existsSync2(join10(root, "runtime", "relay", "bin.cjs"))) {
5658
6321
  return root;
5659
6322
  }
5660
6323
  }
@@ -5669,7 +6332,7 @@ function resolveDepRoot(fromFile) {
5669
6332
  const idx = main.lastIndexOf(marker);
5670
6333
  if (idx === -1) return null;
5671
6334
  const nm = main.slice(0, idx + marker.length - 1);
5672
- return existsSync2(join9(nm, "commander")) ? nm : null;
6335
+ return existsSync2(join10(nm, "commander")) ? nm : null;
5673
6336
  } catch {
5674
6337
  return null;
5675
6338
  }
@@ -5677,13 +6340,13 @@ function resolveDepRoot(fromFile) {
5677
6340
  async function gcOldRelayHomes(keepVersion) {
5678
6341
  let entries;
5679
6342
  try {
5680
- entries = await readdir4(RELAY_HOME_DIR);
6343
+ entries = await readdir6(RELAY_HOME_DIR);
5681
6344
  } catch {
5682
6345
  return;
5683
6346
  }
5684
6347
  for (const name of entries) {
5685
6348
  if (name === keepVersion) continue;
5686
- await rm4(join9(RELAY_HOME_DIR, name), { recursive: true, force: true }).catch(() => {
6349
+ await rm5(join10(RELAY_HOME_DIR, name), { recursive: true, force: true }).catch(() => {
5687
6350
  });
5688
6351
  }
5689
6352
  }
@@ -5794,7 +6457,7 @@ function fetchHealthz(timeoutMs) {
5794
6457
  }
5795
6458
  async function readPidFile() {
5796
6459
  try {
5797
- const text = await readFile6(PID_FILE, "utf8");
6460
+ const text = await readFile7(PID_FILE, "utf8");
5798
6461
  const pid = Number.parseInt(text.trim(), 10);
5799
6462
  return Number.isFinite(pid) && pid > 0 ? pid : null;
5800
6463
  } catch {
@@ -6097,8 +6760,8 @@ async function ensureAgentboxTasksFile(container, services, opts = {}) {
6097
6760
  return { status: "wrote" };
6098
6761
  }
6099
6762
  async function writeFileInBox(container, path, content) {
6100
- const { execa: execa19 } = await import("execa");
6101
- const result = await execa19(
6763
+ const { execa: execa20 } = await import("execa");
6764
+ const result = await execa20(
6102
6765
  "docker",
6103
6766
  ["exec", "-i", "--user", "vscode", container, "sh", "-c", `cat > ${shellQuote(path)}`],
6104
6767
  { input: content, reject: false }
@@ -6122,29 +6785,29 @@ function persistableLimits(lim) {
6122
6785
  return Object.keys(out).length > 0 ? out : void 0;
6123
6786
  }
6124
6787
  function sanitizeBasename(workspacePath) {
6125
- const raw = basename3(resolve3(workspacePath));
6788
+ const raw = basename4(resolve3(workspacePath));
6126
6789
  return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
6127
6790
  }
6128
6791
  function defaultBoxName(workspacePath, id) {
6129
6792
  const base = sanitizeBasename(workspacePath);
6130
6793
  return base.length > 0 ? `${base}-${id}` : id;
6131
6794
  }
6132
- async function pathExists5(p) {
6795
+ async function pathExists6(p) {
6133
6796
  try {
6134
- await stat6(p);
6797
+ await stat8(p);
6135
6798
  return true;
6136
6799
  } catch {
6137
6800
  return false;
6138
6801
  }
6139
6802
  }
6140
6803
  async function buildIdentityMounts() {
6141
- const home = homedir9();
6804
+ const home = homedir10();
6142
6805
  const candidates = [
6143
- { src: join10(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
6806
+ { src: join11(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
6144
6807
  ];
6145
6808
  const out = [];
6146
6809
  for (const c of candidates) {
6147
- if (await pathExists5(c.src)) {
6810
+ if (await pathExists6(c.src)) {
6148
6811
  out.push(`${c.src}:${c.dst}${c.readOnly ? ":ro" : ""}`);
6149
6812
  }
6150
6813
  }
@@ -6154,11 +6817,11 @@ async function createBox(opts) {
6154
6817
  const log = opts.onLog ?? (() => {
6155
6818
  });
6156
6819
  const workspace = resolve3(opts.workspacePath);
6157
- if (!await pathExists5(workspace)) {
6820
+ if (!await pathExists6(workspace)) {
6158
6821
  throw new Error(`workspace does not exist: ${workspace}`);
6159
6822
  }
6160
- const cfgPath = join10(workspace, "agentbox.yaml");
6161
- if (await pathExists5(cfgPath)) {
6823
+ const cfgPath = join11(workspace, "agentbox.yaml");
6824
+ if (await pathExists6(cfgPath)) {
6162
6825
  try {
6163
6826
  const cfg = await loadConfig(cfgPath);
6164
6827
  log(`agentbox.yaml validated (${String(cfg.services.length)} service(s))`);
@@ -6175,6 +6838,7 @@ async function createBox(opts) {
6175
6838
  let checkpointImage;
6176
6839
  let checkpointSource;
6177
6840
  let restoredWorktrees;
6841
+ let resyncResult;
6178
6842
  if (opts.checkpointRef) {
6179
6843
  const projectRootForCkpt = opts.projectRoot ?? workspace;
6180
6844
  const head = await resolveCheckpoint(projectRootForCkpt, opts.checkpointRef);
@@ -6349,7 +7013,7 @@ async function createBox(opts) {
6349
7013
  log(`seeded claude credentials into ${claudeSpec.volume} from host backup`);
6350
7014
  }
6351
7015
  const claudeMounts = buildClaudeMounts(claudeSpec, process.env);
6352
- const wantCodex = opts.codexConfig !== void 0 || await pathExists5(join10(homedir9(), ".codex"));
7016
+ const wantCodex = opts.codexConfig !== void 0 || await pathExists6(join11(homedir10(), ".codex"));
6353
7017
  let codexMounts;
6354
7018
  let codexConfigVolume;
6355
7019
  if (wantCodex) {
@@ -6369,7 +7033,7 @@ async function createBox(opts) {
6369
7033
  codexMounts = buildCodexMounts(codexSpec, process.env);
6370
7034
  codexConfigVolume = codexSpec.volume;
6371
7035
  }
6372
- const wantOpencode = opts.opencodeConfig !== void 0 || await pathExists5(join10(homedir9(), ".config", "opencode")) || await pathExists5(join10(homedir9(), ".local", "share", "opencode"));
7036
+ const wantOpencode = opts.opencodeConfig !== void 0 || await pathExists6(join11(homedir10(), ".config", "opencode")) || await pathExists6(join11(homedir10(), ".local", "share", "opencode"));
6373
7037
  let opencodeMounts;
6374
7038
  let opencodeConfigVolume;
6375
7039
  if (wantOpencode) {
@@ -6390,9 +7054,9 @@ async function createBox(opts) {
6390
7054
  opencodeConfigVolume = opencodeSpec.volume;
6391
7055
  }
6392
7056
  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");
7057
+ const socketDir = join11(boxDir, "run");
7058
+ const socketPath = join11(socketDir, "ctl.sock");
7059
+ const mergedExportDir = join11(boxDir, "workspace");
6396
7060
  await mkdir7(socketDir, { recursive: true });
6397
7061
  await mkdir7(mergedExportDir, { recursive: true });
6398
7062
  const extraVolumes = await buildIdentityMounts();
@@ -6524,7 +7188,7 @@ async function createBox(opts) {
6524
7188
  } catch (err) {
6525
7189
  if (opts.useBranch !== void 0) {
6526
7190
  log(`seedWorkspace failed for --use-branch ${opts.useBranch}; cleaning up the box`);
6527
- await execa13("docker", ["rm", "-f", containerName], { reject: false });
7191
+ await execa14("docker", ["rm", "-f", containerName], { reject: false });
6528
7192
  for (const w of gitWorktreeRecords) {
6529
7193
  await removeInBoxWorktree({
6530
7194
  hostMainRepo: w.hostMainRepo,
@@ -6551,6 +7215,19 @@ async function createBox(opts) {
6551
7215
  log
6552
7216
  );
6553
7217
  log("re-bound /workspace from checkpoint image");
7218
+ if (opts.resyncOnStart !== false) {
7219
+ const repos = await resyncWorkspaceFromHost({
7220
+ container: containerName,
7221
+ worktrees: restoredWorktrees,
7222
+ onLog: log
7223
+ });
7224
+ resyncResult = {
7225
+ repos,
7226
+ hadConflicts: repos.some(
7227
+ (r) => r.mergeConflicts.length > 0 || r.overlaySkipped.length > 0
7228
+ )
7229
+ };
7230
+ }
6554
7231
  } else {
6555
7232
  log("using /workspace from checkpoint image (no worktrees recorded; no rebind)");
6556
7233
  }
@@ -6567,7 +7244,7 @@ async function createBox(opts) {
6567
7244
  }
6568
7245
  if (opts.withPlaywright) {
6569
7246
  log("installing @playwright/cli@latest (--with-playwright)");
6570
- const result = await execa13(
7247
+ const result = await execa14(
6571
7248
  "docker",
6572
7249
  [
6573
7250
  "exec",
@@ -6720,7 +7397,7 @@ async function createBox(opts) {
6720
7397
  createdAt
6721
7398
  };
6722
7399
  await recordBox(record);
6723
- return { record, imageBuilt: built };
7400
+ return { record, imageBuilt: built, resync: resyncResult };
6724
7401
  }
6725
7402
  var DEFAULT_SHELL_SESSION = "shell";
6726
7403
  var SHELL_SESSION_PREFIX = `${DEFAULT_SHELL_SESSION}-`;
@@ -6768,7 +7445,7 @@ function parseShellSessionList(stdout) {
6768
7445
  return out;
6769
7446
  }
6770
7447
  async function listShellSessions(container, user) {
6771
- const res = await execa14(
7448
+ const res = await execa15(
6772
7449
  "docker",
6773
7450
  [
6774
7451
  "exec",
@@ -6792,7 +7469,7 @@ async function startShellSession(opts) {
6792
7469
  const login = opts.login !== false;
6793
7470
  const term = process.env["TERM"] ?? "xterm-256color";
6794
7471
  const cmd = login ? "bash -l" : "bash";
6795
- const result = await execa14(
7472
+ const result = await execa15(
6796
7473
  "docker",
6797
7474
  [
6798
7475
  "exec",
@@ -6841,7 +7518,7 @@ function buildShellSessionAttachArgv(container, sessionName, user) {
6841
7518
  }
6842
7519
  async function shellSessionInfo(container, sessionName, user) {
6843
7520
  const name = sessionName ?? DEFAULT_SHELL_SESSION;
6844
- const has = await execa14(
7521
+ const has = await execa15(
6845
7522
  "docker",
6846
7523
  ["exec", "--user", user ?? CONTAINER_USER, container, "tmux", "has-session", "-t", name],
6847
7524
  { reject: false }
@@ -6849,7 +7526,7 @@ async function shellSessionInfo(container, sessionName, user) {
6849
7526
  return { running: has.exitCode === 0, sessionName: name };
6850
7527
  }
6851
7528
  async function killShellSession(container, sessionName, user) {
6852
- const res = await execa14(
7529
+ const res = await execa15(
6853
7530
  "docker",
6854
7531
  [
6855
7532
  "exec",
@@ -6903,7 +7580,7 @@ async function getBoxEndpoints(record, engine, persisted) {
6903
7580
  for (const svc of persistedServices) pushService(svc.name, svc.port);
6904
7581
  } else {
6905
7582
  try {
6906
- const cfg = await loadConfig(join11(record.workspacePath, "agentbox.yaml"));
7583
+ const cfg = await loadConfig(join12(record.workspacePath, "agentbox.yaml"));
6907
7584
  if (!webServiceName) {
6908
7585
  webServiceName = cfg.services.find((s) => s.expose)?.name ?? null;
6909
7586
  }
@@ -7008,9 +7685,9 @@ function safeHost(url) {
7008
7685
  return "";
7009
7686
  }
7010
7687
  }
7011
- async function pathExists6(p) {
7688
+ async function pathExists7(p) {
7012
7689
  try {
7013
- await stat7(p);
7690
+ await stat9(p);
7014
7691
  return true;
7015
7692
  } catch {
7016
7693
  return false;
@@ -7043,12 +7720,20 @@ async function stopBox(idOrName) {
7043
7720
  await stopContainer(box.container);
7044
7721
  return box;
7045
7722
  }
7723
+ async function resyncBox(idOrName, onLog) {
7724
+ const box = await resolveBox(idOrName);
7725
+ const worktrees = box.gitWorktrees ?? [];
7726
+ if (worktrees.length === 0) return { repos: [], hadConflicts: false };
7727
+ const repos = await resyncWorkspaceFromHost({ container: box.container, worktrees, onLog });
7728
+ const hadConflicts = repos.some((r) => r.mergeConflicts.length > 0 || r.overlaySkipped.length > 0);
7729
+ return { repos, hadConflicts };
7730
+ }
7046
7731
  async function startBox(idOrName) {
7047
7732
  const box = await resolveBox(idOrName);
7048
7733
  for (const w of box.gitWorktrees ?? []) {
7049
- if (!await pathExists6(join12(w.hostMainRepo, ".git"))) {
7734
+ if (!await pathExists7(join13(w.hostMainRepo, ".git"))) {
7050
7735
  throw new Error(
7051
- `main repo for box worktree missing: ${join12(w.hostMainRepo, ".git")} (recreate the box)`
7736
+ `main repo for box worktree missing: ${join13(w.hostMainRepo, ".git")} (recreate the box)`
7052
7737
  );
7053
7738
  }
7054
7739
  }
@@ -7143,7 +7828,7 @@ async function getBoxHostPaths(idOrName) {
7143
7828
  }
7144
7829
  async function dirSizeBytes(path) {
7145
7830
  try {
7146
- const result = await execa15("du", ["-sk", path], { reject: false });
7831
+ const result = await execa16("du", ["-sk", path], { reject: false });
7147
7832
  if (result.exitCode !== 0) return null;
7148
7833
  const sizeKb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
7149
7834
  if (Number.isNaN(sizeKb)) return null;
@@ -7261,14 +7946,14 @@ async function destroyBox(idOrName, opts = {}) {
7261
7946
  let removedSnapshot = null;
7262
7947
  if (box.snapshotDir && !opts.keepSnapshot) {
7263
7948
  try {
7264
- await rm5(box.snapshotDir, { recursive: true, force: true });
7949
+ await rm6(box.snapshotDir, { recursive: true, force: true });
7265
7950
  removedSnapshot = box.snapshotDir;
7266
7951
  } catch {
7267
7952
  removedSnapshot = null;
7268
7953
  }
7269
7954
  }
7270
7955
  try {
7271
- await rm5(boxRunDirFor(box), { recursive: true, force: true });
7956
+ await rm6(boxRunDirFor(box), { recursive: true, force: true });
7272
7957
  } catch {
7273
7958
  }
7274
7959
  await removeBoxRecord(box.id);
@@ -7276,22 +7961,22 @@ async function destroyBox(idOrName, opts = {}) {
7276
7961
  }
7277
7962
  async function listSnapshotDirs() {
7278
7963
  try {
7279
- const entries = await readdir5(SNAPSHOTS_ROOT, { withFileTypes: true });
7280
- return entries.filter((e) => e.isDirectory()).map((e) => join12(SNAPSHOTS_ROOT, e.name));
7964
+ const entries = await readdir7(SNAPSHOTS_ROOT, { withFileTypes: true });
7965
+ return entries.filter((e) => e.isDirectory()).map((e) => join13(SNAPSHOTS_ROOT, e.name));
7281
7966
  } catch {
7282
7967
  return [];
7283
7968
  }
7284
7969
  }
7285
7970
  async function listBoxDirs() {
7286
7971
  try {
7287
- const entries = await readdir5(BOXES_ROOT, { withFileTypes: true });
7288
- return entries.filter((e) => e.isDirectory()).map((e) => join12(BOXES_ROOT, e.name));
7972
+ const entries = await readdir7(BOXES_ROOT, { withFileTypes: true });
7973
+ return entries.filter((e) => e.isDirectory()).map((e) => join13(BOXES_ROOT, e.name));
7289
7974
  } catch {
7290
7975
  return [];
7291
7976
  }
7292
7977
  }
7293
7978
  async function listCheckpointImageTags() {
7294
- const r = await execa15(
7979
+ const r = await execa16(
7295
7980
  "docker",
7296
7981
  ["image", "ls", "--format", "{{.Repository}}:{{.Tag}}", `${CHECKPOINT_IMAGE_PREFIX}*`],
7297
7982
  { reject: false }
@@ -7380,13 +8065,13 @@ async function pruneBoxes(opts = {}) {
7380
8065
  for (const v of orphanVolumes) await removeVolume(v);
7381
8066
  for (const d of orphanSnapshots) {
7382
8067
  try {
7383
- await rm5(d, { recursive: true, force: true });
8068
+ await rm6(d, { recursive: true, force: true });
7384
8069
  } catch {
7385
8070
  }
7386
8071
  }
7387
8072
  for (const d of orphanBoxDirs) {
7388
8073
  try {
7389
- await rm5(d, { recursive: true, force: true });
8074
+ await rm6(d, { recursive: true, force: true });
7390
8075
  } catch {
7391
8076
  }
7392
8077
  }
@@ -7399,7 +8084,7 @@ async function pruneBoxes(opts = {}) {
7399
8084
  } catch {
7400
8085
  }
7401
8086
  try {
7402
- await execa15("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
8087
+ await execa16("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
7403
8088
  } catch {
7404
8089
  }
7405
8090
  try {
@@ -7420,7 +8105,7 @@ async function pruneBoxes(opts = {}) {
7420
8105
  async function snapshotPresent(path) {
7421
8106
  if (!path) return false;
7422
8107
  try {
7423
- const s = await stat7(path);
8108
+ const s = await stat9(path);
7424
8109
  return s.isDirectory();
7425
8110
  } catch {
7426
8111
  return false;
@@ -7461,7 +8146,7 @@ function splitPair(raw) {
7461
8146
  return [parts[0].trim(), parts[1].trim()];
7462
8147
  }
7463
8148
  async function duBytes(path) {
7464
- const result = await execa16("du", ["-sk", path], { reject: false });
8149
+ const result = await execa17("du", ["-sk", path], { reject: false });
7465
8150
  if (result.exitCode !== 0) return null;
7466
8151
  const kb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
7467
8152
  return Number.isNaN(kb) ? null : kb * 1024;
@@ -7470,11 +8155,11 @@ async function volumeSizeBytes(name) {
7470
8155
  if (!name) return null;
7471
8156
  const engine = await detectEngine();
7472
8157
  if (engine === "orbstack") {
7473
- const live = join13(homedir10(), "OrbStack", "docker", "volumes", name);
8158
+ const live = join14(homedir11(), "OrbStack", "docker", "volumes", name);
7474
8159
  const sz = await duBytes(live);
7475
8160
  if (sz !== null) return sz;
7476
8161
  }
7477
- const df = await execa16(
8162
+ const df = await execa17(
7478
8163
  "docker",
7479
8164
  ["system", "df", "-v", "--format", "{{json .Volumes}}"],
7480
8165
  { reject: false }
@@ -7495,7 +8180,7 @@ async function volumeSizeBytes(name) {
7495
8180
  return null;
7496
8181
  }
7497
8182
  async function imageBytes(tag) {
7498
- const r = await execa16("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
8183
+ const r = await execa17("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
7499
8184
  reject: false
7500
8185
  });
7501
8186
  if (r.exitCode !== 0) return null;
@@ -7506,7 +8191,7 @@ async function projectCheckpointImageBytes(projectRoot, name) {
7506
8191
  return imageBytes(checkpointImageTag(projectRoot, name));
7507
8192
  }
7508
8193
  async function allCheckpointImagesBytes() {
7509
- const r = await execa16(
8194
+ const r = await execa17(
7510
8195
  "docker",
7511
8196
  [
7512
8197
  "image",
@@ -7533,7 +8218,7 @@ async function allCheckpointImagesBytes() {
7533
8218
  return any ? total : null;
7534
8219
  }
7535
8220
  async function agentboxHomeBytes() {
7536
- return duBytes(join13(homedir10(), ".agentbox"));
8221
+ return duBytes(join14(homedir11(), ".agentbox"));
7537
8222
  }
7538
8223
  function limitsFromRecord(record) {
7539
8224
  const r = record.resourceLimits;
@@ -7558,7 +8243,7 @@ function reconcileLimits(persisted, dockerJson) {
7558
8243
  };
7559
8244
  }
7560
8245
  async function containerWritableBytes(container) {
7561
- const r = await execa16(
8246
+ const r = await execa17(
7562
8247
  "docker",
7563
8248
  ["ps", "-a", "--filter", `name=^${container}$`, "--format", "{{.Size}}", "--size"],
7564
8249
  { reject: false }
@@ -7605,7 +8290,7 @@ async function boxResourceStats(record) {
7605
8290
  if (await inspectContainerStatus(record.container) !== "running") {
7606
8291
  return base;
7607
8292
  }
7608
- const proc = await execa16(
8293
+ const proc = await execa17(
7609
8294
  "docker",
7610
8295
  ["stats", "--no-stream", "--format", "{{json .}}", record.container],
7611
8296
  { reject: false }
@@ -7651,7 +8336,7 @@ function asText(s) {
7651
8336
  async function uploadToBox(box, hostSrc, boxDst) {
7652
8337
  const srcAbs = resolve4(hostSrc);
7653
8338
  if (!existsSync3(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
7654
- const srcBasename = basename4(srcAbs);
8339
+ const srcBasename = basename5(srcAbs);
7655
8340
  const srcParent = dirname22(srcAbs);
7656
8341
  let boxParent;
7657
8342
  let finalName;
@@ -7659,7 +8344,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7659
8344
  boxParent = boxDst.replace(/\/+$/, "") || "/";
7660
8345
  finalName = srcBasename;
7661
8346
  } else {
7662
- const isDir2 = await execa17(
8347
+ const isDir2 = await execa18(
7663
8348
  "docker",
7664
8349
  ["exec", box.container, "test", "-d", boxDst],
7665
8350
  { reject: false }
@@ -7673,7 +8358,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7673
8358
  }
7674
8359
  }
7675
8360
  const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
7676
- const mk = await execa17(
8361
+ const mk = await execa18(
7677
8362
  "docker",
7678
8363
  ["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
7679
8364
  { reject: false }
@@ -7681,7 +8366,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7681
8366
  if (mk.exitCode !== 0) {
7682
8367
  throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
7683
8368
  }
7684
- const packed = await execa17("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
8369
+ const packed = await execa18("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
7685
8370
  encoding: "buffer",
7686
8371
  reject: false,
7687
8372
  env: { ...process.env, COPYFILE_DISABLE: "1" }
@@ -7689,7 +8374,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7689
8374
  if (packed.exitCode !== 0) {
7690
8375
  throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
7691
8376
  }
7692
- const extract = await execa17(
8377
+ const extract = await execa18(
7693
8378
  "docker",
7694
8379
  ["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
7695
8380
  { input: packed.stdout, reject: false }
@@ -7699,7 +8384,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7699
8384
  }
7700
8385
  if (finalName !== srcBasename) {
7701
8386
  const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
7702
- const mv = await execa17(
8387
+ const mv = await execa18(
7703
8388
  "docker",
7704
8389
  ["exec", "--user", "root", box.container, "mv", initial, finalPath],
7705
8390
  { reject: false }
@@ -7710,7 +8395,7 @@ async function uploadToBox(box, hostSrc, boxDst) {
7710
8395
  );
7711
8396
  }
7712
8397
  }
7713
- const chown = await execa17(
8398
+ const chown = await execa18(
7714
8399
  "docker",
7715
8400
  ["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
7716
8401
  { reject: false }
@@ -7735,11 +8420,11 @@ async function downloadFromBox(box, boxSrc, hostDst) {
7735
8420
  finalName = srcBasename;
7736
8421
  } else {
7737
8422
  hostParent = dirname22(dstAbs);
7738
- finalName = basename4(dstAbs);
8423
+ finalName = basename5(dstAbs);
7739
8424
  }
7740
8425
  mkdirSync(hostParent, { recursive: true });
7741
8426
  const finalPath = posix.join(hostParent, finalName);
7742
- const packed = await execa17(
8427
+ const packed = await execa18(
7743
8428
  "docker",
7744
8429
  ["exec", box.container, "tar", "-C", srcParent, "-cf", "-", srcBasename],
7745
8430
  { encoding: "buffer", reject: false }
@@ -7747,7 +8432,7 @@ async function downloadFromBox(box, boxSrc, hostDst) {
7747
8432
  if (packed.exitCode !== 0) {
7748
8433
  throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
7749
8434
  }
7750
- const extract = await execa17("tar", ["-xf", "-", "-C", hostParent], {
8435
+ const extract = await execa18("tar", ["-xf", "-", "-C", hostParent], {
7751
8436
  input: packed.stdout,
7752
8437
  reject: false
7753
8438
  });
@@ -7770,6 +8455,7 @@ var dockerProvider = {
7770
8455
  checkpointRef: req.checkpointRef,
7771
8456
  fromBranch: req.fromBranch,
7772
8457
  useBranch: req.useBranch,
8458
+ resyncOnStart: req.resyncOnStart,
7773
8459
  image: req.image,
7774
8460
  allowPull: req.allowPull,
7775
8461
  imageRegistry: req.imageRegistry,
@@ -7792,7 +8478,8 @@ var dockerProvider = {
7792
8478
  const result = await createBox(opts);
7793
8479
  return {
7794
8480
  record: { ...result.record, provider: "docker" },
7795
- imageBuilt: result.imageBuilt
8481
+ imageBuilt: result.imageBuilt,
8482
+ resync: result.resync
7796
8483
  };
7797
8484
  },
7798
8485
  async start(box) {
@@ -7879,342 +8566,140 @@ var dockerProvider = {
7879
8566
  return {};
7880
8567
  }
7881
8568
  }
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;
8569
+ const { source } = await pullOrBuild(ref, fingerprint, {
8570
+ onProgress: opts.onLog,
8571
+ allowPull: opts.force ? false : opts.allowPull,
8572
+ registry: opts.registry
8573
+ });
8574
+ if (fingerprint) {
8575
+ opts.onLog?.(
8576
+ `docker image ${ref} ${source}; recorded fingerprint ${fingerprint.contextSha256.slice(0, 12)}`
8577
+ );
8578
+ } else {
8579
+ opts.onLog?.(
8580
+ `docker image ${ref} ${source} (fingerprint unavailable, prepared state not written)`
8581
+ );
8582
+ }
8583
+ return {};
8584
+ }
8585
+ };
8586
+ var BOX_WORKFLOWS_DIR = "/home/vscode/.claude/workflows";
8587
+ var BOX_DYNAMIC_SYNC_MANIFEST = "/home/vscode/.agentbox/dynamic-sync.json";
8588
+ var BOX_MEMORY_DIR = `${BOX_CLAUDE_PROJECT_DIR}/memory`;
8589
+ async function pathExists8(p) {
7957
8590
  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;
8591
+ await stat10(p);
8592
+ return true;
8593
+ } catch {
8594
+ return false;
7969
8595
  }
7970
8596
  }
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;
8597
+ async function walkFiles(root, prefix = "") {
8598
+ let entries;
7996
8599
  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)) {
8600
+ entries = await readdir8(root, { withFileTypes: true });
8601
+ } catch {
8602
+ return [];
8603
+ }
8604
+ const out = [];
8605
+ for (const ent of entries) {
8606
+ const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
8607
+ const full = join15(root, ent.name);
8608
+ if (ent.isDirectory()) {
8609
+ out.push(...await walkFiles(full, rel));
8610
+ } else if (ent.isFile()) {
8611
+ out.push(rel);
8612
+ } else if (ent.isSymbolicLink()) {
8025
8613
  try {
8026
- working = JSON.parse(await readFile7(hostClaudeJson, "utf8"));
8614
+ const s = await stat10(full);
8615
+ if (s.isFile()) out.push(rel);
8027
8616
  } catch {
8028
- working = null;
8029
8617
  }
8030
8618
  }
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;
8619
+ }
8620
+ return out;
8621
+ }
8622
+ async function hashFile(absPath) {
8623
+ const buf = await readFile8(absPath);
8624
+ return createHash22("sha256").update(buf).digest("hex");
8625
+ }
8626
+ async function buildHostSet(name, hostDir, boxDst) {
8627
+ if (hostDir === null) return { dst: boxDst, files: {}, hostDir: null };
8628
+ const rels = await walkFiles(hostDir);
8629
+ const files = {};
8630
+ for (const rel of rels) {
8631
+ files[rel] = await hashFile(join15(hostDir, rel));
8632
+ }
8633
+ return { dst: boxDst, files, hostDir };
8634
+ }
8635
+ async function buildHostSyncManifest(workspacePath, hostHome = homedir12()) {
8636
+ const workflowsDir = join15(hostHome, ".claude", "workflows");
8637
+ const workflowsHost = await pathExists8(workflowsDir) ? workflowsDir : null;
8638
+ const memoryHost = await resolveClaudeMemoryDir(workspacePath, hostHome);
8639
+ const [workflows, memory] = await Promise.all([
8640
+ buildHostSet("workflows", workflowsHost, BOX_WORKFLOWS_DIR),
8641
+ buildHostSet("memory", memoryHost, BOX_MEMORY_DIR)
8642
+ ]);
8643
+ return { sets: { workflows, memory } };
8644
+ }
8645
+ var SET_NAMES = ["workflows", "memory"];
8646
+ function computeSyncDelta(host, box) {
8647
+ const uploads = [];
8648
+ const deletions = [];
8649
+ const nextSets = {};
8650
+ for (const name of SET_NAMES) {
8651
+ const hostSet = host.sets[name];
8652
+ const boxFiles = box?.sets?.[name]?.files ?? {};
8653
+ for (const [rel, hash] of Object.entries(hostSet.files)) {
8654
+ if (boxFiles[rel] !== hash) {
8655
+ uploads.push({
8656
+ set: name,
8657
+ rel,
8658
+ absSrc: join15(hostSet.hostDir, rel),
8659
+ dst: `${hostSet.dst}/${rel}`
8660
+ });
8043
8661
  }
8044
- working = trustWorkspace(working, CLOUD_WORKSPACE).data;
8045
8662
  }
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 {
8663
+ for (const rel of Object.keys(boxFiles)) {
8664
+ if (!(rel in hostSet.files)) {
8665
+ deletions.push({ set: name, rel, dst: `${hostSet.dst}/${rel}` });
8059
8666
  }
8060
8667
  }
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;
8668
+ nextSets[name] = { dst: hostSet.dst, files: hostSet.files };
8132
8669
  }
8670
+ return { uploads, deletions, nextManifest: { version: 1, sets: nextSets } };
8133
8671
  }
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");
8672
+ async function stageDynamicSyncTarball(uploads) {
8673
+ if (uploads.length === 0) {
8674
+ return { tarballPath: null, cleanup: async () => {
8675
+ } };
8139
8676
  }
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");
8677
+ const stageDir = await mkdtemp4(join15(tmpdir4(), "agentbox-dynsync-stage-"));
8165
8678
  let tarballPath = null;
8166
8679
  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");
8680
+ for (const up of uploads) {
8681
+ const target = join15(stageDir, up.set, up.rel);
8682
+ await mkdir8(dirname32(target), { recursive: true });
8683
+ await copyFile2(up.absSrc, target);
8684
+ }
8685
+ tarballPath = join15(tmpdir4(), `agentbox-dynsync-${basename6(stageDir)}.tar.gz`);
8686
+ await execa19("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
8687
+ env: { ...process.env, COPYFILE_DISABLE: "1" }
8688
+ });
8689
+ const tp = tarballPath;
8191
8690
  return {
8192
- tarballPath,
8193
- cleanup: makeCleanup([stageDir, tarballPath]),
8194
- warnings: []
8691
+ tarballPath: tp,
8692
+ cleanup: async () => {
8693
+ await rm7(stageDir, { recursive: true, force: true });
8694
+ await rm7(tp, { force: true });
8695
+ }
8195
8696
  };
8196
8697
  } catch (err) {
8197
- await rm6(stageDir, { recursive: true, force: true });
8198
- if (tarballPath) await rm6(tarballPath, { force: true });
8698
+ await rm7(stageDir, { recursive: true, force: true });
8699
+ if (tarballPath) await rm7(tarballPath, { force: true });
8199
8700
  throw err;
8200
8701
  }
8201
8702
  }
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
8703
  function browserSessionActive(stdout, exitCode) {
8219
8704
  return exitCode === 0 && !/no active sessions/i.test(stdout);
8220
8705
  }
@@ -8249,6 +8734,9 @@ export {
8249
8734
  loadEffectiveConfig,
8250
8735
  resolveDefaultCheckpoint,
8251
8736
  defaultCheckpointConfigKey,
8737
+ resolveBoxSize,
8738
+ resolveBoxImage,
8739
+ boxImageConfigKey,
8252
8740
  setConfigValue,
8253
8741
  unsetConfigValue,
8254
8742
  listProjectsConfigured,
@@ -8299,6 +8787,30 @@ export {
8299
8787
  pullToHost,
8300
8788
  openInFinder,
8301
8789
  ExportError,
8790
+ carrySourceHash,
8791
+ copyCarryPathsToBox,
8792
+ CREDENTIALS_BACKUP_FILE,
8793
+ CODEX_CREDENTIALS_BACKUP_FILE,
8794
+ OPENCODE_CREDENTIALS_BACKUP_FILE,
8795
+ isRealAgentCredential,
8796
+ hostClaudeBackupExpired,
8797
+ parseExtractResult,
8798
+ extractVolumeAuthToBackup,
8799
+ extractCodexCredentials,
8800
+ extractOpencodeCredentials,
8801
+ hostBackupHasCredentials,
8802
+ parseSyncResult,
8803
+ syncClaudeCredentials,
8804
+ encodeClaudeProjectsKey,
8805
+ BOX_CLAUDE_PROJECT_DIR,
8806
+ resolveClaudeMemoryDir,
8807
+ stageClaudeStaticForUpload,
8808
+ stageClaudeCredentialsForUpload,
8809
+ stageCodexStaticForUpload,
8810
+ stageCodexCredentialsForUpload,
8811
+ stageOpencodeStaticForUpload,
8812
+ stageOpencodeCredentialsForUpload,
8813
+ stageOpencodeStateForUpload,
8302
8814
  SHARED_CLAUDE_VOLUME,
8303
8815
  DEFAULT_CLAUDE_SESSION,
8304
8816
  CONTAINER_USER,
@@ -8324,18 +8836,6 @@ export {
8324
8836
  attachClaudeSession,
8325
8837
  claudeSessionInfo,
8326
8838
  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
8839
  SHARED_CODEX_VOLUME,
8340
8840
  DEFAULT_CODEX_SESSION,
8341
8841
  resolveCodexVolume,
@@ -8402,6 +8902,7 @@ export {
8402
8902
  checkpointImageTag,
8403
8903
  projectCheckpointsDir,
8404
8904
  listCheckpoints,
8905
+ listAllCheckpoints,
8405
8906
  resolveCheckpoint,
8406
8907
  listAllCheckpointImages,
8407
8908
  removeCheckpoint,
@@ -8452,6 +8953,7 @@ export {
8452
8953
  pauseBox,
8453
8954
  unpauseBox,
8454
8955
  stopBox,
8956
+ resyncBox,
8455
8957
  startBox,
8456
8958
  openBoxInFinder,
8457
8959
  getBoxHostPaths,
@@ -8468,14 +8970,13 @@ export {
8468
8970
  uploadToBox,
8469
8971
  downloadFromBox,
8470
8972
  dockerProvider,
8471
- stageClaudeStaticForUpload,
8472
- stageClaudeCredentialsForUpload,
8473
- stageCodexStaticForUpload,
8474
- stageCodexCredentialsForUpload,
8475
- stageOpencodeStaticForUpload,
8476
- stageOpencodeCredentialsForUpload,
8477
- stageOpencodeStateForUpload,
8973
+ BOX_WORKFLOWS_DIR,
8974
+ BOX_DYNAMIC_SYNC_MANIFEST,
8975
+ BOX_MEMORY_DIR,
8976
+ buildHostSyncManifest,
8977
+ computeSyncDelta,
8978
+ stageDynamicSyncTarball,
8478
8979
  browserSessionActive,
8479
8980
  ensureBoxBrowser
8480
8981
  };
8481
- //# sourceMappingURL=chunk-ZJXTIH6C.js.map
8982
+ //# sourceMappingURL=chunk-B4QG2MCW.js.map