@madarco/agentbox 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/_cloud-attach-T727ZPRV.js +13 -0
  2. package/dist/chunk-67N47KUS.js +1640 -0
  3. package/dist/chunk-67N47KUS.js.map +1 -0
  4. package/dist/chunk-6OZDFNBF.js +8114 -0
  5. package/dist/chunk-6OZDFNBF.js.map +1 -0
  6. package/dist/chunk-BGK32PZE.js +455 -0
  7. package/dist/chunk-BGK32PZE.js.map +1 -0
  8. package/dist/chunk-FODMEHD3.js +1200 -0
  9. package/dist/chunk-FODMEHD3.js.map +1 -0
  10. package/dist/chunk-G3H2L3O2.js +288 -0
  11. package/dist/chunk-G3H2L3O2.js.map +1 -0
  12. package/dist/chunk-I24B6AXR.js +600 -0
  13. package/dist/chunk-I24B6AXR.js.map +1 -0
  14. package/dist/chunk-LEV3KICD.js +738 -0
  15. package/dist/chunk-LEV3KICD.js.map +1 -0
  16. package/dist/cloud-poller-SUNA6ZQC-2RG5WPRN.js +10 -0
  17. package/dist/dist-L4LCG5SJ.js +293 -0
  18. package/dist/dist-L4LCG5SJ.js.map +1 -0
  19. package/dist/dist-LOZBWMBF.js +447 -0
  20. package/dist/dist-ZODPD2I6.js +1407 -0
  21. package/dist/dist-ZODPD2I6.js.map +1 -0
  22. package/dist/index.js +7281 -2134
  23. package/dist/index.js.map +1 -1
  24. package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js +18 -0
  25. package/package.json +8 -3
  26. package/runtime/daytona/custom-system-CLAUDE.md +39 -0
  27. package/runtime/docker/Dockerfile.box +120 -14
  28. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +15 -8
  29. package/runtime/docker/packages/ctl/dist/bin.cjs +11310 -816
  30. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +68 -0
  31. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +9 -9
  32. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
  33. package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
  34. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
  35. package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
  36. package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
  37. package/runtime/hetzner/agentbox-checkpoint-cleanup +52 -0
  38. package/runtime/hetzner/agentbox-codex-hooks.json +68 -0
  39. package/runtime/hetzner/agentbox-dockerd-start +132 -0
  40. package/runtime/hetzner/agentbox-open +28 -0
  41. package/runtime/hetzner/agentbox-setup-skill.md +196 -0
  42. package/runtime/hetzner/agentbox-vnc-start +77 -0
  43. package/runtime/hetzner/claude-managed-settings.json +115 -0
  44. package/runtime/hetzner/ctl.cjs +23397 -0
  45. package/runtime/hetzner/custom-system-CLAUDE.md +39 -0
  46. package/runtime/hetzner/gh-shim +263 -0
  47. package/runtime/hetzner/git-shim +131 -0
  48. package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
  49. package/runtime/hetzner/scripts/install-box.sh +374 -0
  50. package/runtime/relay/bin.cjs +10017 -817
  51. package/share/agentbox-setup/SKILL.md +15 -8
  52. package/share/host-skills/agentbox/SKILL.md +29 -0
  53. package/share/host-skills/agentbox-info/SKILL.md +211 -0
  54. package/share/host-skills/codex/agentbox.md +35 -0
  55. package/share/host-skills/opencode/agentbox.md +26 -0
  56. package/dist/chunk-BBZMA2K6.js +0 -238
  57. package/dist/chunk-BBZMA2K6.js.map +0 -1
  58. package/dist/chunk-HHMWQNLF.js +0 -1709
  59. package/dist/chunk-HHMWQNLF.js.map +0 -1
  60. package/dist/chunk-HPZMD5DE.js +0 -106
  61. package/dist/chunk-HPZMD5DE.js.map +0 -1
  62. package/dist/chunk-HTTKML3C.js +0 -2655
  63. package/dist/chunk-HTTKML3C.js.map +0 -1
  64. package/dist/chunk-KJNZP6I3.js +0 -586
  65. package/dist/chunk-KJNZP6I3.js.map +0 -1
  66. package/dist/chunk-M7I247BK.js +0 -525
  67. package/dist/chunk-M7I247BK.js.map +0 -1
  68. package/dist/create-6PWXI6HO-OWAMHBAK.js +0 -15
  69. package/dist/lifecycle-EMXR46DI-DUVBXNTV.js +0 -38
  70. package/dist/state-KD7M46ZP-KHFTHFUS.js +0 -26
  71. package/dist/stats-SZXOJE3D-N7OODCHW.js +0 -19
  72. /package/dist/{create-6PWXI6HO-OWAMHBAK.js.map → _cloud-attach-T727ZPRV.js.map} +0 -0
  73. /package/dist/{lifecycle-EMXR46DI-DUVBXNTV.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
  74. /package/dist/{state-KD7M46ZP-KHFTHFUS.js.map → dist-LOZBWMBF.js.map} +0 -0
  75. /package/dist/{stats-SZXOJE3D-N7OODCHW.js.map → prepared-state-CL4CWXQA-ME4HSKDE.js.map} +0 -0
@@ -0,0 +1,1640 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CLAUDE_FORWARDED_ENV_KEYS,
4
+ CODEX_FORWARDED_ENV_KEYS,
5
+ OPENCODE_FORWARDED_ENV_KEYS,
6
+ buildHostEnvFindArgs,
7
+ buildTmuxConfigShellSnippet,
8
+ ensureRelay,
9
+ forgetBoxFromRelay,
10
+ generateRelayToken,
11
+ generateVncPassword,
12
+ hashProjectPath,
13
+ portlessAlias,
14
+ portlessGetUrl,
15
+ portlessUnalias,
16
+ projectDirSegment,
17
+ registerBoxWithRelay,
18
+ sanitizeMnemonic,
19
+ stageClaudeCredentialsForUpload,
20
+ stageClaudeStaticForUpload,
21
+ stageCodexCredentialsForUpload,
22
+ stageCodexStaticForUpload,
23
+ stageOpencodeCredentialsForUpload,
24
+ stageOpencodeStateForUpload,
25
+ stageOpencodeStaticForUpload
26
+ } from "./chunk-6OZDFNBF.js";
27
+ import {
28
+ allocateProjectIndex,
29
+ detectGitRepos,
30
+ readState,
31
+ recordBox,
32
+ removeBoxRecord
33
+ } from "./chunk-BGK32PZE.js";
34
+
35
+ // ../../packages/sandbox-cloud/dist/index.js
36
+ import { randomBytes } from "crypto";
37
+ import { basename as basename2 } from "path";
38
+ import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
39
+ import { homedir } from "os";
40
+ import { basename, join } from "path";
41
+ import { mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
42
+ import { tmpdir } from "os";
43
+ import { join as join2 } from "path";
44
+ import { execa } from "execa";
45
+ import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
46
+ import { tmpdir as tmpdir2 } from "os";
47
+ import { join as join3 } from "path";
48
+ import { execa as execa2 } from "execa";
49
+ import { readFile as readFile2 } from "fs/promises";
50
+ import { join as join4 } from "path";
51
+ import { parse as parseYaml } from "yaml";
52
+ import { execa as execa3 } from "execa";
53
+ import { existsSync, mkdirSync, renameSync, statSync } from "fs";
54
+ import { mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
55
+ import { tmpdir as tmpdir3 } from "os";
56
+ import {
57
+ basename as hostBasename,
58
+ dirname as hostDirname,
59
+ join as hostJoin,
60
+ resolve as hostResolve
61
+ } from "path";
62
+ import { posix } from "path";
63
+ import { execa as execa4 } from "execa";
64
+ import { mkdtemp as mkdtemp4, rm as rm5, stat } from "fs/promises";
65
+ import { tmpdir as tmpdir4 } from "os";
66
+ import { join as join5 } from "path";
67
+ var CREDENTIALS_VOLUME = "agentbox-credentials";
68
+ var AGENT_SPECS = [
69
+ {
70
+ kind: "claude",
71
+ staticMountPath: "/home/vscode/.claude",
72
+ credentialsMountPath: "/home/vscode/.agentbox-creds/claude",
73
+ credentialsSubpath: "claude/",
74
+ stageStatic: (opts) => stageClaudeStaticForUpload({ hostWorkspace: opts.hostWorkspace }),
75
+ stageCredentials: () => stageClaudeCredentialsForUpload()
76
+ },
77
+ {
78
+ kind: "codex",
79
+ staticMountPath: "/home/vscode/.codex",
80
+ credentialsMountPath: "/home/vscode/.agentbox-creds/codex",
81
+ credentialsSubpath: "codex/",
82
+ stageStatic: () => stageCodexStaticForUpload(),
83
+ stageCredentials: () => stageCodexCredentialsForUpload()
84
+ },
85
+ {
86
+ kind: "opencode",
87
+ staticMountPath: "/home/vscode/.local/share/opencode",
88
+ credentialsMountPath: "/home/vscode/.agentbox-creds/opencode",
89
+ credentialsSubpath: "opencode/",
90
+ stageStatic: () => stageOpencodeStaticForUpload(),
91
+ stageCredentials: () => stageOpencodeCredentialsForUpload()
92
+ }
93
+ ];
94
+ var SEED_MARKER = ".agentbox-seeded-at";
95
+ async function ensureAgentVolumesForCloud(backend, opts = {}) {
96
+ const log = opts.onLog ?? (() => {
97
+ });
98
+ if (typeof backend.ensureVolume !== "function") {
99
+ log(
100
+ `cloud backend '${backend.name}' has no volume primitive \u2014 agent credentials will not persist across boxes`
101
+ );
102
+ return { mounts: [], env: buildForwardedEnv([]), agents: [] };
103
+ }
104
+ let volumeId;
105
+ try {
106
+ const ensured = await backend.ensureVolume(CREDENTIALS_VOLUME);
107
+ volumeId = ensured.volumeId;
108
+ } catch (err) {
109
+ const msg = err instanceof Error ? err.message : String(err);
110
+ log(`ensureVolume(${CREDENTIALS_VOLUME}) failed (skipping credentials seed): ${msg}`);
111
+ return { mounts: [], env: buildForwardedEnv([]), agents: [] };
112
+ }
113
+ const mounts = AGENT_SPECS.map((spec) => ({
114
+ volumeId,
115
+ mountPath: spec.credentialsMountPath,
116
+ subpath: spec.credentialsSubpath
117
+ }));
118
+ const agents = AGENT_SPECS.map((s) => s.kind);
119
+ return { mounts, env: buildForwardedEnv(agents), agents };
120
+ }
121
+ function buildForwardedEnv(agents) {
122
+ const env = {};
123
+ if (agents.includes("opencode")) {
124
+ env["OPENCODE_CONFIG_DIR"] = "/home/vscode/.local/share/opencode/config";
125
+ }
126
+ const forwardedKeys = /* @__PURE__ */ new Set([
127
+ ...CLAUDE_FORWARDED_ENV_KEYS,
128
+ ...CODEX_FORWARDED_ENV_KEYS,
129
+ ...OPENCODE_FORWARDED_ENV_KEYS
130
+ ]);
131
+ for (const k of forwardedKeys) {
132
+ const v = process.env[k];
133
+ if (typeof v === "string" && v.length > 0) env[k] = v;
134
+ }
135
+ return env;
136
+ }
137
+ async function seedAgentVolumesIfFresh(backend, handle, opts = {}) {
138
+ const wanted = new Set(opts.agents ?? AGENT_SPECS.map((s) => s.kind));
139
+ const specs = AGENT_SPECS.filter((s) => wanted.has(s.kind));
140
+ await Promise.all(specs.map((spec) => seedCredentialsOne(backend, handle, spec, opts)));
141
+ }
142
+ async function seedCredentialsOne(backend, handle, spec, opts) {
143
+ const log = opts.onLog ?? (() => {
144
+ });
145
+ if (!opts.force) {
146
+ const probe = await backend.exec(
147
+ handle,
148
+ `test -f ${spec.credentialsMountPath}/${SEED_MARKER}`
149
+ );
150
+ if (probe.exitCode === 0) {
151
+ log(`${spec.kind}: credentials already seeded \u2014 mounting only`);
152
+ return;
153
+ }
154
+ }
155
+ log(`${spec.kind}: staging host credentials`);
156
+ const staged = await spec.stageCredentials();
157
+ for (const w of staged.warnings) log(w);
158
+ try {
159
+ if (staged.tarballPath === null) {
160
+ log(`${spec.kind}: no credentials to seed`);
161
+ return;
162
+ }
163
+ let tarSize = 0;
164
+ try {
165
+ const { statSync: statSync2 } = await import("fs");
166
+ tarSize = statSync2(staged.tarballPath).size;
167
+ } catch {
168
+ }
169
+ const sizeKB = (tarSize / 1024).toFixed(1);
170
+ log(`${spec.kind}: uploading ${sizeKB} KB credentials tarball`);
171
+ process.stderr.write(`[agent-creds] ${spec.kind}: uploading ${sizeKB} KB...
172
+ `);
173
+ const t0 = Date.now();
174
+ const remoteTar = `/tmp/agentbox-${spec.kind}-creds.tar.gz`;
175
+ await backend.uploadFile(handle, staged.tarballPath, remoteTar);
176
+ const upDt = ((Date.now() - t0) / 1e3).toFixed(1);
177
+ process.stderr.write(`[agent-creds] ${spec.kind}: upload done in ${upDt}s
178
+ `);
179
+ const stageDir = `/tmp/agentbox-creds-stage-${spec.kind}`;
180
+ const extractCmd = `set -e; rm -rf ${stageDir}; mkdir -p ${stageDir}; tar -xzf ${remoteTar} -C ${stageDir}; cp -r ${stageDir}/. ${spec.credentialsMountPath}/; rm -rf ${stageDir}; date -u +%FT%TZ > ${spec.credentialsMountPath}/${SEED_MARKER}; rm -f ${remoteTar}`;
181
+ const extract = await backend.exec(handle, extractCmd);
182
+ if (extract.exitCode !== 0) {
183
+ const msg = `${spec.kind}: credentials extract failed (exit ${String(extract.exitCode)}); agent falls back to interactive login. stdout: ${extract.stdout.slice(-200)} stderr: ${extract.stderr.slice(-200)}`;
184
+ log(msg);
185
+ process.stderr.write(`[agent-creds] ${msg}
186
+ `);
187
+ return;
188
+ }
189
+ log(`${spec.kind}: credentials seeded \u2713`);
190
+ process.stderr.write(`[agent-creds] ${spec.kind}: credentials seeded
191
+ `);
192
+ } finally {
193
+ await staged.cleanup();
194
+ }
195
+ }
196
+ var OPENCODE_STATE_DIR = "/home/vscode/.local/state/opencode";
197
+ async function seedOpencodeModelState(backend, handle, opts = {}) {
198
+ const log = opts.onLog ?? (() => {
199
+ });
200
+ const staged = await stageOpencodeStateForUpload();
201
+ if (staged.tarballPath === null) {
202
+ log("opencode: no host model selection to seed");
203
+ return;
204
+ }
205
+ try {
206
+ const remoteTar = "/tmp/agentbox-opencode-state.tar.gz";
207
+ await backend.uploadFile(handle, staged.tarballPath, remoteTar);
208
+ const res = await backend.exec(
209
+ handle,
210
+ `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}`
211
+ );
212
+ if (res.exitCode !== 0) {
213
+ log(
214
+ `opencode: model-state seed failed (exit ${String(res.exitCode)}); box falls back to OpenCode's default model. stderr: ${res.stderr.slice(-200)}`
215
+ );
216
+ return;
217
+ }
218
+ log("opencode: model selection seeded \u2713");
219
+ } finally {
220
+ await staged.cleanup();
221
+ }
222
+ }
223
+ function agentSpecsForCloud() {
224
+ return AGENT_SPECS.map((s) => ({
225
+ kind: s.kind,
226
+ staticMountPath: s.staticMountPath,
227
+ credentialsMountPath: s.credentialsMountPath,
228
+ credentialsSubpath: s.credentialsSubpath
229
+ }));
230
+ }
231
+ var CLOUD_CHECKPOINTS_ROOT = join(homedir(), ".agentbox", "cloud-checkpoints");
232
+ var CLOUD_SNAPSHOT_NAME_PREFIX = "agentbox-ckpt-";
233
+ function cloudSnapshotName(projectRoot, name) {
234
+ const mnemonic = sanitizeMnemonic(basename(projectRoot));
235
+ return `${CLOUD_SNAPSHOT_NAME_PREFIX}${hashProjectPath(projectRoot)}_${mnemonic}-${name}`;
236
+ }
237
+ function backendDir(backend, projectRoot) {
238
+ return join(CLOUD_CHECKPOINTS_ROOT, backend, projectDirSegment(projectRoot));
239
+ }
240
+ function checkpointDir(backend, projectRoot, name) {
241
+ return join(backendDir(backend, projectRoot), name);
242
+ }
243
+ async function readManifest(dir) {
244
+ try {
245
+ const raw = await readFile(join(dir, "manifest.json"), "utf8");
246
+ const m = JSON.parse(raw);
247
+ if (m.schema !== 1) return null;
248
+ return m;
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+ async function listCloudCheckpoints(projectRoot, backend) {
254
+ const root = backendDir(backend, projectRoot);
255
+ let entries;
256
+ try {
257
+ entries = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
258
+ } catch {
259
+ return [];
260
+ }
261
+ const out = [];
262
+ for (const name of entries) {
263
+ const dir = join(root, name);
264
+ const manifest = await readManifest(dir);
265
+ if (manifest) out.push({ name, dir, manifest });
266
+ }
267
+ out.sort((a, b) => a.manifest.createdAt.localeCompare(b.manifest.createdAt));
268
+ return out;
269
+ }
270
+ async function resolveCloudCheckpoint(projectRoot, backend, ref) {
271
+ const dir = checkpointDir(backend, projectRoot, ref);
272
+ const manifest = await readManifest(dir);
273
+ if (!manifest) return null;
274
+ return { name: ref, dir, manifest };
275
+ }
276
+ async function writeCloudCheckpointManifest(projectRoot, backend, name, fields) {
277
+ const dir = checkpointDir(backend, projectRoot, name);
278
+ await mkdir(dir, { recursive: true });
279
+ const manifest = {
280
+ schema: 1,
281
+ name,
282
+ backend,
283
+ snapshotName: fields.snapshotName,
284
+ sourceBoxId: fields.sourceBoxId,
285
+ sourceBoxName: fields.sourceBoxName,
286
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
287
+ };
288
+ await writeFile(join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
289
+ return { name, dir, manifest };
290
+ }
291
+ async function removeCloudCheckpointDir(projectRoot, backend, name) {
292
+ const dir = checkpointDir(backend, projectRoot, name);
293
+ const existed = await readManifest(dir) !== null;
294
+ if (!existed) return false;
295
+ await rm(dir, { recursive: true, force: true });
296
+ return true;
297
+ }
298
+ var WORKSPACE_DIR_DEFAULT = "/workspace";
299
+ var REMOTE_TAR_PATH = "/tmp/agentbox-envfiles.tar";
300
+ async function uploadEnvFiles(args) {
301
+ const log = args.onLog ?? (() => {
302
+ });
303
+ if (args.files.length === 0) return { copied: 0 };
304
+ const workspaceDir = args.workspaceDir ?? WORKSPACE_DIR_DEFAULT;
305
+ const found = await execa("find", buildHostEnvFindArgs(args.files).slice(1), {
306
+ cwd: args.workspacePath,
307
+ reject: false
308
+ });
309
+ if (found.exitCode !== 0) {
310
+ log(`env-file scan failed: ${String(found.stderr).slice(0, 300)}`);
311
+ return { copied: 0 };
312
+ }
313
+ const list = String(found.stdout).split("\0").filter((p) => p.length > 0);
314
+ if (list.length === 0) return { copied: 0 };
315
+ const stage = await mkdtemp(join2(tmpdir(), "agentbox-envfiles-"));
316
+ const localTar = join2(stage, "envfiles.tar");
317
+ try {
318
+ const packed = await execa(
319
+ "tar",
320
+ ["-C", args.workspacePath, "--null", "-T", "-", "-cf", localTar],
321
+ { input: list.join("\0"), reject: false }
322
+ );
323
+ if (packed.exitCode !== 0) {
324
+ log(`env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
325
+ return { copied: 0 };
326
+ }
327
+ await writeFile2(join2(stage, ".marker"), "").catch(() => {
328
+ });
329
+ await args.backend.uploadFile(args.handle, localTar, REMOTE_TAR_PATH);
330
+ const extract = await args.backend.exec(
331
+ args.handle,
332
+ `tar -xf ${REMOTE_TAR_PATH} -C ${workspaceDir} --no-same-permissions --no-same-owner -m && rm -f ${REMOTE_TAR_PATH}`
333
+ );
334
+ if (extract.exitCode !== 0) {
335
+ log(
336
+ `env-file extract failed (exit ${String(extract.exitCode)}); stdout: ${extract.stdout.slice(-200)} stderr: ${extract.stderr.slice(-200)}`
337
+ );
338
+ return { copied: 0 };
339
+ }
340
+ } finally {
341
+ await rm2(stage, { recursive: true, force: true });
342
+ }
343
+ return { copied: list.length };
344
+ }
345
+ var BOX_HOME = "/home/vscode";
346
+ async function uploadCarryPaths(args) {
347
+ const log = args.onLog ?? (() => {
348
+ });
349
+ if (args.entries.length === 0) {
350
+ return { copied: 0, errors: [], applied: [] };
351
+ }
352
+ const stage = await mkdtemp2(join3(tmpdir2(), "agentbox-carry-"));
353
+ const errors = [];
354
+ const applied = [];
355
+ let copied = 0;
356
+ try {
357
+ for (const [i, entry] of args.entries.entries()) {
358
+ const where = `carry[${String(i)}] "${entry.rawSrc}"`;
359
+ if (entry.kind === "missing") {
360
+ log(`${where}: skipped (missing on host, optional)`);
361
+ continue;
362
+ }
363
+ try {
364
+ await uploadOneEntry({
365
+ backend: args.backend,
366
+ handle: args.handle,
367
+ entry,
368
+ stageDir: stage,
369
+ index: i
370
+ });
371
+ copied += 1;
372
+ applied.push({ src: entry.absSrc, dest: entry.absDest, bytes: entry.bytes ?? 0 });
373
+ } catch (err) {
374
+ const msg = err instanceof Error ? err.message : String(err);
375
+ errors.push(`${where}: ${msg}`);
376
+ log(`${where}: failed: ${msg}`);
377
+ }
378
+ }
379
+ } finally {
380
+ await rm3(stage, { recursive: true, force: true });
381
+ }
382
+ return { copied, errors, applied };
383
+ }
384
+ async function uploadOneEntry(args) {
385
+ const { entry } = args;
386
+ if (entry.kind === "missing") return;
387
+ const boxDest = entry.absDest.startsWith("~/") ? `${BOX_HOME}/${entry.absDest.slice(2)}` : entry.absDest;
388
+ const isDir = entry.kind === "dir";
389
+ const parentDir = isDir ? boxDest : dirnameUnix(boxDest);
390
+ const localTar = join3(args.stageDir, `carry-${String(args.index)}.tar`);
391
+ const tarArgs = isDir ? ["-C", entry.absSrc, "-cf", localTar, "."] : ["-C", dirnameUnix(entry.absSrc), "-cf", localTar, basenameUnix(entry.absSrc)];
392
+ const packed = await execa2("tar", tarArgs, { reject: false });
393
+ if (packed.exitCode !== 0) {
394
+ throw new Error(`tar pack failed: ${String(packed.stderr).slice(0, 300)}`);
395
+ }
396
+ const remoteTar = `/tmp/agentbox-carry-${String(args.index)}.tar`;
397
+ await args.backend.uploadFile(args.handle, localTar, remoteTar);
398
+ const mode = entry.mode !== void 0 ? entry.mode.toString(8).padStart(4, "0") : "";
399
+ const uid = entry.user ?? 1e3;
400
+ const fileBase = !isDir ? basenameUnix(entry.absSrc) : "";
401
+ const destBase = !isDir ? basenameUnix(boxDest) : "";
402
+ const renameNeeded = !isDir && fileBase !== destBase;
403
+ const parts = [
404
+ `mkdir -p ${shellQuote(parentDir)}`,
405
+ 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`
406
+ ];
407
+ if (renameNeeded) {
408
+ parts.push(
409
+ `mv ${shellQuote(`${parentDir}/${fileBase}`)} ${shellQuote(boxDest)}`
410
+ );
411
+ }
412
+ if (mode) parts.push(`chmod -R ${mode} ${shellQuote(boxDest)}`);
413
+ parts.push(`chown -R ${String(uid)}:${String(uid)} ${shellQuote(boxDest)}`);
414
+ if (boxDest.startsWith(BOX_HOME + "/") && parentDir !== BOX_HOME) {
415
+ parts.push(
416
+ `parent=$(dirname ${shellQuote(boxDest)}); while [ "$parent" != "${BOX_HOME}" ] && [ "$parent" != "/" ]; do chown ${String(uid)}:${String(uid)} "$parent"; parent=$(dirname "$parent"); done`
417
+ );
418
+ }
419
+ parts.push(`rm -f ${remoteTar}`);
420
+ const cmd = parts.join(" && ");
421
+ const res = await args.backend.exec(args.handle, cmd);
422
+ if (res.exitCode !== 0) {
423
+ throw new Error(
424
+ `in-box extract failed (exit ${String(res.exitCode)}): ${(res.stderr || res.stdout).slice(-300)}`
425
+ );
426
+ }
427
+ }
428
+ function dirnameUnix(p) {
429
+ const i = p.lastIndexOf("/");
430
+ if (i <= 0) return "/";
431
+ return p.slice(0, i);
432
+ }
433
+ function basenameUnix(p) {
434
+ const i = p.lastIndexOf("/");
435
+ return i < 0 ? p : p.slice(i + 1);
436
+ }
437
+ function shellQuote(s) {
438
+ return `'${s.replace(/'/g, `'\\''`)}'`;
439
+ }
440
+ async function readExposedServicePorts(workspacePath) {
441
+ let text;
442
+ try {
443
+ text = await readFile2(join4(workspacePath, "agentbox.yaml"), "utf8");
444
+ } catch {
445
+ return [];
446
+ }
447
+ let doc;
448
+ try {
449
+ doc = parseYaml(text);
450
+ } catch {
451
+ return [];
452
+ }
453
+ if (!isPlainObject(doc)) return [];
454
+ const services = doc["services"];
455
+ if (!isPlainObject(services)) return [];
456
+ const out = /* @__PURE__ */ new Set();
457
+ for (const value of Object.values(services)) {
458
+ if (!isPlainObject(value)) continue;
459
+ const expose = value["expose"];
460
+ if (!isPlainObject(expose)) continue;
461
+ const port = expose["port"];
462
+ if (typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536) {
463
+ out.add(port);
464
+ }
465
+ }
466
+ return [...out].sort((a, b) => a - b);
467
+ }
468
+ function isPlainObject(v) {
469
+ return typeof v === "object" && v !== null && !Array.isArray(v);
470
+ }
471
+ function quoteShellArgv(argv) {
472
+ return argv.map(quoteShellArg).join(" ");
473
+ }
474
+ function quoteShellArg(arg) {
475
+ if (arg.length === 0) return "''";
476
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(arg)) return arg;
477
+ return "'" + arg.replace(/'/g, "'\\''") + "'";
478
+ }
479
+ function bashScript(body) {
480
+ return `bash -c ${quoteShellArg(body)}`;
481
+ }
482
+ var REMOTE_UP_TAR = "/tmp/agentbox-cp-up.tar.gz";
483
+ var REMOTE_DOWN_TAR = "/tmp/agentbox-cp-down.tar.gz";
484
+ async function uploadToCloudBox(backend, handle, hostSrc, boxDst) {
485
+ const srcAbs = hostResolve(hostSrc);
486
+ if (!existsSync(srcAbs)) throw new Error(`source not found: ${hostSrc}`);
487
+ const srcBasename = hostBasename(srcAbs);
488
+ const srcParent = hostDirname(srcAbs);
489
+ let boxParent;
490
+ let finalName;
491
+ if (boxDst.endsWith("/")) {
492
+ boxParent = boxDst.replace(/\/+$/, "") || "/";
493
+ finalName = srcBasename;
494
+ } else {
495
+ boxParent = posix.dirname(boxDst);
496
+ finalName = posix.basename(boxDst);
497
+ }
498
+ const finalPath = boxParent === "/" ? `/${finalName}` : `${boxParent}/${finalName}`;
499
+ const stage = await mkdtemp3(hostJoin(tmpdir3(), "agentbox-cp-up-"));
500
+ const localTar = hostJoin(stage, "payload.tar.gz");
501
+ try {
502
+ await execa3("tar", ["-C", srcParent, "-czf", localTar, srcBasename], {
503
+ env: { ...process.env, COPYFILE_DISABLE: "1" }
504
+ });
505
+ await backend.uploadFile(handle, localTar, REMOTE_UP_TAR);
506
+ const initialPath = boxParent === "/" ? `/${srcBasename}` : `${boxParent}/${srcBasename}`;
507
+ const renameStep = finalName !== srcBasename ? `$SUDO cp -f ${quoteShellArg(initialPath)} ${quoteShellArg(finalPath)} && $SUDO rm -f ${quoteShellArg(initialPath)}` : ": # no rename";
508
+ const script = [
509
+ `set -euo pipefail`,
510
+ `if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi`,
511
+ `$SUDO mkdir -p ${quoteShellArg(boxParent)}`,
512
+ // --no-same-permissions / --no-same-owner / -m: Daytona's S3-backed
513
+ // FUSE volumes reject chmod/utime/chown; skipping them lets the extract
514
+ // complete on a mounted-volume destination. Harmless no-op on the
515
+ // sandbox's regular disk. Same flags as the credential-seed extract.
516
+ `$SUDO tar -xzf ${quoteShellArg(REMOTE_UP_TAR)} -C ${quoteShellArg(boxParent)} --no-same-permissions --no-same-owner -m`,
517
+ renameStep,
518
+ // chown only the landed path — anything we mkdir'd through stays at
519
+ // its existing ownership. Tolerate failure (chown bad on read-only mounts).
520
+ `$SUDO chown -R "$(id -un):$(id -gn)" ${quoteShellArg(finalPath)} || true`,
521
+ `rm -f ${quoteShellArg(REMOTE_UP_TAR)}`
522
+ ].join("\n");
523
+ const r = await backend.exec(handle, bashScript(script));
524
+ if (r.exitCode !== 0) {
525
+ throw new Error(`cloud upload extract failed: ${r.stderr || r.stdout}`);
526
+ }
527
+ } finally {
528
+ await rm4(stage, { recursive: true, force: true });
529
+ }
530
+ return { finalPath };
531
+ }
532
+ async function pullCloudDirContents(backend, handle, boxSrcDir, hostDstDir) {
533
+ const dstAbs = hostResolve(hostDstDir);
534
+ mkdirSync(dstAbs, { recursive: true });
535
+ const stage = await mkdtemp3(hostJoin(tmpdir3(), "agentbox-pull-"));
536
+ const localTar = hostJoin(stage, "payload.tar.gz");
537
+ try {
538
+ const packScript = [
539
+ `set -euo pipefail`,
540
+ `cd ${quoteShellArg(boxSrcDir)}`,
541
+ `tar -czf ${quoteShellArg(REMOTE_DOWN_TAR)} .`
542
+ ].join("\n");
543
+ const r = await backend.exec(handle, bashScript(packScript));
544
+ if (r.exitCode !== 0) {
545
+ throw new Error(`cloud workspace pack failed: ${r.stderr || r.stdout}`);
546
+ }
547
+ await backend.downloadFile(handle, REMOTE_DOWN_TAR, localTar);
548
+ await execa3("tar", ["-xzf", localTar, "-C", dstAbs]);
549
+ await backend.exec(handle, `rm -f ${quoteShellArg(REMOTE_DOWN_TAR)}`).catch(() => {
550
+ });
551
+ } finally {
552
+ await rm4(stage, { recursive: true, force: true });
553
+ }
554
+ return { finalPath: dstAbs };
555
+ }
556
+ async function downloadFromCloudBox(backend, handle, boxSrc, hostDst) {
557
+ const srcBasename = posix.basename(boxSrc);
558
+ const srcParent = posix.dirname(boxSrc);
559
+ const dstAbs = hostResolve(hostDst);
560
+ let hostParent;
561
+ let finalName;
562
+ const dstExists = existsSync(dstAbs);
563
+ if (hostDst.endsWith("/") || dstExists && statSync(dstAbs).isDirectory()) {
564
+ hostParent = dstAbs;
565
+ finalName = srcBasename;
566
+ } else {
567
+ hostParent = hostDirname(dstAbs);
568
+ finalName = hostBasename(dstAbs);
569
+ }
570
+ mkdirSync(hostParent, { recursive: true });
571
+ const finalPath = hostJoin(hostParent, finalName);
572
+ const stage = await mkdtemp3(hostJoin(tmpdir3(), "agentbox-cp-down-"));
573
+ const localTar = hostJoin(stage, "payload.tar.gz");
574
+ try {
575
+ const packScript = [
576
+ `set -euo pipefail`,
577
+ `cd ${quoteShellArg(srcParent)}`,
578
+ `tar -czf ${quoteShellArg(REMOTE_DOWN_TAR)} ${quoteShellArg(srcBasename)}`
579
+ ].join("\n");
580
+ const r = await backend.exec(handle, bashScript(packScript));
581
+ if (r.exitCode !== 0) {
582
+ throw new Error(`cloud download pack failed: ${r.stderr || r.stdout}`);
583
+ }
584
+ await backend.downloadFile(handle, REMOTE_DOWN_TAR, localTar);
585
+ await execa3("tar", ["-xzf", localTar, "-C", hostParent]);
586
+ if (finalName !== srcBasename) {
587
+ renameSync(hostJoin(hostParent, srcBasename), finalPath);
588
+ }
589
+ await backend.exec(handle, `rm -f ${quoteShellArg(REMOTE_DOWN_TAR)}`).catch(() => {
590
+ });
591
+ } finally {
592
+ await rm4(stage, { recursive: true, force: true });
593
+ }
594
+ return { finalPath };
595
+ }
596
+ async function launchCloudCtlDaemon(args) {
597
+ const env = [
598
+ `AGENTBOX_BOX_ID=${quoteShellArgv([args.boxId])}`,
599
+ `AGENTBOX_BOX_NAME=${quoteShellArgv([args.boxName])}`,
600
+ `AGENTBOX_BOX_KIND=cloud`
601
+ ];
602
+ if (args.relayUrl) env.push(`AGENTBOX_RELAY_URL=${quoteShellArgv([args.relayUrl])}`);
603
+ if (args.relayToken) env.push(`AGENTBOX_RELAY_TOKEN=${quoteShellArgv([args.relayToken])}`);
604
+ if (args.bridgeToken) env.push(`AGENTBOX_BRIDGE_TOKEN=${quoteShellArgv([args.bridgeToken])}`);
605
+ const script = [
606
+ `set -e`,
607
+ `if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi`,
608
+ `$SUDO mkdir -p /run/agentbox /var/log/agentbox`,
609
+ `$SUDO chown "$(id -un):$(id -gn)" /run/agentbox /var/log/agentbox`,
610
+ `export ${env.join(" ")}`,
611
+ `nohup /usr/local/bin/agentbox-ctl daemon >> /var/log/agentbox/ctl-daemon.log 2>&1 &`,
612
+ `disown`,
613
+ `echo started`
614
+ ].join("\n");
615
+ const r = await args.backend.exec(args.handle, bashScript(script));
616
+ if (r.exitCode !== 0) {
617
+ throw new Error(`agentbox-ctl daemon launch failed: ${r.stderr || r.stdout}`);
618
+ }
619
+ }
620
+ async function launchCloudDockerdDaemon(args) {
621
+ const timeoutMs = args.timeoutMs ?? 6e4;
622
+ const startScript = [
623
+ `set -euo pipefail`,
624
+ `mkdir -p /var/log/agentbox`,
625
+ `nohup sudo -n /usr/local/bin/agentbox-dockerd-start >> /var/log/agentbox/dockerd.log 2>&1 &`,
626
+ `echo "spawned dockerd"`
627
+ ].join("\n");
628
+ const launch = await args.backend.exec(args.handle, bashScript(startScript));
629
+ if (launch.exitCode !== 0) {
630
+ return {
631
+ up: false,
632
+ reason: `dockerd launch failed: ${launch.stderr || launch.stdout}`
633
+ };
634
+ }
635
+ const probeCmd = "[ -S /var/run/docker.sock ] && docker -H unix:///var/run/docker.sock info >/dev/null 2>&1";
636
+ const deadline = Date.now() + timeoutMs;
637
+ while (Date.now() < deadline) {
638
+ const probe = await args.backend.exec(args.handle, probeCmd);
639
+ if (probe.exitCode === 0) return { up: true };
640
+ await new Promise((r) => setTimeout(r, 500));
641
+ }
642
+ return {
643
+ up: false,
644
+ reason: `dockerd did not become ready within ${String(timeoutMs)}ms`
645
+ };
646
+ }
647
+ async function launchCloudVncDaemon(args) {
648
+ const script = [
649
+ `set -e`,
650
+ `cd /home/vscode`,
651
+ `export AGENTBOX_VNC_PASSWORD=${quoteShellArg(args.vncPassword)}`,
652
+ `mkdir -p /var/log/agentbox 2>/dev/null || true`,
653
+ `nohup /usr/local/bin/agentbox-vnc-start >> /var/log/agentbox/vnc-start.log 2>&1 &`,
654
+ `disown`,
655
+ // Match the host-side probe in packages/sandbox-docker/src/vnc.ts: poll for
656
+ // ~5s, then give up. The script itself waits for Xvnc internally; this
657
+ // outer probe confirms websockify's public port came up too.
658
+ `for _ in $(seq 1 50); do`,
659
+ ` if (echo > /dev/tcp/127.0.0.1/6080) 2>/dev/null; then echo ready; exit 0; fi`,
660
+ ` sleep 0.1`,
661
+ `done`,
662
+ `echo "websockify did not bind 6080 within 5s" >&2`,
663
+ `exit 1`
664
+ ].join("\n");
665
+ const r = await args.backend.exec(args.handle, bashScript(script));
666
+ if (r.exitCode !== 0) {
667
+ throw new Error(`agentbox-vnc-start failed: ${r.stderr || r.stdout}`);
668
+ }
669
+ }
670
+ var WORKSPACE_DIR_DEFAULT2 = "/workspace";
671
+ async function seedCloudWorkspace(args) {
672
+ const workspaceDir = args.workspaceDir ?? WORKSPACE_DIR_DEFAULT2;
673
+ const log = args.onLog ?? (() => {
674
+ });
675
+ const repos = await detectGitRepos(args.workspacePath);
676
+ const root = repos.find((r) => r.kind === "root");
677
+ const nested = repos.filter((r) => r.kind === "nested");
678
+ if (root) {
679
+ log(
680
+ nested.length > 0 ? `seeding /workspace from shallow git clone (+${String(nested.length)} nested repo${nested.length === 1 ? "" : "s"})` : "seeding /workspace from shallow git clone"
681
+ );
682
+ await seedFromGitClone({
683
+ backend: args.backend,
684
+ handle: args.handle,
685
+ hostRepo: root.hostMainRepo,
686
+ branch: args.branch,
687
+ workspaceDir,
688
+ bundleDepth: args.bundleDepth,
689
+ fromBranch: args.fromBranch,
690
+ onLog: log
691
+ });
692
+ for (const r of nested) {
693
+ const sub = `${workspaceDir}/${r.relPathFromWorkspace}`;
694
+ log(`seeding nested repo ${r.relPathFromWorkspace} from shallow git clone`);
695
+ await seedFromGitClone({
696
+ backend: args.backend,
697
+ handle: args.handle,
698
+ hostRepo: r.hostMainRepo,
699
+ branch: args.branch,
700
+ workspaceDir: sub,
701
+ bundleDepth: args.bundleDepth,
702
+ onLog: log
703
+ });
704
+ }
705
+ return { fromGit: true, branch: args.branch };
706
+ }
707
+ log("seeding /workspace from workspace tarball (no git detected)");
708
+ await seedFromTar({
709
+ backend: args.backend,
710
+ handle: args.handle,
711
+ hostDir: args.workspacePath,
712
+ workspaceDir
713
+ });
714
+ return { fromGit: false, branch: args.branch };
715
+ }
716
+ var STASH_CARRYOVER_REF = "refs/agentbox-carryover/stash";
717
+ var REMOTE_UNTRACKED_TAR = "/tmp/agentbox-carryover-untracked.tar.gz";
718
+ var DEFAULT_BUNDLE_DEPTH = 200;
719
+ var LARGE_BUNDLE_DEPTH = 100;
720
+ var LARGE_BUNDLE_THRESHOLD_BYTES = 20 * 1024 * 1024;
721
+ async function seedFromGitClone(args) {
722
+ const log = args.onLog ?? (() => {
723
+ });
724
+ const stage = await mkdtemp4(join5(tmpdir4(), "agentbox-clone-"));
725
+ const cloneDir = join5(stage, "clone");
726
+ const tarPath = join5(stage, "workspace.tar.gz");
727
+ const untrackedTarPath = join5(stage, "untracked.tar.gz");
728
+ const stashSha = await safeStashCreate(args.hostRepo);
729
+ const untrackedSize = await maybeBuildUntrackedTar(args.hostRepo, untrackedTarPath);
730
+ let stashRefCreated = false;
731
+ try {
732
+ if (stashSha) {
733
+ const ref = await execa4(
734
+ "git",
735
+ ["-C", args.hostRepo, "update-ref", STASH_CARRYOVER_REF, stashSha],
736
+ { reject: false }
737
+ );
738
+ stashRefCreated = ref.exitCode === 0;
739
+ }
740
+ const configured = args.bundleDepth;
741
+ const adaptive = configured === void 0;
742
+ const initialDepth = adaptive ? DEFAULT_BUNDLE_DEPTH : configured === 0 ? null : configured;
743
+ log(
744
+ adaptive ? `clone: depth=${String(DEFAULT_BUNDLE_DEPTH)} (default, adaptive)` : initialDepth === null ? "clone: depth=full (configured)" : `clone: depth=${String(initialDepth)} (configured)`
745
+ );
746
+ await runShallowClone(args.hostRepo, cloneDir, initialDepth, stashRefCreated, args.fromBranch);
747
+ await tarCloneDir(cloneDir, tarPath);
748
+ if (adaptive && initialDepth !== null) {
749
+ const size = await safeFileSize(tarPath);
750
+ if (size > LARGE_BUNDLE_THRESHOLD_BYTES) {
751
+ const mb = (size / (1024 * 1024)).toFixed(1);
752
+ log(
753
+ `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)}`
754
+ );
755
+ await rm5(cloneDir, { recursive: true, force: true });
756
+ await rm5(tarPath, { force: true });
757
+ await runShallowClone(args.hostRepo, cloneDir, LARGE_BUNDLE_DEPTH, stashRefCreated, args.fromBranch);
758
+ await tarCloneDir(cloneDir, tarPath);
759
+ }
760
+ }
761
+ const remoteUrl = await readOriginUrl(args.hostRepo);
762
+ const remoteTar = "/tmp/agentbox-workspace.tar.gz";
763
+ await args.backend.uploadFile(args.handle, tarPath, remoteTar);
764
+ if (untrackedSize > 0) {
765
+ await args.backend.uploadFile(args.handle, untrackedTarPath, REMOTE_UNTRACKED_TAR);
766
+ }
767
+ const setOrigin = remoteUrl ? `git -C ${quoteShellArgv([args.workspaceDir])} remote set-url origin ${quoteShellArgv([remoteUrl])}` : ": # no host origin to copy";
768
+ const SUDO = `if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi`;
769
+ const carryOverSteps = stashSha ? [
770
+ `if git -C ${quoteShellArgv([args.workspaceDir])} rev-parse --verify ${quoteShellArgv([`refs/remotes/origin/agentbox-carryover/stash`])} >/dev/null 2>&1; then git -C ${quoteShellArgv([args.workspaceDir])} stash apply ${quoteShellArgv([`refs/remotes/origin/agentbox-carryover/stash`])} || echo "agentbox: stash apply soft-failed; carry-over may be incomplete" >&2 ; git -C ${quoteShellArgv([args.workspaceDir])} update-ref -d ${quoteShellArgv([`refs/remotes/origin/agentbox-carryover/stash`])} || true ; fi`
771
+ ] : [];
772
+ if (untrackedSize > 0) {
773
+ carryOverSteps.push(
774
+ `if [ -f ${quoteShellArgv([REMOTE_UNTRACKED_TAR])} ]; then tar -C ${quoteShellArgv([args.workspaceDir])} -xzf ${quoteShellArgv([REMOTE_UNTRACKED_TAR])} && rm -f ${quoteShellArgv([REMOTE_UNTRACKED_TAR])} ; fi`
775
+ );
776
+ }
777
+ const script = [
778
+ `set -euo pipefail`,
779
+ // Move out of any cwd we might inherit from Daytona's executeCommand
780
+ // before we delete /workspace. The agentbox image bakes WORKDIR
781
+ // /workspace; if the shell's cwd is /workspace when we `rm -rf` it,
782
+ // the next process inherits a stale cwd FD and tar's children fail
783
+ // with "Unable to read current working directory".
784
+ `cd /tmp`,
785
+ SUDO,
786
+ // rm -rf only the directory we're about to extract into — for nested
787
+ // repos this is just `/workspace/<rel>`, so the root clone (already
788
+ // at `/workspace`) is preserved.
789
+ `$SUDO rm -rf ${quoteShellArgv([args.workspaceDir])}`,
790
+ `$SUDO mkdir -p ${quoteShellArgv([args.workspaceDir])}`,
791
+ `$SUDO chown "$(id -un):$(id -gn)" ${quoteShellArgv([args.workspaceDir])}`,
792
+ `tar -C ${quoteShellArgv([args.workspaceDir])} -xzf ${quoteShellArgv([remoteTar])}`,
793
+ setOrigin,
794
+ `git -C ${quoteShellArgv([args.workspaceDir])} checkout -B ${quoteShellArgv([args.branch])}`,
795
+ ...carryOverSteps,
796
+ `rm -f ${quoteShellArgv([remoteTar])}`
797
+ ].join("\n");
798
+ const r = await args.backend.exec(args.handle, bashScript(script));
799
+ if (r.exitCode !== 0) {
800
+ throw new Error(`workspace seed (clone) failed: ${r.stderr || r.stdout}`);
801
+ }
802
+ } finally {
803
+ if (stashRefCreated) {
804
+ await execa4("git", ["-C", args.hostRepo, "update-ref", "-d", STASH_CARRYOVER_REF], {
805
+ reject: false
806
+ });
807
+ }
808
+ await rm5(stage, { recursive: true, force: true });
809
+ }
810
+ }
811
+ async function runShallowClone(hostRepo, cloneDir, depth, includeStashRef, fromBranch) {
812
+ const cloneArgs = ["clone", "--no-checkout", "--quiet"];
813
+ if (depth !== null) cloneArgs.push(`--depth=${String(depth)}`);
814
+ if (fromBranch) cloneArgs.push("--branch", fromBranch);
815
+ cloneArgs.push(`file://${hostRepo}`, cloneDir);
816
+ await execa4("git", cloneArgs);
817
+ if (includeStashRef) {
818
+ const fetchArgs = ["-C", cloneDir, "fetch", "--quiet"];
819
+ if (depth !== null) fetchArgs.push(`--depth=${String(depth)}`);
820
+ fetchArgs.push(
821
+ `file://${hostRepo}`,
822
+ `+${STASH_CARRYOVER_REF}:refs/remotes/origin/agentbox-carryover/stash`
823
+ );
824
+ await execa4("git", fetchArgs, { reject: false });
825
+ }
826
+ }
827
+ async function tarCloneDir(cloneDir, outPath) {
828
+ await execa4("tar", ["-C", cloneDir, "-czf", outPath, "."], {
829
+ env: { ...process.env, COPYFILE_DISABLE: "1" }
830
+ });
831
+ }
832
+ async function safeFileSize(path) {
833
+ try {
834
+ return (await stat(path)).size;
835
+ } catch {
836
+ return 0;
837
+ }
838
+ }
839
+ async function safeStashCreate(hostRepo) {
840
+ const r = await execa4("git", ["-C", hostRepo, "stash", "create"], { reject: false });
841
+ if (r.exitCode !== 0) return null;
842
+ const sha = r.stdout.trim();
843
+ return sha.length > 0 ? sha : null;
844
+ }
845
+ async function maybeBuildUntrackedTar(hostRepo, outPath) {
846
+ const list = await execa4(
847
+ "git",
848
+ ["-C", hostRepo, "ls-files", "--others", "--exclude-standard", "-z"],
849
+ { reject: false }
850
+ );
851
+ if (list.exitCode !== 0 || list.stdout.length === 0) return 0;
852
+ const tar = await execa4(
853
+ "tar",
854
+ ["-C", hostRepo, "--null", "-T", "-", "-czf", outPath],
855
+ {
856
+ input: list.stdout,
857
+ env: { ...process.env, COPYFILE_DISABLE: "1" },
858
+ reject: false
859
+ }
860
+ );
861
+ if (tar.exitCode !== 0) return 0;
862
+ try {
863
+ const { stat: stat2 } = await import("fs/promises");
864
+ const s = await stat2(outPath);
865
+ return s.size;
866
+ } catch {
867
+ return 0;
868
+ }
869
+ }
870
+ async function readOriginUrl(hostRepo) {
871
+ const r = await execa4("git", ["-C", hostRepo, "remote", "get-url", "origin"], { reject: false });
872
+ if (r.exitCode !== 0) return null;
873
+ const out = (r.stdout ?? "").trim();
874
+ return out.length > 0 ? out : null;
875
+ }
876
+ async function seedFromTar(args) {
877
+ const stage = await mkdtemp4(join5(tmpdir4(), "agentbox-tar-"));
878
+ const tarPath = join5(stage, "workspace.tar.gz");
879
+ try {
880
+ await execa4("tar", ["-C", args.hostDir, "-czf", tarPath, "."]);
881
+ const remoteTar = "/tmp/agentbox-workspace.tar.gz";
882
+ await args.backend.uploadFile(args.handle, tarPath, remoteTar);
883
+ const SUDO = `if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi`;
884
+ const script = [
885
+ `set -euo pipefail`,
886
+ // Move out of any cwd we might inherit from Daytona's executeCommand
887
+ // before we delete /workspace. The agentbox image bakes WORKDIR
888
+ // /workspace; if the shell's cwd is /workspace when we `rm -rf` it,
889
+ // the next process inherits a stale cwd FD and git-clone's child
890
+ // (index-pack) fails with "Unable to read current working directory".
891
+ `cd /tmp`,
892
+ SUDO,
893
+ `$SUDO rm -rf ${quoteShellArgv([args.workspaceDir])}`,
894
+ `$SUDO mkdir -p ${quoteShellArgv([args.workspaceDir])}`,
895
+ `$SUDO chown "$(id -un):$(id -gn)" ${quoteShellArgv([args.workspaceDir])}`,
896
+ `tar -C ${quoteShellArgv([args.workspaceDir])} -xzf ${quoteShellArgv([remoteTar])}`,
897
+ `rm -f ${quoteShellArgv([remoteTar])}`
898
+ ].join("\n");
899
+ const r = await args.backend.exec(args.handle, bashScript(script));
900
+ if (r.exitCode !== 0) {
901
+ throw new Error(`workspace seed (tar) failed: ${r.stderr || r.stdout}`);
902
+ }
903
+ } finally {
904
+ await rm5(stage, { recursive: true, force: true });
905
+ }
906
+ }
907
+ var CLOUD_WORKSPACE_DIR = "/workspace";
908
+ var CLOUD_WEB_PROXY_PORT = 80;
909
+ var CLOUD_VNC_PORT = 6080;
910
+ var DEFAULT_SIGNED_URL_TTL_SECONDS = 3600;
911
+ var FALLBACK_IMAGE = "agentbox/box:dev";
912
+ var DEFAULT_PORTLESS_PROXY_PORT = 1355;
913
+ function parsePortlessUrl(url) {
914
+ try {
915
+ const u = new URL(url);
916
+ if (!u.hostname.endsWith(".localhost")) return void 0;
917
+ const tls = u.protocol === "https:";
918
+ const proxyPort = u.port ? Number.parseInt(u.port, 10) : tls ? 443 : 80;
919
+ if (!Number.isFinite(proxyPort)) return void 0;
920
+ return { proxyPort, tls };
921
+ } catch {
922
+ return void 0;
923
+ }
924
+ }
925
+ function parseLoopbackPort(url) {
926
+ try {
927
+ const u = new URL(url);
928
+ if (u.hostname !== "127.0.0.1" && u.hostname !== "localhost") return void 0;
929
+ const port = Number.parseInt(u.port, 10);
930
+ return Number.isFinite(port) ? port : void 0;
931
+ } catch {
932
+ return void 0;
933
+ }
934
+ }
935
+ async function registerHostPortlessAlias(args) {
936
+ const localPort = parseLoopbackPort(args.previewUrl);
937
+ if (localPort === void 0) return void 0;
938
+ const ok = await portlessAlias(args.alias, localPort);
939
+ if (!ok) {
940
+ args.onLog(
941
+ `portless: ${args.label} alias not registered (portless CLI missing or not running) \u2014 host URL stays http://127.0.0.1:${String(localPort)}`
942
+ );
943
+ return void 0;
944
+ }
945
+ const url = await portlessGetUrl(args.alias);
946
+ args.onLog(`portless alias ${url} -> 127.0.0.1:${String(localPort)}`);
947
+ return url;
948
+ }
949
+ async function bootstrapPortlessForCloudBox(backend, handle, args) {
950
+ const url = await registerHostPortlessAlias({
951
+ alias: args.boxName,
952
+ previewUrl: args.webPreviewUrl,
953
+ label: "web",
954
+ onLog: args.onLog
955
+ });
956
+ if (!url) return void 0;
957
+ if (backend.startInBoxPortless) {
958
+ const mode = parsePortlessUrl(url) ?? { proxyPort: DEFAULT_PORTLESS_PROXY_PORT, tls: false };
959
+ try {
960
+ await backend.startInBoxPortless(handle, {
961
+ boxName: args.boxName,
962
+ proxyPort: mode.proxyPort,
963
+ tls: mode.tls,
964
+ webPort: args.webPort
965
+ });
966
+ args.onLog(
967
+ `portless: in-box mirror up on 127.0.0.1:${String(mode.proxyPort)} (${mode.tls ? "https" : "http"})`
968
+ );
969
+ } catch (err) {
970
+ args.onLog(
971
+ `portless: in-box mirror failed (continuing): ${err instanceof Error ? err.message : String(err)}`
972
+ );
973
+ }
974
+ }
975
+ return { alias: args.boxName, url };
976
+ }
977
+ function createCloudProvider(backend, opts = {}) {
978
+ const providerName = backend.name;
979
+ function handleFor(box) {
980
+ const sandboxId = box.cloud?.sandboxId;
981
+ if (!sandboxId) {
982
+ throw new Error(`cloud box ${box.name} has no sandboxId \u2014 record is malformed`);
983
+ }
984
+ return { sandboxId };
985
+ }
986
+ function mintBox(req) {
987
+ const id = randomBytes(4).toString("hex");
988
+ const name = req.name ?? `${basename2(req.workspacePath)}-${id}`;
989
+ return {
990
+ id,
991
+ name,
992
+ branch: `agentbox/${name}`
993
+ };
994
+ }
995
+ async function probe(box) {
996
+ try {
997
+ const h = handleFor(box);
998
+ const state = await backend.state(h);
999
+ return state;
1000
+ } catch {
1001
+ return "missing";
1002
+ }
1003
+ }
1004
+ return {
1005
+ name: providerName,
1006
+ async create(req) {
1007
+ const log = req.onLog ?? (() => {
1008
+ });
1009
+ const { id, name, branch } = mintBox(req);
1010
+ const image = opts.provisionImage ? await opts.provisionImage(req) : req.image ?? FALLBACK_IMAGE;
1011
+ const resources = opts.defaultResources ?? { cpu: 2, memory: 4, disk: 8 };
1012
+ const relayToken = generateRelayToken();
1013
+ const bridgeToken = generateRelayToken();
1014
+ try {
1015
+ await ensureRelay({ onLog: log });
1016
+ } catch (err) {
1017
+ log(`relay ensure failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
1018
+ }
1019
+ let snapshotName;
1020
+ let resolvedCheckpointRef;
1021
+ if (req.checkpointRef && req.projectRoot) {
1022
+ const found = await resolveCloudCheckpoint(req.projectRoot, backend.name, req.checkpointRef);
1023
+ if (found) {
1024
+ snapshotName = found.manifest.snapshotName;
1025
+ resolvedCheckpointRef = found.name;
1026
+ log(`provisioning from cloud checkpoint '${found.name}' (snapshot ${snapshotName})`);
1027
+ } else {
1028
+ log(
1029
+ `cloud checkpoint '${req.checkpointRef}' not found for ${backend.name}; provisioning from base image`
1030
+ );
1031
+ }
1032
+ }
1033
+ const agentVolumes = await ensureAgentVolumesForCloud(backend, { onLog: log });
1034
+ log(
1035
+ snapshotName ? `provisioning ${providerName} sandbox from snapshot` : `provisioning ${providerName} sandbox`
1036
+ );
1037
+ const handle = await backend.provision({
1038
+ name,
1039
+ image,
1040
+ snapshot: snapshotName,
1041
+ resources,
1042
+ env: {
1043
+ AGENTBOX_BOX_ID: id,
1044
+ AGENTBOX_BOX_NAME: name,
1045
+ AGENTBOX_BOX_KIND: "cloud",
1046
+ // In-sandbox relay is on the box's loopback at the in-box port.
1047
+ // 8788 is distinct from the host relay's 8787 so a nested agentbox
1048
+ // run inside the box can claim :8787 without colliding.
1049
+ AGENTBOX_RELAY_URL: `http://127.0.0.1:${String(8788)}`,
1050
+ AGENTBOX_RELAY_TOKEN: relayToken,
1051
+ AGENTBOX_BRIDGE_TOKEN: bridgeToken,
1052
+ ...agentVolumes.env
1053
+ },
1054
+ volumes: agentVolumes.mounts,
1055
+ onLog: log
1056
+ });
1057
+ try {
1058
+ if (snapshotName) {
1059
+ log("skipping workspace seed \u2014 snapshot already contains /workspace");
1060
+ } else {
1061
+ await seedCloudWorkspace({
1062
+ backend,
1063
+ handle,
1064
+ workspacePath: req.workspacePath,
1065
+ branch,
1066
+ workspaceDir: CLOUD_WORKSPACE_DIR,
1067
+ bundleDepth: req.bundleDepth,
1068
+ fromBranch: req.fromBranch,
1069
+ onLog: log
1070
+ });
1071
+ }
1072
+ if (agentVolumes.agents.length > 0) {
1073
+ await seedAgentVolumesIfFresh(backend, handle, {
1074
+ agents: agentVolumes.agents,
1075
+ hostWorkspace: req.workspacePath,
1076
+ onLog: log
1077
+ });
1078
+ }
1079
+ await seedOpencodeModelState(backend, handle, { onLog: log });
1080
+ if (req.envFilesToImport && req.envFilesToImport.length > 0) {
1081
+ const { copied } = await uploadEnvFiles({
1082
+ backend,
1083
+ handle,
1084
+ workspacePath: req.workspacePath,
1085
+ files: req.envFilesToImport,
1086
+ workspaceDir: CLOUD_WORKSPACE_DIR,
1087
+ onLog: log
1088
+ });
1089
+ if (copied > 0) log(`copied ${String(copied)} env/config file(s) into /workspace`);
1090
+ }
1091
+ let carrySummary;
1092
+ if (req.carry && req.carry.length > 0) {
1093
+ log(`carry: copying ${String(req.carry.length)} host path(s) into the box`);
1094
+ const result = await uploadCarryPaths({
1095
+ backend,
1096
+ handle,
1097
+ entries: req.carry,
1098
+ onLog: log
1099
+ });
1100
+ log(`carry: copied ${String(result.copied)}/${String(req.carry.length)} entry/entries`);
1101
+ for (const err of result.errors) log(`carry: ${err}`);
1102
+ if (result.applied.length > 0) {
1103
+ carrySummary = { count: result.applied.length, entries: result.applied };
1104
+ }
1105
+ }
1106
+ log("launching agentbox-ctl daemon");
1107
+ await launchCloudCtlDaemon({
1108
+ backend,
1109
+ handle,
1110
+ boxId: id,
1111
+ boxName: name,
1112
+ relayUrl: `http://127.0.0.1:${String(8788)}`,
1113
+ relayToken,
1114
+ bridgeToken
1115
+ });
1116
+ log("launching in-box dockerd");
1117
+ try {
1118
+ const dockerd = await launchCloudDockerdDaemon({ backend, handle, timeoutMs: 6e4 });
1119
+ if (!dockerd.up) log(`dockerd did not become ready (continuing): ${dockerd.reason ?? "unknown"}`);
1120
+ } catch (err) {
1121
+ log(`dockerd daemon launch failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
1122
+ }
1123
+ const vncEnabled = req.vnc?.enabled !== false;
1124
+ const vncPassword = vncEnabled ? generateVncPassword() : void 0;
1125
+ if (vncEnabled && vncPassword) {
1126
+ log("launching VNC stack (Xvnc + websockify + noVNC)");
1127
+ try {
1128
+ await launchCloudVncDaemon({ backend, handle, vncPassword });
1129
+ } catch (err) {
1130
+ log(
1131
+ `VNC daemon launch failed (continuing): ${err instanceof Error ? err.message : String(err)}`
1132
+ );
1133
+ }
1134
+ }
1135
+ let webPreview;
1136
+ try {
1137
+ webPreview = await backend.previewUrl(handle, CLOUD_WEB_PROXY_PORT);
1138
+ } catch {
1139
+ webPreview = void 0;
1140
+ }
1141
+ const portlessOpt = req.providerOptions?.["portless"] ?? true;
1142
+ let portlessAliasName;
1143
+ let portlessUrlResolved;
1144
+ if (portlessOpt && webPreview) {
1145
+ const r = await bootstrapPortlessForCloudBox(backend, handle, {
1146
+ boxName: name,
1147
+ webPreviewUrl: webPreview.url,
1148
+ webPort: CLOUD_WEB_PROXY_PORT,
1149
+ onLog: log
1150
+ });
1151
+ if (r) {
1152
+ portlessAliasName = r.alias;
1153
+ portlessUrlResolved = r.url;
1154
+ }
1155
+ }
1156
+ let vncPreview;
1157
+ if (portlessOpt && vncEnabled) {
1158
+ try {
1159
+ vncPreview = await backend.previewUrl(handle, CLOUD_VNC_PORT);
1160
+ } catch {
1161
+ vncPreview = void 0;
1162
+ }
1163
+ }
1164
+ let portlessVncAliasName;
1165
+ let portlessVncUrlResolved;
1166
+ if (portlessOpt && vncPreview) {
1167
+ const vncAlias = `vnc-${name}`;
1168
+ const url = await registerHostPortlessAlias({
1169
+ alias: vncAlias,
1170
+ previewUrl: vncPreview.url,
1171
+ label: "vnc",
1172
+ onLog: log
1173
+ });
1174
+ if (url) {
1175
+ portlessVncAliasName = vncAlias;
1176
+ portlessVncUrlResolved = url;
1177
+ }
1178
+ }
1179
+ const servicePorts = await readExposedServicePorts(req.workspacePath);
1180
+ const servicePreviews = {};
1181
+ for (const port of servicePorts) {
1182
+ if (port === CLOUD_WEB_PROXY_PORT) continue;
1183
+ try {
1184
+ const p = await backend.previewUrl(handle, port);
1185
+ servicePreviews[port] = p.url;
1186
+ } catch {
1187
+ }
1188
+ }
1189
+ let relayPreview;
1190
+ try {
1191
+ relayPreview = await backend.previewUrl(handle, 8788);
1192
+ } catch {
1193
+ relayPreview = void 0;
1194
+ }
1195
+ if (relayPreview) {
1196
+ try {
1197
+ await registerBoxWithRelay({
1198
+ boxId: id,
1199
+ token: relayToken,
1200
+ name,
1201
+ kind: "cloud",
1202
+ backend: backend.name,
1203
+ previewUrl: relayPreview.url,
1204
+ previewToken: relayPreview.token,
1205
+ bridgeToken,
1206
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1207
+ });
1208
+ } catch (err) {
1209
+ log(
1210
+ `register with host relay failed (continuing): ${err instanceof Error ? err.message : String(err)}`
1211
+ );
1212
+ }
1213
+ }
1214
+ const state = await readState();
1215
+ const projectIndex = req.projectRoot ? allocateProjectIndex(state, req.projectRoot) : void 0;
1216
+ const record = {
1217
+ id,
1218
+ name,
1219
+ provider: providerName,
1220
+ // `container` carries the sandbox id with a `cloud:` prefix —
1221
+ // unique within state, never collides with a real docker
1222
+ // container, and grepping for `agentbox-cloud-*` (the old
1223
+ // synthetic value) finds nothing now. `image` mirrors the
1224
+ // resolved cloud image so `BoxRecord.image: string` stays
1225
+ // required without docker-internal readers seeing `undefined`.
1226
+ container: `cloud:${handle.sandboxId}`,
1227
+ image,
1228
+ workspacePath: req.workspacePath,
1229
+ projectRoot: req.projectRoot,
1230
+ projectIndex,
1231
+ relayToken,
1232
+ withPlaywright: req.withPlaywright,
1233
+ withEnv: req.withEnv,
1234
+ carry: carrySummary,
1235
+ portlessAlias: portlessAliasName,
1236
+ portlessUrl: portlessUrlResolved,
1237
+ portlessVncAlias: portlessVncAliasName,
1238
+ portlessVncUrl: portlessVncUrlResolved,
1239
+ vncEnabled,
1240
+ vncPassword,
1241
+ vncContainerPort: vncEnabled ? CLOUD_VNC_PORT : void 0,
1242
+ resourceLimits: req.limits ? {
1243
+ memoryBytes: req.limits.memoryBytes ?? void 0,
1244
+ cpus: req.limits.cpus ?? void 0,
1245
+ pidsLimit: req.limits.pidsLimit ?? void 0,
1246
+ disk: req.limits.disk ?? void 0
1247
+ } : void 0,
1248
+ cloud: {
1249
+ backend: backend.name,
1250
+ sandboxId: handle.sandboxId,
1251
+ image,
1252
+ webPort: CLOUD_WEB_PROXY_PORT,
1253
+ previewUrls: (() => {
1254
+ const m = { ...servicePreviews };
1255
+ if (webPreview) m[CLOUD_WEB_PROXY_PORT] = webPreview.url;
1256
+ return Object.keys(m).length > 0 ? m : void 0;
1257
+ })(),
1258
+ relayPreviewUrl: relayPreview?.url,
1259
+ relayPreviewToken: relayPreview?.token,
1260
+ bridgeToken,
1261
+ snapshotRef: resolvedCheckpointRef
1262
+ },
1263
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1264
+ };
1265
+ await recordBox(record);
1266
+ return { record, imageBuilt: false };
1267
+ } catch (err) {
1268
+ try {
1269
+ await backend.destroy(handle);
1270
+ } catch {
1271
+ }
1272
+ throw err;
1273
+ }
1274
+ },
1275
+ async start(box) {
1276
+ const h = handleFor(box);
1277
+ await backend.start(h);
1278
+ const webPort = box.cloud?.webPort ?? CLOUD_WEB_PROXY_PORT;
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;
1398
+ },
1399
+ async pause(box) {
1400
+ await backend.pause(handleFor(box));
1401
+ },
1402
+ async resume(box) {
1403
+ await backend.resume(handleFor(box));
1404
+ },
1405
+ async stop(box) {
1406
+ await backend.stop(handleFor(box));
1407
+ },
1408
+ async destroy(box) {
1409
+ try {
1410
+ await backend.destroy(handleFor(box));
1411
+ } catch (err) {
1412
+ const msg = err instanceof Error ? err.message : String(err);
1413
+ if (!/not.?found|missing/i.test(msg)) throw err;
1414
+ }
1415
+ if (box.portlessAlias) {
1416
+ try {
1417
+ await portlessUnalias(box.portlessAlias);
1418
+ } catch {
1419
+ }
1420
+ }
1421
+ if (box.portlessVncAlias) {
1422
+ try {
1423
+ await portlessUnalias(box.portlessVncAlias);
1424
+ } catch {
1425
+ }
1426
+ }
1427
+ try {
1428
+ await forgetBoxFromRelay(box.id);
1429
+ } catch {
1430
+ }
1431
+ await removeBoxRecord(box.id);
1432
+ },
1433
+ async probeState(box) {
1434
+ return probe(box);
1435
+ },
1436
+ async inspect(box) {
1437
+ const state = await probe(box);
1438
+ const webPort = box.cloud?.webPort ?? CLOUD_WEB_PROXY_PORT;
1439
+ const portlessWebUrl = box.portlessAlias !== void 0 ? box.portlessUrl ?? `https://${box.portlessAlias}.localhost` : void 0;
1440
+ const cachedWebUrl = box.cloud?.previewUrls?.[webPort];
1441
+ const webUrl = portlessWebUrl ?? cachedWebUrl;
1442
+ const endpoints = [];
1443
+ if (webUrl) {
1444
+ endpoints.push({
1445
+ kind: "web",
1446
+ name: "web",
1447
+ containerPort: webPort,
1448
+ url: webUrl,
1449
+ reachable: true
1450
+ });
1451
+ }
1452
+ for (const [portStr, url] of Object.entries(box.cloud?.previewUrls ?? {})) {
1453
+ const port = Number.parseInt(portStr, 10);
1454
+ if (!Number.isFinite(port) || port === webPort) continue;
1455
+ endpoints.push({
1456
+ kind: "web",
1457
+ name: `service-${String(port)}`,
1458
+ containerPort: port,
1459
+ url,
1460
+ reachable: true
1461
+ });
1462
+ }
1463
+ return {
1464
+ record: box,
1465
+ state,
1466
+ endpoints: {
1467
+ domain: webUrl ? new URL(webUrl).host : "",
1468
+ domainIsOrb: false,
1469
+ endpoints
1470
+ },
1471
+ raw: void 0
1472
+ };
1473
+ },
1474
+ async exec(box, argv, opts2) {
1475
+ const r = await backend.exec(handleFor(box), quoteShellArgv(argv), {
1476
+ cwd: opts2?.cwd,
1477
+ env: opts2?.env,
1478
+ user: opts2?.user
1479
+ });
1480
+ return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
1481
+ },
1482
+ async buildAttach(box, kind, opts2) {
1483
+ if (!backend.attachArgv) {
1484
+ throw new Error(
1485
+ `cloud backend '${backend.name}' does not implement attachArgv \u2014 interactive attach not supported`
1486
+ );
1487
+ }
1488
+ const handle = handleFor(box);
1489
+ const baseArgv = await backend.attachArgv(handle);
1490
+ const inner = renderInnerCommand(kind, opts2);
1491
+ const argv = opts2?.detached ? [...baseArgv.slice(1), inner] : [...baseArgv.slice(1), "-t", inner];
1492
+ const fullArgv = [baseArgv[0], ...argv];
1493
+ const cleanup = backend.revokeAttachToken ? async () => {
1494
+ await backend.revokeAttachToken(handle, baseArgv);
1495
+ } : void 0;
1496
+ return { argv: fullArgv, cleanup };
1497
+ },
1498
+ async uploadPath(box, hostSrc, boxDst) {
1499
+ return uploadToCloudBox(backend, handleFor(box), hostSrc, boxDst);
1500
+ },
1501
+ async downloadPath(box, boxSrc, hostDst) {
1502
+ return downloadFromCloudBox(backend, handleFor(box), boxSrc, hostDst);
1503
+ },
1504
+ async downloadDirContents(box, boxSrc, hostDst) {
1505
+ return pullCloudDirContents(backend, handleFor(box), boxSrc, hostDst);
1506
+ },
1507
+ async resolveUrl(box, opts2) {
1508
+ const h = handleFor(box);
1509
+ const kind = opts2?.kind ?? "web";
1510
+ if (!opts2?.loopback) {
1511
+ if (kind === "web" && box.portlessAlias) {
1512
+ return box.portlessUrl ?? `https://${box.portlessAlias}.localhost`;
1513
+ }
1514
+ if (kind === "vnc" && box.portlessVncAlias) {
1515
+ return box.portlessVncUrl ?? `https://${box.portlessVncAlias}.localhost`;
1516
+ }
1517
+ }
1518
+ const port = kind === "vnc" ? CLOUD_VNC_PORT : box.cloud?.webPort ?? CLOUD_WEB_PROXY_PORT;
1519
+ if (backend.signedPreviewUrl) {
1520
+ const ttl = opts2?.ttl ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
1521
+ const signed = await backend.signedPreviewUrl(h, port, ttl);
1522
+ return signed.url;
1523
+ }
1524
+ const p = await backend.previewUrl(h, port);
1525
+ throw new Error(
1526
+ `cloud backend '${backend.name}' does not support signed preview URLs; the standard URL (${p.url}) requires a header token (e.g. x-daytona-preview-token: ${p.token ?? "<unset>"}) that browsers can't attach from a click. Use a programmatic client or wait for backend support.`
1527
+ );
1528
+ },
1529
+ // Cloud checkpoint capability. Backends without `createSnapshot` get a
1530
+ // capability stub whose methods throw — the CLI's `agentbox checkpoint
1531
+ // create` then surfaces a clean "not supported" error rather than a
1532
+ // silent no-op.
1533
+ checkpoint: makeCloudCheckpoint(backend)
1534
+ // stats is provider-optional; cloud backends without a metrics API just
1535
+ // omit it. Backends that have one can decorate the returned provider.
1536
+ };
1537
+ }
1538
+ function makeCloudCheckpoint(backend) {
1539
+ return {
1540
+ async create(box, name) {
1541
+ if (!backend.createSnapshot) {
1542
+ throw new Error(
1543
+ `cloud backend '${backend.name}' doesn't support snapshots \u2014 \`agentbox checkpoint\` unavailable`
1544
+ );
1545
+ }
1546
+ if (!box.projectRoot) {
1547
+ throw new Error(
1548
+ `cloud checkpoint requires the box to have a project root (run \`agentbox checkpoint\` from inside the project)`
1549
+ );
1550
+ }
1551
+ if (!box.cloud?.sandboxId) {
1552
+ throw new Error(`cloud box ${box.name} has no sandboxId \u2014 record is malformed`);
1553
+ }
1554
+ const snapshotName = cloudSnapshotName(box.projectRoot, name);
1555
+ await backend.createSnapshot({ sandboxId: box.cloud.sandboxId }, snapshotName);
1556
+ const info = await writeCloudCheckpointManifest(box.projectRoot, backend.name, name, {
1557
+ snapshotName,
1558
+ sourceBoxId: box.id,
1559
+ sourceBoxName: box.name
1560
+ });
1561
+ return { ref: info.name };
1562
+ },
1563
+ async list(projectRoot) {
1564
+ const entries = await listCloudCheckpoints(projectRoot, backend.name);
1565
+ return entries.map((e) => ({ ref: e.name, createdAt: e.manifest.createdAt }));
1566
+ },
1567
+ async remove(projectRoot, ref) {
1568
+ const entry = await resolveCloudCheckpoint(projectRoot, backend.name, ref);
1569
+ if (!entry) return;
1570
+ if (backend.deleteSnapshot) {
1571
+ try {
1572
+ await backend.deleteSnapshot(entry.manifest.snapshotName);
1573
+ } catch {
1574
+ }
1575
+ }
1576
+ await removeCloudCheckpointDir(projectRoot, backend.name, ref);
1577
+ }
1578
+ };
1579
+ }
1580
+ function renderInnerCommand(kind, opts) {
1581
+ const sessionName = opts?.sessionName ?? defaultSessionName(kind);
1582
+ const fallback = opts?.command ?? defaultCommand(kind, opts);
1583
+ if (kind === "logs") {
1584
+ return fallback;
1585
+ }
1586
+ if (opts?.noTmux) {
1587
+ return fallback;
1588
+ }
1589
+ const sessionQ = shellSingle(sessionName);
1590
+ const cwdQ = shellSingle(CLOUD_WORKSPACE_DIR);
1591
+ const fallbackQ = shellSingle(fallback);
1592
+ const configSnippet = buildTmuxConfigShellSnippet(sessionName);
1593
+ const lines = [
1594
+ `command -v tmux >/dev/null || { echo "tmux not installed in sandbox"; exit 127; }`,
1595
+ `tmux has-session -t ${sessionQ} 2>/dev/null || tmux new-session -d -c ${cwdQ} -s ${sessionQ} ${fallbackQ}`,
1596
+ configSnippet
1597
+ ];
1598
+ if (opts?.detached) return lines.join("; ");
1599
+ return [...lines, `exec tmux attach -t ${sessionQ}`].join("; ");
1600
+ }
1601
+ function defaultSessionName(kind) {
1602
+ switch (kind) {
1603
+ case "shell":
1604
+ return "shell";
1605
+ case "agent":
1606
+ return "agent";
1607
+ case "logs":
1608
+ return "logs";
1609
+ }
1610
+ }
1611
+ function defaultCommand(kind, opts) {
1612
+ switch (kind) {
1613
+ case "shell":
1614
+ return "bash -l";
1615
+ case "agent":
1616
+ return "bash -l";
1617
+ case "logs": {
1618
+ if (!opts?.service) {
1619
+ return 'echo "no service specified \u2014 set BuildAttachOptions.service"';
1620
+ }
1621
+ const tail = opts.tail !== void 0 ? String(opts.tail) : "200";
1622
+ const args = [`--tail ${shellSingle(tail)}`];
1623
+ if (opts.follow !== false) args.push("--follow");
1624
+ return `/usr/local/bin/agentbox-ctl logs ${shellSingle(opts.service)} ${args.join(" ")}`;
1625
+ }
1626
+ }
1627
+ }
1628
+ function shellSingle(s) {
1629
+ return "'" + s.replace(/'/g, "'\\''") + "'";
1630
+ }
1631
+
1632
+ export {
1633
+ ensureAgentVolumesForCloud,
1634
+ seedAgentVolumesIfFresh,
1635
+ agentSpecsForCloud,
1636
+ listCloudCheckpoints,
1637
+ resolveCloudCheckpoint,
1638
+ createCloudProvider
1639
+ };
1640
+ //# sourceMappingURL=chunk-67N47KUS.js.map