@claxedo/sandbox-manager 0.9.0 → 0.10.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 (67) hide show
  1. package/README.md +233 -26
  2. package/dist/checkpoint-manager.d.ts +29 -4
  3. package/dist/checkpoint-manager.mjs +133 -24
  4. package/dist/command.d.ts +9 -2
  5. package/dist/command.mjs +8 -8
  6. package/dist/contract.d.ts +687 -0
  7. package/dist/driver-billing.d.ts +29 -0
  8. package/dist/driver-catalog.d.ts +42 -7
  9. package/dist/driver-catalog.mjs +115 -161
  10. package/dist/drivers/boat-client.d.ts +95 -0
  11. package/dist/drivers/boat-named-snapshots.d.ts +31 -0
  12. package/dist/drivers/boat-template.d.ts +49 -0
  13. package/dist/drivers/boat-template.mjs +1196 -0
  14. package/dist/drivers/boat.d.ts +64 -0
  15. package/dist/drivers/boat.mjs +990 -0
  16. package/dist/drivers/cloudflare.d.ts +3 -3
  17. package/dist/drivers/cloudflare.mjs +208 -147
  18. package/dist/drivers/docker.d.ts +4 -3
  19. package/dist/drivers/docker.mjs +154 -126
  20. package/dist/drivers/local-brokering.d.ts +14 -0
  21. package/dist/drivers/local-brokering.mjs +1029 -0
  22. package/dist/drivers/modal.d.ts +2 -2
  23. package/dist/drivers/modal.mjs +121 -111
  24. package/dist/drivers/transient-error.d.ts +2 -0
  25. package/dist/drivers/vercel.d.ts +3 -2
  26. package/dist/drivers/vercel.mjs +151 -115
  27. package/dist/egress-policy.d.ts +134 -0
  28. package/dist/egress-warnings.d.ts +22 -0
  29. package/dist/garbage-collect.d.ts +14 -0
  30. package/dist/hosted-network-policy.d.ts +9 -2
  31. package/dist/hosted-network-policy.mjs +218 -1
  32. package/dist/image-name.d.ts +12 -2
  33. package/dist/image-name.mjs +14 -5
  34. package/dist/image.d.ts +1 -20
  35. package/dist/image.mjs +11 -88
  36. package/dist/index.d.ts +8 -578
  37. package/dist/index.mjs +1379 -320
  38. package/dist/lease-failure.d.ts +173 -0
  39. package/dist/lease-lifecycle.d.ts +65 -0
  40. package/dist/lease-retry-wait.d.ts +8 -0
  41. package/dist/lease-start.d.ts +21 -0
  42. package/dist/lease-types.d.ts +4 -3
  43. package/dist/manager.d.ts +2 -0
  44. package/dist/runtime-env.d.ts +4 -2
  45. package/dist/runtime-env.mjs +37 -2
  46. package/dist/runtime-snapshot.d.ts +49 -0
  47. package/dist/runtime-version.d.ts +9 -0
  48. package/dist/runtime-version.mjs +2 -0
  49. package/dist/service-url-exposure.d.ts +0 -1
  50. package/dist/service-url-exposure.mjs +0 -9
  51. package/dist/start-fresh.d.ts +17 -0
  52. package/dist/start-telemetry.d.ts +37 -0
  53. package/dist/stores/memory.d.ts +1 -1
  54. package/dist/stores/memory.mjs +63 -52
  55. package/package.json +26 -42
  56. package/dist/daytona-allow-list.d.ts +0 -16
  57. package/dist/daytona-allow-list.mjs +0 -11
  58. package/dist/drivers/box.d.ts +0 -52
  59. package/dist/drivers/box.mjs +0 -574
  60. package/dist/drivers/daytona.d.ts +0 -163
  61. package/dist/drivers/daytona.mjs +0 -683
  62. package/dist/drivers/exe.d.ts +0 -26
  63. package/dist/drivers/exe.mjs +0 -492
  64. package/dist/drivers/fetch-bridge.d.ts +0 -10
  65. package/dist/drivers/fetch-bridge.mjs +0 -126
  66. package/dist/lease-policy.d.ts +0 -52
  67. package/dist/lease-policy.mjs +0 -290
