@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.
- package/CHANGELOG.md +65 -0
- package/README.md +11 -8
- package/dist/{_cloud-attach-HJC672UR.js → _cloud-attach-GUBB5RH2.js} +4 -4
- package/dist/{chunk-R5XIDQFR.js → chunk-BKU34KYY.js} +170 -6
- package/dist/chunk-BKU34KYY.js.map +1 -0
- package/dist/{chunk-QYRK5H6Q.js → chunk-BYCLD6D6.js} +17 -9
- package/dist/chunk-BYCLD6D6.js.map +1 -0
- package/dist/chunk-LDMYHWUS.js +346 -0
- package/dist/chunk-LDMYHWUS.js.map +1 -0
- package/dist/{chunk-2LF5YILI.js → chunk-RSKG7AFU.js} +80 -6
- package/dist/chunk-RSKG7AFU.js.map +1 -0
- package/dist/{chunk-4NQXNQ53.js → chunk-TBSIJVSN.js} +149 -47
- package/dist/chunk-TBSIJVSN.js.map +1 -0
- package/dist/{chunk-B4QG2MCW.js → chunk-TCS5HXJX.js} +381 -174
- package/dist/chunk-TCS5HXJX.js.map +1 -0
- package/dist/{chunk-ECLLV5JH.js → chunk-VATTS2MR.js} +156 -5
- package/dist/chunk-VATTS2MR.js.map +1 -0
- package/dist/{chunk-SNTHHWKY.js → chunk-XKH7NTT7.js} +80 -22
- package/dist/chunk-XKH7NTT7.js.map +1 -0
- package/dist/dist-34RKQ74M.js +662 -0
- package/dist/dist-34RKQ74M.js.map +1 -0
- package/dist/{dist-OPIBZ7XM.js → dist-3IMQNTTV.js} +14 -69
- package/dist/dist-3IMQNTTV.js.map +1 -0
- package/dist/{dist-OG6NW6SM.js → dist-4DPOL5A7.js} +5 -3
- package/dist/{dist-JAN5VABY.js → dist-57M6ZA7H.js} +25 -177
- package/dist/dist-57M6ZA7H.js.map +1 -0
- package/dist/{dist-7KVUIKJX.js → dist-J2IHD5T7.js} +37 -226
- package/dist/dist-J2IHD5T7.js.map +1 -0
- package/dist/index.js +1376 -1029
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
- package/package.json +8 -6
- package/runtime/docker/Dockerfile.box +21 -26
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +37 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +40 -16
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +17 -6
- package/runtime/docker/packages/sandbox-docker/scripts/chromium-resolver +57 -0
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +2 -1
- package/runtime/e2b/agentbox-checkpoint-cleanup +52 -0
- package/runtime/e2b/agentbox-codex-hooks.json +68 -0
- package/runtime/e2b/agentbox-open +28 -0
- package/runtime/e2b/agentbox-setup-skill.md +233 -0
- package/runtime/e2b/agentbox-vnc-start +102 -0
- package/runtime/e2b/attach-helper.cjs +167 -0
- package/runtime/e2b/claude-managed-settings.json +116 -0
- package/runtime/e2b/ctl.cjs +23864 -0
- package/runtime/e2b/custom-system-CLAUDE.md +46 -0
- package/runtime/e2b/gh-shim +344 -0
- package/runtime/e2b/git-shim +131 -0
- package/runtime/e2b/scripts/build-template.sh +295 -0
- package/runtime/hetzner/agentbox-setup-skill.md +37 -1
- package/runtime/hetzner/agentbox-vnc-start +17 -6
- package/runtime/hetzner/claude-managed-settings.json +2 -1
- package/runtime/hetzner/ctl.cjs +40 -16
- package/runtime/relay/bin.cjs +297 -228
- package/runtime/vercel/agentbox-setup-skill.md +37 -1
- package/runtime/vercel/agentbox-vnc-start +17 -6
- package/runtime/vercel/claude-managed-settings.json +2 -1
- package/runtime/vercel/ctl.cjs +40 -16
- package/share/agentbox-setup/SKILL.md +37 -1
- package/share/host-skills/agentbox-info/SKILL.md +26 -34
- package/dist/chunk-2LF5YILI.js.map +0 -1
- package/dist/chunk-4NQXNQ53.js.map +0 -1
- package/dist/chunk-B4QG2MCW.js.map +0 -1
- package/dist/chunk-ECLLV5JH.js.map +0 -1
- package/dist/chunk-QYRK5H6Q.js.map +0 -1
- package/dist/chunk-R5XIDQFR.js.map +0 -1
- package/dist/chunk-SNTHHWKY.js.map +0 -1
- package/dist/dist-7KVUIKJX.js.map +0 -1
- package/dist/dist-JAN5VABY.js.map +0 -1
- package/dist/dist-OPIBZ7XM.js.map +0 -1
- /package/dist/{_cloud-attach-HJC672UR.js.map → _cloud-attach-GUBB5RH2.js.map} +0 -0
- /package/dist/{dist-OG6NW6SM.js.map → dist-4DPOL5A7.js.map} +0 -0
- /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
|