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