@madarco/agentbox 0.6.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-67N47KUS.js +1640 -0
- package/dist/chunk-67N47KUS.js.map +1 -0
- package/dist/chunk-6OZDFNBF.js +8114 -0
- 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-FODMEHD3.js +1200 -0
- package/dist/chunk-FODMEHD3.js.map +1 -0
- package/dist/chunk-G3H2L3O2.js +288 -0
- package/dist/chunk-G3H2L3O2.js.map +1 -0
- package/dist/chunk-I24B6AXR.js +600 -0
- package/dist/chunk-I24B6AXR.js.map +1 -0
- package/dist/chunk-LEV3KICD.js +738 -0
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/cloud-poller-SUNA6ZQC-2RG5WPRN.js +10 -0
- package/dist/dist-L4LCG5SJ.js +293 -0
- package/dist/dist-L4LCG5SJ.js.map +1 -0
- package/dist/dist-LOZBWMBF.js +447 -0
- package/dist/dist-ZODPD2I6.js +1407 -0
- package/dist/dist-ZODPD2I6.js.map +1 -0
- package/dist/index.js +7281 -2134
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js +18 -0
- package/package.json +8 -3
- package/runtime/daytona/custom-system-CLAUDE.md +39 -0
- package/runtime/docker/Dockerfile.box +120 -14
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +15 -8
- package/runtime/docker/packages/ctl/dist/bin.cjs +11310 -816
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +68 -0
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +9 -9
- 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-checkpoint-cleanup +52 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +68 -0
- package/runtime/hetzner/agentbox-dockerd-start +132 -0
- package/runtime/hetzner/agentbox-open +28 -0
- package/runtime/hetzner/agentbox-setup-skill.md +196 -0
- package/runtime/hetzner/agentbox-vnc-start +77 -0
- package/runtime/hetzner/claude-managed-settings.json +115 -0
- package/runtime/hetzner/ctl.cjs +23397 -0
- package/runtime/hetzner/custom-system-CLAUDE.md +39 -0
- 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 +374 -0
- package/runtime/relay/bin.cjs +10017 -817
- package/share/agentbox-setup/SKILL.md +15 -8
- 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/chunk-BBZMA2K6.js +0 -238
- package/dist/chunk-BBZMA2K6.js.map +0 -1
- package/dist/chunk-HHMWQNLF.js +0 -1709
- package/dist/chunk-HHMWQNLF.js.map +0 -1
- package/dist/chunk-HPZMD5DE.js +0 -106
- package/dist/chunk-HPZMD5DE.js.map +0 -1
- package/dist/chunk-HTTKML3C.js +0 -2655
- package/dist/chunk-HTTKML3C.js.map +0 -1
- package/dist/chunk-KJNZP6I3.js +0 -586
- package/dist/chunk-KJNZP6I3.js.map +0 -1
- package/dist/chunk-M7I247BK.js +0 -525
- package/dist/chunk-M7I247BK.js.map +0 -1
- package/dist/create-6PWXI6HO-OWAMHBAK.js +0 -15
- package/dist/lifecycle-EMXR46DI-DUVBXNTV.js +0 -38
- package/dist/state-KD7M46ZP-KHFTHFUS.js +0 -26
- package/dist/stats-SZXOJE3D-N7OODCHW.js +0 -19
- /package/dist/{create-6PWXI6HO-OWAMHBAK.js.map → _cloud-attach-T727ZPRV.js.map} +0 -0
- /package/dist/{lifecycle-EMXR46DI-DUVBXNTV.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
- /package/dist/{state-KD7M46ZP-KHFTHFUS.js.map → dist-LOZBWMBF.js.map} +0 -0
- /package/dist/{stats-SZXOJE3D-N7OODCHW.js.map → prepared-state-CL4CWXQA-ME4HSKDE.js.map} +0 -0
package/dist/chunk-KJNZP6I3.js
DELETED
|
@@ -1,586 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ConfigError,
|
|
4
|
-
VNC_CONTAINER_PORT,
|
|
5
|
-
WEB_CONTAINER_PORT,
|
|
6
|
-
bindWorktrees,
|
|
7
|
-
buildClaudeMounts,
|
|
8
|
-
buildIdeMounts,
|
|
9
|
-
chownGitBindParents,
|
|
10
|
-
collectRepoCarryOver,
|
|
11
|
-
createSnapshot,
|
|
12
|
-
cursorServerVolumeName,
|
|
13
|
-
detectGitRepos,
|
|
14
|
-
dockerVolumeName,
|
|
15
|
-
ensureClaudeVolume,
|
|
16
|
-
ensureHomeOwnedByVscode,
|
|
17
|
-
ensureIdeVolumes,
|
|
18
|
-
ensureRelay,
|
|
19
|
-
generateRelayToken,
|
|
20
|
-
generateVncPassword,
|
|
21
|
-
gitWorktreePathFor,
|
|
22
|
-
launchCtlDaemon,
|
|
23
|
-
launchDockerdDaemon,
|
|
24
|
-
launchVncDaemon,
|
|
25
|
-
loadConfig,
|
|
26
|
-
pickFreshBranch,
|
|
27
|
-
registerBoxWithRelay,
|
|
28
|
-
rehydrateRelayRegistry,
|
|
29
|
-
repairIdeOwnership,
|
|
30
|
-
resolveClaudeVolume,
|
|
31
|
-
seedSetupSkillIntoVolume,
|
|
32
|
-
seedWorkspace,
|
|
33
|
-
seedWorkspaceFromDir,
|
|
34
|
-
snapshotPathFor,
|
|
35
|
-
vscodeServerVolumeName
|
|
36
|
-
} from "./chunk-HTTKML3C.js";
|
|
37
|
-
import {
|
|
38
|
-
STATE_DIR,
|
|
39
|
-
allocateProjectIndex,
|
|
40
|
-
readState,
|
|
41
|
-
recordBox
|
|
42
|
-
} from "./chunk-HPZMD5DE.js";
|
|
43
|
-
import {
|
|
44
|
-
CONTAINER_EXPORT_MERGED,
|
|
45
|
-
DEFAULT_BOX_IMAGE,
|
|
46
|
-
DEFAULT_ENV_PATTERNS,
|
|
47
|
-
boxRunDirFor,
|
|
48
|
-
containerExists,
|
|
49
|
-
copyHostEnvFilesToBox,
|
|
50
|
-
copyHostFilesToBox,
|
|
51
|
-
dockerInfo,
|
|
52
|
-
dockerStorageDriver,
|
|
53
|
-
ensureImage,
|
|
54
|
-
ensureVolume,
|
|
55
|
-
publishedHostPort,
|
|
56
|
-
resolveCheckpoint,
|
|
57
|
-
runBox
|
|
58
|
-
} from "./chunk-HHMWQNLF.js";
|
|
59
|
-
|
|
60
|
-
// ../../packages/sandbox-docker/dist/chunk-A4F6SVSK.js
|
|
61
|
-
import { randomBytes } from "crypto";
|
|
62
|
-
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
63
|
-
import { homedir } from "os";
|
|
64
|
-
import { basename, join as join2, resolve } from "path";
|
|
65
|
-
import { execa as execa3 } from "execa";
|
|
66
|
-
import { chmod, mkdir, readFile } from "fs/promises";
|
|
67
|
-
import { join } from "path";
|
|
68
|
-
import { execa } from "execa";
|
|
69
|
-
import { execa as execa2 } from "execa";
|
|
70
|
-
var CREDENTIALS_BACKUP_FILE = join(STATE_DIR, "claude-credentials.json");
|
|
71
|
-
async function hostBackupHasCredentials(path = CREDENTIALS_BACKUP_FILE) {
|
|
72
|
-
try {
|
|
73
|
-
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
74
|
-
const rt = parsed?.claudeAiOauth?.refreshToken;
|
|
75
|
-
return typeof rt === "string" && rt.length > 0;
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function parseSyncResult(stdout) {
|
|
81
|
-
const volumeHasCredentials = /\bVOLREAL=yes\b/.test(stdout);
|
|
82
|
-
if (/\bEXTRACTED=yes\b/.test(stdout)) return { direction: "extracted", volumeHasCredentials };
|
|
83
|
-
if (/\bSEEDED=yes\b/.test(stdout)) return { direction: "seeded", volumeHasCredentials };
|
|
84
|
-
return { direction: "noop", volumeHasCredentials };
|
|
85
|
-
}
|
|
86
|
-
var SYNC_SCRIPT = `
|
|
87
|
-
EXTRACTED=no
|
|
88
|
-
SEEDED=no
|
|
89
|
-
VOL=/dst/.credentials.json
|
|
90
|
-
HOST=/host-state/claude-credentials.json
|
|
91
|
-
if [ -f "$VOL" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$VOL" >/dev/null 2>&1; then VOL_REAL=yes; else VOL_REAL=no; fi
|
|
92
|
-
if [ -f "$HOST" ] && jq -e '(.claudeAiOauth.refreshToken // "") | length > 0' "$HOST" >/dev/null 2>&1; then HOST_REAL=yes; else HOST_REAL=no; fi
|
|
93
|
-
if [ "$VOL_REAL" = yes ] && [ "$ISOLATE" != yes ]; then
|
|
94
|
-
cp -a "$VOL" "$HOST" && chmod 600 "$HOST" && EXTRACTED=yes
|
|
95
|
-
elif [ "$VOL_REAL" = no ] && [ "$HOST_REAL" = yes ]; then
|
|
96
|
-
cp -a "$HOST" "$VOL" && chown 1000:1000 "$VOL" && chmod 600 "$VOL" && SEEDED=yes && VOL_REAL=yes
|
|
97
|
-
fi
|
|
98
|
-
echo "EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL"
|
|
99
|
-
`;
|
|
100
|
-
async function syncClaudeCredentials(spec, opts) {
|
|
101
|
-
try {
|
|
102
|
-
await mkdir(STATE_DIR, { recursive: true });
|
|
103
|
-
const { stdout } = await execa("docker", [
|
|
104
|
-
"run",
|
|
105
|
-
"--rm",
|
|
106
|
-
"--user",
|
|
107
|
-
"0",
|
|
108
|
-
"-v",
|
|
109
|
-
`${spec.volume}:/dst`,
|
|
110
|
-
"-v",
|
|
111
|
-
`${STATE_DIR}:/host-state`,
|
|
112
|
-
"-e",
|
|
113
|
-
`ISOLATE=${opts.isolate ? "yes" : "no"}`,
|
|
114
|
-
opts.image,
|
|
115
|
-
"sh",
|
|
116
|
-
"-c",
|
|
117
|
-
SYNC_SCRIPT
|
|
118
|
-
]);
|
|
119
|
-
const result = parseSyncResult(stdout);
|
|
120
|
-
if (result.direction === "extracted") {
|
|
121
|
-
await chmod(CREDENTIALS_BACKUP_FILE, 384).catch(() => {
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
return result;
|
|
125
|
-
} catch {
|
|
126
|
-
return { direction: "noop", volumeHasCredentials: false };
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
async function writeBoxEnvFile(container, env) {
|
|
130
|
-
const body = formatBoxEnvBody(env);
|
|
131
|
-
const result = await execa2(
|
|
132
|
-
"docker",
|
|
133
|
-
["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
|
|
134
|
-
{ input: body, reject: false }
|
|
135
|
-
);
|
|
136
|
-
if (result.exitCode !== 0) {
|
|
137
|
-
return {
|
|
138
|
-
ok: false,
|
|
139
|
-
reason: `docker exec failed (exit ${String(result.exitCode)}): ${(result.stderr ?? "").toString().slice(0, 400)}`
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
return { ok: true };
|
|
143
|
-
}
|
|
144
|
-
function formatBoxEnvBody(env) {
|
|
145
|
-
const lines = [];
|
|
146
|
-
for (const [k, v] of Object.entries(env)) {
|
|
147
|
-
lines.push(`${k}=${shellSingleQuote(v)}`);
|
|
148
|
-
}
|
|
149
|
-
return lines.join("\n") + "\n";
|
|
150
|
-
}
|
|
151
|
-
function shellSingleQuote(s) {
|
|
152
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
153
|
-
}
|
|
154
|
-
function persistableLimits(lim) {
|
|
155
|
-
if (!lim) return void 0;
|
|
156
|
-
const out = {};
|
|
157
|
-
if (lim.memoryBytes && lim.memoryBytes > 0) out.memoryBytes = Math.floor(lim.memoryBytes);
|
|
158
|
-
if (lim.cpus && lim.cpus > 0) out.cpus = lim.cpus;
|
|
159
|
-
if (lim.pidsLimit && lim.pidsLimit > 0) out.pidsLimit = Math.floor(lim.pidsLimit);
|
|
160
|
-
if (lim.disk) out.disk = lim.disk;
|
|
161
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
162
|
-
}
|
|
163
|
-
function generateBoxId() {
|
|
164
|
-
return randomBytes(4).toString("hex");
|
|
165
|
-
}
|
|
166
|
-
function sanitizeBasename(workspacePath) {
|
|
167
|
-
const raw = basename(resolve(workspacePath));
|
|
168
|
-
return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "").slice(0, 30).replace(/[-._]+$/, "");
|
|
169
|
-
}
|
|
170
|
-
function defaultBoxName(workspacePath, id) {
|
|
171
|
-
const base = sanitizeBasename(workspacePath);
|
|
172
|
-
return base.length > 0 ? `${base}-${id}` : id;
|
|
173
|
-
}
|
|
174
|
-
async function pathExists(p) {
|
|
175
|
-
try {
|
|
176
|
-
await stat(p);
|
|
177
|
-
return true;
|
|
178
|
-
} catch {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
async function buildIdentityMounts() {
|
|
183
|
-
const home = homedir();
|
|
184
|
-
const candidates = [
|
|
185
|
-
{ src: join2(home, ".codex"), dst: "/home/vscode/.codex", readOnly: false },
|
|
186
|
-
{ src: join2(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
|
|
187
|
-
];
|
|
188
|
-
const out = [];
|
|
189
|
-
for (const c of candidates) {
|
|
190
|
-
if (await pathExists(c.src)) {
|
|
191
|
-
out.push(`${c.src}:${c.dst}${c.readOnly ? ":ro" : ""}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return out;
|
|
195
|
-
}
|
|
196
|
-
async function createBox(opts) {
|
|
197
|
-
const log = opts.onLog ?? (() => {
|
|
198
|
-
});
|
|
199
|
-
const workspace = resolve(opts.workspacePath);
|
|
200
|
-
if (!await pathExists(workspace)) {
|
|
201
|
-
throw new Error(`workspace does not exist: ${workspace}`);
|
|
202
|
-
}
|
|
203
|
-
const cfgPath = join2(workspace, "agentbox.yaml");
|
|
204
|
-
if (await pathExists(cfgPath)) {
|
|
205
|
-
try {
|
|
206
|
-
const cfg = await loadConfig(cfgPath);
|
|
207
|
-
log(`agentbox.yaml validated (${String(cfg.services.length)} service(s))`);
|
|
208
|
-
} catch (err) {
|
|
209
|
-
if (err instanceof ConfigError) {
|
|
210
|
-
throw new Error(`agentbox.yaml validation failed:
|
|
211
|
-
${err.message}`);
|
|
212
|
-
}
|
|
213
|
-
throw err;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
await dockerInfo();
|
|
217
|
-
log("docker daemon reachable");
|
|
218
|
-
let checkpointImage;
|
|
219
|
-
let checkpointSource;
|
|
220
|
-
let restoredWorktrees;
|
|
221
|
-
if (opts.checkpointRef) {
|
|
222
|
-
const projectRootForCkpt = opts.projectRoot ?? workspace;
|
|
223
|
-
const head = await resolveCheckpoint(projectRootForCkpt, opts.checkpointRef);
|
|
224
|
-
if (!head) {
|
|
225
|
-
throw new Error(`checkpoint not found: ${opts.checkpointRef}`);
|
|
226
|
-
}
|
|
227
|
-
checkpointImage = head.manifest.image;
|
|
228
|
-
const chain = [head.name, ...head.manifest.parents];
|
|
229
|
-
checkpointSource = { ref: opts.checkpointRef, type: head.manifest.type, chain };
|
|
230
|
-
restoredWorktrees = head.manifest.worktrees;
|
|
231
|
-
log(
|
|
232
|
-
`starting from checkpoint ${opts.checkpointRef} (${head.manifest.type}, ${String(chain.length)} layer(s), image ${head.manifest.image})`
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
const imageRef = checkpointImage ?? opts.image ?? DEFAULT_BOX_IMAGE;
|
|
236
|
-
const ensureRef = checkpointImage ? opts.image ?? DEFAULT_BOX_IMAGE : imageRef;
|
|
237
|
-
const { built } = await ensureImage(ensureRef, {
|
|
238
|
-
onProgress: (line) => log(`[image] ${line}`)
|
|
239
|
-
});
|
|
240
|
-
log(built ? `built image ${ensureRef}` : `using cached image ${imageRef}`);
|
|
241
|
-
let relayUp = false;
|
|
242
|
-
try {
|
|
243
|
-
await ensureRelay({ onLog: log });
|
|
244
|
-
const existing = await readState();
|
|
245
|
-
await rehydrateRelayRegistry(existing.boxes);
|
|
246
|
-
relayUp = true;
|
|
247
|
-
} catch (err) {
|
|
248
|
-
log(`relay unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
249
|
-
}
|
|
250
|
-
const id = generateBoxId();
|
|
251
|
-
const name = opts.name ?? defaultBoxName(workspace, id);
|
|
252
|
-
const containerName = `agentbox-${name}`;
|
|
253
|
-
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
254
|
-
if (await containerExists(containerName)) {
|
|
255
|
-
throw new Error(`container ${containerName} already exists; remove it first`);
|
|
256
|
-
}
|
|
257
|
-
let projectIndex;
|
|
258
|
-
if (opts.projectRoot) {
|
|
259
|
-
projectIndex = allocateProjectIndex(await readState(), opts.projectRoot);
|
|
260
|
-
}
|
|
261
|
-
const repoCarryOvers = [];
|
|
262
|
-
const gitWorktreeRecords = [];
|
|
263
|
-
if (checkpointImage && restoredWorktrees && restoredWorktrees.length > 0) {
|
|
264
|
-
gitWorktreeRecords.push(...restoredWorktrees);
|
|
265
|
-
}
|
|
266
|
-
if (!checkpointImage) {
|
|
267
|
-
const repos = await detectGitRepos(workspace);
|
|
268
|
-
if (repos.length > 0) {
|
|
269
|
-
log(
|
|
270
|
-
`detected ${String(repos.length)} git repo(s): ` + repos.map((r) => `${r.kind}${r.relPathFromWorkspace ? "@" + r.relPathFromWorkspace : ""}`).join(", ")
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
for (const r of repos) {
|
|
274
|
-
const branchBase = r.kind === "root" ? `agentbox/${name}` : `agentbox/${name}--${r.relPathFromWorkspace.replace(/[^A-Za-z0-9._-]+/g, "_")}`;
|
|
275
|
-
const branch = await pickFreshBranch(r.hostMainRepo, branchBase);
|
|
276
|
-
const containerPath = r.kind === "root" ? "/workspace" : `/workspace/${r.relPathFromWorkspace}`;
|
|
277
|
-
const gitWorktreePath = gitWorktreePathFor(branch);
|
|
278
|
-
const carry = await collectRepoCarryOver(r, branch, containerPath, gitWorktreePath);
|
|
279
|
-
repoCarryOvers.push(carry);
|
|
280
|
-
gitWorktreeRecords.push({
|
|
281
|
-
kind: r.kind,
|
|
282
|
-
hostMainRepo: r.hostMainRepo,
|
|
283
|
-
containerPath,
|
|
284
|
-
gitWorktreePath,
|
|
285
|
-
branch,
|
|
286
|
-
relPathFromWorkspace: r.relPathFromWorkspace
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
let snapshotDir = null;
|
|
291
|
-
const snapshotIsUseful = !checkpointImage && repoCarryOvers.length === 0;
|
|
292
|
-
if (opts.useSnapshot && snapshotIsUseful) {
|
|
293
|
-
snapshotDir = snapshotPathFor({ id, name, projectIndex });
|
|
294
|
-
log(`cloning workspace to ${snapshotDir} (APFS clone where available)`);
|
|
295
|
-
const snap = await createSnapshot({ source: workspace, destination: snapshotDir });
|
|
296
|
-
log(`pruned ${snap.prunedPaths.length} platform-dependent dirs from snapshot`);
|
|
297
|
-
} else if (opts.useSnapshot && !checkpointImage) {
|
|
298
|
-
log("skipping --host-snapshot: git worktree path reads content from .git, not from a workspace clone");
|
|
299
|
-
}
|
|
300
|
-
await ensureIdeVolumes(id);
|
|
301
|
-
const dockerCacheShared = opts.docker?.sharedCache === true;
|
|
302
|
-
const dockerVolume = dockerVolumeName(id, dockerCacheShared);
|
|
303
|
-
await ensureVolume(dockerVolume);
|
|
304
|
-
log(`prepared volumes ${vscodeServerVolumeName(id)}, ${cursorServerVolumeName(id)}, ${dockerVolume}`);
|
|
305
|
-
const ide = buildIdeMounts(id);
|
|
306
|
-
const claudeSpec = resolveClaudeVolume({
|
|
307
|
-
isolate: opts.claudeConfig?.isolate ?? false,
|
|
308
|
-
boxId: id
|
|
309
|
-
});
|
|
310
|
-
const claudeEnsured = await ensureClaudeVolume(claudeSpec, {
|
|
311
|
-
syncFromHost: true,
|
|
312
|
-
image: ensureRef,
|
|
313
|
-
hostWorkspace: workspace
|
|
314
|
-
});
|
|
315
|
-
if (claudeEnsured.synced) {
|
|
316
|
-
log(`synced ${claudeSpec.volume} from ~/.claude`);
|
|
317
|
-
if ((claudeEnsured.filteredHookCount ?? 0) > 0) {
|
|
318
|
-
log(
|
|
319
|
-
`filtered ${String(claudeEnsured.filteredHookCount)} host-path hook(s) (paths under ~/)`
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
if (claudeEnsured.installMethodFixed) {
|
|
323
|
-
log("set installMethod=native in synced .claude.json (matches box native install)");
|
|
324
|
-
}
|
|
325
|
-
if (claudeEnsured.aliasedProjectKey) {
|
|
326
|
-
log(`aliased project state for ${workspace} -> /workspace in synced .claude.json`);
|
|
327
|
-
}
|
|
328
|
-
if (claudeEnsured.workspaceTrusted) {
|
|
329
|
-
log("pre-trusted /workspace in synced .claude.json (skips the trust dialog)");
|
|
330
|
-
}
|
|
331
|
-
} else if (claudeEnsured.created) {
|
|
332
|
-
log(`created empty volume ${claudeSpec.volume} (no host ~/.claude to sync)`);
|
|
333
|
-
} else {
|
|
334
|
-
log(`reusing volume ${claudeSpec.volume} (no host ~/.claude to sync)`);
|
|
335
|
-
}
|
|
336
|
-
const seeded = await seedSetupSkillIntoVolume(claudeSpec.volume, ensureRef);
|
|
337
|
-
if (seeded.seeded) log(`refreshed /agentbox-setup skill into ${claudeSpec.volume}`);
|
|
338
|
-
const credSync = await syncClaudeCredentials(claudeSpec, {
|
|
339
|
-
image: ensureRef,
|
|
340
|
-
isolate: opts.claudeConfig?.isolate ?? false
|
|
341
|
-
});
|
|
342
|
-
if (credSync.direction === "extracted") {
|
|
343
|
-
log("extracted box claude credentials to host backup");
|
|
344
|
-
} else if (credSync.direction === "seeded") {
|
|
345
|
-
log(`seeded claude credentials into ${claudeSpec.volume} from host backup`);
|
|
346
|
-
}
|
|
347
|
-
const claudeMounts = buildClaudeMounts(claudeSpec, process.env);
|
|
348
|
-
const boxDir = boxRunDirFor({ id, name, projectIndex });
|
|
349
|
-
const socketDir = join2(boxDir, "run");
|
|
350
|
-
const socketPath = join2(socketDir, "ctl.sock");
|
|
351
|
-
const mergedExportDir = join2(boxDir, "workspace");
|
|
352
|
-
await mkdir2(socketDir, { recursive: true });
|
|
353
|
-
await mkdir2(mergedExportDir, { recursive: true });
|
|
354
|
-
const extraVolumes = await buildIdentityMounts();
|
|
355
|
-
extraVolumes.push(...claudeMounts.extraVolumes);
|
|
356
|
-
extraVolumes.push(...ide.extraVolumes);
|
|
357
|
-
extraVolumes.push(`${socketDir}:/run/agentbox`);
|
|
358
|
-
extraVolumes.push(`${mergedExportDir}:${CONTAINER_EXPORT_MERGED}`);
|
|
359
|
-
extraVolumes.push(`${dockerVolume}:/var/lib/docker`);
|
|
360
|
-
for (const w of gitWorktreeRecords) {
|
|
361
|
-
extraVolumes.push(`${w.hostMainRepo}/.git:${w.hostMainRepo}/.git`);
|
|
362
|
-
}
|
|
363
|
-
for (const v of extraVolumes) log(`mounting agent dir: ${v}`);
|
|
364
|
-
const relayToken = generateRelayToken();
|
|
365
|
-
if (relayUp) {
|
|
366
|
-
try {
|
|
367
|
-
await registerBoxWithRelay({
|
|
368
|
-
boxId: id,
|
|
369
|
-
token: relayToken,
|
|
370
|
-
name,
|
|
371
|
-
containerName,
|
|
372
|
-
createdAt,
|
|
373
|
-
projectIndex,
|
|
374
|
-
worktrees: gitWorktreeRecords
|
|
375
|
-
});
|
|
376
|
-
log(`registered box token with relay`);
|
|
377
|
-
} catch (err) {
|
|
378
|
-
log(`relay register failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
379
|
-
relayUp = false;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const relayEnv = relayUp ? {
|
|
383
|
-
// host.docker.internal resolves to the host (where the relay node
|
|
384
|
-
// process is running). The matching `--add-host` is set in runBox.
|
|
385
|
-
AGENTBOX_RELAY_URL: `http://host.docker.internal:8787`,
|
|
386
|
-
AGENTBOX_RELAY_TOKEN: relayToken
|
|
387
|
-
} : {};
|
|
388
|
-
const vncEnabled = opts.vnc?.enabled !== false;
|
|
389
|
-
const vncPassword = vncEnabled ? generateVncPassword() : void 0;
|
|
390
|
-
const vncEnv = vncEnabled && vncPassword ? { AGENTBOX_VNC_PASSWORD: vncPassword } : {};
|
|
391
|
-
const vncPortMappings = vncEnabled ? [{ hostPort: 0, containerPort: VNC_CONTAINER_PORT, hostIp: "127.0.0.1" }] : [];
|
|
392
|
-
const webPortMappings = [
|
|
393
|
-
{ hostPort: 0, containerPort: WEB_CONTAINER_PORT, hostIp: "127.0.0.1" }
|
|
394
|
-
];
|
|
395
|
-
const agentboxEnv = {
|
|
396
|
-
AGENTBOX: "1",
|
|
397
|
-
AGENTBOX_BOX_NAME: name,
|
|
398
|
-
AGENTBOX_HOST_WORKSPACE: workspace,
|
|
399
|
-
...opts.projectRoot ? { AGENTBOX_PROJECT_ROOT: opts.projectRoot } : {},
|
|
400
|
-
...projectIndex !== void 0 ? { AGENTBOX_PROJECT_INDEX: String(projectIndex) } : {}
|
|
401
|
-
};
|
|
402
|
-
const boxEnvForFile = {
|
|
403
|
-
AGENTBOX_BOX_ID: id,
|
|
404
|
-
...agentboxEnv
|
|
405
|
-
};
|
|
406
|
-
const appliedLimits = opts.limits;
|
|
407
|
-
let effectiveLimits = appliedLimits;
|
|
408
|
-
if (appliedLimits?.disk) {
|
|
409
|
-
const driver = await dockerStorageDriver();
|
|
410
|
-
if (!/^(devicemapper|btrfs|zfs|windowsfilter)$/.test(driver)) {
|
|
411
|
-
log(
|
|
412
|
-
`warning: --disk/box.disk is a no-op on this engine (storage-driver=${driver || "unknown"}); ignoring`
|
|
413
|
-
);
|
|
414
|
-
effectiveLimits = { ...appliedLimits, disk: null };
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
await runBox({
|
|
418
|
-
name: containerName,
|
|
419
|
-
image: imageRef,
|
|
420
|
-
extraVolumes,
|
|
421
|
-
limits: effectiveLimits,
|
|
422
|
-
portMappings: [...vncPortMappings, ...webPortMappings],
|
|
423
|
-
env: {
|
|
424
|
-
AGENTBOX_BOX_ID: id,
|
|
425
|
-
...agentboxEnv,
|
|
426
|
-
...claudeMounts.env,
|
|
427
|
-
...relayEnv,
|
|
428
|
-
...vncEnv,
|
|
429
|
-
...opts.claudeEnv ?? {}
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
log(`container ${containerName} started`);
|
|
433
|
-
if (gitWorktreeRecords.length > 0) {
|
|
434
|
-
await chownGitBindParents({
|
|
435
|
-
container: containerName,
|
|
436
|
-
hostMainRepos: gitWorktreeRecords.map((w) => w.hostMainRepo),
|
|
437
|
-
onLog: log
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
const boxEnv = await writeBoxEnvFile(containerName, boxEnvForFile);
|
|
441
|
-
if (boxEnv.ok) log("wrote /etc/agentbox/box.env");
|
|
442
|
-
else log(`writing /etc/agentbox/box.env failed: ${boxEnv.reason}`);
|
|
443
|
-
await ensureHomeOwnedByVscode(containerName);
|
|
444
|
-
if (!checkpointImage) {
|
|
445
|
-
if (repoCarryOvers.length > 0) {
|
|
446
|
-
try {
|
|
447
|
-
await seedWorkspace({ container: containerName, repos: repoCarryOvers, onLog: log });
|
|
448
|
-
log("seeded /workspace from in-container git worktree(s)");
|
|
449
|
-
} catch (err) {
|
|
450
|
-
log(
|
|
451
|
-
`seedWorkspace failed; leaving ${containerName} running so you can inspect it`
|
|
452
|
-
);
|
|
453
|
-
throw err;
|
|
454
|
-
}
|
|
455
|
-
} else {
|
|
456
|
-
const source = snapshotDir ?? workspace;
|
|
457
|
-
await seedWorkspaceFromDir({ container: containerName, hostSource: source, onLog: log });
|
|
458
|
-
}
|
|
459
|
-
} else if (restoredWorktrees && restoredWorktrees.length > 0) {
|
|
460
|
-
await bindWorktrees(
|
|
461
|
-
containerName,
|
|
462
|
-
restoredWorktrees.map((w) => ({
|
|
463
|
-
kind: w.kind,
|
|
464
|
-
containerPath: w.containerPath,
|
|
465
|
-
gitWorktreePath: w.gitWorktreePath
|
|
466
|
-
})),
|
|
467
|
-
log
|
|
468
|
-
);
|
|
469
|
-
log("re-bound /workspace from checkpoint image");
|
|
470
|
-
} else {
|
|
471
|
-
log("using /workspace from checkpoint image (no worktrees recorded; no rebind)");
|
|
472
|
-
}
|
|
473
|
-
await repairIdeOwnership(containerName);
|
|
474
|
-
log(".vscode-server + .cursor-server ownership verified");
|
|
475
|
-
const ctl = await launchCtlDaemon(containerName, socketPath);
|
|
476
|
-
if (ctl.up) log("agentbox-ctl daemon up");
|
|
477
|
-
else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);
|
|
478
|
-
const dockerd = await launchDockerdDaemon(containerName);
|
|
479
|
-
if (dockerd.up) {
|
|
480
|
-
log(`dockerd up (data root=${dockerVolume})`);
|
|
481
|
-
} else {
|
|
482
|
-
log(`dockerd did not become ready: ${dockerd.reason}`);
|
|
483
|
-
}
|
|
484
|
-
if (opts.withPlaywright) {
|
|
485
|
-
log("installing @playwright/cli@latest (--with-playwright)");
|
|
486
|
-
const result = await execa3(
|
|
487
|
-
"docker",
|
|
488
|
-
[
|
|
489
|
-
"exec",
|
|
490
|
-
"--user",
|
|
491
|
-
"root",
|
|
492
|
-
containerName,
|
|
493
|
-
"bash",
|
|
494
|
-
"-lc",
|
|
495
|
-
"npm install -g @playwright/cli@latest 2>&1"
|
|
496
|
-
],
|
|
497
|
-
{ reject: false }
|
|
498
|
-
);
|
|
499
|
-
for (const line of (result.stdout ?? "").split("\n")) {
|
|
500
|
-
if (line.trim().length > 0) log(`[playwright] ${line}`);
|
|
501
|
-
}
|
|
502
|
-
if (result.exitCode !== 0) {
|
|
503
|
-
throw new Error(
|
|
504
|
-
`failed to install @playwright/cli (exit ${String(result.exitCode)}): ${(result.stderr ?? "").toString().slice(0, 400)}`
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
log("@playwright/cli installed");
|
|
508
|
-
}
|
|
509
|
-
if (opts.withEnv) {
|
|
510
|
-
log("copying host env/config files into /workspace (--with-env)");
|
|
511
|
-
const { copied } = await copyHostEnvFilesToBox({
|
|
512
|
-
container: containerName,
|
|
513
|
-
workspaceDir: workspace,
|
|
514
|
-
patterns: DEFAULT_ENV_PATTERNS,
|
|
515
|
-
onLog: log
|
|
516
|
-
});
|
|
517
|
-
log(copied > 0 ? `copied ${String(copied)} env/config file(s)` : "no env/config files found");
|
|
518
|
-
}
|
|
519
|
-
if (opts.envFilesToImport && opts.envFilesToImport.length > 0) {
|
|
520
|
-
log(`copying ${String(opts.envFilesToImport.length)} selected env/config file(s) into /workspace`);
|
|
521
|
-
const { copied } = await copyHostFilesToBox({
|
|
522
|
-
container: containerName,
|
|
523
|
-
workspaceDir: workspace,
|
|
524
|
-
files: opts.envFilesToImport,
|
|
525
|
-
onLog: log
|
|
526
|
-
});
|
|
527
|
-
if (copied !== opts.envFilesToImport.length) {
|
|
528
|
-
log(`copied ${String(copied)}/${String(opts.envFilesToImport.length)} selected env/config file(s)`);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
let vncHostPort = null;
|
|
532
|
-
if (vncEnabled) {
|
|
533
|
-
const vnc = await launchVncDaemon(containerName);
|
|
534
|
-
if (vnc.up) log("vnc stack up (Xvnc + websockify + noVNC)");
|
|
535
|
-
else log(`vnc stack did not become reachable: ${vnc.reason}`);
|
|
536
|
-
vncHostPort = await publishedHostPort(containerName, VNC_CONTAINER_PORT);
|
|
537
|
-
if (vncHostPort) log(`vnc web on host 127.0.0.1:${String(vncHostPort)}`);
|
|
538
|
-
}
|
|
539
|
-
const webHostPort = await publishedHostPort(containerName, WEB_CONTAINER_PORT);
|
|
540
|
-
if (webHostPort) {
|
|
541
|
-
log(
|
|
542
|
-
`web port reserved on host 127.0.0.1:${String(webHostPort)} (forwards to the web service once agentbox.yaml sets a service expose:)`
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
const record = {
|
|
546
|
-
id,
|
|
547
|
-
name,
|
|
548
|
-
container: containerName,
|
|
549
|
-
image: imageRef,
|
|
550
|
-
workspacePath: workspace,
|
|
551
|
-
snapshotDir,
|
|
552
|
-
socketPath,
|
|
553
|
-
claudeConfigVolume: claudeSpec.volume,
|
|
554
|
-
vscodeServerVolume: vscodeServerVolumeName(id),
|
|
555
|
-
cursorServerVolume: cursorServerVolumeName(id),
|
|
556
|
-
relayToken: relayUp ? relayToken : void 0,
|
|
557
|
-
gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : void 0,
|
|
558
|
-
withPlaywright: opts.withPlaywright ? true : void 0,
|
|
559
|
-
withEnv: opts.withEnv ? true : void 0,
|
|
560
|
-
vncEnabled: vncEnabled ? true : void 0,
|
|
561
|
-
vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : void 0,
|
|
562
|
-
vncHostPort: vncHostPort ?? void 0,
|
|
563
|
-
vncPassword,
|
|
564
|
-
webContainerPort: WEB_CONTAINER_PORT,
|
|
565
|
-
webHostPort: webHostPort ?? void 0,
|
|
566
|
-
dockerVolume,
|
|
567
|
-
dockerCacheShared: dockerCacheShared || void 0,
|
|
568
|
-
projectRoot: opts.projectRoot,
|
|
569
|
-
projectIndex,
|
|
570
|
-
checkpointImage,
|
|
571
|
-
checkpointSource,
|
|
572
|
-
resourceLimits: persistableLimits(effectiveLimits),
|
|
573
|
-
createdAt
|
|
574
|
-
};
|
|
575
|
-
await recordBox(record);
|
|
576
|
-
return { record, imageBuilt: built };
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
export {
|
|
580
|
-
hostBackupHasCredentials,
|
|
581
|
-
syncClaudeCredentials,
|
|
582
|
-
sanitizeBasename,
|
|
583
|
-
defaultBoxName,
|
|
584
|
-
createBox
|
|
585
|
-
};
|
|
586
|
-
//# sourceMappingURL=chunk-KJNZP6I3.js.map
|