@madarco/agentbox 0.13.0 → 0.14.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 (74) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +11 -8
  3. package/dist/{_cloud-attach-HJC672UR.js → _cloud-attach-GUBB5RH2.js} +4 -4
  4. package/dist/{chunk-R5XIDQFR.js → chunk-BKU34KYY.js} +170 -6
  5. package/dist/chunk-BKU34KYY.js.map +1 -0
  6. package/dist/{chunk-QYRK5H6Q.js → chunk-BYCLD6D6.js} +17 -9
  7. package/dist/chunk-BYCLD6D6.js.map +1 -0
  8. package/dist/chunk-LDMYHWUS.js +346 -0
  9. package/dist/chunk-LDMYHWUS.js.map +1 -0
  10. package/dist/{chunk-2LF5YILI.js → chunk-RSKG7AFU.js} +80 -6
  11. package/dist/chunk-RSKG7AFU.js.map +1 -0
  12. package/dist/{chunk-4NQXNQ53.js → chunk-TBSIJVSN.js} +149 -47
  13. package/dist/chunk-TBSIJVSN.js.map +1 -0
  14. package/dist/{chunk-B4QG2MCW.js → chunk-TCS5HXJX.js} +381 -174
  15. package/dist/chunk-TCS5HXJX.js.map +1 -0
  16. package/dist/{chunk-ECLLV5JH.js → chunk-VATTS2MR.js} +156 -5
  17. package/dist/chunk-VATTS2MR.js.map +1 -0
  18. package/dist/{chunk-SNTHHWKY.js → chunk-XKH7NTT7.js} +80 -22
  19. package/dist/chunk-XKH7NTT7.js.map +1 -0
  20. package/dist/dist-34RKQ74M.js +662 -0
  21. package/dist/dist-34RKQ74M.js.map +1 -0
  22. package/dist/{dist-OPIBZ7XM.js → dist-3IMQNTTV.js} +14 -69
  23. package/dist/dist-3IMQNTTV.js.map +1 -0
  24. package/dist/{dist-OG6NW6SM.js → dist-4DPOL5A7.js} +5 -3
  25. package/dist/{dist-JAN5VABY.js → dist-57M6ZA7H.js} +25 -177
  26. package/dist/dist-57M6ZA7H.js.map +1 -0
  27. package/dist/{dist-7KVUIKJX.js → dist-J2IHD5T7.js} +37 -226
  28. package/dist/dist-J2IHD5T7.js.map +1 -0
  29. package/dist/index.js +1376 -1029
  30. package/dist/index.js.map +1 -1
  31. package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
  32. package/package.json +8 -6
  33. package/runtime/docker/Dockerfile.box +21 -26
  34. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +37 -1
  35. package/runtime/docker/packages/ctl/dist/bin.cjs +40 -16
  36. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +17 -6
  37. package/runtime/docker/packages/sandbox-docker/scripts/chromium-resolver +57 -0
  38. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +2 -1
  39. package/runtime/e2b/agentbox-checkpoint-cleanup +52 -0
  40. package/runtime/e2b/agentbox-codex-hooks.json +68 -0
  41. package/runtime/e2b/agentbox-open +28 -0
  42. package/runtime/e2b/agentbox-setup-skill.md +233 -0
  43. package/runtime/e2b/agentbox-vnc-start +102 -0
  44. package/runtime/e2b/attach-helper.cjs +167 -0
  45. package/runtime/e2b/claude-managed-settings.json +116 -0
  46. package/runtime/e2b/ctl.cjs +23864 -0
  47. package/runtime/e2b/custom-system-CLAUDE.md +46 -0
  48. package/runtime/e2b/gh-shim +344 -0
  49. package/runtime/e2b/git-shim +131 -0
  50. package/runtime/e2b/scripts/build-template.sh +295 -0
  51. package/runtime/hetzner/agentbox-setup-skill.md +37 -1
  52. package/runtime/hetzner/agentbox-vnc-start +17 -6
  53. package/runtime/hetzner/claude-managed-settings.json +2 -1
  54. package/runtime/hetzner/ctl.cjs +40 -16
  55. package/runtime/relay/bin.cjs +297 -228
  56. package/runtime/vercel/agentbox-setup-skill.md +37 -1
  57. package/runtime/vercel/agentbox-vnc-start +17 -6
  58. package/runtime/vercel/claude-managed-settings.json +2 -1
  59. package/runtime/vercel/ctl.cjs +40 -16
  60. package/share/agentbox-setup/SKILL.md +37 -1
  61. package/share/host-skills/agentbox-info/SKILL.md +26 -34
  62. package/dist/chunk-2LF5YILI.js.map +0 -1
  63. package/dist/chunk-4NQXNQ53.js.map +0 -1
  64. package/dist/chunk-B4QG2MCW.js.map +0 -1
  65. package/dist/chunk-ECLLV5JH.js.map +0 -1
  66. package/dist/chunk-QYRK5H6Q.js.map +0 -1
  67. package/dist/chunk-R5XIDQFR.js.map +0 -1
  68. package/dist/chunk-SNTHHWKY.js.map +0 -1
  69. package/dist/dist-7KVUIKJX.js.map +0 -1
  70. package/dist/dist-JAN5VABY.js.map +0 -1
  71. package/dist/dist-OPIBZ7XM.js.map +0 -1
  72. /package/dist/{_cloud-attach-HJC672UR.js.map → _cloud-attach-GUBB5RH2.js.map} +0 -0
  73. /package/dist/{dist-OG6NW6SM.js.map → dist-4DPOL5A7.js.map} +0 -0
  74. /package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js.map → prepared-state-MQHD3M5F-Q27AZU53.js.map} +0 -0