@@ -17,13 +17,43 @@ function trimToUndefined(value) {
17
17
  }
18
18
 
19
19
  // src/runtime-env.ts
20
+ var WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS = [
21
+ "WORKSPACE_RUNTIME_WORKSPACE_ID",
22
+ "WORKSPACE_RUNTIME_HOST_ID",
23
+ "WORKSPACE_RUNTIME_RELAY_WORKSPACE_IDS"
24
+ ];
25
+ function workspaceRuntimeIdentityEnvConflicts(env) {
26
+ if (!env) return [];
27
+ return WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS.filter((key) => env[key] !== void 0);
28
+ }
29
+ function assertWorkspaceRuntimeIdentityEnv(env) {
30
+ const conflicts = workspaceRuntimeIdentityEnvConflicts(env);
31
+ if (conflicts.length === 0) return;
32
+ throw new Error(
33
+ `sandbox env cannot set runtime identity ${conflicts.join(", ")}: the driver's placement owns the identity its target reports and the lease records`
34
+ );
35
+ }
36
+ var WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS = /* @__PURE__ */ new Set([
37
+ ...WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS,
38
+ "WORKSPACE_RUNTIME_DIRECTORY",
39
+ "WORKSPACE_RUNTIME_PORT",
40
+ "WORKSPACE_RUNTIME_HOST",
41
+ "WORKSPACE_RUNTIME_SOURCE_KIND",
42
+ "WORKSPACE_RUNTIME_GIT_REPO_URL",
43
+ "WORKSPACE_RUNTIME_GIT_BRANCH",
44
+ "WORKSPACE_RUNTIME_NATIVE_HARNESS",
45
+ "WORKSPACE_RUNTIME_RELAY_JWKS_URL",
46
+ "WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL",
47
+ "WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL"
48
+ ]);
20
49
  function workspaceRuntimeBootEnv(input) {
50
+ assertWorkspaceRuntimeIdentityEnv(input.env);
21
51
  const env = {
22
52
  ...workspaceRuntimeTargetEnv(input),
23
53
  ...workspaceRuntimeSourceEnv(input),
24
54
  ...input.env
25
55
  };
26
- if (input.runner) env.WORKSPACE_RUNTIME_RUNNER = input.runner;
56
+ if (input.nativeHarness) env.WORKSPACE_RUNTIME_NATIVE_HARNESS = input.nativeHarness;
27
57
  if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
28
58
  if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
29
59
  if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
@@ -68,6 +98,12 @@ var DEFAULT_SANDBOX_IMAGE_REPOSITORY = "ghcr.io/kyashrathore/claxedo-sandbox";
68
98
  function snapshotVersion(value) {
69
99
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
70
100
  }
101
+ function assertSandboxImageReference(image) {
102
+ if (!image || image !== image.trim() || image.startsWith("-") || /\s/.test(image)) {
103
+ throw new Error(`sandbox image reference is not a legal image identifier: ${JSON.stringify(image)}`);
104
+ }
105
+ return image;
106
+ }
71
107
  function sandboxImageRepository(env = process.env) {
72
108
  const repository = env.CLAXEDO_SANDBOX_IMAGE_REPOSITORY?.trim();
73
109
  return repository ? repository : DEFAULT_SANDBOX_IMAGE_REPOSITORY;
@@ -76,58 +112,47 @@ function buildIdSuffix(buildId, env) {
76
112
  const id = (buildId ?? env.CLAXEDO_SANDBOX_BUILD_ID)?.trim();
77
113
  return id ? `-${snapshotVersion(id)}` : "";
78
114
  }
79
- function defaultSandboxImage(version = workspaceRuntimeVersion(), buildId, env = process.env) {
80
- return `${sandboxImageRepository(env)}:workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
81
- }
82
- function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env = process.env) {
83
- return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
84
- }
85
-
86
- // ../claxedo-helpers/dist/guards.mjs
87
- function isRecord(value) {
88
- return value !== null && typeof value === "object" && !Array.isArray(value);
89
- }
90
-
91
- // src/json.ts
92
- function num(input) {
93
- return typeof input === "number" ? input : void 0;
115
+ var AGENT_PLUGINS_TAG = "-agent-plugins";
116
+ function defaultSandboxImage(version = workspaceRuntimeVersion(), buildId, env = process.env, options = {}) {
117
+ const feature = options.agentPlugins ? AGENT_PLUGINS_TAG : "";
118
+ return `${sandboxImageRepository(env)}:workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}${feature}-v${SNAPSHOT_SCHEMA_VERSION}`;
94
119
  }
95
120
 
96
121
  // src/image.ts
97
122
  var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
98
123
  var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
99
- var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
100
124
 
101
- // src/hosted-network-policy.ts
102
- var SANDBOX_MODEL_PROVIDER_HOSTS = [
103
- "api.anthropic.com",
104
- "api.openai.com",
105
- "generativelanguage.googleapis.com",
106
- "openrouter.ai",
107
- // Model catalog + engine config, fetched on runner startup.
108
- "models.dev",
109
- "opencode.ai"
110
- ];
111
- var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
112
- "registry.npmjs.org",
113
- "nodejs.org",
114
- "pypi.org",
115
- "files.pythonhosted.org",
116
- "crates.io",
117
- "static.crates.io",
118
- "index.crates.io",
119
- "proxy.golang.org",
120
- "sum.golang.org"
121
- ];
122
- var SANDBOX_BASELINE_EGRESS_HOSTS = [
123
- ...SANDBOX_MODEL_PROVIDER_HOSTS,
124
- ...SANDBOX_PACKAGE_REGISTRY_HOSTS
125
- ];
125
+ // src/driver-billing.ts
126
+ var HOUR_SECONDS = 3600;
127
+ var boatBilling = {
128
+ source: { url: "https://docs.boat.dev/pricing", readOn: "2026-10-12" },
129
+ meter: "Boat bills machine time per second while a sandbox runs, at an hourly rate per size; a stopped sandbox costs nothing.",
130
+ rates: {
131
+ small: { size: "small", vcpu: 2, memoryGb: 4, usdPerHour: 0.018 },
132
+ default: { size: "default", vcpu: 4, memoryGb: 8, usdPerHour: 0.036 },
133
+ large: { size: "large", vcpu: 8, memoryGb: 16, usdPerHour: 0.072 }
134
+ }
135
+ };
136
+ var CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY = 25e-7;
137
+ var CLOUDFLARE_USD_PER_GB_SECOND_DISK = 7e-8;
138
+ var CLOUDFLARE_USD_PER_VCPU_SECOND_ACTIVE = 2e-5;
139
+ var CLOUDFLARE_STANDARD_1 = { size: "standard-1", vcpu: 0.5, memoryGb: 4, diskGb: 8 };
140
+ var cloudflareBilling = {
141
+ source: { url: "https://developers.cloudflare.com/containers/pricing/", readOn: "2026-10-12" },
142
+ meter: "Cloudflare bills a container for every 10 ms it runs: memory at $0.0000025 per GiB-second and disk at $0.00000007 per GB-second for the whole run, plus CPU at $0.00002 per vCPU-second of active use.",
143
+ rates: {
144
+ default: {
145
+ size: CLOUDFLARE_STANDARD_1.size,
146
+ vcpu: CLOUDFLARE_STANDARD_1.vcpu,
147
+ memoryGb: CLOUDFLARE_STANDARD_1.memoryGb,
148
+ usdPerHour: (CLOUDFLARE_STANDARD_1.memoryGb * CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY + CLOUDFLARE_STANDARD_1.diskGb * CLOUDFLARE_USD_PER_GB_SECOND_DISK) * HOUR_SECONDS
149
+ }
150
+ },
151
+ excludes: `active CPU, billed at $${CLOUDFLARE_USD_PER_VCPU_SECOND_ACTIVE} per vCPU-second and not measured by Claxedo, and the monthly allowance a Workers Paid plan includes`
152
+ };
126
153
 
127
154
  // ../sandbox-contract/dist/index.mjs
128
155
  var sandboxDriverCredentialFields = {
129
- exe: [{ key: "api_token", label: "API Token", secret: true }],
130
- daytona: [{ key: "api_key", label: "API Key", secret: true }],
131
156
  modal: [
132
157
  { key: "token_id", label: "Token ID", secret: true },
133
158
  { key: "token_secret", label: "Token Secret", secret: true }
@@ -141,68 +166,26 @@ var sandboxDriverCredentialFields = {
141
166
  { key: "api_token", label: "API Token", secret: true },
142
167
  { key: "worker_url", label: "Worker URL" }
143
168
  ],
144
- box: [{ key: "api_key", label: "API Key", secret: true }],
169
+ boat: [{ key: "api_key", label: "API Key", secret: true }],
145
170
  docker: [{ key: "image", label: "Image" }]
146
171
  };
147
172
  var sandboxDriverLabels = {
148
- exe: "exe.dev",
149
- daytona: "Daytona",
150
173
  modal: "Modal",
151
174
  vercel: "Vercel",
152
175
  cloudflare: "Cloudflare",
153
- box: "Box",
176
+ boat: "Boat",
154
177
  docker: "Docker"
155
178
  };
156
179
 
157
180
  // src/driver-catalog.ts
158
181
  var sandboxDriverCatalog = {
159
- exe: {
160
- id: "exe",
161
- label: sandboxDriverLabels.exe,
162
- credentialFields: sandboxDriverCredentialFields.exe,
163
- metadata: {
164
- driverRunsIn: ["worker", "node"],
165
- hostStopBehavior: "not-supported",
166
- hostResumeBehavior: "same-host",
167
- targetAccess: "relay",
168
- secretBrokering: "none",
169
- egressControl: "none",
170
- persistence: {
171
- resume: "same-sandbox",
172
- capture: "none",
173
- clone: true,
174
- captureSource: "not-applicable",
175
- retention: "provider-managed",
176
- restoreMount: "same-resource"
177
- }
178
- }
179
- },
180
- daytona: {
181
- id: "daytona",
182
- label: sandboxDriverLabels.daytona,
183
- credentialFields: sandboxDriverCredentialFields.daytona,
184
- metadata: {
185
- driverRunsIn: ["worker", "node"],
186
- hostStopBehavior: "suspends-host",
187
- hostResumeBehavior: "same-host",
188
- targetAccess: "relay",
189
- secretBrokering: "native",
190
- // The driver translates names to domainAllowList, or addresses to networkAllowList.
191
- egressControl: "hosts-and-cidrs",
192
- persistence: {
193
- resume: "same-sandbox",
194
- capture: "filesystem",
195
- clone: false,
196
- captureSource: "preserved",
197
- retention: "provider-managed",
198
- restoreMount: "new-resource"
199
- }
200
- }
201
- },
202
182
  modal: {
203
183
  id: "modal",
204
184
  label: sandboxDriverLabels.modal,
205
185
  credentialFields: sandboxDriverCredentialFields.modal,
186
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
187
+ runtimeEnv: "callback",
188
+ preparedImage: true,
206
189
  metadata: {
207
190
  driverRunsIn: ["node"],
208
191
  hostStopBehavior: "terminates-host",
@@ -225,6 +208,9 @@ var sandboxDriverCatalog = {
225
208
  id: "vercel",
226
209
  label: sandboxDriverLabels.vercel,
227
210
  credentialFields: sandboxDriverCredentialFields.vercel,
211
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
212
+ runtimeEnv: "callback",
213
+ preparedImage: false,
228
214
  metadata: {
229
215
  driverRunsIn: ["node"],
230
216
  hostStopBehavior: "terminates-host",
@@ -247,30 +233,40 @@ var sandboxDriverCatalog = {
247
233
  id: "cloudflare",
248
234
  label: sandboxDriverLabels.cloudflare,
249
235
  credentialFields: sandboxDriverCredentialFields.cloudflare,
236
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
237
+ runtimeEnv: "callback",
238
+ preparedImage: false,
239
+ billing: cloudflareBilling,
250
240
  metadata: {
251
241
  driverRunsIn: ["worker"],
252
- hostStopBehavior: "not-supported",
242
+ hostStopBehavior: "terminates-host",
253
243
  hostResumeBehavior: "same-host",
244
+ hostWatchesIdle: true,
254
245
  targetAccess: "relay",
255
246
  secretBrokering: "native",
256
247
  // Credential handlers do not restrict unrelated destinations.
257
248
  egressControl: "none",
249
+ // A container snapshot holds the whole root filesystem; the platform keeps it 30 days past its last restore and offers no deletion.
258
250
  persistence: {
259
251
  resume: "replacement-restore",
260
- capture: "directories",
252
+ capture: "filesystem",
261
253
  clone: false,
262
254
  captureSource: "preserved",
263
255
  retention: "provider-managed",
264
- restoreMount: "copy-on-write"
256
+ restoreMount: "new-resource"
265
257
  }
266
258
  }
267
259
  },
268
- box: {
269
- id: "box",
270
- label: sandboxDriverLabels.box,
271
- credentialFields: sandboxDriverCredentialFields.box,
260
+ boat: {
261
+ id: "boat",
262
+ label: sandboxDriverLabels.boat,
263
+ credentialFields: sandboxDriverCredentialFields.boat,
264
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
265
+ runtimeEnv: "callback",
266
+ preparedImage: true,
267
+ billing: boatBilling,
272
268
  metadata: {
273
- driverRunsIn: ["node"],
269
+ driverRunsIn: ["worker", "node"],
274
270
  hostStopBehavior: "suspends-host",
275
271
  hostResumeBehavior: "same-host",
276
272
  targetAccess: "relay",
@@ -283,13 +279,17 @@ var sandboxDriverCatalog = {
283
279
  captureSource: "not-applicable",
284
280
  retention: "provider-managed",
285
281
  restoreMount: "same-resource"
286
- }
282
+ },
283
+ machineClasses: ["small", "default", "large"]
287
284
  }
288
285
  },
289
286
  docker: {
290
287
  id: "docker",
291
288
  label: sandboxDriverLabels.docker,
292
289
  credentialFields: sandboxDriverCredentialFields.docker,
290
+ runtimeNetwork: { controlPlane: "docker-host", relay: "configured" },
291
+ runtimeEnv: "callback",
292
+ preparedImage: true,
293
293
  metadata: {
294
294
  driverRunsIn: ["local"],
295
295
  hostStopBehavior: "terminates-host",
@@ -309,6 +309,16 @@ var sandboxDriverCatalog = {
309
309
  }
310
310
  };
311
311
 
312
+ // ../claxedo-helpers/dist/guards.mjs
313
+ function isRecord(value) {
314
+ return value !== null && typeof value === "object" && !Array.isArray(value);
315
+ }
316
+
317
+ // src/json.ts
318
+ function num(input) {
319
+ return typeof input === "number" ? input : void 0;
320
+ }
321
+
312
322
  // src/drivers/transient-error.ts
313
323
  function textOf(input) {
314
324
  return typeof input === "string" ? input : "";
@@ -317,14 +327,28 @@ function driverErrorSignals(err) {
317
327
  const shaped = isRecord(err) ? err : {};
318
328
  const nested = isRecord(shaped.response) ? shaped.response : {};
319
329
  return {
320
- status: num(nested.status) ?? num(shaped.status),
330
+ // `response.status` is the axios shape; `statusCode` is what SDK-native
331
+ // error classes expose instead.
332
+ status: num(nested.status) ?? num(shaped.status) ?? num(shaped.statusCode),
321
333
  text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
322
334
  };
323
335
  }
324
336
  function isTransientDriverError(err, markers) {
325
- const { status, text: text2 } = driverErrorSignals(err);
337
+ const { status, text } = driverErrorSignals(err);
326
338
  if (status !== void 0 && status >= 500) return true;
327
- return markers.some((marker) => text2.includes(marker));
339
+ return markers.some((marker) => text.includes(marker));
340
+ }
341
+ var TRANSIENT_DRIVER_RETRY_MS = 2e3;
342
+
343
+ // ../claxedo-helpers/dist/deadline.mjs
344
+ async function withDeadlineSignal(timeoutMs, work, parent) {
345
+ const deadline = new AbortController();
346
+ const timer = setTimeout(() => deadline.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
347
+ try {
348
+ return await work(parent ? AbortSignal.any([parent, deadline.signal]) : deadline.signal);
349
+ } finally {
350
+ clearTimeout(timer);
351
+ }
328
352
  }
329
353
 
330
354
  // src/drivers/docker.ts
@@ -381,10 +405,11 @@ function nameFor(workspaceId) {
381
405
  const cleanId = workspaceId.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
382
406
  return `claxedo-${cleanId || "workspace"}-${Date.now().toString(36)}`.slice(0, 63);
383
407
  }
384
- async function docker(args, timeoutMs = DEFAULT_OPERATION_TIMEOUT_MS) {
408
+ async function docker(args, timeoutMs = DEFAULT_OPERATION_TIMEOUT_MS, env) {
385
409
  return execFileAsync("docker", args, {
386
410
  maxBuffer: 20 * 1024 * 1024,
387
- timeout: timeoutMs
411
+ timeout: timeoutMs,
412
+ ...env ? { env: { ...process.env, ...env } } : {}
388
413
  });
389
414
  }
390
415
  function sleep(ms) {
@@ -395,22 +420,25 @@ async function stageLocalAgentAuth(containerId, input) {
395
420
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "claxedo-docker-auth-"));
396
421
  try {
397
422
  const stagedRoot = path.join(tmp, "root");
398
- const copied = await Promise.all(dockerLocalAuthCandidates(input.authHome).map(async (item) => {
399
- const stat = await fs.stat(item.source).catch(() => void 0);
400
- if (!stat) return false;
401
- if (item.kind === "file" && !stat.isFile()) return false;
402
- if (item.kind === "dir" && !stat.isDirectory()) return false;
423
+ let copied = false;
424
+ for (const item of dockerLocalAuthCandidates(input.authHome)) {
425
+ const stat = await fs.stat(item.source).catch((error) => {
426
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
427
+ throw error;
428
+ });
429
+ if (!stat) continue;
430
+ if (item.kind === "file" && !stat.isFile()) continue;
431
+ if (item.kind === "dir" && !stat.isDirectory()) continue;
403
432
  const target = path.join(stagedRoot, item.target.replace(/^\/root\/?/, ""));
404
433
  await fs.mkdir(path.dirname(target), { recursive: true });
405
434
  if (item.kind === "dir") await fs.cp(item.source, target, { recursive: true });
406
435
  else await fs.copyFile(item.source, target);
407
- return true;
408
- }));
409
- if (!copied.some(Boolean)) return;
436
+ copied = true;
437
+ }
438
+ if (!copied) return;
410
439
  await input.docker(["cp", stagedRoot, `${containerId}:${AUTH_STAGE_DIR}`], input.timeoutMs);
411
440
  } finally {
412
- await fs.rm(tmp, { recursive: true, force: true }).catch(() => {
413
- });
441
+ await fs.rm(tmp, { recursive: true, force: true });
414
442
  }
415
443
  }
416
444
  function authHydrationScript() {
@@ -432,8 +460,8 @@ function bootScript(directory, runtimeCommand) {
432
460
  `exec ${runtimeCommand}`
433
461
  ].join(" && ");
434
462
  }
435
- function envArgs(input) {
436
- return Object.entries(input).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
463
+ function envNameArgs(input) {
464
+ return Object.keys(input).flatMap((key) => ["--env", key]);
437
465
  }
438
466
  function labelArgs(input) {
439
467
  return Object.entries(input).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
@@ -462,7 +490,9 @@ function createDockerSandboxDriver(options) {
462
490
  if (input.bootSource?.kind === "driver-snapshot") {
463
491
  throw new Error("Docker SandboxDriver does not support driver snapshots; use image boot");
464
492
  }
465
- return input.bootSource?.kind === "image" ? input.bootSource.image : input.snapshot ?? options.image;
493
+ return assertSandboxImageReference(
494
+ input.bootSource?.kind === "image" ? input.bootSource.image : input.snapshot ?? options.image
495
+ );
466
496
  }
467
497
  function bootEnv(input, hostId) {
468
498
  return workspaceRuntimeBootEnv({
@@ -473,7 +503,7 @@ function createDockerSandboxDriver(options) {
473
503
  host: "0.0.0.0",
474
504
  source: input.source,
475
505
  env: input.env,
476
- runner: options.runner
506
+ nativeHarness: options.nativeHarness
477
507
  });
478
508
  }
479
509
  async function readyTarget(input, sandboxId, hostId) {
@@ -499,9 +529,7 @@ function createDockerSandboxDriver(options) {
499
529
  let error = "workspace runtime not ready";
500
530
  while (Date.now() < until) {
501
531
  try {
502
- const res = await healthFetch(`${url}/global/health`, {
503
- signal: AbortSignal.timeout(Math.min(2e3, Math.max(1, healthTimeoutMs)))
504
- });
532
+ const res = await withDeadlineSignal(Math.min(2e3, Math.max(1, healthTimeoutMs)), (signal) => healthFetch(`${url}/global/health`, { signal }));
505
533
  if (res.ok) return;
506
534
  error = `${res.status} ${res.statusText}`;
507
535
  } catch (err) {
@@ -531,17 +559,17 @@ function createDockerSandboxDriver(options) {
531
559
  ...labelArgs(input.labels),
532
560
  "-p",
533
561
  `127.0.0.1::${runtimePort(input)}`,
534
- ...envArgs(env),
562
+ ...envNameArgs(env),
535
563
  "--entrypoint",
536
564
  "sh",
537
565
  image(input),
538
566
  "-lc",
539
567
  bootScript(workspaceDirectory(input), runtimeCommand)
540
- ], 12e4).catch((err) => {
568
+ ], 12e4, env).catch((err) => {
541
569
  if (transientDriverError(err)) return void 0;
542
570
  throw err;
543
571
  });
544
- if (!created) return { provisioning: true, retryAfterMs: 2e3 };
572
+ if (!created) return { provisioning: true, retryAfterMs: TRANSIENT_DRIVER_RETRY_MS };
545
573
  const sandboxId = String(created.stdout ?? "").trim() || hostId;
546
574
  await stageLocalAgentAuth(sandboxId, { authHome, syncLocalAuth, docker: dockerCommand, timeoutMs });
547
575
  await dockerCommand(["start", sandboxId], timeoutMs);
@@ -550,7 +578,7 @@ function createDockerSandboxDriver(options) {
550
578
  async function resumeHost(input) {
551
579
  const sandboxId = input.lease.sandboxId ?? input.lease.sandbox_id;
552
580
  if (!sandboxId) return ensureHost(input.ensure);
553
- await dockerCommand(["inspect", "-f", "{{.Id}}", sandboxId], timeoutMs).catch(() => void 0);
581
+ await dockerCommand(["inspect", "-f", "{{.Id}}", sandboxId], timeoutMs);
554
582
  await dockerCommand(["start", sandboxId], timeoutMs).catch((err) => {
555
583
  if (transientDriverError(err)) return void 0;
556
584
  throw err;
@@ -0,0 +1,14 @@
1
+ import type { SandboxDriver } from "../contract";
2
+ import { localBrokeringTestDriverCatalogEntry } from "../driver-catalog";
3
+ export type LocalBrokeringDriverOptions = {
4
+ root: string;
5
+ executable: string;
6
+ args: string[];
7
+ allowedOrigins: readonly string[];
8
+ directOrigins: readonly string[];
9
+ upstreams?: Readonly<Record<string, string>>;
10
+ inheritedEnv?: Record<string, string>;
11
+ };
12
+ export declare function createLocalBrokeringSandboxDriver(options: LocalBrokeringDriverOptions): SandboxDriver & {
13
+ catalogEntry: typeof localBrokeringTestDriverCatalogEntry;
14
+ };