@madarco/agentbox 0.4.1 → 0.6.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/{chunk-J35IH7W5.js → chunk-BBZMA2K6.js} +61 -23
- package/dist/chunk-BBZMA2K6.js.map +1 -0
- package/dist/{chunk-SOMIKEN2.js → chunk-HHMWQNLF.js} +272 -214
- package/dist/chunk-HHMWQNLF.js.map +1 -0
- package/dist/{chunk-IDR4HVIC.js → chunk-HPZMD5DE.js} +2 -2
- package/dist/chunk-HPZMD5DE.js.map +1 -0
- package/dist/{chunk-NSIECUCS.js → chunk-HTTKML3C.js} +705 -289
- package/dist/chunk-HTTKML3C.js.map +1 -0
- package/dist/{chunk-WR5FFGE5.js → chunk-KJNZP6I3.js} +218 -128
- package/dist/chunk-KJNZP6I3.js.map +1 -0
- package/dist/{chunk-FQD6ZWYW.js → chunk-M7I247BK.js} +68 -65
- package/dist/chunk-M7I247BK.js.map +1 -0
- package/dist/create-6PWXI6HO-OWAMHBAK.js +15 -0
- package/dist/index.js +2394 -1283
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-LURNDNYO-UWQYPNPX.js → lifecycle-EMXR46DI-DUVBXNTV.js} +5 -5
- package/dist/{state-ZSP3ORXW-WI6KOIG3.js → state-KD7M46ZP-KHFTHFUS.js} +2 -2
- package/dist/stats-SZXOJE3D-N7OODCHW.js +19 -0
- package/package.json +3 -2
- package/runtime/docker/Dockerfile.box +65 -25
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +52 -55
- package/runtime/docker/packages/ctl/dist/bin.cjs +272 -160
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +52 -0
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +87 -7
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +28 -0
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +21 -15
- package/runtime/relay/bin.cjs +407 -12
- package/share/agentbox-setup/SKILL.md +52 -55
- package/dist/chunk-FQD6ZWYW.js.map +0 -1
- package/dist/chunk-IDR4HVIC.js.map +0 -1
- package/dist/chunk-J35IH7W5.js.map +0 -1
- package/dist/chunk-NSIECUCS.js.map +0 -1
- package/dist/chunk-SOMIKEN2.js.map +0 -1
- package/dist/chunk-WR5FFGE5.js.map +0 -1
- package/dist/create-4BQY2UYU-CGSW3RGE.js +0 -15
- package/dist/stats-GZFLPYTU-DBJ2DVBJ.js +0 -19
- /package/dist/{create-4BQY2UYU-CGSW3RGE.js.map → create-6PWXI6HO-OWAMHBAK.js.map} +0 -0
- /package/dist/{lifecycle-LURNDNYO-UWQYPNPX.js.map → lifecycle-EMXR46DI-DUVBXNTV.js.map} +0 -0
- /package/dist/{state-ZSP3ORXW-WI6KOIG3.js.map → state-KD7M46ZP-KHFTHFUS.js.map} +0 -0
- /package/dist/{stats-GZFLPYTU-DBJ2DVBJ.js.map → stats-SZXOJE3D-N7OODCHW.js.map} +0 -0
|
@@ -3,64 +3,132 @@ import {
|
|
|
3
3
|
ConfigError,
|
|
4
4
|
VNC_CONTAINER_PORT,
|
|
5
5
|
WEB_CONTAINER_PORT,
|
|
6
|
+
bindWorktrees,
|
|
6
7
|
buildClaudeMounts,
|
|
7
8
|
buildIdeMounts,
|
|
8
|
-
|
|
9
|
+
chownGitBindParents,
|
|
10
|
+
collectRepoCarryOver,
|
|
9
11
|
createSnapshot,
|
|
10
12
|
cursorServerVolumeName,
|
|
11
13
|
detectGitRepos,
|
|
12
14
|
dockerVolumeName,
|
|
13
15
|
ensureClaudeVolume,
|
|
16
|
+
ensureHomeOwnedByVscode,
|
|
14
17
|
ensureIdeVolumes,
|
|
15
18
|
ensureRelay,
|
|
16
19
|
generateRelayToken,
|
|
17
20
|
generateVncPassword,
|
|
21
|
+
gitWorktreePathFor,
|
|
18
22
|
launchCtlDaemon,
|
|
19
23
|
launchDockerdDaemon,
|
|
20
24
|
launchVncDaemon,
|
|
21
25
|
loadConfig,
|
|
22
|
-
|
|
26
|
+
pickFreshBranch,
|
|
23
27
|
registerBoxWithRelay,
|
|
24
28
|
rehydrateRelayRegistry,
|
|
25
29
|
repairIdeOwnership,
|
|
26
30
|
resolveClaudeVolume,
|
|
31
|
+
seedSetupSkillIntoVolume,
|
|
32
|
+
seedWorkspace,
|
|
33
|
+
seedWorkspaceFromDir,
|
|
27
34
|
snapshotPathFor,
|
|
28
|
-
verifyOverlay,
|
|
29
35
|
vscodeServerVolumeName
|
|
30
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-HTTKML3C.js";
|
|
31
37
|
import {
|
|
38
|
+
STATE_DIR,
|
|
32
39
|
allocateProjectIndex,
|
|
33
40
|
readState,
|
|
34
41
|
recordBox
|
|
35
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-HPZMD5DE.js";
|
|
36
43
|
import {
|
|
37
|
-
CHECKPOINT_MOUNT,
|
|
38
44
|
CONTAINER_EXPORT_MERGED,
|
|
39
|
-
CONTAINER_EXPORT_UPPER,
|
|
40
45
|
DEFAULT_BOX_IMAGE,
|
|
41
46
|
DEFAULT_ENV_PATTERNS,
|
|
42
47
|
boxRunDirFor,
|
|
43
48
|
containerExists,
|
|
44
49
|
copyHostEnvFilesToBox,
|
|
50
|
+
copyHostFilesToBox,
|
|
45
51
|
dockerInfo,
|
|
46
52
|
dockerStorageDriver,
|
|
47
53
|
ensureImage,
|
|
48
54
|
ensureVolume,
|
|
49
55
|
publishedHostPort,
|
|
50
|
-
|
|
56
|
+
resolveCheckpoint,
|
|
51
57
|
runBox
|
|
52
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-HHMWQNLF.js";
|
|
53
59
|
|
|
54
|
-
// ../../packages/sandbox-docker/dist/chunk-
|
|
60
|
+
// ../../packages/sandbox-docker/dist/chunk-A4F6SVSK.js
|
|
55
61
|
import { randomBytes } from "crypto";
|
|
56
|
-
import { mkdir, stat } from "fs/promises";
|
|
62
|
+
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
57
63
|
import { homedir } from "os";
|
|
58
|
-
import { basename, join, resolve } from "path";
|
|
59
|
-
import { execa as
|
|
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";
|
|
60
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
|
+
}
|
|
61
129
|
async function writeBoxEnvFile(container, env) {
|
|
62
130
|
const body = formatBoxEnvBody(env);
|
|
63
|
-
const result = await
|
|
131
|
+
const result = await execa2(
|
|
64
132
|
"docker",
|
|
65
133
|
["exec", "--user", "root", "-i", container, "sh", "-c", "umask 022 && cat > /etc/agentbox/box.env"],
|
|
66
134
|
{ input: body, reject: false }
|
|
@@ -114,8 +182,8 @@ async function pathExists(p) {
|
|
|
114
182
|
async function buildIdentityMounts() {
|
|
115
183
|
const home = homedir();
|
|
116
184
|
const candidates = [
|
|
117
|
-
{ src:
|
|
118
|
-
{ src:
|
|
185
|
+
{ src: join2(home, ".codex"), dst: "/home/vscode/.codex", readOnly: false },
|
|
186
|
+
{ src: join2(home, ".gitconfig"), dst: "/home/vscode/.gitconfig", readOnly: true }
|
|
119
187
|
];
|
|
120
188
|
const out = [];
|
|
121
189
|
for (const c of candidates) {
|
|
@@ -132,7 +200,7 @@ async function createBox(opts) {
|
|
|
132
200
|
if (!await pathExists(workspace)) {
|
|
133
201
|
throw new Error(`workspace does not exist: ${workspace}`);
|
|
134
202
|
}
|
|
135
|
-
const cfgPath =
|
|
203
|
+
const cfgPath = join2(workspace, "agentbox.yaml");
|
|
136
204
|
if (await pathExists(cfgPath)) {
|
|
137
205
|
try {
|
|
138
206
|
const cfg = await loadConfig(cfgPath);
|
|
@@ -147,11 +215,29 @@ async function createBox(opts) {
|
|
|
147
215
|
}
|
|
148
216
|
await dockerInfo();
|
|
149
217
|
log("docker daemon reachable");
|
|
150
|
-
|
|
151
|
-
|
|
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, {
|
|
152
238
|
onProgress: (line) => log(`[image] ${line}`)
|
|
153
239
|
});
|
|
154
|
-
log(built ? `built image ${
|
|
240
|
+
log(built ? `built image ${ensureRef}` : `using cached image ${imageRef}`);
|
|
155
241
|
let relayUp = false;
|
|
156
242
|
try {
|
|
157
243
|
await ensureRelay({ onLog: log });
|
|
@@ -168,78 +254,54 @@ async function createBox(opts) {
|
|
|
168
254
|
if (await containerExists(containerName)) {
|
|
169
255
|
throw new Error(`container ${containerName} already exists; remove it first`);
|
|
170
256
|
}
|
|
171
|
-
|
|
172
|
-
|
|
257
|
+
let projectIndex;
|
|
258
|
+
if (opts.projectRoot) {
|
|
259
|
+
projectIndex = allocateProjectIndex(await readState(), opts.projectRoot);
|
|
260
|
+
}
|
|
261
|
+
const repoCarryOvers = [];
|
|
173
262
|
const gitWorktreeRecords = [];
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (repos.length > 0) {
|
|
177
|
-
log(
|
|
178
|
-
`detected ${String(repos.length)} git repo(s): ` + repos.map((r) => `${r.kind}${r.relPathFromWorkspace ? "@" + r.relPathFromWorkspace : ""}`).join(", ")
|
|
179
|
-
);
|
|
263
|
+
if (checkpointImage && restoredWorktrees && restoredWorktrees.length > 0) {
|
|
264
|
+
gitWorktreeRecords.push(...restoredWorktrees);
|
|
180
265
|
}
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
});
|
|
199
|
-
if (r.kind === "nested") {
|
|
200
|
-
nestedWorktreeBinds.push({
|
|
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,
|
|
201
283
|
containerPath,
|
|
202
|
-
|
|
284
|
+
gitWorktreePath,
|
|
285
|
+
branch,
|
|
286
|
+
relPathFromWorkspace: r.relPathFromWorkspace
|
|
203
287
|
});
|
|
204
288
|
}
|
|
205
289
|
}
|
|
206
|
-
let lowerPath = workspace;
|
|
207
|
-
const rootWorktree = gitWorktreeRecords.find((w) => w.kind === "root");
|
|
208
|
-
if (rootWorktree) {
|
|
209
|
-
lowerPath = rootWorktree.hostWorktreeDir;
|
|
210
|
-
log(`using worktree as overlay lower: ${lowerPath}`);
|
|
211
|
-
}
|
|
212
290
|
let snapshotDir = null;
|
|
213
|
-
|
|
214
|
-
|
|
291
|
+
const snapshotIsUseful = !checkpointImage && repoCarryOvers.length === 0;
|
|
292
|
+
if (opts.useSnapshot && snapshotIsUseful) {
|
|
293
|
+
snapshotDir = snapshotPathFor({ id, name, projectIndex });
|
|
215
294
|
log(`cloning workspace to ${snapshotDir} (APFS clone where available)`);
|
|
216
|
-
const snap = await createSnapshot({ source:
|
|
295
|
+
const snap = await createSnapshot({ source: workspace, destination: snapshotDir });
|
|
217
296
|
log(`pruned ${snap.prunedPaths.length} platform-dependent dirs from snapshot`);
|
|
218
|
-
|
|
297
|
+
} else if (opts.useSnapshot && !checkpointImage) {
|
|
298
|
+
log("skipping --host-snapshot: git worktree path reads content from .git, not from a workspace clone");
|
|
219
299
|
}
|
|
220
|
-
let lowerDirs;
|
|
221
|
-
let checkpointVolume;
|
|
222
|
-
let checkpointSource;
|
|
223
|
-
if (opts.checkpointRef) {
|
|
224
|
-
const projectRootForCkpt = opts.projectRoot ?? workspace;
|
|
225
|
-
const spec = await resolveCheckpointLower(projectRootForCkpt, opts.checkpointRef);
|
|
226
|
-
checkpointVolume = spec.volume;
|
|
227
|
-
const layerDirs = spec.subpaths.map((s) => `${CHECKPOINT_MOUNT}/${s}`);
|
|
228
|
-
lowerDirs = spec.type === "merged" ? layerDirs : [...layerDirs, "/host-src"];
|
|
229
|
-
checkpointSource = { ref: opts.checkpointRef, type: spec.type, chain: spec.chain };
|
|
230
|
-
log(
|
|
231
|
-
`starting from checkpoint ${opts.checkpointRef} (${spec.type}, ${String(spec.subpaths.length)} layer(s), volume ${spec.volume})`
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
const upperVolume = `agentbox-upper-${id}`;
|
|
235
|
-
await ensureVolume(upperVolume);
|
|
236
300
|
await ensureIdeVolumes(id);
|
|
237
301
|
const dockerCacheShared = opts.docker?.sharedCache === true;
|
|
238
302
|
const dockerVolume = dockerVolumeName(id, dockerCacheShared);
|
|
239
303
|
await ensureVolume(dockerVolume);
|
|
240
|
-
log(
|
|
241
|
-
`prepared volumes ${upperVolume}, ${vscodeServerVolumeName(id)}, ${cursorServerVolumeName(id)}, ${dockerVolume}`
|
|
242
|
-
);
|
|
304
|
+
log(`prepared volumes ${vscodeServerVolumeName(id)}, ${cursorServerVolumeName(id)}, ${dockerVolume}`);
|
|
243
305
|
const ide = buildIdeMounts(id);
|
|
244
306
|
const claudeSpec = resolveClaudeVolume({
|
|
245
307
|
isolate: opts.claudeConfig?.isolate ?? false,
|
|
@@ -247,7 +309,7 @@ async function createBox(opts) {
|
|
|
247
309
|
});
|
|
248
310
|
const claudeEnsured = await ensureClaudeVolume(claudeSpec, {
|
|
249
311
|
syncFromHost: true,
|
|
250
|
-
image:
|
|
312
|
+
image: ensureRef,
|
|
251
313
|
hostWorkspace: workspace
|
|
252
314
|
});
|
|
253
315
|
if (claudeEnsured.synced) {
|
|
@@ -257,44 +319,47 @@ async function createBox(opts) {
|
|
|
257
319
|
`filtered ${String(claudeEnsured.filteredHookCount)} host-path hook(s) (paths under ~/)`
|
|
258
320
|
);
|
|
259
321
|
}
|
|
260
|
-
if (claudeEnsured.
|
|
261
|
-
log("
|
|
322
|
+
if (claudeEnsured.installMethodFixed) {
|
|
323
|
+
log("set installMethod=native in synced .claude.json (matches box native install)");
|
|
262
324
|
}
|
|
263
325
|
if (claudeEnsured.aliasedProjectKey) {
|
|
264
326
|
log(`aliased project state for ${workspace} -> /workspace in synced .claude.json`);
|
|
265
327
|
}
|
|
328
|
+
if (claudeEnsured.workspaceTrusted) {
|
|
329
|
+
log("pre-trusted /workspace in synced .claude.json (skips the trust dialog)");
|
|
330
|
+
}
|
|
266
331
|
} else if (claudeEnsured.created) {
|
|
267
332
|
log(`created empty volume ${claudeSpec.volume} (no host ~/.claude to sync)`);
|
|
268
333
|
} else {
|
|
269
334
|
log(`reusing volume ${claudeSpec.volume} (no host ~/.claude to sync)`);
|
|
270
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
|
+
}
|
|
271
347
|
const claudeMounts = buildClaudeMounts(claudeSpec, process.env);
|
|
272
|
-
const boxDir = boxRunDirFor(id);
|
|
273
|
-
const socketDir =
|
|
274
|
-
const socketPath =
|
|
275
|
-
const mergedExportDir =
|
|
276
|
-
|
|
277
|
-
await
|
|
278
|
-
await mkdir(mergedExportDir, { recursive: true });
|
|
279
|
-
await mkdir(upperExportDir, { recursive: true });
|
|
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 });
|
|
280
354
|
const extraVolumes = await buildIdentityMounts();
|
|
281
355
|
extraVolumes.push(...claudeMounts.extraVolumes);
|
|
282
356
|
extraVolumes.push(...ide.extraVolumes);
|
|
283
357
|
extraVolumes.push(`${socketDir}:/run/agentbox`);
|
|
284
358
|
extraVolumes.push(`${mergedExportDir}:${CONTAINER_EXPORT_MERGED}`);
|
|
285
|
-
extraVolumes.push(`${upperExportDir}:${CONTAINER_EXPORT_UPPER}`);
|
|
286
359
|
extraVolumes.push(`${dockerVolume}:/var/lib/docker`);
|
|
287
360
|
for (const w of gitWorktreeRecords) {
|
|
288
361
|
extraVolumes.push(`${w.hostMainRepo}/.git:${w.hostMainRepo}/.git`);
|
|
289
362
|
}
|
|
290
|
-
for (const w of gitWorktreeRecords) {
|
|
291
|
-
if (w.kind === "nested") {
|
|
292
|
-
extraVolumes.push(`${w.hostWorktreeDir}:/agentbox-worktrees/${w.relPathFromWorkspace}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (checkpointVolume) {
|
|
296
|
-
extraVolumes.push(`${checkpointVolume}:${CHECKPOINT_MOUNT}:ro`);
|
|
297
|
-
}
|
|
298
363
|
for (const v of extraVolumes) log(`mounting agent dir: ${v}`);
|
|
299
364
|
const relayToken = generateRelayToken();
|
|
300
365
|
if (relayUp) {
|
|
@@ -305,6 +370,7 @@ async function createBox(opts) {
|
|
|
305
370
|
name,
|
|
306
371
|
containerName,
|
|
307
372
|
createdAt,
|
|
373
|
+
projectIndex,
|
|
308
374
|
worktrees: gitWorktreeRecords
|
|
309
375
|
});
|
|
310
376
|
log(`registered box token with relay`);
|
|
@@ -326,10 +392,6 @@ async function createBox(opts) {
|
|
|
326
392
|
const webPortMappings = [
|
|
327
393
|
{ hostPort: 0, containerPort: WEB_CONTAINER_PORT, hostIp: "127.0.0.1" }
|
|
328
394
|
];
|
|
329
|
-
let projectIndex;
|
|
330
|
-
if (opts.projectRoot) {
|
|
331
|
-
projectIndex = allocateProjectIndex(await readState(), opts.projectRoot);
|
|
332
|
-
}
|
|
333
395
|
const agentboxEnv = {
|
|
334
396
|
AGENTBOX: "1",
|
|
335
397
|
AGENTBOX_BOX_NAME: name,
|
|
@@ -355,8 +417,6 @@ async function createBox(opts) {
|
|
|
355
417
|
await runBox({
|
|
356
418
|
name: containerName,
|
|
357
419
|
image: imageRef,
|
|
358
|
-
lowerPath,
|
|
359
|
-
upperVolume,
|
|
360
420
|
extraVolumes,
|
|
361
421
|
limits: effectiveLimits,
|
|
362
422
|
portMappings: [...vncPortMappings, ...webPortMappings],
|
|
@@ -370,27 +430,46 @@ async function createBox(opts) {
|
|
|
370
430
|
}
|
|
371
431
|
});
|
|
372
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
|
+
}
|
|
373
440
|
const boxEnv = await writeBoxEnvFile(containerName, boxEnvForFile);
|
|
374
441
|
if (boxEnv.ok) log("wrote /etc/agentbox/box.env");
|
|
375
442
|
else log(`writing /etc/agentbox/box.env failed: ${boxEnv.reason}`);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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 });
|
|
381
458
|
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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)");
|
|
392
472
|
}
|
|
393
|
-
log("overlay verified");
|
|
394
473
|
await repairIdeOwnership(containerName);
|
|
395
474
|
log(".vscode-server + .cursor-server ownership verified");
|
|
396
475
|
const ctl = await launchCtlDaemon(containerName, socketPath);
|
|
@@ -398,13 +477,13 @@ ${detail}`);
|
|
|
398
477
|
else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);
|
|
399
478
|
const dockerd = await launchDockerdDaemon(containerName);
|
|
400
479
|
if (dockerd.up) {
|
|
401
|
-
log(`dockerd up (
|
|
480
|
+
log(`dockerd up (data root=${dockerVolume})`);
|
|
402
481
|
} else {
|
|
403
482
|
log(`dockerd did not become ready: ${dockerd.reason}`);
|
|
404
483
|
}
|
|
405
484
|
if (opts.withPlaywright) {
|
|
406
485
|
log("installing @playwright/cli@latest (--with-playwright)");
|
|
407
|
-
const result = await
|
|
486
|
+
const result = await execa3(
|
|
408
487
|
"docker",
|
|
409
488
|
[
|
|
410
489
|
"exec",
|
|
@@ -437,6 +516,18 @@ ${detail}`);
|
|
|
437
516
|
});
|
|
438
517
|
log(copied > 0 ? `copied ${String(copied)} env/config file(s)` : "no env/config files found");
|
|
439
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
|
+
}
|
|
440
531
|
let vncHostPort = null;
|
|
441
532
|
if (vncEnabled) {
|
|
442
533
|
const vnc = await launchVncDaemon(containerName);
|
|
@@ -457,8 +548,6 @@ ${detail}`);
|
|
|
457
548
|
container: containerName,
|
|
458
549
|
image: imageRef,
|
|
459
550
|
workspacePath: workspace,
|
|
460
|
-
lowerPath,
|
|
461
|
-
upperVolume,
|
|
462
551
|
snapshotDir,
|
|
463
552
|
socketPath,
|
|
464
553
|
claudeConfigVolume: claudeSpec.volume,
|
|
@@ -478,19 +567,20 @@ ${detail}`);
|
|
|
478
567
|
dockerCacheShared: dockerCacheShared || void 0,
|
|
479
568
|
projectRoot: opts.projectRoot,
|
|
480
569
|
projectIndex,
|
|
481
|
-
|
|
482
|
-
checkpointVolume,
|
|
570
|
+
checkpointImage,
|
|
483
571
|
checkpointSource,
|
|
484
572
|
resourceLimits: persistableLimits(effectiveLimits),
|
|
485
573
|
createdAt
|
|
486
574
|
};
|
|
487
575
|
await recordBox(record);
|
|
488
|
-
return { record,
|
|
576
|
+
return { record, imageBuilt: built };
|
|
489
577
|
}
|
|
490
578
|
|
|
491
579
|
export {
|
|
580
|
+
hostBackupHasCredentials,
|
|
581
|
+
syncClaudeCredentials,
|
|
492
582
|
sanitizeBasename,
|
|
493
583
|
defaultBoxName,
|
|
494
584
|
createBox
|
|
495
585
|
};
|
|
496
|
-
//# sourceMappingURL=chunk-
|
|
586
|
+
//# sourceMappingURL=chunk-KJNZP6I3.js.map
|