@@ -0,0 +1,662 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ RUNTIME_ASSETS,
4
+ Sandbox,
5
+ Template,
6
+ candidatesFor,
7
+ currentE2bBaseFingerprintLive,
8
+ ensureE2bBaseTemplate,
9
+ ensureE2bCredentials,
10
+ ensureE2bEnvLoaded,
11
+ findStagedCliRuntimeRoot,
12
+ maskKey,
13
+ preparedStatePath,
14
+ readE2bCredStatus,
15
+ readPreparedState,
16
+ reloadE2bEnv,
17
+ resolveApiKey,
18
+ resolveRuntimeAssets,
19
+ secretsPath,
20
+ updatePreparedState,
21
+ writePreparedState
22
+ } from "./chunk-LDMYHWUS.js";
23
+ import {
24
+ createCloudProvider,
25
+ currentCloudBaseFingerprint,
26
+ listCloudCheckpoints,
27
+ removeCloudCheckpointDir,
28
+ renderInnerCommand,
29
+ resolveCloudCheckpoint,
30
+ writeCloudCheckpointManifest
31
+ } from "./chunk-TBSIJVSN.js";
32
+ import "./chunk-TCS5HXJX.js";
33
+ import {
34
+ computeContextSha256,
35
+ readCliStamp,
36
+ recordBox
37
+ } from "./chunk-XKH7NTT7.js";
38
+ import "./chunk-G3H2L3O2.js";
39
+
40
+ // ../../packages/sandbox-e2b/dist/index.js
41
+ import { readFile } from "fs/promises";
42
+ import { copyFile, mkdir, mkdtemp, rm } from "fs/promises";
43
+ import { tmpdir } from "os";
44
+ import { dirname, join, resolve } from "path";
45
+ import { existsSync } from "fs";
46
+ import { dirname as dirname2, resolve as resolve2 } from "path";
47
+ import { fileURLToPath } from "url";
48
+ var DEFAULT_BACKOFF = [1e3, 2e3, 4e3];
49
+ var DEFAULT_ATTEMPT_TIMEOUT_MS = 3e4;
50
+ var AttemptTimeoutError = class extends Error {
51
+ constructor(method, ms) {
52
+ super(`e2b ${method}: per-attempt timeout after ${String(ms)}ms`);
53
+ this.name = "AttemptTimeoutError";
54
+ }
55
+ };
56
+ function statusCodeOf(err) {
57
+ if (!err || typeof err !== "object") return void 0;
58
+ for (const key of ["statusCode", "status", "code"]) {
59
+ const v = err[key];
60
+ if (typeof v === "number") return v;
61
+ }
62
+ const resp = err.response;
63
+ if (resp && typeof resp.status === "number") return resp.status;
64
+ return void 0;
65
+ }
66
+ function isRetriable(err, allowAmbiguous) {
67
+ if (err instanceof AttemptTimeoutError) return allowAmbiguous;
68
+ const name = err instanceof Error ? err.name : void 0;
69
+ if (name === "RateLimitError") return true;
70
+ if (name === "SandboxNotFoundError") return false;
71
+ const status = statusCodeOf(err);
72
+ if (status !== void 0) {
73
+ if (status === 429) return true;
74
+ if (status >= 500 && status <= 599) return allowAmbiguous;
75
+ return false;
76
+ }
77
+ if (err && typeof err === "object") {
78
+ const candidates = [err, err.cause];
79
+ for (const c of candidates) {
80
+ if (!c || typeof c !== "object") continue;
81
+ const code = c.code;
82
+ if (code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ECONNABORTED" || code === "EAI_AGAIN" || code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "UND_ERR_SOCKET" || code === "UND_ERR_CONNECT_TIMEOUT") {
83
+ return allowAmbiguous;
84
+ }
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ async function withE2bRetry(opts, fn) {
90
+ const backoff = opts.backoffMs ?? DEFAULT_BACKOFF;
91
+ const maxAttempts = backoff.length + 1;
92
+ const timeoutMs = opts.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS;
93
+ const log = opts.onRetry ?? defaultRetryLog;
94
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
95
+ try {
96
+ return await raceTimeout(fn(), timeoutMs, opts.method);
97
+ } catch (err) {
98
+ const last = attempt === maxAttempts;
99
+ if (last || !isRetriable(err, opts.retryOnAmbiguous)) throw err;
100
+ const delay = backoff[attempt - 1] ?? backoff[backoff.length - 1] ?? 4e3;
101
+ log(
102
+ `e2b ${opts.method}: attempt ${String(attempt)} failed (${errorSummary(err)}); retrying in ${String(delay)}ms`
103
+ );
104
+ await sleep(delay);
105
+ }
106
+ }
107
+ throw new Error(`withE2bRetry: exhausted attempts for ${opts.method}`);
108
+ }
109
+ function defaultRetryLog(line) {
110
+ process.stderr.write(`
111
+ [e2b-retry] ${line}
112
+ `);
113
+ }
114
+ function sleep(ms) {
115
+ return new Promise((r) => setTimeout(r, ms));
116
+ }
117
+ async function raceTimeout(p, ms, method) {
118
+ let timer;
119
+ try {
120
+ return await Promise.race([
121
+ p,
122
+ new Promise((_resolve, reject) => {
123
+ timer = setTimeout(() => reject(new AttemptTimeoutError(method, ms)), ms);
124
+ })
125
+ ]);
126
+ } finally {
127
+ if (timer !== void 0) clearTimeout(timer);
128
+ }
129
+ }
130
+ function errorSummary(err) {
131
+ if (err instanceof Error) {
132
+ const status = statusCodeOf(err);
133
+ return status !== void 0 ? `${err.name}(${String(status)}): ${truncate(err.message)}` : `${err.name}: ${truncate(err.message)}`;
134
+ }
135
+ return truncate(String(err));
136
+ }
137
+ function truncate(s, max = 160) {
138
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
139
+ }
140
+ var DEFAULT_BOX_IMAGE_REF = "agentbox/box:dev";
141
+ var BOX_USER = "vscode";
142
+ var BOX_OWNER = "vscode:vscode";
143
+ var DEFAULT_E2B_DOMAIN = "e2b.app";
144
+ var DEFAULT_TIMEOUT_MS = 45 * 6e4;
145
+ var E2B_WEB_PORT = 8080;
146
+ function shq(s) {
147
+ return "'" + s.replace(/'/g, "'\\''") + "'";
148
+ }
149
+ function bufferToArrayBuffer(b) {
150
+ const ab = new ArrayBuffer(b.byteLength);
151
+ new Uint8Array(ab).set(b);
152
+ return ab;
153
+ }
154
+ function mapState(s) {
155
+ switch (s) {
156
+ case "running":
157
+ return "running";
158
+ case "paused":
159
+ return "paused";
160
+ default:
161
+ return "missing";
162
+ }
163
+ }
164
+ function isNotFound(err) {
165
+ if (!err || typeof err !== "object") return false;
166
+ const name = err instanceof Error ? err.name : "";
167
+ if (name === "SandboxNotFoundError" || name === "NotFoundError") return true;
168
+ const status = err.statusCode ?? err.status;
169
+ return status === 404;
170
+ }
171
+ function safeMetadataName(name) {
172
+ return name.replace(/[\u0000-\u001f]/g, "").slice(0, 200);
173
+ }
174
+ var e2bBackend = {
175
+ name: "e2b",
176
+ // The cloud scaffold's WebProxy binds whatever port we expose here, and
177
+ // `agentbox url --kind=web` resolves via `getHost(port)`. 8080 matches the
178
+ // non-privileged convention vercel uses — `getHost` accepts any port, but
179
+ // staying on 8080 keeps the in-box ctl flag (AGENTBOX_WEB_PROXY_PORT)
180
+ // identical across cloud providers.
181
+ webProxyPort: E2B_WEB_PORT,
182
+ async provision(req) {
183
+ const apiKey = resolveApiKey();
184
+ const log = req.onLog ?? (() => {
185
+ });
186
+ if (req.snapshot === void 0) {
187
+ ensureE2bBaseTemplate();
188
+ }
189
+ const template = req.snapshot ?? readPreparedState().base?.templateId;
190
+ if (!template) {
191
+ throw new Error(
192
+ "e2b provision: no template available \u2014 `agentbox prepare --provider e2b` must run first"
193
+ );
194
+ }
195
+ const sb = await withE2bRetry(
196
+ { method: "provision", retryOnAmbiguous: false, attemptTimeoutMs: 3e5, backoffMs: [] },
197
+ async () => Sandbox.create({
198
+ apiKey,
199
+ template,
200
+ // Friendly name (so prune can see it) + the 'agentbox' marker so
201
+ // `list()` can filter out sandboxes provisioned by other tooling.
202
+ metadata: { agentbox: "true", "agentbox.name": safeMetadataName(req.name), name: safeMetadataName(req.name) },
203
+ envs: req.env,
204
+ timeoutMs: req.timeoutMs ?? DEFAULT_TIMEOUT_MS
205
+ })
206
+ );
207
+ log(`e2b: created sandbox ${sb.sandboxId} (template ${template})`);
208
+ return { sandboxId: sb.sandboxId };
209
+ },
210
+ async get(sandboxId) {
211
+ const apiKey = resolveApiKey();
212
+ return withE2bRetry({ method: "get", retryOnAmbiguous: true }, async () => {
213
+ try {
214
+ await Sandbox.getInfo(sandboxId, { apiKey });
215
+ return { sandboxId };
216
+ } catch (err) {
217
+ if (isNotFound(err)) return null;
218
+ throw err;
219
+ }
220
+ });
221
+ },
222
+ async list() {
223
+ const apiKey = resolveApiKey();
224
+ return withE2bRetry({ method: "list", retryOnAmbiguous: true }, async () => {
225
+ const summaries = [];
226
+ for (const state of ["running", "paused"]) {
227
+ const paginator = Sandbox.list({ apiKey, query: { state: [state] } });
228
+ while (paginator.hasNext) {
229
+ const page = await paginator.nextItems();
230
+ for (const info of page) {
231
+ if (info.metadata?.["agentbox"] !== "true") continue;
232
+ const friendly = info.metadata?.["agentbox.name"] ?? info.metadata?.["name"];
233
+ const summary = { sandboxId: info.sandboxId, state };
234
+ if (friendly) summary.name = friendly;
235
+ const startedAt = info.startedAt;
236
+ if (startedAt instanceof Date) summary.createdAt = startedAt.toISOString();
237
+ summaries.push(summary);
238
+ }
239
+ }
240
+ }
241
+ return summaries;
242
+ });
243
+ },
244
+ // E2B has no separate stop primitive — sandboxes are either running or
245
+ // paused. start is therefore a connect-and-resume (auto-resume inside
246
+ // Sandbox.connect handles a paused box transparently).
247
+ async start(h) {
248
+ const apiKey = resolveApiKey();
249
+ await withE2bRetry(
250
+ { method: "start", retryOnAmbiguous: true, attemptTimeoutMs: 12e4 },
251
+ async () => {
252
+ await Sandbox.connect(h.sandboxId, { apiKey });
253
+ }
254
+ );
255
+ },
256
+ // stop ≡ pause on E2B (the pause IS the cold-storage state).
257
+ async stop(h) {
258
+ await this.pause(h);
259
+ },
260
+ async pause(h) {
261
+ const apiKey = resolveApiKey();
262
+ await withE2bRetry(
263
+ { method: "pause", retryOnAmbiguous: true, attemptTimeoutMs: 12e4 },
264
+ async () => {
265
+ await Sandbox.pause(h.sandboxId, { apiKey });
266
+ }
267
+ );
268
+ },
269
+ async resume(h) {
270
+ await this.start(h);
271
+ },
272
+ async destroy(h) {
273
+ const apiKey = resolveApiKey();
274
+ await withE2bRetry(
275
+ { method: "destroy", retryOnAmbiguous: true, attemptTimeoutMs: 12e4 },
276
+ async () => {
277
+ try {
278
+ await Sandbox.kill(h.sandboxId, { apiKey });
279
+ } catch (err) {
280
+ if (isNotFound(err)) return;
281
+ throw err;
282
+ }
283
+ }
284
+ );
285
+ },
286
+ async state(h) {
287
+ const apiKey = resolveApiKey();
288
+ return withE2bRetry({ method: "state", retryOnAmbiguous: true }, async () => {
289
+ try {
290
+ const info = await Sandbox.getInfo(h.sandboxId, { apiKey });
291
+ return mapState(info.state);
292
+ } catch (err) {
293
+ if (isNotFound(err)) return "missing";
294
+ throw err;
295
+ }
296
+ });
297
+ },
298
+ async exec(h, cmd, opts) {
299
+ const apiKey = resolveApiKey();
300
+ const timeoutMs = opts?.attemptTimeoutMs ?? 3e5;
301
+ return withE2bRetry(
302
+ {
303
+ method: "exec",
304
+ retryOnAmbiguous: opts?.noRetry ? false : true,
305
+ attemptTimeoutMs: timeoutMs,
306
+ backoffMs: opts?.noRetry ? [] : void 0
307
+ },
308
+ async () => {
309
+ const sb = await Sandbox.connect(h.sandboxId, { apiKey });
310
+ const user = opts?.user ?? BOX_USER;
311
+ try {
312
+ const r = await sb.commands.run(cmd, {
313
+ ...opts?.cwd !== void 0 ? { cwd: opts.cwd } : {},
314
+ ...opts?.env !== void 0 ? { envs: opts.env } : {},
315
+ user,
316
+ timeoutMs
317
+ });
318
+ return { exitCode: r.exitCode, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
319
+ } catch (err) {
320
+ if (err instanceof Error && err.name === "CommandExitError") {
321
+ const ce = err;
322
+ return { exitCode: ce.exitCode, stdout: ce.stdout ?? "", stderr: ce.stderr ?? "" };
323
+ }
324
+ throw err;
325
+ }
326
+ }
327
+ );
328
+ },
329
+ async uploadFile(h, localPath, remotePath) {
330
+ const apiKey = resolveApiKey();
331
+ await withE2bRetry(
332
+ { method: "uploadFile", retryOnAmbiguous: true, attemptTimeoutMs: 3e5 },
333
+ async () => {
334
+ const data = await readFile(localPath);
335
+ const sb = await Sandbox.connect(h.sandboxId, { apiKey });
336
+ await sb.files.write([{ path: remotePath, data: bufferToArrayBuffer(data) }]);
337
+ try {
338
+ await sb.commands.run(`sudo -n chown ${BOX_OWNER} ${shq(remotePath)}`, {
339
+ user: "root",
340
+ timeoutMs: 1e4
341
+ });
342
+ } catch {
343
+ }
344
+ }
345
+ );
346
+ },
347
+ async downloadFile(h, remotePath, localPath) {
348
+ const apiKey = resolveApiKey();
349
+ await withE2bRetry(
350
+ { method: "downloadFile", retryOnAmbiguous: true, attemptTimeoutMs: 3e5 },
351
+ async () => {
352
+ const sb = await Sandbox.connect(h.sandboxId, { apiKey });
353
+ const bytes = await sb.files.read(remotePath, { format: "bytes" });
354
+ const { writeFile } = await import("fs/promises");
355
+ await writeFile(localPath, Buffer.from(bytes));
356
+ }
357
+ );
358
+ },
359
+ async listFiles(h, remoteDir) {
360
+ const apiKey = resolveApiKey();
361
+ return withE2bRetry({ method: "listFiles", retryOnAmbiguous: true }, async () => {
362
+ const sb = await Sandbox.connect(h.sandboxId, { apiKey });
363
+ const entries = await sb.files.list(remoteDir);
364
+ return entries.map((e) => ({ name: e.name, isDir: e.type === "dir" }));
365
+ });
366
+ },
367
+ async previewUrl(h, port) {
368
+ const domain = process.env.E2B_DOMAIN ?? DEFAULT_E2B_DOMAIN;
369
+ return { url: `https://${String(port)}-${h.sandboxId}.${domain}`, token: void 0 };
370
+ },
371
+ // Fewer params than the interface's (h, port, expiresInSeconds) is fine —
372
+ // E2B preview URLs are already public + browser-usable; no per-URL TTL.
373
+ async signedPreviewUrl(h, port) {
374
+ return this.previewUrl(h, port);
375
+ },
376
+ /**
377
+ * Probe whether a snapshot (i.e. a template id) is still bootable. E2B's
378
+ * snapshot ids look like `template-id:tag` or `team-slug/name:tag` — both
379
+ * accepted by `Template.exists(name)`. Returns false on any lookup failure
380
+ * (treated by the cloud-provider as "gone" so it falls back to a from-base
381
+ * boot rather than 410ing the user).
382
+ */
383
+ async snapshotExists(snapshotName2) {
384
+ const apiKey = resolveApiKey();
385
+ return withE2bRetry({ method: "snapshotExists", retryOnAmbiguous: true }, async () => {
386
+ try {
387
+ return await Template.exists(snapshotName2, { apiKey });
388
+ } catch {
389
+ return false;
390
+ }
391
+ });
392
+ }
393
+ };
394
+ var TEMPLATE_NAME = "agentbox-base:latest";
395
+ var DEFAULT_TAG = "latest";
396
+ var DEFAULT_CPU = 2;
397
+ var DEFAULT_MEMORY_MB = 4096;
398
+ async function prepareE2b(opts = {}) {
399
+ await ensureE2bCredentials();
400
+ const apiKey = resolveApiKey();
401
+ const log = opts.onLog ?? (() => {
402
+ });
403
+ const progress = (s) => log(`prepare-e2b: ${s}`);
404
+ const assets = resolveRuntimeAssets({
405
+ cliRuntimeRoot: opts.cliRuntimeRoot ?? findStagedCliRuntimeRoot(),
406
+ repoRoot: opts.repoRoot
407
+ });
408
+ const contextSha = await computeContextSha256(
409
+ assets.map((a) => ({ rel: a.name, abs: a.localPath }))
410
+ );
411
+ const existing = readPreparedState();
412
+ if (!opts.force && existing.base) {
413
+ if (existing.base.contextSha256 === contextSha) {
414
+ const stillThere = await templateExists(existing.base.templateId, apiKey);
415
+ if (stillThere) {
416
+ progress(
417
+ `template ${existing.base.templateId} already exists (fingerprint ${contextSha.slice(0, 12)} matches); skipping (pass --force to rebuild)`
418
+ );
419
+ return { snapshotName: existing.base.templateId };
420
+ }
421
+ progress(`recorded template ${existing.base.templateId} is gone on E2B; rebuilding`);
422
+ } else {
423
+ progress(
424
+ `build context changed (was ${existing.base.contextSha256?.slice(0, 12) ?? "<none>"}, now ${contextSha.slice(0, 12)}); rebuilding`
425
+ );
426
+ }
427
+ }
428
+ const contextDir = await mkdtemp(join(tmpdir(), "agentbox-e2b-build-"));
429
+ try {
430
+ progress(`staging build context at ${contextDir}`);
431
+ await stageAssetsInto(contextDir, assets);
432
+ progress("assembling template build (fromBaseImage + asset copy + runCmd)");
433
+ const template = Template({ fileContextPath: contextDir }).fromBaseImage();
434
+ for (const a of assets) {
435
+ progress(` copy ${a.name} -> ${a.remotePath}`);
436
+ template.copy(a.name, a.remotePath, {
437
+ forceUpload: true,
438
+ mode: a.remoteMode,
439
+ user: "root"
440
+ });
441
+ }
442
+ template.runCmd("bash /tmp/agentbox-build-template.sh 2>&1", { user: "root" });
443
+ const finalTemplate = template.setReadyCmd(
444
+ "test -x /usr/local/bin/agentbox-ctl"
445
+ );
446
+ const cpuCount = opts.cpuCount ?? DEFAULT_CPU;
447
+ const memoryMB = opts.memoryMB ?? DEFAULT_MEMORY_MB;
448
+ progress(
449
+ `running Template.build('${TEMPLATE_NAME}', { cpuCount: ${String(cpuCount)}, memoryMB: ${String(memoryMB)} })`
450
+ );
451
+ const info = await Template.build(finalTemplate, TEMPLATE_NAME, {
452
+ apiKey,
453
+ cpuCount,
454
+ memoryMB,
455
+ onBuildLogs: (entry) => {
456
+ log(`[build] ${formatBuildLog(entry)}`);
457
+ }
458
+ });
459
+ progress(`template built: id=${info.templateId} build=${info.buildId} name=${info.name}`);
460
+ const tag = info.tags?.[0] ?? DEFAULT_TAG;
461
+ const cliStamp = readCliStamp();
462
+ const taggedId = `${info.templateId}:${tag}`;
463
+ writePreparedState({
464
+ schema: 1,
465
+ base: {
466
+ templateId: taggedId,
467
+ // info.name is the full `name:tag` pair Template.build() was called
468
+ // with (e.g. `agentbox-base:latest`). Earlier code re-appended `:${tag}`
469
+ // and produced `agentbox-base:latest:latest` in the status display.
470
+ templateName: info.name,
471
+ contextSha256: contextSha,
472
+ cliVersion: cliStamp.cliVersion,
473
+ cliCommit: cliStamp.cliCommit,
474
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
475
+ }
476
+ });
477
+ progress(`wrote ${preparedStatePath()}`);
478
+ progress(`prepare complete \u2014 base template ${taggedId}`);
479
+ return { snapshotName: taggedId };
480
+ } finally {
481
+ await rm(contextDir, { recursive: true, force: true }).catch(() => {
482
+ });
483
+ }
484
+ }
485
+ async function stageAssetsInto(contextDir, assets) {
486
+ for (const a of assets) {
487
+ const dest = resolve(contextDir, a.name);
488
+ await mkdir(dirname(dest), { recursive: true });
489
+ await copyFile(a.localPath, dest);
490
+ }
491
+ }
492
+ async function templateExists(name, apiKey) {
493
+ try {
494
+ return await Template.exists(name, { apiKey });
495
+ } catch {
496
+ return false;
497
+ }
498
+ }
499
+ function formatBuildLog(entry) {
500
+ const raw = typeof entry.message === "string" ? entry.message : entry.toString();
501
+ const cleaned = raw.replace(/\r?\n+$/, "");
502
+ return cleaned.length > 200 ? cleaned.slice(0, 200) + "\u2026" : cleaned;
503
+ }
504
+ var prepareE2bProvider = (req) => prepareE2b({
505
+ name: req.name,
506
+ hostWorkspace: req.hostWorkspace ?? process.cwd(),
507
+ force: req.force,
508
+ onLog: req.onLog
509
+ });
510
+ var SELF = dirname2(fileURLToPath(import.meta.url));
511
+ function resolveAttachHelperPath() {
512
+ const candidates = [
513
+ // dev: dist/index.js sibling
514
+ resolve2(SELF, "attach-helper.cjs"),
515
+ // dev: src compiled to dist/, while index.ts is in src/
516
+ resolve2(SELF, "..", "dist", "attach-helper.cjs"),
517
+ // staged CLI: apps/cli/runtime/e2b/attach-helper.cjs
518
+ resolve2(SELF, "..", "runtime", "e2b", "attach-helper.cjs"),
519
+ resolve2(SELF, "..", "..", "runtime", "e2b", "attach-helper.cjs")
520
+ ];
521
+ for (const p of candidates) {
522
+ if (existsSync(p)) return p;
523
+ }
524
+ return candidates[0];
525
+ }
526
+ async function buildE2bAttach(box, kind, opts) {
527
+ const sandboxId = box.cloud?.sandboxId;
528
+ if (!sandboxId) {
529
+ throw new Error(`e2b box ${box.name} has no sandboxId \u2014 record is malformed`);
530
+ }
531
+ const helper = resolveAttachHelperPath();
532
+ if (!existsSync(helper)) {
533
+ throw new Error(
534
+ `e2b attach helper not found at ${helper} \u2014 rebuild the CLI (\`pnpm -w build\`) so packages/sandbox-e2b/dist/attach-helper.cjs is generated.`
535
+ );
536
+ }
537
+ const apiKey = resolveApiKey();
538
+ const inner = renderInnerCommand(kind, opts);
539
+ const argv = [
540
+ process.execPath,
541
+ helper,
542
+ "--sandbox-id",
543
+ sandboxId,
544
+ "--user",
545
+ "vscode"
546
+ ];
547
+ return {
548
+ argv,
549
+ env: {
550
+ E2B_API_KEY: apiKey,
551
+ AGENTBOX_E2B_INNER_CMD: inner
552
+ }
553
+ };
554
+ }
555
+ var BACKEND_NAME = "e2b";
556
+ var cloudProvider = createCloudProvider(e2bBackend, {
557
+ // E2B applies resources at the template level (Template.build({ cpuCount,
558
+ // memoryMB }) — `prepare` sets these). The numbers below are advisory
559
+ // metadata for BoxRecord stats / the dashboard pane; per-create overrides
560
+ // aren't honored by the SDK.
561
+ defaultResources: { cpu: 2, memory: 4, disk: 8 },
562
+ launchDockerd: false
563
+ });
564
+ async function createE2bSnapshot(sandboxId, name) {
565
+ const apiKey = resolveApiKey();
566
+ return withE2bRetry(
567
+ { method: "createSnapshot", retryOnAmbiguous: false, attemptTimeoutMs: 9e5, backoffMs: [] },
568
+ async () => {
569
+ const info = await Sandbox.createSnapshot(sandboxId, { apiKey, name });
570
+ return info.snapshotId;
571
+ }
572
+ );
573
+ }
574
+ async function deleteE2bSnapshot(snapshotId) {
575
+ const apiKey = resolveApiKey();
576
+ await withE2bRetry({ method: "deleteSnapshot", retryOnAmbiguous: true }, async () => {
577
+ try {
578
+ await Sandbox.deleteSnapshot(snapshotId, { apiKey });
579
+ } catch (err) {
580
+ const msg = err instanceof Error ? err.message : String(err);
581
+ if (/not.?found|404/i.test(msg)) return;
582
+ throw err;
583
+ }
584
+ });
585
+ }
586
+ function snapshotName(boxName, checkpointName) {
587
+ const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9.-]+/g, "-").replace(/^-+|-+$/g, "");
588
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").replace("Z", "");
589
+ return `agentbox-${sanitize(boxName)}-${sanitize(checkpointName)}-${sanitize(ts)}`;
590
+ }
591
+ var e2bCheckpoint = {
592
+ async create(box, name) {
593
+ if (!box.projectRoot) {
594
+ throw new Error(
595
+ "cloud checkpoint requires the box to have a project root (run `agentbox checkpoint` from inside the project)"
596
+ );
597
+ }
598
+ if (!box.cloud?.sandboxId) {
599
+ throw new Error(`e2b box ${box.name} has no sandboxId \u2014 record is malformed`);
600
+ }
601
+ const e2bSnapName = snapshotName(box.name, name);
602
+ const snapshotId = await createE2bSnapshot(box.cloud.sandboxId, e2bSnapName);
603
+ try {
604
+ await recordBox({ ...box, cloud: { ...box.cloud, lastState: "paused" } });
605
+ } catch {
606
+ }
607
+ const info = await writeCloudCheckpointManifest(box.projectRoot, BACKEND_NAME, name, {
608
+ snapshotName: snapshotId,
609
+ sourceBoxId: box.id,
610
+ sourceBoxName: box.name,
611
+ baseProvider: BACKEND_NAME,
612
+ baseFingerprint: currentCloudBaseFingerprint(BACKEND_NAME),
613
+ cliVersion: readCliStamp().cliVersion
614
+ });
615
+ return { ref: info.name };
616
+ },
617
+ async list(projectRoot) {
618
+ const entries = await listCloudCheckpoints(projectRoot, BACKEND_NAME);
619
+ return entries.map((e) => ({ ref: e.name, createdAt: e.manifest.createdAt }));
620
+ },
621
+ async remove(projectRoot, ref) {
622
+ const entry = await resolveCloudCheckpoint(projectRoot, BACKEND_NAME, ref);
623
+ if (!entry) return;
624
+ try {
625
+ await deleteE2bSnapshot(entry.manifest.snapshotName);
626
+ } catch {
627
+ }
628
+ await removeCloudCheckpointDir(projectRoot, BACKEND_NAME, ref);
629
+ }
630
+ };
631
+ var e2bProvider = {
632
+ ...cloudProvider,
633
+ prepare: prepareE2bProvider,
634
+ buildAttach: buildE2bAttach,
635
+ checkpoint: e2bCheckpoint,
636
+ baseFingerprint: () => currentE2bBaseFingerprintLive()
637
+ };
638
+ export {
639
+ DEFAULT_BOX_IMAGE_REF,
640
+ RUNTIME_ASSETS,
641
+ buildE2bAttach,
642
+ candidatesFor,
643
+ currentE2bBaseFingerprintLive,
644
+ e2bBackend,
645
+ e2bProvider,
646
+ ensureE2bBaseTemplate,
647
+ ensureE2bCredentials,
648
+ ensureE2bEnvLoaded,
649
+ findStagedCliRuntimeRoot,
650
+ maskKey,
651
+ prepareE2b,
652
+ prepareE2bProvider,
653
+ preparedStatePath,
654
+ readE2bCredStatus,
655
+ readPreparedState,
656
+ reloadE2bEnv,
657
+ resolveRuntimeAssets,
658
+ secretsPath,
659
+ updatePreparedState,
660
+ writePreparedState
661
+ };
662
+ //# sourceMappingURL=dist-34RKQ74M.js.map