@madarco/agentbox 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_cloud-attach-ZXBCNWJX.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-BXQMIEHC.js} +459 -110
- package/dist/chunk-BXQMIEHC.js.map +1 -0
- package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
- package/dist/chunk-G3H2L3O2.js.map +1 -0
- package/dist/{chunk-7KOEFGN2.js → chunk-GU5LW4B5.js} +385 -31
- package/dist/chunk-GU5LW4B5.js.map +1 -0
- package/dist/chunk-KL36BRN4.js +455 -0
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-NCJP5MTN.js} +1281 -556
- package/dist/chunk-NCJP5MTN.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-ETCFRVPA.js → dist-32EZBYG4.js} +50 -20
- package/dist/{dist-R67WMLCF.js → dist-CX5CGVEB.js} +120 -10
- package/dist/dist-CX5CGVEB.js.map +1 -0
- package/dist/{dist-QZGJIBT5.js → dist-GDHP34ZK.js} +141 -75
- package/dist/dist-GDHP34ZK.js.map +1 -0
- package/dist/dist-XML54CNB.js +849 -0
- package/dist/dist-XML54CNB.js.map +1 -0
- package/dist/index.js +3881 -867
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js +18 -0
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js.map +1 -0
- package/package.json +7 -5
- package/runtime/daytona/custom-system-CLAUDE.md +39 -0
- package/runtime/docker/Dockerfile.box +22 -0
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +1214 -98
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
- package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
- package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
- package/runtime/hetzner/agentbox-setup-skill.md +1 -1
- package/runtime/hetzner/agentbox-vnc-start +15 -1
- package/runtime/hetzner/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1214 -98
- package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
- package/runtime/hetzner/gh-shim +263 -0
- package/runtime/hetzner/git-shim +131 -0
- package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/scripts/install-box.sh +11 -2
- package/runtime/relay/bin.cjs +1146 -63
- package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
- package/runtime/vercel/agentbox-codex-hooks.json +68 -0
- package/runtime/vercel/agentbox-open +28 -0
- package/runtime/vercel/agentbox-setup-skill.md +196 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23466 -0
- package/runtime/vercel/custom-system-CLAUDE.md +50 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +274 -0
- package/share/agentbox-setup/SKILL.md +1 -1
- package/share/host-skills/agentbox/SKILL.md +29 -0
- package/share/host-skills/agentbox-info/SKILL.md +211 -0
- package/share/host-skills/codex/agentbox.md +35 -0
- package/share/host-skills/opencode/agentbox.md +26 -0
- package/dist/_cloud-attach-DMVH6GWO.js +0 -12
- package/dist/chunk-7KOEFGN2.js.map +0 -1
- package/dist/chunk-NAVL4R34.js.map +0 -1
- package/dist/chunk-NW5NYTQM.js.map +0 -1
- package/dist/chunk-UK72UQ5U.js.map +0 -1
- package/dist/chunk-V5KZGB5V.js.map +0 -1
- package/dist/dist-QZGJIBT5.js.map +0 -1
- package/dist/dist-R67WMLCF.js.map +0 -1
- /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-ZXBCNWJX.js.map} +0 -0
- /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
- /package/dist/{dist-ETCFRVPA.js.map → dist-32EZBYG4.js.map} +0 -0
|
@@ -1,50 +1,64 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
CLAUDE_FORWARDED_ENV_KEYS,
|
|
4
|
+
CODEX_CREDENTIALS_BACKUP_FILE,
|
|
4
5
|
CODEX_FORWARDED_ENV_KEYS,
|
|
6
|
+
CREDENTIALS_BACKUP_FILE,
|
|
7
|
+
OPENCODE_CREDENTIALS_BACKUP_FILE,
|
|
5
8
|
OPENCODE_FORWARDED_ENV_KEYS,
|
|
6
|
-
allocateProjectIndex,
|
|
7
9
|
buildHostEnvFindArgs,
|
|
8
|
-
|
|
10
|
+
buildTmuxConfigShellSnippet,
|
|
9
11
|
ensureRelay,
|
|
10
12
|
forgetBoxFromRelay,
|
|
11
13
|
generateRelayToken,
|
|
12
14
|
generateVncPassword,
|
|
13
15
|
hashProjectPath,
|
|
16
|
+
isRealAgentCredential,
|
|
14
17
|
portlessAlias,
|
|
15
18
|
portlessGetUrl,
|
|
16
19
|
portlessUnalias,
|
|
17
20
|
projectDirSegment,
|
|
18
|
-
readState,
|
|
19
|
-
recordBox,
|
|
20
21
|
registerBoxWithRelay,
|
|
21
|
-
removeBoxRecord,
|
|
22
22
|
sanitizeMnemonic,
|
|
23
23
|
stageClaudeCredentialsForUpload,
|
|
24
24
|
stageClaudeStaticForUpload,
|
|
25
25
|
stageCodexCredentialsForUpload,
|
|
26
26
|
stageCodexStaticForUpload,
|
|
27
27
|
stageOpencodeCredentialsForUpload,
|
|
28
|
+
stageOpencodeStateForUpload,
|
|
28
29
|
stageOpencodeStaticForUpload
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-NCJP5MTN.js";
|
|
31
|
+
import {
|
|
32
|
+
allocateProjectIndex,
|
|
33
|
+
detectGitRepos,
|
|
34
|
+
readState,
|
|
35
|
+
recordBox,
|
|
36
|
+
removeBoxRecord
|
|
37
|
+
} from "./chunk-KL36BRN4.js";
|
|
30
38
|
|
|
31
39
|
// ../../packages/sandbox-cloud/dist/index.js
|
|
32
40
|
import { randomBytes } from "crypto";
|
|
33
41
|
import { basename as basename2 } from "path";
|
|
34
|
-
import {
|
|
42
|
+
import { chmod, mkdir, writeFile } from "fs/promises";
|
|
43
|
+
import { dirname } from "path";
|
|
44
|
+
import { mkdir as mkdir2, readFile, readdir, rm, writeFile as writeFile2 } from "fs/promises";
|
|
35
45
|
import { homedir } from "os";
|
|
36
46
|
import { basename, join } from "path";
|
|
37
|
-
import { mkdtemp, rm as rm2, writeFile as
|
|
47
|
+
import { mkdtemp, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
38
48
|
import { tmpdir } from "os";
|
|
39
49
|
import { join as join2 } from "path";
|
|
40
50
|
import { execa } from "execa";
|
|
41
|
-
import {
|
|
51
|
+
import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
|
|
52
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
42
53
|
import { join as join3 } from "path";
|
|
43
|
-
import { parse as parseYaml } from "yaml";
|
|
44
54
|
import { execa as execa2 } from "execa";
|
|
55
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
56
|
+
import { join as join4 } from "path";
|
|
57
|
+
import { parse as parseYaml } from "yaml";
|
|
58
|
+
import { execa as execa3 } from "execa";
|
|
45
59
|
import { existsSync, mkdirSync, renameSync, statSync } from "fs";
|
|
46
|
-
import { mkdtemp as
|
|
47
|
-
import { tmpdir as
|
|
60
|
+
import { mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
|
|
61
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
48
62
|
import {
|
|
49
63
|
basename as hostBasename,
|
|
50
64
|
dirname as hostDirname,
|
|
@@ -52,10 +66,10 @@ import {
|
|
|
52
66
|
resolve as hostResolve
|
|
53
67
|
} from "path";
|
|
54
68
|
import { posix } from "path";
|
|
55
|
-
import { execa as
|
|
56
|
-
import { mkdtemp as
|
|
57
|
-
import { tmpdir as
|
|
58
|
-
import { join as
|
|
69
|
+
import { execa as execa4 } from "execa";
|
|
70
|
+
import { mkdtemp as mkdtemp4, rm as rm5, stat } from "fs/promises";
|
|
71
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
72
|
+
import { join as join5 } from "path";
|
|
59
73
|
var CREDENTIALS_VOLUME = "agentbox-credentials";
|
|
60
74
|
var AGENT_SPECS = [
|
|
61
75
|
{
|
|
@@ -185,6 +199,33 @@ async function seedCredentialsOne(backend, handle, spec, opts) {
|
|
|
185
199
|
await staged.cleanup();
|
|
186
200
|
}
|
|
187
201
|
}
|
|
202
|
+
var OPENCODE_STATE_DIR = "/home/vscode/.local/state/opencode";
|
|
203
|
+
async function seedOpencodeModelState(backend, handle, opts = {}) {
|
|
204
|
+
const log = opts.onLog ?? (() => {
|
|
205
|
+
});
|
|
206
|
+
const staged = await stageOpencodeStateForUpload();
|
|
207
|
+
if (staged.tarballPath === null) {
|
|
208
|
+
log("opencode: no host model selection to seed");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const remoteTar = "/tmp/agentbox-opencode-state.tar.gz";
|
|
213
|
+
await backend.uploadFile(handle, staged.tarballPath, remoteTar);
|
|
214
|
+
const res = await backend.exec(
|
|
215
|
+
handle,
|
|
216
|
+
`set -e; mkdir -p ${OPENCODE_STATE_DIR}; tar -xzf ${remoteTar} -C ${OPENCODE_STATE_DIR}; chown -R vscode:vscode ${OPENCODE_STATE_DIR} 2>/dev/null || true; rm -f ${remoteTar}`
|
|
217
|
+
);
|
|
218
|
+
if (res.exitCode !== 0) {
|
|
219
|
+
log(
|
|
220
|
+
`opencode: model-state seed failed (exit ${String(res.exitCode)}); box falls back to OpenCode's default model. stderr: ${res.stderr.slice(-200)}`
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
log("opencode: model selection seeded \u2713");
|
|
225
|
+
} finally {
|
|
226
|
+
await staged.cleanup();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
188
229
|
function agentSpecsForCloud() {
|
|
189
230
|
return AGENT_SPECS.map((s) => ({
|
|
190
231
|
kind: s.kind,
|
|
@@ -193,6 +234,39 @@ function agentSpecsForCloud() {
|
|
|
193
234
|
credentialsSubpath: s.credentialsSubpath
|
|
194
235
|
}));
|
|
195
236
|
}
|
|
237
|
+
var EXTRACT_SPECS = [
|
|
238
|
+
{ kind: "claude", boxPath: "/home/vscode/.claude/.credentials.json", hostBackup: CREDENTIALS_BACKUP_FILE },
|
|
239
|
+
{ kind: "codex", boxPath: "/home/vscode/.codex/auth.json", hostBackup: CODEX_CREDENTIALS_BACKUP_FILE },
|
|
240
|
+
{
|
|
241
|
+
kind: "opencode",
|
|
242
|
+
boxPath: "/home/vscode/.local/share/opencode/auth.json",
|
|
243
|
+
hostBackup: OPENCODE_CREDENTIALS_BACKUP_FILE
|
|
244
|
+
}
|
|
245
|
+
];
|
|
246
|
+
async function extractCloudAgentCredentials(backend, handle, opts = {}) {
|
|
247
|
+
const log = opts.onLog ?? (() => {
|
|
248
|
+
});
|
|
249
|
+
const extracted = [];
|
|
250
|
+
for (const spec of EXTRACT_SPECS) {
|
|
251
|
+
const hostBackup = opts.backups?.[spec.kind] ?? spec.hostBackup;
|
|
252
|
+
try {
|
|
253
|
+
const r = await backend.exec(handle, `cat ${spec.boxPath} 2>/dev/null`, { noRetry: true });
|
|
254
|
+
const text = r.stdout;
|
|
255
|
+
if (r.exitCode !== 0 || !text || !isRealAgentCredential(spec.kind, text)) continue;
|
|
256
|
+
await mkdir(dirname(hostBackup), { recursive: true });
|
|
257
|
+
await writeFile(hostBackup, text, { mode: 384 });
|
|
258
|
+
await chmod(hostBackup, 384).catch(() => {
|
|
259
|
+
});
|
|
260
|
+
extracted.push(spec.kind);
|
|
261
|
+
log(`extracted ${spec.kind} login from box to ${hostBackup}`);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
log(
|
|
264
|
+
`WARN: ${spec.kind} credential extract failed (${err instanceof Error ? err.message : String(err)}) \u2014 skipping`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return extracted;
|
|
269
|
+
}
|
|
196
270
|
var CLOUD_CHECKPOINTS_ROOT = join(homedir(), ".agentbox", "cloud-checkpoints");
|
|
197
271
|
var CLOUD_SNAPSHOT_NAME_PREFIX = "agentbox-ckpt-";
|
|
198
272
|
function cloudSnapshotName(projectRoot, name) {
|
|
@@ -240,7 +314,7 @@ async function resolveCloudCheckpoint(projectRoot, backend, ref) {
|
|
|
240
314
|
}
|
|
241
315
|
async function writeCloudCheckpointManifest(projectRoot, backend, name, fields) {
|
|
242
316
|
const dir = checkpointDir(backend, projectRoot, name);
|
|
243
|
-
await
|
|
317
|
+
await mkdir2(dir, { recursive: true });
|
|
244
318
|
const manifest = {
|
|
245
319
|
schema: 1,
|
|
246
320
|
name,
|
|
@@ -250,7 +324,7 @@ async function writeCloudCheckpointManifest(projectRoot, backend, name, fields)
|
|
|
250
324
|
sourceBoxName: fields.sourceBoxName,
|
|
251
325
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
252
326
|
};
|
|
253
|
-
await
|
|
327
|
+
await writeFile2(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
254
328
|
return { name, dir, manifest };
|
|
255
329
|
}
|
|
256
330
|
async function removeCloudCheckpointDir(projectRoot, backend, name) {
|
|
@@ -289,7 +363,7 @@ async function uploadEnvFiles(args) {
|
|
|
289
363
|
log(`env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
|
|
290
364
|
return { copied: 0 };
|
|
291
365
|
}
|
|
292
|
-
await
|
|
366
|
+
await writeFile3(join2(stage, ".marker"), "").catch(() => {
|
|
293
367
|
});
|
|
294
368
|
await args.backend.uploadFile(args.handle, localTar, REMOTE_TAR_PATH);
|
|
295
369
|
const extract = await args.backend.exec(
|
|
@@ -307,10 +381,106 @@ async function uploadEnvFiles(args) {
|
|
|
307
381
|
}
|
|
308
382
|
return { copied: list.length };
|
|
309
383
|
}
|
|
384
|
+
var BOX_HOME = "/home/vscode";
|
|
385
|
+
async function uploadCarryPaths(args) {
|
|
386
|
+
const log = args.onLog ?? (() => {
|
|
387
|
+
});
|
|
388
|
+
if (args.entries.length === 0) {
|
|
389
|
+
return { copied: 0, errors: [], applied: [] };
|
|
390
|
+
}
|
|
391
|
+
const stage = await mkdtemp2(join3(tmpdir2(), "agentbox-carry-"));
|
|
392
|
+
const errors = [];
|
|
393
|
+
const applied = [];
|
|
394
|
+
let copied = 0;
|
|
395
|
+
try {
|
|
396
|
+
for (const [i, entry] of args.entries.entries()) {
|
|
397
|
+
const where = `carry[${String(i)}] "${entry.rawSrc}"`;
|
|
398
|
+
if (entry.kind === "missing") {
|
|
399
|
+
log(`${where}: skipped (missing on host, optional)`);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
await uploadOneEntry({
|
|
404
|
+
backend: args.backend,
|
|
405
|
+
handle: args.handle,
|
|
406
|
+
entry,
|
|
407
|
+
stageDir: stage,
|
|
408
|
+
index: i
|
|
409
|
+
});
|
|
410
|
+
copied += 1;
|
|
411
|
+
applied.push({ src: entry.absSrc, dest: entry.absDest, bytes: entry.bytes ?? 0 });
|
|
412
|
+
} catch (err) {
|
|
413
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
414
|
+
errors.push(`${where}: ${msg}`);
|
|
415
|
+
log(`${where}: failed: ${msg}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
} finally {
|
|
419
|
+
await rm3(stage, { recursive: true, force: true });
|
|
420
|
+
}
|
|
421
|
+
return { copied, errors, applied };
|
|
422
|
+
}
|
|
423
|
+
async function uploadOneEntry(args) {
|
|
424
|
+
const { entry } = args;
|
|
425
|
+
if (entry.kind === "missing") return;
|
|
426
|
+
const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
|
|
427
|
+
const isDir = entry.kind === "dir";
|
|
428
|
+
const parentDir = isDir ? boxDest : dirnameUnix(boxDest);
|
|
429
|
+
const localTar = join3(args.stageDir, `carry-${String(args.index)}.tar`);
|
|
430
|
+
const tarArgs = isDir ? ["-C", entry.absSrc, "-cf", localTar, "."] : ["-C", dirnameUnix(entry.absSrc), "-cf", localTar, basenameUnix(entry.absSrc)];
|
|
431
|
+
const packed = await execa2("tar", tarArgs, { reject: false });
|
|
432
|
+
if (packed.exitCode !== 0) {
|
|
433
|
+
throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
|
|
434
|
+
}
|
|
435
|
+
const remoteTar = `/tmp/agentbox-carry-${String(args.index)}.tar`;
|
|
436
|
+
await args.backend.uploadFile(args.handle, localTar, remoteTar);
|
|
437
|
+
const mode = entry.mode !== void 0 ? entry.mode.toString(8).padStart(4, "0") : "";
|
|
438
|
+
const uid = entry.user ?? 1e3;
|
|
439
|
+
const fileBase = !isDir ? basenameUnix(entry.absSrc) : "";
|
|
440
|
+
const destBase = !isDir ? basenameUnix(boxDest) : "";
|
|
441
|
+
const renameNeeded = !isDir && fileBase !== destBase;
|
|
442
|
+
const parts = [
|
|
443
|
+
`mkdir -p ${shellQuote(parentDir)}`,
|
|
444
|
+
isDir ? `tar -xf ${remoteTar} -C ${shellQuote(boxDest)} --no-same-permissions --no-same-owner -m` : `tar -xf ${remoteTar} -C ${shellQuote(parentDir)} --no-same-permissions --no-same-owner -m`
|
|
445
|
+
];
|
|
446
|
+
if (renameNeeded) {
|
|
447
|
+
parts.push(
|
|
448
|
+
`mv ${shellQuote(`${parentDir}/${fileBase}`)} ${shellQuote(boxDest)}`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (mode) parts.push(`chmod -R ${mode} ${shellQuote(boxDest)}`);
|
|
452
|
+
parts.push(`chown -R ${String(uid)}:${String(uid)} ${shellQuote(boxDest)}`);
|
|
453
|
+
if (boxDest.startsWith(BOX_HOME + "/") && parentDir !== BOX_HOME) {
|
|
454
|
+
parts.push(
|
|
455
|
+
`parent=$(dirname ${shellQuote(boxDest)}); while [ "$parent" != "${BOX_HOME}" ] && [ "$parent" != "/" ]; do chown ${String(uid)}:${String(uid)} "$parent"; parent=$(dirname "$parent"); done`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
parts.push(`rm -f ${remoteTar}`);
|
|
459
|
+
const cmd = parts.join(" && ");
|
|
460
|
+
const execOpts = args.backend.name === "vercel" ? { user: "root" } : void 0;
|
|
461
|
+
const res = await args.backend.exec(args.handle, cmd, execOpts);
|
|
462
|
+
if (res.exitCode !== 0) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
`in-box extract failed (exit ${String(res.exitCode)}): ${(res.stderr || res.stdout).slice(-300)}`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function dirnameUnix(p) {
|
|
469
|
+
const i = p.lastIndexOf("/");
|
|
470
|
+
if (i <= 0) return "/";
|
|
471
|
+
return p.slice(0, i);
|
|
472
|
+
}
|
|
473
|
+
function basenameUnix(p) {
|
|
474
|
+
const i = p.lastIndexOf("/");
|
|
475
|
+
return i < 0 ? p : p.slice(i + 1);
|
|
476
|
+
}
|
|
477
|
+
function shellQuote(s) {
|
|
478
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
479
|
+
}
|
|
310
480
|
async function readExposedServicePorts(workspacePath) {
|
|
311
481
|
let text;
|
|
312
482
|
try {
|
|
313
|
-
text = await readFile2(
|
|
483
|
+
text = await readFile2(join4(workspacePath, "agentbox.yaml"), "utf8");
|
|
314
484
|
} catch {
|
|
315
485
|
return [];
|
|
316
486
|
}
|
|
@@ -366,10 +536,10 @@ async function uploadToCloudBox(backend, handle, hostSrc, boxDst) {
|
|
|
366
536
|
finalName = posix.basename(boxDst);
|
|
367
537
|
}
|
|
368
538
|
const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
|
|
369
|
-
const stage = await
|
|
539
|
+
const stage = await mkdtemp3(hostJoin(tmpdir3(), "agentbox-cp-up-"));
|
|
370
540
|
const localTar = hostJoin(stage, "payload.tar.gz");
|
|
371
541
|
try {
|
|
372
|
-
await
|
|
542
|
+
await execa3("tar", ["-C", srcParent, "-czf", localTar, srcBasename], {
|
|
373
543
|
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
374
544
|
});
|
|
375
545
|
await backend.uploadFile(handle, localTar, REMOTE_UP_TAR);
|
|
@@ -395,14 +565,14 @@ async function uploadToCloudBox(backend, handle, hostSrc, boxDst) {
|
|
|
395
565
|
throw new Error(`cloud upload extract failed: ${r.stderr || r.stdout}`);
|
|
396
566
|
}
|
|
397
567
|
} finally {
|
|
398
|
-
await
|
|
568
|
+
await rm4(stage, { recursive: true, force: true });
|
|
399
569
|
}
|
|
400
570
|
return { finalPath };
|
|
401
571
|
}
|
|
402
572
|
async function pullCloudDirContents(backend, handle, boxSrcDir, hostDstDir) {
|
|
403
573
|
const dstAbs = hostResolve(hostDstDir);
|
|
404
574
|
mkdirSync(dstAbs, { recursive: true });
|
|
405
|
-
const stage = await
|
|
575
|
+
const stage = await mkdtemp3(hostJoin(tmpdir3(), "agentbox-pull-"));
|
|
406
576
|
const localTar = hostJoin(stage, "payload.tar.gz");
|
|
407
577
|
try {
|
|
408
578
|
const packScript = [
|
|
@@ -415,11 +585,11 @@ async function pullCloudDirContents(backend, handle, boxSrcDir, hostDstDir) {
|
|
|
415
585
|
throw new Error(`cloud workspace pack failed: ${r.stderr || r.stdout}`);
|
|
416
586
|
}
|
|
417
587
|
await backend.downloadFile(handle, REMOTE_DOWN_TAR, localTar);
|
|
418
|
-
await
|
|
588
|
+
await execa3("tar", ["-xzf", localTar, "-C", dstAbs]);
|
|
419
589
|
await backend.exec(handle, `rm -f ${quoteShellArg(REMOTE_DOWN_TAR)}`).catch(() => {
|
|
420
590
|
});
|
|
421
591
|
} finally {
|
|
422
|
-
await
|
|
592
|
+
await rm4(stage, { recursive: true, force: true });
|
|
423
593
|
}
|
|
424
594
|
return { finalPath: dstAbs };
|
|
425
595
|
}
|
|
@@ -439,7 +609,7 @@ async function downloadFromCloudBox(backend, handle, boxSrc, hostDst) {
|
|
|
439
609
|
}
|
|
440
610
|
mkdirSync(hostParent, { recursive: true });
|
|
441
611
|
const finalPath = hostJoin(hostParent, finalName);
|
|
442
|
-
const stage = await
|
|
612
|
+
const stage = await mkdtemp3(hostJoin(tmpdir3(), "agentbox-cp-down-"));
|
|
443
613
|
const localTar = hostJoin(stage, "payload.tar.gz");
|
|
444
614
|
try {
|
|
445
615
|
const packScript = [
|
|
@@ -452,14 +622,14 @@ async function downloadFromCloudBox(backend, handle, boxSrc, hostDst) {
|
|
|
452
622
|
throw new Error(`cloud download pack failed: ${r.stderr || r.stdout}`);
|
|
453
623
|
}
|
|
454
624
|
await backend.downloadFile(handle, REMOTE_DOWN_TAR, localTar);
|
|
455
|
-
await
|
|
625
|
+
await execa3("tar", ["-xzf", localTar, "-C", hostParent]);
|
|
456
626
|
if (finalName !== srcBasename) {
|
|
457
627
|
renameSync(hostJoin(hostParent, srcBasename), finalPath);
|
|
458
628
|
}
|
|
459
629
|
await backend.exec(handle, `rm -f ${quoteShellArg(REMOTE_DOWN_TAR)}`).catch(() => {
|
|
460
630
|
});
|
|
461
631
|
} finally {
|
|
462
|
-
await
|
|
632
|
+
await rm4(stage, { recursive: true, force: true });
|
|
463
633
|
}
|
|
464
634
|
return { finalPath };
|
|
465
635
|
}
|
|
@@ -547,24 +717,30 @@ async function seedCloudWorkspace(args) {
|
|
|
547
717
|
const nested = repos.filter((r) => r.kind === "nested");
|
|
548
718
|
if (root) {
|
|
549
719
|
log(
|
|
550
|
-
nested.length > 0 ? `seeding /workspace from git
|
|
720
|
+
nested.length > 0 ? `seeding /workspace from shallow git clone (+${String(nested.length)} nested repo${nested.length === 1 ? "" : "s"})` : "seeding /workspace from shallow git clone"
|
|
551
721
|
);
|
|
552
|
-
await
|
|
722
|
+
await seedFromGitClone({
|
|
553
723
|
backend: args.backend,
|
|
554
724
|
handle: args.handle,
|
|
555
725
|
hostRepo: root.hostMainRepo,
|
|
556
726
|
branch: args.branch,
|
|
557
|
-
workspaceDir
|
|
727
|
+
workspaceDir,
|
|
728
|
+
bundleDepth: args.bundleDepth,
|
|
729
|
+
fromBranch: args.fromBranch,
|
|
730
|
+
useBranch: args.useBranch,
|
|
731
|
+
onLog: log
|
|
558
732
|
});
|
|
559
733
|
for (const r of nested) {
|
|
560
734
|
const sub = `${workspaceDir}/${r.relPathFromWorkspace}`;
|
|
561
|
-
log(`seeding nested repo ${r.relPathFromWorkspace} from git
|
|
562
|
-
await
|
|
735
|
+
log(`seeding nested repo ${r.relPathFromWorkspace} from shallow git clone`);
|
|
736
|
+
await seedFromGitClone({
|
|
563
737
|
backend: args.backend,
|
|
564
738
|
handle: args.handle,
|
|
565
739
|
hostRepo: r.hostMainRepo,
|
|
566
740
|
branch: args.branch,
|
|
567
|
-
workspaceDir: sub
|
|
741
|
+
workspaceDir: sub,
|
|
742
|
+
bundleDepth: args.bundleDepth,
|
|
743
|
+
onLog: log
|
|
568
744
|
});
|
|
569
745
|
}
|
|
570
746
|
return { fromGit: true, branch: args.branch };
|
|
@@ -580,41 +756,53 @@ async function seedCloudWorkspace(args) {
|
|
|
580
756
|
}
|
|
581
757
|
var STASH_CARRYOVER_REF = "refs/agentbox-carryover/stash";
|
|
582
758
|
var REMOTE_UNTRACKED_TAR = "/tmp/agentbox-carryover-untracked.tar.gz";
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
759
|
+
var DEFAULT_BUNDLE_DEPTH = 200;
|
|
760
|
+
var LARGE_BUNDLE_DEPTH = 100;
|
|
761
|
+
var LARGE_BUNDLE_THRESHOLD_BYTES = 20 * 1024 * 1024;
|
|
762
|
+
async function seedFromGitClone(args) {
|
|
763
|
+
const log = args.onLog ?? (() => {
|
|
764
|
+
});
|
|
765
|
+
const stage = await mkdtemp4(join5(tmpdir4(), "agentbox-clone-"));
|
|
766
|
+
const cloneDir = join5(stage, "clone");
|
|
767
|
+
const tarPath = join5(stage, "workspace.tar.gz");
|
|
768
|
+
const untrackedTarPath = join5(stage, "untracked.tar.gz");
|
|
769
|
+
const stashSha = args.useBranch ? null : await safeStashCreate(args.hostRepo);
|
|
770
|
+
const untrackedSize = args.useBranch ? 0 : await maybeBuildUntrackedTar(args.hostRepo, untrackedTarPath);
|
|
589
771
|
let stashRefCreated = false;
|
|
590
772
|
try {
|
|
591
773
|
if (stashSha) {
|
|
592
|
-
const ref = await
|
|
774
|
+
const ref = await execa4(
|
|
593
775
|
"git",
|
|
594
776
|
["-C", args.hostRepo, "update-ref", STASH_CARRYOVER_REF, stashSha],
|
|
595
777
|
{ reject: false }
|
|
596
778
|
);
|
|
597
779
|
stashRefCreated = ref.exitCode === 0;
|
|
598
780
|
}
|
|
599
|
-
const
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
781
|
+
const configured = args.bundleDepth;
|
|
782
|
+
const adaptive = configured === void 0;
|
|
783
|
+
const initialDepth = adaptive ? DEFAULT_BUNDLE_DEPTH : configured === 0 ? null : configured;
|
|
784
|
+
log(
|
|
785
|
+
adaptive ? `clone: depth=${String(DEFAULT_BUNDLE_DEPTH)} (default, adaptive)` : initialDepth === null ? "clone: depth=full (configured)" : `clone: depth=${String(initialDepth)} (configured)`
|
|
786
|
+
);
|
|
787
|
+
const cloneBranch = args.useBranch ?? args.fromBranch;
|
|
788
|
+
await runShallowClone(args.hostRepo, cloneDir, initialDepth, stashRefCreated, cloneBranch);
|
|
789
|
+
await tarCloneDir(cloneDir, tarPath);
|
|
790
|
+
if (adaptive && initialDepth !== null) {
|
|
791
|
+
const size = await safeFileSize(tarPath);
|
|
792
|
+
if (size > LARGE_BUNDLE_THRESHOLD_BYTES) {
|
|
793
|
+
const mb = (size / (1024 * 1024)).toFixed(1);
|
|
794
|
+
log(
|
|
795
|
+
`clone tar exceeded ${String(LARGE_BUNDLE_THRESHOLD_BYTES / (1024 * 1024))} MB at depth ${String(DEFAULT_BUNDLE_DEPTH)} (${mb} MB), rebuilding at depth ${String(LARGE_BUNDLE_DEPTH)}`
|
|
796
|
+
);
|
|
797
|
+
await rm5(cloneDir, { recursive: true, force: true });
|
|
798
|
+
await rm5(tarPath, { force: true });
|
|
799
|
+
await runShallowClone(args.hostRepo, cloneDir, LARGE_BUNDLE_DEPTH, stashRefCreated, cloneBranch);
|
|
800
|
+
await tarCloneDir(cloneDir, tarPath);
|
|
801
|
+
}
|
|
614
802
|
}
|
|
615
803
|
const remoteUrl = await readOriginUrl(args.hostRepo);
|
|
616
|
-
const
|
|
617
|
-
await args.backend.uploadFile(args.handle,
|
|
804
|
+
const remoteTar = "/tmp/agentbox-workspace.tar.gz";
|
|
805
|
+
await args.backend.uploadFile(args.handle, tarPath, remoteTar);
|
|
618
806
|
if (untrackedSize > 0) {
|
|
619
807
|
await args.backend.uploadFile(args.handle, untrackedTarPath, REMOTE_UNTRACKED_TAR);
|
|
620
808
|
}
|
|
@@ -633,50 +821,80 @@ async function seedFromGitBundle(args) {
|
|
|
633
821
|
// Move out of any cwd we might inherit from Daytona's executeCommand
|
|
634
822
|
// before we delete /workspace. The agentbox image bakes WORKDIR
|
|
635
823
|
// /workspace; if the shell's cwd is /workspace when we `rm -rf` it,
|
|
636
|
-
// the next process inherits a stale cwd FD and
|
|
637
|
-
//
|
|
824
|
+
// the next process inherits a stale cwd FD and tar's children fail
|
|
825
|
+
// with "Unable to read current working directory".
|
|
638
826
|
`cd /tmp`,
|
|
639
827
|
SUDO,
|
|
640
|
-
// rm -rf only the directory we're about to
|
|
828
|
+
// rm -rf only the directory we're about to extract into — for nested
|
|
641
829
|
// repos this is just `/workspace/<rel>`, so the root clone (already
|
|
642
830
|
// at `/workspace`) is preserved.
|
|
643
831
|
`$SUDO rm -rf ${quoteShellArgv([args.workspaceDir])}`,
|
|
644
832
|
`$SUDO mkdir -p ${quoteShellArgv([args.workspaceDir])}`,
|
|
645
833
|
`$SUDO chown "$(id -un):$(id -gn)" ${quoteShellArgv([args.workspaceDir])}`,
|
|
646
|
-
`
|
|
834
|
+
`tar -C ${quoteShellArgv([args.workspaceDir])} -xzf ${quoteShellArgv([remoteTar])}`,
|
|
647
835
|
setOrigin,
|
|
648
|
-
|
|
649
|
-
|
|
836
|
+
// reuse: the clone already landed on `<branch>` (pinned via `--branch`);
|
|
837
|
+
// a plain checkout materializes the working tree without resetting the
|
|
838
|
+
// ref. fork: `-B` (re)points `<branch>` at the clone HEAD.
|
|
839
|
+
args.useBranch ? `git -C ${quoteShellArgv([args.workspaceDir])} checkout ${quoteShellArgv([args.branch])}` : `git -C ${quoteShellArgv([args.workspaceDir])} checkout -B ${quoteShellArgv([args.branch])}`,
|
|
650
840
|
...carryOverSteps,
|
|
651
|
-
`rm -f ${quoteShellArgv([
|
|
841
|
+
`rm -f ${quoteShellArgv([remoteTar])}`
|
|
652
842
|
].join("\n");
|
|
653
843
|
const r = await args.backend.exec(args.handle, bashScript(script));
|
|
654
844
|
if (r.exitCode !== 0) {
|
|
655
|
-
throw new Error(`workspace seed (
|
|
845
|
+
throw new Error(`workspace seed (clone) failed: ${r.stderr || r.stdout}`);
|
|
656
846
|
}
|
|
657
847
|
} finally {
|
|
658
848
|
if (stashRefCreated) {
|
|
659
|
-
await
|
|
849
|
+
await execa4("git", ["-C", args.hostRepo, "update-ref", "-d", STASH_CARRYOVER_REF], {
|
|
660
850
|
reject: false
|
|
661
851
|
});
|
|
662
852
|
}
|
|
663
|
-
await
|
|
853
|
+
await rm5(stage, { recursive: true, force: true });
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async function runShallowClone(hostRepo, cloneDir, depth, includeStashRef, fromBranch) {
|
|
857
|
+
const cloneArgs = ["clone", "--no-checkout", "--quiet"];
|
|
858
|
+
if (depth !== null) cloneArgs.push(`--depth=${String(depth)}`);
|
|
859
|
+
if (fromBranch) cloneArgs.push("--branch", fromBranch);
|
|
860
|
+
cloneArgs.push(`file://${hostRepo}`, cloneDir);
|
|
861
|
+
await execa4("git", cloneArgs);
|
|
862
|
+
if (includeStashRef) {
|
|
863
|
+
const fetchArgs = ["-C", cloneDir, "fetch", "--quiet"];
|
|
864
|
+
if (depth !== null) fetchArgs.push(`--depth=${String(depth)}`);
|
|
865
|
+
fetchArgs.push(
|
|
866
|
+
`file://${hostRepo}`,
|
|
867
|
+
`+${STASH_CARRYOVER_REF}:refs/remotes/origin/agentbox-carryover/stash`
|
|
868
|
+
);
|
|
869
|
+
await execa4("git", fetchArgs, { reject: false });
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
async function tarCloneDir(cloneDir, outPath) {
|
|
873
|
+
await execa4("tar", ["-C", cloneDir, "-czf", outPath, "."], {
|
|
874
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
async function safeFileSize(path) {
|
|
878
|
+
try {
|
|
879
|
+
return (await stat(path)).size;
|
|
880
|
+
} catch {
|
|
881
|
+
return 0;
|
|
664
882
|
}
|
|
665
883
|
}
|
|
666
884
|
async function safeStashCreate(hostRepo) {
|
|
667
|
-
const r = await
|
|
885
|
+
const r = await execa4("git", ["-C", hostRepo, "stash", "create"], { reject: false });
|
|
668
886
|
if (r.exitCode !== 0) return null;
|
|
669
887
|
const sha = r.stdout.trim();
|
|
670
888
|
return sha.length > 0 ? sha : null;
|
|
671
889
|
}
|
|
672
890
|
async function maybeBuildUntrackedTar(hostRepo, outPath) {
|
|
673
|
-
const list = await
|
|
891
|
+
const list = await execa4(
|
|
674
892
|
"git",
|
|
675
893
|
["-C", hostRepo, "ls-files", "--others", "--exclude-standard", "-z"],
|
|
676
894
|
{ reject: false }
|
|
677
895
|
);
|
|
678
896
|
if (list.exitCode !== 0 || list.stdout.length === 0) return 0;
|
|
679
|
-
const tar = await
|
|
897
|
+
const tar = await execa4(
|
|
680
898
|
"tar",
|
|
681
899
|
["-C", hostRepo, "--null", "-T", "-", "-czf", outPath],
|
|
682
900
|
{
|
|
@@ -687,24 +905,24 @@ async function maybeBuildUntrackedTar(hostRepo, outPath) {
|
|
|
687
905
|
);
|
|
688
906
|
if (tar.exitCode !== 0) return 0;
|
|
689
907
|
try {
|
|
690
|
-
const { stat } = await import("fs/promises");
|
|
691
|
-
const s = await
|
|
908
|
+
const { stat: stat2 } = await import("fs/promises");
|
|
909
|
+
const s = await stat2(outPath);
|
|
692
910
|
return s.size;
|
|
693
911
|
} catch {
|
|
694
912
|
return 0;
|
|
695
913
|
}
|
|
696
914
|
}
|
|
697
915
|
async function readOriginUrl(hostRepo) {
|
|
698
|
-
const r = await
|
|
916
|
+
const r = await execa4("git", ["-C", hostRepo, "remote", "get-url", "origin"], { reject: false });
|
|
699
917
|
if (r.exitCode !== 0) return null;
|
|
700
918
|
const out = (r.stdout ?? "").trim();
|
|
701
919
|
return out.length > 0 ? out : null;
|
|
702
920
|
}
|
|
703
921
|
async function seedFromTar(args) {
|
|
704
|
-
const stage = await
|
|
705
|
-
const tarPath =
|
|
922
|
+
const stage = await mkdtemp4(join5(tmpdir4(), "agentbox-tar-"));
|
|
923
|
+
const tarPath = join5(stage, "workspace.tar.gz");
|
|
706
924
|
try {
|
|
707
|
-
await
|
|
925
|
+
await execa4("tar", ["-C", args.hostDir, "-czf", tarPath, "."]);
|
|
708
926
|
const remoteTar = "/tmp/agentbox-workspace.tar.gz";
|
|
709
927
|
await args.backend.uploadFile(args.handle, tarPath, remoteTar);
|
|
710
928
|
const SUDO = `if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi`;
|
|
@@ -728,7 +946,7 @@ async function seedFromTar(args) {
|
|
|
728
946
|
throw new Error(`workspace seed (tar) failed: ${r.stderr || r.stdout}`);
|
|
729
947
|
}
|
|
730
948
|
} finally {
|
|
731
|
-
await
|
|
949
|
+
await rm5(stage, { recursive: true, force: true });
|
|
732
950
|
}
|
|
733
951
|
}
|
|
734
952
|
var CLOUD_WORKSPACE_DIR = "/workspace";
|
|
@@ -759,18 +977,28 @@ function parseLoopbackPort(url) {
|
|
|
759
977
|
return void 0;
|
|
760
978
|
}
|
|
761
979
|
}
|
|
762
|
-
async function
|
|
763
|
-
const localPort = parseLoopbackPort(args.
|
|
980
|
+
async function registerHostPortlessAlias(args) {
|
|
981
|
+
const localPort = parseLoopbackPort(args.previewUrl);
|
|
764
982
|
if (localPort === void 0) return void 0;
|
|
765
|
-
const ok = await portlessAlias(args.
|
|
983
|
+
const ok = await portlessAlias(args.alias, localPort);
|
|
766
984
|
if (!ok) {
|
|
767
985
|
args.onLog(
|
|
768
|
-
`portless: alias not registered (portless CLI missing or not running) \u2014 host URL stays http://127.0.0.1:${String(localPort)}`
|
|
986
|
+
`portless: ${args.label} alias not registered (portless CLI missing or not running) \u2014 host URL stays http://127.0.0.1:${String(localPort)}`
|
|
769
987
|
);
|
|
770
988
|
return void 0;
|
|
771
989
|
}
|
|
772
|
-
const url = await portlessGetUrl(args.
|
|
990
|
+
const url = await portlessGetUrl(args.alias);
|
|
773
991
|
args.onLog(`portless alias ${url} -> 127.0.0.1:${String(localPort)}`);
|
|
992
|
+
return url;
|
|
993
|
+
}
|
|
994
|
+
async function bootstrapPortlessForCloudBox(backend, handle, args) {
|
|
995
|
+
const url = await registerHostPortlessAlias({
|
|
996
|
+
alias: args.boxName,
|
|
997
|
+
previewUrl: args.webPreviewUrl,
|
|
998
|
+
label: "web",
|
|
999
|
+
onLog: args.onLog
|
|
1000
|
+
});
|
|
1001
|
+
if (!url) return void 0;
|
|
774
1002
|
if (backend.startInBoxPortless) {
|
|
775
1003
|
const mode = parsePortlessUrl(url) ?? { proxyPort: DEFAULT_PORTLESS_PROXY_PORT, tls: false };
|
|
776
1004
|
try {
|
|
@@ -806,7 +1034,9 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
806
1034
|
return {
|
|
807
1035
|
id,
|
|
808
1036
|
name,
|
|
809
|
-
branch
|
|
1037
|
+
// --use-branch reuses the named branch directly; otherwise fork a fresh
|
|
1038
|
+
// per-box branch. The CLI validated `useBranch` exists host-side.
|
|
1039
|
+
branch: req.useBranch ?? `agentbox/${name}`
|
|
810
1040
|
};
|
|
811
1041
|
}
|
|
812
1042
|
async function probe(box) {
|
|
@@ -825,7 +1055,13 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
825
1055
|
});
|
|
826
1056
|
const { id, name, branch } = mintBox(req);
|
|
827
1057
|
const image = opts.provisionImage ? await opts.provisionImage(req) : req.image ?? FALLBACK_IMAGE;
|
|
828
|
-
const
|
|
1058
|
+
const baseResources = opts.defaultResources ?? { cpu: 2, memory: 4, disk: 8 };
|
|
1059
|
+
const vcpuOverride = req.providerOptions?.["vcpus"];
|
|
1060
|
+
const resources = typeof vcpuOverride === "number" && vcpuOverride > 0 ? { ...baseResources, cpu: vcpuOverride } : baseResources;
|
|
1061
|
+
const timeoutOverride = req.providerOptions?.["timeoutMs"];
|
|
1062
|
+
const timeoutMs = typeof timeoutOverride === "number" && timeoutOverride > 0 ? timeoutOverride : void 0;
|
|
1063
|
+
const networkPolicyOpt = req.providerOptions?.["networkPolicy"];
|
|
1064
|
+
const networkPolicy = typeof networkPolicyOpt === "string" && networkPolicyOpt.trim() !== "" ? networkPolicyOpt.trim() : void 0;
|
|
829
1065
|
const relayToken = generateRelayToken();
|
|
830
1066
|
const bridgeToken = generateRelayToken();
|
|
831
1067
|
try {
|
|
@@ -848,6 +1084,7 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
848
1084
|
}
|
|
849
1085
|
}
|
|
850
1086
|
const agentVolumes = await ensureAgentVolumesForCloud(backend, { onLog: log });
|
|
1087
|
+
const exposeServicePorts = await readExposedServicePorts(req.workspacePath);
|
|
851
1088
|
log(
|
|
852
1089
|
snapshotName ? `provisioning ${providerName} sandbox from snapshot` : `provisioning ${providerName} sandbox`
|
|
853
1090
|
);
|
|
@@ -856,12 +1093,17 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
856
1093
|
image,
|
|
857
1094
|
snapshot: snapshotName,
|
|
858
1095
|
resources,
|
|
1096
|
+
timeoutMs,
|
|
1097
|
+
exposePorts: exposeServicePorts,
|
|
1098
|
+
networkPolicy,
|
|
859
1099
|
env: {
|
|
860
1100
|
AGENTBOX_BOX_ID: id,
|
|
861
1101
|
AGENTBOX_BOX_NAME: name,
|
|
862
1102
|
AGENTBOX_BOX_KIND: "cloud",
|
|
863
|
-
// In-sandbox relay is on the box's loopback at the
|
|
864
|
-
|
|
1103
|
+
// In-sandbox relay is on the box's loopback at the in-box port.
|
|
1104
|
+
// 8788 is distinct from the host relay's 8787 so a nested agentbox
|
|
1105
|
+
// run inside the box can claim :8787 without colliding.
|
|
1106
|
+
AGENTBOX_RELAY_URL: `http://127.0.0.1:${String(8788)}`,
|
|
865
1107
|
AGENTBOX_RELAY_TOKEN: relayToken,
|
|
866
1108
|
AGENTBOX_BRIDGE_TOKEN: bridgeToken,
|
|
867
1109
|
...agentVolumes.env
|
|
@@ -879,6 +1121,9 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
879
1121
|
workspacePath: req.workspacePath,
|
|
880
1122
|
branch,
|
|
881
1123
|
workspaceDir: CLOUD_WORKSPACE_DIR,
|
|
1124
|
+
bundleDepth: req.bundleDepth,
|
|
1125
|
+
fromBranch: req.fromBranch,
|
|
1126
|
+
useBranch: req.useBranch,
|
|
882
1127
|
onLog: log
|
|
883
1128
|
});
|
|
884
1129
|
}
|
|
@@ -889,6 +1134,7 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
889
1134
|
onLog: log
|
|
890
1135
|
});
|
|
891
1136
|
}
|
|
1137
|
+
await seedOpencodeModelState(backend, handle, { onLog: log });
|
|
892
1138
|
if (req.envFilesToImport && req.envFilesToImport.length > 0) {
|
|
893
1139
|
const { copied } = await uploadEnvFiles({
|
|
894
1140
|
backend,
|
|
@@ -900,22 +1146,39 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
900
1146
|
});
|
|
901
1147
|
if (copied > 0) log(`copied ${String(copied)} env/config file(s) into /workspace`);
|
|
902
1148
|
}
|
|
1149
|
+
let carrySummary;
|
|
1150
|
+
if (req.carry && req.carry.length > 0) {
|
|
1151
|
+
log(`carry: copying ${String(req.carry.length)} host path(s) into the box`);
|
|
1152
|
+
const result = await uploadCarryPaths({
|
|
1153
|
+
backend,
|
|
1154
|
+
handle,
|
|
1155
|
+
entries: req.carry,
|
|
1156
|
+
onLog: log
|
|
1157
|
+
});
|
|
1158
|
+
log(`carry: copied ${String(result.copied)}/${String(req.carry.length)} entry/entries`);
|
|
1159
|
+
for (const err of result.errors) log(`carry: ${err}`);
|
|
1160
|
+
if (result.applied.length > 0) {
|
|
1161
|
+
carrySummary = { count: result.applied.length, entries: result.applied };
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
903
1164
|
log("launching agentbox-ctl daemon");
|
|
904
1165
|
await launchCloudCtlDaemon({
|
|
905
1166
|
backend,
|
|
906
1167
|
handle,
|
|
907
1168
|
boxId: id,
|
|
908
1169
|
boxName: name,
|
|
909
|
-
relayUrl: `http://127.0.0.1:${String(
|
|
1170
|
+
relayUrl: `http://127.0.0.1:${String(8788)}`,
|
|
910
1171
|
relayToken,
|
|
911
1172
|
bridgeToken
|
|
912
1173
|
});
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1174
|
+
if (opts.launchDockerd !== false) {
|
|
1175
|
+
log("launching in-box dockerd");
|
|
1176
|
+
try {
|
|
1177
|
+
const dockerd = await launchCloudDockerdDaemon({ backend, handle, timeoutMs: 6e4 });
|
|
1178
|
+
if (!dockerd.up) log(`dockerd did not become ready (continuing): ${dockerd.reason ?? "unknown"}`);
|
|
1179
|
+
} catch (err) {
|
|
1180
|
+
log(`dockerd daemon launch failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
1181
|
+
}
|
|
919
1182
|
}
|
|
920
1183
|
const vncEnabled = req.vnc?.enabled !== false;
|
|
921
1184
|
const vncPassword = vncEnabled ? generateVncPassword() : void 0;
|
|
@@ -950,7 +1213,30 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
950
1213
|
portlessUrlResolved = r.url;
|
|
951
1214
|
}
|
|
952
1215
|
}
|
|
953
|
-
|
|
1216
|
+
let vncPreview;
|
|
1217
|
+
if (portlessOpt && vncEnabled) {
|
|
1218
|
+
try {
|
|
1219
|
+
vncPreview = await backend.previewUrl(handle, CLOUD_VNC_PORT);
|
|
1220
|
+
} catch {
|
|
1221
|
+
vncPreview = void 0;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
let portlessVncAliasName;
|
|
1225
|
+
let portlessVncUrlResolved;
|
|
1226
|
+
if (portlessOpt && vncPreview) {
|
|
1227
|
+
const vncAlias = `vnc-${name}`;
|
|
1228
|
+
const url = await registerHostPortlessAlias({
|
|
1229
|
+
alias: vncAlias,
|
|
1230
|
+
previewUrl: vncPreview.url,
|
|
1231
|
+
label: "vnc",
|
|
1232
|
+
onLog: log
|
|
1233
|
+
});
|
|
1234
|
+
if (url) {
|
|
1235
|
+
portlessVncAliasName = vncAlias;
|
|
1236
|
+
portlessVncUrlResolved = url;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const servicePorts = exposeServicePorts;
|
|
954
1240
|
const servicePreviews = {};
|
|
955
1241
|
for (const port of servicePorts) {
|
|
956
1242
|
if (port === CLOUD_WEB_PROXY_PORT) continue;
|
|
@@ -962,16 +1248,19 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
962
1248
|
}
|
|
963
1249
|
let relayPreview;
|
|
964
1250
|
try {
|
|
965
|
-
relayPreview = await backend.previewUrl(handle,
|
|
1251
|
+
relayPreview = await backend.previewUrl(handle, 8788);
|
|
966
1252
|
} catch {
|
|
967
1253
|
relayPreview = void 0;
|
|
968
1254
|
}
|
|
1255
|
+
const state = await readState();
|
|
1256
|
+
const projectIndex = req.projectRoot ? allocateProjectIndex(state, req.projectRoot) : void 0;
|
|
969
1257
|
if (relayPreview) {
|
|
970
1258
|
try {
|
|
971
1259
|
await registerBoxWithRelay({
|
|
972
1260
|
boxId: id,
|
|
973
1261
|
token: relayToken,
|
|
974
1262
|
name,
|
|
1263
|
+
projectIndex,
|
|
975
1264
|
kind: "cloud",
|
|
976
1265
|
backend: backend.name,
|
|
977
1266
|
previewUrl: relayPreview.url,
|
|
@@ -985,8 +1274,6 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
985
1274
|
);
|
|
986
1275
|
}
|
|
987
1276
|
}
|
|
988
|
-
const state = await readState();
|
|
989
|
-
const projectIndex = req.projectRoot ? allocateProjectIndex(state, req.projectRoot) : void 0;
|
|
990
1277
|
const record = {
|
|
991
1278
|
id,
|
|
992
1279
|
name,
|
|
@@ -1005,8 +1292,11 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1005
1292
|
relayToken,
|
|
1006
1293
|
withPlaywright: req.withPlaywright,
|
|
1007
1294
|
withEnv: req.withEnv,
|
|
1295
|
+
carry: carrySummary,
|
|
1008
1296
|
portlessAlias: portlessAliasName,
|
|
1009
1297
|
portlessUrl: portlessUrlResolved,
|
|
1298
|
+
portlessVncAlias: portlessVncAliasName,
|
|
1299
|
+
portlessVncUrl: portlessVncUrlResolved,
|
|
1010
1300
|
vncEnabled,
|
|
1011
1301
|
vncPassword,
|
|
1012
1302
|
vncContainerPort: vncEnabled ? CLOUD_VNC_PORT : void 0,
|
|
@@ -1069,7 +1359,7 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1069
1359
|
}
|
|
1070
1360
|
let relayPreview;
|
|
1071
1361
|
try {
|
|
1072
|
-
relayPreview = await backend.previewUrl(h,
|
|
1362
|
+
relayPreview = await backend.previewUrl(h, 8788);
|
|
1073
1363
|
} catch {
|
|
1074
1364
|
relayPreview = box.cloud?.relayPreviewUrl ? { url: box.cloud.relayPreviewUrl, token: box.cloud.relayPreviewToken } : void 0;
|
|
1075
1365
|
}
|
|
@@ -1093,10 +1383,31 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1093
1383
|
portlessUrlResolved = r.url;
|
|
1094
1384
|
}
|
|
1095
1385
|
}
|
|
1386
|
+
let portlessVncAliasName = box.portlessVncAlias;
|
|
1387
|
+
let portlessVncUrlResolved = box.portlessVncUrl;
|
|
1388
|
+
if (box.portlessVncAlias && box.vncEnabled) {
|
|
1389
|
+
try {
|
|
1390
|
+
const vncPreview = await backend.previewUrl(h, CLOUD_VNC_PORT);
|
|
1391
|
+
const url = await registerHostPortlessAlias({
|
|
1392
|
+
alias: box.portlessVncAlias,
|
|
1393
|
+
previewUrl: vncPreview.url,
|
|
1394
|
+
label: "vnc",
|
|
1395
|
+
onLog: () => {
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
if (url) {
|
|
1399
|
+
portlessVncAliasName = box.portlessVncAlias;
|
|
1400
|
+
portlessVncUrlResolved = url;
|
|
1401
|
+
}
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1096
1405
|
const next = {
|
|
1097
1406
|
...box,
|
|
1098
1407
|
portlessAlias: portlessAliasName,
|
|
1099
1408
|
portlessUrl: portlessUrlResolved,
|
|
1409
|
+
portlessVncAlias: portlessVncAliasName,
|
|
1410
|
+
portlessVncUrl: portlessVncUrlResolved,
|
|
1100
1411
|
cloud: {
|
|
1101
1412
|
...box.cloud ?? { backend: providerName, sandboxId: h.sandboxId },
|
|
1102
1413
|
webPort,
|
|
@@ -1111,15 +1422,17 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1111
1422
|
handle: h,
|
|
1112
1423
|
boxId: box.id,
|
|
1113
1424
|
boxName: box.name,
|
|
1114
|
-
relayUrl: `http://127.0.0.1:${String(
|
|
1425
|
+
relayUrl: `http://127.0.0.1:${String(8788)}`,
|
|
1115
1426
|
relayToken: box.relayToken ?? "",
|
|
1116
1427
|
bridgeToken: box.cloud?.bridgeToken
|
|
1117
1428
|
});
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1429
|
+
if (opts.launchDockerd !== false) {
|
|
1430
|
+
try {
|
|
1431
|
+
const dockerd = await launchCloudDockerdDaemon({ backend, handle: h, timeoutMs: 6e4 });
|
|
1432
|
+
if (!dockerd.up) {
|
|
1433
|
+
}
|
|
1434
|
+
} catch {
|
|
1121
1435
|
}
|
|
1122
|
-
} catch {
|
|
1123
1436
|
}
|
|
1124
1437
|
if (box.vncEnabled && box.vncPassword) {
|
|
1125
1438
|
try {
|
|
@@ -1168,6 +1481,12 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1168
1481
|
} catch {
|
|
1169
1482
|
}
|
|
1170
1483
|
}
|
|
1484
|
+
if (box.portlessVncAlias) {
|
|
1485
|
+
try {
|
|
1486
|
+
await portlessUnalias(box.portlessVncAlias);
|
|
1487
|
+
} catch {
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1171
1490
|
try {
|
|
1172
1491
|
await forgetBoxFromRelay(box.id);
|
|
1173
1492
|
} catch {
|
|
@@ -1232,7 +1551,7 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1232
1551
|
const handle = handleFor(box);
|
|
1233
1552
|
const baseArgv = await backend.attachArgv(handle);
|
|
1234
1553
|
const inner = renderInnerCommand(kind, opts2);
|
|
1235
|
-
const argv = [...baseArgv.slice(1), "-t", inner];
|
|
1554
|
+
const argv = opts2?.detached ? [...baseArgv.slice(1), inner] : [...baseArgv.slice(1), "-t", inner];
|
|
1236
1555
|
const fullArgv = [baseArgv[0], ...argv];
|
|
1237
1556
|
const cleanup = backend.revokeAttachToken ? async () => {
|
|
1238
1557
|
await backend.revokeAttachToken(handle, baseArgv);
|
|
@@ -1251,6 +1570,14 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1251
1570
|
async resolveUrl(box, opts2) {
|
|
1252
1571
|
const h = handleFor(box);
|
|
1253
1572
|
const kind = opts2?.kind ?? "web";
|
|
1573
|
+
if (!opts2?.loopback) {
|
|
1574
|
+
if (kind === "web" && box.portlessAlias) {
|
|
1575
|
+
return box.portlessUrl ?? `https://${box.portlessAlias}.localhost`;
|
|
1576
|
+
}
|
|
1577
|
+
if (kind === "vnc" && box.portlessVncAlias) {
|
|
1578
|
+
return box.portlessVncUrl ?? `https://${box.portlessVncAlias}.localhost`;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1254
1581
|
const port = kind === "vnc" ? CLOUD_VNC_PORT : box.cloud?.webPort ?? CLOUD_WEB_PROXY_PORT;
|
|
1255
1582
|
if (backend.signedPreviewUrl) {
|
|
1256
1583
|
const ttl = opts2?.ttl ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
|
|
@@ -1266,7 +1593,16 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1266
1593
|
// capability stub whose methods throw — the CLI's `agentbox checkpoint
|
|
1267
1594
|
// create` then surfaces a clean "not supported" error rather than a
|
|
1268
1595
|
// silent no-op.
|
|
1269
|
-
checkpoint: makeCloudCheckpoint(backend)
|
|
1596
|
+
checkpoint: makeCloudCheckpoint(backend),
|
|
1597
|
+
// Extract the box's agent login(s) back to the host (~/.agentbox) so the
|
|
1598
|
+
// next box inherits the login. Lives on the base cloud provider (not inside
|
|
1599
|
+
// `checkpoint.create`) so it works even for providers that override the
|
|
1600
|
+
// whole `checkpoint` capability (vercel). The CLI calls this on
|
|
1601
|
+
// `checkpoint create --set-default`, while the box is guaranteed running.
|
|
1602
|
+
async extractAgentCredentials(box) {
|
|
1603
|
+
if (!box.cloud?.sandboxId) return [];
|
|
1604
|
+
return extractCloudAgentCredentials(backend, { sandboxId: box.cloud.sandboxId });
|
|
1605
|
+
}
|
|
1270
1606
|
// stats is provider-optional; cloud backends without a metrics API just
|
|
1271
1607
|
// omit it. Backends that have one can decorate the returned provider.
|
|
1272
1608
|
};
|
|
@@ -1322,7 +1658,17 @@ function renderInnerCommand(kind, opts) {
|
|
|
1322
1658
|
if (opts?.noTmux) {
|
|
1323
1659
|
return fallback;
|
|
1324
1660
|
}
|
|
1325
|
-
|
|
1661
|
+
const sessionQ = shellSingle(sessionName);
|
|
1662
|
+
const cwdQ = shellSingle(CLOUD_WORKSPACE_DIR);
|
|
1663
|
+
const fallbackQ = shellSingle(fallback);
|
|
1664
|
+
const configSnippet = buildTmuxConfigShellSnippet(sessionName);
|
|
1665
|
+
const lines = [
|
|
1666
|
+
`command -v tmux >/dev/null || { echo "tmux not installed in sandbox"; exit 127; }`,
|
|
1667
|
+
`tmux has-session -t ${sessionQ} 2>/dev/null || tmux new-session -d -c ${cwdQ} -s ${sessionQ} ${fallbackQ}`,
|
|
1668
|
+
configSnippet
|
|
1669
|
+
];
|
|
1670
|
+
if (opts?.detached) return lines.join("; ");
|
|
1671
|
+
return [...lines, `exec tmux attach -t ${sessionQ}`].join("; ");
|
|
1326
1672
|
}
|
|
1327
1673
|
function defaultSessionName(kind) {
|
|
1328
1674
|
switch (kind) {
|
|
@@ -1361,6 +1707,9 @@ export {
|
|
|
1361
1707
|
agentSpecsForCloud,
|
|
1362
1708
|
listCloudCheckpoints,
|
|
1363
1709
|
resolveCloudCheckpoint,
|
|
1364
|
-
|
|
1710
|
+
writeCloudCheckpointManifest,
|
|
1711
|
+
removeCloudCheckpointDir,
|
|
1712
|
+
createCloudProvider,
|
|
1713
|
+
renderInnerCommand
|
|
1365
1714
|
};
|
|
1366
|
-
//# sourceMappingURL=chunk-
|
|
1715
|
+
//# sourceMappingURL=chunk-BXQMIEHC.js.map
|