@claxedo/sandbox-manager 0.10.2 → 0.10.4

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 (37) hide show
  1. package/README.md +67 -29
  2. package/dist/checkpoint-manager.mjs +11 -0
  3. package/dist/contract.d.ts +28 -16
  4. package/dist/driver-billing.d.ts +23 -0
  5. package/dist/driver-catalog.d.ts +1 -2
  6. package/dist/driver-catalog.mjs +61 -11
  7. package/dist/driver-ensure-input.d.ts +3 -2
  8. package/dist/drivers/agent37-client.d.ts +87 -0
  9. package/dist/drivers/agent37.d.ts +25 -0
  10. package/dist/drivers/agent37.mjs +886 -0
  11. package/dist/drivers/boat-client.d.ts +5 -5
  12. package/dist/drivers/boat-machine.d.ts +7 -17
  13. package/dist/drivers/boat-template.mjs +87 -35
  14. package/dist/drivers/boat.d.ts +0 -5
  15. package/dist/drivers/boat.mjs +81 -26
  16. package/dist/drivers/cloudflare.d.ts +5 -9
  17. package/dist/drivers/cloudflare.mjs +97 -43
  18. package/dist/drivers/docker.mjs +70 -19
  19. package/dist/drivers/local-brokering.mjs +90 -18
  20. package/dist/drivers/modal.mjs +56 -11
  21. package/dist/drivers/transient-error.d.ts +0 -1
  22. package/dist/drivers/vercel.mjs +65 -15
  23. package/dist/egress-policy.d.ts +33 -32
  24. package/dist/egress-warnings.d.ts +4 -6
  25. package/dist/hosted-network-policy.d.ts +10 -64
  26. package/dist/hosted-network-policy.mjs +1 -1
  27. package/dist/image-name.d.ts +0 -1
  28. package/dist/index.mjs +103 -78
  29. package/dist/json.d.ts +1 -13
  30. package/dist/lease-failure.d.ts +3 -3
  31. package/dist/lease-types.d.ts +1 -10
  32. package/dist/runtime-env.d.ts +6 -7
  33. package/dist/runtime-env.mjs +5 -10
  34. package/dist/runtime-version.d.ts +4 -0
  35. package/dist/runtime-version.mjs +6 -0
  36. package/dist/start-admission.d.ts +32 -0
  37. package/package.json +12 -4
