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