@madarco/agentbox 0.7.0 → 0.9.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/dist/_cloud-attach-ZXBCNWJX.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-BXQMIEHC.js} +459 -110
- package/dist/chunk-BXQMIEHC.js.map +1 -0
- package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
- package/dist/chunk-G3H2L3O2.js.map +1 -0
- package/dist/{chunk-7KOEFGN2.js → chunk-GU5LW4B5.js} +385 -31
- package/dist/chunk-GU5LW4B5.js.map +1 -0
- package/dist/chunk-KL36BRN4.js +455 -0
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-NCJP5MTN.js} +1281 -556
- package/dist/chunk-NCJP5MTN.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-ETCFRVPA.js → dist-32EZBYG4.js} +50 -20
- package/dist/{dist-R67WMLCF.js → dist-CX5CGVEB.js} +120 -10
- package/dist/dist-CX5CGVEB.js.map +1 -0
- package/dist/{dist-QZGJIBT5.js → dist-GDHP34ZK.js} +141 -75
- package/dist/dist-GDHP34ZK.js.map +1 -0
- package/dist/dist-XML54CNB.js +849 -0
- package/dist/dist-XML54CNB.js.map +1 -0
- package/dist/index.js +3881 -867
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js +18 -0
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js.map +1 -0
- package/package.json +7 -5
- package/runtime/daytona/custom-system-CLAUDE.md +39 -0
- package/runtime/docker/Dockerfile.box +22 -0
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +1214 -98
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
- package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
- package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
- package/runtime/hetzner/agentbox-setup-skill.md +1 -1
- package/runtime/hetzner/agentbox-vnc-start +15 -1
- package/runtime/hetzner/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1214 -98
- package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
- package/runtime/hetzner/gh-shim +263 -0
- package/runtime/hetzner/git-shim +131 -0
- package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/scripts/install-box.sh +11 -2
- package/runtime/relay/bin.cjs +1146 -63
- package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
- package/runtime/vercel/agentbox-codex-hooks.json +68 -0
- package/runtime/vercel/agentbox-open +28 -0
- package/runtime/vercel/agentbox-setup-skill.md +196 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23466 -0
- package/runtime/vercel/custom-system-CLAUDE.md +50 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +274 -0
- package/share/agentbox-setup/SKILL.md +1 -1
- package/share/host-skills/agentbox/SKILL.md +29 -0
- package/share/host-skills/agentbox-info/SKILL.md +211 -0
- package/share/host-skills/codex/agentbox.md +35 -0
- package/share/host-skills/opencode/agentbox.md +26 -0
- package/dist/_cloud-attach-DMVH6GWO.js +0 -12
- package/dist/chunk-7KOEFGN2.js.map +0 -1
- package/dist/chunk-NAVL4R34.js.map +0 -1
- package/dist/chunk-NW5NYTQM.js.map +0 -1
- package/dist/chunk-UK72UQ5U.js.map +0 -1
- package/dist/chunk-V5KZGB5V.js.map +0 -1
- package/dist/dist-QZGJIBT5.js.map +0 -1
- package/dist/dist-R67WMLCF.js.map +0 -1
- /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-ZXBCNWJX.js.map} +0 -0
- /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
- /package/dist/{dist-ETCFRVPA.js.map → dist-32EZBYG4.js.map} +0 -0
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_BOX_IMAGE,
|
|
4
|
+
GitWorktreeError,
|
|
5
|
+
STATE_DIR,
|
|
6
|
+
STATE_FILE,
|
|
7
|
+
allocateProjectIndex,
|
|
8
|
+
buildImage,
|
|
9
|
+
computeDockerContextFingerprint,
|
|
10
|
+
detectGitRepos,
|
|
11
|
+
ensureImage,
|
|
12
|
+
findBox,
|
|
13
|
+
imageExists,
|
|
14
|
+
pickFreshBranch,
|
|
15
|
+
preparedMatches,
|
|
16
|
+
readCliStamp,
|
|
17
|
+
readPreparedDockerState,
|
|
18
|
+
readState,
|
|
19
|
+
recordBox,
|
|
20
|
+
removeBoxRecord,
|
|
21
|
+
writePreparedDockerState
|
|
22
|
+
} from "./chunk-KL36BRN4.js";
|
|
2
23
|
|
|
3
24
|
// ../../packages/sandbox-docker/dist/index.js
|
|
4
25
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
5
26
|
import { mkdir as mkdir7, stat as stat6 } from "fs/promises";
|
|
6
27
|
import { homedir as homedir9 } from "os";
|
|
7
|
-
import { basename as basename2, join as join10, resolve as
|
|
8
|
-
import { execa as
|
|
28
|
+
import { basename as basename2, join as join10, resolve as resolve3 } from "path";
|
|
29
|
+
import { execa as execa13 } from "execa";
|
|
9
30
|
|
|
10
31
|
// ../../packages/ctl/dist/index.js
|
|
11
32
|
import { readFile } from "fs/promises";
|
|
12
33
|
import { parse as parseYaml } from "yaml";
|
|
34
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
35
|
+
import { parse as parseYaml2 } from "yaml";
|
|
36
|
+
var BOX_STATUS_EVENT = "box-status";
|
|
13
37
|
function renderStatusTable(rows) {
|
|
14
38
|
if (rows.length === 0) return "(no services configured)";
|
|
15
39
|
const headers = ["NAME", "STATE", "PID", "RESTARTS", "LAST EXIT", "BLOCKED ON", "COMMAND"];
|
|
@@ -372,7 +396,7 @@ function assertBool(raw, where) {
|
|
|
372
396
|
if (typeof raw !== "boolean") throw new ConfigError(`${where} must be a boolean`);
|
|
373
397
|
return raw;
|
|
374
398
|
}
|
|
375
|
-
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["services", "tasks", "ide", "defaults"]);
|
|
399
|
+
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["services", "tasks", "ide", "defaults", "carry"]);
|
|
376
400
|
function validateUnitGraph(tasks, services) {
|
|
377
401
|
const names = /* @__PURE__ */ new Set();
|
|
378
402
|
for (const t of tasks) {
|
|
@@ -488,27 +512,217 @@ async function loadConfig(path) {
|
|
|
488
512
|
}
|
|
489
513
|
return parseConfig(text);
|
|
490
514
|
}
|
|
515
|
+
var CarryConfigError = class extends Error {
|
|
516
|
+
constructor(message) {
|
|
517
|
+
super(message);
|
|
518
|
+
this.name = "CarryConfigError";
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
var ITEM_KEYS = /* @__PURE__ */ new Set(["src", "dest", "mode", "user", "optional"]);
|
|
522
|
+
function parseUser(raw, where) {
|
|
523
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
524
|
+
let n;
|
|
525
|
+
if (typeof raw === "number") {
|
|
526
|
+
if (!Number.isInteger(raw) || raw < 0) {
|
|
527
|
+
throw new CarryConfigError(`${where}.user must be a non-negative integer uid (got ${String(raw)})`);
|
|
528
|
+
}
|
|
529
|
+
n = raw;
|
|
530
|
+
} else if (typeof raw === "string") {
|
|
531
|
+
const trimmed = raw.trim();
|
|
532
|
+
if (!/^[0-9]+$/.test(trimmed)) {
|
|
533
|
+
throw new CarryConfigError(
|
|
534
|
+
`${where}.user "${raw}" must be a numeric uid (e.g. 1000). Usernames not supported \u2014 look up the uid first.`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
n = parseInt(trimmed, 10);
|
|
538
|
+
} else {
|
|
539
|
+
throw new CarryConfigError(`${where}.user must be a non-negative integer uid`);
|
|
540
|
+
}
|
|
541
|
+
if (n > 65535) {
|
|
542
|
+
throw new CarryConfigError(`${where}.user must be between 0 and 65535 (got ${String(n)})`);
|
|
543
|
+
}
|
|
544
|
+
return n;
|
|
545
|
+
}
|
|
546
|
+
function isPlainObject2(v) {
|
|
547
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
548
|
+
}
|
|
549
|
+
function assertSrcShape(src, where) {
|
|
550
|
+
if (src.length === 0) {
|
|
551
|
+
throw new CarryConfigError(`${where}.src must not be empty`);
|
|
552
|
+
}
|
|
553
|
+
if (!src.startsWith("/") && !src.startsWith("~/") && !src.startsWith("./")) {
|
|
554
|
+
throw new CarryConfigError(
|
|
555
|
+
`${where}.src "${src}" must start with /, ~/, or ./ (bare relative paths are rejected to avoid surprises)`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function assertDestShape(dest, where) {
|
|
560
|
+
if (dest.length === 0) {
|
|
561
|
+
throw new CarryConfigError(`${where}.dest must not be empty`);
|
|
562
|
+
}
|
|
563
|
+
if (!dest.startsWith("/") && !dest.startsWith("~/")) {
|
|
564
|
+
throw new CarryConfigError(
|
|
565
|
+
`${where}.dest "${dest}" must start with / or ~/ (relative box-side paths are not allowed)`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function parseMode(raw, where) {
|
|
570
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
571
|
+
let n;
|
|
572
|
+
if (typeof raw === "number") {
|
|
573
|
+
if (!Number.isInteger(raw) || raw < 0) {
|
|
574
|
+
throw new CarryConfigError(`${where}.mode must be a non-negative integer (got ${String(raw)})`);
|
|
575
|
+
}
|
|
576
|
+
n = raw;
|
|
577
|
+
} else if (typeof raw === "string") {
|
|
578
|
+
const trimmed = raw.trim();
|
|
579
|
+
if (trimmed.length === 0) {
|
|
580
|
+
throw new CarryConfigError(`${where}.mode must not be empty`);
|
|
581
|
+
}
|
|
582
|
+
const cleaned = trimmed.startsWith("0o") || trimmed.startsWith("0O") ? trimmed.slice(2) : trimmed;
|
|
583
|
+
if (!/^[0-7]+$/.test(cleaned)) {
|
|
584
|
+
throw new CarryConfigError(
|
|
585
|
+
`${where}.mode "${raw}" must be an octal number (e.g. 0o600, "0600", "600")`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
n = parseInt(cleaned, 8);
|
|
589
|
+
} else {
|
|
590
|
+
throw new CarryConfigError(`${where}.mode must be a number or octal string`);
|
|
591
|
+
}
|
|
592
|
+
if (n < 0 || n > 4095) {
|
|
593
|
+
throw new CarryConfigError(`${where}.mode must be between 0 and 0o7777 (got ${n.toString(8)})`);
|
|
594
|
+
}
|
|
595
|
+
return n;
|
|
596
|
+
}
|
|
597
|
+
function parseShorthand(raw, where) {
|
|
598
|
+
const eq = raw.indexOf("=");
|
|
599
|
+
let src;
|
|
600
|
+
let dest;
|
|
601
|
+
if (eq === -1) {
|
|
602
|
+
src = raw;
|
|
603
|
+
dest = raw;
|
|
604
|
+
} else {
|
|
605
|
+
src = raw.slice(0, eq);
|
|
606
|
+
dest = raw.slice(eq + 1);
|
|
607
|
+
}
|
|
608
|
+
src = src.trim();
|
|
609
|
+
dest = dest.trim();
|
|
610
|
+
assertSrcShape(src, where);
|
|
611
|
+
if (eq === -1) {
|
|
612
|
+
if (src.startsWith("./")) {
|
|
613
|
+
throw new CarryConfigError(
|
|
614
|
+
`${where} shorthand "${raw}" must specify an explicit dest (use "src=dest") when src starts with ./`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
assertDestShape(dest, where);
|
|
619
|
+
return { src, dest, optional: false };
|
|
620
|
+
}
|
|
621
|
+
function parseMapping(raw, where) {
|
|
622
|
+
for (const key of Object.keys(raw)) {
|
|
623
|
+
if (!ITEM_KEYS.has(key)) {
|
|
624
|
+
throw new CarryConfigError(`${where} has unknown key "${key}"`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const srcRaw = raw.src;
|
|
628
|
+
if (typeof srcRaw !== "string") {
|
|
629
|
+
throw new CarryConfigError(`${where}.src must be a string`);
|
|
630
|
+
}
|
|
631
|
+
const src = srcRaw.trim();
|
|
632
|
+
assertSrcShape(src, where);
|
|
633
|
+
let dest;
|
|
634
|
+
if (raw.dest === void 0 || raw.dest === null) {
|
|
635
|
+
if (src.startsWith("./")) {
|
|
636
|
+
throw new CarryConfigError(
|
|
637
|
+
`${where}.dest is required when src starts with ./ (no sensible in-box default)`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
dest = src;
|
|
641
|
+
} else {
|
|
642
|
+
if (typeof raw.dest !== "string") {
|
|
643
|
+
throw new CarryConfigError(`${where}.dest must be a string`);
|
|
644
|
+
}
|
|
645
|
+
dest = raw.dest.trim();
|
|
646
|
+
}
|
|
647
|
+
assertDestShape(dest, where);
|
|
648
|
+
const mode = parseMode(raw.mode, where);
|
|
649
|
+
const user = parseUser(raw.user, where);
|
|
650
|
+
let optional = false;
|
|
651
|
+
if (raw.optional !== void 0 && raw.optional !== null) {
|
|
652
|
+
if (typeof raw.optional !== "boolean") {
|
|
653
|
+
throw new CarryConfigError(`${where}.optional must be a boolean`);
|
|
654
|
+
}
|
|
655
|
+
optional = raw.optional;
|
|
656
|
+
}
|
|
657
|
+
const out = { src, dest, optional };
|
|
658
|
+
if (mode !== void 0) out.mode = mode;
|
|
659
|
+
if (user !== void 0) out.user = user;
|
|
660
|
+
return out;
|
|
661
|
+
}
|
|
662
|
+
function parseCarryRaw(raw) {
|
|
663
|
+
if (raw === void 0 || raw === null) return [];
|
|
664
|
+
if (!Array.isArray(raw)) {
|
|
665
|
+
throw new CarryConfigError("carry must be a list of strings or mappings");
|
|
666
|
+
}
|
|
667
|
+
const out = [];
|
|
668
|
+
for (const [i, item] of raw.entries()) {
|
|
669
|
+
const where = `carry[${String(i)}]`;
|
|
670
|
+
if (typeof item === "string") {
|
|
671
|
+
out.push(parseShorthand(item, where));
|
|
672
|
+
} else if (isPlainObject2(item)) {
|
|
673
|
+
out.push(parseMapping(item, where));
|
|
674
|
+
} else {
|
|
675
|
+
throw new CarryConfigError(`${where} must be a string or mapping`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return out;
|
|
679
|
+
}
|
|
680
|
+
function parseCarrySection(text) {
|
|
681
|
+
let doc;
|
|
682
|
+
try {
|
|
683
|
+
doc = parseYaml2(text);
|
|
684
|
+
} catch (err) {
|
|
685
|
+
throw new CarryConfigError(
|
|
686
|
+
`yaml parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
if (doc === null || doc === void 0) return [];
|
|
690
|
+
if (!isPlainObject2(doc)) {
|
|
691
|
+
throw new CarryConfigError("top-level config must be a mapping");
|
|
692
|
+
}
|
|
693
|
+
return parseCarryRaw(doc.carry);
|
|
694
|
+
}
|
|
695
|
+
async function loadCarrySection(path) {
|
|
696
|
+
let text;
|
|
697
|
+
try {
|
|
698
|
+
text = await readFile2(path, "utf8");
|
|
699
|
+
} catch (err) {
|
|
700
|
+
if (err.code === "ENOENT") return [];
|
|
701
|
+
throw err;
|
|
702
|
+
}
|
|
703
|
+
return parseCarrySection(text);
|
|
704
|
+
}
|
|
491
705
|
|
|
492
706
|
// ../../packages/sandbox-docker/dist/index.js
|
|
493
707
|
import { spawnSync } from "child_process";
|
|
494
|
-
import { mkdir as mkdir22, mkdtemp, readdir as readdir3, readFile as
|
|
495
|
-
import { homedir as
|
|
496
|
-
import { join as
|
|
708
|
+
import { mkdir as mkdir22, mkdtemp, readdir as readdir3, readFile as readFile24, rm as rm2, stat as stat3, writeFile as writeFile3 } from "fs/promises";
|
|
709
|
+
import { homedir as homedir2, tmpdir } from "os";
|
|
710
|
+
import { join as join22, relative } from "path";
|
|
497
711
|
import { setTimeout as delay } from "timers/promises";
|
|
498
712
|
import { execa as execa3 } from "execa";
|
|
499
|
-
import { execa as
|
|
713
|
+
import { execa as execa2 } from "execa";
|
|
500
714
|
import { mkdir as mkdir3, readFile as readFile4 } from "fs/promises";
|
|
501
715
|
import { homedir as homedir3 } from "os";
|
|
502
716
|
import { join as join4 } from "path";
|
|
503
717
|
import { execa as execa22 } from "execa";
|
|
504
718
|
|
|
505
719
|
// ../../packages/config/dist/index.js
|
|
506
|
-
import { parse as
|
|
720
|
+
import { parse as parseYaml3 } from "yaml";
|
|
507
721
|
import { createHash } from "crypto";
|
|
508
722
|
import { realpath, stat } from "fs/promises";
|
|
509
723
|
import { homedir } from "os";
|
|
510
724
|
import { basename, dirname, join, resolve } from "path";
|
|
511
|
-
import { readFile as
|
|
725
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
512
726
|
import { parse as parseYaml22 } from "yaml";
|
|
513
727
|
import { mkdir, readFile as readFile22, rename, rm, stat as stat2, writeFile } from "fs/promises";
|
|
514
728
|
import { dirname as dirname2, isAbsolute, join as join2 } from "path";
|
|
@@ -522,6 +736,7 @@ var BUILT_IN_DEFAULTS = {
|
|
|
522
736
|
defaultCheckpointDocker: "",
|
|
523
737
|
defaultCheckpointDaytona: "",
|
|
524
738
|
defaultCheckpointHetzner: "",
|
|
739
|
+
defaultCheckpointVercel: "",
|
|
525
740
|
withPlaywright: false,
|
|
526
741
|
withEnv: false,
|
|
527
742
|
vnc: true,
|
|
@@ -533,7 +748,11 @@ var BUILT_IN_DEFAULTS = {
|
|
|
533
748
|
memory: 0,
|
|
534
749
|
cpus: 0,
|
|
535
750
|
pidsLimit: 0,
|
|
536
|
-
disk: ""
|
|
751
|
+
disk: "",
|
|
752
|
+
bundleDepth: void 0,
|
|
753
|
+
vercelVcpus: 2,
|
|
754
|
+
vercelTimeoutMs: 27e5,
|
|
755
|
+
vercelNetworkPolicy: ""
|
|
537
756
|
},
|
|
538
757
|
checkpoint: {
|
|
539
758
|
maxLayers: 3
|
|
@@ -582,6 +801,15 @@ var BUILT_IN_DEFAULTS = {
|
|
|
582
801
|
maxRunningBoxes: 5,
|
|
583
802
|
idleMinutes: 5
|
|
584
803
|
},
|
|
804
|
+
queue: {
|
|
805
|
+
enabled: true,
|
|
806
|
+
maxConcurrent: 5,
|
|
807
|
+
maxWorking: 0,
|
|
808
|
+
idleGraceSeconds: 15
|
|
809
|
+
},
|
|
810
|
+
cloud: {
|
|
811
|
+
useCurrentBranch: false
|
|
812
|
+
},
|
|
585
813
|
maintenance: {
|
|
586
814
|
pruneProjectConfigs: true,
|
|
587
815
|
pruneProjectConfigsEvery: 50
|
|
@@ -591,8 +819,8 @@ var KEY_REGISTRY = [
|
|
|
591
819
|
{
|
|
592
820
|
key: "box.provider",
|
|
593
821
|
type: "enum",
|
|
594
|
-
enumValues: ["docker", "daytona", "hetzner"],
|
|
595
|
-
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes,
|
|
822
|
+
enumValues: ["docker", "daytona", "hetzner", "vercel"],
|
|
823
|
+
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, or Vercel Sandboxes."
|
|
596
824
|
},
|
|
597
825
|
{
|
|
598
826
|
key: "box.hostSnapshot",
|
|
@@ -622,6 +850,12 @@ var KEY_REGISTRY = [
|
|
|
622
850
|
description: "Per-provider override of `box.defaultCheckpoint` for hetzner. Wins over the global when set; set via `agentbox checkpoint set-default --provider hetzner`.",
|
|
623
851
|
advanced: true
|
|
624
852
|
},
|
|
853
|
+
{
|
|
854
|
+
key: "box.defaultCheckpointVercel",
|
|
855
|
+
type: "string",
|
|
856
|
+
description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
|
|
857
|
+
advanced: true
|
|
858
|
+
},
|
|
625
859
|
{
|
|
626
860
|
key: "checkpoint.maxLayers",
|
|
627
861
|
type: "int",
|
|
@@ -690,6 +924,26 @@ var KEY_REGISTRY = [
|
|
|
690
924
|
description: "Best-effort writable-layer size for new boxes, e.g. '10G'. No-op on overlay2 / the macOS engines.",
|
|
691
925
|
advanced: true
|
|
692
926
|
},
|
|
927
|
+
{
|
|
928
|
+
key: "box.bundleDepth",
|
|
929
|
+
type: "int",
|
|
930
|
+
description: "Cap git bundle history shipped to cloud sandboxes (daytona, hetzner). 0 = full history. Unset = adaptive default (last 200 commits; re-bundle at 100 if the bundle exceeds 20 MB). Ignored for docker (which bind-mounts .git/)."
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
key: "box.vercelVcpus",
|
|
934
|
+
type: "int",
|
|
935
|
+
description: "vCPUs for new --provider vercel boxes (Vercel couples RAM at 2048 MB/vCPU). Default 2. Vercel only accepts specific counts (e.g. 1, 2, 4, 8) \u2014 an unsupported value fails create with a 400. Vercel-only; ignored by other providers."
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
key: "box.vercelTimeoutMs",
|
|
939
|
+
type: "int",
|
|
940
|
+
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."
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
key: "box.vercelNetworkPolicy",
|
|
944
|
+
type: "string",
|
|
945
|
+
description: "Egress lock for new --provider vercel boxes: 'allow-all' (default, unset), 'deny-all', or a comma-separated domain allowlist (e.g. 'github.com,*.npmjs.org') that denies everything else. Vercel-only; ignored by other providers."
|
|
946
|
+
},
|
|
693
947
|
{
|
|
694
948
|
key: "claude.sessionName",
|
|
695
949
|
type: "string",
|
|
@@ -797,6 +1051,31 @@ var KEY_REGISTRY = [
|
|
|
797
1051
|
type: "int",
|
|
798
1052
|
description: "Minutes a box must be continuously idle (claude state) before it is eligible for auto-pause."
|
|
799
1053
|
},
|
|
1054
|
+
{
|
|
1055
|
+
key: "queue.enabled",
|
|
1056
|
+
type: "bool",
|
|
1057
|
+
description: "Run `agentbox claude|codex|opencode -i <prompt>` jobs through the host-wide background queue (FIFO, capped by queue.maxConcurrent)."
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
key: "queue.maxConcurrent",
|
|
1061
|
+
type: "int",
|
|
1062
|
+
description: "Max number of simultaneously-running boxes (across providers) before background `-i` jobs queue up instead of starting immediately. Per-invocation override: `--max-running <n>`."
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
key: "queue.maxWorking",
|
|
1066
|
+
type: "int",
|
|
1067
|
+
description: "Max agents actively working/thinking (quota-consuming) at once before background `-i` jobs queue. 0 = disabled (use the queue.maxConcurrent running-box gate). Counts all boxes, foreground + queued. Per-invocation override: `--max-working <n>`."
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
key: "queue.idleGraceSeconds",
|
|
1071
|
+
type: "int",
|
|
1072
|
+
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."
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
key: "cloud.useCurrentBranch",
|
|
1076
|
+
type: "bool",
|
|
1077
|
+
description: "On cloud providers (daytona/hetzner), start new boxes on the host's current branch instead of forking a new agentbox/<box-name> branch. Overridden by an explicit --use-branch / --from-branch."
|
|
1078
|
+
},
|
|
800
1079
|
{
|
|
801
1080
|
key: "maintenance.pruneProjectConfigs",
|
|
802
1081
|
type: "bool",
|
|
@@ -818,7 +1097,7 @@ var UserConfigError = class extends Error {
|
|
|
818
1097
|
this.name = "UserConfigError";
|
|
819
1098
|
}
|
|
820
1099
|
};
|
|
821
|
-
function
|
|
1100
|
+
function isPlainObject3(v) {
|
|
822
1101
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
823
1102
|
}
|
|
824
1103
|
var RENAMED_KEYS = /* @__PURE__ */ new Map([["box.snapshot", "box.hostSnapshot"]]);
|
|
@@ -875,25 +1154,34 @@ function coerceTypedValue(raw, desc, where) {
|
|
|
875
1154
|
function parseUserConfig(text, where) {
|
|
876
1155
|
let doc;
|
|
877
1156
|
try {
|
|
878
|
-
doc =
|
|
1157
|
+
doc = parseYaml3(text);
|
|
879
1158
|
} catch (err) {
|
|
880
1159
|
throw new UserConfigError(
|
|
881
1160
|
`${where}: yaml parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
882
1161
|
);
|
|
883
1162
|
}
|
|
884
1163
|
if (doc === null || doc === void 0) return {};
|
|
885
|
-
if (!
|
|
1164
|
+
if (!isPlainObject3(doc)) {
|
|
886
1165
|
throw new UserConfigError(`${where}: top-level must be a mapping`);
|
|
887
1166
|
}
|
|
888
1167
|
return parseUserConfigObject(doc, where);
|
|
889
1168
|
}
|
|
890
1169
|
function parseUserConfigObject(doc, where) {
|
|
891
1170
|
if (doc === null || doc === void 0) return {};
|
|
892
|
-
if (!
|
|
1171
|
+
if (!isPlainObject3(doc)) {
|
|
893
1172
|
throw new UserConfigError(`${where}: must be a mapping`);
|
|
894
1173
|
}
|
|
895
1174
|
const out = {};
|
|
896
1175
|
for (const [branchName, branchRaw] of Object.entries(doc)) {
|
|
1176
|
+
if (branchName === "schema") {
|
|
1177
|
+
if (branchRaw !== void 0 && branchRaw !== null) {
|
|
1178
|
+
if (typeof branchRaw !== "number" || !Number.isInteger(branchRaw)) {
|
|
1179
|
+
throw new UserConfigError(`${where}.schema: must be an integer (got ${String(branchRaw)})`);
|
|
1180
|
+
}
|
|
1181
|
+
out.schema = branchRaw;
|
|
1182
|
+
}
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
897
1185
|
const branchSpec = BRANCHES.get(branchName);
|
|
898
1186
|
if (!branchSpec) {
|
|
899
1187
|
throw new UserConfigError(
|
|
@@ -901,7 +1189,7 @@ function parseUserConfigObject(doc, where) {
|
|
|
901
1189
|
);
|
|
902
1190
|
}
|
|
903
1191
|
if (branchRaw === null || branchRaw === void 0) continue;
|
|
904
|
-
if (!
|
|
1192
|
+
if (!isPlainObject3(branchRaw)) {
|
|
905
1193
|
throw new UserConfigError(`${where}.${branchName}: must be a mapping`);
|
|
906
1194
|
}
|
|
907
1195
|
const branchOut = {};
|
|
@@ -978,9 +1266,9 @@ function lookupKeyOrThrow(key) {
|
|
|
978
1266
|
}
|
|
979
1267
|
return desc;
|
|
980
1268
|
}
|
|
981
|
-
var
|
|
982
|
-
var GLOBAL_CONFIG_FILE = join(
|
|
983
|
-
var PROJECTS_DIR = join(
|
|
1269
|
+
var STATE_DIR2 = join(homedir(), ".agentbox");
|
|
1270
|
+
var GLOBAL_CONFIG_FILE = join(STATE_DIR2, "config.yaml");
|
|
1271
|
+
var PROJECTS_DIR = join(STATE_DIR2, "projects");
|
|
984
1272
|
var WORKSPACE_CONFIG_BASENAME = "agentbox.yaml";
|
|
985
1273
|
async function findProjectRoot(cwd) {
|
|
986
1274
|
const start = await canonicalize(cwd);
|
|
@@ -1042,7 +1330,7 @@ async function configPathFor(scope, cwd) {
|
|
|
1042
1330
|
async function loadOptionalUserConfig(path) {
|
|
1043
1331
|
let text;
|
|
1044
1332
|
try {
|
|
1045
|
-
text = await
|
|
1333
|
+
text = await readFile3(path, "utf8");
|
|
1046
1334
|
} catch (err) {
|
|
1047
1335
|
if (err.code === "ENOENT") return {};
|
|
1048
1336
|
throw err;
|
|
@@ -1053,7 +1341,7 @@ async function loadProjectAgentboxDefaults(workspacePath) {
|
|
|
1053
1341
|
const path = workspaceConfigFile(workspacePath);
|
|
1054
1342
|
let text;
|
|
1055
1343
|
try {
|
|
1056
|
-
text = await
|
|
1344
|
+
text = await readFile3(path, "utf8");
|
|
1057
1345
|
} catch (err) {
|
|
1058
1346
|
if (err.code === "ENOENT") return {};
|
|
1059
1347
|
throw err;
|
|
@@ -1143,7 +1431,7 @@ function writeLeaf(obj, branch, leaf, value) {
|
|
|
1143
1431
|
b[leaf] = value;
|
|
1144
1432
|
}
|
|
1145
1433
|
function resolveDefaultCheckpoint(cfg, provider) {
|
|
1146
|
-
const perProvider = provider === "daytona" ? cfg.box.defaultCheckpointDaytona : provider === "hetzner" ? cfg.box.defaultCheckpointHetzner : cfg.box.defaultCheckpointDocker;
|
|
1434
|
+
const perProvider = provider === "daytona" ? cfg.box.defaultCheckpointDaytona : provider === "hetzner" ? cfg.box.defaultCheckpointHetzner : provider === "vercel" ? cfg.box.defaultCheckpointVercel : cfg.box.defaultCheckpointDocker;
|
|
1147
1435
|
if (perProvider && perProvider.length > 0) return perProvider;
|
|
1148
1436
|
return cfg.box.defaultCheckpoint;
|
|
1149
1437
|
}
|
|
@@ -1151,6 +1439,7 @@ function defaultCheckpointConfigKey(provider) {
|
|
|
1151
1439
|
if (provider === "docker") return "box.defaultCheckpointDocker";
|
|
1152
1440
|
if (provider === "daytona") return "box.defaultCheckpointDaytona";
|
|
1153
1441
|
if (provider === "hetzner") return "box.defaultCheckpointHetzner";
|
|
1442
|
+
if (provider === "vercel") return "box.defaultCheckpointVercel";
|
|
1154
1443
|
return "box.defaultCheckpoint";
|
|
1155
1444
|
}
|
|
1156
1445
|
async function setConfigValue(scope, key, value, cwd, opts = {}) {
|
|
@@ -1161,6 +1450,7 @@ async function setConfigValue(scope, key, value, cwd, opts = {}) {
|
|
|
1161
1450
|
const path = await configPathFor(scope, cwd);
|
|
1162
1451
|
const current = await readExistingDoc(path);
|
|
1163
1452
|
setLeaf(current, key, coerced);
|
|
1453
|
+
stampSchema(current);
|
|
1164
1454
|
parseUserConfig(stringifyYaml(current), path);
|
|
1165
1455
|
await atomicWriteYaml(path, current);
|
|
1166
1456
|
if (scope === "project") {
|
|
@@ -1177,6 +1467,7 @@ async function unsetConfigValue(scope, key, cwd) {
|
|
|
1177
1467
|
const current = await readExistingDoc(path);
|
|
1178
1468
|
const existed = unsetLeaf(current, key);
|
|
1179
1469
|
if (!existed) return { path, existed: false };
|
|
1470
|
+
stampSchema(current);
|
|
1180
1471
|
await atomicWriteYaml(path, current);
|
|
1181
1472
|
if (scope === "project") {
|
|
1182
1473
|
const root = (await findProjectRoot(cwd)).root;
|
|
@@ -1287,6 +1578,12 @@ async function readExistingDoc(path) {
|
|
|
1287
1578
|
}
|
|
1288
1579
|
return parseUserConfig(text, path);
|
|
1289
1580
|
}
|
|
1581
|
+
var CURRENT_CONFIG_SCHEMA = 1;
|
|
1582
|
+
function stampSchema(doc) {
|
|
1583
|
+
if (typeof doc.schema !== "number") {
|
|
1584
|
+
doc.schema = CURRENT_CONFIG_SCHEMA;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1290
1587
|
function setLeaf(doc, key, value) {
|
|
1291
1588
|
const idx = key.indexOf(".");
|
|
1292
1589
|
const branch = key.slice(0, idx);
|
|
@@ -1341,181 +1638,7 @@ async function touchProjectMeta(absPath) {
|
|
|
1341
1638
|
// ../../packages/sandbox-docker/dist/index.js
|
|
1342
1639
|
import { chmod, mkdir as mkdir32, readFile as readFile32 } from "fs/promises";
|
|
1343
1640
|
import { join as join32 } from "path";
|
|
1344
|
-
import { execa as
|
|
1345
|
-
|
|
1346
|
-
// ../../packages/sandbox-core/dist/index.js
|
|
1347
|
-
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
1348
|
-
import { homedir as homedir2 } from "os";
|
|
1349
|
-
import { dirname as dirname3, join as join3 } from "path";
|
|
1350
|
-
import { execa } from "execa";
|
|
1351
|
-
import { readdir as readdir2, stat as stat3 } from "fs/promises";
|
|
1352
|
-
import { join as join22 } from "path";
|
|
1353
|
-
var STATE_DIR2 = join3(homedir2(), ".agentbox");
|
|
1354
|
-
var STATE_FILE = join3(STATE_DIR2, "state.json");
|
|
1355
|
-
var EMPTY = { version: 1, boxes: [] };
|
|
1356
|
-
async function readState(path = STATE_FILE) {
|
|
1357
|
-
try {
|
|
1358
|
-
const raw = await readFile3(path, "utf8");
|
|
1359
|
-
const parsed = JSON.parse(raw);
|
|
1360
|
-
if (parsed.version !== 1 || !Array.isArray(parsed.boxes)) {
|
|
1361
|
-
throw new Error(`unrecognized state file shape at ${path}`);
|
|
1362
|
-
}
|
|
1363
|
-
for (const b of parsed.boxes) {
|
|
1364
|
-
b.provider ??= "docker";
|
|
1365
|
-
if ((b.provider ?? "docker") === "docker" && !b.docker) {
|
|
1366
|
-
b.docker = projectDockerFields(b);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
return parsed;
|
|
1370
|
-
} catch (err) {
|
|
1371
|
-
if (err.code === "ENOENT") {
|
|
1372
|
-
return { ...EMPTY };
|
|
1373
|
-
}
|
|
1374
|
-
throw err;
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
async function writeState(state, path = STATE_FILE) {
|
|
1378
|
-
await mkdir2(dirname3(path), { recursive: true });
|
|
1379
|
-
await writeFile2(path, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
1380
|
-
}
|
|
1381
|
-
async function recordBox(box, path = STATE_FILE) {
|
|
1382
|
-
const toWrite = (box.provider ?? "docker") === "docker" && !box.docker ? { ...box, docker: projectDockerFields(box) } : box;
|
|
1383
|
-
const state = await readState(path);
|
|
1384
|
-
const next = {
|
|
1385
|
-
version: 1,
|
|
1386
|
-
boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite]
|
|
1387
|
-
};
|
|
1388
|
-
await writeState(next, path);
|
|
1389
|
-
}
|
|
1390
|
-
function projectDockerFields(box) {
|
|
1391
|
-
return {
|
|
1392
|
-
container: box.container,
|
|
1393
|
-
image: box.image,
|
|
1394
|
-
snapshotDir: box.snapshotDir ?? null,
|
|
1395
|
-
socketPath: box.socketPath,
|
|
1396
|
-
claudeConfigVolume: box.claudeConfigVolume,
|
|
1397
|
-
codexConfigVolume: box.codexConfigVolume,
|
|
1398
|
-
opencodeConfigVolume: box.opencodeConfigVolume,
|
|
1399
|
-
vscodeServerVolume: box.vscodeServerVolume,
|
|
1400
|
-
cursorServerVolume: box.cursorServerVolume,
|
|
1401
|
-
vncHostPort: box.vncHostPort,
|
|
1402
|
-
webHostPort: box.webHostPort,
|
|
1403
|
-
portlessAlias: box.portlessAlias,
|
|
1404
|
-
portlessUrl: box.portlessUrl,
|
|
1405
|
-
dockerVolume: box.dockerVolume,
|
|
1406
|
-
dockerCacheShared: box.dockerCacheShared,
|
|
1407
|
-
checkpointImage: box.checkpointImage
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
|
-
async function removeBoxRecord(id, path = STATE_FILE) {
|
|
1411
|
-
const state = await readState(path);
|
|
1412
|
-
const before = state.boxes.length;
|
|
1413
|
-
const next = {
|
|
1414
|
-
version: 1,
|
|
1415
|
-
boxes: state.boxes.filter((b) => b.id !== id)
|
|
1416
|
-
};
|
|
1417
|
-
if (next.boxes.length === before) return false;
|
|
1418
|
-
await writeState(next, path);
|
|
1419
|
-
return true;
|
|
1420
|
-
}
|
|
1421
|
-
function findBox(idOrName, state) {
|
|
1422
|
-
const q = idOrName.trim();
|
|
1423
|
-
if (q.length === 0) return { kind: "none" };
|
|
1424
|
-
const exactId = state.boxes.find((b) => b.id === q);
|
|
1425
|
-
if (exactId) return { kind: "ok", box: exactId };
|
|
1426
|
-
const prefixMatches = state.boxes.filter((b) => b.id.startsWith(q));
|
|
1427
|
-
if (prefixMatches.length === 1) return { kind: "ok", box: prefixMatches[0] };
|
|
1428
|
-
if (prefixMatches.length > 1) return { kind: "ambiguous", matches: prefixMatches };
|
|
1429
|
-
const byName = state.boxes.find((b) => b.name === q);
|
|
1430
|
-
if (byName) return { kind: "ok", box: byName };
|
|
1431
|
-
const byContainer = state.boxes.find((b) => b.container === q);
|
|
1432
|
-
if (byContainer) return { kind: "ok", box: byContainer };
|
|
1433
|
-
return { kind: "none" };
|
|
1434
|
-
}
|
|
1435
|
-
function allocateProjectIndex(state, projectRoot) {
|
|
1436
|
-
let max = 0;
|
|
1437
|
-
for (const b of state.boxes) {
|
|
1438
|
-
if (b.projectRoot !== projectRoot) continue;
|
|
1439
|
-
if (typeof b.projectIndex === "number" && b.projectIndex > max) {
|
|
1440
|
-
max = b.projectIndex;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
return max + 1;
|
|
1444
|
-
}
|
|
1445
|
-
function autoPickProjectBox(state, projectRoot) {
|
|
1446
|
-
const matches = state.boxes.filter((b) => b.projectRoot === projectRoot);
|
|
1447
|
-
if (matches.length === 0) return { kind: "none" };
|
|
1448
|
-
if (matches.length === 1) return { kind: "ok", box: matches[0] };
|
|
1449
|
-
return { kind: "ambiguous", matches };
|
|
1450
|
-
}
|
|
1451
|
-
function resolveBoxRef(ref, state, projectRoot) {
|
|
1452
|
-
if (ref === void 0) {
|
|
1453
|
-
if (projectRoot === void 0) return { kind: "none" };
|
|
1454
|
-
return autoPickProjectBox(state, projectRoot);
|
|
1455
|
-
}
|
|
1456
|
-
const trimmed = ref.trim();
|
|
1457
|
-
if (projectRoot !== void 0 && /^[1-9][0-9]*$/.test(trimmed)) {
|
|
1458
|
-
const idx = Number.parseInt(trimmed, 10);
|
|
1459
|
-
const hit = state.boxes.find(
|
|
1460
|
-
(b) => b.projectRoot === projectRoot && b.projectIndex === idx
|
|
1461
|
-
);
|
|
1462
|
-
return hit ? { kind: "ok", box: hit } : { kind: "none" };
|
|
1463
|
-
}
|
|
1464
|
-
return findBox(trimmed, state);
|
|
1465
|
-
}
|
|
1466
|
-
async function detectGitRepos(workspace) {
|
|
1467
|
-
const out = [];
|
|
1468
|
-
if (await isGitDir(join22(workspace, ".git"))) {
|
|
1469
|
-
out.push({ kind: "root", hostMainRepo: workspace, relPathFromWorkspace: "" });
|
|
1470
|
-
}
|
|
1471
|
-
let entries;
|
|
1472
|
-
try {
|
|
1473
|
-
entries = await readdir2(workspace, { withFileTypes: true });
|
|
1474
|
-
} catch {
|
|
1475
|
-
return out;
|
|
1476
|
-
}
|
|
1477
|
-
for (const e of entries) {
|
|
1478
|
-
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
|
1479
|
-
const sub = join22(workspace, e.name);
|
|
1480
|
-
if (await isGitDir(join22(sub, ".git"))) {
|
|
1481
|
-
out.push({ kind: "nested", hostMainRepo: sub, relPathFromWorkspace: e.name });
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
return out;
|
|
1485
|
-
}
|
|
1486
|
-
async function isGitDir(path) {
|
|
1487
|
-
try {
|
|
1488
|
-
const s = await stat3(path);
|
|
1489
|
-
return s.isDirectory();
|
|
1490
|
-
} catch {
|
|
1491
|
-
return false;
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
async function pickFreshBranch(hostMainRepo, base) {
|
|
1495
|
-
let candidate = base;
|
|
1496
|
-
let suffix = 2;
|
|
1497
|
-
while (await branchExists(hostMainRepo, candidate)) {
|
|
1498
|
-
candidate = `${base}-${String(suffix++)}`;
|
|
1499
|
-
if (suffix > 100) throw new GitWorktreeError(`could not find a free branch name near ${base}`);
|
|
1500
|
-
}
|
|
1501
|
-
return candidate;
|
|
1502
|
-
}
|
|
1503
|
-
async function branchExists(hostMainRepo, name) {
|
|
1504
|
-
const result = await execa(
|
|
1505
|
-
"git",
|
|
1506
|
-
["-C", hostMainRepo, "show-ref", "--verify", "--quiet", `refs/heads/${name}`],
|
|
1507
|
-
{ reject: false }
|
|
1508
|
-
);
|
|
1509
|
-
return result.exitCode === 0;
|
|
1510
|
-
}
|
|
1511
|
-
var GitWorktreeError = class extends Error {
|
|
1512
|
-
constructor(message) {
|
|
1513
|
-
super(message);
|
|
1514
|
-
this.name = "GitWorktreeError";
|
|
1515
|
-
}
|
|
1516
|
-
};
|
|
1517
|
-
|
|
1518
|
-
// ../../packages/sandbox-docker/dist/index.js
|
|
1641
|
+
import { execa as execa4 } from "execa";
|
|
1519
1642
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1520
1643
|
import { stat as stat22 } from "fs/promises";
|
|
1521
1644
|
import { homedir as homedir32 } from "os";
|
|
@@ -1526,7 +1649,7 @@ import { stat as stat32 } from "fs/promises";
|
|
|
1526
1649
|
import { homedir as homedir4 } from "os";
|
|
1527
1650
|
import { join as join5 } from "path";
|
|
1528
1651
|
import { execa as execa6 } from "execa";
|
|
1529
|
-
import { randomBytes } from "crypto";
|
|
1652
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1530
1653
|
import { execa as execa7 } from "execa";
|
|
1531
1654
|
import { existsSync } from "fs";
|
|
1532
1655
|
import { readFile as readFile42 } from "fs/promises";
|
|
@@ -1534,41 +1657,213 @@ import { homedir as homedir5 } from "os";
|
|
|
1534
1657
|
import { join as join6 } from "path";
|
|
1535
1658
|
import { execa as execa8 } from "execa";
|
|
1536
1659
|
import { execa as execa9 } from "execa";
|
|
1537
|
-
import {
|
|
1538
|
-
import { fileURLToPath } from "url";
|
|
1539
|
-
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
1540
|
-
import { execa as execa10 } from "execa";
|
|
1541
|
-
import { mkdir as mkdir4, readdir as readdir22, rm as rm22, stat as stat42 } from "fs/promises";
|
|
1660
|
+
import { mkdir as mkdir4, readdir as readdir22, rm as rm22, stat as stat4 } from "fs/promises";
|
|
1542
1661
|
import { homedir as homedir6, platform } from "os";
|
|
1543
|
-
import { join as join7, resolve as
|
|
1662
|
+
import { join as join7, resolve as resolve2 } from "path";
|
|
1544
1663
|
import { mkdir as mkdir5, mkdtemp as mkdtemp2, readFile as readFile5, readdir as readdir32, rm as rm3, writeFile as writeFile22 } from "fs/promises";
|
|
1545
1664
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
|
|
1546
1665
|
import { basename as basename3, join as join8 } from "path";
|
|
1547
|
-
import { execa as
|
|
1666
|
+
import { execa as execa10 } from "execa";
|
|
1548
1667
|
import { stat as stat5 } from "fs/promises";
|
|
1668
|
+
import { execa as execa11 } from "execa";
|
|
1549
1669
|
import { execa as execa12 } from "execa";
|
|
1550
|
-
import { execa as execa13 } from "execa";
|
|
1551
1670
|
import { spawn } from "child_process";
|
|
1552
|
-
import { randomBytes as
|
|
1553
|
-
import { existsSync as
|
|
1554
|
-
import { mkdir as mkdir6, readFile as readFile6, unlink, writeFile as writeFile32 } from "fs/promises";
|
|
1671
|
+
import { randomBytes as randomBytes22 } from "crypto";
|
|
1672
|
+
import { existsSync as existsSync2, openSync } from "fs";
|
|
1673
|
+
import { mkdir as mkdir6, readFile as readFile6, unlink as unlink2, writeFile as writeFile32 } from "fs/promises";
|
|
1555
1674
|
import { request as httpRequest } from "http";
|
|
1556
1675
|
import { homedir as homedir8 } from "os";
|
|
1557
|
-
import { dirname as
|
|
1676
|
+
import { dirname as dirname3, join as join9, resolve as resolve22 } from "path";
|
|
1558
1677
|
import { setTimeout as delay2 } from "timers/promises";
|
|
1559
|
-
import { fileURLToPath
|
|
1678
|
+
import { fileURLToPath } from "url";
|
|
1560
1679
|
|
|
1561
1680
|
// ../../packages/relay/dist/index.js
|
|
1562
|
-
import {
|
|
1681
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
1682
|
+
import { execa } from "execa";
|
|
1683
|
+
import { spawn as spawn4 } from "child_process";
|
|
1684
|
+
import {
|
|
1685
|
+
mkdir as mkdir2,
|
|
1686
|
+
readdir as readdir2,
|
|
1687
|
+
readFile as readFile23,
|
|
1688
|
+
rename as rename2,
|
|
1689
|
+
unlink,
|
|
1690
|
+
writeFile as writeFile2
|
|
1691
|
+
} from "fs/promises";
|
|
1692
|
+
import { join as join3 } from "path";
|
|
1563
1693
|
var DEFAULT_RELAY_PORT = 8787;
|
|
1564
1694
|
var RELAY_CONTAINER_NAME = "agentbox-relay";
|
|
1565
1695
|
var RELAY_NETWORK_NAME = "agentbox-net";
|
|
1566
1696
|
var RELAY_IMAGE_REF = "agentbox/relay:dev";
|
|
1567
1697
|
var DEFAULT_HOST_ACTION_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
1698
|
+
function hashRpcParams(params) {
|
|
1699
|
+
return createHash2("sha256").update(canonicalJson(params)).digest("hex");
|
|
1700
|
+
}
|
|
1701
|
+
function canonicalJson(v) {
|
|
1702
|
+
if (v === null) return "null";
|
|
1703
|
+
if (typeof v === "undefined") return "null";
|
|
1704
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "null";
|
|
1705
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
1706
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
1707
|
+
if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
|
|
1708
|
+
if (typeof v === "object") {
|
|
1709
|
+
const entries = Object.entries(v).filter(([k]) => k !== "hostInitiated").filter(([, val]) => val !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1710
|
+
return "{" + entries.map(([k, val]) => JSON.stringify(k) + ":" + canonicalJson(val)).join(",") + "}";
|
|
1711
|
+
}
|
|
1712
|
+
return "null";
|
|
1713
|
+
}
|
|
1714
|
+
var GH_PR_OPS = [
|
|
1715
|
+
"create",
|
|
1716
|
+
"view",
|
|
1717
|
+
"list",
|
|
1718
|
+
"comment",
|
|
1719
|
+
"review",
|
|
1720
|
+
"merge",
|
|
1721
|
+
"checkout",
|
|
1722
|
+
"close",
|
|
1723
|
+
"reopen"
|
|
1724
|
+
];
|
|
1725
|
+
function injectPrCreateHead(op, branch, args) {
|
|
1726
|
+
if (op !== "create") return args;
|
|
1727
|
+
if (!branch || branch === "HEAD") return args;
|
|
1728
|
+
if (hasHeadArg(args)) return args;
|
|
1729
|
+
return ["--head", branch, ...args];
|
|
1730
|
+
}
|
|
1731
|
+
function hasHeadArg(args) {
|
|
1732
|
+
return args.some((a) => a === "--head" || a.startsWith("--head=") || a.startsWith("-H"));
|
|
1733
|
+
}
|
|
1568
1734
|
var MAX_BODY_BYTES = 1024 * 1024;
|
|
1735
|
+
var QUEUE_DIR = join3(STATE_DIR, "queue");
|
|
1736
|
+
async function loadQueueConfig() {
|
|
1737
|
+
const d = BUILT_IN_DEFAULTS.queue;
|
|
1738
|
+
let global = {};
|
|
1739
|
+
try {
|
|
1740
|
+
global = parseUserConfig(await readFile23(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
|
|
1741
|
+
} catch {
|
|
1742
|
+
}
|
|
1743
|
+
const q = global.queue ?? {};
|
|
1744
|
+
return {
|
|
1745
|
+
enabled: q.enabled ?? d.enabled,
|
|
1746
|
+
maxConcurrent: q.maxConcurrent ?? d.maxConcurrent,
|
|
1747
|
+
maxWorking: q.maxWorking ?? d.maxWorking,
|
|
1748
|
+
idleGraceMs: (q.idleGraceSeconds ?? d.idleGraceSeconds) * 1e3
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
async function writeJob(job) {
|
|
1752
|
+
await mkdir2(QUEUE_DIR, { recursive: true });
|
|
1753
|
+
const final = join3(QUEUE_DIR, `${job.id}.json`);
|
|
1754
|
+
const tmp = `${final}.tmp.${String(process.pid)}.${String(Date.now())}`;
|
|
1755
|
+
await writeFile2(tmp, JSON.stringify(job, null, 2) + "\n", "utf8");
|
|
1756
|
+
await rename2(tmp, final);
|
|
1757
|
+
}
|
|
1758
|
+
async function readJob(id) {
|
|
1759
|
+
try {
|
|
1760
|
+
const raw = await readFile23(join3(QUEUE_DIR, `${id}.json`), "utf8");
|
|
1761
|
+
return JSON.parse(raw);
|
|
1762
|
+
} catch (err) {
|
|
1763
|
+
if (err.code === "ENOENT") return null;
|
|
1764
|
+
throw err;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
async function deleteJob(id) {
|
|
1768
|
+
try {
|
|
1769
|
+
await unlink(join3(QUEUE_DIR, `${id}.json`));
|
|
1770
|
+
} catch (err) {
|
|
1771
|
+
if (err.code !== "ENOENT") throw err;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async function loadQueue() {
|
|
1775
|
+
let entries;
|
|
1776
|
+
try {
|
|
1777
|
+
entries = await readdir2(QUEUE_DIR);
|
|
1778
|
+
} catch (err) {
|
|
1779
|
+
if (err.code === "ENOENT") return [];
|
|
1780
|
+
throw err;
|
|
1781
|
+
}
|
|
1782
|
+
const out = [];
|
|
1783
|
+
for (const name of entries) {
|
|
1784
|
+
if (!name.endsWith(".json")) continue;
|
|
1785
|
+
try {
|
|
1786
|
+
const raw = await readFile23(join3(QUEUE_DIR, name), "utf8");
|
|
1787
|
+
out.push(JSON.parse(raw));
|
|
1788
|
+
} catch {
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
out.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0);
|
|
1792
|
+
return out;
|
|
1793
|
+
}
|
|
1794
|
+
var RUNNING_COUNT_CACHE_MS = 3e3;
|
|
1795
|
+
var runningCountCache = null;
|
|
1796
|
+
async function defaultCountRunningBoxes() {
|
|
1797
|
+
const now = Date.now();
|
|
1798
|
+
if (runningCountCache && runningCountCache.expiresAt > now) {
|
|
1799
|
+
return runningCountCache.value;
|
|
1800
|
+
}
|
|
1801
|
+
const value = await uncachedCountRunningBoxes();
|
|
1802
|
+
runningCountCache = { value, expiresAt: now + RUNNING_COUNT_CACHE_MS };
|
|
1803
|
+
return value;
|
|
1804
|
+
}
|
|
1805
|
+
async function uncachedCountRunningBoxes() {
|
|
1806
|
+
let boxes;
|
|
1807
|
+
try {
|
|
1808
|
+
boxes = (await readState(STATE_FILE)).boxes;
|
|
1809
|
+
} catch {
|
|
1810
|
+
return 0;
|
|
1811
|
+
}
|
|
1812
|
+
if (boxes.length === 0) return 0;
|
|
1813
|
+
let count = 0;
|
|
1814
|
+
const dockerBoxes = [];
|
|
1815
|
+
for (const b of boxes) {
|
|
1816
|
+
const provider = b.provider ?? "docker";
|
|
1817
|
+
if (provider === "docker") {
|
|
1818
|
+
dockerBoxes.push(b);
|
|
1819
|
+
} else {
|
|
1820
|
+
count += 1;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
if (dockerBoxes.length > 0) {
|
|
1824
|
+
const states = await Promise.all(dockerBoxes.map((b) => inspectDockerState(b.container)));
|
|
1825
|
+
for (const s of states) {
|
|
1826
|
+
if (s === "running") count += 1;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
return count;
|
|
1830
|
+
}
|
|
1831
|
+
function inspectDockerState(containerName) {
|
|
1832
|
+
return new Promise((resolveP) => {
|
|
1833
|
+
const child = spawn4("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
|
|
1834
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1835
|
+
});
|
|
1836
|
+
let out = "";
|
|
1837
|
+
let settled = false;
|
|
1838
|
+
const finish = (state) => {
|
|
1839
|
+
if (settled) return;
|
|
1840
|
+
settled = true;
|
|
1841
|
+
resolveP(state);
|
|
1842
|
+
};
|
|
1843
|
+
const timer = setTimeout(() => {
|
|
1844
|
+
child.kill("SIGTERM");
|
|
1845
|
+
finish("other");
|
|
1846
|
+
}, 1e4);
|
|
1847
|
+
child.stdout?.on("data", (c) => {
|
|
1848
|
+
out += c.toString("utf8");
|
|
1849
|
+
});
|
|
1850
|
+
child.on("error", () => {
|
|
1851
|
+
clearTimeout(timer);
|
|
1852
|
+
finish("other");
|
|
1853
|
+
});
|
|
1854
|
+
child.on("close", () => {
|
|
1855
|
+
clearTimeout(timer);
|
|
1856
|
+
finish(out.trim() === "running" ? "running" : "other");
|
|
1857
|
+
});
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
var QUEUE_LOGS_DIR = join3(STATE_DIR, "logs");
|
|
1861
|
+
function queueLogPath(id) {
|
|
1862
|
+
return join3(QUEUE_LOGS_DIR, `queue-${id}.log`);
|
|
1863
|
+
}
|
|
1569
1864
|
|
|
1570
1865
|
// ../../packages/sandbox-docker/dist/index.js
|
|
1571
|
-
import { execa as
|
|
1866
|
+
import { execa as execa15 } from "execa";
|
|
1572
1867
|
import { readdir as readdir4, rm as rm4, stat as stat7 } from "fs/promises";
|
|
1573
1868
|
import { join as join12 } from "path";
|
|
1574
1869
|
|
|
@@ -1624,14 +1919,17 @@ var AmbiguousBoxError = class extends Error {
|
|
|
1624
1919
|
};
|
|
1625
1920
|
|
|
1626
1921
|
// ../../packages/sandbox-docker/dist/index.js
|
|
1627
|
-
import { execa as
|
|
1922
|
+
import { execa as execa14 } from "execa";
|
|
1628
1923
|
import { join as join11 } from "path";
|
|
1629
1924
|
import { homedir as homedir10 } from "os";
|
|
1630
1925
|
import { join as join13 } from "path";
|
|
1926
|
+
import { execa as execa16 } from "execa";
|
|
1927
|
+
import { existsSync as existsSync3, mkdirSync, renameSync, statSync } from "fs";
|
|
1928
|
+
import { basename as basename32, dirname as dirname22, posix, resolve as resolve4 } from "path";
|
|
1631
1929
|
import { execa as execa17 } from "execa";
|
|
1632
1930
|
import { copyFile, mkdtemp as mkdtemp3, readdir as readdir5, readFile as readFile7, rm as rm5, stat as stat8, writeFile as writeFile4 } from "fs/promises";
|
|
1633
1931
|
import { homedir as homedir11, tmpdir as tmpdir3 } from "os";
|
|
1634
|
-
import { basename as
|
|
1932
|
+
import { basename as basename4, join as join14, relative as relative2 } from "path";
|
|
1635
1933
|
import { execa as execa18 } from "execa";
|
|
1636
1934
|
function isHostPathHookCommand(command, hostHome) {
|
|
1637
1935
|
if (typeof command !== "string" || command.length === 0) return false;
|
|
@@ -1758,16 +2056,16 @@ function rewritePluginPaths(value, hostPluginsPrefix) {
|
|
|
1758
2056
|
}
|
|
1759
2057
|
return value;
|
|
1760
2058
|
}
|
|
1761
|
-
function
|
|
2059
|
+
function isPlainObject4(v) {
|
|
1762
2060
|
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1763
2061
|
}
|
|
1764
2062
|
function additiveMerge(hostRoot, boxRoot, hostPluginsPrefix, selectMap, withMap) {
|
|
1765
2063
|
const hostMap = selectMap(hostRoot);
|
|
1766
2064
|
const boxMap = selectMap(boxRoot);
|
|
1767
|
-
if (!
|
|
2065
|
+
if (!isPlainObject4(boxMap)) {
|
|
1768
2066
|
return { data: hostRoot, changed: false, addedKeys: [] };
|
|
1769
2067
|
}
|
|
1770
|
-
const base =
|
|
2068
|
+
const base = isPlainObject4(hostMap) ? { ...hostMap } : {};
|
|
1771
2069
|
const addedKeys = [];
|
|
1772
2070
|
for (const [key, value] of Object.entries(boxMap)) {
|
|
1773
2071
|
if (Object.prototype.hasOwnProperty.call(base, key)) continue;
|
|
@@ -1782,7 +2080,7 @@ function additiveMerge(hostRoot, boxRoot, hostPluginsPrefix, selectMap, withMap)
|
|
|
1782
2080
|
function mergeKnownMarketplaces(hostJson, boxJson, opts) {
|
|
1783
2081
|
const prefix = `${opts.hostHome}/.claude/plugins/`;
|
|
1784
2082
|
return additiveMerge(
|
|
1785
|
-
|
|
2083
|
+
isPlainObject4(hostJson) ? hostJson : {},
|
|
1786
2084
|
boxJson,
|
|
1787
2085
|
prefix,
|
|
1788
2086
|
(root) => root,
|
|
@@ -1791,24 +2089,24 @@ function mergeKnownMarketplaces(hostJson, boxJson, opts) {
|
|
|
1791
2089
|
}
|
|
1792
2090
|
function mergeInstalledPlugins(hostJson, boxJson, opts) {
|
|
1793
2091
|
const prefix = `${opts.hostHome}/.claude/plugins/`;
|
|
1794
|
-
const hostRoot =
|
|
2092
|
+
const hostRoot = isPlainObject4(hostJson) ? hostJson : { plugins: {} };
|
|
1795
2093
|
return additiveMerge(
|
|
1796
2094
|
hostRoot,
|
|
1797
2095
|
boxJson,
|
|
1798
2096
|
prefix,
|
|
1799
|
-
(root) =>
|
|
2097
|
+
(root) => isPlainObject4(root) ? root["plugins"] : void 0,
|
|
1800
2098
|
(host, merged) => ({ ...host, plugins: merged })
|
|
1801
2099
|
);
|
|
1802
2100
|
}
|
|
1803
2101
|
function referencedPluginVersionKeys(installedPluginsJson) {
|
|
1804
2102
|
const keys = /* @__PURE__ */ new Set();
|
|
1805
|
-
if (!
|
|
2103
|
+
if (!isPlainObject4(installedPluginsJson)) return keys;
|
|
1806
2104
|
const plugins = installedPluginsJson["plugins"];
|
|
1807
|
-
if (!
|
|
2105
|
+
if (!isPlainObject4(plugins)) return keys;
|
|
1808
2106
|
for (const entries of Object.values(plugins)) {
|
|
1809
2107
|
if (!Array.isArray(entries)) continue;
|
|
1810
2108
|
for (const entry of entries) {
|
|
1811
|
-
if (!
|
|
2109
|
+
if (!isPlainObject4(entry)) continue;
|
|
1812
2110
|
const installPath = entry["installPath"];
|
|
1813
2111
|
if (typeof installPath !== "string") continue;
|
|
1814
2112
|
const segments = installPath.split("/").filter((s) => s.length > 0);
|
|
@@ -1819,7 +2117,7 @@ function referencedPluginVersionKeys(installedPluginsJson) {
|
|
|
1819
2117
|
return keys;
|
|
1820
2118
|
}
|
|
1821
2119
|
async function dockerInfo() {
|
|
1822
|
-
const result = await
|
|
2120
|
+
const result = await execa2("docker", ["info"], { reject: false });
|
|
1823
2121
|
if (result.exitCode !== 0) {
|
|
1824
2122
|
throw new Error(
|
|
1825
2123
|
`docker info failed (exit ${String(result.exitCode)}). Is the Docker daemon running?
|
|
@@ -1866,6 +2164,9 @@ async function runBox(spec) {
|
|
|
1866
2164
|
// on the macOS engines). Boxes use it to reach the host relay process.
|
|
1867
2165
|
"--add-host=host.docker.internal:host-gateway"
|
|
1868
2166
|
];
|
|
2167
|
+
if (process.env.AGENTBOX === "1") {
|
|
2168
|
+
args.push("--user", "0");
|
|
2169
|
+
}
|
|
1869
2170
|
const lim = spec.limits;
|
|
1870
2171
|
if (lim) {
|
|
1871
2172
|
if (lim.memoryBytes && lim.memoryBytes > 0) {
|
|
@@ -1892,11 +2193,11 @@ async function runBox(spec) {
|
|
|
1892
2193
|
args.push("-e", `${k}=${val}`);
|
|
1893
2194
|
}
|
|
1894
2195
|
args.push(spec.image, "sleep", "infinity");
|
|
1895
|
-
const { stdout } = await
|
|
2196
|
+
const { stdout } = await execa2("docker", args);
|
|
1896
2197
|
return stdout.trim();
|
|
1897
2198
|
}
|
|
1898
2199
|
async function dockerStorageDriver() {
|
|
1899
|
-
const result = await
|
|
2200
|
+
const result = await execa2("docker", ["info", "--format", "{{.Driver}}"], { reject: false });
|
|
1900
2201
|
if (result.exitCode !== 0) return "";
|
|
1901
2202
|
return (result.stdout ?? "").trim();
|
|
1902
2203
|
}
|
|
@@ -1905,7 +2206,7 @@ async function execInBox(container, cmd, opts = {}) {
|
|
|
1905
2206
|
if (opts.detach) args.push("-d");
|
|
1906
2207
|
if (opts.user) args.push("--user", opts.user);
|
|
1907
2208
|
args.push(container, ...cmd);
|
|
1908
|
-
const result = await
|
|
2209
|
+
const result = await execa2("docker", args, {
|
|
1909
2210
|
reject: false,
|
|
1910
2211
|
...opts.timeoutMs ? { timeout: opts.timeoutMs } : {}
|
|
1911
2212
|
});
|
|
@@ -1916,49 +2217,49 @@ async function execInBox(container, cmd, opts = {}) {
|
|
|
1916
2217
|
};
|
|
1917
2218
|
}
|
|
1918
2219
|
async function removeContainer(container) {
|
|
1919
|
-
await
|
|
2220
|
+
await execa2("docker", ["rm", "-f", container], { reject: false });
|
|
1920
2221
|
}
|
|
1921
2222
|
async function removeVolume(name) {
|
|
1922
|
-
await
|
|
2223
|
+
await execa2("docker", ["volume", "rm", name], { reject: false });
|
|
1923
2224
|
}
|
|
1924
2225
|
async function removeImage(ref, opts = {}) {
|
|
1925
2226
|
const args = ["image", "rm"];
|
|
1926
2227
|
if (opts.force !== false) args.push("-f");
|
|
1927
2228
|
args.push(ref);
|
|
1928
|
-
const result = await
|
|
2229
|
+
const result = await execa2("docker", args, { reject: false });
|
|
1929
2230
|
return result.exitCode === 0;
|
|
1930
2231
|
}
|
|
1931
2232
|
async function containerExists(name) {
|
|
1932
|
-
const result = await
|
|
2233
|
+
const result = await execa2("docker", ["container", "inspect", "--format", "{{.Id}}", name], {
|
|
1933
2234
|
reject: false
|
|
1934
2235
|
});
|
|
1935
2236
|
return result.exitCode === 0;
|
|
1936
2237
|
}
|
|
1937
2238
|
async function volumeExists(name) {
|
|
1938
|
-
const result = await
|
|
2239
|
+
const result = await execa2("docker", ["volume", "inspect", name], { reject: false });
|
|
1939
2240
|
return result.exitCode === 0;
|
|
1940
2241
|
}
|
|
1941
2242
|
async function ensureVolume(name) {
|
|
1942
2243
|
if (await volumeExists(name)) return;
|
|
1943
|
-
await
|
|
2244
|
+
await execa2("docker", ["volume", "create", name]);
|
|
1944
2245
|
}
|
|
1945
2246
|
async function removeNetwork(name) {
|
|
1946
|
-
await
|
|
2247
|
+
await execa2("docker", ["network", "rm", name], { reject: false });
|
|
1947
2248
|
}
|
|
1948
2249
|
async function pauseContainer(name) {
|
|
1949
|
-
await
|
|
2250
|
+
await execa2("docker", ["pause", name]);
|
|
1950
2251
|
}
|
|
1951
2252
|
async function unpauseContainer(name) {
|
|
1952
|
-
await
|
|
2253
|
+
await execa2("docker", ["unpause", name]);
|
|
1953
2254
|
}
|
|
1954
2255
|
async function stopContainer(name) {
|
|
1955
|
-
await
|
|
2256
|
+
await execa2("docker", ["stop", name]);
|
|
1956
2257
|
}
|
|
1957
2258
|
async function startContainer(name) {
|
|
1958
|
-
await
|
|
2259
|
+
await execa2("docker", ["start", name]);
|
|
1959
2260
|
}
|
|
1960
2261
|
async function inspectContainerStatus(name) {
|
|
1961
|
-
const result = await
|
|
2262
|
+
const result = await execa2("docker", ["inspect", "--format", "{{.State.Status}}", name], {
|
|
1962
2263
|
reject: false
|
|
1963
2264
|
});
|
|
1964
2265
|
if (result.exitCode !== 0) return "missing";
|
|
@@ -1979,7 +2280,7 @@ async function inspectContainerStatus(name) {
|
|
|
1979
2280
|
}
|
|
1980
2281
|
}
|
|
1981
2282
|
async function inspectContainer(name) {
|
|
1982
|
-
const result = await
|
|
2283
|
+
const result = await execa2("docker", ["inspect", name], { reject: false });
|
|
1983
2284
|
if (result.exitCode !== 0) return null;
|
|
1984
2285
|
try {
|
|
1985
2286
|
const parsed = JSON.parse(result.stdout ?? "null");
|
|
@@ -1989,7 +2290,7 @@ async function inspectContainer(name) {
|
|
|
1989
2290
|
}
|
|
1990
2291
|
}
|
|
1991
2292
|
async function inspectVolumeMountpoint(name) {
|
|
1992
|
-
const result = await
|
|
2293
|
+
const result = await execa2("docker", ["volume", "inspect", "--format", "{{.Mountpoint}}", name], {
|
|
1993
2294
|
reject: false
|
|
1994
2295
|
});
|
|
1995
2296
|
if (result.exitCode !== 0) return null;
|
|
@@ -1997,7 +2298,7 @@ async function inspectVolumeMountpoint(name) {
|
|
|
1997
2298
|
}
|
|
1998
2299
|
var AGENTBOX_PREFIX = "agentbox-";
|
|
1999
2300
|
async function listAgentboxContainers() {
|
|
2000
|
-
const result = await
|
|
2301
|
+
const result = await execa2(
|
|
2001
2302
|
"docker",
|
|
2002
2303
|
["ps", "-a", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Names}}"],
|
|
2003
2304
|
{ reject: false }
|
|
@@ -2006,7 +2307,7 @@ async function listAgentboxContainers() {
|
|
|
2006
2307
|
return (result.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.startsWith(AGENTBOX_PREFIX));
|
|
2007
2308
|
}
|
|
2008
2309
|
async function publishedHostPort(container, containerPort) {
|
|
2009
|
-
const result = await
|
|
2310
|
+
const result = await execa2("docker", ["port", container, `${String(containerPort)}/tcp`], {
|
|
2010
2311
|
reject: false
|
|
2011
2312
|
});
|
|
2012
2313
|
if (result.exitCode !== 0) return null;
|
|
@@ -2016,7 +2317,7 @@ async function publishedHostPort(container, containerPort) {
|
|
|
2016
2317
|
return m ? Number(m[1]) : null;
|
|
2017
2318
|
}
|
|
2018
2319
|
async function listAgentboxVolumes() {
|
|
2019
|
-
const result = await
|
|
2320
|
+
const result = await execa2(
|
|
2020
2321
|
"docker",
|
|
2021
2322
|
["volume", "ls", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Name}}"],
|
|
2022
2323
|
{ reject: false }
|
|
@@ -2385,9 +2686,126 @@ var ExportError = class extends Error {
|
|
|
2385
2686
|
this.stderr = stderr;
|
|
2386
2687
|
this.name = "ExportError";
|
|
2387
2688
|
}
|
|
2388
|
-
stdout;
|
|
2389
|
-
stderr;
|
|
2390
|
-
};
|
|
2689
|
+
stdout;
|
|
2690
|
+
stderr;
|
|
2691
|
+
};
|
|
2692
|
+
async function copyCarryPathsToBox(opts) {
|
|
2693
|
+
const log = opts.onLog ?? (() => {
|
|
2694
|
+
});
|
|
2695
|
+
let copied = 0;
|
|
2696
|
+
const errors = [];
|
|
2697
|
+
const applied = [];
|
|
2698
|
+
for (const [i, entry] of opts.entries.entries()) {
|
|
2699
|
+
const where = `carry[${String(i)}] "${entry.rawSrc}"`;
|
|
2700
|
+
if (entry.kind === "missing") {
|
|
2701
|
+
log(`${where}: skipped (missing on host, optional)`);
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
try {
|
|
2705
|
+
await copyOneEntry(opts.container, entry);
|
|
2706
|
+
copied += 1;
|
|
2707
|
+
applied.push({ src: entry.absSrc, dest: entry.absDest, bytes: entry.bytes ?? 0 });
|
|
2708
|
+
} catch (err) {
|
|
2709
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2710
|
+
errors.push(`${where}: ${msg}`);
|
|
2711
|
+
log(`${where}: failed: ${msg}`);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
return { copied, errors, applied };
|
|
2715
|
+
}
|
|
2716
|
+
var BOX_HOME = "/home/vscode";
|
|
2717
|
+
async function copyOneEntry(container, entry) {
|
|
2718
|
+
if (entry.kind === "missing") return;
|
|
2719
|
+
const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
|
|
2720
|
+
const boxDestParent = boxDest.endsWith("/") ? boxDest.slice(0, -1) : boxDest;
|
|
2721
|
+
const parentDir = entry.kind === "dir" ? boxDestParent : dirnameUnix(boxDestParent);
|
|
2722
|
+
const mkdir8 = await execa22(
|
|
2723
|
+
"docker",
|
|
2724
|
+
["exec", "--user", "0:0", container, "mkdir", "-p", parentDir],
|
|
2725
|
+
{ reject: false }
|
|
2726
|
+
);
|
|
2727
|
+
if (mkdir8.exitCode !== 0) {
|
|
2728
|
+
throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir8.stderr).slice(0, 300)}`);
|
|
2729
|
+
}
|
|
2730
|
+
if (entry.kind === "file") {
|
|
2731
|
+
const cp = await execa22(
|
|
2732
|
+
"docker",
|
|
2733
|
+
["cp", entry.absSrc, `${container}:${boxDest}`],
|
|
2734
|
+
{ reject: false }
|
|
2735
|
+
);
|
|
2736
|
+
if (cp.exitCode !== 0) {
|
|
2737
|
+
throw new Error(`docker cp failed: ${String(cp.stderr).slice(0, 300)}`);
|
|
2738
|
+
}
|
|
2739
|
+
} else {
|
|
2740
|
+
const packed = await execa22(
|
|
2741
|
+
"tar",
|
|
2742
|
+
["-C", entry.absSrc, "-cf", "-", "."],
|
|
2743
|
+
{ encoding: "buffer", reject: false }
|
|
2744
|
+
);
|
|
2745
|
+
if (packed.exitCode !== 0) {
|
|
2746
|
+
throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
|
|
2747
|
+
}
|
|
2748
|
+
const extract = await execa22(
|
|
2749
|
+
"docker",
|
|
2750
|
+
[
|
|
2751
|
+
"exec",
|
|
2752
|
+
"-i",
|
|
2753
|
+
"--user",
|
|
2754
|
+
"0:0",
|
|
2755
|
+
container,
|
|
2756
|
+
"tar",
|
|
2757
|
+
"-xf",
|
|
2758
|
+
"-",
|
|
2759
|
+
"-C",
|
|
2760
|
+
boxDest,
|
|
2761
|
+
"--no-same-permissions",
|
|
2762
|
+
"--no-same-owner",
|
|
2763
|
+
"-m"
|
|
2764
|
+
],
|
|
2765
|
+
{ input: packed.stdout, reject: false }
|
|
2766
|
+
);
|
|
2767
|
+
if (extract.exitCode !== 0) {
|
|
2768
|
+
throw new Error(`tar extract failed: ${String(extract.stderr).slice(0, 300)}`);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
if (entry.mode !== void 0) {
|
|
2772
|
+
const modeStr = entry.mode.toString(8).padStart(4, "0");
|
|
2773
|
+
const chmod2 = await execa22(
|
|
2774
|
+
"docker",
|
|
2775
|
+
["exec", "--user", "0:0", container, "chmod", "-R", modeStr, boxDest],
|
|
2776
|
+
{ reject: false }
|
|
2777
|
+
);
|
|
2778
|
+
if (chmod2.exitCode !== 0) {
|
|
2779
|
+
throw new Error(`chmod failed: ${String(chmod2.stderr).slice(0, 300)}`);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
const uid = entry.user ?? 1e3;
|
|
2783
|
+
const chown = await execa22(
|
|
2784
|
+
"docker",
|
|
2785
|
+
["exec", "--user", "0:0", container, "chown", "-R", `${String(uid)}:${String(uid)}`, boxDest],
|
|
2786
|
+
{ reject: false }
|
|
2787
|
+
);
|
|
2788
|
+
if (chown.exitCode !== 0) {
|
|
2789
|
+
throw new Error(`chown failed: ${String(chown.stderr).slice(0, 300)}`);
|
|
2790
|
+
}
|
|
2791
|
+
if (boxDest.startsWith(BOX_HOME + "/") && dirnameUnix(boxDest) !== BOX_HOME) {
|
|
2792
|
+
const safeDest = boxDest.replace(/'/g, `'\\''`);
|
|
2793
|
+
const script = `set -e; parent="$(dirname '${safeDest}')"; while [ "$parent" != "${BOX_HOME}" ] && [ "$parent" != "/" ]; do chown ${String(uid)}:${String(uid)} "$parent"; parent="$(dirname "$parent")"; done`;
|
|
2794
|
+
const chownParents = await execa22(
|
|
2795
|
+
"docker",
|
|
2796
|
+
["exec", "--user", "0:0", container, "bash", "-c", script],
|
|
2797
|
+
{ reject: false }
|
|
2798
|
+
);
|
|
2799
|
+
if (chownParents.exitCode !== 0) {
|
|
2800
|
+
throw new Error(`chown parents failed: ${String(chownParents.stderr).slice(0, 300)}`);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
function dirnameUnix(p) {
|
|
2805
|
+
const i = p.lastIndexOf("/");
|
|
2806
|
+
if (i <= 0) return "/";
|
|
2807
|
+
return p.slice(0, i);
|
|
2808
|
+
}
|
|
2391
2809
|
var SHARED_CLAUDE_VOLUME = "agentbox-claude-config";
|
|
2392
2810
|
var DEFAULT_CLAUDE_SESSION = "claude";
|
|
2393
2811
|
var CONTAINER_CLAUDE_DIR = "/home/vscode/.claude";
|
|
@@ -2403,7 +2821,7 @@ function resolveClaudeVolume(opts) {
|
|
|
2403
2821
|
}
|
|
2404
2822
|
async function pathExists(p) {
|
|
2405
2823
|
try {
|
|
2406
|
-
await
|
|
2824
|
+
await stat3(p);
|
|
2407
2825
|
return true;
|
|
2408
2826
|
} catch {
|
|
2409
2827
|
return false;
|
|
@@ -2427,10 +2845,10 @@ async function findBrokenSymlinks(root) {
|
|
|
2427
2845
|
return;
|
|
2428
2846
|
}
|
|
2429
2847
|
for (const ent of entries) {
|
|
2430
|
-
const full =
|
|
2848
|
+
const full = join22(dir, ent.name);
|
|
2431
2849
|
if (ent.isSymbolicLink()) {
|
|
2432
2850
|
try {
|
|
2433
|
-
await
|
|
2851
|
+
await stat3(full);
|
|
2434
2852
|
} catch {
|
|
2435
2853
|
broken.push(relative(root, full));
|
|
2436
2854
|
}
|
|
@@ -2447,13 +2865,13 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2447
2865
|
await ensureVolume(spec.volume);
|
|
2448
2866
|
const created = !existed;
|
|
2449
2867
|
if (!opts.syncFromHost) return { created, synced: false };
|
|
2450
|
-
const hostClaude =
|
|
2868
|
+
const hostClaude = join22(homedir2(), ".claude");
|
|
2451
2869
|
if (!await pathExists(hostClaude)) return { created, synced: false };
|
|
2452
|
-
const hostClaudeJson =
|
|
2870
|
+
const hostClaudeJson = join22(homedir2(), ".claude.json");
|
|
2453
2871
|
const hasJson = await pathExists(hostClaudeJson);
|
|
2454
2872
|
const seedClaudeJson = !await volumeHasClaudeJson(spec.volume, opts.image);
|
|
2455
|
-
const hostHome =
|
|
2456
|
-
const hostAgents =
|
|
2873
|
+
const hostHome = homedir2();
|
|
2874
|
+
const hostAgents = join22(homedir2(), ".agents");
|
|
2457
2875
|
const hasAgents = await pathExists(hostAgents);
|
|
2458
2876
|
const args = [
|
|
2459
2877
|
"run",
|
|
@@ -2471,15 +2889,15 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2471
2889
|
];
|
|
2472
2890
|
if (hasJson && seedClaudeJson) args.push("-v", `${hostClaudeJson}:/src-claude-json:ro`);
|
|
2473
2891
|
if (hasAgents) args.push("-v", `${hostAgents}:/.agents:ro`);
|
|
2474
|
-
const filterDir = await mkdtemp(
|
|
2892
|
+
const filterDir = await mkdtemp(join22(tmpdir(), "agentbox-claude-filter-"));
|
|
2475
2893
|
let filteredHookCount = 0;
|
|
2476
2894
|
let installMethodFixed = false;
|
|
2477
2895
|
let aliasedProjectKey = false;
|
|
2478
2896
|
let workspaceTrusted = false;
|
|
2479
2897
|
try {
|
|
2480
2898
|
const settingsResult = await maybeFilterTo(
|
|
2481
|
-
|
|
2482
|
-
|
|
2899
|
+
join22(hostClaude, "settings.json"),
|
|
2900
|
+
join22(filterDir, "settings.json"),
|
|
2483
2901
|
hostHome
|
|
2484
2902
|
);
|
|
2485
2903
|
filteredHookCount += settingsResult.removedHooks;
|
|
@@ -2487,7 +2905,7 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2487
2905
|
} else if (hasJson) {
|
|
2488
2906
|
const jsonResult = await maybeFilterTo(
|
|
2489
2907
|
hostClaudeJson,
|
|
2490
|
-
|
|
2908
|
+
join22(filterDir, "_claude.json"),
|
|
2491
2909
|
hostHome,
|
|
2492
2910
|
{
|
|
2493
2911
|
setInstallMethodNative: true,
|
|
@@ -2501,7 +2919,7 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2501
2919
|
workspaceTrusted = jsonResult.workspaceTrusted;
|
|
2502
2920
|
} else {
|
|
2503
2921
|
await writeFile3(
|
|
2504
|
-
|
|
2922
|
+
join22(filterDir, "_claude.json"),
|
|
2505
2923
|
JSON.stringify(
|
|
2506
2924
|
{
|
|
2507
2925
|
installMethod: "native",
|
|
@@ -2603,7 +3021,7 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
|
|
|
2603
3021
|
};
|
|
2604
3022
|
let parsed;
|
|
2605
3023
|
try {
|
|
2606
|
-
parsed = JSON.parse(await
|
|
3024
|
+
parsed = JSON.parse(await readFile24(src, "utf8"));
|
|
2607
3025
|
} catch {
|
|
2608
3026
|
return zero;
|
|
2609
3027
|
}
|
|
@@ -2664,14 +3082,14 @@ var PLUGIN_INSTALL_BACKOFF_MIN = Math.round(PLUGIN_INSTALL_BACKOFF_MS / 6e4);
|
|
|
2664
3082
|
var NPM_CACHE_DIR = "/home/vscode/.claude/.agentbox-npm-cache";
|
|
2665
3083
|
async function isFile(p) {
|
|
2666
3084
|
try {
|
|
2667
|
-
return (await
|
|
3085
|
+
return (await stat3(p)).isFile();
|
|
2668
3086
|
} catch {
|
|
2669
3087
|
return false;
|
|
2670
3088
|
}
|
|
2671
3089
|
}
|
|
2672
3090
|
async function isRecentFailMarker(p) {
|
|
2673
3091
|
try {
|
|
2674
|
-
const st = await
|
|
3092
|
+
const st = await stat3(p);
|
|
2675
3093
|
return Date.now() - st.mtimeMs < PLUGIN_INSTALL_BACKOFF_MS;
|
|
2676
3094
|
} catch {
|
|
2677
3095
|
return false;
|
|
@@ -2679,14 +3097,14 @@ async function isRecentFailMarker(p) {
|
|
|
2679
3097
|
}
|
|
2680
3098
|
async function isDir(p) {
|
|
2681
3099
|
try {
|
|
2682
|
-
return (await
|
|
3100
|
+
return (await stat3(p)).isDirectory();
|
|
2683
3101
|
} catch {
|
|
2684
3102
|
return false;
|
|
2685
3103
|
}
|
|
2686
3104
|
}
|
|
2687
3105
|
async function readReferencedPluginKeys(installedPluginsJsonPath) {
|
|
2688
3106
|
try {
|
|
2689
|
-
const raw = await
|
|
3107
|
+
const raw = await readFile24(installedPluginsJsonPath, "utf8");
|
|
2690
3108
|
return referencedPluginVersionKeys(JSON.parse(raw));
|
|
2691
3109
|
} catch {
|
|
2692
3110
|
return /* @__PURE__ */ new Set();
|
|
@@ -2694,7 +3112,7 @@ async function readReferencedPluginKeys(installedPluginsJsonPath) {
|
|
|
2694
3112
|
}
|
|
2695
3113
|
async function scanPluginCacheForRebuild(cacheRoot) {
|
|
2696
3114
|
const referenced = await readReferencedPluginKeys(
|
|
2697
|
-
|
|
3115
|
+
join22(cacheRoot, "..", "installed_plugins.json")
|
|
2698
3116
|
);
|
|
2699
3117
|
let marketplaces;
|
|
2700
3118
|
try {
|
|
@@ -2704,7 +3122,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
|
|
|
2704
3122
|
}
|
|
2705
3123
|
for (const m of marketplaces) {
|
|
2706
3124
|
if (!m.isDirectory()) continue;
|
|
2707
|
-
const mPath =
|
|
3125
|
+
const mPath = join22(cacheRoot, m.name);
|
|
2708
3126
|
let plugins;
|
|
2709
3127
|
try {
|
|
2710
3128
|
plugins = await readdir3(mPath, { withFileTypes: true });
|
|
@@ -2713,7 +3131,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
|
|
|
2713
3131
|
}
|
|
2714
3132
|
for (const p of plugins) {
|
|
2715
3133
|
if (!p.isDirectory()) continue;
|
|
2716
|
-
const pPath =
|
|
3134
|
+
const pPath = join22(mPath, p.name);
|
|
2717
3135
|
let versions;
|
|
2718
3136
|
try {
|
|
2719
3137
|
versions = await readdir3(pPath, { withFileTypes: true });
|
|
@@ -2723,10 +3141,10 @@ async function scanPluginCacheForRebuild(cacheRoot) {
|
|
|
2723
3141
|
for (const v of versions) {
|
|
2724
3142
|
if (!v.isDirectory()) continue;
|
|
2725
3143
|
if (referenced.size > 0 && !referenced.has(`${m.name}/${p.name}/${v.name}`)) continue;
|
|
2726
|
-
const vPath =
|
|
2727
|
-
if (!await isFile(
|
|
2728
|
-
if (await isFile(
|
|
2729
|
-
if (await isRecentFailMarker(
|
|
3144
|
+
const vPath = join22(pPath, v.name);
|
|
3145
|
+
if (!await isFile(join22(vPath, "package.json"))) continue;
|
|
3146
|
+
if (await isFile(join22(vPath, PLUGIN_INSTALLED_MARKER))) continue;
|
|
3147
|
+
if (await isRecentFailMarker(join22(vPath, PLUGIN_FAILED_MARKER))) continue;
|
|
2730
3148
|
return true;
|
|
2731
3149
|
}
|
|
2732
3150
|
}
|
|
@@ -3018,67 +3436,31 @@ async function waitForTmuxPaneContent(container, sessionName, timeoutMs = 2e4) {
|
|
|
3018
3436
|
await delay(400);
|
|
3019
3437
|
}
|
|
3020
3438
|
}
|
|
3021
|
-
function
|
|
3022
|
-
const s = sessionName;
|
|
3439
|
+
function tmuxConfigSubcommands(sessionName) {
|
|
3023
3440
|
return [
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
"
|
|
3032
|
-
"set",
|
|
3033
|
-
"-g",
|
|
3034
|
-
"prefix",
|
|
3035
|
-
"C-a",
|
|
3036
|
-
";",
|
|
3037
|
-
"set",
|
|
3038
|
-
"-g",
|
|
3039
|
-
"prefix2",
|
|
3040
|
-
"C-b",
|
|
3041
|
-
";",
|
|
3042
|
-
"bind-key",
|
|
3043
|
-
"C-a",
|
|
3044
|
-
"send-prefix",
|
|
3045
|
-
";",
|
|
3046
|
-
"bind-key",
|
|
3047
|
-
"C-b",
|
|
3048
|
-
"send-prefix",
|
|
3049
|
-
"-2",
|
|
3050
|
-
";",
|
|
3051
|
-
"bind-key",
|
|
3052
|
-
"d",
|
|
3053
|
-
"detach-client",
|
|
3054
|
-
// Modified-key reporting: without `extended-keys on`, tmux strips the
|
|
3055
|
-
// modifier from Shift+Enter / Ctrl+Enter / etc. so Claude Code can't
|
|
3056
|
-
// distinguish them from a plain Enter — pressing Shift+Enter submits the
|
|
3057
|
-
// prompt instead of inserting a newline. `csi-u` is the format Claude
|
|
3058
|
-
// Code recognises after `/terminal-setup`. Server-global so it survives
|
|
3059
|
-
// grouped sibling sessions (e.g. the dashboard's `<name>-dash`).
|
|
3060
|
-
";",
|
|
3061
|
-
"set",
|
|
3062
|
-
"-g",
|
|
3063
|
-
"extended-keys",
|
|
3064
|
-
"on",
|
|
3065
|
-
";",
|
|
3066
|
-
"set",
|
|
3067
|
-
"-as",
|
|
3068
|
-
"terminal-features",
|
|
3069
|
-
",*:extkeys",
|
|
3070
|
-
// Hide the inner tmux status bar — the wrapped-pty footer (for
|
|
3071
|
-
// `agentbox claude` / `agentbox shell`) and the dashboard's own status
|
|
3072
|
-
// row already show the box name + detach hint; without `status off`
|
|
3073
|
-
// they double up.
|
|
3074
|
-
";",
|
|
3075
|
-
"set",
|
|
3076
|
-
"-t",
|
|
3077
|
-
s,
|
|
3078
|
-
"status",
|
|
3079
|
-
"off"
|
|
3441
|
+
["set", "-g", "prefix", "C-a"],
|
|
3442
|
+
["set", "-g", "prefix2", "C-b"],
|
|
3443
|
+
["bind-key", "C-a", "send-prefix"],
|
|
3444
|
+
["bind-key", "C-b", "send-prefix", "-2"],
|
|
3445
|
+
["bind-key", "d", "detach-client"],
|
|
3446
|
+
["set", "-g", "extended-keys", "on"],
|
|
3447
|
+
["set", "-as", "terminal-features", ",*:extkeys"],
|
|
3448
|
+
["set", "-t", sessionName, "status", "off"]
|
|
3080
3449
|
];
|
|
3081
3450
|
}
|
|
3451
|
+
function buildTmuxSessionArgs(sessionName) {
|
|
3452
|
+
const out = [];
|
|
3453
|
+
for (const sub of tmuxConfigSubcommands(sessionName)) {
|
|
3454
|
+
out.push(";", ...sub);
|
|
3455
|
+
}
|
|
3456
|
+
return out;
|
|
3457
|
+
}
|
|
3458
|
+
function buildTmuxConfigShellSnippet(sessionName) {
|
|
3459
|
+
return tmuxConfigSubcommands(sessionName).map((sub) => `tmux ${sub.map(shellSingleQuoteIfNeeded).join(" ")}`).join("; ");
|
|
3460
|
+
}
|
|
3461
|
+
function shellSingleQuoteIfNeeded(s) {
|
|
3462
|
+
return /^[A-Za-z0-9_:.\/=+-]+$/.test(s) ? s : "'" + s.replace(/'/g, "'\\''") + "'";
|
|
3463
|
+
}
|
|
3082
3464
|
function buildShellArgv(container) {
|
|
3083
3465
|
const term = process.env["TERM"] ?? "xterm-256color";
|
|
3084
3466
|
return ["exec", "-it", "-e", `TERM=${term}`, "--user", CONTAINER_USER, container, "bash", "-l"];
|
|
@@ -3197,14 +3579,14 @@ async function listChildDirs(dir) {
|
|
|
3197
3579
|
}
|
|
3198
3580
|
async function readJsonFile(path) {
|
|
3199
3581
|
try {
|
|
3200
|
-
return JSON.parse(await
|
|
3582
|
+
return JSON.parse(await readFile24(path, "utf8"));
|
|
3201
3583
|
} catch {
|
|
3202
3584
|
return void 0;
|
|
3203
3585
|
}
|
|
3204
3586
|
}
|
|
3205
3587
|
async function pullClaudeExtras(spec, opts) {
|
|
3206
|
-
const hostHome =
|
|
3207
|
-
const hostClaude =
|
|
3588
|
+
const hostHome = homedir2();
|
|
3589
|
+
const hostClaude = join22(hostHome, ".claude");
|
|
3208
3590
|
const inventoryScript = [
|
|
3209
3591
|
"for cat in skills agents commands; do",
|
|
3210
3592
|
' [ -d "/src/$cat" ] || continue;',
|
|
@@ -3266,7 +3648,7 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3266
3648
|
const newItems = [];
|
|
3267
3649
|
const applyPaths = [];
|
|
3268
3650
|
for (const cat of PULL_DIR_CATEGORIES) {
|
|
3269
|
-
const hostNames = await listChildDirs(
|
|
3651
|
+
const hostNames = await listChildDirs(join22(hostClaude, cat));
|
|
3270
3652
|
const excludes = cat === "skills" ? SKILL_EXCLUDE_PREFIXES : [];
|
|
3271
3653
|
for (const name of pickNewItems(boxDirs[cat] ?? [], hostNames, excludes)) {
|
|
3272
3654
|
newItems.push({ category: cat, name });
|
|
@@ -3274,8 +3656,8 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3274
3656
|
}
|
|
3275
3657
|
}
|
|
3276
3658
|
const hostPluginKeys = [];
|
|
3277
|
-
for (const m of await listChildDirs(
|
|
3278
|
-
for (const p of await listChildDirs(
|
|
3659
|
+
for (const m of await listChildDirs(join22(hostClaude, "plugins", "cache"))) {
|
|
3660
|
+
for (const p of await listChildDirs(join22(hostClaude, "plugins", "cache", m))) {
|
|
3279
3661
|
hostPluginKeys.push(`${m}/${p}`);
|
|
3280
3662
|
}
|
|
3281
3663
|
}
|
|
@@ -3283,8 +3665,8 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3283
3665
|
newItems.push({ category: "plugins", name: key });
|
|
3284
3666
|
applyPaths.push({ src: `/src/plugins/cache/${key}`, dest: `/dst/plugins/cache/${key}` });
|
|
3285
3667
|
}
|
|
3286
|
-
const hostInstalled = await readJsonFile(
|
|
3287
|
-
const hostMarkets = await readJsonFile(
|
|
3668
|
+
const hostInstalled = await readJsonFile(join22(hostClaude, "plugins", "installed_plugins.json"));
|
|
3669
|
+
const hostMarkets = await readJsonFile(join22(hostClaude, "plugins", "known_marketplaces.json"));
|
|
3288
3670
|
const mergedInstalled = mergeInstalledPlugins(hostInstalled, boxJson["installed_plugins"], {
|
|
3289
3671
|
hostHome
|
|
3290
3672
|
});
|
|
@@ -3327,17 +3709,17 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3327
3709
|
}
|
|
3328
3710
|
}
|
|
3329
3711
|
if (mergedMarkets.changed || mergedInstalled.changed) {
|
|
3330
|
-
await mkdir22(
|
|
3712
|
+
await mkdir22(join22(hostClaude, "plugins"), { recursive: true });
|
|
3331
3713
|
if (mergedMarkets.changed) {
|
|
3332
3714
|
await writeFile3(
|
|
3333
|
-
|
|
3715
|
+
join22(hostClaude, "plugins", "known_marketplaces.json"),
|
|
3334
3716
|
`${JSON.stringify(mergedMarkets.data, null, 2)}
|
|
3335
3717
|
`
|
|
3336
3718
|
);
|
|
3337
3719
|
}
|
|
3338
3720
|
if (mergedInstalled.changed) {
|
|
3339
3721
|
await writeFile3(
|
|
3340
|
-
|
|
3722
|
+
join22(hostClaude, "plugins", "installed_plugins.json"),
|
|
3341
3723
|
`${JSON.stringify(mergedInstalled.data, null, 2)}
|
|
3342
3724
|
`
|
|
3343
3725
|
);
|
|
@@ -3345,7 +3727,23 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3345
3727
|
}
|
|
3346
3728
|
return { newItems, mergedRegistries };
|
|
3347
3729
|
}
|
|
3348
|
-
var CREDENTIALS_BACKUP_FILE = join32(
|
|
3730
|
+
var CREDENTIALS_BACKUP_FILE = join32(STATE_DIR, "claude-credentials.json");
|
|
3731
|
+
var CODEX_CREDENTIALS_BACKUP_FILE = join32(STATE_DIR, "codex-credentials.json");
|
|
3732
|
+
var OPENCODE_CREDENTIALS_BACKUP_FILE = join32(STATE_DIR, "opencode-credentials.json");
|
|
3733
|
+
function isRealAgentCredential(agent, text) {
|
|
3734
|
+
let parsed;
|
|
3735
|
+
try {
|
|
3736
|
+
parsed = JSON.parse(text);
|
|
3737
|
+
} catch {
|
|
3738
|
+
return false;
|
|
3739
|
+
}
|
|
3740
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
3741
|
+
if (agent === "claude") {
|
|
3742
|
+
const rt = parsed.claudeAiOauth?.refreshToken;
|
|
3743
|
+
return typeof rt === "string" && rt.length > 0;
|
|
3744
|
+
}
|
|
3745
|
+
return Object.keys(parsed).length > 0;
|
|
3746
|
+
}
|
|
3349
3747
|
async function hostBackupHasCredentials(path = CREDENTIALS_BACKUP_FILE) {
|
|
3350
3748
|
try {
|
|
3351
3749
|
const parsed = JSON.parse(await readFile32(path, "utf8"));
|
|
@@ -3377,8 +3775,8 @@ echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
|
|
|
3377
3775
|
`;
|
|
3378
3776
|
async function syncClaudeCredentials(spec, opts) {
|
|
3379
3777
|
try {
|
|
3380
|
-
await mkdir32(
|
|
3381
|
-
const { stdout } = await
|
|
3778
|
+
await mkdir32(STATE_DIR, { recursive: true });
|
|
3779
|
+
const { stdout } = await execa4("docker", [
|
|
3382
3780
|
"run",
|
|
3383
3781
|
"--rm",
|
|
3384
3782
|
"--user",
|
|
@@ -3386,7 +3784,7 @@ async function syncClaudeCredentials(spec, opts) {
|
|
|
3386
3784
|
"-v",
|
|
3387
3785
|
`${spec.volume}:/dst`,
|
|
3388
3786
|
"-v",
|
|
3389
|
-
`${
|
|
3787
|
+
`${STATE_DIR}:/host-state`,
|
|
3390
3788
|
"-e",
|
|
3391
3789
|
`ISOLATE=${opts.isolate ? "yes" : "no"}`,
|
|
3392
3790
|
opts.image,
|
|
@@ -3529,9 +3927,10 @@ ${(install.stdout ?? "").toString().slice(-600)}`
|
|
|
3529
3927
|
}
|
|
3530
3928
|
return { installed: true };
|
|
3531
3929
|
}
|
|
3930
|
+
var CODEX_AGENTBOX_FLAGS = ["--enable", "hooks", "--dangerously-bypass-hook-trust"];
|
|
3532
3931
|
async function startCodexSession(opts) {
|
|
3533
3932
|
const sessionName = opts.sessionName ?? DEFAULT_CODEX_SESSION;
|
|
3534
|
-
const cmd = ["codex", ...opts.codexArgs].map(shQuote2).join(" ");
|
|
3933
|
+
const cmd = ["codex", ...CODEX_AGENTBOX_FLAGS, ...opts.codexArgs].map(shQuote2).join(" ");
|
|
3535
3934
|
const term = process.env["TERM"] ?? "xterm-256color";
|
|
3536
3935
|
const envFlags = ["-e", `TERM=${term}`];
|
|
3537
3936
|
for (const k of CODEX_FORWARDED_ENV_KEYS) {
|
|
@@ -3726,6 +4125,8 @@ var SHARED_OPENCODE_VOLUME = "agentbox-opencode-config";
|
|
|
3726
4125
|
var DEFAULT_OPENCODE_SESSION = "opencode";
|
|
3727
4126
|
var CONTAINER_OPENCODE_DIR = "/home/vscode/.local/share/opencode";
|
|
3728
4127
|
var CONTAINER_OPENCODE_CONFIG_DIR = "/home/vscode/.local/share/opencode/config";
|
|
4128
|
+
var CONTAINER_OPENCODE_STATE_HOME = "/home/vscode/.local/share/opencode/.state";
|
|
4129
|
+
var IN_BOX_OPENCODE_PLUGIN_PATH = "/usr/local/share/agentbox/opencode-agentbox-plugin.js";
|
|
3729
4130
|
function resolveOpencodeVolume(opts) {
|
|
3730
4131
|
if (opts.isolate) {
|
|
3731
4132
|
return { volume: `${SHARED_OPENCODE_VOLUME}-${opts.boxId}` };
|
|
@@ -3751,13 +4152,16 @@ async function ensureOpencodeVolume(spec, opts) {
|
|
|
3751
4152
|
const created = !existed;
|
|
3752
4153
|
const hostData = join5(homedir4(), ".local", "share", "opencode");
|
|
3753
4154
|
const hostConfig = join5(homedir4(), ".config", "opencode");
|
|
4155
|
+
const hostState = join5(homedir4(), ".local", "state", "opencode");
|
|
3754
4156
|
const hasData = await pathExists3(hostData);
|
|
3755
4157
|
const hasConfig = await pathExists3(hostConfig);
|
|
3756
|
-
const
|
|
4158
|
+
const hasState = await pathExists3(hostState);
|
|
4159
|
+
const willSync = opts.syncFromHost && (hasData || hasConfig || hasState);
|
|
3757
4160
|
if (willSync) {
|
|
3758
4161
|
const args = ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/dst`];
|
|
3759
4162
|
if (hasData) args.push("-v", `${hostData}:/src-data:ro`);
|
|
3760
4163
|
if (hasConfig) args.push("-v", `${hostConfig}:/src-config:ro`);
|
|
4164
|
+
if (hasState) args.push("-v", `${hostState}:/src-state:ro`);
|
|
3761
4165
|
const steps = [];
|
|
3762
4166
|
if (hasData) {
|
|
3763
4167
|
steps.push(
|
|
@@ -3767,6 +4171,11 @@ async function ensureOpencodeVolume(spec, opts) {
|
|
|
3767
4171
|
if (hasConfig) {
|
|
3768
4172
|
steps.push("mkdir -p /dst/config && rsync -a /src-config/ /dst/config/");
|
|
3769
4173
|
}
|
|
4174
|
+
if (hasState) {
|
|
4175
|
+
steps.push(
|
|
4176
|
+
"mkdir -p /dst/.state/opencode && rsync -a --update --exclude=locks /src-state/ /dst/.state/opencode/"
|
|
4177
|
+
);
|
|
4178
|
+
}
|
|
3770
4179
|
steps.push("chown -R 1000:1000 /dst");
|
|
3771
4180
|
args.push(opts.image, "sh", "-c", steps.join(" && "));
|
|
3772
4181
|
await execa6("docker", args);
|
|
@@ -3790,6 +4199,25 @@ async function ensureOpencodeVolume(spec, opts) {
|
|
|
3790
4199
|
);
|
|
3791
4200
|
return { created, synced: false };
|
|
3792
4201
|
}
|
|
4202
|
+
async function seedOpencodePlugin(volume, image) {
|
|
4203
|
+
try {
|
|
4204
|
+
const { stdout } = await execa6("docker", [
|
|
4205
|
+
"run",
|
|
4206
|
+
"--rm",
|
|
4207
|
+
"--user",
|
|
4208
|
+
"0",
|
|
4209
|
+
"-v",
|
|
4210
|
+
`${volume}:/dst`,
|
|
4211
|
+
image,
|
|
4212
|
+
"sh",
|
|
4213
|
+
"-c",
|
|
4214
|
+
`{ [ -f ${IN_BOX_OPENCODE_PLUGIN_PATH} ] && mkdir -p /dst/config/plugins && cp -a ${IN_BOX_OPENCODE_PLUGIN_PATH} /dst/config/plugins/agentbox-state.js && chown -R 1000:1000 /dst/config/plugins && echo SEEDED; } || true`
|
|
4215
|
+
]);
|
|
4216
|
+
return { seeded: stdout.includes("SEEDED") };
|
|
4217
|
+
} catch {
|
|
4218
|
+
return { seeded: false };
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
3793
4221
|
var OPENCODE_FORWARDED_ENV_KEYS = [
|
|
3794
4222
|
"ANTHROPIC_API_KEY",
|
|
3795
4223
|
"OPENAI_API_KEY",
|
|
@@ -3800,7 +4228,10 @@ var OPENCODE_FORWARDED_ENV_KEYS = [
|
|
|
3800
4228
|
"GROQ_API_KEY"
|
|
3801
4229
|
];
|
|
3802
4230
|
function buildOpencodeMounts(spec, hostEnv) {
|
|
3803
|
-
const env = {
|
|
4231
|
+
const env = {
|
|
4232
|
+
OPENCODE_CONFIG_DIR: CONTAINER_OPENCODE_CONFIG_DIR,
|
|
4233
|
+
XDG_STATE_HOME: CONTAINER_OPENCODE_STATE_HOME
|
|
4234
|
+
};
|
|
3804
4235
|
for (const k of OPENCODE_FORWARDED_ENV_KEYS) {
|
|
3805
4236
|
const v = hostEnv[k];
|
|
3806
4237
|
if (typeof v === "string" && v.length > 0) env[k] = v;
|
|
@@ -3915,6 +4346,8 @@ function buildOpencodeLoginRunArgv(opts) {
|
|
|
3915
4346
|
"DISPLAY=",
|
|
3916
4347
|
"-e",
|
|
3917
4348
|
`OPENCODE_CONFIG_DIR=${CONTAINER_OPENCODE_CONFIG_DIR}`,
|
|
4349
|
+
"-e",
|
|
4350
|
+
`XDG_STATE_HOME=${CONTAINER_OPENCODE_STATE_HOME}`,
|
|
3918
4351
|
"-v",
|
|
3919
4352
|
`${opts.volume}:${CONTAINER_OPENCODE_DIR}`,
|
|
3920
4353
|
"--user",
|
|
@@ -4105,7 +4538,7 @@ async function launchVncDaemon(container, timeoutMs = 5e3) {
|
|
|
4105
4538
|
}
|
|
4106
4539
|
var VNC_PASSWORD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
4107
4540
|
function generateVncPassword() {
|
|
4108
|
-
const bytes =
|
|
4541
|
+
const bytes = randomBytes2(8);
|
|
4109
4542
|
let out = "";
|
|
4110
4543
|
for (let i = 0; i < 8; i++) {
|
|
4111
4544
|
out += VNC_PASSWORD_ALPHABET[bytes[i] % VNC_PASSWORD_ALPHABET.length];
|
|
@@ -4124,6 +4557,10 @@ function buildVncUrls(record, engine) {
|
|
|
4124
4557
|
if (record.vncHostPort) {
|
|
4125
4558
|
urls.loopbackUrl = `http://127.0.0.1:${String(record.vncHostPort)}/vnc.html?${qs}`;
|
|
4126
4559
|
}
|
|
4560
|
+
if (record.portlessVncAlias) {
|
|
4561
|
+
const base = record.portlessVncUrl ?? `https://${record.portlessVncAlias}.localhost`;
|
|
4562
|
+
urls.portlessUrl = `${base}/vnc.html?${qs}`;
|
|
4563
|
+
}
|
|
4127
4564
|
return urls;
|
|
4128
4565
|
}
|
|
4129
4566
|
var WEB_CONTAINER_PORT = 80;
|
|
@@ -4206,23 +4643,11 @@ async function seedWorkspace(opts) {
|
|
|
4206
4643
|
for (const r of opts.repos) {
|
|
4207
4644
|
const main = r.repo.hostMainRepo;
|
|
4208
4645
|
const wt = r.gitWorktreePath;
|
|
4646
|
+
const baseRef = r.repo.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
|
|
4647
|
+
const addArgs = r.reuseBranch ? ["worktree", "add", wt, r.branch] : ["worktree", "add", "-b", r.branch, wt, baseRef];
|
|
4209
4648
|
const add = await execa7(
|
|
4210
4649
|
"docker",
|
|
4211
|
-
[
|
|
4212
|
-
"exec",
|
|
4213
|
-
"--user",
|
|
4214
|
-
"vscode",
|
|
4215
|
-
opts.container,
|
|
4216
|
-
"git",
|
|
4217
|
-
"-C",
|
|
4218
|
-
main,
|
|
4219
|
-
"worktree",
|
|
4220
|
-
"add",
|
|
4221
|
-
"-b",
|
|
4222
|
-
r.branch,
|
|
4223
|
-
wt,
|
|
4224
|
-
"HEAD"
|
|
4225
|
-
],
|
|
4650
|
+
["exec", "--user", "vscode", opts.container, "git", "-C", main, ...addArgs],
|
|
4226
4651
|
{ reject: false }
|
|
4227
4652
|
);
|
|
4228
4653
|
if (add.exitCode !== 0) {
|
|
@@ -4515,79 +4940,6 @@ async function isProxyRunning() {
|
|
|
4515
4940
|
return false;
|
|
4516
4941
|
}
|
|
4517
4942
|
}
|
|
4518
|
-
var DEFAULT_BOX_IMAGE = "agentbox/box:dev";
|
|
4519
|
-
var here = dirname4(fileURLToPath(import.meta.url));
|
|
4520
|
-
function resolveDockerBuild() {
|
|
4521
|
-
const override = process.env.AGENTBOX_DOCKER_CONTEXT;
|
|
4522
|
-
if (override && existsSync2(resolve2(override, "Dockerfile.box"))) {
|
|
4523
|
-
return { dockerfile: resolve2(override, "Dockerfile.box"), context: override };
|
|
4524
|
-
}
|
|
4525
|
-
const staged = resolve2(here, "..", "runtime", "docker");
|
|
4526
|
-
if (existsSync2(resolve2(staged, "Dockerfile.box"))) {
|
|
4527
|
-
return { dockerfile: resolve2(staged, "Dockerfile.box"), context: staged };
|
|
4528
|
-
}
|
|
4529
|
-
const packageRoot = resolve2(here, "..");
|
|
4530
|
-
return {
|
|
4531
|
-
dockerfile: resolve2(packageRoot, "Dockerfile.box"),
|
|
4532
|
-
context: resolve2(packageRoot, "..", "..")
|
|
4533
|
-
};
|
|
4534
|
-
}
|
|
4535
|
-
var { dockerfile: DOCKERFILE_PATH_RESOLVED, context: BUILD_CONTEXT_DIR_RESOLVED } = resolveDockerBuild();
|
|
4536
|
-
var DOCKERFILE_PATH = DOCKERFILE_PATH_RESOLVED;
|
|
4537
|
-
var BUILD_CONTEXT_DIR = BUILD_CONTEXT_DIR_RESOLVED;
|
|
4538
|
-
async function imageExists(ref) {
|
|
4539
|
-
const result = await execa9("docker", ["image", "inspect", ref], { reject: false });
|
|
4540
|
-
return result.exitCode === 0;
|
|
4541
|
-
}
|
|
4542
|
-
async function imageInfo(ref = DEFAULT_BOX_IMAGE) {
|
|
4543
|
-
const result = await execa9(
|
|
4544
|
-
"docker",
|
|
4545
|
-
["image", "inspect", "--format", "{{.Size}}|{{.Created}}", ref],
|
|
4546
|
-
{ reject: false }
|
|
4547
|
-
);
|
|
4548
|
-
if (result.exitCode !== 0) return { ref, exists: false };
|
|
4549
|
-
const [sizeStr, createdAt] = result.stdout.trim().split("|");
|
|
4550
|
-
const sizeBytes = sizeStr ? Number.parseInt(sizeStr, 10) : NaN;
|
|
4551
|
-
return {
|
|
4552
|
-
ref,
|
|
4553
|
-
exists: true,
|
|
4554
|
-
sizeBytes: Number.isFinite(sizeBytes) ? sizeBytes : void 0,
|
|
4555
|
-
createdAt: createdAt && createdAt.length > 0 ? createdAt : void 0
|
|
4556
|
-
};
|
|
4557
|
-
}
|
|
4558
|
-
async function buildImage(opts = {}) {
|
|
4559
|
-
const ref = opts.ref ?? DEFAULT_BOX_IMAGE;
|
|
4560
|
-
const dockerfile = opts.dockerfile ?? DOCKERFILE_PATH;
|
|
4561
|
-
const contextDir = opts.contextDir ?? BUILD_CONTEXT_DIR;
|
|
4562
|
-
const subprocess = execa9("docker", ["build", "-t", ref, "-f", dockerfile, contextDir], {
|
|
4563
|
-
stderr: "pipe",
|
|
4564
|
-
stdout: "pipe"
|
|
4565
|
-
});
|
|
4566
|
-
if (opts.onProgress) {
|
|
4567
|
-
const forward = (chunk) => {
|
|
4568
|
-
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
4569
|
-
for (const line of text.split(/\r?\n/)) {
|
|
4570
|
-
if (line.length > 0) opts.onProgress?.(line);
|
|
4571
|
-
}
|
|
4572
|
-
};
|
|
4573
|
-
subprocess.stdout?.on("data", forward);
|
|
4574
|
-
subprocess.stderr?.on("data", forward);
|
|
4575
|
-
}
|
|
4576
|
-
await subprocess;
|
|
4577
|
-
return ref;
|
|
4578
|
-
}
|
|
4579
|
-
async function ensureImage(ref = DEFAULT_BOX_IMAGE, opts = {}) {
|
|
4580
|
-
if (await imageExists(ref)) {
|
|
4581
|
-
return { ref, built: false };
|
|
4582
|
-
}
|
|
4583
|
-
await buildImage({
|
|
4584
|
-
ref,
|
|
4585
|
-
dockerfile: opts.dockerfile,
|
|
4586
|
-
contextDir: opts.contextDir,
|
|
4587
|
-
onProgress: opts.onProgress
|
|
4588
|
-
});
|
|
4589
|
-
return { ref, built: true };
|
|
4590
|
-
}
|
|
4591
4943
|
var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
4592
4944
|
"node_modules",
|
|
4593
4945
|
".next",
|
|
@@ -4633,12 +4985,12 @@ async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
|
|
|
4633
4985
|
return matches;
|
|
4634
4986
|
}
|
|
4635
4987
|
async function createSnapshot(opts) {
|
|
4636
|
-
const source =
|
|
4637
|
-
const destination =
|
|
4988
|
+
const source = resolve2(opts.source);
|
|
4989
|
+
const destination = resolve2(opts.destination);
|
|
4638
4990
|
const excluded = opts.excluded ?? EXCLUDE_DIRS;
|
|
4639
4991
|
await mkdir4(SNAPSHOTS_ROOT, { recursive: true });
|
|
4640
4992
|
const cpArgs = platform() === "darwin" ? ["-cR"] : ["-R"];
|
|
4641
|
-
await
|
|
4993
|
+
await execa9("cp", [...cpArgs, `${source}/`, destination]);
|
|
4642
4994
|
const toPrune = await findExcludedDirs(destination, excluded);
|
|
4643
4995
|
await Promise.all(toPrune.map((p) => rm22(p, { recursive: true, force: true })));
|
|
4644
4996
|
return { destination, prunedPaths: toPrune };
|
|
@@ -4659,7 +5011,7 @@ async function readManifest(dir) {
|
|
|
4659
5011
|
try {
|
|
4660
5012
|
const raw = await readFile5(join8(dir, "manifest.json"), "utf8");
|
|
4661
5013
|
const m = JSON.parse(raw);
|
|
4662
|
-
if (m.schema !== 2) return null;
|
|
5014
|
+
if (m.schema !== 2 && m.schema !== 3) return null;
|
|
4663
5015
|
return m;
|
|
4664
5016
|
} catch {
|
|
4665
5017
|
return null;
|
|
@@ -4747,7 +5099,7 @@ async function runCleanup(container, log) {
|
|
|
4747
5099
|
}
|
|
4748
5100
|
}
|
|
4749
5101
|
async function inspectImageConfig(imageRef) {
|
|
4750
|
-
const r = await
|
|
5102
|
+
const r = await execa10("docker", ["image", "inspect", imageRef], { reject: false });
|
|
4751
5103
|
if (r.exitCode !== 0) {
|
|
4752
5104
|
throw new CheckpointError(`docker image inspect ${imageRef} failed`, r.stdout, r.stderr);
|
|
4753
5105
|
}
|
|
@@ -4823,14 +5175,14 @@ async function createCheckpoint(opts) {
|
|
|
4823
5175
|
await runCleanup(box.container, log);
|
|
4824
5176
|
if (type === "layered") {
|
|
4825
5177
|
log(`docker commit ${box.container} -> ${tag} (layered)`);
|
|
4826
|
-
const r = await
|
|
5178
|
+
const r = await execa10("docker", ["commit", box.container, tag], { reject: false });
|
|
4827
5179
|
if (r.exitCode !== 0) {
|
|
4828
5180
|
throw new CheckpointError(`docker commit failed for ${box.container}`, r.stdout, r.stderr);
|
|
4829
5181
|
}
|
|
4830
5182
|
} else {
|
|
4831
5183
|
log(`docker commit ${box.container} -> <intermediate> (flattened path)`);
|
|
4832
5184
|
const intermediate = `${tag}-intermediate`;
|
|
4833
|
-
const commit = await
|
|
5185
|
+
const commit = await execa10("docker", ["commit", box.container, intermediate], {
|
|
4834
5186
|
reject: false
|
|
4835
5187
|
});
|
|
4836
5188
|
if (commit.exitCode !== 0) {
|
|
@@ -4843,8 +5195,15 @@ async function createCheckpoint(opts) {
|
|
|
4843
5195
|
}
|
|
4844
5196
|
}
|
|
4845
5197
|
const base = (box.gitWorktrees ?? []).some((w) => w.kind === "root") ? "worktree" : "workspace";
|
|
5198
|
+
const prepared = readPreparedDockerState();
|
|
5199
|
+
let baseFingerprint = prepared?.base?.contextSha256;
|
|
5200
|
+
if (!baseFingerprint) {
|
|
5201
|
+
const fp = await computeDockerContextFingerprint();
|
|
5202
|
+
baseFingerprint = fp?.contextSha256;
|
|
5203
|
+
}
|
|
5204
|
+
const stamp = readCliStamp();
|
|
4846
5205
|
const manifest = {
|
|
4847
|
-
schema:
|
|
5206
|
+
schema: 3,
|
|
4848
5207
|
name,
|
|
4849
5208
|
type,
|
|
4850
5209
|
image: tag,
|
|
@@ -4854,6 +5213,9 @@ async function createCheckpoint(opts) {
|
|
|
4854
5213
|
sourceBoxId: box.id,
|
|
4855
5214
|
sourceBoxName: box.name,
|
|
4856
5215
|
worktrees: box.gitWorktrees,
|
|
5216
|
+
baseProvider: "docker",
|
|
5217
|
+
baseFingerprint,
|
|
5218
|
+
cliVersion: stamp.cliVersion,
|
|
4857
5219
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4858
5220
|
};
|
|
4859
5221
|
await writeFile22(join8(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
@@ -4865,7 +5227,7 @@ async function createCheckpoint(opts) {
|
|
|
4865
5227
|
}
|
|
4866
5228
|
async function flattenImage(sourceTag, destTag, log) {
|
|
4867
5229
|
const tmpName = `agentbox-flatten-${Date.now().toString(36)}`;
|
|
4868
|
-
const create = await
|
|
5230
|
+
const create = await execa10(
|
|
4869
5231
|
"docker",
|
|
4870
5232
|
["create", "--name", tmpName, sourceTag, "sleep", "0"],
|
|
4871
5233
|
{ reject: false }
|
|
@@ -4877,7 +5239,7 @@ async function flattenImage(sourceTag, destTag, log) {
|
|
|
4877
5239
|
try {
|
|
4878
5240
|
const rootfsPath = join8(scratch, "rootfs.tar");
|
|
4879
5241
|
log(`exporting rootfs of ${sourceTag} to ${rootfsPath}`);
|
|
4880
|
-
const exp = await
|
|
5242
|
+
const exp = await execa10("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
|
|
4881
5243
|
if (exp.exitCode !== 0) {
|
|
4882
5244
|
throw new CheckpointError(`docker export failed`, exp.stdout, exp.stderr);
|
|
4883
5245
|
}
|
|
@@ -4890,7 +5252,7 @@ async function flattenImage(sourceTag, destTag, log) {
|
|
|
4890
5252
|
];
|
|
4891
5253
|
await writeFile22(join8(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
|
|
4892
5254
|
log(`building flattened ${destTag} from rootfs.tar (FROM scratch)`);
|
|
4893
|
-
const build = await
|
|
5255
|
+
const build = await execa10(
|
|
4894
5256
|
"docker",
|
|
4895
5257
|
["build", "-t", destTag, "-f", join8(scratch, "Dockerfile"), scratch],
|
|
4896
5258
|
{ reject: false }
|
|
@@ -4899,7 +5261,7 @@ async function flattenImage(sourceTag, destTag, log) {
|
|
|
4899
5261
|
throw new CheckpointError(`flatten docker build failed`, build.stdout, build.stderr);
|
|
4900
5262
|
}
|
|
4901
5263
|
} finally {
|
|
4902
|
-
await
|
|
5264
|
+
await execa10("docker", ["rm", "-f", tmpName], { reject: false });
|
|
4903
5265
|
await rm3(scratch, { recursive: true, force: true });
|
|
4904
5266
|
}
|
|
4905
5267
|
}
|
|
@@ -4943,7 +5305,7 @@ async function pathExists4(p) {
|
|
|
4943
5305
|
}
|
|
4944
5306
|
async function writeBoxEnvFile(container, env) {
|
|
4945
5307
|
const body = formatBoxEnvBody(env);
|
|
4946
|
-
const result = await
|
|
5308
|
+
const result = await execa11(
|
|
4947
5309
|
"docker",
|
|
4948
5310
|
["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
|
|
4949
5311
|
{ input: body, reject: false }
|
|
@@ -4967,7 +5329,7 @@ function shellSingleQuote(s) {
|
|
|
4967
5329
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4968
5330
|
}
|
|
4969
5331
|
async function ensureHomeOwnedByVscode(container) {
|
|
4970
|
-
await
|
|
5332
|
+
await execa12(
|
|
4971
5333
|
"docker",
|
|
4972
5334
|
[
|
|
4973
5335
|
"exec",
|
|
@@ -5004,25 +5366,72 @@ async function ensureRelay(opts = {}) {
|
|
|
5004
5366
|
await removeContainer(RELAY_CONTAINER_NAME);
|
|
5005
5367
|
log(`removed legacy relay container ${RELAY_CONTAINER_NAME}`);
|
|
5006
5368
|
}
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5369
|
+
const health = await fetchHealthz(500);
|
|
5370
|
+
if (health !== null) {
|
|
5371
|
+
if (health.cliEntry !== false) {
|
|
5372
|
+
return ENDPOINT;
|
|
5373
|
+
}
|
|
5374
|
+
log("relay is alive but lacks AGENTBOX_CLI_ENTRY (cp/download/checkpoint would fail) \u2014 reclaiming");
|
|
5375
|
+
await reclaimRelay(health.pid, log);
|
|
5376
|
+
} else {
|
|
5377
|
+
const existingPid = await readPidFile();
|
|
5378
|
+
if (existingPid !== null && await processAlive(existingPid)) {
|
|
5379
|
+
for (let i = 0; i < 10; i++) {
|
|
5380
|
+
if (await pingHealthz(300)) return ENDPOINT;
|
|
5381
|
+
await delay2(200);
|
|
5382
|
+
}
|
|
5383
|
+
log(`relay pid ${String(existingPid)} alive but /healthz unresponsive \u2014 proceeding anyway`);
|
|
5384
|
+
return ENDPOINT;
|
|
5385
|
+
}
|
|
5386
|
+
if (existingPid !== null) {
|
|
5387
|
+
await unlink2(PID_FILE).catch(() => {
|
|
5388
|
+
});
|
|
5015
5389
|
}
|
|
5016
|
-
log(`relay pid ${String(existingPid)} alive but /healthz unresponsive \u2014 proceeding anyway`);
|
|
5017
|
-
return ENDPOINT;
|
|
5018
|
-
}
|
|
5019
|
-
if (existingPid !== null) {
|
|
5020
|
-
await unlink(PID_FILE).catch(() => {
|
|
5021
|
-
});
|
|
5022
5390
|
}
|
|
5023
5391
|
const relayBin = resolveRelayBin();
|
|
5024
|
-
const logFd = openSync(LOG_FILE, "a");
|
|
5025
5392
|
const cliEntry = resolveCliEntry();
|
|
5393
|
+
if (cliEntry === null) {
|
|
5394
|
+
throw new Error(
|
|
5395
|
+
"cannot start the host relay: agentbox CLI entry not found (is the build complete / dist present?). Set AGENTBOX_CLI_ENTRY to override."
|
|
5396
|
+
);
|
|
5397
|
+
}
|
|
5398
|
+
return spawnRelay(relayBin, cliEntry, log);
|
|
5399
|
+
}
|
|
5400
|
+
async function reclaimRelay(reportedPid, log) {
|
|
5401
|
+
const pidFromFile = await readPidFile();
|
|
5402
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5403
|
+
for (const pid of [reportedPid, pidFromFile]) {
|
|
5404
|
+
if (typeof pid !== "number" || pid <= 0 || seen.has(pid)) continue;
|
|
5405
|
+
seen.add(pid);
|
|
5406
|
+
if (!await processAlive(pid)) continue;
|
|
5407
|
+
log(`stopping crippled relay pid ${String(pid)}`);
|
|
5408
|
+
await killPid(pid);
|
|
5409
|
+
}
|
|
5410
|
+
await unlink2(PID_FILE).catch(() => {
|
|
5411
|
+
});
|
|
5412
|
+
if (await pingHealthz(300)) {
|
|
5413
|
+
throw new Error(
|
|
5414
|
+
`a relay without AGENTBOX_CLI_ENTRY is still listening on :${String(PORT)} and could not be stopped (reported pid ${String(reportedPid ?? "unknown")}); kill it manually and retry`
|
|
5415
|
+
);
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
async function killPid(pid) {
|
|
5419
|
+
try {
|
|
5420
|
+
process.kill(pid, "SIGTERM");
|
|
5421
|
+
} catch {
|
|
5422
|
+
return;
|
|
5423
|
+
}
|
|
5424
|
+
for (let i = 0; i < 20; i++) {
|
|
5425
|
+
if (!await processAlive(pid)) return;
|
|
5426
|
+
await delay2(100);
|
|
5427
|
+
}
|
|
5428
|
+
try {
|
|
5429
|
+
process.kill(pid, "SIGKILL");
|
|
5430
|
+
} catch {
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
async function spawnRelay(relayBin, cliEntry, log) {
|
|
5434
|
+
const logFd = openSync(LOG_FILE, "a");
|
|
5026
5435
|
const child = spawn(
|
|
5027
5436
|
process.execPath,
|
|
5028
5437
|
[relayBin, "serve", "--port", String(PORT), "--host", "0.0.0.0"],
|
|
@@ -5031,7 +5440,7 @@ async function ensureRelay(opts = {}) {
|
|
|
5031
5440
|
stdio: ["ignore", logFd, logFd],
|
|
5032
5441
|
env: {
|
|
5033
5442
|
...process.env,
|
|
5034
|
-
|
|
5443
|
+
AGENTBOX_CLI_ENTRY: cliEntry
|
|
5035
5444
|
}
|
|
5036
5445
|
}
|
|
5037
5446
|
);
|
|
@@ -5053,16 +5462,16 @@ async function ensureRelay(opts = {}) {
|
|
|
5053
5462
|
}
|
|
5054
5463
|
function resolveRelayBin() {
|
|
5055
5464
|
const override = process.env.AGENTBOX_RELAY_BIN;
|
|
5056
|
-
if (override &&
|
|
5057
|
-
const
|
|
5465
|
+
if (override && existsSync2(override)) return override;
|
|
5466
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
5058
5467
|
const candidates = [
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5468
|
+
resolve22(here, "..", "runtime", "relay", "bin.cjs"),
|
|
5469
|
+
resolve22(here, "..", "..", "relay", "dist", "bin.cjs"),
|
|
5470
|
+
resolve22(here, "..", "..", "..", "@agentbox", "relay", "dist", "bin.cjs"),
|
|
5471
|
+
resolve22(here, "..", "..", "node_modules", "@agentbox", "relay", "dist", "bin.cjs")
|
|
5063
5472
|
];
|
|
5064
5473
|
for (const c of candidates) {
|
|
5065
|
-
if (
|
|
5474
|
+
if (existsSync2(c)) return c;
|
|
5066
5475
|
}
|
|
5067
5476
|
throw new Error(
|
|
5068
5477
|
`could not locate @agentbox/relay bin; tried:
|
|
@@ -5071,17 +5480,17 @@ function resolveRelayBin() {
|
|
|
5071
5480
|
}
|
|
5072
5481
|
function resolveCliEntry() {
|
|
5073
5482
|
const override = process.env.AGENTBOX_CLI_ENTRY;
|
|
5074
|
-
if (override &&
|
|
5075
|
-
const
|
|
5483
|
+
if (override && existsSync2(override)) return override;
|
|
5484
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
5076
5485
|
const candidates = [
|
|
5077
5486
|
// Bundled CLI (dev + published): this module IS bundled into the CLI
|
|
5078
5487
|
// entry, so the entry is index.js next to this file.
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5488
|
+
resolve22(here, "index.js"),
|
|
5489
|
+
resolve22(here, "..", "..", "..", "apps", "cli", "dist", "index.js"),
|
|
5490
|
+
resolve22(here, "..", "..", "..", "..", "dist", "index.js")
|
|
5082
5491
|
];
|
|
5083
5492
|
for (const c of candidates) {
|
|
5084
|
-
if (
|
|
5493
|
+
if (existsSync2(c)) return c;
|
|
5085
5494
|
}
|
|
5086
5495
|
return null;
|
|
5087
5496
|
}
|
|
@@ -5091,7 +5500,7 @@ async function stopRelay() {
|
|
|
5091
5500
|
return { stopped: false, pid: null };
|
|
5092
5501
|
}
|
|
5093
5502
|
if (!await processAlive(pid)) {
|
|
5094
|
-
await
|
|
5503
|
+
await unlink2(PID_FILE).catch(() => {
|
|
5095
5504
|
});
|
|
5096
5505
|
return { stopped: false, pid };
|
|
5097
5506
|
}
|
|
@@ -5109,7 +5518,7 @@ async function stopRelay() {
|
|
|
5109
5518
|
} catch {
|
|
5110
5519
|
}
|
|
5111
5520
|
}
|
|
5112
|
-
await
|
|
5521
|
+
await unlink2(PID_FILE).catch(() => {
|
|
5113
5522
|
});
|
|
5114
5523
|
return { stopped: true, pid };
|
|
5115
5524
|
}
|
|
@@ -5163,7 +5572,13 @@ function fetchHealthz(timeoutMs) {
|
|
|
5163
5572
|
try {
|
|
5164
5573
|
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
5165
5574
|
if (typeof parsed.ok === "boolean" && typeof parsed.boxes === "number" && typeof parsed.events === "number") {
|
|
5166
|
-
resolveP({
|
|
5575
|
+
resolveP({
|
|
5576
|
+
ok: parsed.ok,
|
|
5577
|
+
boxes: parsed.boxes,
|
|
5578
|
+
events: parsed.events,
|
|
5579
|
+
pid: typeof parsed.pid === "number" ? parsed.pid : void 0,
|
|
5580
|
+
cliEntry: typeof parsed.cliEntry === "boolean" ? parsed.cliEntry : void 0
|
|
5581
|
+
});
|
|
5167
5582
|
} else {
|
|
5168
5583
|
resolveP(null);
|
|
5169
5584
|
}
|
|
@@ -5200,7 +5615,7 @@ async function processAlive(pid) {
|
|
|
5200
5615
|
}
|
|
5201
5616
|
}
|
|
5202
5617
|
function generateRelayToken() {
|
|
5203
|
-
return
|
|
5618
|
+
return randomBytes22(32).toString("hex");
|
|
5204
5619
|
}
|
|
5205
5620
|
async function registerBoxWithRelay(args) {
|
|
5206
5621
|
const worktrees = (args.worktrees ?? []).map((w) => ({
|
|
@@ -5249,6 +5664,20 @@ async function clearRelayNotice(boxId, id) {
|
|
|
5249
5664
|
} catch {
|
|
5250
5665
|
}
|
|
5251
5666
|
}
|
|
5667
|
+
async function mintHostInitiatedToken(boxId, method, paramsHash, ttlMs) {
|
|
5668
|
+
try {
|
|
5669
|
+
const body = await adminPostForJson("/admin/host-initiated/mint", {
|
|
5670
|
+
boxId,
|
|
5671
|
+
method,
|
|
5672
|
+
paramsHash,
|
|
5673
|
+
...typeof ttlMs === "number" ? { ttlMs } : {}
|
|
5674
|
+
});
|
|
5675
|
+
const token = body?.token;
|
|
5676
|
+
return typeof token === "string" && token.length > 0 ? token : null;
|
|
5677
|
+
} catch {
|
|
5678
|
+
return null;
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5252
5681
|
async function adminPost(path, body) {
|
|
5253
5682
|
const json = JSON.stringify(body);
|
|
5254
5683
|
await new Promise((resolveP, rejectP) => {
|
|
@@ -5501,7 +5930,7 @@ function generateBoxId() {
|
|
|
5501
5930
|
return randomBytes3(4).toString("hex");
|
|
5502
5931
|
}
|
|
5503
5932
|
function sanitizeBasename(workspacePath) {
|
|
5504
|
-
const raw = basename2(
|
|
5933
|
+
const raw = basename2(resolve3(workspacePath));
|
|
5505
5934
|
return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
|
|
5506
5935
|
}
|
|
5507
5936
|
function defaultBoxName(workspacePath, id) {
|
|
@@ -5532,7 +5961,7 @@ async function buildIdentityMounts() {
|
|
|
5532
5961
|
async function createBox(opts) {
|
|
5533
5962
|
const log = opts.onLog ?? (() => {
|
|
5534
5963
|
});
|
|
5535
|
-
const workspace =
|
|
5964
|
+
const workspace = resolve3(opts.workspacePath);
|
|
5536
5965
|
if (!await pathExists5(workspace)) {
|
|
5537
5966
|
throw new Error(`workspace does not exist: ${workspace}`);
|
|
5538
5967
|
}
|
|
@@ -5567,6 +5996,27 @@ async function createBox(opts) {
|
|
|
5567
5996
|
log(
|
|
5568
5997
|
`starting from checkpoint ${opts.checkpointRef} (${head.manifest.type}, ${String(chain.length)} layer(s), image ${head.manifest.image})`
|
|
5569
5998
|
);
|
|
5999
|
+
const ckptFingerprint = head.manifest.baseFingerprint;
|
|
6000
|
+
const ckptCliVersion = head.manifest.cliVersion ?? "unknown";
|
|
6001
|
+
if (head.manifest.schema === 2) {
|
|
6002
|
+
log(
|
|
6003
|
+
`WARNING: checkpoint '${opts.checkpointRef}' was captured before checkpoint versioning landed.
|
|
6004
|
+
Its base-image layers may be older than your current base image. If the box is missing
|
|
6005
|
+
expected updates, remove the checkpoint with \`agentbox checkpoint rm ${opts.checkpointRef}\` and recreate it.`
|
|
6006
|
+
);
|
|
6007
|
+
} else {
|
|
6008
|
+
const prepared = readPreparedDockerState();
|
|
6009
|
+
const currentFingerprint = prepared?.base?.contextSha256 ?? (await computeDockerContextFingerprint())?.contextSha256;
|
|
6010
|
+
if (ckptFingerprint && currentFingerprint && ckptFingerprint !== currentFingerprint) {
|
|
6011
|
+
log(
|
|
6012
|
+
`WARNING: checkpoint '${opts.checkpointRef}' was captured against an older base image.
|
|
6013
|
+
captured: cli ${ckptCliVersion}, fingerprint ${ckptFingerprint.slice(0, 12)}
|
|
6014
|
+
current : cli ${prepared?.base?.cliVersion ?? "unknown"}, fingerprint ${currentFingerprint.slice(0, 12)}
|
|
6015
|
+
The restored box will keep the old base layers and will NOT include base-image updates.
|
|
6016
|
+
To pick up updates: \`agentbox checkpoint rm ${opts.checkpointRef}\` and recreate from a fresh box.`
|
|
6017
|
+
);
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
5570
6020
|
}
|
|
5571
6021
|
const imageRef = checkpointImage ?? opts.image ?? DEFAULT_BOX_IMAGE;
|
|
5572
6022
|
const ensureRef = checkpointImage ? opts.image ?? DEFAULT_BOX_IMAGE : imageRef;
|
|
@@ -5607,9 +6057,33 @@ async function createBox(opts) {
|
|
|
5607
6057
|
);
|
|
5608
6058
|
}
|
|
5609
6059
|
for (const r of repos) {
|
|
6060
|
+
const containerPath = r.kind === "root" ? "/workspace" : `/workspace/${r.relPathFromWorkspace}`;
|
|
6061
|
+
const reuseBranch = r.kind === "root" && opts.useBranch !== void 0;
|
|
6062
|
+
if (reuseBranch) {
|
|
6063
|
+
const branch2 = opts.useBranch;
|
|
6064
|
+
const gitWorktreePath2 = gitWorktreePathFor(branch2);
|
|
6065
|
+
repoCarryOvers.push({
|
|
6066
|
+
repo: r,
|
|
6067
|
+
containerPath,
|
|
6068
|
+
gitWorktreePath: gitWorktreePath2,
|
|
6069
|
+
branch: branch2,
|
|
6070
|
+
stashSha: null,
|
|
6071
|
+
untrackedNul: "",
|
|
6072
|
+
hostSource: r.hostMainRepo,
|
|
6073
|
+
reuseBranch: true
|
|
6074
|
+
});
|
|
6075
|
+
gitWorktreeRecords.push({
|
|
6076
|
+
kind: r.kind,
|
|
6077
|
+
hostMainRepo: r.hostMainRepo,
|
|
6078
|
+
containerPath,
|
|
6079
|
+
gitWorktreePath: gitWorktreePath2,
|
|
6080
|
+
branch: branch2,
|
|
6081
|
+
relPathFromWorkspace: r.relPathFromWorkspace
|
|
6082
|
+
});
|
|
6083
|
+
continue;
|
|
6084
|
+
}
|
|
5610
6085
|
const branchBase = r.kind === "root" ? `agentbox/${name}` : `agentbox/${name}--${r.relPathFromWorkspace.replace(/[^A-Za-z0-9._-]+/g, "_")}`;
|
|
5611
6086
|
const branch = await pickFreshBranch(r.hostMainRepo, branchBase);
|
|
5612
|
-
const containerPath = r.kind === "root" ? "/workspace" : `/workspace/${r.relPathFromWorkspace}`;
|
|
5613
6087
|
const gitWorktreePath = gitWorktreePathFor(branch);
|
|
5614
6088
|
const carry = await collectRepoCarryOver(r, branch, containerPath, gitWorktreePath);
|
|
5615
6089
|
repoCarryOvers.push(carry);
|
|
@@ -5716,6 +6190,8 @@ async function createBox(opts) {
|
|
|
5716
6190
|
if (opencodeEnsured.synced) log(`synced ${opencodeSpec.volume} from ~/.config + ~/.local/share opencode`);
|
|
5717
6191
|
else if (opencodeEnsured.created) log(`created empty volume ${opencodeSpec.volume} (no host opencode)`);
|
|
5718
6192
|
else log(`reusing volume ${opencodeSpec.volume}`);
|
|
6193
|
+
const opencodePlugin = await seedOpencodePlugin(opencodeSpec.volume, ensureRef);
|
|
6194
|
+
if (opencodePlugin.seeded) log(`seeded agentbox-state plugin into ${opencodeSpec.volume}`);
|
|
5719
6195
|
opencodeMounts = buildOpencodeMounts(opencodeSpec, process.env);
|
|
5720
6196
|
opencodeConfigVolume = opencodeSpec.volume;
|
|
5721
6197
|
}
|
|
@@ -5771,10 +6247,15 @@ async function createBox(opts) {
|
|
|
5771
6247
|
}
|
|
5772
6248
|
}
|
|
5773
6249
|
const relayEnv = relayUp ? {
|
|
5774
|
-
//
|
|
5775
|
-
//
|
|
5776
|
-
|
|
5777
|
-
|
|
6250
|
+
// The in-box ctl client always talks to its own in-box relay/forwarder
|
|
6251
|
+
// on AGENTBOX_BOX_RELAY_PORT (default 8788). For docker boxes the
|
|
6252
|
+
// forwarder transparently proxies to the host relay at
|
|
6253
|
+
// host.docker.internal:8787 (the matching `--add-host` is set in
|
|
6254
|
+
// runBox). This keeps :8787 inside the box free for a nested
|
|
6255
|
+
// agentbox to claim its own host relay.
|
|
6256
|
+
AGENTBOX_RELAY_URL: `http://127.0.0.1:8788`,
|
|
6257
|
+
AGENTBOX_RELAY_TOKEN: relayToken,
|
|
6258
|
+
AGENTBOX_HOST_RELAY_URL: `http://host.docker.internal:8787`
|
|
5778
6259
|
} : {};
|
|
5779
6260
|
const vncEnabled = opts.vnc?.enabled !== false;
|
|
5780
6261
|
const vncPassword = vncEnabled ? generateVncPassword() : void 0;
|
|
@@ -5839,12 +6320,26 @@ async function createBox(opts) {
|
|
|
5839
6320
|
if (!checkpointImage) {
|
|
5840
6321
|
if (repoCarryOvers.length > 0) {
|
|
5841
6322
|
try {
|
|
5842
|
-
await seedWorkspace({
|
|
6323
|
+
await seedWorkspace({
|
|
6324
|
+
container: containerName,
|
|
6325
|
+
repos: repoCarryOvers,
|
|
6326
|
+
fromBranch: opts.fromBranch,
|
|
6327
|
+
onLog: log
|
|
6328
|
+
});
|
|
5843
6329
|
log("seeded /workspace from in-container git worktree(s)");
|
|
5844
6330
|
} catch (err) {
|
|
5845
|
-
|
|
5846
|
-
`seedWorkspace failed
|
|
5847
|
-
|
|
6331
|
+
if (opts.useBranch !== void 0) {
|
|
6332
|
+
log(`seedWorkspace failed for --use-branch ${opts.useBranch}; cleaning up the box`);
|
|
6333
|
+
await execa13("docker", ["rm", "-f", containerName], { reject: false });
|
|
6334
|
+
for (const w of gitWorktreeRecords) {
|
|
6335
|
+
await removeInBoxWorktree({
|
|
6336
|
+
hostMainRepo: w.hostMainRepo,
|
|
6337
|
+
gitWorktreePath: w.gitWorktreePath
|
|
6338
|
+
});
|
|
6339
|
+
}
|
|
6340
|
+
} else {
|
|
6341
|
+
log(`seedWorkspace failed; leaving ${containerName} running so you can inspect it`);
|
|
6342
|
+
}
|
|
5848
6343
|
throw err;
|
|
5849
6344
|
}
|
|
5850
6345
|
} else {
|
|
@@ -5878,7 +6373,7 @@ async function createBox(opts) {
|
|
|
5878
6373
|
}
|
|
5879
6374
|
if (opts.withPlaywright) {
|
|
5880
6375
|
log("installing @playwright/cli@latest (--with-playwright)");
|
|
5881
|
-
const result = await
|
|
6376
|
+
const result = await execa13(
|
|
5882
6377
|
"docker",
|
|
5883
6378
|
[
|
|
5884
6379
|
"exec",
|
|
@@ -5923,6 +6418,20 @@ async function createBox(opts) {
|
|
|
5923
6418
|
log(`copied ${String(copied)}/${String(opts.envFilesToImport.length)} selected env/config file(s)`);
|
|
5924
6419
|
}
|
|
5925
6420
|
}
|
|
6421
|
+
let carrySummary;
|
|
6422
|
+
if (opts.carry && opts.carry.length > 0) {
|
|
6423
|
+
log(`carry: copying ${String(opts.carry.length)} host path(s) into the box`);
|
|
6424
|
+
const result = await copyCarryPathsToBox({
|
|
6425
|
+
container: containerName,
|
|
6426
|
+
entries: opts.carry,
|
|
6427
|
+
onLog: log
|
|
6428
|
+
});
|
|
6429
|
+
log(`carry: copied ${String(result.copied)}/${String(opts.carry.length)} entry/entries`);
|
|
6430
|
+
for (const err of result.errors) log(`carry: ${err}`);
|
|
6431
|
+
if (result.applied.length > 0) {
|
|
6432
|
+
carrySummary = { count: result.applied.length, entries: result.applied };
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
5926
6435
|
let vncHostPort = null;
|
|
5927
6436
|
if (vncEnabled) {
|
|
5928
6437
|
const vnc = await launchVncDaemon(containerName);
|
|
@@ -5939,7 +6448,9 @@ async function createBox(opts) {
|
|
|
5939
6448
|
}
|
|
5940
6449
|
let portlessAliasName;
|
|
5941
6450
|
let portlessUrl;
|
|
5942
|
-
|
|
6451
|
+
let portlessVncAliasName;
|
|
6452
|
+
let portlessVncUrl;
|
|
6453
|
+
if (opts.portless === true && (webHostPort || vncEnabled && vncHostPort)) {
|
|
5943
6454
|
try {
|
|
5944
6455
|
const engine = await detectEngine();
|
|
5945
6456
|
if (engine === "orbstack") {
|
|
@@ -5948,15 +6459,29 @@ async function createBox(opts) {
|
|
|
5948
6459
|
const portless = await detectPortless();
|
|
5949
6460
|
if (!portless.installed) {
|
|
5950
6461
|
log("portless not installed \u2014 run `npm install -g portless` for a <name>.localhost URL");
|
|
5951
|
-
} else
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6462
|
+
} else {
|
|
6463
|
+
if (webHostPort) {
|
|
6464
|
+
if (await portlessAlias(name, webHostPort)) {
|
|
6465
|
+
portlessAliasName = name;
|
|
6466
|
+
portlessUrl = await portlessGetUrl(name);
|
|
6467
|
+
log(`portless alias ${portlessUrl} -> 127.0.0.1:${String(webHostPort)}`);
|
|
6468
|
+
} else {
|
|
6469
|
+
log("portless alias failed (best-effort) \u2014 box still reachable on the loopback URL");
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
if (vncEnabled && vncHostPort) {
|
|
6473
|
+
const vncAlias = `vnc-${name}`;
|
|
6474
|
+
if (await portlessAlias(vncAlias, vncHostPort)) {
|
|
6475
|
+
portlessVncAliasName = vncAlias;
|
|
6476
|
+
portlessVncUrl = await portlessGetUrl(vncAlias);
|
|
6477
|
+
log(`portless alias ${portlessVncUrl} -> 127.0.0.1:${String(vncHostPort)}`);
|
|
6478
|
+
} else {
|
|
6479
|
+
log("portless vnc alias failed (best-effort) \u2014 VNC still reachable on the loopback URL");
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
if (!portless.proxyRunning && (portlessAliasName || portlessVncAliasName)) {
|
|
5956
6483
|
log(`portless proxy not running \u2014 start it with \`${portlessStartHint()}\``);
|
|
5957
6484
|
}
|
|
5958
|
-
} else {
|
|
5959
|
-
log("portless alias failed (best-effort) \u2014 box still reachable on the loopback URL");
|
|
5960
6485
|
}
|
|
5961
6486
|
}
|
|
5962
6487
|
} catch (err) {
|
|
@@ -5980,6 +6505,7 @@ async function createBox(opts) {
|
|
|
5980
6505
|
gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : void 0,
|
|
5981
6506
|
withPlaywright: opts.withPlaywright ? true : void 0,
|
|
5982
6507
|
withEnv: opts.withEnv ? true : void 0,
|
|
6508
|
+
carry: carrySummary,
|
|
5983
6509
|
vncEnabled: vncEnabled ? true : void 0,
|
|
5984
6510
|
vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : void 0,
|
|
5985
6511
|
vncHostPort: vncHostPort ?? void 0,
|
|
@@ -5988,6 +6514,8 @@ async function createBox(opts) {
|
|
|
5988
6514
|
webHostPort: webHostPort ?? void 0,
|
|
5989
6515
|
portlessAlias: portlessAliasName,
|
|
5990
6516
|
portlessUrl,
|
|
6517
|
+
portlessVncAlias: portlessVncAliasName,
|
|
6518
|
+
portlessVncUrl,
|
|
5991
6519
|
dockerVolume,
|
|
5992
6520
|
dockerCacheShared: dockerCacheShared || void 0,
|
|
5993
6521
|
projectRoot: opts.projectRoot,
|
|
@@ -6046,7 +6574,7 @@ function parseShellSessionList(stdout) {
|
|
|
6046
6574
|
return out;
|
|
6047
6575
|
}
|
|
6048
6576
|
async function listShellSessions(container, user) {
|
|
6049
|
-
const res = await
|
|
6577
|
+
const res = await execa14(
|
|
6050
6578
|
"docker",
|
|
6051
6579
|
[
|
|
6052
6580
|
"exec",
|
|
@@ -6070,7 +6598,7 @@ async function startShellSession(opts) {
|
|
|
6070
6598
|
const login = opts.login !== false;
|
|
6071
6599
|
const term = process.env["TERM"] ?? "xterm-256color";
|
|
6072
6600
|
const cmd = login ? "bash -l" : "bash";
|
|
6073
|
-
const result = await
|
|
6601
|
+
const result = await execa14(
|
|
6074
6602
|
"docker",
|
|
6075
6603
|
[
|
|
6076
6604
|
"exec",
|
|
@@ -6119,7 +6647,7 @@ function buildShellSessionAttachArgv(container, sessionName, user) {
|
|
|
6119
6647
|
}
|
|
6120
6648
|
async function shellSessionInfo(container, sessionName, user) {
|
|
6121
6649
|
const name = sessionName ?? DEFAULT_SHELL_SESSION;
|
|
6122
|
-
const has = await
|
|
6650
|
+
const has = await execa14(
|
|
6123
6651
|
"docker",
|
|
6124
6652
|
["exec", "--user", user ?? CONTAINER_USER, container, "tmux", "has-session", "-t", name],
|
|
6125
6653
|
{ reject: false }
|
|
@@ -6127,7 +6655,7 @@ async function shellSessionInfo(container, sessionName, user) {
|
|
|
6127
6655
|
return { running: has.exitCode === 0, sessionName: name };
|
|
6128
6656
|
}
|
|
6129
6657
|
async function killShellSession(container, sessionName, user) {
|
|
6130
|
-
const res = await
|
|
6658
|
+
const res = await execa14(
|
|
6131
6659
|
"docker",
|
|
6132
6660
|
[
|
|
6133
6661
|
"exec",
|
|
@@ -6149,7 +6677,7 @@ async function getBoxEndpoints(record, engine, persisted) {
|
|
|
6149
6677
|
const endpoints = [];
|
|
6150
6678
|
if (record.vncEnabled && record.vncPassword) {
|
|
6151
6679
|
const vncUrls = buildVncUrls(record, engine);
|
|
6152
|
-
const url = vncUrls.orbUrl ?? vncUrls.loopbackUrl;
|
|
6680
|
+
const url = engine === "orbstack" ? vncUrls.orbUrl ?? vncUrls.loopbackUrl : vncUrls.portlessUrl ?? vncUrls.loopbackUrl;
|
|
6153
6681
|
endpoints.push({
|
|
6154
6682
|
kind: "vnc",
|
|
6155
6683
|
name: "vnc",
|
|
@@ -6216,18 +6744,30 @@ async function listBoxes() {
|
|
|
6216
6744
|
const portlessWebUrl = b.portlessAlias !== void 0 ? b.portlessUrl ?? `https://${b.portlessAlias}.localhost` : void 0;
|
|
6217
6745
|
const cachedWebUrl = webPort > 0 ? b.cloud?.previewUrls?.[webPort] : void 0;
|
|
6218
6746
|
const webUrl = portlessWebUrl ?? cachedWebUrl;
|
|
6747
|
+
const portlessVncBase = b.portlessVncAlias !== void 0 ? b.portlessVncUrl ?? `https://${b.portlessVncAlias}.localhost` : void 0;
|
|
6748
|
+
const vncUrl = portlessVncBase && b.vncPassword ? `${portlessVncBase}/vnc.html?autoconnect=1&password=${encodeURIComponent(b.vncPassword)}` : void 0;
|
|
6749
|
+
const cloudEndpoints = [];
|
|
6750
|
+
if (webUrl) {
|
|
6751
|
+
cloudEndpoints.push({
|
|
6752
|
+
kind: "web",
|
|
6753
|
+
name: "web",
|
|
6754
|
+
containerPort: webPort,
|
|
6755
|
+
url: webUrl,
|
|
6756
|
+
reachable: true
|
|
6757
|
+
});
|
|
6758
|
+
}
|
|
6759
|
+
if (b.vncEnabled && b.vncPassword) {
|
|
6760
|
+
cloudEndpoints.push({
|
|
6761
|
+
kind: "vnc",
|
|
6762
|
+
name: "vnc",
|
|
6763
|
+
containerPort: b.vncContainerPort ?? 6080,
|
|
6764
|
+
...vncUrl ? { url: vncUrl, reachable: true } : { reachable: false }
|
|
6765
|
+
});
|
|
6766
|
+
}
|
|
6219
6767
|
const endpoints2 = {
|
|
6220
6768
|
domain: webUrl ? safeHost(webUrl) : "",
|
|
6221
6769
|
domainIsOrb: false,
|
|
6222
|
-
endpoints:
|
|
6223
|
-
{
|
|
6224
|
-
kind: "web",
|
|
6225
|
-
name: "web",
|
|
6226
|
-
containerPort: webPort,
|
|
6227
|
-
url: webUrl,
|
|
6228
|
-
reachable: true
|
|
6229
|
-
}
|
|
6230
|
-
] : []
|
|
6770
|
+
endpoints: cloudEndpoints
|
|
6231
6771
|
};
|
|
6232
6772
|
return {
|
|
6233
6773
|
...b,
|
|
@@ -6351,19 +6891,31 @@ async function startBox(idOrName) {
|
|
|
6351
6891
|
box.webHostPort = freshWebPort;
|
|
6352
6892
|
await recordBox(box);
|
|
6353
6893
|
}
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6894
|
+
}
|
|
6895
|
+
if (box.portlessAlias && box.webHostPort || box.portlessVncAlias && box.vncHostPort) {
|
|
6896
|
+
try {
|
|
6897
|
+
const portless = await detectPortless();
|
|
6898
|
+
if (portless.installed) {
|
|
6899
|
+
let dirty = false;
|
|
6900
|
+
if (box.portlessAlias && box.webHostPort) {
|
|
6358
6901
|
await portlessAlias(box.portlessAlias, box.webHostPort);
|
|
6359
6902
|
const url = await portlessGetUrl(box.portlessAlias);
|
|
6360
6903
|
if (url !== box.portlessUrl) {
|
|
6361
6904
|
box.portlessUrl = url;
|
|
6362
|
-
|
|
6905
|
+
dirty = true;
|
|
6363
6906
|
}
|
|
6364
6907
|
}
|
|
6365
|
-
|
|
6908
|
+
if (box.portlessVncAlias && box.vncHostPort) {
|
|
6909
|
+
await portlessAlias(box.portlessVncAlias, box.vncHostPort);
|
|
6910
|
+
const url = await portlessGetUrl(box.portlessVncAlias);
|
|
6911
|
+
if (url !== box.portlessVncUrl) {
|
|
6912
|
+
box.portlessVncUrl = url;
|
|
6913
|
+
dirty = true;
|
|
6914
|
+
}
|
|
6915
|
+
}
|
|
6916
|
+
if (dirty) await recordBox(box);
|
|
6366
6917
|
}
|
|
6918
|
+
} catch {
|
|
6367
6919
|
}
|
|
6368
6920
|
}
|
|
6369
6921
|
if (box.relayToken) {
|
|
@@ -6395,7 +6947,7 @@ async function getBoxHostPaths(idOrName) {
|
|
|
6395
6947
|
}
|
|
6396
6948
|
async function dirSizeBytes(path) {
|
|
6397
6949
|
try {
|
|
6398
|
-
const result = await
|
|
6950
|
+
const result = await execa15("du", ["-sk", path], { reject: false });
|
|
6399
6951
|
if (result.exitCode !== 0) return null;
|
|
6400
6952
|
const sizeKb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
|
|
6401
6953
|
if (Number.isNaN(sizeKb)) return null;
|
|
@@ -6463,6 +7015,12 @@ async function destroyBox(idOrName, opts = {}) {
|
|
|
6463
7015
|
} catch {
|
|
6464
7016
|
}
|
|
6465
7017
|
}
|
|
7018
|
+
if (box.portlessVncAlias) {
|
|
7019
|
+
try {
|
|
7020
|
+
await portlessUnalias(box.portlessVncAlias);
|
|
7021
|
+
} catch {
|
|
7022
|
+
}
|
|
7023
|
+
}
|
|
6466
7024
|
const ownsWorktrees = !box.checkpointImage;
|
|
6467
7025
|
if (ownsWorktrees) {
|
|
6468
7026
|
for (const w of box.gitWorktrees ?? []) {
|
|
@@ -6537,7 +7095,7 @@ async function listBoxDirs() {
|
|
|
6537
7095
|
}
|
|
6538
7096
|
}
|
|
6539
7097
|
async function listCheckpointImageTags() {
|
|
6540
|
-
const r = await
|
|
7098
|
+
const r = await execa15(
|
|
6541
7099
|
"docker",
|
|
6542
7100
|
["image", "ls", "--format", "{{.Repository}}:{{.Tag}}", `${CHECKPOINT_IMAGE_PREFIX}*`],
|
|
6543
7101
|
{ reject: false }
|
|
@@ -6645,7 +7203,7 @@ async function pruneBoxes(opts = {}) {
|
|
|
6645
7203
|
} catch {
|
|
6646
7204
|
}
|
|
6647
7205
|
try {
|
|
6648
|
-
await
|
|
7206
|
+
await execa15("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
|
|
6649
7207
|
} catch {
|
|
6650
7208
|
}
|
|
6651
7209
|
try {
|
|
@@ -6707,7 +7265,7 @@ function splitPair(raw) {
|
|
|
6707
7265
|
return [parts[0].trim(), parts[1].trim()];
|
|
6708
7266
|
}
|
|
6709
7267
|
async function duBytes(path) {
|
|
6710
|
-
const result = await
|
|
7268
|
+
const result = await execa16("du", ["-sk", path], { reject: false });
|
|
6711
7269
|
if (result.exitCode !== 0) return null;
|
|
6712
7270
|
const kb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
|
|
6713
7271
|
return Number.isNaN(kb) ? null : kb * 1024;
|
|
@@ -6720,7 +7278,7 @@ async function volumeSizeBytes(name) {
|
|
|
6720
7278
|
const sz = await duBytes(live);
|
|
6721
7279
|
if (sz !== null) return sz;
|
|
6722
7280
|
}
|
|
6723
|
-
const df = await
|
|
7281
|
+
const df = await execa16(
|
|
6724
7282
|
"docker",
|
|
6725
7283
|
["system", "df", "-v", "--format", "{{json .Volumes}}"],
|
|
6726
7284
|
{ reject: false }
|
|
@@ -6741,7 +7299,7 @@ async function volumeSizeBytes(name) {
|
|
|
6741
7299
|
return null;
|
|
6742
7300
|
}
|
|
6743
7301
|
async function imageBytes(tag) {
|
|
6744
|
-
const r = await
|
|
7302
|
+
const r = await execa16("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
|
|
6745
7303
|
reject: false
|
|
6746
7304
|
});
|
|
6747
7305
|
if (r.exitCode !== 0) return null;
|
|
@@ -6752,7 +7310,7 @@ async function projectCheckpointImageBytes(projectRoot, name) {
|
|
|
6752
7310
|
return imageBytes(checkpointImageTag(projectRoot, name));
|
|
6753
7311
|
}
|
|
6754
7312
|
async function allCheckpointImagesBytes() {
|
|
6755
|
-
const r = await
|
|
7313
|
+
const r = await execa16(
|
|
6756
7314
|
"docker",
|
|
6757
7315
|
[
|
|
6758
7316
|
"image",
|
|
@@ -6804,7 +7362,7 @@ function reconcileLimits(persisted, dockerJson) {
|
|
|
6804
7362
|
};
|
|
6805
7363
|
}
|
|
6806
7364
|
async function containerWritableBytes(container) {
|
|
6807
|
-
const r = await
|
|
7365
|
+
const r = await execa16(
|
|
6808
7366
|
"docker",
|
|
6809
7367
|
["ps", "-a", "--filter", `name=^${container}$`, "--format", "{{.Size}}", "--size"],
|
|
6810
7368
|
{ reject: false }
|
|
@@ -6851,7 +7409,7 @@ async function boxResourceStats(record) {
|
|
|
6851
7409
|
if (await inspectContainerStatus(record.container) !== "running") {
|
|
6852
7410
|
return base;
|
|
6853
7411
|
}
|
|
6854
|
-
const proc = await
|
|
7412
|
+
const proc = await execa16(
|
|
6855
7413
|
"docker",
|
|
6856
7414
|
["stats", "--no-stream", "--format", "{{json .}}", record.container],
|
|
6857
7415
|
{ reject: false }
|
|
@@ -6886,6 +7444,125 @@ async function boxResourceStats(record) {
|
|
|
6886
7444
|
blockWriteBytes: blkPair ? parseDockerSize(blkPair[1]) : null
|
|
6887
7445
|
};
|
|
6888
7446
|
}
|
|
7447
|
+
function posixDirname(p) {
|
|
7448
|
+
return posix.dirname(p) || "/";
|
|
7449
|
+
}
|
|
7450
|
+
function asText(s) {
|
|
7451
|
+
if (s === void 0) return "";
|
|
7452
|
+
if (typeof s === "string") return s;
|
|
7453
|
+
return Buffer.from(s).toString("utf8");
|
|
7454
|
+
}
|
|
7455
|
+
async function uploadToBox(box, hostSrc, boxDst) {
|
|
7456
|
+
const srcAbs = resolve4(hostSrc);
|
|
7457
|
+
if (!existsSync3(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
|
|
7458
|
+
const srcBasename = basename32(srcAbs);
|
|
7459
|
+
const srcParent = dirname22(srcAbs);
|
|
7460
|
+
let boxParent;
|
|
7461
|
+
let finalName;
|
|
7462
|
+
if (boxDst.endsWith("/")) {
|
|
7463
|
+
boxParent = boxDst.replace(/\/+$/, "") || "/";
|
|
7464
|
+
finalName = srcBasename;
|
|
7465
|
+
} else {
|
|
7466
|
+
const isDir2 = await execa17(
|
|
7467
|
+
"docker",
|
|
7468
|
+
["exec", box.container, "test", "-d", boxDst],
|
|
7469
|
+
{ reject: false }
|
|
7470
|
+
);
|
|
7471
|
+
if (isDir2.exitCode === 0) {
|
|
7472
|
+
boxParent = boxDst.replace(/\/+$/, "") || "/";
|
|
7473
|
+
finalName = srcBasename;
|
|
7474
|
+
} else {
|
|
7475
|
+
boxParent = posixDirname(boxDst);
|
|
7476
|
+
finalName = posix.basename(boxDst);
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
|
|
7480
|
+
const mk = await execa17(
|
|
7481
|
+
"docker",
|
|
7482
|
+
["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
|
|
7483
|
+
{ reject: false }
|
|
7484
|
+
);
|
|
7485
|
+
if (mk.exitCode !== 0) {
|
|
7486
|
+
throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
|
|
7487
|
+
}
|
|
7488
|
+
const packed = await execa17("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
|
|
7489
|
+
encoding: "buffer",
|
|
7490
|
+
reject: false,
|
|
7491
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
7492
|
+
});
|
|
7493
|
+
if (packed.exitCode !== 0) {
|
|
7494
|
+
throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
|
|
7495
|
+
}
|
|
7496
|
+
const extract = await execa17(
|
|
7497
|
+
"docker",
|
|
7498
|
+
["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
|
|
7499
|
+
{ input: packed.stdout, reject: false }
|
|
7500
|
+
);
|
|
7501
|
+
if (extract.exitCode !== 0) {
|
|
7502
|
+
throw new Error(`tar extract in box failed: ${asText(extract.stderr).slice(0, 300)}`);
|
|
7503
|
+
}
|
|
7504
|
+
if (finalName !== srcBasename) {
|
|
7505
|
+
const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
|
|
7506
|
+
const mv = await execa17(
|
|
7507
|
+
"docker",
|
|
7508
|
+
["exec", "--user", "root", box.container, "mv", initial, finalPath],
|
|
7509
|
+
{ reject: false }
|
|
7510
|
+
);
|
|
7511
|
+
if (mv.exitCode !== 0) {
|
|
7512
|
+
throw new Error(
|
|
7513
|
+
`rename ${initial} -> ${finalPath} in box failed: ${asText(mv.stderr).slice(0, 300)}`
|
|
7514
|
+
);
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
const chown = await execa17(
|
|
7518
|
+
"docker",
|
|
7519
|
+
["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
|
|
7520
|
+
{ reject: false }
|
|
7521
|
+
);
|
|
7522
|
+
if (chown.exitCode !== 0) {
|
|
7523
|
+
return {
|
|
7524
|
+
finalPath,
|
|
7525
|
+
warn: `chown ${finalPath} to vscode (uid 1000) failed; ownership inside the box may be root.`
|
|
7526
|
+
};
|
|
7527
|
+
}
|
|
7528
|
+
return { finalPath };
|
|
7529
|
+
}
|
|
7530
|
+
async function downloadFromBox(box, boxSrc, hostDst) {
|
|
7531
|
+
const srcBasename = posix.basename(boxSrc);
|
|
7532
|
+
const srcParent = posixDirname(boxSrc);
|
|
7533
|
+
const dstAbs = resolve4(hostDst);
|
|
7534
|
+
let hostParent;
|
|
7535
|
+
let finalName;
|
|
7536
|
+
const dstExists = existsSync3(dstAbs);
|
|
7537
|
+
if (hostDst.endsWith("/") || dstExists && statSync(dstAbs).isDirectory()) {
|
|
7538
|
+
hostParent = dstAbs;
|
|
7539
|
+
finalName = srcBasename;
|
|
7540
|
+
} else {
|
|
7541
|
+
hostParent = dirname22(dstAbs);
|
|
7542
|
+
finalName = basename32(dstAbs);
|
|
7543
|
+
}
|
|
7544
|
+
mkdirSync(hostParent, { recursive: true });
|
|
7545
|
+
const finalPath = posix.join(hostParent, finalName);
|
|
7546
|
+
const packed = await execa17(
|
|
7547
|
+
"docker",
|
|
7548
|
+
["exec", box.container, "tar", "-C", srcParent, "-cf", "-", srcBasename],
|
|
7549
|
+
{ encoding: "buffer", reject: false }
|
|
7550
|
+
);
|
|
7551
|
+
if (packed.exitCode !== 0) {
|
|
7552
|
+
throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
|
|
7553
|
+
}
|
|
7554
|
+
const extract = await execa17("tar", ["-xf", "-", "-C", hostParent], {
|
|
7555
|
+
input: packed.stdout,
|
|
7556
|
+
reject: false
|
|
7557
|
+
});
|
|
7558
|
+
if (extract.exitCode !== 0) {
|
|
7559
|
+
throw new Error(`tar extract on host failed: ${asText(extract.stderr).slice(0, 300)}`);
|
|
7560
|
+
}
|
|
7561
|
+
if (finalName !== srcBasename) {
|
|
7562
|
+
renameSync(posix.join(hostParent, srcBasename), finalPath);
|
|
7563
|
+
}
|
|
7564
|
+
return { finalPath };
|
|
7565
|
+
}
|
|
6889
7566
|
var dockerProvider = {
|
|
6890
7567
|
name: "docker",
|
|
6891
7568
|
async create(req) {
|
|
@@ -6895,6 +7572,8 @@ var dockerProvider = {
|
|
|
6895
7572
|
name: req.name,
|
|
6896
7573
|
useSnapshot: po.useSnapshot ?? false,
|
|
6897
7574
|
checkpointRef: req.checkpointRef,
|
|
7575
|
+
fromBranch: req.fromBranch,
|
|
7576
|
+
useBranch: req.useBranch,
|
|
6898
7577
|
image: req.image,
|
|
6899
7578
|
onLog: req.onLog,
|
|
6900
7579
|
claudeConfig: po.claudeConfig,
|
|
@@ -6904,6 +7583,7 @@ var dockerProvider = {
|
|
|
6904
7583
|
withPlaywright: req.withPlaywright,
|
|
6905
7584
|
withEnv: req.withEnv,
|
|
6906
7585
|
envFilesToImport: req.envFilesToImport,
|
|
7586
|
+
carry: req.carry,
|
|
6907
7587
|
vnc: req.vnc,
|
|
6908
7588
|
docker: po.sharedCache !== void 0 ? { sharedCache: po.sharedCache } : void 0,
|
|
6909
7589
|
portless: po.portless,
|
|
@@ -6954,6 +7634,14 @@ var dockerProvider = {
|
|
|
6954
7634
|
const r = await execInBox(box.container, argv, opts?.user ? { user: opts.user } : {});
|
|
6955
7635
|
return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
|
|
6956
7636
|
},
|
|
7637
|
+
async uploadPath(box, hostSrc, boxDst) {
|
|
7638
|
+
const r = await uploadToBox(box, hostSrc, boxDst);
|
|
7639
|
+
return { finalPath: r.finalPath };
|
|
7640
|
+
},
|
|
7641
|
+
async downloadPath(box, boxSrc, hostDst) {
|
|
7642
|
+
const r = await downloadFromBox(box, boxSrc, hostDst);
|
|
7643
|
+
return { finalPath: r.finalPath };
|
|
7644
|
+
},
|
|
6957
7645
|
async resolveUrl(box, opts) {
|
|
6958
7646
|
if (box.webContainerPort === void 0) {
|
|
6959
7647
|
throw new Error(
|
|
@@ -6976,13 +7664,33 @@ var dockerProvider = {
|
|
|
6976
7664
|
},
|
|
6977
7665
|
async prepare(opts) {
|
|
6978
7666
|
const ref = DEFAULT_BOX_IMAGE;
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7667
|
+
const fingerprint = await computeDockerContextFingerprint();
|
|
7668
|
+
const prepared = readPreparedDockerState();
|
|
7669
|
+
if (!opts.force) {
|
|
7670
|
+
const exists = await imageExists(ref);
|
|
7671
|
+
if (exists && fingerprint && preparedMatches(prepared, fingerprint.contextSha256)) {
|
|
7672
|
+
opts.onLog?.(
|
|
7673
|
+
`docker image ${ref} up to date (fingerprint ${fingerprint.contextSha256.slice(0, 12)}) \u2014 skipping (use --force to rebuild)`
|
|
7674
|
+
);
|
|
7675
|
+
return {};
|
|
7676
|
+
}
|
|
7677
|
+
if (exists && !fingerprint) {
|
|
7678
|
+
opts.onLog?.(
|
|
7679
|
+
`docker image ${ref} present but build context could not be fingerprinted \u2014 skipping (use --force to rebuild)`
|
|
7680
|
+
);
|
|
7681
|
+
return {};
|
|
7682
|
+
}
|
|
6982
7683
|
}
|
|
6983
7684
|
opts.onLog?.(`building docker image ${ref}\u2026`);
|
|
6984
7685
|
await buildImage({ ref, onProgress: opts.onLog });
|
|
6985
|
-
|
|
7686
|
+
if (fingerprint) {
|
|
7687
|
+
writePreparedDockerState({ imageRef: ref, contextSha256: fingerprint.contextSha256 });
|
|
7688
|
+
opts.onLog?.(
|
|
7689
|
+
`docker image ${ref} built; recorded fingerprint ${fingerprint.contextSha256.slice(0, 12)}`
|
|
7690
|
+
);
|
|
7691
|
+
} else {
|
|
7692
|
+
opts.onLog?.(`docker image ${ref} built (fingerprint unavailable, prepared state not written)`);
|
|
7693
|
+
}
|
|
6986
7694
|
return {};
|
|
6987
7695
|
}
|
|
6988
7696
|
};
|
|
@@ -7028,7 +7736,7 @@ function emptyResult(warnings = []) {
|
|
|
7028
7736
|
}, warnings };
|
|
7029
7737
|
}
|
|
7030
7738
|
async function tarballFromDir(stageDir, agent) {
|
|
7031
|
-
const tarballPath = join14(tmpdir3(), `agentbox-${agent}-${
|
|
7739
|
+
const tarballPath = join14(tmpdir3(), `agentbox-${agent}-${basename4(stageDir)}.tar.gz`);
|
|
7032
7740
|
await execa18("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
|
|
7033
7741
|
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
7034
7742
|
});
|
|
@@ -7222,6 +7930,9 @@ async function stageCodexStaticForUpload(opts = {}) {
|
|
|
7222
7930
|
}
|
|
7223
7931
|
}
|
|
7224
7932
|
async function stageCodexCredentialsForUpload(opts = {}) {
|
|
7933
|
+
if (await pathExists7(CODEX_CREDENTIALS_BACKUP_FILE)) {
|
|
7934
|
+
return stageSingleFileTarball("codex-creds", CODEX_CREDENTIALS_BACKUP_FILE, "auth.json");
|
|
7935
|
+
}
|
|
7225
7936
|
const hostHome = opts.hostHome ?? homedir11();
|
|
7226
7937
|
const hostAuth = join14(hostHome, ".codex", "auth.json");
|
|
7227
7938
|
if (!await pathExists7(hostAuth)) return emptyResult([CODEX_KEYCHAIN_WARNING]);
|
|
@@ -7286,11 +7997,20 @@ async function stageOpencodeStaticForUpload(opts = {}) {
|
|
|
7286
7997
|
}
|
|
7287
7998
|
}
|
|
7288
7999
|
async function stageOpencodeCredentialsForUpload(opts = {}) {
|
|
8000
|
+
if (await pathExists7(OPENCODE_CREDENTIALS_BACKUP_FILE)) {
|
|
8001
|
+
return stageSingleFileTarball("opencode-creds", OPENCODE_CREDENTIALS_BACKUP_FILE, "auth.json");
|
|
8002
|
+
}
|
|
7289
8003
|
const hostHome = opts.hostHome ?? homedir11();
|
|
7290
8004
|
const hostAuth = join14(hostHome, ".local", "share", "opencode", "auth.json");
|
|
7291
8005
|
if (!await pathExists7(hostAuth)) return emptyResult();
|
|
7292
8006
|
return stageSingleFileTarball("opencode-creds", hostAuth, "auth.json");
|
|
7293
8007
|
}
|
|
8008
|
+
async function stageOpencodeStateForUpload(opts = {}) {
|
|
8009
|
+
const hostHome = opts.hostHome ?? homedir11();
|
|
8010
|
+
const hostModel = join14(hostHome, ".local", "state", "opencode", "model.json");
|
|
8011
|
+
if (!await pathExists7(hostModel)) return emptyResult();
|
|
8012
|
+
return stageSingleFileTarball("opencode-state", hostModel, "model.json");
|
|
8013
|
+
}
|
|
7294
8014
|
function browserSessionActive(stdout, exitCode) {
|
|
7295
8015
|
return exitCode === 0 && !/no active sessions/i.test(stdout);
|
|
7296
8016
|
}
|
|
@@ -7330,25 +8050,25 @@ export {
|
|
|
7330
8050
|
listProjectsConfigured,
|
|
7331
8051
|
pruneOrphanProjectConfigs,
|
|
7332
8052
|
bumpProjectGcCounter,
|
|
8053
|
+
BOX_STATUS_EVENT,
|
|
7333
8054
|
renderStatusTable,
|
|
7334
8055
|
renderTaskTable,
|
|
7335
8056
|
renderPortsTable,
|
|
7336
|
-
|
|
7337
|
-
STATE_FILE,
|
|
7338
|
-
readState,
|
|
7339
|
-
recordBox,
|
|
7340
|
-
removeBoxRecord,
|
|
7341
|
-
findBox,
|
|
7342
|
-
allocateProjectIndex,
|
|
7343
|
-
autoPickProjectBox,
|
|
7344
|
-
resolveBoxRef,
|
|
7345
|
-
detectGitRepos,
|
|
7346
|
-
pickFreshBranch,
|
|
7347
|
-
GitWorktreeError,
|
|
8057
|
+
loadCarrySection,
|
|
7348
8058
|
DEFAULT_RELAY_PORT,
|
|
7349
8059
|
RELAY_CONTAINER_NAME,
|
|
7350
8060
|
RELAY_NETWORK_NAME,
|
|
7351
8061
|
RELAY_IMAGE_REF,
|
|
8062
|
+
hashRpcParams,
|
|
8063
|
+
GH_PR_OPS,
|
|
8064
|
+
injectPrCreateHead,
|
|
8065
|
+
loadQueueConfig,
|
|
8066
|
+
writeJob,
|
|
8067
|
+
readJob,
|
|
8068
|
+
deleteJob,
|
|
8069
|
+
loadQueue,
|
|
8070
|
+
defaultCountRunningBoxes,
|
|
8071
|
+
queueLogPath,
|
|
7352
8072
|
resolveAgentLauncher,
|
|
7353
8073
|
BoxNotFoundError,
|
|
7354
8074
|
AmbiguousBoxError,
|
|
@@ -7390,6 +8110,7 @@ export {
|
|
|
7390
8110
|
buildDashboardAttachArgv,
|
|
7391
8111
|
waitForTmuxPaneContent,
|
|
7392
8112
|
buildTmuxSessionArgs,
|
|
8113
|
+
buildTmuxConfigShellSnippet,
|
|
7393
8114
|
buildShellArgv,
|
|
7394
8115
|
buildClaudeLoginRunArgv,
|
|
7395
8116
|
runInteractiveClaudeLogin,
|
|
@@ -7399,6 +8120,9 @@ export {
|
|
|
7399
8120
|
claudeSessionInfo,
|
|
7400
8121
|
pullClaudeExtras,
|
|
7401
8122
|
CREDENTIALS_BACKUP_FILE,
|
|
8123
|
+
CODEX_CREDENTIALS_BACKUP_FILE,
|
|
8124
|
+
OPENCODE_CREDENTIALS_BACKUP_FILE,
|
|
8125
|
+
isRealAgentCredential,
|
|
7402
8126
|
hostBackupHasCredentials,
|
|
7403
8127
|
parseSyncResult,
|
|
7404
8128
|
syncClaudeCredentials,
|
|
@@ -7422,6 +8146,7 @@ export {
|
|
|
7422
8146
|
DEFAULT_OPENCODE_SESSION,
|
|
7423
8147
|
resolveOpencodeVolume,
|
|
7424
8148
|
ensureOpencodeVolume,
|
|
8149
|
+
seedOpencodePlugin,
|
|
7425
8150
|
OPENCODE_FORWARDED_ENV_KEYS,
|
|
7426
8151
|
buildOpencodeMounts,
|
|
7427
8152
|
OpencodeSessionError,
|
|
@@ -7459,10 +8184,6 @@ export {
|
|
|
7459
8184
|
installPortless,
|
|
7460
8185
|
startPortlessProxy,
|
|
7461
8186
|
resolvePortlessHostStateDir,
|
|
7462
|
-
DEFAULT_BOX_IMAGE,
|
|
7463
|
-
imageExists,
|
|
7464
|
-
imageInfo,
|
|
7465
|
-
ensureImage,
|
|
7466
8187
|
EXCLUDE_DIRS,
|
|
7467
8188
|
SNAPSHOTS_ROOT,
|
|
7468
8189
|
snapshotPathFor,
|
|
@@ -7485,6 +8206,7 @@ export {
|
|
|
7485
8206
|
forgetBoxFromRelay,
|
|
7486
8207
|
setRelayNotice,
|
|
7487
8208
|
clearRelayNotice,
|
|
8209
|
+
mintHostInitiatedToken,
|
|
7488
8210
|
rehydrateRelayRegistry,
|
|
7489
8211
|
IDE_FLAVORS,
|
|
7490
8212
|
ideProfile,
|
|
@@ -7533,6 +8255,8 @@ export {
|
|
|
7533
8255
|
allCheckpointImagesBytes,
|
|
7534
8256
|
agentboxHomeBytes,
|
|
7535
8257
|
boxResourceStats,
|
|
8258
|
+
uploadToBox,
|
|
8259
|
+
downloadFromBox,
|
|
7536
8260
|
dockerProvider,
|
|
7537
8261
|
stageClaudeStaticForUpload,
|
|
7538
8262
|
stageClaudeCredentialsForUpload,
|
|
@@ -7540,7 +8264,8 @@ export {
|
|
|
7540
8264
|
stageCodexCredentialsForUpload,
|
|
7541
8265
|
stageOpencodeStaticForUpload,
|
|
7542
8266
|
stageOpencodeCredentialsForUpload,
|
|
8267
|
+
stageOpencodeStateForUpload,
|
|
7543
8268
|
browserSessionActive,
|
|
7544
8269
|
ensureBoxBrowser
|
|
7545
8270
|
};
|
|
7546
|
-
//# sourceMappingURL=chunk-
|
|
8271
|
+
//# sourceMappingURL=chunk-NCJP5MTN.js.map
|