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