@madarco/agentbox 0.7.0 → 0.8.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-T727ZPRV.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-67N47KUS.js} +359 -85
- package/dist/chunk-67N47KUS.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-6OZDFNBF.js} +1084 -516
- package/dist/chunk-6OZDFNBF.js.map +1 -0
- package/dist/chunk-BGK32PZE.js +455 -0
- package/dist/chunk-BGK32PZE.js.map +1 -0
- package/dist/{chunk-7KOEFGN2.js → chunk-FODMEHD3.js} +52 -14
- package/dist/chunk-FODMEHD3.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-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-R67WMLCF.js → dist-L4LCG5SJ.js} +120 -10
- package/dist/dist-L4LCG5SJ.js.map +1 -0
- package/dist/{dist-ETCFRVPA.js → dist-LOZBWMBF.js} +44 -20
- package/dist/{dist-QZGJIBT5.js → dist-ZODPD2I6.js} +142 -74
- package/dist/dist-ZODPD2I6.js.map +1 -0
- package/dist/index.js +3563 -845
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js +18 -0
- package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js.map +1 -0
- package/package.json +4 -4
- 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 +1118 -71
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- 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/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1118 -71
- 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 +927 -36
- 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-T727ZPRV.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-LOZBWMBF.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-BGK32PZE.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";
|
|
@@ -533,7 +747,8 @@ var BUILT_IN_DEFAULTS = {
|
|
|
533
747
|
memory: 0,
|
|
534
748
|
cpus: 0,
|
|
535
749
|
pidsLimit: 0,
|
|
536
|
-
disk: ""
|
|
750
|
+
disk: "",
|
|
751
|
+
bundleDepth: void 0
|
|
537
752
|
},
|
|
538
753
|
checkpoint: {
|
|
539
754
|
maxLayers: 3
|
|
@@ -582,6 +797,10 @@ var BUILT_IN_DEFAULTS = {
|
|
|
582
797
|
maxRunningBoxes: 5,
|
|
583
798
|
idleMinutes: 5
|
|
584
799
|
},
|
|
800
|
+
queue: {
|
|
801
|
+
enabled: true,
|
|
802
|
+
maxConcurrent: 5
|
|
803
|
+
},
|
|
585
804
|
maintenance: {
|
|
586
805
|
pruneProjectConfigs: true,
|
|
587
806
|
pruneProjectConfigsEvery: 50
|
|
@@ -690,6 +909,11 @@ var KEY_REGISTRY = [
|
|
|
690
909
|
description: "Best-effort writable-layer size for new boxes, e.g. '10G'. No-op on overlay2 / the macOS engines.",
|
|
691
910
|
advanced: true
|
|
692
911
|
},
|
|
912
|
+
{
|
|
913
|
+
key: "box.bundleDepth",
|
|
914
|
+
type: "int",
|
|
915
|
+
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/)."
|
|
916
|
+
},
|
|
693
917
|
{
|
|
694
918
|
key: "claude.sessionName",
|
|
695
919
|
type: "string",
|
|
@@ -797,6 +1021,16 @@ var KEY_REGISTRY = [
|
|
|
797
1021
|
type: "int",
|
|
798
1022
|
description: "Minutes a box must be continuously idle (claude state) before it is eligible for auto-pause."
|
|
799
1023
|
},
|
|
1024
|
+
{
|
|
1025
|
+
key: "queue.enabled",
|
|
1026
|
+
type: "bool",
|
|
1027
|
+
description: "Run `agentbox claude|codex|opencode -i <prompt>` jobs through the host-wide background queue (FIFO, capped by queue.maxConcurrent)."
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
key: "queue.maxConcurrent",
|
|
1031
|
+
type: "int",
|
|
1032
|
+
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>`."
|
|
1033
|
+
},
|
|
800
1034
|
{
|
|
801
1035
|
key: "maintenance.pruneProjectConfigs",
|
|
802
1036
|
type: "bool",
|
|
@@ -818,7 +1052,7 @@ var UserConfigError = class extends Error {
|
|
|
818
1052
|
this.name = "UserConfigError";
|
|
819
1053
|
}
|
|
820
1054
|
};
|
|
821
|
-
function
|
|
1055
|
+
function isPlainObject3(v) {
|
|
822
1056
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
823
1057
|
}
|
|
824
1058
|
var RENAMED_KEYS = /* @__PURE__ */ new Map([["box.snapshot", "box.hostSnapshot"]]);
|
|
@@ -875,25 +1109,34 @@ function coerceTypedValue(raw, desc, where) {
|
|
|
875
1109
|
function parseUserConfig(text, where) {
|
|
876
1110
|
let doc;
|
|
877
1111
|
try {
|
|
878
|
-
doc =
|
|
1112
|
+
doc = parseYaml3(text);
|
|
879
1113
|
} catch (err) {
|
|
880
1114
|
throw new UserConfigError(
|
|
881
1115
|
`${where}: yaml parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
882
1116
|
);
|
|
883
1117
|
}
|
|
884
1118
|
if (doc === null || doc === void 0) return {};
|
|
885
|
-
if (!
|
|
1119
|
+
if (!isPlainObject3(doc)) {
|
|
886
1120
|
throw new UserConfigError(`${where}: top-level must be a mapping`);
|
|
887
1121
|
}
|
|
888
1122
|
return parseUserConfigObject(doc, where);
|
|
889
1123
|
}
|
|
890
1124
|
function parseUserConfigObject(doc, where) {
|
|
891
1125
|
if (doc === null || doc === void 0) return {};
|
|
892
|
-
if (!
|
|
1126
|
+
if (!isPlainObject3(doc)) {
|
|
893
1127
|
throw new UserConfigError(`${where}: must be a mapping`);
|
|
894
1128
|
}
|
|
895
1129
|
const out = {};
|
|
896
1130
|
for (const [branchName, branchRaw] of Object.entries(doc)) {
|
|
1131
|
+
if (branchName === "schema") {
|
|
1132
|
+
if (branchRaw !== void 0 && branchRaw !== null) {
|
|
1133
|
+
if (typeof branchRaw !== "number" || !Number.isInteger(branchRaw)) {
|
|
1134
|
+
throw new UserConfigError(`${where}.schema: must be an integer (got ${String(branchRaw)})`);
|
|
1135
|
+
}
|
|
1136
|
+
out.schema = branchRaw;
|
|
1137
|
+
}
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
897
1140
|
const branchSpec = BRANCHES.get(branchName);
|
|
898
1141
|
if (!branchSpec) {
|
|
899
1142
|
throw new UserConfigError(
|
|
@@ -901,7 +1144,7 @@ function parseUserConfigObject(doc, where) {
|
|
|
901
1144
|
);
|
|
902
1145
|
}
|
|
903
1146
|
if (branchRaw === null || branchRaw === void 0) continue;
|
|
904
|
-
if (!
|
|
1147
|
+
if (!isPlainObject3(branchRaw)) {
|
|
905
1148
|
throw new UserConfigError(`${where}.${branchName}: must be a mapping`);
|
|
906
1149
|
}
|
|
907
1150
|
const branchOut = {};
|
|
@@ -978,9 +1221,9 @@ function lookupKeyOrThrow(key) {
|
|
|
978
1221
|
}
|
|
979
1222
|
return desc;
|
|
980
1223
|
}
|
|
981
|
-
var
|
|
982
|
-
var GLOBAL_CONFIG_FILE = join(
|
|
983
|
-
var PROJECTS_DIR = join(
|
|
1224
|
+
var STATE_DIR2 = join(homedir(), ".agentbox");
|
|
1225
|
+
var GLOBAL_CONFIG_FILE = join(STATE_DIR2, "config.yaml");
|
|
1226
|
+
var PROJECTS_DIR = join(STATE_DIR2, "projects");
|
|
984
1227
|
var WORKSPACE_CONFIG_BASENAME = "agentbox.yaml";
|
|
985
1228
|
async function findProjectRoot(cwd) {
|
|
986
1229
|
const start = await canonicalize(cwd);
|
|
@@ -1042,7 +1285,7 @@ async function configPathFor(scope, cwd) {
|
|
|
1042
1285
|
async function loadOptionalUserConfig(path) {
|
|
1043
1286
|
let text;
|
|
1044
1287
|
try {
|
|
1045
|
-
text = await
|
|
1288
|
+
text = await readFile3(path, "utf8");
|
|
1046
1289
|
} catch (err) {
|
|
1047
1290
|
if (err.code === "ENOENT") return {};
|
|
1048
1291
|
throw err;
|
|
@@ -1053,7 +1296,7 @@ async function loadProjectAgentboxDefaults(workspacePath) {
|
|
|
1053
1296
|
const path = workspaceConfigFile(workspacePath);
|
|
1054
1297
|
let text;
|
|
1055
1298
|
try {
|
|
1056
|
-
text = await
|
|
1299
|
+
text = await readFile3(path, "utf8");
|
|
1057
1300
|
} catch (err) {
|
|
1058
1301
|
if (err.code === "ENOENT") return {};
|
|
1059
1302
|
throw err;
|
|
@@ -1161,6 +1404,7 @@ async function setConfigValue(scope, key, value, cwd, opts = {}) {
|
|
|
1161
1404
|
const path = await configPathFor(scope, cwd);
|
|
1162
1405
|
const current = await readExistingDoc(path);
|
|
1163
1406
|
setLeaf(current, key, coerced);
|
|
1407
|
+
stampSchema(current);
|
|
1164
1408
|
parseUserConfig(stringifyYaml(current), path);
|
|
1165
1409
|
await atomicWriteYaml(path, current);
|
|
1166
1410
|
if (scope === "project") {
|
|
@@ -1177,6 +1421,7 @@ async function unsetConfigValue(scope, key, cwd) {
|
|
|
1177
1421
|
const current = await readExistingDoc(path);
|
|
1178
1422
|
const existed = unsetLeaf(current, key);
|
|
1179
1423
|
if (!existed) return { path, existed: false };
|
|
1424
|
+
stampSchema(current);
|
|
1180
1425
|
await atomicWriteYaml(path, current);
|
|
1181
1426
|
if (scope === "project") {
|
|
1182
1427
|
const root = (await findProjectRoot(cwd)).root;
|
|
@@ -1287,6 +1532,12 @@ async function readExistingDoc(path) {
|
|
|
1287
1532
|
}
|
|
1288
1533
|
return parseUserConfig(text, path);
|
|
1289
1534
|
}
|
|
1535
|
+
var CURRENT_CONFIG_SCHEMA = 1;
|
|
1536
|
+
function stampSchema(doc) {
|
|
1537
|
+
if (typeof doc.schema !== "number") {
|
|
1538
|
+
doc.schema = CURRENT_CONFIG_SCHEMA;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1290
1541
|
function setLeaf(doc, key, value) {
|
|
1291
1542
|
const idx = key.indexOf(".");
|
|
1292
1543
|
const branch = key.slice(0, idx);
|
|
@@ -1341,181 +1592,7 @@ async function touchProjectMeta(absPath) {
|
|
|
1341
1592
|
// ../../packages/sandbox-docker/dist/index.js
|
|
1342
1593
|
import { chmod, mkdir as mkdir32, readFile as readFile32 } from "fs/promises";
|
|
1343
1594
|
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
|
|
1595
|
+
import { execa as execa4 } from "execa";
|
|
1519
1596
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1520
1597
|
import { stat as stat22 } from "fs/promises";
|
|
1521
1598
|
import { homedir as homedir32 } from "os";
|
|
@@ -1526,7 +1603,7 @@ import { stat as stat32 } from "fs/promises";
|
|
|
1526
1603
|
import { homedir as homedir4 } from "os";
|
|
1527
1604
|
import { join as join5 } from "path";
|
|
1528
1605
|
import { execa as execa6 } from "execa";
|
|
1529
|
-
import { randomBytes } from "crypto";
|
|
1606
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1530
1607
|
import { execa as execa7 } from "execa";
|
|
1531
1608
|
import { existsSync } from "fs";
|
|
1532
1609
|
import { readFile as readFile42 } from "fs/promises";
|
|
@@ -1534,41 +1611,202 @@ import { homedir as homedir5 } from "os";
|
|
|
1534
1611
|
import { join as join6 } from "path";
|
|
1535
1612
|
import { execa as execa8 } from "execa";
|
|
1536
1613
|
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";
|
|
1614
|
+
import { mkdir as mkdir4, readdir as readdir22, rm as rm22, stat as stat4 } from "fs/promises";
|
|
1542
1615
|
import { homedir as homedir6, platform } from "os";
|
|
1543
|
-
import { join as join7, resolve as
|
|
1616
|
+
import { join as join7, resolve as resolve2 } from "path";
|
|
1544
1617
|
import { mkdir as mkdir5, mkdtemp as mkdtemp2, readFile as readFile5, readdir as readdir32, rm as rm3, writeFile as writeFile22 } from "fs/promises";
|
|
1545
1618
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
|
|
1546
1619
|
import { basename as basename3, join as join8 } from "path";
|
|
1547
|
-
import { execa as
|
|
1620
|
+
import { execa as execa10 } from "execa";
|
|
1548
1621
|
import { stat as stat5 } from "fs/promises";
|
|
1622
|
+
import { execa as execa11 } from "execa";
|
|
1549
1623
|
import { execa as execa12 } from "execa";
|
|
1550
|
-
import { execa as execa13 } from "execa";
|
|
1551
1624
|
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";
|
|
1625
|
+
import { randomBytes as randomBytes22 } from "crypto";
|
|
1626
|
+
import { existsSync as existsSync2, openSync } from "fs";
|
|
1627
|
+
import { mkdir as mkdir6, readFile as readFile6, unlink as unlink2, writeFile as writeFile32 } from "fs/promises";
|
|
1555
1628
|
import { request as httpRequest } from "http";
|
|
1556
1629
|
import { homedir as homedir8 } from "os";
|
|
1557
|
-
import { dirname as
|
|
1630
|
+
import { dirname as dirname3, join as join9, resolve as resolve22 } from "path";
|
|
1558
1631
|
import { setTimeout as delay2 } from "timers/promises";
|
|
1559
|
-
import { fileURLToPath
|
|
1632
|
+
import { fileURLToPath } from "url";
|
|
1560
1633
|
|
|
1561
1634
|
// ../../packages/relay/dist/index.js
|
|
1562
|
-
import {
|
|
1635
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
1636
|
+
import { execa } from "execa";
|
|
1637
|
+
import { spawn as spawn4 } from "child_process";
|
|
1638
|
+
import {
|
|
1639
|
+
mkdir as mkdir2,
|
|
1640
|
+
readdir as readdir2,
|
|
1641
|
+
readFile as readFile23,
|
|
1642
|
+
rename as rename2,
|
|
1643
|
+
unlink,
|
|
1644
|
+
writeFile as writeFile2
|
|
1645
|
+
} from "fs/promises";
|
|
1646
|
+
import { join as join3 } from "path";
|
|
1563
1647
|
var DEFAULT_RELAY_PORT = 8787;
|
|
1564
1648
|
var RELAY_CONTAINER_NAME = "agentbox-relay";
|
|
1565
1649
|
var RELAY_NETWORK_NAME = "agentbox-net";
|
|
1566
1650
|
var RELAY_IMAGE_REF = "agentbox/relay:dev";
|
|
1567
1651
|
var DEFAULT_HOST_ACTION_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
1652
|
+
function hashRpcParams(params) {
|
|
1653
|
+
return createHash2("sha256").update(canonicalJson(params)).digest("hex");
|
|
1654
|
+
}
|
|
1655
|
+
function canonicalJson(v) {
|
|
1656
|
+
if (v === null) return "null";
|
|
1657
|
+
if (typeof v === "undefined") return "null";
|
|
1658
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "null";
|
|
1659
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
1660
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
1661
|
+
if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
|
|
1662
|
+
if (typeof v === "object") {
|
|
1663
|
+
const entries = Object.entries(v).filter(([k]) => k !== "hostInitiated").filter(([, val]) => val !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1664
|
+
return "{" + entries.map(([k, val]) => JSON.stringify(k) + ":" + canonicalJson(val)).join(",") + "}";
|
|
1665
|
+
}
|
|
1666
|
+
return "null";
|
|
1667
|
+
}
|
|
1668
|
+
var GH_PR_OPS = [
|
|
1669
|
+
"create",
|
|
1670
|
+
"view",
|
|
1671
|
+
"list",
|
|
1672
|
+
"comment",
|
|
1673
|
+
"review",
|
|
1674
|
+
"merge",
|
|
1675
|
+
"checkout",
|
|
1676
|
+
"close",
|
|
1677
|
+
"reopen"
|
|
1678
|
+
];
|
|
1568
1679
|
var MAX_BODY_BYTES = 1024 * 1024;
|
|
1680
|
+
var QUEUE_DIR = join3(STATE_DIR, "queue");
|
|
1681
|
+
async function loadQueueConfig() {
|
|
1682
|
+
const d = BUILT_IN_DEFAULTS.queue;
|
|
1683
|
+
let global = {};
|
|
1684
|
+
try {
|
|
1685
|
+
global = parseUserConfig(await readFile23(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
|
|
1686
|
+
} catch {
|
|
1687
|
+
}
|
|
1688
|
+
const q = global.queue ?? {};
|
|
1689
|
+
return {
|
|
1690
|
+
enabled: q.enabled ?? d.enabled,
|
|
1691
|
+
maxConcurrent: q.maxConcurrent ?? d.maxConcurrent
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
async function writeJob(job) {
|
|
1695
|
+
await mkdir2(QUEUE_DIR, { recursive: true });
|
|
1696
|
+
const final = join3(QUEUE_DIR, `${job.id}.json`);
|
|
1697
|
+
const tmp = `${final}.tmp.${String(process.pid)}.${String(Date.now())}`;
|
|
1698
|
+
await writeFile2(tmp, JSON.stringify(job, null, 2) + "\n", "utf8");
|
|
1699
|
+
await rename2(tmp, final);
|
|
1700
|
+
}
|
|
1701
|
+
async function readJob(id) {
|
|
1702
|
+
try {
|
|
1703
|
+
const raw = await readFile23(join3(QUEUE_DIR, `${id}.json`), "utf8");
|
|
1704
|
+
return JSON.parse(raw);
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
if (err.code === "ENOENT") return null;
|
|
1707
|
+
throw err;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
async function deleteJob(id) {
|
|
1711
|
+
try {
|
|
1712
|
+
await unlink(join3(QUEUE_DIR, `${id}.json`));
|
|
1713
|
+
} catch (err) {
|
|
1714
|
+
if (err.code !== "ENOENT") throw err;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
async function loadQueue() {
|
|
1718
|
+
let entries;
|
|
1719
|
+
try {
|
|
1720
|
+
entries = await readdir2(QUEUE_DIR);
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
if (err.code === "ENOENT") return [];
|
|
1723
|
+
throw err;
|
|
1724
|
+
}
|
|
1725
|
+
const out = [];
|
|
1726
|
+
for (const name of entries) {
|
|
1727
|
+
if (!name.endsWith(".json")) continue;
|
|
1728
|
+
try {
|
|
1729
|
+
const raw = await readFile23(join3(QUEUE_DIR, name), "utf8");
|
|
1730
|
+
out.push(JSON.parse(raw));
|
|
1731
|
+
} catch {
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
out.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0);
|
|
1735
|
+
return out;
|
|
1736
|
+
}
|
|
1737
|
+
var RUNNING_COUNT_CACHE_MS = 3e3;
|
|
1738
|
+
var runningCountCache = null;
|
|
1739
|
+
async function defaultCountRunningBoxes() {
|
|
1740
|
+
const now = Date.now();
|
|
1741
|
+
if (runningCountCache && runningCountCache.expiresAt > now) {
|
|
1742
|
+
return runningCountCache.value;
|
|
1743
|
+
}
|
|
1744
|
+
const value = await uncachedCountRunningBoxes();
|
|
1745
|
+
runningCountCache = { value, expiresAt: now + RUNNING_COUNT_CACHE_MS };
|
|
1746
|
+
return value;
|
|
1747
|
+
}
|
|
1748
|
+
async function uncachedCountRunningBoxes() {
|
|
1749
|
+
let boxes;
|
|
1750
|
+
try {
|
|
1751
|
+
boxes = (await readState(STATE_FILE)).boxes;
|
|
1752
|
+
} catch {
|
|
1753
|
+
return 0;
|
|
1754
|
+
}
|
|
1755
|
+
if (boxes.length === 0) return 0;
|
|
1756
|
+
let count = 0;
|
|
1757
|
+
const dockerBoxes = [];
|
|
1758
|
+
for (const b of boxes) {
|
|
1759
|
+
const provider = b.provider ?? "docker";
|
|
1760
|
+
if (provider === "docker") {
|
|
1761
|
+
dockerBoxes.push(b);
|
|
1762
|
+
} else {
|
|
1763
|
+
count += 1;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
if (dockerBoxes.length > 0) {
|
|
1767
|
+
const states = await Promise.all(dockerBoxes.map((b) => inspectDockerState(b.container)));
|
|
1768
|
+
for (const s of states) {
|
|
1769
|
+
if (s === "running") count += 1;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return count;
|
|
1773
|
+
}
|
|
1774
|
+
function inspectDockerState(containerName) {
|
|
1775
|
+
return new Promise((resolveP) => {
|
|
1776
|
+
const child = spawn4("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
|
|
1777
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1778
|
+
});
|
|
1779
|
+
let out = "";
|
|
1780
|
+
let settled = false;
|
|
1781
|
+
const finish = (state) => {
|
|
1782
|
+
if (settled) return;
|
|
1783
|
+
settled = true;
|
|
1784
|
+
resolveP(state);
|
|
1785
|
+
};
|
|
1786
|
+
const timer = setTimeout(() => {
|
|
1787
|
+
child.kill("SIGTERM");
|
|
1788
|
+
finish("other");
|
|
1789
|
+
}, 1e4);
|
|
1790
|
+
child.stdout?.on("data", (c) => {
|
|
1791
|
+
out += c.toString("utf8");
|
|
1792
|
+
});
|
|
1793
|
+
child.on("error", () => {
|
|
1794
|
+
clearTimeout(timer);
|
|
1795
|
+
finish("other");
|
|
1796
|
+
});
|
|
1797
|
+
child.on("close", () => {
|
|
1798
|
+
clearTimeout(timer);
|
|
1799
|
+
finish(out.trim() === "running" ? "running" : "other");
|
|
1800
|
+
});
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
var QUEUE_LOGS_DIR = join3(STATE_DIR, "logs");
|
|
1804
|
+
function queueLogPath(id) {
|
|
1805
|
+
return join3(QUEUE_LOGS_DIR, `queue-${id}.log`);
|
|
1806
|
+
}
|
|
1569
1807
|
|
|
1570
1808
|
// ../../packages/sandbox-docker/dist/index.js
|
|
1571
|
-
import { execa as
|
|
1809
|
+
import { execa as execa15 } from "execa";
|
|
1572
1810
|
import { readdir as readdir4, rm as rm4, stat as stat7 } from "fs/promises";
|
|
1573
1811
|
import { join as join12 } from "path";
|
|
1574
1812
|
|
|
@@ -1624,14 +1862,17 @@ var AmbiguousBoxError = class extends Error {
|
|
|
1624
1862
|
};
|
|
1625
1863
|
|
|
1626
1864
|
// ../../packages/sandbox-docker/dist/index.js
|
|
1627
|
-
import { execa as
|
|
1865
|
+
import { execa as execa14 } from "execa";
|
|
1628
1866
|
import { join as join11 } from "path";
|
|
1629
1867
|
import { homedir as homedir10 } from "os";
|
|
1630
1868
|
import { join as join13 } from "path";
|
|
1869
|
+
import { execa as execa16 } from "execa";
|
|
1870
|
+
import { existsSync as existsSync3, mkdirSync, renameSync, statSync } from "fs";
|
|
1871
|
+
import { basename as basename32, dirname as dirname22, posix, resolve as resolve4 } from "path";
|
|
1631
1872
|
import { execa as execa17 } from "execa";
|
|
1632
1873
|
import { copyFile, mkdtemp as mkdtemp3, readdir as readdir5, readFile as readFile7, rm as rm5, stat as stat8, writeFile as writeFile4 } from "fs/promises";
|
|
1633
1874
|
import { homedir as homedir11, tmpdir as tmpdir3 } from "os";
|
|
1634
|
-
import { basename as
|
|
1875
|
+
import { basename as basename4, join as join14, relative as relative2 } from "path";
|
|
1635
1876
|
import { execa as execa18 } from "execa";
|
|
1636
1877
|
function isHostPathHookCommand(command, hostHome) {
|
|
1637
1878
|
if (typeof command !== "string" || command.length === 0) return false;
|
|
@@ -1758,16 +1999,16 @@ function rewritePluginPaths(value, hostPluginsPrefix) {
|
|
|
1758
1999
|
}
|
|
1759
2000
|
return value;
|
|
1760
2001
|
}
|
|
1761
|
-
function
|
|
2002
|
+
function isPlainObject4(v) {
|
|
1762
2003
|
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1763
2004
|
}
|
|
1764
2005
|
function additiveMerge(hostRoot, boxRoot, hostPluginsPrefix, selectMap, withMap) {
|
|
1765
2006
|
const hostMap = selectMap(hostRoot);
|
|
1766
2007
|
const boxMap = selectMap(boxRoot);
|
|
1767
|
-
if (!
|
|
2008
|
+
if (!isPlainObject4(boxMap)) {
|
|
1768
2009
|
return { data: hostRoot, changed: false, addedKeys: [] };
|
|
1769
2010
|
}
|
|
1770
|
-
const base =
|
|
2011
|
+
const base = isPlainObject4(hostMap) ? { ...hostMap } : {};
|
|
1771
2012
|
const addedKeys = [];
|
|
1772
2013
|
for (const [key, value] of Object.entries(boxMap)) {
|
|
1773
2014
|
if (Object.prototype.hasOwnProperty.call(base, key)) continue;
|
|
@@ -1782,7 +2023,7 @@ function additiveMerge(hostRoot, boxRoot, hostPluginsPrefix, selectMap, withMap)
|
|
|
1782
2023
|
function mergeKnownMarketplaces(hostJson, boxJson, opts) {
|
|
1783
2024
|
const prefix = `${opts.hostHome}/.claude/plugins/`;
|
|
1784
2025
|
return additiveMerge(
|
|
1785
|
-
|
|
2026
|
+
isPlainObject4(hostJson) ? hostJson : {},
|
|
1786
2027
|
boxJson,
|
|
1787
2028
|
prefix,
|
|
1788
2029
|
(root) => root,
|
|
@@ -1791,24 +2032,24 @@ function mergeKnownMarketplaces(hostJson, boxJson, opts) {
|
|
|
1791
2032
|
}
|
|
1792
2033
|
function mergeInstalledPlugins(hostJson, boxJson, opts) {
|
|
1793
2034
|
const prefix = `${opts.hostHome}/.claude/plugins/`;
|
|
1794
|
-
const hostRoot =
|
|
2035
|
+
const hostRoot = isPlainObject4(hostJson) ? hostJson : { plugins: {} };
|
|
1795
2036
|
return additiveMerge(
|
|
1796
2037
|
hostRoot,
|
|
1797
2038
|
boxJson,
|
|
1798
2039
|
prefix,
|
|
1799
|
-
(root) =>
|
|
2040
|
+
(root) => isPlainObject4(root) ? root["plugins"] : void 0,
|
|
1800
2041
|
(host, merged) => ({ ...host, plugins: merged })
|
|
1801
2042
|
);
|
|
1802
2043
|
}
|
|
1803
2044
|
function referencedPluginVersionKeys(installedPluginsJson) {
|
|
1804
2045
|
const keys = /* @__PURE__ */ new Set();
|
|
1805
|
-
if (!
|
|
2046
|
+
if (!isPlainObject4(installedPluginsJson)) return keys;
|
|
1806
2047
|
const plugins = installedPluginsJson["plugins"];
|
|
1807
|
-
if (!
|
|
2048
|
+
if (!isPlainObject4(plugins)) return keys;
|
|
1808
2049
|
for (const entries of Object.values(plugins)) {
|
|
1809
2050
|
if (!Array.isArray(entries)) continue;
|
|
1810
2051
|
for (const entry of entries) {
|
|
1811
|
-
if (!
|
|
2052
|
+
if (!isPlainObject4(entry)) continue;
|
|
1812
2053
|
const installPath = entry["installPath"];
|
|
1813
2054
|
if (typeof installPath !== "string") continue;
|
|
1814
2055
|
const segments = installPath.split("/").filter((s) => s.length > 0);
|
|
@@ -1819,7 +2060,7 @@ function referencedPluginVersionKeys(installedPluginsJson) {
|
|
|
1819
2060
|
return keys;
|
|
1820
2061
|
}
|
|
1821
2062
|
async function dockerInfo() {
|
|
1822
|
-
const result = await
|
|
2063
|
+
const result = await execa2("docker", ["info"], { reject: false });
|
|
1823
2064
|
if (result.exitCode !== 0) {
|
|
1824
2065
|
throw new Error(
|
|
1825
2066
|
`docker info failed (exit ${String(result.exitCode)}). Is the Docker daemon running?
|
|
@@ -1866,6 +2107,9 @@ async function runBox(spec) {
|
|
|
1866
2107
|
// on the macOS engines). Boxes use it to reach the host relay process.
|
|
1867
2108
|
"--add-host=host.docker.internal:host-gateway"
|
|
1868
2109
|
];
|
|
2110
|
+
if (process.env.AGENTBOX === "1") {
|
|
2111
|
+
args.push("--user", "0");
|
|
2112
|
+
}
|
|
1869
2113
|
const lim = spec.limits;
|
|
1870
2114
|
if (lim) {
|
|
1871
2115
|
if (lim.memoryBytes && lim.memoryBytes > 0) {
|
|
@@ -1892,11 +2136,11 @@ async function runBox(spec) {
|
|
|
1892
2136
|
args.push("-e", `${k}=${val}`);
|
|
1893
2137
|
}
|
|
1894
2138
|
args.push(spec.image, "sleep", "infinity");
|
|
1895
|
-
const { stdout } = await
|
|
2139
|
+
const { stdout } = await execa2("docker", args);
|
|
1896
2140
|
return stdout.trim();
|
|
1897
2141
|
}
|
|
1898
2142
|
async function dockerStorageDriver() {
|
|
1899
|
-
const result = await
|
|
2143
|
+
const result = await execa2("docker", ["info", "--format", "{{.Driver}}"], { reject: false });
|
|
1900
2144
|
if (result.exitCode !== 0) return "";
|
|
1901
2145
|
return (result.stdout ?? "").trim();
|
|
1902
2146
|
}
|
|
@@ -1905,7 +2149,7 @@ async function execInBox(container, cmd, opts = {}) {
|
|
|
1905
2149
|
if (opts.detach) args.push("-d");
|
|
1906
2150
|
if (opts.user) args.push("--user", opts.user);
|
|
1907
2151
|
args.push(container, ...cmd);
|
|
1908
|
-
const result = await
|
|
2152
|
+
const result = await execa2("docker", args, {
|
|
1909
2153
|
reject: false,
|
|
1910
2154
|
...opts.timeoutMs ? { timeout: opts.timeoutMs } : {}
|
|
1911
2155
|
});
|
|
@@ -1916,49 +2160,49 @@ async function execInBox(container, cmd, opts = {}) {
|
|
|
1916
2160
|
};
|
|
1917
2161
|
}
|
|
1918
2162
|
async function removeContainer(container) {
|
|
1919
|
-
await
|
|
2163
|
+
await execa2("docker", ["rm", "-f", container], { reject: false });
|
|
1920
2164
|
}
|
|
1921
2165
|
async function removeVolume(name) {
|
|
1922
|
-
await
|
|
2166
|
+
await execa2("docker", ["volume", "rm", name], { reject: false });
|
|
1923
2167
|
}
|
|
1924
2168
|
async function removeImage(ref, opts = {}) {
|
|
1925
2169
|
const args = ["image", "rm"];
|
|
1926
2170
|
if (opts.force !== false) args.push("-f");
|
|
1927
2171
|
args.push(ref);
|
|
1928
|
-
const result = await
|
|
2172
|
+
const result = await execa2("docker", args, { reject: false });
|
|
1929
2173
|
return result.exitCode === 0;
|
|
1930
2174
|
}
|
|
1931
2175
|
async function containerExists(name) {
|
|
1932
|
-
const result = await
|
|
2176
|
+
const result = await execa2("docker", ["container", "inspect", "--format", "{{.Id}}", name], {
|
|
1933
2177
|
reject: false
|
|
1934
2178
|
});
|
|
1935
2179
|
return result.exitCode === 0;
|
|
1936
2180
|
}
|
|
1937
2181
|
async function volumeExists(name) {
|
|
1938
|
-
const result = await
|
|
2182
|
+
const result = await execa2("docker", ["volume", "inspect", name], { reject: false });
|
|
1939
2183
|
return result.exitCode === 0;
|
|
1940
2184
|
}
|
|
1941
2185
|
async function ensureVolume(name) {
|
|
1942
2186
|
if (await volumeExists(name)) return;
|
|
1943
|
-
await
|
|
2187
|
+
await execa2("docker", ["volume", "create", name]);
|
|
1944
2188
|
}
|
|
1945
2189
|
async function removeNetwork(name) {
|
|
1946
|
-
await
|
|
2190
|
+
await execa2("docker", ["network", "rm", name], { reject: false });
|
|
1947
2191
|
}
|
|
1948
2192
|
async function pauseContainer(name) {
|
|
1949
|
-
await
|
|
2193
|
+
await execa2("docker", ["pause", name]);
|
|
1950
2194
|
}
|
|
1951
2195
|
async function unpauseContainer(name) {
|
|
1952
|
-
await
|
|
2196
|
+
await execa2("docker", ["unpause", name]);
|
|
1953
2197
|
}
|
|
1954
2198
|
async function stopContainer(name) {
|
|
1955
|
-
await
|
|
2199
|
+
await execa2("docker", ["stop", name]);
|
|
1956
2200
|
}
|
|
1957
2201
|
async function startContainer(name) {
|
|
1958
|
-
await
|
|
2202
|
+
await execa2("docker", ["start", name]);
|
|
1959
2203
|
}
|
|
1960
2204
|
async function inspectContainerStatus(name) {
|
|
1961
|
-
const result = await
|
|
2205
|
+
const result = await execa2("docker", ["inspect", "--format", "{{.State.Status}}", name], {
|
|
1962
2206
|
reject: false
|
|
1963
2207
|
});
|
|
1964
2208
|
if (result.exitCode !== 0) return "missing";
|
|
@@ -1979,7 +2223,7 @@ async function inspectContainerStatus(name) {
|
|
|
1979
2223
|
}
|
|
1980
2224
|
}
|
|
1981
2225
|
async function inspectContainer(name) {
|
|
1982
|
-
const result = await
|
|
2226
|
+
const result = await execa2("docker", ["inspect", name], { reject: false });
|
|
1983
2227
|
if (result.exitCode !== 0) return null;
|
|
1984
2228
|
try {
|
|
1985
2229
|
const parsed = JSON.parse(result.stdout ?? "null");
|
|
@@ -1989,7 +2233,7 @@ async function inspectContainer(name) {
|
|
|
1989
2233
|
}
|
|
1990
2234
|
}
|
|
1991
2235
|
async function inspectVolumeMountpoint(name) {
|
|
1992
|
-
const result = await
|
|
2236
|
+
const result = await execa2("docker", ["volume", "inspect", "--format", "{{.Mountpoint}}", name], {
|
|
1993
2237
|
reject: false
|
|
1994
2238
|
});
|
|
1995
2239
|
if (result.exitCode !== 0) return null;
|
|
@@ -1997,7 +2241,7 @@ async function inspectVolumeMountpoint(name) {
|
|
|
1997
2241
|
}
|
|
1998
2242
|
var AGENTBOX_PREFIX = "agentbox-";
|
|
1999
2243
|
async function listAgentboxContainers() {
|
|
2000
|
-
const result = await
|
|
2244
|
+
const result = await execa2(
|
|
2001
2245
|
"docker",
|
|
2002
2246
|
["ps", "-a", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Names}}"],
|
|
2003
2247
|
{ reject: false }
|
|
@@ -2006,7 +2250,7 @@ async function listAgentboxContainers() {
|
|
|
2006
2250
|
return (result.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.startsWith(AGENTBOX_PREFIX));
|
|
2007
2251
|
}
|
|
2008
2252
|
async function publishedHostPort(container, containerPort) {
|
|
2009
|
-
const result = await
|
|
2253
|
+
const result = await execa2("docker", ["port", container, `${String(containerPort)}/tcp`], {
|
|
2010
2254
|
reject: false
|
|
2011
2255
|
});
|
|
2012
2256
|
if (result.exitCode !== 0) return null;
|
|
@@ -2016,7 +2260,7 @@ async function publishedHostPort(container, containerPort) {
|
|
|
2016
2260
|
return m ? Number(m[1]) : null;
|
|
2017
2261
|
}
|
|
2018
2262
|
async function listAgentboxVolumes() {
|
|
2019
|
-
const result = await
|
|
2263
|
+
const result = await execa2(
|
|
2020
2264
|
"docker",
|
|
2021
2265
|
["volume", "ls", "--filter", `name=^${AGENTBOX_PREFIX}`, "--format", "{{.Name}}"],
|
|
2022
2266
|
{ reject: false }
|
|
@@ -2388,6 +2632,123 @@ var ExportError = class extends Error {
|
|
|
2388
2632
|
stdout;
|
|
2389
2633
|
stderr;
|
|
2390
2634
|
};
|
|
2635
|
+
async function copyCarryPathsToBox(opts) {
|
|
2636
|
+
const log = opts.onLog ?? (() => {
|
|
2637
|
+
});
|
|
2638
|
+
let copied = 0;
|
|
2639
|
+
const errors = [];
|
|
2640
|
+
const applied = [];
|
|
2641
|
+
for (const [i, entry] of opts.entries.entries()) {
|
|
2642
|
+
const where = `carry[${String(i)}] "${entry.rawSrc}"`;
|
|
2643
|
+
if (entry.kind === "missing") {
|
|
2644
|
+
log(`${where}: skipped (missing on host, optional)`);
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
try {
|
|
2648
|
+
await copyOneEntry(opts.container, entry);
|
|
2649
|
+
copied += 1;
|
|
2650
|
+
applied.push({ src: entry.absSrc, dest: entry.absDest, bytes: entry.bytes ?? 0 });
|
|
2651
|
+
} catch (err) {
|
|
2652
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2653
|
+
errors.push(`${where}: ${msg}`);
|
|
2654
|
+
log(`${where}: failed: ${msg}`);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return { copied, errors, applied };
|
|
2658
|
+
}
|
|
2659
|
+
var BOX_HOME = "/home/vscode";
|
|
2660
|
+
async function copyOneEntry(container, entry) {
|
|
2661
|
+
if (entry.kind === "missing") return;
|
|
2662
|
+
const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
|
|
2663
|
+
const boxDestParent = boxDest.endsWith("/") ? boxDest.slice(0, -1) : boxDest;
|
|
2664
|
+
const parentDir = entry.kind === "dir" ? boxDestParent : dirnameUnix(boxDestParent);
|
|
2665
|
+
const mkdir8 = await execa22(
|
|
2666
|
+
"docker",
|
|
2667
|
+
["exec", "--user", "0:0", container, "mkdir", "-p", parentDir],
|
|
2668
|
+
{ reject: false }
|
|
2669
|
+
);
|
|
2670
|
+
if (mkdir8.exitCode !== 0) {
|
|
2671
|
+
throw new Error(`mkdir -p ${parentDir} failed: ${String(mkdir8.stderr).slice(0, 300)}`);
|
|
2672
|
+
}
|
|
2673
|
+
if (entry.kind === "file") {
|
|
2674
|
+
const cp = await execa22(
|
|
2675
|
+
"docker",
|
|
2676
|
+
["cp", entry.absSrc, `${container}:${boxDest}`],
|
|
2677
|
+
{ reject: false }
|
|
2678
|
+
);
|
|
2679
|
+
if (cp.exitCode !== 0) {
|
|
2680
|
+
throw new Error(`docker cp failed: ${String(cp.stderr).slice(0, 300)}`);
|
|
2681
|
+
}
|
|
2682
|
+
} else {
|
|
2683
|
+
const packed = await execa22(
|
|
2684
|
+
"tar",
|
|
2685
|
+
["-C", entry.absSrc, "-cf", "-", "."],
|
|
2686
|
+
{ encoding: "buffer", reject: false }
|
|
2687
|
+
);
|
|
2688
|
+
if (packed.exitCode !== 0) {
|
|
2689
|
+
throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
|
|
2690
|
+
}
|
|
2691
|
+
const extract = await execa22(
|
|
2692
|
+
"docker",
|
|
2693
|
+
[
|
|
2694
|
+
"exec",
|
|
2695
|
+
"-i",
|
|
2696
|
+
"--user",
|
|
2697
|
+
"0:0",
|
|
2698
|
+
container,
|
|
2699
|
+
"tar",
|
|
2700
|
+
"-xf",
|
|
2701
|
+
"-",
|
|
2702
|
+
"-C",
|
|
2703
|
+
boxDest,
|
|
2704
|
+
"--no-same-permissions",
|
|
2705
|
+
"--no-same-owner",
|
|
2706
|
+
"-m"
|
|
2707
|
+
],
|
|
2708
|
+
{ input: packed.stdout, reject: false }
|
|
2709
|
+
);
|
|
2710
|
+
if (extract.exitCode !== 0) {
|
|
2711
|
+
throw new Error(`tar extract failed: ${String(extract.stderr).slice(0, 300)}`);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
if (entry.mode !== void 0) {
|
|
2715
|
+
const modeStr = entry.mode.toString(8).padStart(4, "0");
|
|
2716
|
+
const chmod2 = await execa22(
|
|
2717
|
+
"docker",
|
|
2718
|
+
["exec", "--user", "0:0", container, "chmod", "-R", modeStr, boxDest],
|
|
2719
|
+
{ reject: false }
|
|
2720
|
+
);
|
|
2721
|
+
if (chmod2.exitCode !== 0) {
|
|
2722
|
+
throw new Error(`chmod failed: ${String(chmod2.stderr).slice(0, 300)}`);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
const uid = entry.user ?? 1e3;
|
|
2726
|
+
const chown = await execa22(
|
|
2727
|
+
"docker",
|
|
2728
|
+
["exec", "--user", "0:0", container, "chown", "-R", `${String(uid)}:${String(uid)}`, boxDest],
|
|
2729
|
+
{ reject: false }
|
|
2730
|
+
);
|
|
2731
|
+
if (chown.exitCode !== 0) {
|
|
2732
|
+
throw new Error(`chown failed: ${String(chown.stderr).slice(0, 300)}`);
|
|
2733
|
+
}
|
|
2734
|
+
if (boxDest.startsWith(BOX_HOME + "/") && dirnameUnix(boxDest) !== BOX_HOME) {
|
|
2735
|
+
const safeDest = boxDest.replace(/'/g, `'\\''`);
|
|
2736
|
+
const script = `set -e; parent="$(dirname '${safeDest}')"; while [ "$parent" != "${BOX_HOME}" ] && [ "$parent" != "/" ]; do chown ${String(uid)}:${String(uid)} "$parent"; parent="$(dirname "$parent")"; done`;
|
|
2737
|
+
const chownParents = await execa22(
|
|
2738
|
+
"docker",
|
|
2739
|
+
["exec", "--user", "0:0", container, "bash", "-c", script],
|
|
2740
|
+
{ reject: false }
|
|
2741
|
+
);
|
|
2742
|
+
if (chownParents.exitCode !== 0) {
|
|
2743
|
+
throw new Error(`chown parents failed: ${String(chownParents.stderr).slice(0, 300)}`);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
function dirnameUnix(p) {
|
|
2748
|
+
const i = p.lastIndexOf("/");
|
|
2749
|
+
if (i <= 0) return "/";
|
|
2750
|
+
return p.slice(0, i);
|
|
2751
|
+
}
|
|
2391
2752
|
var SHARED_CLAUDE_VOLUME = "agentbox-claude-config";
|
|
2392
2753
|
var DEFAULT_CLAUDE_SESSION = "claude";
|
|
2393
2754
|
var CONTAINER_CLAUDE_DIR = "/home/vscode/.claude";
|
|
@@ -2403,7 +2764,7 @@ function resolveClaudeVolume(opts) {
|
|
|
2403
2764
|
}
|
|
2404
2765
|
async function pathExists(p) {
|
|
2405
2766
|
try {
|
|
2406
|
-
await
|
|
2767
|
+
await stat3(p);
|
|
2407
2768
|
return true;
|
|
2408
2769
|
} catch {
|
|
2409
2770
|
return false;
|
|
@@ -2427,10 +2788,10 @@ async function findBrokenSymlinks(root) {
|
|
|
2427
2788
|
return;
|
|
2428
2789
|
}
|
|
2429
2790
|
for (const ent of entries) {
|
|
2430
|
-
const full =
|
|
2791
|
+
const full = join22(dir, ent.name);
|
|
2431
2792
|
if (ent.isSymbolicLink()) {
|
|
2432
2793
|
try {
|
|
2433
|
-
await
|
|
2794
|
+
await stat3(full);
|
|
2434
2795
|
} catch {
|
|
2435
2796
|
broken.push(relative(root, full));
|
|
2436
2797
|
}
|
|
@@ -2447,13 +2808,13 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2447
2808
|
await ensureVolume(spec.volume);
|
|
2448
2809
|
const created = !existed;
|
|
2449
2810
|
if (!opts.syncFromHost) return { created, synced: false };
|
|
2450
|
-
const hostClaude =
|
|
2811
|
+
const hostClaude = join22(homedir2(), ".claude");
|
|
2451
2812
|
if (!await pathExists(hostClaude)) return { created, synced: false };
|
|
2452
|
-
const hostClaudeJson =
|
|
2813
|
+
const hostClaudeJson = join22(homedir2(), ".claude.json");
|
|
2453
2814
|
const hasJson = await pathExists(hostClaudeJson);
|
|
2454
2815
|
const seedClaudeJson = !await volumeHasClaudeJson(spec.volume, opts.image);
|
|
2455
|
-
const hostHome =
|
|
2456
|
-
const hostAgents =
|
|
2816
|
+
const hostHome = homedir2();
|
|
2817
|
+
const hostAgents = join22(homedir2(), ".agents");
|
|
2457
2818
|
const hasAgents = await pathExists(hostAgents);
|
|
2458
2819
|
const args = [
|
|
2459
2820
|
"run",
|
|
@@ -2471,15 +2832,15 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2471
2832
|
];
|
|
2472
2833
|
if (hasJson && seedClaudeJson) args.push("-v", `${hostClaudeJson}:/src-claude-json:ro`);
|
|
2473
2834
|
if (hasAgents) args.push("-v", `${hostAgents}:/.agents:ro`);
|
|
2474
|
-
const filterDir = await mkdtemp(
|
|
2835
|
+
const filterDir = await mkdtemp(join22(tmpdir(), "agentbox-claude-filter-"));
|
|
2475
2836
|
let filteredHookCount = 0;
|
|
2476
2837
|
let installMethodFixed = false;
|
|
2477
2838
|
let aliasedProjectKey = false;
|
|
2478
2839
|
let workspaceTrusted = false;
|
|
2479
2840
|
try {
|
|
2480
2841
|
const settingsResult = await maybeFilterTo(
|
|
2481
|
-
|
|
2482
|
-
|
|
2842
|
+
join22(hostClaude, "settings.json"),
|
|
2843
|
+
join22(filterDir, "settings.json"),
|
|
2483
2844
|
hostHome
|
|
2484
2845
|
);
|
|
2485
2846
|
filteredHookCount += settingsResult.removedHooks;
|
|
@@ -2487,7 +2848,7 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2487
2848
|
} else if (hasJson) {
|
|
2488
2849
|
const jsonResult = await maybeFilterTo(
|
|
2489
2850
|
hostClaudeJson,
|
|
2490
|
-
|
|
2851
|
+
join22(filterDir, "_claude.json"),
|
|
2491
2852
|
hostHome,
|
|
2492
2853
|
{
|
|
2493
2854
|
setInstallMethodNative: true,
|
|
@@ -2501,7 +2862,7 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
2501
2862
|
workspaceTrusted = jsonResult.workspaceTrusted;
|
|
2502
2863
|
} else {
|
|
2503
2864
|
await writeFile3(
|
|
2504
|
-
|
|
2865
|
+
join22(filterDir, "_claude.json"),
|
|
2505
2866
|
JSON.stringify(
|
|
2506
2867
|
{
|
|
2507
2868
|
installMethod: "native",
|
|
@@ -2603,7 +2964,7 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
|
|
|
2603
2964
|
};
|
|
2604
2965
|
let parsed;
|
|
2605
2966
|
try {
|
|
2606
|
-
parsed = JSON.parse(await
|
|
2967
|
+
parsed = JSON.parse(await readFile24(src, "utf8"));
|
|
2607
2968
|
} catch {
|
|
2608
2969
|
return zero;
|
|
2609
2970
|
}
|
|
@@ -2664,14 +3025,14 @@ var PLUGIN_INSTALL_BACKOFF_MIN = Math.round(PLUGIN_INSTALL_BACKOFF_MS / 6e4);
|
|
|
2664
3025
|
var NPM_CACHE_DIR = "/home/vscode/.claude/.agentbox-npm-cache";
|
|
2665
3026
|
async function isFile(p) {
|
|
2666
3027
|
try {
|
|
2667
|
-
return (await
|
|
3028
|
+
return (await stat3(p)).isFile();
|
|
2668
3029
|
} catch {
|
|
2669
3030
|
return false;
|
|
2670
3031
|
}
|
|
2671
3032
|
}
|
|
2672
3033
|
async function isRecentFailMarker(p) {
|
|
2673
3034
|
try {
|
|
2674
|
-
const st = await
|
|
3035
|
+
const st = await stat3(p);
|
|
2675
3036
|
return Date.now() - st.mtimeMs < PLUGIN_INSTALL_BACKOFF_MS;
|
|
2676
3037
|
} catch {
|
|
2677
3038
|
return false;
|
|
@@ -2679,14 +3040,14 @@ async function isRecentFailMarker(p) {
|
|
|
2679
3040
|
}
|
|
2680
3041
|
async function isDir(p) {
|
|
2681
3042
|
try {
|
|
2682
|
-
return (await
|
|
3043
|
+
return (await stat3(p)).isDirectory();
|
|
2683
3044
|
} catch {
|
|
2684
3045
|
return false;
|
|
2685
3046
|
}
|
|
2686
3047
|
}
|
|
2687
3048
|
async function readReferencedPluginKeys(installedPluginsJsonPath) {
|
|
2688
3049
|
try {
|
|
2689
|
-
const raw = await
|
|
3050
|
+
const raw = await readFile24(installedPluginsJsonPath, "utf8");
|
|
2690
3051
|
return referencedPluginVersionKeys(JSON.parse(raw));
|
|
2691
3052
|
} catch {
|
|
2692
3053
|
return /* @__PURE__ */ new Set();
|
|
@@ -2694,7 +3055,7 @@ async function readReferencedPluginKeys(installedPluginsJsonPath) {
|
|
|
2694
3055
|
}
|
|
2695
3056
|
async function scanPluginCacheForRebuild(cacheRoot) {
|
|
2696
3057
|
const referenced = await readReferencedPluginKeys(
|
|
2697
|
-
|
|
3058
|
+
join22(cacheRoot, "..", "installed_plugins.json")
|
|
2698
3059
|
);
|
|
2699
3060
|
let marketplaces;
|
|
2700
3061
|
try {
|
|
@@ -2704,7 +3065,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
|
|
|
2704
3065
|
}
|
|
2705
3066
|
for (const m of marketplaces) {
|
|
2706
3067
|
if (!m.isDirectory()) continue;
|
|
2707
|
-
const mPath =
|
|
3068
|
+
const mPath = join22(cacheRoot, m.name);
|
|
2708
3069
|
let plugins;
|
|
2709
3070
|
try {
|
|
2710
3071
|
plugins = await readdir3(mPath, { withFileTypes: true });
|
|
@@ -2713,7 +3074,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
|
|
|
2713
3074
|
}
|
|
2714
3075
|
for (const p of plugins) {
|
|
2715
3076
|
if (!p.isDirectory()) continue;
|
|
2716
|
-
const pPath =
|
|
3077
|
+
const pPath = join22(mPath, p.name);
|
|
2717
3078
|
let versions;
|
|
2718
3079
|
try {
|
|
2719
3080
|
versions = await readdir3(pPath, { withFileTypes: true });
|
|
@@ -2723,10 +3084,10 @@ async function scanPluginCacheForRebuild(cacheRoot) {
|
|
|
2723
3084
|
for (const v of versions) {
|
|
2724
3085
|
if (!v.isDirectory()) continue;
|
|
2725
3086
|
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(
|
|
3087
|
+
const vPath = join22(pPath, v.name);
|
|
3088
|
+
if (!await isFile(join22(vPath, "package.json"))) continue;
|
|
3089
|
+
if (await isFile(join22(vPath, PLUGIN_INSTALLED_MARKER))) continue;
|
|
3090
|
+
if (await isRecentFailMarker(join22(vPath, PLUGIN_FAILED_MARKER))) continue;
|
|
2730
3091
|
return true;
|
|
2731
3092
|
}
|
|
2732
3093
|
}
|
|
@@ -3018,67 +3379,31 @@ async function waitForTmuxPaneContent(container, sessionName, timeoutMs = 2e4) {
|
|
|
3018
3379
|
await delay(400);
|
|
3019
3380
|
}
|
|
3020
3381
|
}
|
|
3021
|
-
function
|
|
3022
|
-
const s = sessionName;
|
|
3382
|
+
function tmuxConfigSubcommands(sessionName) {
|
|
3023
3383
|
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"
|
|
3384
|
+
["set", "-g", "prefix", "C-a"],
|
|
3385
|
+
["set", "-g", "prefix2", "C-b"],
|
|
3386
|
+
["bind-key", "C-a", "send-prefix"],
|
|
3387
|
+
["bind-key", "C-b", "send-prefix", "-2"],
|
|
3388
|
+
["bind-key", "d", "detach-client"],
|
|
3389
|
+
["set", "-g", "extended-keys", "on"],
|
|
3390
|
+
["set", "-as", "terminal-features", ",*:extkeys"],
|
|
3391
|
+
["set", "-t", sessionName, "status", "off"]
|
|
3080
3392
|
];
|
|
3081
3393
|
}
|
|
3394
|
+
function buildTmuxSessionArgs(sessionName) {
|
|
3395
|
+
const out = [];
|
|
3396
|
+
for (const sub of tmuxConfigSubcommands(sessionName)) {
|
|
3397
|
+
out.push(";", ...sub);
|
|
3398
|
+
}
|
|
3399
|
+
return out;
|
|
3400
|
+
}
|
|
3401
|
+
function buildTmuxConfigShellSnippet(sessionName) {
|
|
3402
|
+
return tmuxConfigSubcommands(sessionName).map((sub) => `tmux ${sub.map(shellSingleQuoteIfNeeded).join(" ")}`).join("; ");
|
|
3403
|
+
}
|
|
3404
|
+
function shellSingleQuoteIfNeeded(s) {
|
|
3405
|
+
return /^[A-Za-z0-9_:.\/=+-]+$/.test(s) ? s : "'" + s.replace(/'/g, "'\\''") + "'";
|
|
3406
|
+
}
|
|
3082
3407
|
function buildShellArgv(container) {
|
|
3083
3408
|
const term = process.env["TERM"] ?? "xterm-256color";
|
|
3084
3409
|
return ["exec", "-it", "-e", `TERM=${term}`, "--user", CONTAINER_USER, container, "bash", "-l"];
|
|
@@ -3197,14 +3522,14 @@ async function listChildDirs(dir) {
|
|
|
3197
3522
|
}
|
|
3198
3523
|
async function readJsonFile(path) {
|
|
3199
3524
|
try {
|
|
3200
|
-
return JSON.parse(await
|
|
3525
|
+
return JSON.parse(await readFile24(path, "utf8"));
|
|
3201
3526
|
} catch {
|
|
3202
3527
|
return void 0;
|
|
3203
3528
|
}
|
|
3204
3529
|
}
|
|
3205
3530
|
async function pullClaudeExtras(spec, opts) {
|
|
3206
|
-
const hostHome =
|
|
3207
|
-
const hostClaude =
|
|
3531
|
+
const hostHome = homedir2();
|
|
3532
|
+
const hostClaude = join22(hostHome, ".claude");
|
|
3208
3533
|
const inventoryScript = [
|
|
3209
3534
|
"for cat in skills agents commands; do",
|
|
3210
3535
|
' [ -d "/src/$cat" ] || continue;',
|
|
@@ -3266,7 +3591,7 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3266
3591
|
const newItems = [];
|
|
3267
3592
|
const applyPaths = [];
|
|
3268
3593
|
for (const cat of PULL_DIR_CATEGORIES) {
|
|
3269
|
-
const hostNames = await listChildDirs(
|
|
3594
|
+
const hostNames = await listChildDirs(join22(hostClaude, cat));
|
|
3270
3595
|
const excludes = cat === "skills" ? SKILL_EXCLUDE_PREFIXES : [];
|
|
3271
3596
|
for (const name of pickNewItems(boxDirs[cat] ?? [], hostNames, excludes)) {
|
|
3272
3597
|
newItems.push({ category: cat, name });
|
|
@@ -3274,8 +3599,8 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3274
3599
|
}
|
|
3275
3600
|
}
|
|
3276
3601
|
const hostPluginKeys = [];
|
|
3277
|
-
for (const m of await listChildDirs(
|
|
3278
|
-
for (const p of await listChildDirs(
|
|
3602
|
+
for (const m of await listChildDirs(join22(hostClaude, "plugins", "cache"))) {
|
|
3603
|
+
for (const p of await listChildDirs(join22(hostClaude, "plugins", "cache", m))) {
|
|
3279
3604
|
hostPluginKeys.push(`${m}/${p}`);
|
|
3280
3605
|
}
|
|
3281
3606
|
}
|
|
@@ -3283,8 +3608,8 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3283
3608
|
newItems.push({ category: "plugins", name: key });
|
|
3284
3609
|
applyPaths.push({ src: `/src/plugins/cache/${key}`, dest: `/dst/plugins/cache/${key}` });
|
|
3285
3610
|
}
|
|
3286
|
-
const hostInstalled = await readJsonFile(
|
|
3287
|
-
const hostMarkets = await readJsonFile(
|
|
3611
|
+
const hostInstalled = await readJsonFile(join22(hostClaude, "plugins", "installed_plugins.json"));
|
|
3612
|
+
const hostMarkets = await readJsonFile(join22(hostClaude, "plugins", "known_marketplaces.json"));
|
|
3288
3613
|
const mergedInstalled = mergeInstalledPlugins(hostInstalled, boxJson["installed_plugins"], {
|
|
3289
3614
|
hostHome
|
|
3290
3615
|
});
|
|
@@ -3327,17 +3652,17 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3327
3652
|
}
|
|
3328
3653
|
}
|
|
3329
3654
|
if (mergedMarkets.changed || mergedInstalled.changed) {
|
|
3330
|
-
await mkdir22(
|
|
3655
|
+
await mkdir22(join22(hostClaude, "plugins"), { recursive: true });
|
|
3331
3656
|
if (mergedMarkets.changed) {
|
|
3332
3657
|
await writeFile3(
|
|
3333
|
-
|
|
3658
|
+
join22(hostClaude, "plugins", "known_marketplaces.json"),
|
|
3334
3659
|
`${JSON.stringify(mergedMarkets.data, null, 2)}
|
|
3335
3660
|
`
|
|
3336
3661
|
);
|
|
3337
3662
|
}
|
|
3338
3663
|
if (mergedInstalled.changed) {
|
|
3339
3664
|
await writeFile3(
|
|
3340
|
-
|
|
3665
|
+
join22(hostClaude, "plugins", "installed_plugins.json"),
|
|
3341
3666
|
`${JSON.stringify(mergedInstalled.data, null, 2)}
|
|
3342
3667
|
`
|
|
3343
3668
|
);
|
|
@@ -3345,7 +3670,7 @@ async function pullClaudeExtras(spec, opts) {
|
|
|
3345
3670
|
}
|
|
3346
3671
|
return { newItems, mergedRegistries };
|
|
3347
3672
|
}
|
|
3348
|
-
var CREDENTIALS_BACKUP_FILE = join32(
|
|
3673
|
+
var CREDENTIALS_BACKUP_FILE = join32(STATE_DIR, "claude-credentials.json");
|
|
3349
3674
|
async function hostBackupHasCredentials(path = CREDENTIALS_BACKUP_FILE) {
|
|
3350
3675
|
try {
|
|
3351
3676
|
const parsed = JSON.parse(await readFile32(path, "utf8"));
|
|
@@ -3377,8 +3702,8 @@ echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
|
|
|
3377
3702
|
`;
|
|
3378
3703
|
async function syncClaudeCredentials(spec, opts) {
|
|
3379
3704
|
try {
|
|
3380
|
-
await mkdir32(
|
|
3381
|
-
const { stdout } = await
|
|
3705
|
+
await mkdir32(STATE_DIR, { recursive: true });
|
|
3706
|
+
const { stdout } = await execa4("docker", [
|
|
3382
3707
|
"run",
|
|
3383
3708
|
"--rm",
|
|
3384
3709
|
"--user",
|
|
@@ -3386,7 +3711,7 @@ async function syncClaudeCredentials(spec, opts) {
|
|
|
3386
3711
|
"-v",
|
|
3387
3712
|
`${spec.volume}:/dst`,
|
|
3388
3713
|
"-v",
|
|
3389
|
-
`${
|
|
3714
|
+
`${STATE_DIR}:/host-state`,
|
|
3390
3715
|
"-e",
|
|
3391
3716
|
`ISOLATE=${opts.isolate ? "yes" : "no"}`,
|
|
3392
3717
|
opts.image,
|
|
@@ -3529,9 +3854,10 @@ ${(install.stdout ?? "").toString().slice(-600)}`
|
|
|
3529
3854
|
}
|
|
3530
3855
|
return { installed: true };
|
|
3531
3856
|
}
|
|
3857
|
+
var CODEX_AGENTBOX_FLAGS = ["--enable", "hooks", "--dangerously-bypass-hook-trust"];
|
|
3532
3858
|
async function startCodexSession(opts) {
|
|
3533
3859
|
const sessionName = opts.sessionName ?? DEFAULT_CODEX_SESSION;
|
|
3534
|
-
const cmd = ["codex", ...opts.codexArgs].map(shQuote2).join(" ");
|
|
3860
|
+
const cmd = ["codex", ...CODEX_AGENTBOX_FLAGS, ...opts.codexArgs].map(shQuote2).join(" ");
|
|
3535
3861
|
const term = process.env["TERM"] ?? "xterm-256color";
|
|
3536
3862
|
const envFlags = ["-e", `TERM=${term}`];
|
|
3537
3863
|
for (const k of CODEX_FORWARDED_ENV_KEYS) {
|
|
@@ -3726,6 +4052,8 @@ var SHARED_OPENCODE_VOLUME = "agentbox-opencode-config";
|
|
|
3726
4052
|
var DEFAULT_OPENCODE_SESSION = "opencode";
|
|
3727
4053
|
var CONTAINER_OPENCODE_DIR = "/home/vscode/.local/share/opencode";
|
|
3728
4054
|
var CONTAINER_OPENCODE_CONFIG_DIR = "/home/vscode/.local/share/opencode/config";
|
|
4055
|
+
var CONTAINER_OPENCODE_STATE_HOME = "/home/vscode/.local/share/opencode/.state";
|
|
4056
|
+
var IN_BOX_OPENCODE_PLUGIN_PATH = "/usr/local/share/agentbox/opencode-agentbox-plugin.js";
|
|
3729
4057
|
function resolveOpencodeVolume(opts) {
|
|
3730
4058
|
if (opts.isolate) {
|
|
3731
4059
|
return { volume: `${SHARED_OPENCODE_VOLUME}-${opts.boxId}` };
|
|
@@ -3751,13 +4079,16 @@ async function ensureOpencodeVolume(spec, opts) {
|
|
|
3751
4079
|
const created = !existed;
|
|
3752
4080
|
const hostData = join5(homedir4(), ".local", "share", "opencode");
|
|
3753
4081
|
const hostConfig = join5(homedir4(), ".config", "opencode");
|
|
4082
|
+
const hostState = join5(homedir4(), ".local", "state", "opencode");
|
|
3754
4083
|
const hasData = await pathExists3(hostData);
|
|
3755
4084
|
const hasConfig = await pathExists3(hostConfig);
|
|
3756
|
-
const
|
|
4085
|
+
const hasState = await pathExists3(hostState);
|
|
4086
|
+
const willSync = opts.syncFromHost && (hasData || hasConfig || hasState);
|
|
3757
4087
|
if (willSync) {
|
|
3758
4088
|
const args = ["run", "--rm", "--user", "0", "-v", `${spec.volume}:/dst`];
|
|
3759
4089
|
if (hasData) args.push("-v", `${hostData}:/src-data:ro`);
|
|
3760
4090
|
if (hasConfig) args.push("-v", `${hostConfig}:/src-config:ro`);
|
|
4091
|
+
if (hasState) args.push("-v", `${hostState}:/src-state:ro`);
|
|
3761
4092
|
const steps = [];
|
|
3762
4093
|
if (hasData) {
|
|
3763
4094
|
steps.push(
|
|
@@ -3767,6 +4098,11 @@ async function ensureOpencodeVolume(spec, opts) {
|
|
|
3767
4098
|
if (hasConfig) {
|
|
3768
4099
|
steps.push("mkdir -p /dst/config && rsync -a /src-config/ /dst/config/");
|
|
3769
4100
|
}
|
|
4101
|
+
if (hasState) {
|
|
4102
|
+
steps.push(
|
|
4103
|
+
"mkdir -p /dst/.state/opencode && rsync -a --update --exclude=locks /src-state/ /dst/.state/opencode/"
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
3770
4106
|
steps.push("chown -R 1000:1000 /dst");
|
|
3771
4107
|
args.push(opts.image, "sh", "-c", steps.join(" && "));
|
|
3772
4108
|
await execa6("docker", args);
|
|
@@ -3790,6 +4126,25 @@ async function ensureOpencodeVolume(spec, opts) {
|
|
|
3790
4126
|
);
|
|
3791
4127
|
return { created, synced: false };
|
|
3792
4128
|
}
|
|
4129
|
+
async function seedOpencodePlugin(volume, image) {
|
|
4130
|
+
try {
|
|
4131
|
+
const { stdout } = await execa6("docker", [
|
|
4132
|
+
"run",
|
|
4133
|
+
"--rm",
|
|
4134
|
+
"--user",
|
|
4135
|
+
"0",
|
|
4136
|
+
"-v",
|
|
4137
|
+
`${volume}:/dst`,
|
|
4138
|
+
image,
|
|
4139
|
+
"sh",
|
|
4140
|
+
"-c",
|
|
4141
|
+
`{ [ -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`
|
|
4142
|
+
]);
|
|
4143
|
+
return { seeded: stdout.includes("SEEDED") };
|
|
4144
|
+
} catch {
|
|
4145
|
+
return { seeded: false };
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
3793
4148
|
var OPENCODE_FORWARDED_ENV_KEYS = [
|
|
3794
4149
|
"ANTHROPIC_API_KEY",
|
|
3795
4150
|
"OPENAI_API_KEY",
|
|
@@ -3800,7 +4155,10 @@ var OPENCODE_FORWARDED_ENV_KEYS = [
|
|
|
3800
4155
|
"GROQ_API_KEY"
|
|
3801
4156
|
];
|
|
3802
4157
|
function buildOpencodeMounts(spec, hostEnv) {
|
|
3803
|
-
const env = {
|
|
4158
|
+
const env = {
|
|
4159
|
+
OPENCODE_CONFIG_DIR: CONTAINER_OPENCODE_CONFIG_DIR,
|
|
4160
|
+
XDG_STATE_HOME: CONTAINER_OPENCODE_STATE_HOME
|
|
4161
|
+
};
|
|
3804
4162
|
for (const k of OPENCODE_FORWARDED_ENV_KEYS) {
|
|
3805
4163
|
const v = hostEnv[k];
|
|
3806
4164
|
if (typeof v === "string" && v.length > 0) env[k] = v;
|
|
@@ -3915,6 +4273,8 @@ function buildOpencodeLoginRunArgv(opts) {
|
|
|
3915
4273
|
"DISPLAY=",
|
|
3916
4274
|
"-e",
|
|
3917
4275
|
`OPENCODE_CONFIG_DIR=${CONTAINER_OPENCODE_CONFIG_DIR}`,
|
|
4276
|
+
"-e",
|
|
4277
|
+
`XDG_STATE_HOME=${CONTAINER_OPENCODE_STATE_HOME}`,
|
|
3918
4278
|
"-v",
|
|
3919
4279
|
`${opts.volume}:${CONTAINER_OPENCODE_DIR}`,
|
|
3920
4280
|
"--user",
|
|
@@ -4105,7 +4465,7 @@ async function launchVncDaemon(container, timeoutMs = 5e3) {
|
|
|
4105
4465
|
}
|
|
4106
4466
|
var VNC_PASSWORD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
4107
4467
|
function generateVncPassword() {
|
|
4108
|
-
const bytes =
|
|
4468
|
+
const bytes = randomBytes2(8);
|
|
4109
4469
|
let out = "";
|
|
4110
4470
|
for (let i = 0; i < 8; i++) {
|
|
4111
4471
|
out += VNC_PASSWORD_ALPHABET[bytes[i] % VNC_PASSWORD_ALPHABET.length];
|
|
@@ -4124,6 +4484,10 @@ function buildVncUrls(record, engine) {
|
|
|
4124
4484
|
if (record.vncHostPort) {
|
|
4125
4485
|
urls.loopbackUrl = `http://127.0.0.1:${String(record.vncHostPort)}/vnc.html?${qs}`;
|
|
4126
4486
|
}
|
|
4487
|
+
if (record.portlessVncAlias) {
|
|
4488
|
+
const base = record.portlessVncUrl ?? `https://${record.portlessVncAlias}.localhost`;
|
|
4489
|
+
urls.portlessUrl = `${base}/vnc.html?${qs}`;
|
|
4490
|
+
}
|
|
4127
4491
|
return urls;
|
|
4128
4492
|
}
|
|
4129
4493
|
var WEB_CONTAINER_PORT = 80;
|
|
@@ -4206,6 +4570,7 @@ async function seedWorkspace(opts) {
|
|
|
4206
4570
|
for (const r of opts.repos) {
|
|
4207
4571
|
const main = r.repo.hostMainRepo;
|
|
4208
4572
|
const wt = r.gitWorktreePath;
|
|
4573
|
+
const baseRef = r.repo.kind === "root" ? opts.fromBranch ?? "HEAD" : "HEAD";
|
|
4209
4574
|
const add = await execa7(
|
|
4210
4575
|
"docker",
|
|
4211
4576
|
[
|
|
@@ -4221,7 +4586,7 @@ async function seedWorkspace(opts) {
|
|
|
4221
4586
|
"-b",
|
|
4222
4587
|
r.branch,
|
|
4223
4588
|
wt,
|
|
4224
|
-
|
|
4589
|
+
baseRef
|
|
4225
4590
|
],
|
|
4226
4591
|
{ reject: false }
|
|
4227
4592
|
);
|
|
@@ -4515,79 +4880,6 @@ async function isProxyRunning() {
|
|
|
4515
4880
|
return false;
|
|
4516
4881
|
}
|
|
4517
4882
|
}
|
|
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
4883
|
var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
4592
4884
|
"node_modules",
|
|
4593
4885
|
".next",
|
|
@@ -4633,12 +4925,12 @@ async function findExcludedDirs(root, excluded = EXCLUDE_DIRS) {
|
|
|
4633
4925
|
return matches;
|
|
4634
4926
|
}
|
|
4635
4927
|
async function createSnapshot(opts) {
|
|
4636
|
-
const source =
|
|
4637
|
-
const destination =
|
|
4928
|
+
const source = resolve2(opts.source);
|
|
4929
|
+
const destination = resolve2(opts.destination);
|
|
4638
4930
|
const excluded = opts.excluded ?? EXCLUDE_DIRS;
|
|
4639
4931
|
await mkdir4(SNAPSHOTS_ROOT, { recursive: true });
|
|
4640
4932
|
const cpArgs = platform() === "darwin" ? ["-cR"] : ["-R"];
|
|
4641
|
-
await
|
|
4933
|
+
await execa9("cp", [...cpArgs, `${source}/`, destination]);
|
|
4642
4934
|
const toPrune = await findExcludedDirs(destination, excluded);
|
|
4643
4935
|
await Promise.all(toPrune.map((p) => rm22(p, { recursive: true, force: true })));
|
|
4644
4936
|
return { destination, prunedPaths: toPrune };
|
|
@@ -4659,7 +4951,7 @@ async function readManifest(dir) {
|
|
|
4659
4951
|
try {
|
|
4660
4952
|
const raw = await readFile5(join8(dir, "manifest.json"), "utf8");
|
|
4661
4953
|
const m = JSON.parse(raw);
|
|
4662
|
-
if (m.schema !== 2) return null;
|
|
4954
|
+
if (m.schema !== 2 && m.schema !== 3) return null;
|
|
4663
4955
|
return m;
|
|
4664
4956
|
} catch {
|
|
4665
4957
|
return null;
|
|
@@ -4747,7 +5039,7 @@ async function runCleanup(container, log) {
|
|
|
4747
5039
|
}
|
|
4748
5040
|
}
|
|
4749
5041
|
async function inspectImageConfig(imageRef) {
|
|
4750
|
-
const r = await
|
|
5042
|
+
const r = await execa10("docker", ["image", "inspect", imageRef], { reject: false });
|
|
4751
5043
|
if (r.exitCode !== 0) {
|
|
4752
5044
|
throw new CheckpointError(`docker image inspect ${imageRef} failed`, r.stdout, r.stderr);
|
|
4753
5045
|
}
|
|
@@ -4823,14 +5115,14 @@ async function createCheckpoint(opts) {
|
|
|
4823
5115
|
await runCleanup(box.container, log);
|
|
4824
5116
|
if (type === "layered") {
|
|
4825
5117
|
log(`docker commit ${box.container} -> ${tag} (layered)`);
|
|
4826
|
-
const r = await
|
|
5118
|
+
const r = await execa10("docker", ["commit", box.container, tag], { reject: false });
|
|
4827
5119
|
if (r.exitCode !== 0) {
|
|
4828
5120
|
throw new CheckpointError(`docker commit failed for ${box.container}`, r.stdout, r.stderr);
|
|
4829
5121
|
}
|
|
4830
5122
|
} else {
|
|
4831
5123
|
log(`docker commit ${box.container} -> <intermediate> (flattened path)`);
|
|
4832
5124
|
const intermediate = `${tag}-intermediate`;
|
|
4833
|
-
const commit = await
|
|
5125
|
+
const commit = await execa10("docker", ["commit", box.container, intermediate], {
|
|
4834
5126
|
reject: false
|
|
4835
5127
|
});
|
|
4836
5128
|
if (commit.exitCode !== 0) {
|
|
@@ -4843,8 +5135,15 @@ async function createCheckpoint(opts) {
|
|
|
4843
5135
|
}
|
|
4844
5136
|
}
|
|
4845
5137
|
const base = (box.gitWorktrees ?? []).some((w) => w.kind === "root") ? "worktree" : "workspace";
|
|
5138
|
+
const prepared = readPreparedDockerState();
|
|
5139
|
+
let baseFingerprint = prepared?.base?.contextSha256;
|
|
5140
|
+
if (!baseFingerprint) {
|
|
5141
|
+
const fp = await computeDockerContextFingerprint();
|
|
5142
|
+
baseFingerprint = fp?.contextSha256;
|
|
5143
|
+
}
|
|
5144
|
+
const stamp = readCliStamp();
|
|
4846
5145
|
const manifest = {
|
|
4847
|
-
schema:
|
|
5146
|
+
schema: 3,
|
|
4848
5147
|
name,
|
|
4849
5148
|
type,
|
|
4850
5149
|
image: tag,
|
|
@@ -4854,6 +5153,9 @@ async function createCheckpoint(opts) {
|
|
|
4854
5153
|
sourceBoxId: box.id,
|
|
4855
5154
|
sourceBoxName: box.name,
|
|
4856
5155
|
worktrees: box.gitWorktrees,
|
|
5156
|
+
baseProvider: "docker",
|
|
5157
|
+
baseFingerprint,
|
|
5158
|
+
cliVersion: stamp.cliVersion,
|
|
4857
5159
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4858
5160
|
};
|
|
4859
5161
|
await writeFile22(join8(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
@@ -4865,7 +5167,7 @@ async function createCheckpoint(opts) {
|
|
|
4865
5167
|
}
|
|
4866
5168
|
async function flattenImage(sourceTag, destTag, log) {
|
|
4867
5169
|
const tmpName = `agentbox-flatten-${Date.now().toString(36)}`;
|
|
4868
|
-
const create = await
|
|
5170
|
+
const create = await execa10(
|
|
4869
5171
|
"docker",
|
|
4870
5172
|
["create", "--name", tmpName, sourceTag, "sleep", "0"],
|
|
4871
5173
|
{ reject: false }
|
|
@@ -4877,7 +5179,7 @@ async function flattenImage(sourceTag, destTag, log) {
|
|
|
4877
5179
|
try {
|
|
4878
5180
|
const rootfsPath = join8(scratch, "rootfs.tar");
|
|
4879
5181
|
log(`exporting rootfs of ${sourceTag} to ${rootfsPath}`);
|
|
4880
|
-
const exp = await
|
|
5182
|
+
const exp = await execa10("docker", ["export", "-o", rootfsPath, tmpName], { reject: false });
|
|
4881
5183
|
if (exp.exitCode !== 0) {
|
|
4882
5184
|
throw new CheckpointError(`docker export failed`, exp.stdout, exp.stderr);
|
|
4883
5185
|
}
|
|
@@ -4890,7 +5192,7 @@ async function flattenImage(sourceTag, destTag, log) {
|
|
|
4890
5192
|
];
|
|
4891
5193
|
await writeFile22(join8(scratch, "Dockerfile"), lines.join("\n") + "\n", "utf8");
|
|
4892
5194
|
log(`building flattened ${destTag} from rootfs.tar (FROM scratch)`);
|
|
4893
|
-
const build = await
|
|
5195
|
+
const build = await execa10(
|
|
4894
5196
|
"docker",
|
|
4895
5197
|
["build", "-t", destTag, "-f", join8(scratch, "Dockerfile"), scratch],
|
|
4896
5198
|
{ reject: false }
|
|
@@ -4899,7 +5201,7 @@ async function flattenImage(sourceTag, destTag, log) {
|
|
|
4899
5201
|
throw new CheckpointError(`flatten docker build failed`, build.stdout, build.stderr);
|
|
4900
5202
|
}
|
|
4901
5203
|
} finally {
|
|
4902
|
-
await
|
|
5204
|
+
await execa10("docker", ["rm", "-f", tmpName], { reject: false });
|
|
4903
5205
|
await rm3(scratch, { recursive: true, force: true });
|
|
4904
5206
|
}
|
|
4905
5207
|
}
|
|
@@ -4943,7 +5245,7 @@ async function pathExists4(p) {
|
|
|
4943
5245
|
}
|
|
4944
5246
|
async function writeBoxEnvFile(container, env) {
|
|
4945
5247
|
const body = formatBoxEnvBody(env);
|
|
4946
|
-
const result = await
|
|
5248
|
+
const result = await execa11(
|
|
4947
5249
|
"docker",
|
|
4948
5250
|
["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
|
|
4949
5251
|
{ input: body, reject: false }
|
|
@@ -4967,7 +5269,7 @@ function shellSingleQuote(s) {
|
|
|
4967
5269
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4968
5270
|
}
|
|
4969
5271
|
async function ensureHomeOwnedByVscode(container) {
|
|
4970
|
-
await
|
|
5272
|
+
await execa12(
|
|
4971
5273
|
"docker",
|
|
4972
5274
|
[
|
|
4973
5275
|
"exec",
|
|
@@ -5017,7 +5319,7 @@ async function ensureRelay(opts = {}) {
|
|
|
5017
5319
|
return ENDPOINT;
|
|
5018
5320
|
}
|
|
5019
5321
|
if (existingPid !== null) {
|
|
5020
|
-
await
|
|
5322
|
+
await unlink2(PID_FILE).catch(() => {
|
|
5021
5323
|
});
|
|
5022
5324
|
}
|
|
5023
5325
|
const relayBin = resolveRelayBin();
|
|
@@ -5053,16 +5355,16 @@ async function ensureRelay(opts = {}) {
|
|
|
5053
5355
|
}
|
|
5054
5356
|
function resolveRelayBin() {
|
|
5055
5357
|
const override = process.env.AGENTBOX_RELAY_BIN;
|
|
5056
|
-
if (override &&
|
|
5057
|
-
const
|
|
5358
|
+
if (override && existsSync2(override)) return override;
|
|
5359
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
5058
5360
|
const candidates = [
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5361
|
+
resolve22(here, "..", "runtime", "relay", "bin.cjs"),
|
|
5362
|
+
resolve22(here, "..", "..", "relay", "dist", "bin.cjs"),
|
|
5363
|
+
resolve22(here, "..", "..", "..", "@agentbox", "relay", "dist", "bin.cjs"),
|
|
5364
|
+
resolve22(here, "..", "..", "node_modules", "@agentbox", "relay", "dist", "bin.cjs")
|
|
5063
5365
|
];
|
|
5064
5366
|
for (const c of candidates) {
|
|
5065
|
-
if (
|
|
5367
|
+
if (existsSync2(c)) return c;
|
|
5066
5368
|
}
|
|
5067
5369
|
throw new Error(
|
|
5068
5370
|
`could not locate @agentbox/relay bin; tried:
|
|
@@ -5071,17 +5373,17 @@ function resolveRelayBin() {
|
|
|
5071
5373
|
}
|
|
5072
5374
|
function resolveCliEntry() {
|
|
5073
5375
|
const override = process.env.AGENTBOX_CLI_ENTRY;
|
|
5074
|
-
if (override &&
|
|
5075
|
-
const
|
|
5376
|
+
if (override && existsSync2(override)) return override;
|
|
5377
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
5076
5378
|
const candidates = [
|
|
5077
5379
|
// Bundled CLI (dev + published): this module IS bundled into the CLI
|
|
5078
5380
|
// entry, so the entry is index.js next to this file.
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5381
|
+
resolve22(here, "index.js"),
|
|
5382
|
+
resolve22(here, "..", "..", "..", "apps", "cli", "dist", "index.js"),
|
|
5383
|
+
resolve22(here, "..", "..", "..", "..", "dist", "index.js")
|
|
5082
5384
|
];
|
|
5083
5385
|
for (const c of candidates) {
|
|
5084
|
-
if (
|
|
5386
|
+
if (existsSync2(c)) return c;
|
|
5085
5387
|
}
|
|
5086
5388
|
return null;
|
|
5087
5389
|
}
|
|
@@ -5091,7 +5393,7 @@ async function stopRelay() {
|
|
|
5091
5393
|
return { stopped: false, pid: null };
|
|
5092
5394
|
}
|
|
5093
5395
|
if (!await processAlive(pid)) {
|
|
5094
|
-
await
|
|
5396
|
+
await unlink2(PID_FILE).catch(() => {
|
|
5095
5397
|
});
|
|
5096
5398
|
return { stopped: false, pid };
|
|
5097
5399
|
}
|
|
@@ -5109,7 +5411,7 @@ async function stopRelay() {
|
|
|
5109
5411
|
} catch {
|
|
5110
5412
|
}
|
|
5111
5413
|
}
|
|
5112
|
-
await
|
|
5414
|
+
await unlink2(PID_FILE).catch(() => {
|
|
5113
5415
|
});
|
|
5114
5416
|
return { stopped: true, pid };
|
|
5115
5417
|
}
|
|
@@ -5200,7 +5502,7 @@ async function processAlive(pid) {
|
|
|
5200
5502
|
}
|
|
5201
5503
|
}
|
|
5202
5504
|
function generateRelayToken() {
|
|
5203
|
-
return
|
|
5505
|
+
return randomBytes22(32).toString("hex");
|
|
5204
5506
|
}
|
|
5205
5507
|
async function registerBoxWithRelay(args) {
|
|
5206
5508
|
const worktrees = (args.worktrees ?? []).map((w) => ({
|
|
@@ -5249,6 +5551,20 @@ async function clearRelayNotice(boxId, id) {
|
|
|
5249
5551
|
} catch {
|
|
5250
5552
|
}
|
|
5251
5553
|
}
|
|
5554
|
+
async function mintHostInitiatedToken(boxId, method, paramsHash, ttlMs) {
|
|
5555
|
+
try {
|
|
5556
|
+
const body = await adminPostForJson("/admin/host-initiated/mint", {
|
|
5557
|
+
boxId,
|
|
5558
|
+
method,
|
|
5559
|
+
paramsHash,
|
|
5560
|
+
...typeof ttlMs === "number" ? { ttlMs } : {}
|
|
5561
|
+
});
|
|
5562
|
+
const token = body?.token;
|
|
5563
|
+
return typeof token === "string" && token.length > 0 ? token : null;
|
|
5564
|
+
} catch {
|
|
5565
|
+
return null;
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5252
5568
|
async function adminPost(path, body) {
|
|
5253
5569
|
const json = JSON.stringify(body);
|
|
5254
5570
|
await new Promise((resolveP, rejectP) => {
|
|
@@ -5501,7 +5817,7 @@ function generateBoxId() {
|
|
|
5501
5817
|
return randomBytes3(4).toString("hex");
|
|
5502
5818
|
}
|
|
5503
5819
|
function sanitizeBasename(workspacePath) {
|
|
5504
|
-
const raw = basename2(
|
|
5820
|
+
const raw = basename2(resolve3(workspacePath));
|
|
5505
5821
|
return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
|
|
5506
5822
|
}
|
|
5507
5823
|
function defaultBoxName(workspacePath, id) {
|
|
@@ -5532,7 +5848,7 @@ async function buildIdentityMounts() {
|
|
|
5532
5848
|
async function createBox(opts) {
|
|
5533
5849
|
const log = opts.onLog ?? (() => {
|
|
5534
5850
|
});
|
|
5535
|
-
const workspace =
|
|
5851
|
+
const workspace = resolve3(opts.workspacePath);
|
|
5536
5852
|
if (!await pathExists5(workspace)) {
|
|
5537
5853
|
throw new Error(`workspace does not exist: ${workspace}`);
|
|
5538
5854
|
}
|
|
@@ -5567,6 +5883,27 @@ async function createBox(opts) {
|
|
|
5567
5883
|
log(
|
|
5568
5884
|
`starting from checkpoint ${opts.checkpointRef} (${head.manifest.type}, ${String(chain.length)} layer(s), image ${head.manifest.image})`
|
|
5569
5885
|
);
|
|
5886
|
+
const ckptFingerprint = head.manifest.baseFingerprint;
|
|
5887
|
+
const ckptCliVersion = head.manifest.cliVersion ?? "unknown";
|
|
5888
|
+
if (head.manifest.schema === 2) {
|
|
5889
|
+
log(
|
|
5890
|
+
`WARNING: checkpoint '${opts.checkpointRef}' was captured before checkpoint versioning landed.
|
|
5891
|
+
Its base-image layers may be older than your current base image. If the box is missing
|
|
5892
|
+
expected updates, remove the checkpoint with \`agentbox checkpoint rm ${opts.checkpointRef}\` and recreate it.`
|
|
5893
|
+
);
|
|
5894
|
+
} else {
|
|
5895
|
+
const prepared = readPreparedDockerState();
|
|
5896
|
+
const currentFingerprint = prepared?.base?.contextSha256 ?? (await computeDockerContextFingerprint())?.contextSha256;
|
|
5897
|
+
if (ckptFingerprint && currentFingerprint && ckptFingerprint !== currentFingerprint) {
|
|
5898
|
+
log(
|
|
5899
|
+
`WARNING: checkpoint '${opts.checkpointRef}' was captured against an older base image.
|
|
5900
|
+
captured: cli ${ckptCliVersion}, fingerprint ${ckptFingerprint.slice(0, 12)}
|
|
5901
|
+
current : cli ${prepared?.base?.cliVersion ?? "unknown"}, fingerprint ${currentFingerprint.slice(0, 12)}
|
|
5902
|
+
The restored box will keep the old base layers and will NOT include base-image updates.
|
|
5903
|
+
To pick up updates: \`agentbox checkpoint rm ${opts.checkpointRef}\` and recreate from a fresh box.`
|
|
5904
|
+
);
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5570
5907
|
}
|
|
5571
5908
|
const imageRef = checkpointImage ?? opts.image ?? DEFAULT_BOX_IMAGE;
|
|
5572
5909
|
const ensureRef = checkpointImage ? opts.image ?? DEFAULT_BOX_IMAGE : imageRef;
|
|
@@ -5716,6 +6053,8 @@ async function createBox(opts) {
|
|
|
5716
6053
|
if (opencodeEnsured.synced) log(`synced ${opencodeSpec.volume} from ~/.config + ~/.local/share opencode`);
|
|
5717
6054
|
else if (opencodeEnsured.created) log(`created empty volume ${opencodeSpec.volume} (no host opencode)`);
|
|
5718
6055
|
else log(`reusing volume ${opencodeSpec.volume}`);
|
|
6056
|
+
const opencodePlugin = await seedOpencodePlugin(opencodeSpec.volume, ensureRef);
|
|
6057
|
+
if (opencodePlugin.seeded) log(`seeded agentbox-state plugin into ${opencodeSpec.volume}`);
|
|
5719
6058
|
opencodeMounts = buildOpencodeMounts(opencodeSpec, process.env);
|
|
5720
6059
|
opencodeConfigVolume = opencodeSpec.volume;
|
|
5721
6060
|
}
|
|
@@ -5771,10 +6110,15 @@ async function createBox(opts) {
|
|
|
5771
6110
|
}
|
|
5772
6111
|
}
|
|
5773
6112
|
const relayEnv = relayUp ? {
|
|
5774
|
-
//
|
|
5775
|
-
//
|
|
5776
|
-
|
|
5777
|
-
|
|
6113
|
+
// The in-box ctl client always talks to its own in-box relay/forwarder
|
|
6114
|
+
// on AGENTBOX_BOX_RELAY_PORT (default 8788). For docker boxes the
|
|
6115
|
+
// forwarder transparently proxies to the host relay at
|
|
6116
|
+
// host.docker.internal:8787 (the matching `--add-host` is set in
|
|
6117
|
+
// runBox). This keeps :8787 inside the box free for a nested
|
|
6118
|
+
// agentbox to claim its own host relay.
|
|
6119
|
+
AGENTBOX_RELAY_URL: `http://127.0.0.1:8788`,
|
|
6120
|
+
AGENTBOX_RELAY_TOKEN: relayToken,
|
|
6121
|
+
AGENTBOX_HOST_RELAY_URL: `http://host.docker.internal:8787`
|
|
5778
6122
|
} : {};
|
|
5779
6123
|
const vncEnabled = opts.vnc?.enabled !== false;
|
|
5780
6124
|
const vncPassword = vncEnabled ? generateVncPassword() : void 0;
|
|
@@ -5839,7 +6183,12 @@ async function createBox(opts) {
|
|
|
5839
6183
|
if (!checkpointImage) {
|
|
5840
6184
|
if (repoCarryOvers.length > 0) {
|
|
5841
6185
|
try {
|
|
5842
|
-
await seedWorkspace({
|
|
6186
|
+
await seedWorkspace({
|
|
6187
|
+
container: containerName,
|
|
6188
|
+
repos: repoCarryOvers,
|
|
6189
|
+
fromBranch: opts.fromBranch,
|
|
6190
|
+
onLog: log
|
|
6191
|
+
});
|
|
5843
6192
|
log("seeded /workspace from in-container git worktree(s)");
|
|
5844
6193
|
} catch (err) {
|
|
5845
6194
|
log(
|
|
@@ -5878,7 +6227,7 @@ async function createBox(opts) {
|
|
|
5878
6227
|
}
|
|
5879
6228
|
if (opts.withPlaywright) {
|
|
5880
6229
|
log("installing @playwright/cli@latest (--with-playwright)");
|
|
5881
|
-
const result = await
|
|
6230
|
+
const result = await execa13(
|
|
5882
6231
|
"docker",
|
|
5883
6232
|
[
|
|
5884
6233
|
"exec",
|
|
@@ -5923,6 +6272,20 @@ async function createBox(opts) {
|
|
|
5923
6272
|
log(`copied ${String(copied)}/${String(opts.envFilesToImport.length)} selected env/config file(s)`);
|
|
5924
6273
|
}
|
|
5925
6274
|
}
|
|
6275
|
+
let carrySummary;
|
|
6276
|
+
if (opts.carry && opts.carry.length > 0) {
|
|
6277
|
+
log(`carry: copying ${String(opts.carry.length)} host path(s) into the box`);
|
|
6278
|
+
const result = await copyCarryPathsToBox({
|
|
6279
|
+
container: containerName,
|
|
6280
|
+
entries: opts.carry,
|
|
6281
|
+
onLog: log
|
|
6282
|
+
});
|
|
6283
|
+
log(`carry: copied ${String(result.copied)}/${String(opts.carry.length)} entry/entries`);
|
|
6284
|
+
for (const err of result.errors) log(`carry: ${err}`);
|
|
6285
|
+
if (result.applied.length > 0) {
|
|
6286
|
+
carrySummary = { count: result.applied.length, entries: result.applied };
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
5926
6289
|
let vncHostPort = null;
|
|
5927
6290
|
if (vncEnabled) {
|
|
5928
6291
|
const vnc = await launchVncDaemon(containerName);
|
|
@@ -5939,7 +6302,9 @@ async function createBox(opts) {
|
|
|
5939
6302
|
}
|
|
5940
6303
|
let portlessAliasName;
|
|
5941
6304
|
let portlessUrl;
|
|
5942
|
-
|
|
6305
|
+
let portlessVncAliasName;
|
|
6306
|
+
let portlessVncUrl;
|
|
6307
|
+
if (opts.portless === true && (webHostPort || vncEnabled && vncHostPort)) {
|
|
5943
6308
|
try {
|
|
5944
6309
|
const engine = await detectEngine();
|
|
5945
6310
|
if (engine === "orbstack") {
|
|
@@ -5948,15 +6313,29 @@ async function createBox(opts) {
|
|
|
5948
6313
|
const portless = await detectPortless();
|
|
5949
6314
|
if (!portless.installed) {
|
|
5950
6315
|
log("portless not installed \u2014 run `npm install -g portless` for a <name>.localhost URL");
|
|
5951
|
-
} else
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6316
|
+
} else {
|
|
6317
|
+
if (webHostPort) {
|
|
6318
|
+
if (await portlessAlias(name, webHostPort)) {
|
|
6319
|
+
portlessAliasName = name;
|
|
6320
|
+
portlessUrl = await portlessGetUrl(name);
|
|
6321
|
+
log(`portless alias ${portlessUrl} -> 127.0.0.1:${String(webHostPort)}`);
|
|
6322
|
+
} else {
|
|
6323
|
+
log("portless alias failed (best-effort) \u2014 box still reachable on the loopback URL");
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
if (vncEnabled && vncHostPort) {
|
|
6327
|
+
const vncAlias = `vnc-${name}`;
|
|
6328
|
+
if (await portlessAlias(vncAlias, vncHostPort)) {
|
|
6329
|
+
portlessVncAliasName = vncAlias;
|
|
6330
|
+
portlessVncUrl = await portlessGetUrl(vncAlias);
|
|
6331
|
+
log(`portless alias ${portlessVncUrl} -> 127.0.0.1:${String(vncHostPort)}`);
|
|
6332
|
+
} else {
|
|
6333
|
+
log("portless vnc alias failed (best-effort) \u2014 VNC still reachable on the loopback URL");
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
if (!portless.proxyRunning && (portlessAliasName || portlessVncAliasName)) {
|
|
5956
6337
|
log(`portless proxy not running \u2014 start it with \`${portlessStartHint()}\``);
|
|
5957
6338
|
}
|
|
5958
|
-
} else {
|
|
5959
|
-
log("portless alias failed (best-effort) \u2014 box still reachable on the loopback URL");
|
|
5960
6339
|
}
|
|
5961
6340
|
}
|
|
5962
6341
|
} catch (err) {
|
|
@@ -5980,6 +6359,7 @@ async function createBox(opts) {
|
|
|
5980
6359
|
gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : void 0,
|
|
5981
6360
|
withPlaywright: opts.withPlaywright ? true : void 0,
|
|
5982
6361
|
withEnv: opts.withEnv ? true : void 0,
|
|
6362
|
+
carry: carrySummary,
|
|
5983
6363
|
vncEnabled: vncEnabled ? true : void 0,
|
|
5984
6364
|
vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : void 0,
|
|
5985
6365
|
vncHostPort: vncHostPort ?? void 0,
|
|
@@ -5988,6 +6368,8 @@ async function createBox(opts) {
|
|
|
5988
6368
|
webHostPort: webHostPort ?? void 0,
|
|
5989
6369
|
portlessAlias: portlessAliasName,
|
|
5990
6370
|
portlessUrl,
|
|
6371
|
+
portlessVncAlias: portlessVncAliasName,
|
|
6372
|
+
portlessVncUrl,
|
|
5991
6373
|
dockerVolume,
|
|
5992
6374
|
dockerCacheShared: dockerCacheShared || void 0,
|
|
5993
6375
|
projectRoot: opts.projectRoot,
|
|
@@ -6046,7 +6428,7 @@ function parseShellSessionList(stdout) {
|
|
|
6046
6428
|
return out;
|
|
6047
6429
|
}
|
|
6048
6430
|
async function listShellSessions(container, user) {
|
|
6049
|
-
const res = await
|
|
6431
|
+
const res = await execa14(
|
|
6050
6432
|
"docker",
|
|
6051
6433
|
[
|
|
6052
6434
|
"exec",
|
|
@@ -6070,7 +6452,7 @@ async function startShellSession(opts) {
|
|
|
6070
6452
|
const login = opts.login !== false;
|
|
6071
6453
|
const term = process.env["TERM"] ?? "xterm-256color";
|
|
6072
6454
|
const cmd = login ? "bash -l" : "bash";
|
|
6073
|
-
const result = await
|
|
6455
|
+
const result = await execa14(
|
|
6074
6456
|
"docker",
|
|
6075
6457
|
[
|
|
6076
6458
|
"exec",
|
|
@@ -6119,7 +6501,7 @@ function buildShellSessionAttachArgv(container, sessionName, user) {
|
|
|
6119
6501
|
}
|
|
6120
6502
|
async function shellSessionInfo(container, sessionName, user) {
|
|
6121
6503
|
const name = sessionName ?? DEFAULT_SHELL_SESSION;
|
|
6122
|
-
const has = await
|
|
6504
|
+
const has = await execa14(
|
|
6123
6505
|
"docker",
|
|
6124
6506
|
["exec", "--user", user ?? CONTAINER_USER, container, "tmux", "has-session", "-t", name],
|
|
6125
6507
|
{ reject: false }
|
|
@@ -6127,7 +6509,7 @@ async function shellSessionInfo(container, sessionName, user) {
|
|
|
6127
6509
|
return { running: has.exitCode === 0, sessionName: name };
|
|
6128
6510
|
}
|
|
6129
6511
|
async function killShellSession(container, sessionName, user) {
|
|
6130
|
-
const res = await
|
|
6512
|
+
const res = await execa14(
|
|
6131
6513
|
"docker",
|
|
6132
6514
|
[
|
|
6133
6515
|
"exec",
|
|
@@ -6149,7 +6531,7 @@ async function getBoxEndpoints(record, engine, persisted) {
|
|
|
6149
6531
|
const endpoints = [];
|
|
6150
6532
|
if (record.vncEnabled && record.vncPassword) {
|
|
6151
6533
|
const vncUrls = buildVncUrls(record, engine);
|
|
6152
|
-
const url = vncUrls.orbUrl ?? vncUrls.loopbackUrl;
|
|
6534
|
+
const url = engine === "orbstack" ? vncUrls.orbUrl ?? vncUrls.loopbackUrl : vncUrls.portlessUrl ?? vncUrls.loopbackUrl;
|
|
6153
6535
|
endpoints.push({
|
|
6154
6536
|
kind: "vnc",
|
|
6155
6537
|
name: "vnc",
|
|
@@ -6216,18 +6598,30 @@ async function listBoxes() {
|
|
|
6216
6598
|
const portlessWebUrl = b.portlessAlias !== void 0 ? b.portlessUrl ?? `https://${b.portlessAlias}.localhost` : void 0;
|
|
6217
6599
|
const cachedWebUrl = webPort > 0 ? b.cloud?.previewUrls?.[webPort] : void 0;
|
|
6218
6600
|
const webUrl = portlessWebUrl ?? cachedWebUrl;
|
|
6601
|
+
const portlessVncBase = b.portlessVncAlias !== void 0 ? b.portlessVncUrl ?? `https://${b.portlessVncAlias}.localhost` : void 0;
|
|
6602
|
+
const vncUrl = portlessVncBase && b.vncPassword ? `${portlessVncBase}/vnc.html?autoconnect=1&password=${encodeURIComponent(b.vncPassword)}` : void 0;
|
|
6603
|
+
const cloudEndpoints = [];
|
|
6604
|
+
if (webUrl) {
|
|
6605
|
+
cloudEndpoints.push({
|
|
6606
|
+
kind: "web",
|
|
6607
|
+
name: "web",
|
|
6608
|
+
containerPort: webPort,
|
|
6609
|
+
url: webUrl,
|
|
6610
|
+
reachable: true
|
|
6611
|
+
});
|
|
6612
|
+
}
|
|
6613
|
+
if (b.vncEnabled && b.vncPassword) {
|
|
6614
|
+
cloudEndpoints.push({
|
|
6615
|
+
kind: "vnc",
|
|
6616
|
+
name: "vnc",
|
|
6617
|
+
containerPort: b.vncContainerPort ?? 6080,
|
|
6618
|
+
...vncUrl ? { url: vncUrl, reachable: true } : { reachable: false }
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6219
6621
|
const endpoints2 = {
|
|
6220
6622
|
domain: webUrl ? safeHost(webUrl) : "",
|
|
6221
6623
|
domainIsOrb: false,
|
|
6222
|
-
endpoints:
|
|
6223
|
-
{
|
|
6224
|
-
kind: "web",
|
|
6225
|
-
name: "web",
|
|
6226
|
-
containerPort: webPort,
|
|
6227
|
-
url: webUrl,
|
|
6228
|
-
reachable: true
|
|
6229
|
-
}
|
|
6230
|
-
] : []
|
|
6624
|
+
endpoints: cloudEndpoints
|
|
6231
6625
|
};
|
|
6232
6626
|
return {
|
|
6233
6627
|
...b,
|
|
@@ -6351,19 +6745,31 @@ async function startBox(idOrName) {
|
|
|
6351
6745
|
box.webHostPort = freshWebPort;
|
|
6352
6746
|
await recordBox(box);
|
|
6353
6747
|
}
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6748
|
+
}
|
|
6749
|
+
if (box.portlessAlias && box.webHostPort || box.portlessVncAlias && box.vncHostPort) {
|
|
6750
|
+
try {
|
|
6751
|
+
const portless = await detectPortless();
|
|
6752
|
+
if (portless.installed) {
|
|
6753
|
+
let dirty = false;
|
|
6754
|
+
if (box.portlessAlias && box.webHostPort) {
|
|
6358
6755
|
await portlessAlias(box.portlessAlias, box.webHostPort);
|
|
6359
6756
|
const url = await portlessGetUrl(box.portlessAlias);
|
|
6360
6757
|
if (url !== box.portlessUrl) {
|
|
6361
6758
|
box.portlessUrl = url;
|
|
6362
|
-
|
|
6759
|
+
dirty = true;
|
|
6363
6760
|
}
|
|
6364
6761
|
}
|
|
6365
|
-
|
|
6762
|
+
if (box.portlessVncAlias && box.vncHostPort) {
|
|
6763
|
+
await portlessAlias(box.portlessVncAlias, box.vncHostPort);
|
|
6764
|
+
const url = await portlessGetUrl(box.portlessVncAlias);
|
|
6765
|
+
if (url !== box.portlessVncUrl) {
|
|
6766
|
+
box.portlessVncUrl = url;
|
|
6767
|
+
dirty = true;
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
if (dirty) await recordBox(box);
|
|
6366
6771
|
}
|
|
6772
|
+
} catch {
|
|
6367
6773
|
}
|
|
6368
6774
|
}
|
|
6369
6775
|
if (box.relayToken) {
|
|
@@ -6395,7 +6801,7 @@ async function getBoxHostPaths(idOrName) {
|
|
|
6395
6801
|
}
|
|
6396
6802
|
async function dirSizeBytes(path) {
|
|
6397
6803
|
try {
|
|
6398
|
-
const result = await
|
|
6804
|
+
const result = await execa15("du", ["-sk", path], { reject: false });
|
|
6399
6805
|
if (result.exitCode !== 0) return null;
|
|
6400
6806
|
const sizeKb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
|
|
6401
6807
|
if (Number.isNaN(sizeKb)) return null;
|
|
@@ -6463,6 +6869,12 @@ async function destroyBox(idOrName, opts = {}) {
|
|
|
6463
6869
|
} catch {
|
|
6464
6870
|
}
|
|
6465
6871
|
}
|
|
6872
|
+
if (box.portlessVncAlias) {
|
|
6873
|
+
try {
|
|
6874
|
+
await portlessUnalias(box.portlessVncAlias);
|
|
6875
|
+
} catch {
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6466
6878
|
const ownsWorktrees = !box.checkpointImage;
|
|
6467
6879
|
if (ownsWorktrees) {
|
|
6468
6880
|
for (const w of box.gitWorktrees ?? []) {
|
|
@@ -6537,7 +6949,7 @@ async function listBoxDirs() {
|
|
|
6537
6949
|
}
|
|
6538
6950
|
}
|
|
6539
6951
|
async function listCheckpointImageTags() {
|
|
6540
|
-
const r = await
|
|
6952
|
+
const r = await execa15(
|
|
6541
6953
|
"docker",
|
|
6542
6954
|
["image", "ls", "--format", "{{.Repository}}:{{.Tag}}", `${CHECKPOINT_IMAGE_PREFIX}*`],
|
|
6543
6955
|
{ reject: false }
|
|
@@ -6645,7 +7057,7 @@ async function pruneBoxes(opts = {}) {
|
|
|
6645
7057
|
} catch {
|
|
6646
7058
|
}
|
|
6647
7059
|
try {
|
|
6648
|
-
await
|
|
7060
|
+
await execa15("docker", ["image", "rm", RELAY_IMAGE_REF], { reject: false });
|
|
6649
7061
|
} catch {
|
|
6650
7062
|
}
|
|
6651
7063
|
try {
|
|
@@ -6707,7 +7119,7 @@ function splitPair(raw) {
|
|
|
6707
7119
|
return [parts[0].trim(), parts[1].trim()];
|
|
6708
7120
|
}
|
|
6709
7121
|
async function duBytes(path) {
|
|
6710
|
-
const result = await
|
|
7122
|
+
const result = await execa16("du", ["-sk", path], { reject: false });
|
|
6711
7123
|
if (result.exitCode !== 0) return null;
|
|
6712
7124
|
const kb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
|
|
6713
7125
|
return Number.isNaN(kb) ? null : kb * 1024;
|
|
@@ -6720,7 +7132,7 @@ async function volumeSizeBytes(name) {
|
|
|
6720
7132
|
const sz = await duBytes(live);
|
|
6721
7133
|
if (sz !== null) return sz;
|
|
6722
7134
|
}
|
|
6723
|
-
const df = await
|
|
7135
|
+
const df = await execa16(
|
|
6724
7136
|
"docker",
|
|
6725
7137
|
["system", "df", "-v", "--format", "{{json .Volumes}}"],
|
|
6726
7138
|
{ reject: false }
|
|
@@ -6741,7 +7153,7 @@ async function volumeSizeBytes(name) {
|
|
|
6741
7153
|
return null;
|
|
6742
7154
|
}
|
|
6743
7155
|
async function imageBytes(tag) {
|
|
6744
|
-
const r = await
|
|
7156
|
+
const r = await execa16("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
|
|
6745
7157
|
reject: false
|
|
6746
7158
|
});
|
|
6747
7159
|
if (r.exitCode !== 0) return null;
|
|
@@ -6752,7 +7164,7 @@ async function projectCheckpointImageBytes(projectRoot, name) {
|
|
|
6752
7164
|
return imageBytes(checkpointImageTag(projectRoot, name));
|
|
6753
7165
|
}
|
|
6754
7166
|
async function allCheckpointImagesBytes() {
|
|
6755
|
-
const r = await
|
|
7167
|
+
const r = await execa16(
|
|
6756
7168
|
"docker",
|
|
6757
7169
|
[
|
|
6758
7170
|
"image",
|
|
@@ -6804,7 +7216,7 @@ function reconcileLimits(persisted, dockerJson) {
|
|
|
6804
7216
|
};
|
|
6805
7217
|
}
|
|
6806
7218
|
async function containerWritableBytes(container) {
|
|
6807
|
-
const r = await
|
|
7219
|
+
const r = await execa16(
|
|
6808
7220
|
"docker",
|
|
6809
7221
|
["ps", "-a", "--filter", `name=^${container}$`, "--format", "{{.Size}}", "--size"],
|
|
6810
7222
|
{ reject: false }
|
|
@@ -6851,7 +7263,7 @@ async function boxResourceStats(record) {
|
|
|
6851
7263
|
if (await inspectContainerStatus(record.container) !== "running") {
|
|
6852
7264
|
return base;
|
|
6853
7265
|
}
|
|
6854
|
-
const proc = await
|
|
7266
|
+
const proc = await execa16(
|
|
6855
7267
|
"docker",
|
|
6856
7268
|
["stats", "--no-stream", "--format", "{{json .}}", record.container],
|
|
6857
7269
|
{ reject: false }
|
|
@@ -6886,6 +7298,125 @@ async function boxResourceStats(record) {
|
|
|
6886
7298
|
blockWriteBytes: blkPair ? parseDockerSize(blkPair[1]) : null
|
|
6887
7299
|
};
|
|
6888
7300
|
}
|
|
7301
|
+
function posixDirname(p) {
|
|
7302
|
+
return posix.dirname(p) || "/";
|
|
7303
|
+
}
|
|
7304
|
+
function asText(s) {
|
|
7305
|
+
if (s === void 0) return "";
|
|
7306
|
+
if (typeof s === "string") return s;
|
|
7307
|
+
return Buffer.from(s).toString("utf8");
|
|
7308
|
+
}
|
|
7309
|
+
async function uploadToBox(box, hostSrc, boxDst) {
|
|
7310
|
+
const srcAbs = resolve4(hostSrc);
|
|
7311
|
+
if (!existsSync3(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
|
|
7312
|
+
const srcBasename = basename32(srcAbs);
|
|
7313
|
+
const srcParent = dirname22(srcAbs);
|
|
7314
|
+
let boxParent;
|
|
7315
|
+
let finalName;
|
|
7316
|
+
if (boxDst.endsWith("/")) {
|
|
7317
|
+
boxParent = boxDst.replace(/\/+$/, "") || "/";
|
|
7318
|
+
finalName = srcBasename;
|
|
7319
|
+
} else {
|
|
7320
|
+
const isDir2 = await execa17(
|
|
7321
|
+
"docker",
|
|
7322
|
+
["exec", box.container, "test", "-d", boxDst],
|
|
7323
|
+
{ reject: false }
|
|
7324
|
+
);
|
|
7325
|
+
if (isDir2.exitCode === 0) {
|
|
7326
|
+
boxParent = boxDst.replace(/\/+$/, "") || "/";
|
|
7327
|
+
finalName = srcBasename;
|
|
7328
|
+
} else {
|
|
7329
|
+
boxParent = posixDirname(boxDst);
|
|
7330
|
+
finalName = posix.basename(boxDst);
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
|
|
7334
|
+
const mk = await execa17(
|
|
7335
|
+
"docker",
|
|
7336
|
+
["exec", "--user", "root", box.container, "mkdir", "-p", boxParent],
|
|
7337
|
+
{ reject: false }
|
|
7338
|
+
);
|
|
7339
|
+
if (mk.exitCode !== 0) {
|
|
7340
|
+
throw new Error(`mkdir -p ${boxParent} in box failed: ${asText(mk.stderr).slice(0, 300)}`);
|
|
7341
|
+
}
|
|
7342
|
+
const packed = await execa17("tar", ["-C", srcParent, "-cf", "-", srcBasename], {
|
|
7343
|
+
encoding: "buffer",
|
|
7344
|
+
reject: false,
|
|
7345
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
7346
|
+
});
|
|
7347
|
+
if (packed.exitCode !== 0) {
|
|
7348
|
+
throw new Error(`tar pack failed: ${asText(packed.stderr).slice(0, 300)}`);
|
|
7349
|
+
}
|
|
7350
|
+
const extract = await execa17(
|
|
7351
|
+
"docker",
|
|
7352
|
+
["exec", "-i", "--user", "root", box.container, "tar", "-xf", "-", "-C", boxParent],
|
|
7353
|
+
{ input: packed.stdout, reject: false }
|
|
7354
|
+
);
|
|
7355
|
+
if (extract.exitCode !== 0) {
|
|
7356
|
+
throw new Error(`tar extract in box failed: ${asText(extract.stderr).slice(0, 300)}`);
|
|
7357
|
+
}
|
|
7358
|
+
if (finalName !== srcBasename) {
|
|
7359
|
+
const initial = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
|
|
7360
|
+
const mv = await execa17(
|
|
7361
|
+
"docker",
|
|
7362
|
+
["exec", "--user", "root", box.container, "mv", initial, finalPath],
|
|
7363
|
+
{ reject: false }
|
|
7364
|
+
);
|
|
7365
|
+
if (mv.exitCode !== 0) {
|
|
7366
|
+
throw new Error(
|
|
7367
|
+
`rename ${initial} -> ${finalPath} in box failed: ${asText(mv.stderr).slice(0, 300)}`
|
|
7368
|
+
);
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
const chown = await execa17(
|
|
7372
|
+
"docker",
|
|
7373
|
+
["exec", "--user", "root", box.container, "chown", "-R", "1000:1000", finalPath],
|
|
7374
|
+
{ reject: false }
|
|
7375
|
+
);
|
|
7376
|
+
if (chown.exitCode !== 0) {
|
|
7377
|
+
return {
|
|
7378
|
+
finalPath,
|
|
7379
|
+
warn: `chown ${finalPath} to vscode (uid 1000) failed; ownership inside the box may be root.`
|
|
7380
|
+
};
|
|
7381
|
+
}
|
|
7382
|
+
return { finalPath };
|
|
7383
|
+
}
|
|
7384
|
+
async function downloadFromBox(box, boxSrc, hostDst) {
|
|
7385
|
+
const srcBasename = posix.basename(boxSrc);
|
|
7386
|
+
const srcParent = posixDirname(boxSrc);
|
|
7387
|
+
const dstAbs = resolve4(hostDst);
|
|
7388
|
+
let hostParent;
|
|
7389
|
+
let finalName;
|
|
7390
|
+
const dstExists = existsSync3(dstAbs);
|
|
7391
|
+
if (hostDst.endsWith("/") || dstExists && statSync(dstAbs).isDirectory()) {
|
|
7392
|
+
hostParent = dstAbs;
|
|
7393
|
+
finalName = srcBasename;
|
|
7394
|
+
} else {
|
|
7395
|
+
hostParent = dirname22(dstAbs);
|
|
7396
|
+
finalName = basename32(dstAbs);
|
|
7397
|
+
}
|
|
7398
|
+
mkdirSync(hostParent, { recursive: true });
|
|
7399
|
+
const finalPath = posix.join(hostParent, finalName);
|
|
7400
|
+
const packed = await execa17(
|
|
7401
|
+
"docker",
|
|
7402
|
+
["exec", box.container, "tar", "-C", srcParent, "-cf", "-", srcBasename],
|
|
7403
|
+
{ encoding: "buffer", reject: false }
|
|
7404
|
+
);
|
|
7405
|
+
if (packed.exitCode !== 0) {
|
|
7406
|
+
throw new Error(`tar pack in box failed: ${asText(packed.stderr).slice(0, 300)}`);
|
|
7407
|
+
}
|
|
7408
|
+
const extract = await execa17("tar", ["-xf", "-", "-C", hostParent], {
|
|
7409
|
+
input: packed.stdout,
|
|
7410
|
+
reject: false
|
|
7411
|
+
});
|
|
7412
|
+
if (extract.exitCode !== 0) {
|
|
7413
|
+
throw new Error(`tar extract on host failed: ${asText(extract.stderr).slice(0, 300)}`);
|
|
7414
|
+
}
|
|
7415
|
+
if (finalName !== srcBasename) {
|
|
7416
|
+
renameSync(posix.join(hostParent, srcBasename), finalPath);
|
|
7417
|
+
}
|
|
7418
|
+
return { finalPath };
|
|
7419
|
+
}
|
|
6889
7420
|
var dockerProvider = {
|
|
6890
7421
|
name: "docker",
|
|
6891
7422
|
async create(req) {
|
|
@@ -6895,6 +7426,7 @@ var dockerProvider = {
|
|
|
6895
7426
|
name: req.name,
|
|
6896
7427
|
useSnapshot: po.useSnapshot ?? false,
|
|
6897
7428
|
checkpointRef: req.checkpointRef,
|
|
7429
|
+
fromBranch: req.fromBranch,
|
|
6898
7430
|
image: req.image,
|
|
6899
7431
|
onLog: req.onLog,
|
|
6900
7432
|
claudeConfig: po.claudeConfig,
|
|
@@ -6904,6 +7436,7 @@ var dockerProvider = {
|
|
|
6904
7436
|
withPlaywright: req.withPlaywright,
|
|
6905
7437
|
withEnv: req.withEnv,
|
|
6906
7438
|
envFilesToImport: req.envFilesToImport,
|
|
7439
|
+
carry: req.carry,
|
|
6907
7440
|
vnc: req.vnc,
|
|
6908
7441
|
docker: po.sharedCache !== void 0 ? { sharedCache: po.sharedCache } : void 0,
|
|
6909
7442
|
portless: po.portless,
|
|
@@ -6954,6 +7487,14 @@ var dockerProvider = {
|
|
|
6954
7487
|
const r = await execInBox(box.container, argv, opts?.user ? { user: opts.user } : {});
|
|
6955
7488
|
return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
|
|
6956
7489
|
},
|
|
7490
|
+
async uploadPath(box, hostSrc, boxDst) {
|
|
7491
|
+
const r = await uploadToBox(box, hostSrc, boxDst);
|
|
7492
|
+
return { finalPath: r.finalPath };
|
|
7493
|
+
},
|
|
7494
|
+
async downloadPath(box, boxSrc, hostDst) {
|
|
7495
|
+
const r = await downloadFromBox(box, boxSrc, hostDst);
|
|
7496
|
+
return { finalPath: r.finalPath };
|
|
7497
|
+
},
|
|
6957
7498
|
async resolveUrl(box, opts) {
|
|
6958
7499
|
if (box.webContainerPort === void 0) {
|
|
6959
7500
|
throw new Error(
|
|
@@ -6976,13 +7517,33 @@ var dockerProvider = {
|
|
|
6976
7517
|
},
|
|
6977
7518
|
async prepare(opts) {
|
|
6978
7519
|
const ref = DEFAULT_BOX_IMAGE;
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7520
|
+
const fingerprint = await computeDockerContextFingerprint();
|
|
7521
|
+
const prepared = readPreparedDockerState();
|
|
7522
|
+
if (!opts.force) {
|
|
7523
|
+
const exists = await imageExists(ref);
|
|
7524
|
+
if (exists && fingerprint && preparedMatches(prepared, fingerprint.contextSha256)) {
|
|
7525
|
+
opts.onLog?.(
|
|
7526
|
+
`docker image ${ref} up to date (fingerprint ${fingerprint.contextSha256.slice(0, 12)}) \u2014 skipping (use --force to rebuild)`
|
|
7527
|
+
);
|
|
7528
|
+
return {};
|
|
7529
|
+
}
|
|
7530
|
+
if (exists && !fingerprint) {
|
|
7531
|
+
opts.onLog?.(
|
|
7532
|
+
`docker image ${ref} present but build context could not be fingerprinted \u2014 skipping (use --force to rebuild)`
|
|
7533
|
+
);
|
|
7534
|
+
return {};
|
|
7535
|
+
}
|
|
6982
7536
|
}
|
|
6983
7537
|
opts.onLog?.(`building docker image ${ref}\u2026`);
|
|
6984
7538
|
await buildImage({ ref, onProgress: opts.onLog });
|
|
6985
|
-
|
|
7539
|
+
if (fingerprint) {
|
|
7540
|
+
writePreparedDockerState({ imageRef: ref, contextSha256: fingerprint.contextSha256 });
|
|
7541
|
+
opts.onLog?.(
|
|
7542
|
+
`docker image ${ref} built; recorded fingerprint ${fingerprint.contextSha256.slice(0, 12)}`
|
|
7543
|
+
);
|
|
7544
|
+
} else {
|
|
7545
|
+
opts.onLog?.(`docker image ${ref} built (fingerprint unavailable, prepared state not written)`);
|
|
7546
|
+
}
|
|
6986
7547
|
return {};
|
|
6987
7548
|
}
|
|
6988
7549
|
};
|
|
@@ -7028,7 +7589,7 @@ function emptyResult(warnings = []) {
|
|
|
7028
7589
|
}, warnings };
|
|
7029
7590
|
}
|
|
7030
7591
|
async function tarballFromDir(stageDir, agent) {
|
|
7031
|
-
const tarballPath = join14(tmpdir3(), `agentbox-${agent}-${
|
|
7592
|
+
const tarballPath = join14(tmpdir3(), `agentbox-${agent}-${basename4(stageDir)}.tar.gz`);
|
|
7032
7593
|
await execa18("tar", ["-czf", tarballPath, "-C", stageDir, "."], {
|
|
7033
7594
|
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
7034
7595
|
});
|
|
@@ -7291,6 +7852,12 @@ async function stageOpencodeCredentialsForUpload(opts = {}) {
|
|
|
7291
7852
|
if (!await pathExists7(hostAuth)) return emptyResult();
|
|
7292
7853
|
return stageSingleFileTarball("opencode-creds", hostAuth, "auth.json");
|
|
7293
7854
|
}
|
|
7855
|
+
async function stageOpencodeStateForUpload(opts = {}) {
|
|
7856
|
+
const hostHome = opts.hostHome ?? homedir11();
|
|
7857
|
+
const hostModel = join14(hostHome, ".local", "state", "opencode", "model.json");
|
|
7858
|
+
if (!await pathExists7(hostModel)) return emptyResult();
|
|
7859
|
+
return stageSingleFileTarball("opencode-state", hostModel, "model.json");
|
|
7860
|
+
}
|
|
7294
7861
|
function browserSessionActive(stdout, exitCode) {
|
|
7295
7862
|
return exitCode === 0 && !/no active sessions/i.test(stdout);
|
|
7296
7863
|
}
|
|
@@ -7330,25 +7897,24 @@ export {
|
|
|
7330
7897
|
listProjectsConfigured,
|
|
7331
7898
|
pruneOrphanProjectConfigs,
|
|
7332
7899
|
bumpProjectGcCounter,
|
|
7900
|
+
BOX_STATUS_EVENT,
|
|
7333
7901
|
renderStatusTable,
|
|
7334
7902
|
renderTaskTable,
|
|
7335
7903
|
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,
|
|
7904
|
+
loadCarrySection,
|
|
7348
7905
|
DEFAULT_RELAY_PORT,
|
|
7349
7906
|
RELAY_CONTAINER_NAME,
|
|
7350
7907
|
RELAY_NETWORK_NAME,
|
|
7351
7908
|
RELAY_IMAGE_REF,
|
|
7909
|
+
hashRpcParams,
|
|
7910
|
+
GH_PR_OPS,
|
|
7911
|
+
loadQueueConfig,
|
|
7912
|
+
writeJob,
|
|
7913
|
+
readJob,
|
|
7914
|
+
deleteJob,
|
|
7915
|
+
loadQueue,
|
|
7916
|
+
defaultCountRunningBoxes,
|
|
7917
|
+
queueLogPath,
|
|
7352
7918
|
resolveAgentLauncher,
|
|
7353
7919
|
BoxNotFoundError,
|
|
7354
7920
|
AmbiguousBoxError,
|
|
@@ -7390,6 +7956,7 @@ export {
|
|
|
7390
7956
|
buildDashboardAttachArgv,
|
|
7391
7957
|
waitForTmuxPaneContent,
|
|
7392
7958
|
buildTmuxSessionArgs,
|
|
7959
|
+
buildTmuxConfigShellSnippet,
|
|
7393
7960
|
buildShellArgv,
|
|
7394
7961
|
buildClaudeLoginRunArgv,
|
|
7395
7962
|
runInteractiveClaudeLogin,
|
|
@@ -7422,6 +7989,7 @@ export {
|
|
|
7422
7989
|
DEFAULT_OPENCODE_SESSION,
|
|
7423
7990
|
resolveOpencodeVolume,
|
|
7424
7991
|
ensureOpencodeVolume,
|
|
7992
|
+
seedOpencodePlugin,
|
|
7425
7993
|
OPENCODE_FORWARDED_ENV_KEYS,
|
|
7426
7994
|
buildOpencodeMounts,
|
|
7427
7995
|
OpencodeSessionError,
|
|
@@ -7459,10 +8027,6 @@ export {
|
|
|
7459
8027
|
installPortless,
|
|
7460
8028
|
startPortlessProxy,
|
|
7461
8029
|
resolvePortlessHostStateDir,
|
|
7462
|
-
DEFAULT_BOX_IMAGE,
|
|
7463
|
-
imageExists,
|
|
7464
|
-
imageInfo,
|
|
7465
|
-
ensureImage,
|
|
7466
8030
|
EXCLUDE_DIRS,
|
|
7467
8031
|
SNAPSHOTS_ROOT,
|
|
7468
8032
|
snapshotPathFor,
|
|
@@ -7485,6 +8049,7 @@ export {
|
|
|
7485
8049
|
forgetBoxFromRelay,
|
|
7486
8050
|
setRelayNotice,
|
|
7487
8051
|
clearRelayNotice,
|
|
8052
|
+
mintHostInitiatedToken,
|
|
7488
8053
|
rehydrateRelayRegistry,
|
|
7489
8054
|
IDE_FLAVORS,
|
|
7490
8055
|
ideProfile,
|
|
@@ -7533,6 +8098,8 @@ export {
|
|
|
7533
8098
|
allCheckpointImagesBytes,
|
|
7534
8099
|
agentboxHomeBytes,
|
|
7535
8100
|
boxResourceStats,
|
|
8101
|
+
uploadToBox,
|
|
8102
|
+
downloadFromBox,
|
|
7536
8103
|
dockerProvider,
|
|
7537
8104
|
stageClaudeStaticForUpload,
|
|
7538
8105
|
stageClaudeCredentialsForUpload,
|
|
@@ -7540,7 +8107,8 @@ export {
|
|
|
7540
8107
|
stageCodexCredentialsForUpload,
|
|
7541
8108
|
stageOpencodeStaticForUpload,
|
|
7542
8109
|
stageOpencodeCredentialsForUpload,
|
|
8110
|
+
stageOpencodeStateForUpload,
|
|
7543
8111
|
browserSessionActive,
|
|
7544
8112
|
ensureBoxBrowser
|
|
7545
8113
|
};
|
|
7546
|
-
//# sourceMappingURL=chunk-
|
|
8114
|
+
//# sourceMappingURL=chunk-6OZDFNBF.js.map
|