@madarco/agentbox 0.8.0 → 0.10.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/CHANGELOG.md +89 -0
- package/README.md +161 -0
- package/dist/{_cloud-attach-T727ZPRV.js → _cloud-attach-O6NYTLES.js} +4 -4
- package/dist/{chunk-67N47KUS.js → chunk-2GPORKYF.js} +349 -182
- package/dist/chunk-2GPORKYF.js.map +1 -0
- package/dist/{chunk-6OZDFNBF.js → chunk-7UIAO7PC.js} +401 -82
- package/dist/chunk-7UIAO7PC.js.map +1 -0
- package/dist/{chunk-BGK32PZE.js → chunk-KL36BRN4.js} +2 -2
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-FODMEHD3.js → chunk-R4O5WPHW.js} +705 -77
- package/dist/chunk-R4O5WPHW.js.map +1 -0
- package/dist/{dist-ZODPD2I6.js → dist-5FQGYRW5.js} +20 -10
- package/dist/dist-5FQGYRW5.js.map +1 -0
- package/dist/{dist-LOZBWMBF.js → dist-BQNX7RQE.js} +19 -3
- package/dist/dist-PZW3GWWU.js +874 -0
- package/dist/dist-PZW3GWWU.js.map +1 -0
- package/dist/{dist-L4LCG5SJ.js → dist-TMHSUVTP.js} +4 -4
- package/dist/index.js +2385 -842
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js → prepared-state-CL4CWXQA-H5THETIM.js} +2 -2
- package/package.json +11 -7
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +9 -8
- package/runtime/docker/packages/ctl/dist/bin.cjs +129 -31
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- package/runtime/hetzner/agentbox-setup-skill.md +9 -8
- package/runtime/hetzner/agentbox-vnc-start +15 -1
- package/runtime/hetzner/ctl.cjs +129 -31
- package/runtime/relay/bin.cjs +260 -39
- 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 +197 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23495 -0
- package/runtime/vercel/custom-system-CLAUDE.md +47 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +314 -0
- package/share/agentbox-setup/SKILL.md +9 -8
- package/dist/chunk-67N47KUS.js.map +0 -1
- package/dist/chunk-6OZDFNBF.js.map +0 -1
- package/dist/chunk-BGK32PZE.js.map +0 -1
- package/dist/chunk-FODMEHD3.js.map +0 -1
- package/dist/dist-ZODPD2I6.js.map +0 -1
- /package/dist/{_cloud-attach-T727ZPRV.js.map → _cloud-attach-O6NYTLES.js.map} +0 -0
- /package/dist/{dist-LOZBWMBF.js.map → dist-BQNX7RQE.js.map} +0 -0
- /package/dist/{dist-L4LCG5SJ.js.map → dist-TMHSUVTP.js.map} +0 -0
- /package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js.map → prepared-state-CL4CWXQA-H5THETIM.js.map} +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
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
9
|
buildHostEnvFindArgs,
|
|
7
10
|
buildTmuxConfigShellSnippet,
|
|
@@ -10,6 +13,7 @@ import {
|
|
|
10
13
|
generateRelayToken,
|
|
11
14
|
generateVncPassword,
|
|
12
15
|
hashProjectPath,
|
|
16
|
+
isRealAgentCredential,
|
|
13
17
|
portlessAlias,
|
|
14
18
|
portlessGetUrl,
|
|
15
19
|
portlessUnalias,
|
|
@@ -23,22 +27,24 @@ import {
|
|
|
23
27
|
stageOpencodeCredentialsForUpload,
|
|
24
28
|
stageOpencodeStateForUpload,
|
|
25
29
|
stageOpencodeStaticForUpload
|
|
26
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-7UIAO7PC.js";
|
|
27
31
|
import {
|
|
28
32
|
allocateProjectIndex,
|
|
29
33
|
detectGitRepos,
|
|
30
34
|
readState,
|
|
31
35
|
recordBox,
|
|
32
36
|
removeBoxRecord
|
|
33
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-KL36BRN4.js";
|
|
34
38
|
|
|
35
39
|
// ../../packages/sandbox-cloud/dist/index.js
|
|
36
40
|
import { randomBytes } from "crypto";
|
|
37
41
|
import { basename as basename2 } from "path";
|
|
38
|
-
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";
|
|
39
45
|
import { homedir } from "os";
|
|
40
46
|
import { basename, join } from "path";
|
|
41
|
-
import { mkdtemp, rm as rm2, writeFile as
|
|
47
|
+
import { mkdtemp, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
42
48
|
import { tmpdir } from "os";
|
|
43
49
|
import { join as join2 } from "path";
|
|
44
50
|
import { execa } from "execa";
|
|
@@ -228,6 +234,39 @@ function agentSpecsForCloud() {
|
|
|
228
234
|
credentialsSubpath: s.credentialsSubpath
|
|
229
235
|
}));
|
|
230
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
|
+
}
|
|
231
270
|
var CLOUD_CHECKPOINTS_ROOT = join(homedir(), ".agentbox", "cloud-checkpoints");
|
|
232
271
|
var CLOUD_SNAPSHOT_NAME_PREFIX = "agentbox-ckpt-";
|
|
233
272
|
function cloudSnapshotName(projectRoot, name) {
|
|
@@ -275,7 +314,7 @@ async function resolveCloudCheckpoint(projectRoot, backend, ref) {
|
|
|
275
314
|
}
|
|
276
315
|
async function writeCloudCheckpointManifest(projectRoot, backend, name, fields) {
|
|
277
316
|
const dir = checkpointDir(backend, projectRoot, name);
|
|
278
|
-
await
|
|
317
|
+
await mkdir2(dir, { recursive: true });
|
|
279
318
|
const manifest = {
|
|
280
319
|
schema: 1,
|
|
281
320
|
name,
|
|
@@ -285,7 +324,7 @@ async function writeCloudCheckpointManifest(projectRoot, backend, name, fields)
|
|
|
285
324
|
sourceBoxName: fields.sourceBoxName,
|
|
286
325
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
287
326
|
};
|
|
288
|
-
await
|
|
327
|
+
await writeFile2(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
289
328
|
return { name, dir, manifest };
|
|
290
329
|
}
|
|
291
330
|
async function removeCloudCheckpointDir(projectRoot, backend, name) {
|
|
@@ -295,6 +334,27 @@ async function removeCloudCheckpointDir(projectRoot, backend, name) {
|
|
|
295
334
|
await rm(dir, { recursive: true, force: true });
|
|
296
335
|
return true;
|
|
297
336
|
}
|
|
337
|
+
async function probeCloudCheckpoint(backend, projectRoot, ref) {
|
|
338
|
+
const found = await resolveCloudCheckpoint(projectRoot, backend.name, ref);
|
|
339
|
+
if (!found) return { live: false, pruned: false };
|
|
340
|
+
if (!backend.snapshotExists) return { live: true, pruned: false };
|
|
341
|
+
const live = await backend.snapshotExists(found.manifest.snapshotName);
|
|
342
|
+
if (live) return { live: true, pruned: false };
|
|
343
|
+
await removeCloudCheckpointDir(projectRoot, backend.name, ref);
|
|
344
|
+
return { live: false, pruned: true };
|
|
345
|
+
}
|
|
346
|
+
function isSnapshotGoneError(err) {
|
|
347
|
+
if (err === null || typeof err !== "object") return false;
|
|
348
|
+
const e = err;
|
|
349
|
+
const status = e.response?.status ?? e.status;
|
|
350
|
+
if (status === 410) return true;
|
|
351
|
+
const parts = [
|
|
352
|
+
typeof e.json?.error?.message === "string" ? e.json.error.message : "",
|
|
353
|
+
typeof e.message === "string" ? e.message : ""
|
|
354
|
+
];
|
|
355
|
+
const msg = parts.join(" ").toLowerCase();
|
|
356
|
+
return /snapshot[^.]*\b(expired|deleted|gone|not[ -]?found)\b/.test(msg) || msg.includes("expired or deleted");
|
|
357
|
+
}
|
|
298
358
|
var WORKSPACE_DIR_DEFAULT = "/workspace";
|
|
299
359
|
var REMOTE_TAR_PATH = "/tmp/agentbox-envfiles.tar";
|
|
300
360
|
async function uploadEnvFiles(args) {
|
|
@@ -324,7 +384,7 @@ async function uploadEnvFiles(args) {
|
|
|
324
384
|
log(`env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
|
|
325
385
|
return { copied: 0 };
|
|
326
386
|
}
|
|
327
|
-
await
|
|
387
|
+
await writeFile3(join2(stage, ".marker"), "").catch(() => {
|
|
328
388
|
});
|
|
329
389
|
await args.backend.uploadFile(args.handle, localTar, REMOTE_TAR_PATH);
|
|
330
390
|
const extract = await args.backend.exec(
|
|
@@ -418,7 +478,8 @@ async function uploadOneEntry(args) {
|
|
|
418
478
|
}
|
|
419
479
|
parts.push(`rm -f ${remoteTar}`);
|
|
420
480
|
const cmd = parts.join(" && ");
|
|
421
|
-
const
|
|
481
|
+
const execOpts = args.backend.name === "vercel" ? { user: "root" } : void 0;
|
|
482
|
+
const res = await args.backend.exec(args.handle, cmd, execOpts);
|
|
422
483
|
if (res.exitCode !== 0) {
|
|
423
484
|
throw new Error(
|
|
424
485
|
`in-box extract failed (exit ${String(res.exitCode)}): ${(res.stderr || res.stdout).slice(-300)}`
|
|
@@ -602,6 +663,8 @@ async function launchCloudCtlDaemon(args) {
|
|
|
602
663
|
if (args.relayUrl) env.push(`AGENTBOX_RELAY_URL=${quoteShellArgv([args.relayUrl])}`);
|
|
603
664
|
if (args.relayToken) env.push(`AGENTBOX_RELAY_TOKEN=${quoteShellArgv([args.relayToken])}`);
|
|
604
665
|
if (args.bridgeToken) env.push(`AGENTBOX_BRIDGE_TOKEN=${quoteShellArgv([args.bridgeToken])}`);
|
|
666
|
+
if (args.webProxyPort !== void 0)
|
|
667
|
+
env.push(`AGENTBOX_WEB_PROXY_PORT=${quoteShellArgv([String(args.webProxyPort)])}`);
|
|
605
668
|
const script = [
|
|
606
669
|
`set -e`,
|
|
607
670
|
`if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi`,
|
|
@@ -687,6 +750,7 @@ async function seedCloudWorkspace(args) {
|
|
|
687
750
|
workspaceDir,
|
|
688
751
|
bundleDepth: args.bundleDepth,
|
|
689
752
|
fromBranch: args.fromBranch,
|
|
753
|
+
useBranch: args.useBranch,
|
|
690
754
|
onLog: log
|
|
691
755
|
});
|
|
692
756
|
for (const r of nested) {
|
|
@@ -725,8 +789,8 @@ async function seedFromGitClone(args) {
|
|
|
725
789
|
const cloneDir = join5(stage, "clone");
|
|
726
790
|
const tarPath = join5(stage, "workspace.tar.gz");
|
|
727
791
|
const untrackedTarPath = join5(stage, "untracked.tar.gz");
|
|
728
|
-
const stashSha = await safeStashCreate(args.hostRepo);
|
|
729
|
-
const untrackedSize = await maybeBuildUntrackedTar(args.hostRepo, untrackedTarPath);
|
|
792
|
+
const stashSha = args.useBranch ? null : await safeStashCreate(args.hostRepo);
|
|
793
|
+
const untrackedSize = args.useBranch ? 0 : await maybeBuildUntrackedTar(args.hostRepo, untrackedTarPath);
|
|
730
794
|
let stashRefCreated = false;
|
|
731
795
|
try {
|
|
732
796
|
if (stashSha) {
|
|
@@ -743,7 +807,8 @@ async function seedFromGitClone(args) {
|
|
|
743
807
|
log(
|
|
744
808
|
adaptive ? `clone: depth=${String(DEFAULT_BUNDLE_DEPTH)} (default, adaptive)` : initialDepth === null ? "clone: depth=full (configured)" : `clone: depth=${String(initialDepth)} (configured)`
|
|
745
809
|
);
|
|
746
|
-
|
|
810
|
+
const cloneBranch = args.useBranch ?? args.fromBranch;
|
|
811
|
+
await runShallowClone(args.hostRepo, cloneDir, initialDepth, stashRefCreated, cloneBranch);
|
|
747
812
|
await tarCloneDir(cloneDir, tarPath);
|
|
748
813
|
if (adaptive && initialDepth !== null) {
|
|
749
814
|
const size = await safeFileSize(tarPath);
|
|
@@ -754,7 +819,7 @@ async function seedFromGitClone(args) {
|
|
|
754
819
|
);
|
|
755
820
|
await rm5(cloneDir, { recursive: true, force: true });
|
|
756
821
|
await rm5(tarPath, { force: true });
|
|
757
|
-
await runShallowClone(args.hostRepo, cloneDir, LARGE_BUNDLE_DEPTH, stashRefCreated,
|
|
822
|
+
await runShallowClone(args.hostRepo, cloneDir, LARGE_BUNDLE_DEPTH, stashRefCreated, cloneBranch);
|
|
758
823
|
await tarCloneDir(cloneDir, tarPath);
|
|
759
824
|
}
|
|
760
825
|
}
|
|
@@ -791,7 +856,10 @@ async function seedFromGitClone(args) {
|
|
|
791
856
|
`$SUDO chown "$(id -un):$(id -gn)" ${quoteShellArgv([args.workspaceDir])}`,
|
|
792
857
|
`tar -C ${quoteShellArgv([args.workspaceDir])} -xzf ${quoteShellArgv([remoteTar])}`,
|
|
793
858
|
setOrigin,
|
|
794
|
-
|
|
859
|
+
// reuse: the clone already landed on `<branch>` (pinned via `--branch`);
|
|
860
|
+
// a plain checkout materializes the working tree without resetting the
|
|
861
|
+
// ref. fork: `-B` (re)points `<branch>` at the clone HEAD.
|
|
862
|
+
args.useBranch ? `git -C ${quoteShellArgv([args.workspaceDir])} checkout ${quoteShellArgv([args.branch])}` : `git -C ${quoteShellArgv([args.workspaceDir])} checkout -B ${quoteShellArgv([args.branch])}`,
|
|
795
863
|
...carryOverSteps,
|
|
796
864
|
`rm -f ${quoteShellArgv([remoteTar])}`
|
|
797
865
|
].join("\n");
|
|
@@ -989,7 +1057,9 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
989
1057
|
return {
|
|
990
1058
|
id,
|
|
991
1059
|
name,
|
|
992
|
-
branch
|
|
1060
|
+
// --use-branch reuses the named branch directly; otherwise fork a fresh
|
|
1061
|
+
// per-box branch. The CLI validated `useBranch` exists host-side.
|
|
1062
|
+
branch: req.useBranch ?? `agentbox/${name}`
|
|
993
1063
|
};
|
|
994
1064
|
}
|
|
995
1065
|
async function probe(box) {
|
|
@@ -1001,6 +1071,141 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1001
1071
|
return "missing";
|
|
1002
1072
|
}
|
|
1003
1073
|
}
|
|
1074
|
+
async function persistLastState(box, lastState) {
|
|
1075
|
+
if (!box.cloud) return;
|
|
1076
|
+
try {
|
|
1077
|
+
await recordBox({ ...box, cloud: { ...box.cloud, lastState } });
|
|
1078
|
+
} catch {
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async function reEnsureCloudBox(box, h) {
|
|
1082
|
+
const webPort = box.cloud?.webPort ?? backend.webProxyPort ?? CLOUD_WEB_PROXY_PORT;
|
|
1083
|
+
let webPreview;
|
|
1084
|
+
try {
|
|
1085
|
+
webPreview = await backend.previewUrl(h, webPort);
|
|
1086
|
+
} catch {
|
|
1087
|
+
const cached = box.cloud?.previewUrls?.[webPort];
|
|
1088
|
+
webPreview = cached ? { url: cached } : void 0;
|
|
1089
|
+
}
|
|
1090
|
+
const servicePreviews = {};
|
|
1091
|
+
try {
|
|
1092
|
+
const ports = await readExposedServicePorts(box.workspacePath);
|
|
1093
|
+
for (const port of ports) {
|
|
1094
|
+
if (port === webPort) continue;
|
|
1095
|
+
try {
|
|
1096
|
+
const p = await backend.previewUrl(h, port);
|
|
1097
|
+
servicePreviews[port] = p.url;
|
|
1098
|
+
} catch {
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
} catch {
|
|
1102
|
+
}
|
|
1103
|
+
let relayPreview;
|
|
1104
|
+
try {
|
|
1105
|
+
relayPreview = await backend.previewUrl(h, 8788);
|
|
1106
|
+
} catch {
|
|
1107
|
+
relayPreview = box.cloud?.relayPreviewUrl ? { url: box.cloud.relayPreviewUrl, token: box.cloud.relayPreviewToken } : void 0;
|
|
1108
|
+
}
|
|
1109
|
+
const mergedPreviews = {
|
|
1110
|
+
...box.cloud?.previewUrls ?? {},
|
|
1111
|
+
...servicePreviews
|
|
1112
|
+
};
|
|
1113
|
+
if (webPreview !== void 0) mergedPreviews[webPort] = webPreview.url;
|
|
1114
|
+
let portlessAliasName = box.portlessAlias;
|
|
1115
|
+
let portlessUrlResolved = box.portlessUrl;
|
|
1116
|
+
if (box.portlessAlias && webPreview) {
|
|
1117
|
+
const r = await bootstrapPortlessForCloudBox(backend, h, {
|
|
1118
|
+
boxName: box.name,
|
|
1119
|
+
webPreviewUrl: webPreview.url,
|
|
1120
|
+
webPort,
|
|
1121
|
+
onLog: () => {
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
if (r) {
|
|
1125
|
+
portlessAliasName = r.alias;
|
|
1126
|
+
portlessUrlResolved = r.url;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
let portlessVncAliasName = box.portlessVncAlias;
|
|
1130
|
+
let portlessVncUrlResolved = box.portlessVncUrl;
|
|
1131
|
+
if (box.portlessVncAlias && box.vncEnabled) {
|
|
1132
|
+
try {
|
|
1133
|
+
const vncPreview = await backend.previewUrl(h, CLOUD_VNC_PORT);
|
|
1134
|
+
const url = await registerHostPortlessAlias({
|
|
1135
|
+
alias: box.portlessVncAlias,
|
|
1136
|
+
previewUrl: vncPreview.url,
|
|
1137
|
+
label: "vnc",
|
|
1138
|
+
onLog: () => {
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
if (url) {
|
|
1142
|
+
portlessVncAliasName = box.portlessVncAlias;
|
|
1143
|
+
portlessVncUrlResolved = url;
|
|
1144
|
+
}
|
|
1145
|
+
} catch {
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const next = {
|
|
1149
|
+
...box,
|
|
1150
|
+
portlessAlias: portlessAliasName,
|
|
1151
|
+
portlessUrl: portlessUrlResolved,
|
|
1152
|
+
portlessVncAlias: portlessVncAliasName,
|
|
1153
|
+
portlessVncUrl: portlessVncUrlResolved,
|
|
1154
|
+
cloud: {
|
|
1155
|
+
...box.cloud ?? { backend: providerName, sandboxId: h.sandboxId },
|
|
1156
|
+
webPort,
|
|
1157
|
+
previewUrls: Object.keys(mergedPreviews).length > 0 ? mergedPreviews : void 0,
|
|
1158
|
+
relayPreviewUrl: relayPreview?.url ?? box.cloud?.relayPreviewUrl,
|
|
1159
|
+
relayPreviewToken: relayPreview?.token ?? box.cloud?.relayPreviewToken,
|
|
1160
|
+
// reEnsureCloudBox only runs on a freshly-woken box (start/resume), so
|
|
1161
|
+
// the box is now running — persist it for the fast `agentbox list` path.
|
|
1162
|
+
lastState: "running"
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
await recordBox(next);
|
|
1166
|
+
await launchCloudCtlDaemon({
|
|
1167
|
+
backend,
|
|
1168
|
+
handle: h,
|
|
1169
|
+
boxId: box.id,
|
|
1170
|
+
boxName: box.name,
|
|
1171
|
+
relayUrl: `http://127.0.0.1:${String(8788)}`,
|
|
1172
|
+
relayToken: box.relayToken ?? "",
|
|
1173
|
+
bridgeToken: box.cloud?.bridgeToken,
|
|
1174
|
+
webProxyPort: backend.webProxyPort
|
|
1175
|
+
});
|
|
1176
|
+
if (opts.launchDockerd !== false) {
|
|
1177
|
+
try {
|
|
1178
|
+
const dockerd = await launchCloudDockerdDaemon({ backend, handle: h, timeoutMs: 6e4 });
|
|
1179
|
+
if (!dockerd.up) {
|
|
1180
|
+
}
|
|
1181
|
+
} catch {
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (box.vncEnabled && box.vncPassword) {
|
|
1185
|
+
try {
|
|
1186
|
+
await launchCloudVncDaemon({ backend, handle: h, vncPassword: box.vncPassword });
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (relayPreview && box.relayToken && box.cloud?.bridgeToken) {
|
|
1191
|
+
try {
|
|
1192
|
+
await registerBoxWithRelay({
|
|
1193
|
+
boxId: box.id,
|
|
1194
|
+
token: box.relayToken,
|
|
1195
|
+
name: box.name,
|
|
1196
|
+
kind: "cloud",
|
|
1197
|
+
backend: backend.name,
|
|
1198
|
+
previewUrl: relayPreview.url,
|
|
1199
|
+
previewToken: relayPreview.token,
|
|
1200
|
+
bridgeToken: box.cloud.bridgeToken,
|
|
1201
|
+
createdAt: box.createdAt,
|
|
1202
|
+
projectIndex: box.projectIndex
|
|
1203
|
+
});
|
|
1204
|
+
} catch {
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return next;
|
|
1208
|
+
}
|
|
1004
1209
|
return {
|
|
1005
1210
|
name: providerName,
|
|
1006
1211
|
async create(req) {
|
|
@@ -1008,7 +1213,13 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1008
1213
|
});
|
|
1009
1214
|
const { id, name, branch } = mintBox(req);
|
|
1010
1215
|
const image = opts.provisionImage ? await opts.provisionImage(req) : req.image ?? FALLBACK_IMAGE;
|
|
1011
|
-
const
|
|
1216
|
+
const baseResources = opts.defaultResources ?? { cpu: 2, memory: 4, disk: 8 };
|
|
1217
|
+
const vcpuOverride = req.providerOptions?.["vcpus"];
|
|
1218
|
+
const resources = typeof vcpuOverride === "number" && vcpuOverride > 0 ? { ...baseResources, cpu: vcpuOverride } : baseResources;
|
|
1219
|
+
const timeoutOverride = req.providerOptions?.["timeoutMs"];
|
|
1220
|
+
const timeoutMs = typeof timeoutOverride === "number" && timeoutOverride > 0 ? timeoutOverride : void 0;
|
|
1221
|
+
const networkPolicyOpt = req.providerOptions?.["networkPolicy"];
|
|
1222
|
+
const networkPolicy = typeof networkPolicyOpt === "string" && networkPolicyOpt.trim() !== "" ? networkPolicyOpt.trim() : void 0;
|
|
1012
1223
|
const relayToken = generateRelayToken();
|
|
1013
1224
|
const bridgeToken = generateRelayToken();
|
|
1014
1225
|
try {
|
|
@@ -1031,29 +1242,57 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1031
1242
|
}
|
|
1032
1243
|
}
|
|
1033
1244
|
const agentVolumes = await ensureAgentVolumesForCloud(backend, { onLog: log });
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1245
|
+
const exposeServicePorts = await readExposedServicePorts(req.workspacePath);
|
|
1246
|
+
const provisionEnv = {
|
|
1247
|
+
AGENTBOX_BOX_ID: id,
|
|
1248
|
+
AGENTBOX_BOX_NAME: name,
|
|
1249
|
+
AGENTBOX_BOX_KIND: "cloud",
|
|
1250
|
+
// In-sandbox relay is on the box's loopback at the in-box port.
|
|
1251
|
+
// 8788 is distinct from the host relay's 8787 so a nested agentbox
|
|
1252
|
+
// run inside the box can claim :8787 without colliding.
|
|
1253
|
+
AGENTBOX_RELAY_URL: `http://127.0.0.1:${String(8788)}`,
|
|
1254
|
+
AGENTBOX_RELAY_TOKEN: relayToken,
|
|
1255
|
+
AGENTBOX_BRIDGE_TOKEN: bridgeToken,
|
|
1256
|
+
...agentVolumes.env
|
|
1257
|
+
};
|
|
1258
|
+
const provisionFrom = async (snapshot) => {
|
|
1259
|
+
log(
|
|
1260
|
+
snapshot ? `provisioning ${providerName} sandbox from snapshot` : `provisioning ${providerName} sandbox`
|
|
1261
|
+
);
|
|
1262
|
+
return backend.provision({
|
|
1263
|
+
name,
|
|
1264
|
+
image,
|
|
1265
|
+
snapshot,
|
|
1266
|
+
resources,
|
|
1267
|
+
timeoutMs,
|
|
1268
|
+
exposePorts: exposeServicePorts,
|
|
1269
|
+
networkPolicy,
|
|
1270
|
+
env: provisionEnv,
|
|
1271
|
+
volumes: agentVolumes.mounts,
|
|
1272
|
+
onLog: log
|
|
1273
|
+
});
|
|
1274
|
+
};
|
|
1275
|
+
let handle;
|
|
1276
|
+
try {
|
|
1277
|
+
handle = await provisionFrom(snapshotName);
|
|
1278
|
+
} catch (err) {
|
|
1279
|
+
if (snapshotName && isSnapshotGoneError(err)) {
|
|
1280
|
+
log(
|
|
1281
|
+
`checkpoint snapshot '${resolvedCheckpointRef ?? snapshotName}' has expired or been deleted; starting a fresh box from the base image instead`
|
|
1282
|
+
);
|
|
1283
|
+
if (req.projectRoot && resolvedCheckpointRef) {
|
|
1284
|
+
try {
|
|
1285
|
+
await removeCloudCheckpointDir(req.projectRoot, backend.name, resolvedCheckpointRef);
|
|
1286
|
+
} catch {
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
snapshotName = void 0;
|
|
1290
|
+
resolvedCheckpointRef = void 0;
|
|
1291
|
+
handle = await provisionFrom(void 0);
|
|
1292
|
+
} else {
|
|
1293
|
+
throw err;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1057
1296
|
try {
|
|
1058
1297
|
if (snapshotName) {
|
|
1059
1298
|
log("skipping workspace seed \u2014 snapshot already contains /workspace");
|
|
@@ -1066,6 +1305,7 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1066
1305
|
workspaceDir: CLOUD_WORKSPACE_DIR,
|
|
1067
1306
|
bundleDepth: req.bundleDepth,
|
|
1068
1307
|
fromBranch: req.fromBranch,
|
|
1308
|
+
useBranch: req.useBranch,
|
|
1069
1309
|
onLog: log
|
|
1070
1310
|
});
|
|
1071
1311
|
}
|
|
@@ -1111,14 +1351,17 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1111
1351
|
boxName: name,
|
|
1112
1352
|
relayUrl: `http://127.0.0.1:${String(8788)}`,
|
|
1113
1353
|
relayToken,
|
|
1114
|
-
bridgeToken
|
|
1354
|
+
bridgeToken,
|
|
1355
|
+
webProxyPort: backend.webProxyPort
|
|
1115
1356
|
});
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1357
|
+
if (opts.launchDockerd !== false) {
|
|
1358
|
+
log("launching in-box dockerd");
|
|
1359
|
+
try {
|
|
1360
|
+
const dockerd = await launchCloudDockerdDaemon({ backend, handle, timeoutMs: 6e4 });
|
|
1361
|
+
if (!dockerd.up) log(`dockerd did not become ready (continuing): ${dockerd.reason ?? "unknown"}`);
|
|
1362
|
+
} catch (err) {
|
|
1363
|
+
log(`dockerd daemon launch failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
1364
|
+
}
|
|
1122
1365
|
}
|
|
1123
1366
|
const vncEnabled = req.vnc?.enabled !== false;
|
|
1124
1367
|
const vncPassword = vncEnabled ? generateVncPassword() : void 0;
|
|
@@ -1132,9 +1375,10 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1132
1375
|
);
|
|
1133
1376
|
}
|
|
1134
1377
|
}
|
|
1378
|
+
const wp = backend.webProxyPort ?? CLOUD_WEB_PROXY_PORT;
|
|
1135
1379
|
let webPreview;
|
|
1136
1380
|
try {
|
|
1137
|
-
webPreview = await backend.previewUrl(handle,
|
|
1381
|
+
webPreview = await backend.previewUrl(handle, wp);
|
|
1138
1382
|
} catch {
|
|
1139
1383
|
webPreview = void 0;
|
|
1140
1384
|
}
|
|
@@ -1145,7 +1389,7 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1145
1389
|
const r = await bootstrapPortlessForCloudBox(backend, handle, {
|
|
1146
1390
|
boxName: name,
|
|
1147
1391
|
webPreviewUrl: webPreview.url,
|
|
1148
|
-
webPort:
|
|
1392
|
+
webPort: wp,
|
|
1149
1393
|
onLog: log
|
|
1150
1394
|
});
|
|
1151
1395
|
if (r) {
|
|
@@ -1176,10 +1420,10 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1176
1420
|
portlessVncUrlResolved = url;
|
|
1177
1421
|
}
|
|
1178
1422
|
}
|
|
1179
|
-
const servicePorts =
|
|
1423
|
+
const servicePorts = exposeServicePorts;
|
|
1180
1424
|
const servicePreviews = {};
|
|
1181
1425
|
for (const port of servicePorts) {
|
|
1182
|
-
if (port ===
|
|
1426
|
+
if (port === wp) continue;
|
|
1183
1427
|
try {
|
|
1184
1428
|
const p = await backend.previewUrl(handle, port);
|
|
1185
1429
|
servicePreviews[port] = p.url;
|
|
@@ -1192,12 +1436,15 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1192
1436
|
} catch {
|
|
1193
1437
|
relayPreview = void 0;
|
|
1194
1438
|
}
|
|
1439
|
+
const state = await readState();
|
|
1440
|
+
const projectIndex = req.projectRoot ? allocateProjectIndex(state, req.projectRoot) : void 0;
|
|
1195
1441
|
if (relayPreview) {
|
|
1196
1442
|
try {
|
|
1197
1443
|
await registerBoxWithRelay({
|
|
1198
1444
|
boxId: id,
|
|
1199
1445
|
token: relayToken,
|
|
1200
1446
|
name,
|
|
1447
|
+
projectIndex,
|
|
1201
1448
|
kind: "cloud",
|
|
1202
1449
|
backend: backend.name,
|
|
1203
1450
|
previewUrl: relayPreview.url,
|
|
@@ -1211,8 +1458,6 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1211
1458
|
);
|
|
1212
1459
|
}
|
|
1213
1460
|
}
|
|
1214
|
-
const state = await readState();
|
|
1215
|
-
const projectIndex = req.projectRoot ? allocateProjectIndex(state, req.projectRoot) : void 0;
|
|
1216
1461
|
const record = {
|
|
1217
1462
|
id,
|
|
1218
1463
|
name,
|
|
@@ -1249,16 +1494,17 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1249
1494
|
backend: backend.name,
|
|
1250
1495
|
sandboxId: handle.sandboxId,
|
|
1251
1496
|
image,
|
|
1252
|
-
webPort:
|
|
1497
|
+
webPort: wp,
|
|
1253
1498
|
previewUrls: (() => {
|
|
1254
1499
|
const m = { ...servicePreviews };
|
|
1255
|
-
if (webPreview) m[
|
|
1500
|
+
if (webPreview) m[wp] = webPreview.url;
|
|
1256
1501
|
return Object.keys(m).length > 0 ? m : void 0;
|
|
1257
1502
|
})(),
|
|
1258
1503
|
relayPreviewUrl: relayPreview?.url,
|
|
1259
1504
|
relayPreviewToken: relayPreview?.token,
|
|
1260
1505
|
bridgeToken,
|
|
1261
|
-
snapshotRef: resolvedCheckpointRef
|
|
1506
|
+
snapshotRef: resolvedCheckpointRef,
|
|
1507
|
+
lastState: "running"
|
|
1262
1508
|
},
|
|
1263
1509
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1264
1510
|
};
|
|
@@ -1275,135 +1521,20 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1275
1521
|
async start(box) {
|
|
1276
1522
|
const h = handleFor(box);
|
|
1277
1523
|
await backend.start(h);
|
|
1278
|
-
|
|
1279
|
-
let webPreview;
|
|
1280
|
-
try {
|
|
1281
|
-
webPreview = await backend.previewUrl(h, webPort);
|
|
1282
|
-
} catch {
|
|
1283
|
-
const cached = box.cloud?.previewUrls?.[webPort];
|
|
1284
|
-
webPreview = cached ? { url: cached } : void 0;
|
|
1285
|
-
}
|
|
1286
|
-
const servicePreviews = {};
|
|
1287
|
-
try {
|
|
1288
|
-
const ports = await readExposedServicePorts(box.workspacePath);
|
|
1289
|
-
for (const port of ports) {
|
|
1290
|
-
if (port === webPort) continue;
|
|
1291
|
-
try {
|
|
1292
|
-
const p = await backend.previewUrl(h, port);
|
|
1293
|
-
servicePreviews[port] = p.url;
|
|
1294
|
-
} catch {
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
} catch {
|
|
1298
|
-
}
|
|
1299
|
-
let relayPreview;
|
|
1300
|
-
try {
|
|
1301
|
-
relayPreview = await backend.previewUrl(h, 8788);
|
|
1302
|
-
} catch {
|
|
1303
|
-
relayPreview = box.cloud?.relayPreviewUrl ? { url: box.cloud.relayPreviewUrl, token: box.cloud.relayPreviewToken } : void 0;
|
|
1304
|
-
}
|
|
1305
|
-
const mergedPreviews = {
|
|
1306
|
-
...box.cloud?.previewUrls ?? {},
|
|
1307
|
-
...servicePreviews
|
|
1308
|
-
};
|
|
1309
|
-
if (webPreview !== void 0) mergedPreviews[webPort] = webPreview.url;
|
|
1310
|
-
let portlessAliasName = box.portlessAlias;
|
|
1311
|
-
let portlessUrlResolved = box.portlessUrl;
|
|
1312
|
-
if (box.portlessAlias && webPreview) {
|
|
1313
|
-
const r = await bootstrapPortlessForCloudBox(backend, h, {
|
|
1314
|
-
boxName: box.name,
|
|
1315
|
-
webPreviewUrl: webPreview.url,
|
|
1316
|
-
webPort,
|
|
1317
|
-
onLog: () => {
|
|
1318
|
-
}
|
|
1319
|
-
});
|
|
1320
|
-
if (r) {
|
|
1321
|
-
portlessAliasName = r.alias;
|
|
1322
|
-
portlessUrlResolved = r.url;
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
let portlessVncAliasName = box.portlessVncAlias;
|
|
1326
|
-
let portlessVncUrlResolved = box.portlessVncUrl;
|
|
1327
|
-
if (box.portlessVncAlias && box.vncEnabled) {
|
|
1328
|
-
try {
|
|
1329
|
-
const vncPreview = await backend.previewUrl(h, CLOUD_VNC_PORT);
|
|
1330
|
-
const url = await registerHostPortlessAlias({
|
|
1331
|
-
alias: box.portlessVncAlias,
|
|
1332
|
-
previewUrl: vncPreview.url,
|
|
1333
|
-
label: "vnc",
|
|
1334
|
-
onLog: () => {
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1337
|
-
if (url) {
|
|
1338
|
-
portlessVncAliasName = box.portlessVncAlias;
|
|
1339
|
-
portlessVncUrlResolved = url;
|
|
1340
|
-
}
|
|
1341
|
-
} catch {
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
const next = {
|
|
1345
|
-
...box,
|
|
1346
|
-
portlessAlias: portlessAliasName,
|
|
1347
|
-
portlessUrl: portlessUrlResolved,
|
|
1348
|
-
portlessVncAlias: portlessVncAliasName,
|
|
1349
|
-
portlessVncUrl: portlessVncUrlResolved,
|
|
1350
|
-
cloud: {
|
|
1351
|
-
...box.cloud ?? { backend: providerName, sandboxId: h.sandboxId },
|
|
1352
|
-
webPort,
|
|
1353
|
-
previewUrls: Object.keys(mergedPreviews).length > 0 ? mergedPreviews : void 0,
|
|
1354
|
-
relayPreviewUrl: relayPreview?.url ?? box.cloud?.relayPreviewUrl,
|
|
1355
|
-
relayPreviewToken: relayPreview?.token ?? box.cloud?.relayPreviewToken
|
|
1356
|
-
}
|
|
1357
|
-
};
|
|
1358
|
-
await recordBox(next);
|
|
1359
|
-
await launchCloudCtlDaemon({
|
|
1360
|
-
backend,
|
|
1361
|
-
handle: h,
|
|
1362
|
-
boxId: box.id,
|
|
1363
|
-
boxName: box.name,
|
|
1364
|
-
relayUrl: `http://127.0.0.1:${String(8788)}`,
|
|
1365
|
-
relayToken: box.relayToken ?? "",
|
|
1366
|
-
bridgeToken: box.cloud?.bridgeToken
|
|
1367
|
-
});
|
|
1368
|
-
try {
|
|
1369
|
-
const dockerd = await launchCloudDockerdDaemon({ backend, handle: h, timeoutMs: 6e4 });
|
|
1370
|
-
if (!dockerd.up) {
|
|
1371
|
-
}
|
|
1372
|
-
} catch {
|
|
1373
|
-
}
|
|
1374
|
-
if (box.vncEnabled && box.vncPassword) {
|
|
1375
|
-
try {
|
|
1376
|
-
await launchCloudVncDaemon({ backend, handle: h, vncPassword: box.vncPassword });
|
|
1377
|
-
} catch {
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
if (relayPreview && box.relayToken && box.cloud?.bridgeToken) {
|
|
1381
|
-
try {
|
|
1382
|
-
await registerBoxWithRelay({
|
|
1383
|
-
boxId: box.id,
|
|
1384
|
-
token: box.relayToken,
|
|
1385
|
-
name: box.name,
|
|
1386
|
-
kind: "cloud",
|
|
1387
|
-
backend: backend.name,
|
|
1388
|
-
previewUrl: relayPreview.url,
|
|
1389
|
-
previewToken: relayPreview.token,
|
|
1390
|
-
bridgeToken: box.cloud.bridgeToken,
|
|
1391
|
-
createdAt: box.createdAt,
|
|
1392
|
-
projectIndex: box.projectIndex
|
|
1393
|
-
});
|
|
1394
|
-
} catch {
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
return next;
|
|
1524
|
+
return reEnsureCloudBox(box, h);
|
|
1398
1525
|
},
|
|
1399
1526
|
async pause(box) {
|
|
1400
1527
|
await backend.pause(handleFor(box));
|
|
1528
|
+
await persistLastState(box, "paused");
|
|
1401
1529
|
},
|
|
1402
1530
|
async resume(box) {
|
|
1403
|
-
|
|
1531
|
+
const h = handleFor(box);
|
|
1532
|
+
await backend.resume(h);
|
|
1533
|
+
await reEnsureCloudBox(box, h);
|
|
1404
1534
|
},
|
|
1405
1535
|
async stop(box) {
|
|
1406
1536
|
await backend.stop(handleFor(box));
|
|
1537
|
+
await persistLastState(box, "paused");
|
|
1407
1538
|
},
|
|
1408
1539
|
async destroy(box) {
|
|
1409
1540
|
try {
|
|
@@ -1435,7 +1566,7 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1435
1566
|
},
|
|
1436
1567
|
async inspect(box) {
|
|
1437
1568
|
const state = await probe(box);
|
|
1438
|
-
const webPort = box.cloud?.webPort ?? CLOUD_WEB_PROXY_PORT;
|
|
1569
|
+
const webPort = box.cloud?.webPort ?? backend.webProxyPort ?? CLOUD_WEB_PROXY_PORT;
|
|
1439
1570
|
const portlessWebUrl = box.portlessAlias !== void 0 ? box.portlessUrl ?? `https://${box.portlessAlias}.localhost` : void 0;
|
|
1440
1571
|
const cachedWebUrl = box.cloud?.previewUrls?.[webPort];
|
|
1441
1572
|
const webUrl = portlessWebUrl ?? cachedWebUrl;
|
|
@@ -1515,11 +1646,22 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1515
1646
|
return box.portlessVncUrl ?? `https://${box.portlessVncAlias}.localhost`;
|
|
1516
1647
|
}
|
|
1517
1648
|
}
|
|
1518
|
-
const port = kind === "vnc" ? CLOUD_VNC_PORT : box.cloud?.webPort ?? CLOUD_WEB_PROXY_PORT;
|
|
1649
|
+
const port = kind === "vnc" ? CLOUD_VNC_PORT : box.cloud?.webPort ?? backend.webProxyPort ?? CLOUD_WEB_PROXY_PORT;
|
|
1519
1650
|
if (backend.signedPreviewUrl) {
|
|
1520
1651
|
const ttl = opts2?.ttl ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
|
|
1521
|
-
|
|
1522
|
-
|
|
1652
|
+
try {
|
|
1653
|
+
const signed = await backend.signedPreviewUrl(h, port, ttl);
|
|
1654
|
+
return signed.url;
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
if (kind === "web") {
|
|
1657
|
+
const fallbackPort = await firstExposedServicePort(box);
|
|
1658
|
+
if (fallbackPort !== void 0 && fallbackPort !== port) {
|
|
1659
|
+
const signed = await backend.signedPreviewUrl(h, fallbackPort, ttl);
|
|
1660
|
+
return signed.url;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
throw err;
|
|
1664
|
+
}
|
|
1523
1665
|
}
|
|
1524
1666
|
const p = await backend.previewUrl(h, port);
|
|
1525
1667
|
throw new Error(
|
|
@@ -1530,11 +1672,32 @@ function createCloudProvider(backend, opts = {}) {
|
|
|
1530
1672
|
// capability stub whose methods throw — the CLI's `agentbox checkpoint
|
|
1531
1673
|
// create` then surfaces a clean "not supported" error rather than a
|
|
1532
1674
|
// silent no-op.
|
|
1533
|
-
checkpoint: makeCloudCheckpoint(backend)
|
|
1675
|
+
checkpoint: makeCloudCheckpoint(backend),
|
|
1676
|
+
// Extract the box's agent login(s) back to the host (~/.agentbox) so the
|
|
1677
|
+
// next box inherits the login. Lives on the base cloud provider (not inside
|
|
1678
|
+
// `checkpoint.create`) so it works even for providers that override the
|
|
1679
|
+
// whole `checkpoint` capability (vercel). The CLI calls this on
|
|
1680
|
+
// `checkpoint create --set-default`, while the box is guaranteed running.
|
|
1681
|
+
async extractAgentCredentials(box) {
|
|
1682
|
+
if (!box.cloud?.sandboxId) return [];
|
|
1683
|
+
return extractCloudAgentCredentials(backend, { sandboxId: box.cloud.sandboxId });
|
|
1684
|
+
}
|
|
1534
1685
|
// stats is provider-optional; cloud backends without a metrics API just
|
|
1535
1686
|
// omit it. Backends that have one can decorate the returned provider.
|
|
1536
1687
|
};
|
|
1537
1688
|
}
|
|
1689
|
+
var RESERVED_CLOUD_PORTS = /* @__PURE__ */ new Set([CLOUD_WEB_PROXY_PORT, CLOUD_VNC_PORT, 8788]);
|
|
1690
|
+
async function firstExposedServicePort(box) {
|
|
1691
|
+
const reserved = (p) => RESERVED_CLOUD_PORTS.has(p) || p === box.cloud?.webPort;
|
|
1692
|
+
const fromRecord = Object.keys(box.cloud?.previewUrls ?? {}).map(Number).filter((p) => Number.isInteger(p) && !reserved(p));
|
|
1693
|
+
if (fromRecord.length > 0) return Math.min(...fromRecord);
|
|
1694
|
+
try {
|
|
1695
|
+
const fromYaml = (await readExposedServicePorts(box.workspacePath)).filter((p) => !reserved(p));
|
|
1696
|
+
if (fromYaml.length > 0) return Math.min(...fromYaml);
|
|
1697
|
+
} catch {
|
|
1698
|
+
}
|
|
1699
|
+
return void 0;
|
|
1700
|
+
}
|
|
1538
1701
|
function makeCloudCheckpoint(backend) {
|
|
1539
1702
|
return {
|
|
1540
1703
|
async create(box, name) {
|
|
@@ -1635,6 +1798,10 @@ export {
|
|
|
1635
1798
|
agentSpecsForCloud,
|
|
1636
1799
|
listCloudCheckpoints,
|
|
1637
1800
|
resolveCloudCheckpoint,
|
|
1638
|
-
|
|
1801
|
+
writeCloudCheckpointManifest,
|
|
1802
|
+
removeCloudCheckpointDir,
|
|
1803
|
+
probeCloudCheckpoint,
|
|
1804
|
+
createCloudProvider,
|
|
1805
|
+
renderInnerCommand
|
|
1639
1806
|
};
|
|
1640
|
-
//# sourceMappingURL=chunk-
|
|
1807
|
+
//# sourceMappingURL=chunk-2GPORKYF.js.map
|