@@ -5,19 +5,16 @@ export type CloudflareSandboxDriverOptions = {
5
5
  workerUrl: string;
6
6
  /** Bearer token the Worker validates (its `API_TOKEN` secret). */
7
7
  apiToken: string;
8
- /** Port the workspace-runtime listens on inside the sandbox (exposed publicly). */
8
+ /** Port the workspace-runtime listens on inside the container, which the Worker's proxy route forwards to. */
9
9
  runtimePort?: number;
10
- /** Command that launches the workspace-runtime inside the image. */
11
10
  runtimeCommand?: string;
12
- /** Workspace directory inside the sandbox. */
13
11
  workspaceDir?: string;
14
12
  /**
15
13
  * Static control-plane config injected into every sandbox so the runtime can
16
- * verify relay-proxied requests. Never carries per-workspace secrets — those
17
- * (if any) belong to the future egress broker.
18
- * relayJwksUrl → WORKSPACE_RUNTIME_RELAY_JWKS_URL (relay public key for RAT verify)
19
- * relayVerifyPem → WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM (fallback to JWKS)
20
- * managementJwksUrl → WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL (config apply)
14
+ * verify relay-proxied requests; never a per-workspace secret.
15
+ * relayJwksUrl → WORKSPACE_RUNTIME_RELAY_JWKS_URL (relay host token keys)
16
+ * relayVerifyPem → WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM (fallback to JWKS)
17
+ * managementJwksUrl → WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL (config apply)
21
18
  */
22
19
  controlEnv?: WorkspaceRuntimeControlEnv;
23
20
  /** Default native harness injected as WORKSPACE_RUNTIME_NATIVE_HARNESS, for example "opencode". */
@@ -26,7 +23,6 @@ export type CloudflareSandboxDriverOptions = {
26
23
  env?: (input: SandboxDriverEnsureInput, sandbox: {
27
24
  id: string;
28
25
  }) => Record<string, string> | Promise<Record<string, string>>;
29
- /** Injected for tests. */
30
26
  fetch?: typeof fetch;
31
27
  /** ensure-runtime/HTTP timeout. */
32
28
  timeoutMs?: number;
@@ -12,6 +12,27 @@ var boatBilling = {
12
12
  large: { size: "large", vcpu: 8, memoryGb: 16, usdPerHour: 0.072 }
13
13
  }
14
14
  };
15
+ var AGENT37_USD_PER_MONTH = { vcpu: 0.8, memoryGb: 0.7, diskGb: 0.09 };
16
+ var AGENT37_MONTH_HOURS = 730;
17
+ var agent37MachineShapes = {
18
+ small: { cpu: 2, memory: 4, disk: 10 },
19
+ default: { cpu: 4, memory: 8, disk: 20 },
20
+ large: { cpu: 4, memory: 16, disk: 40 }
21
+ };
22
+ function agent37Rate(shape) {
23
+ const usdPerMonth = shape.cpu * AGENT37_USD_PER_MONTH.vcpu + shape.memory * AGENT37_USD_PER_MONTH.memoryGb + shape.disk * AGENT37_USD_PER_MONTH.diskGb;
24
+ return { size: `${shape.cpu} vCPU / ${shape.memory} GB`, vcpu: shape.cpu, memoryGb: shape.memory, usdPerHour: usdPerMonth / AGENT37_MONTH_HOURS };
25
+ }
26
+ var agent37Billing = {
27
+ source: { url: "https://www.agent37.com/docs/agents-api/billing", readOn: "2026-11-11" },
28
+ meter: "Agent37 meters an instance per minute at a monthly rate over 730 hours: $0.80 per vCPU, $0.70 per GB of memory and $0.09 per GB of disk while it runs, and the disk alone while it is stopped.",
29
+ rates: {
30
+ small: agent37Rate(agent37MachineShapes.small),
31
+ default: agent37Rate(agent37MachineShapes.default),
32
+ large: agent37Rate(agent37MachineShapes.large)
33
+ },
34
+ excludes: `the disk of a stopped instance, billed at $${AGENT37_USD_PER_MONTH.diskGb} per GB a month until the instance is deleted`
35
+ };
15
36
  var CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY = 25e-7;
16
37
  var CLOUDFLARE_USD_PER_GB_SECOND_DISK = 7e-8;
17
38
  var CLOUDFLARE_USD_PER_VCPU_SECOND_ACTIVE = 2e-5;
@@ -50,6 +71,7 @@ var sandboxDriverCredentialFields = {
50
71
  { key: "worker_url", label: "Worker URL" }
51
72
  ],
52
73
  boat: [{ key: "api_key", label: "API Key", secret: true }],
74
+ agent37: [{ key: "api_key", label: "API Key", secret: true }],
53
75
  docker: [{ key: "image", label: "Image" }]
54
76
  };
