@claxedo/sandbox-manager 0.6.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -15
- package/dist/checkpoint-manager.d.ts +47 -0
- package/dist/checkpoint-manager.mjs +116 -0
- package/dist/daytona-allow-list.d.ts +11 -0
- package/dist/daytona-allow-list.mjs +5 -1
- package/dist/driver-catalog.d.ts +5 -52
- package/dist/driver-catalog.mjs +263 -121
- package/dist/drivers/box.mjs +309 -39
- package/dist/drivers/cloudflare.d.ts +18 -7
- package/dist/drivers/cloudflare.mjs +423 -34
- package/dist/drivers/daytona.d.ts +82 -13
- package/dist/drivers/daytona.mjs +495 -45
- package/dist/drivers/docker.mjs +284 -38
- package/dist/drivers/exe.d.ts +26 -0
- package/dist/drivers/exe.mjs +492 -0
- package/dist/drivers/fetch-bridge.mjs +29 -5
- package/dist/drivers/modal.d.ts +2 -5
- package/dist/drivers/modal.mjs +296 -46
- package/dist/drivers/transient-error.d.ts +11 -0
- package/dist/drivers/vercel.d.ts +27 -11
- package/dist/drivers/vercel.mjs +339 -59
- package/dist/hosted-network-policy.d.ts +87 -0
- package/dist/hosted-network-policy.mjs +61 -0
- package/dist/image-name.mjs +34 -0
- package/dist/image.d.ts +18 -3
- package/dist/image.mjs +47 -32
- package/dist/index.d.ts +281 -17
- package/dist/index.mjs +393 -26
- package/dist/json.d.ts +13 -0
- package/dist/lease-policy.d.ts +6 -3
- package/dist/lease-policy.mjs +213 -54
- package/dist/lease-types.d.ts +17 -3
- package/dist/runtime-env.d.ts +12 -1
- package/dist/runtime-env.mjs +14 -0
- package/dist/runtime-version.mjs +1 -9
- package/dist/service-url-exposure.d.ts +1 -2
- package/dist/service-url-exposure.mjs +1 -17
- package/dist/stores/memory.mjs +39 -4
- package/package.json +67 -51
- package/dist/drivers/cloudflare-egress.d.ts +0 -40
- package/dist/drivers/cloudflare-egress.mjs +0 -113
package/dist/drivers/box.mjs
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
+
// ../claxedo-helpers/dist/string.mjs
|
|
2
|
+
function isString(value) {
|
|
3
|
+
return typeof value === "string";
|
|
4
|
+
}
|
|
5
|
+
function trimToUndefined(value) {
|
|
6
|
+
if (!isString(value))
|
|
7
|
+
return;
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
1
12
|
// src/runtime-env.ts
|
|
13
|
+
function workspaceRuntimeBootEnv(input) {
|
|
14
|
+
const env = {
|
|
15
|
+
...workspaceRuntimeTargetEnv(input),
|
|
16
|
+
...workspaceRuntimeSourceEnv(input),
|
|
17
|
+
...input.env
|
|
18
|
+
};
|
|
19
|
+
if (input.runner) env.WORKSPACE_RUNTIME_RUNNER = input.runner;
|
|
20
|
+
if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
|
|
21
|
+
if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
|
|
22
|
+
if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
|
|
23
|
+
if (input.controlEnv?.sessionAuthorityUrl) env.WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL = input.controlEnv.sessionAuthorityUrl;
|
|
24
|
+
return env;
|
|
25
|
+
}
|
|
2
26
|
function workspaceRuntimeTargetEnv(input) {
|
|
3
27
|
return {
|
|
4
28
|
WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
|
|
@@ -25,21 +49,10 @@ function shell(input) {
|
|
|
25
49
|
// src/constants.ts
|
|
26
50
|
var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
|
|
27
51
|
|
|
28
|
-
// src/image.ts
|
|
29
|
-
import { Image, DaytonaNotFoundError } from "@daytona/sdk";
|
|
30
|
-
|
|
31
52
|
// src/runtime-version.ts
|
|
32
53
|
var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
33
|
-
var versionCache;
|
|
34
54
|
function workspaceRuntimeVersion() {
|
|
35
|
-
|
|
36
|
-
const envVersion = process.env.WORKSPACE_RUNTIME_VERSION;
|
|
37
|
-
if (envVersion) {
|
|
38
|
-
versionCache = envVersion;
|
|
39
|
-
return versionCache;
|
|
40
|
-
}
|
|
41
|
-
versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
42
|
-
return versionCache;
|
|
55
|
+
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
// src/image-name.ts
|
|
@@ -63,11 +76,256 @@ function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env =
|
|
|
63
76
|
return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
|
|
64
77
|
}
|
|
65
78
|
|
|
79
|
+
// ../claxedo-helpers/dist/guards.mjs
|
|
80
|
+
function isRecord(value) {
|
|
81
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
82
|
+
}
|
|
83
|
+
function asRecord(value) {
|
|
84
|
+
return isRecord(value) ? value : void 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/json.ts
|
|
88
|
+
function text(input) {
|
|
89
|
+
return typeof input === "string" && input.length > 0 ? input : void 0;
|
|
90
|
+
}
|
|
91
|
+
function num(input) {
|
|
92
|
+
return typeof input === "number" ? input : void 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
// src/image.ts
|
|
67
96
|
var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
|
|
68
97
|
var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
|
|
69
98
|
var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
|
|
70
99
|
|
|
100
|
+
// src/hosted-network-policy.ts
|
|
101
|
+
var SANDBOX_MODEL_PROVIDER_HOSTS = [
|
|
102
|
+
"api.anthropic.com",
|
|
103
|
+
"api.openai.com",
|
|
104
|
+
"generativelanguage.googleapis.com",
|
|
105
|
+
"openrouter.ai",
|
|
106
|
+
// Model catalog + engine config, fetched on runner startup.
|
|
107
|
+
"models.dev",
|
|
108
|
+
"opencode.ai"
|
|
109
|
+
];
|
|
110
|
+
var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
|
|
111
|
+
"registry.npmjs.org",
|
|
112
|
+
"nodejs.org",
|
|
113
|
+
"pypi.org",
|
|
114
|
+
"files.pythonhosted.org",
|
|
115
|
+
"crates.io",
|
|
116
|
+
"static.crates.io",
|
|
117
|
+
"index.crates.io",
|
|
118
|
+
"proxy.golang.org",
|
|
119
|
+
"sum.golang.org"
|
|
120
|
+
];
|
|
121
|
+
var SANDBOX_BASELINE_EGRESS_HOSTS = [
|
|
122
|
+
...SANDBOX_MODEL_PROVIDER_HOSTS,
|
|
123
|
+
...SANDBOX_PACKAGE_REGISTRY_HOSTS
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// ../sandbox-contract/dist/index.mjs
|
|
127
|
+
var sandboxDriverCredentialFields = {
|
|
128
|
+
exe: [{ key: "api_token", label: "API Token", secret: true }],
|
|
129
|
+
daytona: [{ key: "api_key", label: "API Key", secret: true }],
|
|
130
|
+
modal: [
|
|
131
|
+
{ key: "token_id", label: "Token ID", secret: true },
|
|
132
|
+
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
133
|
+
],
|
|
134
|
+
vercel: [
|
|
135
|
+
{ key: "access_token", label: "Access Token", secret: true },
|
|
136
|
+
{ key: "team_id", label: "Team ID" },
|
|
137
|
+
{ key: "project_id", label: "Project ID" }
|
|
138
|
+
],
|
|
139
|
+
cloudflare: [
|
|
140
|
+
{ key: "api_token", label: "API Token", secret: true },
|
|
141
|
+
{ key: "worker_url", label: "Worker URL" }
|
|
142
|
+
],
|
|
143
|
+
box: [{ key: "api_key", label: "API Key", secret: true }],
|
|
144
|
+
docker: [{ key: "image", label: "Image" }]
|
|
145
|
+
};
|
|
146
|
+
var sandboxDriverLabels = {
|
|
147
|
+
exe: "exe.dev",
|
|
148
|
+
daytona: "Daytona",
|
|
149
|
+
modal: "Modal",
|
|
150
|
+
vercel: "Vercel",
|
|
151
|
+
cloudflare: "Cloudflare",
|
|
152
|
+
box: "Box",
|
|
153
|
+
docker: "Docker"
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/driver-catalog.ts
|
|
157
|
+
var sandboxDriverCatalog = {
|
|
158
|
+
exe: {
|
|
159
|
+
id: "exe",
|
|
160
|
+
label: sandboxDriverLabels.exe,
|
|
161
|
+
credentialFields: sandboxDriverCredentialFields.exe,
|
|
162
|
+
metadata: {
|
|
163
|
+
driverRunsIn: ["worker", "node"],
|
|
164
|
+
hostStopBehavior: "not-supported",
|
|
165
|
+
hostResumeBehavior: "same-host",
|
|
166
|
+
targetAccess: "relay",
|
|
167
|
+
secretBrokering: "none",
|
|
168
|
+
egressControl: "none",
|
|
169
|
+
persistence: {
|
|
170
|
+
resume: "same-sandbox",
|
|
171
|
+
capture: "none",
|
|
172
|
+
clone: true,
|
|
173
|
+
captureSource: "not-applicable",
|
|
174
|
+
retention: "provider-managed",
|
|
175
|
+
restoreMount: "same-resource"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
daytona: {
|
|
180
|
+
id: "daytona",
|
|
181
|
+
label: sandboxDriverLabels.daytona,
|
|
182
|
+
credentialFields: sandboxDriverCredentialFields.daytona,
|
|
183
|
+
metadata: {
|
|
184
|
+
driverRunsIn: ["worker", "node"],
|
|
185
|
+
hostStopBehavior: "suspends-host",
|
|
186
|
+
hostResumeBehavior: "same-host",
|
|
187
|
+
targetAccess: "relay",
|
|
188
|
+
secretBrokering: "native",
|
|
189
|
+
// The driver translates names to domainAllowList, or addresses to networkAllowList.
|
|
190
|
+
egressControl: "hosts-and-cidrs",
|
|
191
|
+
persistence: {
|
|
192
|
+
resume: "same-sandbox",
|
|
193
|
+
capture: "filesystem",
|
|
194
|
+
clone: false,
|
|
195
|
+
captureSource: "preserved",
|
|
196
|
+
retention: "provider-managed",
|
|
197
|
+
restoreMount: "new-resource"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
modal: {
|
|
202
|
+
id: "modal",
|
|
203
|
+
label: sandboxDriverLabels.modal,
|
|
204
|
+
credentialFields: sandboxDriverCredentialFields.modal,
|
|
205
|
+
metadata: {
|
|
206
|
+
driverRunsIn: ["node"],
|
|
207
|
+
hostStopBehavior: "terminates-host",
|
|
208
|
+
hostResumeBehavior: "replacement-host",
|
|
209
|
+
targetAccess: "relay",
|
|
210
|
+
secretBrokering: "none",
|
|
211
|
+
// The driver does not install Modal's domain allowlist or alpha sidecar.
|
|
212
|
+
egressControl: "none",
|
|
213
|
+
persistence: {
|
|
214
|
+
resume: "replacement-restore",
|
|
215
|
+
capture: "filesystem",
|
|
216
|
+
clone: false,
|
|
217
|
+
captureSource: "preserved",
|
|
218
|
+
retention: "provider-managed",
|
|
219
|
+
restoreMount: "new-resource"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
vercel: {
|
|
224
|
+
id: "vercel",
|
|
225
|
+
label: sandboxDriverLabels.vercel,
|
|
226
|
+
credentialFields: sandboxDriverCredentialFields.vercel,
|
|
227
|
+
metadata: {
|
|
228
|
+
driverRunsIn: ["node"],
|
|
229
|
+
hostStopBehavior: "terminates-host",
|
|
230
|
+
hostResumeBehavior: "replacement-host",
|
|
231
|
+
targetAccess: "relay",
|
|
232
|
+
secretBrokering: "native",
|
|
233
|
+
// The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
|
|
234
|
+
egressControl: "hosts",
|
|
235
|
+
persistence: {
|
|
236
|
+
resume: "replacement-restore",
|
|
237
|
+
capture: "filesystem",
|
|
238
|
+
clone: false,
|
|
239
|
+
captureSource: "stopped",
|
|
240
|
+
retention: "explicit",
|
|
241
|
+
restoreMount: "new-resource"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
cloudflare: {
|
|
246
|
+
id: "cloudflare",
|
|
247
|
+
label: sandboxDriverLabels.cloudflare,
|
|
248
|
+
credentialFields: sandboxDriverCredentialFields.cloudflare,
|
|
249
|
+
metadata: {
|
|
250
|
+
driverRunsIn: ["worker"],
|
|
251
|
+
hostStopBehavior: "not-supported",
|
|
252
|
+
hostResumeBehavior: "same-host",
|
|
253
|
+
targetAccess: "relay",
|
|
254
|
+
secretBrokering: "native",
|
|
255
|
+
// Credential handlers do not restrict unrelated destinations.
|
|
256
|
+
egressControl: "none",
|
|
257
|
+
persistence: {
|
|
258
|
+
resume: "replacement-restore",
|
|
259
|
+
capture: "directories",
|
|
260
|
+
clone: false,
|
|
261
|
+
captureSource: "preserved",
|
|
262
|
+
retention: "provider-managed",
|
|
263
|
+
restoreMount: "copy-on-write"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
box: {
|
|
268
|
+
id: "box",
|
|
269
|
+
label: sandboxDriverLabels.box,
|
|
270
|
+
credentialFields: sandboxDriverCredentialFields.box,
|
|
271
|
+
metadata: {
|
|
272
|
+
driverRunsIn: ["node"],
|
|
273
|
+
hostStopBehavior: "suspends-host",
|
|
274
|
+
hostResumeBehavior: "same-host",
|
|
275
|
+
targetAccess: "relay",
|
|
276
|
+
secretBrokering: "none",
|
|
277
|
+
egressControl: "none",
|
|
278
|
+
persistence: {
|
|
279
|
+
resume: "same-sandbox",
|
|
280
|
+
capture: "none",
|
|
281
|
+
clone: false,
|
|
282
|
+
captureSource: "not-applicable",
|
|
283
|
+
retention: "provider-managed",
|
|
284
|
+
restoreMount: "same-resource"
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
docker: {
|
|
289
|
+
id: "docker",
|
|
290
|
+
label: sandboxDriverLabels.docker,
|
|
291
|
+
credentialFields: sandboxDriverCredentialFields.docker,
|
|
292
|
+
metadata: {
|
|
293
|
+
driverRunsIn: ["local"],
|
|
294
|
+
hostStopBehavior: "terminates-host",
|
|
295
|
+
hostResumeBehavior: "same-host",
|
|
296
|
+
targetAccess: "loopback",
|
|
297
|
+
secretBrokering: "none",
|
|
298
|
+
egressControl: "none",
|
|
299
|
+
persistence: {
|
|
300
|
+
resume: "replacement-restore",
|
|
301
|
+
capture: "same-resource",
|
|
302
|
+
clone: false,
|
|
303
|
+
captureSource: "preserved",
|
|
304
|
+
retention: "provider-managed",
|
|
305
|
+
restoreMount: "same-resource"
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/drivers/transient-error.ts
|
|
312
|
+
function textOf(input) {
|
|
313
|
+
return typeof input === "string" ? input : "";
|
|
314
|
+
}
|
|
315
|
+
function driverErrorSignals(err) {
|
|
316
|
+
const shaped = isRecord(err) ? err : {};
|
|
317
|
+
const nested = isRecord(shaped.response) ? shaped.response : {};
|
|
318
|
+
return {
|
|
319
|
+
status: num(nested.status) ?? num(shaped.status),
|
|
320
|
+
text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function isTransientDriverError(err, markers) {
|
|
324
|
+
const { status, text: text2 } = driverErrorSignals(err);
|
|
325
|
+
if (status !== void 0 && status >= 500) return true;
|
|
326
|
+
return markers.some((marker) => text2.includes(marker));
|
|
327
|
+
}
|
|
328
|
+
|
|
71
329
|
// src/drivers/box.ts
|
|
72
330
|
var DEFAULT_BASE_URL = "https://ascii.dev/api/box/v1";
|
|
73
331
|
var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
|
|
@@ -87,11 +345,9 @@ function clean(input) {
|
|
|
87
345
|
const txt = input?.trim();
|
|
88
346
|
return txt ? txt : void 0;
|
|
89
347
|
}
|
|
348
|
+
var TRANSIENT_MARKERS = ["timeout", "unavailable", "econnreset", "network"];
|
|
90
349
|
function transientDriverError(err) {
|
|
91
|
-
|
|
92
|
-
if (typeof shaped.status === "number" && shaped.status >= 500) return true;
|
|
93
|
-
const text = `${shaped.code ?? ""} ${shaped.name ?? ""} ${shaped.message ?? ""}`.toLowerCase();
|
|
94
|
-
return text.includes("timeout") || text.includes("unavailable") || text.includes("econnreset") || text.includes("network");
|
|
350
|
+
return isTransientDriverError(err, TRANSIENT_MARKERS);
|
|
95
351
|
}
|
|
96
352
|
var BoxApiError = class extends Error {
|
|
97
353
|
status;
|
|
@@ -129,14 +385,29 @@ function createBoxSandboxDriver(options) {
|
|
|
129
385
|
} catch (err) {
|
|
130
386
|
throw new BoxApiError(err instanceof Error ? err.message : String(err));
|
|
131
387
|
}
|
|
132
|
-
const
|
|
133
|
-
const body =
|
|
388
|
+
const payload = await res.text();
|
|
389
|
+
const body = (payload ? asRecord(JSON.parse(payload)) : void 0) ?? {};
|
|
134
390
|
if (!res.ok || body.ok === false) {
|
|
135
|
-
const detail = clean(body.error) ?? clean(body.message) ?? res.statusText;
|
|
391
|
+
const detail = clean(text(body.error)) ?? clean(text(body.message)) ?? res.statusText;
|
|
136
392
|
throw new BoxApiError(`Box API ${init?.method ?? "GET"} ${path} failed: ${detail}`, res.status);
|
|
137
393
|
}
|
|
138
394
|
return body;
|
|
139
395
|
}
|
|
396
|
+
function boxOf(body) {
|
|
397
|
+
const box = asRecord(body.box);
|
|
398
|
+
const id = text(box?.id);
|
|
399
|
+
const state = text(box?.state);
|
|
400
|
+
if (!box || !id || !state) throw new BoxApiError("Box API response omitted its box envelope");
|
|
401
|
+
return {
|
|
402
|
+
id,
|
|
403
|
+
state,
|
|
404
|
+
...text(box.name) ? { name: text(box.name) } : {},
|
|
405
|
+
...box.url === void 0 ? {} : { url: text(box.url) ?? null },
|
|
406
|
+
...box.ip === void 0 ? {} : { ip: text(box.ip) ?? null },
|
|
407
|
+
...box.subdomain === void 0 ? {} : { subdomain: text(box.subdomain) ?? null },
|
|
408
|
+
...box.createdAt === void 0 ? {} : { createdAt: text(box.createdAt) ?? null }
|
|
409
|
+
};
|
|
410
|
+
}
|
|
140
411
|
function runtimePort(input) {
|
|
141
412
|
return input.workspaceRuntimePort ?? options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
|
|
142
413
|
}
|
|
@@ -148,26 +419,23 @@ function createBoxSandboxDriver(options) {
|
|
|
148
419
|
return input.snapshot ?? image;
|
|
149
420
|
}
|
|
150
421
|
function bootEnv(input, hostId) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
162
|
-
if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
|
|
163
|
-
return env;
|
|
422
|
+
return workspaceRuntimeBootEnv({
|
|
423
|
+
workspaceId: input.workspaceId,
|
|
424
|
+
hostId,
|
|
425
|
+
directory: workspaceDirectory(input),
|
|
426
|
+
port: runtimePort(input),
|
|
427
|
+
host: "0.0.0.0",
|
|
428
|
+
source: input.source,
|
|
429
|
+
env: input.env,
|
|
430
|
+
runner: options.runner
|
|
431
|
+
});
|
|
164
432
|
}
|
|
165
433
|
async function exec(boxId, command) {
|
|
166
434
|
const body = await api(`/boxes/${boxId}/commands`, {
|
|
167
435
|
method: "POST",
|
|
168
436
|
body: { command }
|
|
169
437
|
});
|
|
170
|
-
const result = body.result ?? body;
|
|
438
|
+
const result = asRecord(body.result) ?? body;
|
|
171
439
|
return {
|
|
172
440
|
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
173
441
|
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
@@ -177,14 +445,14 @@ function createBoxSandboxDriver(options) {
|
|
|
177
445
|
async function execOrThrow(boxId, command, label) {
|
|
178
446
|
const result = await exec(boxId, command);
|
|
179
447
|
if ((result.exitCode ?? 0) !== 0) {
|
|
180
|
-
throw new BoxApiError(`Box ${boxId} ${label} failed (exit ${result.exitCode}): ${
|
|
448
|
+
throw new BoxApiError(`Box ${boxId} ${label} failed (exit ${result.exitCode}): ${trimToUndefined(result.stderr) ?? trimToUndefined(result.stdout) ?? ""}`);
|
|
181
449
|
}
|
|
182
450
|
return result;
|
|
183
451
|
}
|
|
184
452
|
async function waitUntilReady(boxId) {
|
|
185
453
|
const until = Date.now() + provisionTimeoutMs;
|
|
186
454
|
for (; ; ) {
|
|
187
|
-
const
|
|
455
|
+
const box = boxOf(await api(`/boxes/${boxId}`));
|
|
188
456
|
if (READY_STATES.has(box.state)) return true;
|
|
189
457
|
if (!PENDING_STATES.has(box.state)) {
|
|
190
458
|
throw new BoxApiError(`Box ${boxId} entered non-ready state: ${box.state}`);
|
|
@@ -210,7 +478,7 @@ function createBoxSandboxDriver(options) {
|
|
|
210
478
|
boxId,
|
|
211
479
|
`curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:${port}/global/health || true`
|
|
212
480
|
);
|
|
213
|
-
const code =
|
|
481
|
+
const code = trimToUndefined(probe.stdout);
|
|
214
482
|
if (code === "200") return;
|
|
215
483
|
last = `health probe returned ${code ?? "no response"}`;
|
|
216
484
|
await sleep(healthIntervalMs);
|
|
@@ -227,7 +495,7 @@ function createBoxSandboxDriver(options) {
|
|
|
227
495
|
await execOrThrow(boxId, `host ${port}`, "host publish");
|
|
228
496
|
await waitForHealth(boxId, input);
|
|
229
497
|
const urlResult = await execOrThrow(boxId, `host url ${port}`, "host url");
|
|
230
|
-
const url =
|
|
498
|
+
const url = trimToUndefined(urlResult.stdout?.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).pop());
|
|
231
499
|
if (!url) throw new BoxApiError(`Box ${boxId} did not return a public URL for port ${port}`);
|
|
232
500
|
return {
|
|
233
501
|
workspaceId: input.workspaceId,
|
|
@@ -255,7 +523,7 @@ function createBoxSandboxDriver(options) {
|
|
|
255
523
|
throw err;
|
|
256
524
|
});
|
|
257
525
|
if (!created) return { provisioning: true, retryAfterMs: provisionIntervalMs };
|
|
258
|
-
const boxId = created.
|
|
526
|
+
const boxId = boxOf(created).id;
|
|
259
527
|
const ready = await waitUntilReady(boxId);
|
|
260
528
|
if (!ready) return { provisioning: true, retryAfterMs: provisionIntervalMs };
|
|
261
529
|
return boot(boxId, input, hostId);
|
|
@@ -288,7 +556,9 @@ function createBoxSandboxDriver(options) {
|
|
|
288
556
|
hostStopBehavior: "suspends-host",
|
|
289
557
|
hostResumeBehavior: "same-host",
|
|
290
558
|
targetAccess: "relay",
|
|
291
|
-
secretBrokering: "none"
|
|
559
|
+
secretBrokering: "none",
|
|
560
|
+
egressControl: "none",
|
|
561
|
+
persistence: sandboxDriverCatalog.box.metadata.persistence
|
|
292
562
|
},
|
|
293
563
|
ensureHost,
|
|
294
564
|
resumeHost,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SandboxDriver, type SandboxDriverEnsureInput, type SandboxListingUnsupported } from "..";
|
|
2
|
+
import { type WorkspaceRuntimeControlEnv } from "../runtime-env";
|
|
2
3
|
export type CloudflareSandboxDriverOptions = {
|
|
3
4
|
/** Base URL of the deployed Cloudflare Sandbox Worker (e.g. https://sbx.example.com). */
|
|
4
5
|
workerUrl: string;
|
|
@@ -18,12 +19,8 @@ export type CloudflareSandboxDriverOptions = {
|
|
|
18
19
|
* relayVerifyPem → WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM (fallback to JWKS)
|
|
19
20
|
* managementJwksUrl → WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL (config apply)
|
|
20
21
|
*/
|
|
21
|
-
controlEnv?:
|
|
22
|
-
|
|
23
|
-
relayVerifyPem?: string;
|
|
24
|
-
managementJwksUrl?: string;
|
|
25
|
-
};
|
|
26
|
-
/** Default runner injected as WORKSPACE_RUNTIME_RUNNER, e.g. "opencode" | "claude-acp" | "codex-acp". */
|
|
22
|
+
controlEnv?: WorkspaceRuntimeControlEnv;
|
|
23
|
+
/** Default runner injected as WORKSPACE_RUNTIME_RUNNER, for example "opencode". */
|
|
27
24
|
runner?: string;
|
|
28
25
|
/** Dynamic runtime env that needs the sandbox id or current lease. */
|
|
29
26
|
env?: (input: SandboxDriverEnsureInput, sandbox: {
|
|
@@ -34,4 +31,18 @@ export type CloudflareSandboxDriverOptions = {
|
|
|
34
31
|
/** ensure-runtime/HTTP timeout. */
|
|
35
32
|
timeoutMs?: number;
|
|
36
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Thrown when this driver's sandbox Worker cannot enumerate sandboxes — one
|
|
36
|
+
* deployed before the `/sandboxes` registry route, or without its R2 binding.
|
|
37
|
+
*
|
|
38
|
+
* A typed signal rather than a plain Error because the manager must tell apart
|
|
39
|
+
* two cases that both "fail to list": a transient listing error (a real sweep
|
|
40
|
+
* failure, propagate it) and a standing capability gap (report
|
|
41
|
+
* `listingUnsupported`, same as a driver with no `list()`). Returning `[]` for
|
|
42
|
+
* the latter would report a clean sweep while seeing nothing.
|
|
43
|
+
*/
|
|
44
|
+
export declare class CloudflareSandboxListingUnsupportedError extends Error implements SandboxListingUnsupported {
|
|
45
|
+
readonly listingUnsupported: true;
|
|
46
|
+
constructor(message: string);
|
|
47
|
+
}
|
|
37
48
|
export declare function createCloudflareSandboxDriver(options: CloudflareSandboxDriverOptions): SandboxDriver;
|