@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.
Files changed (41) hide show
  1. package/README.md +60 -15
  2. package/dist/checkpoint-manager.d.ts +47 -0
  3. package/dist/checkpoint-manager.mjs +116 -0
  4. package/dist/daytona-allow-list.d.ts +11 -0
  5. package/dist/daytona-allow-list.mjs +5 -1
  6. package/dist/driver-catalog.d.ts +5 -52
  7. package/dist/driver-catalog.mjs +263 -121
  8. package/dist/drivers/box.mjs +309 -39
  9. package/dist/drivers/cloudflare.d.ts +18 -7
  10. package/dist/drivers/cloudflare.mjs +423 -34
  11. package/dist/drivers/daytona.d.ts +82 -13
  12. package/dist/drivers/daytona.mjs +495 -45
  13. package/dist/drivers/docker.mjs +284 -38
  14. package/dist/drivers/exe.d.ts +26 -0
  15. package/dist/drivers/exe.mjs +492 -0
  16. package/dist/drivers/fetch-bridge.mjs +29 -5
  17. package/dist/drivers/modal.d.ts +2 -5
  18. package/dist/drivers/modal.mjs +296 -46
  19. package/dist/drivers/transient-error.d.ts +11 -0
  20. package/dist/drivers/vercel.d.ts +27 -11
  21. package/dist/drivers/vercel.mjs +339 -59
  22. package/dist/hosted-network-policy.d.ts +87 -0
  23. package/dist/hosted-network-policy.mjs +61 -0
  24. package/dist/image-name.mjs +34 -0
  25. package/dist/image.d.ts +18 -3
  26. package/dist/image.mjs +47 -32
  27. package/dist/index.d.ts +281 -17
  28. package/dist/index.mjs +393 -26
  29. package/dist/json.d.ts +13 -0
  30. package/dist/lease-policy.d.ts +6 -3
  31. package/dist/lease-policy.mjs +213 -54
  32. package/dist/lease-types.d.ts +17 -3
  33. package/dist/runtime-env.d.ts +12 -1
  34. package/dist/runtime-env.mjs +14 -0
  35. package/dist/runtime-version.mjs +1 -9
  36. package/dist/service-url-exposure.d.ts +1 -2
  37. package/dist/service-url-exposure.mjs +1 -17
  38. package/dist/stores/memory.mjs +39 -4
  39. package/package.json +67 -51
  40. package/dist/drivers/cloudflare-egress.d.ts +0 -40
  41. package/dist/drivers/cloudflare-egress.mjs +0 -113
@@ -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
- if (versionCache) return versionCache;
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
- const shaped = err;
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 text = await res.text();
133
- const body = text ? JSON.parse(text) : {};
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
- const env = {
152
- ...workspaceRuntimeTargetEnv({
153
- workspaceId: input.workspaceId,
154
- hostId,
155
- directory: workspaceDirectory(input),
156
- port: runtimePort(input),
157
- host: "0.0.0.0"
158
- }),
159
- ...workspaceRuntimeSourceEnv({ source: input.source }),
160
- ...input.env
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}): ${clean(result.stderr) ?? clean(result.stdout) ?? ""}`);
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 { box } = await api(`/boxes/${boxId}`);
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 = clean(probe.stdout);
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 = clean(urlResult.stdout?.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).pop());
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.box.id;
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 { SandboxDriver, SandboxDriverEnsureInput } from "..";
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
- relayJwksUrl?: string;
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;