55
77
  var sandboxDriverLabels = {
@@ -57,6 +79,7 @@ var sandboxDriverLabels = {
57
79
  vercel: "Vercel",
58
80
  cloudflare: "Cloudflare",
59
81
  boat: "Boat",
82
+ agent37: "Agent37",
60
83
  docker: "Docker"
61
84
  };
62
85
  function cloudflareWorkerBaseUrl(input) {
@@ -139,8 +162,8 @@ var sandboxDriverCatalog = {
139
162
  hostWatchesIdle: true,
140
163
  targetAccess: "relay",
141
164
  secretBrokering: "native",
142
- // Credential handlers do not restrict unrelated destinations.
143
- egressControl: "none",
165
+ // The sandbox Worker starts the container without internet and routes each allowed host through itself; a Worker that cannot confirm that refuses the start.
166
+ egressControl: "hosts",
144
167
  // A container snapshot holds the whole root filesystem; the platform keeps it 30 days past its last restore and offers no deletion.
145
168
  persistence: {
146
169
  resume: "replacement-restore",
@@ -178,6 +201,33 @@ var sandboxDriverCatalog = {
178
201
  machineClasses: ["small", "default", "large"]
179
202
  }
180
203
  },
204
+ agent37: {
205
+ id: "agent37",
206
+ label: sandboxDriverLabels.agent37,
207
+ credentialFields: sandboxDriverCredentialFields.agent37,
208
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
209
+ runtimeEnv: "callback",
210
+ preparedImage: true,
211
+ billing: agent37Billing,
212
+ metadata: {
213
+ driverRunsIn: ["worker", "node"],
214
+ hostStopBehavior: "suspends-host",
215
+ hostResumeBehavior: "same-host",
216
+ targetAccess: "relay",
217
+ secretBrokering: "none",
218
+ // An instance's network policy lists domains, reached over HTTPS on 443 alone, and IPv4 ranges; private ranges stay blocked whatever it lists.
219
+ egressControl: "hosts-and-cidrs",
220
+ persistence: {
221
+ resume: "same-sandbox",
222
+ capture: "none",
223
+ clone: false,
224
+ captureSource: "not-applicable",
225
+ retention: "provider-managed",
226
+ restoreMount: "same-resource"
227
+ },
228
+ machineClasses: ["small", "default", "large"]
229
+ }
230
+ },
181
231
  docker: {
182
232
  id: "docker",
183
233
  label: sandboxDriverLabels.docker,
@@ -204,6 +254,17 @@ var sandboxDriverCatalog = {
204
254
  }
205
255
  };
206
256
 
257
+ // ../claxedo-helpers/dist/deadline.mjs
258
+ async function withDeadlineSignal(timeoutMs, work, parent) {
259
+ const deadline = new AbortController();
260
+ const timer = setTimeout(() => deadline.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
261
+ try {
262
+ return await work(parent ? AbortSignal.any([parent, deadline.signal]) : deadline.signal);
263
+ } finally {
264
+ clearTimeout(timer);
265
+ }
266
+ }
267
+
207
268
  // src/egress-policy.ts
208
269
  function brokeredSecretPlaceholder(name) {
209
270
  return `claxedo-broker:${name}`;
@@ -242,10 +303,11 @@ function isRecord(value) {
242
303
  function asRecord(value) {
243
304
  return isRecord(value) ? value : void 0;
244
305
  }
245
-
246
- // src/json.ts
247
- function text(input) {
248
- return typeof input === "string" && input.length > 0 ? input : void 0;
306
+ function isNonEmptyString(value) {
307
+ return typeof value === "string" && value.length > 0;
308
+ }
309
+ function nonEmptyString(value) {
310
+ return isNonEmptyString(value) ? value : void 0;
249
311
  }
250
312
 
251
313
  // src/runtime-env.ts
@@ -337,19 +399,6 @@ var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
337
399
  var DEFAULT_TIMEOUT_MS = 45e3;
338
400
  var DEFAULT_ENSURE_TIMEOUT_MS = 3e5;
339
401
  var DEFAULT_SNAPSHOT_TIMEOUT_MS = 18e4;
340
- function deadlineSignal(timeoutMs) {
341
- const controller = new AbortController();
342
- const timer = setTimeout(
343
- () => controller.abort(new DOMException("The operation timed out", "TimeoutError")),
344
- timeoutMs
345
- );
346
- return {
347
- signal: controller.signal,
348
- cleanup() {
349
- clearTimeout(timer);
350
- }
351
- };
352
- }
353
402
  var RUNTIME_HOME = "/home/claxedo";
354
403
  function sandboxIdFor(workspaceId) {
355
404
  return `claxedo-${workspaceId}`;
@@ -381,19 +430,17 @@ function createCloudflareSandboxDriver(options) {
381
430
  };
382
431
  async function call(sandboxId, action, body, method = "POST") {
383
432
  const url = `${base}/sandbox/${encodeURIComponent(sandboxId)}${action ? `/${action}` : ""}`;
384
- const deadline = deadlineSignal(action === "ensure-runtime" ? ensureTimeoutMs : action === "snapshot" ? snapshotTimeoutMs : timeoutMs);
385
- try {
433
+ const deadlineMs = action === "ensure-runtime" ? ensureTimeoutMs : action === "snapshot" ? snapshotTimeoutMs : timeoutMs;
434
+ return withDeadlineSignal(deadlineMs, async (signal) => {
386
435
  const res = await doFetch(url, {
387
436
  method,
388
437
  headers,
389
438
  ...method === "POST" ? { body: JSON.stringify(body) } : {},
390
- signal: deadline.signal
439
+ signal
391
440
  });
392
441
  const data = asRecord(await res.json().catch(() => ({}))) ?? {};
393
442
  return { status: res.status, data };
394
- } finally {
395
- deadline.cleanup();
396
- }
443
+ });
397
444
  }
398
445
  function workspaceDirectory(input) {
399
446
  return input.workspaceRoot ?? workspaceDir;
@@ -441,10 +488,15 @@ function createCloudflareSandboxDriver(options) {
441
488
  };
442
489
  });
443
490
  }
491
+ function networkPolicy(input) {
492
+ if (!input.net || input.net.mode === "allow-all") return void 0;
493
+ return { hosts: [...new Set(input.net.hosts ?? [])] };
494
+ }
444
495
  async function ensureHost(input) {
445
496
  const sandboxId = sandboxIdFor(input.workspaceId);
446
497
  const hostId = sandboxId;
447
498
  const egress = egressRegistrations(input);
499
+ const net = networkPolicy(input);
448
500
  const response = await call(
449
501
  sandboxId,
450
502
  "ensure-runtime",
@@ -457,7 +509,8 @@ function createCloudflareSandboxDriver(options) {
457
509
  // Not container env — labels are control-plane metadata.
458
510
  labels: input.labels,
459
511
  ...input.bootSource?.kind === "driver-snapshot" ? { restore: { snapshotId: input.bootSource.snapshotId }, minimumRuntimeVersion: MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION } : {},
460
- ...egress ? { egress } : {}
512
+ ...egress ? { egress } : {},
513
+ ...net ? { net } : {}
461
514
  }
462
515
  ).catch((error) => {
463
516
  if (error instanceof Error && error.name === "TimeoutError") return void 0;
@@ -465,18 +518,22 @@ function createCloudflareSandboxDriver(options) {
465
518
  });
466
519
  if (!response) return { provisioning: true, retryAfterMs: 0 };
467
520
  const { status, data } = response;
468
- const runtimeUrl = text(data.url);
521
+ const runtimeUrl = nonEmptyString(data.url);
469
522
  if (status === 503 && data.error === "workspace-runtime did not become ready") {
470
523
  return { provisioning: true, retryAfterMs: 0 };
471
524
  }
472
- if (data.exited === true) throw new SandboxRuntimeBootError(text(data.error) ?? "The workspace runtime exited before it was ready");
525
+ if (data.exited === true) throw new SandboxRuntimeBootError(nonEmptyString(data.error) ?? "The workspace runtime exited before it was ready");
473
526
  if (status === 410 && isSandboxCheckpointUnusableCause(data.checkpointUnusable)) {
474
- throw new SandboxCheckpointUnusableError(data.checkpointUnusable, text(data.error) ?? "This workspace's saved files cannot be restored; choose Start fresh.");
527
+ throw new SandboxCheckpointUnusableError(data.checkpointUnusable, nonEmptyString(data.error) ?? "This workspace's saved files cannot be restored; choose Start fresh.");
475
528
  }
476
529
  if (status >= 400 || !runtimeUrl) {
477
- const reason = `Cloudflare ensure-runtime failed (${status}): ${text(data.error) ?? "no runtime url"}`;
530
+ const reason = `Cloudflare ensure-runtime failed (${status}): ${nonEmptyString(data.error) ?? "no runtime url"}`;
478
531
  throw status === 401 || status === 403 ? new SandboxProviderKeyRefusedError(reason) : new Error(reason);
479
532
  }
533
+ if (net && asRecord(data.egress)?.contained !== true) {
534
+ await call(sandboxId, "stop", { epoch: input.epoch }).catch(() => void 0);
535
+ throw new Error("Cloudflare sandbox Worker did not confirm egress containment; deploy the current sandbox Worker (scripts/sandbox/cloudflare-worker)");
536
+ }
480
537
  const targetOut = {
481
538
  workspaceId: input.workspaceId,
482
539
  sandboxId,
@@ -514,12 +571,11 @@ function createCloudflareSandboxDriver(options) {
514
571
  //
515
572
  // A missing registry route or R2 binding cannot establish an empty inventory.
516
573
  async list() {
517
- const deadline = deadlineSignal(timeoutMs);
518
- try {
574
+ return withDeadlineSignal(timeoutMs, async (signal) => {
519
575
  const res = await doFetch(`${base}/sandboxes`, {
520
576
  method: "GET",
521
577
  headers,
522
- signal: deadline.signal
578
+ signal
523
579
  });
524
580
  if (res.status === 404 || res.status === 501) {
525
581
  throw new CloudflareSandboxListingUnsupportedError(`Cloudflare sandbox Worker cannot enumerate sandboxes (${res.status}); requires the /sandboxes registry route and SANDBOX_REGISTRY binding`);
@@ -528,11 +584,11 @@ function createCloudflareSandboxDriver(options) {
528
584
  if (!data) throw new Error("Invalid Cloudflare sandbox listing envelope");
529
585
  if (data.supported === false) {
530
586
  throw new CloudflareSandboxListingUnsupportedError(
531
- text(data.error) ?? `Cloudflare sandbox Worker cannot enumerate sandboxes (${res.status}) \u2014 deploy a Worker with the /sandboxes registry route and an R2 SANDBOX_REGISTRY binding`
587
+ nonEmptyString(data.error) ?? `Cloudflare sandbox Worker cannot enumerate sandboxes (${res.status}) \u2014 deploy a Worker with the /sandboxes registry route and an R2 SANDBOX_REGISTRY binding`
532
588
  );
533
589
  }
534
590
  if (!res.ok) {
535
- throw new Error(`Cloudflare sandbox listing failed (${res.status}): ${text(data.error) ?? "unknown error"}`);
591
+ throw new Error(`Cloudflare sandbox listing failed (${res.status}): ${nonEmptyString(data.error) ?? "unknown error"}`);
536
592
  }
537
593
  return registryEntries(data.sandboxes).flatMap((entry) => {
538
594
  const sandboxId = entry.sandboxId;
@@ -551,9 +607,7 @@ function createCloudflareSandboxDriver(options) {
551
607
  };
552
608
  return [target];
553
609
  });
554
- } finally {
555
- deadline.cleanup();
556
- }
610
+ });
557
611
  },
558
612
  metadata: {
559
613
  driverRunsIn: ["worker"],
@@ -562,7 +616,7 @@ function createCloudflareSandboxDriver(options) {
562
616
  hostWatchesIdle: true,
563
617
  targetAccess: "relay",
564
618
  secretBrokering: "native",
565
- egressControl: "none",
619
+ egressControl: "hosts",
566
620
  persistence: sandboxDriverCatalog.cloudflare.metadata.persistence
567
621
  },
568
622
  ensureHost,
@@ -575,14 +629,14 @@ function createCloudflareSandboxDriver(options) {
575
629
  async stop(target) {
576
630
  const { status, data } = await call(target.sandboxId, "stop", { epoch: target.epoch });
577
631
  if (status >= 400 || data.ok !== true) {
578
- throw new Error(`Cloudflare stop failed (${status}) for ${target.sandboxId}: ${text(data.error) ?? "not stopped"}`);
632
+ throw new Error(`Cloudflare stop failed (${status}) for ${target.sandboxId}: ${nonEmptyString(data.error) ?? "not stopped"}`);
579
633
  }
580
634
  },
581
635
  // The sandbox's object outlives its container and keeps the snapshots it took, so a container that is not running is a stopped host, never a gone one.
582
636
  async inspect(target) {
583
637
  const { status, data } = await call(target.sandboxId, "inspect", {});
584
638
  if (status < 200 || status >= 300 || typeof data.running !== "boolean") {
585
- throw new Error(`Cloudflare inspect failed (${status}) for ${target.sandboxId}: ${text(data.error) ?? "no container state"}`);
639
+ throw new Error(`Cloudflare inspect failed (${status}) for ${target.sandboxId}: ${nonEmptyString(data.error) ?? "no container state"}`);
586
640
  }
587
641
  return data.running ? "serving" : "stopped";
588
642
  },
@@ -594,9 +648,9 @@ function createCloudflareSandboxDriver(options) {
594
648
  },
595
649
  async snapshot(target) {
596
650
  const { status, data } = await call(target.sandboxId, "snapshot", {});
597
- const snapshotId = text(data.snapshotId);
651
+ const snapshotId = nonEmptyString(data.snapshotId);
598
652
  if (status >= 400 || !snapshotId) {
599
- throw new Error(`Cloudflare snapshot failed (${status}): ${text(data.error) ?? "no snapshot id"}`);
653
+ throw new Error(`Cloudflare snapshot failed (${status}): ${nonEmptyString(data.error) ?? "no snapshot id"}`);
600
654
  }
601
655
  return { snapshotId };
602
656
  }
@@ -138,6 +138,27 @@ var boatBilling = {
138
138
  large: { size: "large", vcpu: 8, memoryGb: 16, usdPerHour: 0.072 }
139
139
  }
140
140
  };
141
+ var AGENT37_USD_PER_MONTH = { vcpu: 0.8, memoryGb: 0.7, diskGb: 0.09 };
142
+ var AGENT37_MONTH_HOURS = 730;
143
+ var agent37MachineShapes = {
144
+ small: { cpu: 2, memory: 4, disk: 10 },
145
+ default: { cpu: 4, memory: 8, disk: 20 },
146
+ large: { cpu: 4, memory: 16, disk: 40 }
147
+ };
148
+ function agent37Rate(shape) {
149
+ const usdPerMonth = shape.cpu * AGENT37_USD_PER_MONTH.vcpu + shape.memory * AGENT37_USD_PER_MONTH.memoryGb + shape.disk * AGENT37_USD_PER_MONTH.diskGb;
150
+ return { size: `${shape.cpu} vCPU / ${shape.memory} GB`, vcpu: shape.cpu, memoryGb: shape.memory, usdPerHour: usdPerMonth / AGENT37_MONTH_HOURS };
151
+ }
152
+ var agent37Billing = {
153
+ source: { url: "https://www.agent37.com/docs/agents-api/billing", readOn: "2026-11-11" },
154
+ meter: "Agent37 meters an instance per minute at a monthly rate over 730 hours: $0.80 per vCPU, $0.70 per GB of memory and $0.09 per GB of disk while it runs, and the disk alone while it is stopped.",
155
+ rates: {
156
+ small: agent37Rate(agent37MachineShapes.small),
157
+ default: agent37Rate(agent37MachineShapes.default),
158
+ large: agent37Rate(agent37MachineShapes.large)
159
+ },
160
+ excludes: `the disk of a stopped instance, billed at $${AGENT37_USD_PER_MONTH.diskGb} per GB a month until the instance is deleted`
161
+ };
141
162
  var CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY = 25e-7;
142
163
  var CLOUDFLARE_USD_PER_GB_SECOND_DISK = 7e-8;
143
164
  var CLOUDFLARE_USD_PER_VCPU_SECOND_ACTIVE = 2e-5;
@@ -172,6 +193,7 @@ var sandboxDriverCredentialFields = {
172
193
  { key: "worker_url", label: "Worker URL" }
173
194
  ],
174
195
  boat: [{ key: "api_key", label: "API Key", secret: true }],
196
+ agent37: [{ key: "api_key", label: "API Key", secret: true }],
175
197
  docker: [{ key: "image", label: "Image" }]
176
198
  };
177
199
  var sandboxDriverLabels = {
@@ -179,6 +201,7 @@ var sandboxDriverLabels = {
179
201
  vercel: "Vercel",
180
202
  cloudflare: "Cloudflare",
181
203
  boat: "Boat",
204
+ agent37: "Agent37",
182
205
  docker: "Docker"
183
206
  };
184
207
 
@@ -249,8 +272,8 @@ var sandboxDriverCatalog = {
249
272
  hostWatchesIdle: true,
250
273
  targetAccess: "relay",
251
274
  secretBrokering: "native",
252
- // Credential handlers do not restrict unrelated destinations.
253
- egressControl: "none",
275
+ // The sandbox Worker starts the container without internet and routes each allowed host through itself; a Worker that cannot confirm that refuses the start.
276
+ egressControl: "hosts",
254
277
  // A container snapshot holds the whole root filesystem; the platform keeps it 30 days past its last restore and offers no deletion.
255
278
  persistence: {
256
279
  resume: "replacement-restore",
@@ -288,6 +311,33 @@ var sandboxDriverCatalog = {
288
311
  machineClasses: ["small", "default", "large"]
289
312
  }
290
313
  },
314
+ agent37: {
315
+ id: "agent37",
316
+ label: sandboxDriverLabels.agent37,
317
+ credentialFields: sandboxDriverCredentialFields.agent37,
318
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
319
+ runtimeEnv: "callback",
320
+ preparedImage: true,
321
+ billing: agent37Billing,
322
+ metadata: {
323
+ driverRunsIn: ["worker", "node"],
324
+ hostStopBehavior: "suspends-host",
325
+ hostResumeBehavior: "same-host",
326
+ targetAccess: "relay",
327
+ secretBrokering: "none",
328
+ // An instance's network policy lists domains, reached over HTTPS on 443 alone, and IPv4 ranges; private ranges stay blocked whatever it lists.
329
+ egressControl: "hosts-and-cidrs",
330
+ persistence: {
331
+ resume: "same-sandbox",
332
+ capture: "none",
333
+ clone: false,
334
+ captureSource: "not-applicable",
335
+ retention: "provider-managed",
336
+ restoreMount: "same-resource"
337
+ },
338
+ machineClasses: ["small", "default", "large"]
339
+ }
340
+ },
291
341
  docker: {
292
342
  id: "docker",
293
343
  label: sandboxDriverLabels.docker,
@@ -318,10 +368,8 @@ var sandboxDriverCatalog = {
318
368
  function isRecord(value) {
319
369
  return value !== null && typeof value === "object" && !Array.isArray(value);
320
370
  }
321
-
322
- // src/json.ts
323
- function num(input) {
324
- return typeof input === "number" ? input : void 0;
371
+ function asNumber(value) {
372
+ return typeof value === "number" ? value : void 0;
325
373
  }
326
374
 
327
375
  // src/drivers/transient-error.ts
@@ -334,7 +382,7 @@ function driverErrorSignals(err) {
334
382
  return {
335
383
  // `response.status` is the axios shape; `statusCode` is what SDK-native
336
384
  // error classes expose instead.
337
- status: num(nested.status) ?? num(shaped.status) ?? num(shaped.statusCode),
385
+ status: asNumber(nested.status) ?? asNumber(shaped.status) ?? asNumber(shaped.statusCode),
338
386
  text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
339
387
  };
340
388
  }
@@ -356,6 +404,18 @@ async function withDeadlineSignal(timeoutMs, work, parent) {
356
404
  }
357
405
  }
358
406
 
407
+ // ../claxedo-helpers/dist/index.mjs
408
+ function sleep(ms) {
409
+ return new Promise((resolve) => {
410
+ setTimeout(resolve, ms);
411
+ });
412
+ }
413
+
414
+ // ../claxedo-helpers/dist/env.mjs
415
+ function isEnabledFlag(value) {
416
+ return ["1", "true", "yes", "on"].includes(value?.trim().toLowerCase() ?? "");
417
+ }
418
+
359
419
  // src/drivers/docker.ts
360
420
  var execFileAsync = promisify(execFile);
361
421
  var DEFAULT_OPERATION_TIMEOUT_MS = 6e4;
@@ -366,7 +426,7 @@ var DEFAULT_WORKSPACE_DIR = "/workspace";
366
426
  var DOCKER_LABEL = "claxedo";
367
427
  var AUTH_STAGE_DIR = "/tmp/claxedo-auth";
368
428
  function dockerSandboxSyncLocalAuth(env = process.env) {
369
- return enabledFlag(env.CLAXEDO_DOCKER_SANDBOX_SYNC_LOCAL_AUTH);
429
+ return isEnabledFlag(env.CLAXEDO_DOCKER_SANDBOX_SYNC_LOCAL_AUTH);
370
430
  }
371
431
  function dockerSandboxAuthHome(env = process.env) {
372
432
  return trimToUndefined(env.CLAXEDO_DOCKER_SANDBOX_AUTH_HOME) ?? trimToUndefined(env.HOME) ?? os.homedir();
@@ -403,9 +463,6 @@ function dockerLocalAuthCandidates(home) {
403
463
  }
404
464
  ];
405
465
  }
406
- function enabledFlag(input) {
407
- return ["1", "true", "yes", "on"].includes(input?.trim().toLowerCase() ?? "");
408
- }
409
466
  function nameFor(workspaceId) {
410
467
  const cleanId = workspaceId.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
411
468
  return `claxedo-${cleanId || "workspace"}-${Date.now().toString(36)}`.slice(0, 63);
@@ -417,9 +474,6 @@ async function docker(args, timeoutMs = DEFAULT_OPERATION_TIMEOUT_MS, env) {
417
474
  ...env ? { env: { ...process.env, ...env } } : {}
418
475
  });
419
476
  }
420
- function sleep(ms) {
421
- return new Promise((resolve) => setTimeout(resolve, ms));
422
- }
423
477
  async function stageLocalAgentAuth(containerId, input) {
424
478
  if (!input.syncLocalAuth) return;
425
479
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "claxedo-docker-auth-"));
@@ -472,9 +526,6 @@ function labelArgs(input) {
472
526
  return Object.entries(input).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
473
527
  }
474
528
  var TRANSIENT_MARKERS = ["timeout", "temporarily unavailable", "connection refused"];
475
- function transientDriverError(err) {
476
- return isTransientDriverError(err, TRANSIENT_MARKERS);
477
- }
478
529
  function createDockerSandboxDriver(options) {
479
530
  const dockerCommand = options.docker ?? docker;
480
531
  const runtimeCommand = options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND;
@@ -571,7 +622,7 @@ function createDockerSandboxDriver(options) {
571
622
  "-lc",
572
623
  bootScript(workspaceDirectory(input), runtimeCommand)
573
624
  ], 12e4, env).catch((err) => {
574
- if (transientDriverError(err)) return void 0;
625
+ if (isTransientDriverError(err, TRANSIENT_MARKERS)) return void 0;
575
626
  throw err;
576
627
  });
577
628
  if (!created) return { provisioning: true, retryAfterMs: TRANSIENT_DRIVER_RETRY_MS };
@@ -585,7 +636,7 @@ function createDockerSandboxDriver(options) {
585
636
  if (!sandboxId) return ensureHost(input.ensure);
586
637
  await dockerCommand(["inspect", "-f", "{{.Id}}", sandboxId], timeoutMs);
587
638
  await dockerCommand(["start", sandboxId], timeoutMs).catch((err) => {
588
- if (transientDriverError(err)) return void 0;
639
+ if (isTransientDriverError(err, TRANSIENT_MARKERS)) return void 0;
589
640
  throw err;
590
641
  });
591
642
  return readyTarget(input.ensure, sandboxId, input.ensure.hostId ?? sandboxId);