@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
@@ -0,0 +1,886 @@
1
+ // ../claxedo-helpers/dist/index.mjs
2
+ function sleep(ms) {
3
+ return new Promise((resolve) => {
4
+ setTimeout(resolve, ms);
5
+ });
6
+ }
7
+
8
+ // ../claxedo-helpers/dist/deadline.mjs
9
+ async function withDeadlineSignal(timeoutMs, work, parent) {
10
+ const deadline = new AbortController();
11
+ const timer = setTimeout(() => deadline.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
12
+ try {
13
+ return await work(parent ? AbortSignal.any([parent, deadline.signal]) : deadline.signal);
14
+ } finally {
15
+ clearTimeout(timer);
16
+ }
17
+ }
18
+
19
+ // src/constants.ts
20
+ var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
21
+
22
+ // src/contract.ts
23
+ var SANDBOX_IMAGE_LABEL = "image";
24
+
25
+ // src/command.ts
26
+ function shell(input) {
27
+ return `'${input.replace(/'/g, `'"'"'`)}'`;
28
+ }
29
+ function envFile(input) {
30
+ return Object.entries(input).map(([key, value]) => {
31
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
32
+ throw new Error(`environment variable ${JSON.stringify(key)} cannot be exported through an env file`);
33
+ }
34
+ return `export ${key}=${shell(value)}`;
35
+ }).join("\n") + "\n";
36
+ }
37
+
38
+ // src/driver-billing.ts
39
+ var HOUR_SECONDS = 3600;
40
+ var boatBilling = {
41
+ source: { url: "https://docs.boat.dev/pricing", readOn: "2026-10-12" },
42
+ meter: "Boat bills machine time per second while a sandbox runs, at an hourly rate per size; a stopped sandbox costs nothing.",
43
+ rates: {
44
+ small: { size: "small", vcpu: 2, memoryGb: 4, usdPerHour: 0.018 },
45
+ default: { size: "default", vcpu: 4, memoryGb: 8, usdPerHour: 0.036 },
46
+ large: { size: "large", vcpu: 8, memoryGb: 16, usdPerHour: 0.072 }
47
+ }
48
+ };
49
+ var AGENT37_USD_PER_MONTH = { vcpu: 0.8, memoryGb: 0.7, diskGb: 0.09 };
50
+ var AGENT37_MONTH_HOURS = 730;
51
+ var agent37MachineShapes = {
52
+ small: { cpu: 2, memory: 4, disk: 10 },
53
+ default: { cpu: 4, memory: 8, disk: 20 },
54
+ large: { cpu: 4, memory: 16, disk: 40 }
55
+ };
56
+ function agent37Rate(shape) {
57
+ const usdPerMonth = shape.cpu * AGENT37_USD_PER_MONTH.vcpu + shape.memory * AGENT37_USD_PER_MONTH.memoryGb + shape.disk * AGENT37_USD_PER_MONTH.diskGb;
58
+ return { size: `${shape.cpu} vCPU / ${shape.memory} GB`, vcpu: shape.cpu, memoryGb: shape.memory, usdPerHour: usdPerMonth / AGENT37_MONTH_HOURS };
59
+ }
60
+ var agent37Billing = {
61
+ source: { url: "https://www.agent37.com/docs/agents-api/billing", readOn: "2026-11-11" },
62
+ 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.",
63
+ rates: {
64
+ small: agent37Rate(agent37MachineShapes.small),
65
+ default: agent37Rate(agent37MachineShapes.default),
66
+ large: agent37Rate(agent37MachineShapes.large)
67
+ },
68
+ excludes: `the disk of a stopped instance, billed at $${AGENT37_USD_PER_MONTH.diskGb} per GB a month until the instance is deleted`
69
+ };
70
+ var CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY = 25e-7;
71
+ var CLOUDFLARE_USD_PER_GB_SECOND_DISK = 7e-8;
72
+ var CLOUDFLARE_USD_PER_VCPU_SECOND_ACTIVE = 2e-5;
73
+ var CLOUDFLARE_STANDARD_1 = { size: "standard-1", vcpu: 0.5, memoryGb: 4, diskGb: 8 };
74
+ var cloudflareBilling = {
75
+ source: { url: "https://developers.cloudflare.com/containers/pricing/", readOn: "2026-10-12" },
76
+ 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.",
77
+ rates: {
78
+ default: {
79
+ size: CLOUDFLARE_STANDARD_1.size,
80
+ vcpu: CLOUDFLARE_STANDARD_1.vcpu,
81
+ memoryGb: CLOUDFLARE_STANDARD_1.memoryGb,
82
+ usdPerHour: (CLOUDFLARE_STANDARD_1.memoryGb * CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY + CLOUDFLARE_STANDARD_1.diskGb * CLOUDFLARE_USD_PER_GB_SECOND_DISK) * HOUR_SECONDS
83
+ }
84
+ },
85
+ 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`
86
+ };
87
+
88
+ // ../sandbox-contract/dist/index.mjs
89
+ var sandboxDriverCredentialFields = {
90
+ modal: [
91
+ { key: "token_id", label: "Token ID", secret: true },
92
+ { key: "token_secret", label: "Token Secret", secret: true }
93
+ ],
94
+ vercel: [
95
+ { key: "access_token", label: "Access Token", secret: true },
96
+ { key: "team_id", label: "Team ID" },
97
+ { key: "project_id", label: "Project ID" }
98
+ ],
99
+ cloudflare: [
100
+ { key: "api_token", label: "API Token", secret: true },
101
+ { key: "worker_url", label: "Worker URL" }
102
+ ],
103
+ boat: [{ key: "api_key", label: "API Key", secret: true }],
104
+ agent37: [{ key: "api_key", label: "API Key", secret: true }],
105
+ docker: [{ key: "image", label: "Image" }]
106
+ };
107
+ var sandboxDriverLabels = {
108
+ modal: "Modal",
109
+ vercel: "Vercel",
110
+ cloudflare: "Cloudflare",
111
+ boat: "Boat",
112
+ agent37: "Agent37",
113
+ docker: "Docker"
114
+ };
115
+
116
+ // src/driver-catalog.ts
117
+ var sandboxDriverCatalog = {
118
+ modal: {
119
+ id: "modal",
120
+ label: sandboxDriverLabels.modal,
121
+ credentialFields: sandboxDriverCredentialFields.modal,
122
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
123
+ runtimeEnv: "callback",
124
+ preparedImage: true,
125
+ metadata: {
126
+ driverRunsIn: ["node"],
127
+ hostStopBehavior: "terminates-host",
128
+ hostResumeBehavior: "replacement-host",
129
+ targetAccess: "relay",
130
+ secretBrokering: "none",
131
+ // The driver does not install Modal's domain allowlist or alpha sidecar.
132
+ egressControl: "none",
133
+ persistence: {
134
+ resume: "replacement-restore",
135
+ capture: "filesystem",
136
+ clone: false,
137
+ captureSource: "preserved",
138
+ retention: "provider-managed",
139
+ restoreMount: "new-resource"
140
+ }
141
+ }
142
+ },
143
+ vercel: {
144
+ id: "vercel",
145
+ label: sandboxDriverLabels.vercel,
146
+ credentialFields: sandboxDriverCredentialFields.vercel,
147
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
148
+ runtimeEnv: "callback",
149
+ preparedImage: false,
150
+ metadata: {
151
+ driverRunsIn: ["node"],
152
+ hostStopBehavior: "terminates-host",
153
+ hostResumeBehavior: "replacement-host",
154
+ targetAccess: "relay",
155
+ secretBrokering: "native",
156
+ // The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
157
+ egressControl: "hosts",
158
+ persistence: {
159
+ resume: "replacement-restore",
160
+ capture: "filesystem",
161
+ clone: false,
162
+ captureSource: "stopped",
163
+ retention: "explicit",
164
+ restoreMount: "new-resource"
165
+ }
166
+ }
167
+ },
168
+ cloudflare: {
169
+ id: "cloudflare",
170
+ label: sandboxDriverLabels.cloudflare,
171
+ credentialFields: sandboxDriverCredentialFields.cloudflare,
172
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
173
+ runtimeEnv: "callback",
174
+ preparedImage: false,
175
+ billing: cloudflareBilling,
176
+ metadata: {
177
+ driverRunsIn: ["worker"],
178
+ hostStopBehavior: "terminates-host",
179
+ hostResumeBehavior: "same-host",
180
+ hostWatchesIdle: true,
181
+ targetAccess: "relay",
182
+ secretBrokering: "native",
183
+ // The sandbox Worker starts the container without internet and routes each allowed host through itself; a Worker that cannot confirm that refuses the start.
184
+ egressControl: "hosts",
185
+ // A container snapshot holds the whole root filesystem; the platform keeps it 30 days past its last restore and offers no deletion.
186
+ persistence: {
187
+ resume: "replacement-restore",
188
+ capture: "filesystem",
189
+ clone: false,
190
+ captureSource: "preserved",
191
+ retention: "provider-managed",
192
+ restoreMount: "new-resource"
193
+ }
194
+ }
195
+ },
196
+ boat: {
197
+ id: "boat",
198
+ label: sandboxDriverLabels.boat,
199
+ credentialFields: sandboxDriverCredentialFields.boat,
200
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
201
+ runtimeEnv: "callback",
202
+ preparedImage: true,
203
+ billing: boatBilling,
204
+ metadata: {
205
+ driverRunsIn: ["worker", "node"],
206
+ hostStopBehavior: "suspends-host",
207
+ hostResumeBehavior: "same-host",
208
+ targetAccess: "relay",
209
+ secretBrokering: "none",
210
+ egressControl: "none",
211
+ persistence: {
212
+ resume: "same-sandbox",
213
+ capture: "none",
214
+ clone: false,
215
+ captureSource: "not-applicable",
216
+ retention: "provider-managed",
217
+ restoreMount: "same-resource"
218
+ },
219
+ machineClasses: ["small", "default", "large"]
220
+ }
221
+ },
222
+ agent37: {
223
+ id: "agent37",
224
+ label: sandboxDriverLabels.agent37,
225
+ credentialFields: sandboxDriverCredentialFields.agent37,
226
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
227
+ runtimeEnv: "callback",
228
+ preparedImage: true,
229
+ billing: agent37Billing,
230
+ metadata: {
231
+ driverRunsIn: ["worker", "node"],
232
+ hostStopBehavior: "suspends-host",
233
+ hostResumeBehavior: "same-host",
234
+ targetAccess: "relay",
235
+ secretBrokering: "none",
236
+ // An instance's network policy lists domains, reached over HTTPS on 443 alone, and IPv4 ranges; private ranges stay blocked whatever it lists.
237
+ egressControl: "hosts-and-cidrs",
238
+ persistence: {
239
+ resume: "same-sandbox",
240
+ capture: "none",
241
+ clone: false,
242
+ captureSource: "not-applicable",
243
+ retention: "provider-managed",
244
+ restoreMount: "same-resource"
245
+ },
246
+ machineClasses: ["small", "default", "large"]
247
+ }
248
+ },
249
+ docker: {
250
+ id: "docker",
251
+ label: sandboxDriverLabels.docker,
252
+ credentialFields: sandboxDriverCredentialFields.docker,
253
+ runtimeNetwork: { controlPlane: "docker-host", relay: "configured" },
254
+ runtimeEnv: "callback",
255
+ preparedImage: true,
256
+ metadata: {
257
+ driverRunsIn: ["local"],
258
+ hostStopBehavior: "terminates-host",
259
+ hostResumeBehavior: "same-host",
260
+ targetAccess: "loopback",
261
+ secretBrokering: "none",
262
+ egressControl: "none",
263
+ persistence: {
264
+ resume: "replacement-restore",
265
+ capture: "same-resource",
266
+ clone: false,
267
+ captureSource: "preserved",
268
+ retention: "provider-managed",
269
+ restoreMount: "same-resource"
270
+ }
271
+ }
272
+ }
273
+ };
274
+
275
+ // src/runtime-version.ts
276
+ var SERVED_WORKSPACE_RUNTIME_FLOOR = {
277
+ version: "0.10.1",
278
+ image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
279
+ };
280
+ var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
281
+ var SANDBOX_IMAGE_RUNTIME_VERSION_FILE = "opt/workspace-runtime/workspace-runtime-version";
282
+ function servedRuntimeVersionTest(file) {
283
+ return `version=$(cat ${file} 2>/dev/null) && [ -n "$version" ] && [ "$(printf '%s\\n%s\\n' ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} "$version" | sort -V | head -n 1)" = ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} ]`;
284
+ }
285
+
286
+ // src/image-name.ts
287
+ function assertSandboxImageReference(image) {
288
+ if (!image || image !== image.trim() || image.startsWith("-") || /\s/.test(image)) {
289
+ throw new Error(`sandbox image reference is not a legal image identifier: ${JSON.stringify(image)}`);
290
+ }
291
+ return image;
292
+ }
293
+
294
+ // ../claxedo-helpers/dist/guards.mjs
295
+ function isRecord(value) {
296
+ return value !== null && typeof value === "object" && !Array.isArray(value);
297
+ }
298
+ function asRecord(value) {
299
+ return isRecord(value) ? value : void 0;
300
+ }
301
+ function asNumber(value) {
302
+ return typeof value === "number" ? value : void 0;
303
+ }
304
+
305
+ // src/lease-failure.ts
306
+ var SandboxRuntimeBootError = class extends Error {
307
+ runtimeBootFailed = true;
308
+ constructor(reason) {
309
+ super(reason);
310
+ this.name = "SandboxRuntimeBootError";
311
+ }
312
+ };
313
+ var SandboxHostGoneError = class extends Error {
314
+ hostGone = true;
315
+ constructor(reason) {
316
+ super(reason);
317
+ this.name = "SandboxHostGoneError";
318
+ }
319
+ };
320
+ var WORKSPACE_RUNTIME_BOOT_FAILED_LINE = "workspace_runtime_boot_failed: ";
321
+ function workspaceRuntimeBootReason(diagnostic) {
322
+ const at = diagnostic.lastIndexOf(WORKSPACE_RUNTIME_BOOT_FAILED_LINE);
323
+ if (at < 0) return void 0;
324
+ const [reason = ""] = diagnostic.slice(at + WORKSPACE_RUNTIME_BOOT_FAILED_LINE.length).split(/\n\s+at /);
325
+ return reason.trim().replace(/^Error: /, "") || void 0;
326
+ }
327
+ function runtimeBootDiagnostic(log, maxChars) {
328
+ const trimmed = log.trim();
329
+ const reason = workspaceRuntimeBootReason(trimmed);
330
+ if (reason === void 0) return trimmed.slice(-maxChars);
331
+ return `${WORKSPACE_RUNTIME_BOOT_FAILED_LINE}${reason}`.slice(0, maxChars);
332
+ }
333
+
334
+ // src/runtime-env.ts
335
+ var WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS = [
336
+ "WORKSPACE_RUNTIME_WORKSPACE_ID",
337
+ "WORKSPACE_RUNTIME_HOST_ID",
338
+ "WORKSPACE_RUNTIME_RELAY_WORKSPACE_IDS"
339
+ ];
340
+ function workspaceRuntimeIdentityEnvConflicts(env) {
341
+ if (!env) return [];
342
+ return WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS.filter((key) => env[key] !== void 0);
343
+ }
344
+ function assertWorkspaceRuntimeIdentityEnv(env) {
345
+ const conflicts = workspaceRuntimeIdentityEnvConflicts(env);
346
+ if (conflicts.length === 0) return;
347
+ throw new Error(
348
+ `sandbox env cannot set runtime identity ${conflicts.join(", ")}: the driver's placement owns the identity its target reports and the lease records`
349
+ );
350
+ }
351
+ var WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS = /* @__PURE__ */ new Set([
352
+ ...WORKSPACE_RUNTIME_IDENTITY_ENV_KEYS,
353
+ "WORKSPACE_RUNTIME_DIRECTORY",
354
+ "WORKSPACE_RUNTIME_PORT",
355
+ "WORKSPACE_RUNTIME_HOST",
356
+ "WORKSPACE_RUNTIME_SOURCE_KIND",
357
+ "WORKSPACE_RUNTIME_GIT_REPO_URL",
358
+ "WORKSPACE_RUNTIME_GIT_BRANCH",
359
+ "WORKSPACE_RUNTIME_NATIVE_HARNESS",
360
+ "WORKSPACE_RUNTIME_RELAY_JWKS_URL",
361
+ "WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL",
362
+ "WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL"
363
+ ]);
364
+ function workspaceRuntimeBootSecrets(env) {
365
+ return Object.entries(env).flatMap(([key, value]) => WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS.has(key) ? [] : [value]);
366
+ }
367
+ var REDACTED_VALUE_MIN_LENGTH = 8;
368
+ function redactWorkspaceRuntimeBootSecrets(text, secrets) {
369
+ return secrets.filter((secret) => secret.length >= REDACTED_VALUE_MIN_LENGTH).reduce((output, secret) => output.split(secret).join("[redacted]"), text);
370
+ }
371
+ function workspaceRuntimeBootEnv(input) {
372
+ assertWorkspaceRuntimeIdentityEnv(input.env);
373
+ const env = {
374
+ ...workspaceRuntimeTargetEnv(input),
375
+ ...workspaceRuntimeSourceEnv(input),
376
+ ...input.env
377
+ };
378
+ if (input.nativeHarness) env.WORKSPACE_RUNTIME_NATIVE_HARNESS = input.nativeHarness;
379
+ if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
380
+ if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
381
+ if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
382
+ if (input.controlEnv?.sessionAuthorityUrl) env.WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL = input.controlEnv.sessionAuthorityUrl;
383
+ return env;
384
+ }
385
+ function workspaceRuntimeTargetEnv(input) {
386
+ return {
387
+ WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
388
+ ...input.hostId ? { WORKSPACE_RUNTIME_HOST_ID: input.hostId } : {},
389
+ WORKSPACE_RUNTIME_DIRECTORY: input.directory,
390
+ WORKSPACE_RUNTIME_PORT: String(input.port),
391
+ ...input.host ? { WORKSPACE_RUNTIME_HOST: input.host } : {}
392
+ };
393
+ }
394
+ function workspaceRuntimeSourceEnv(input) {
395
+ if (!input.source || input.source.kind === "empty") return { WORKSPACE_RUNTIME_SOURCE_KIND: "empty" };
396
+ return {
397
+ WORKSPACE_RUNTIME_SOURCE_KIND: "git",
398
+ WORKSPACE_RUNTIME_GIT_REPO_URL: input.source.repoUrl,
399
+ ...input.source.branch ? { WORKSPACE_RUNTIME_GIT_BRANCH: input.source.branch } : {}
400
+ };
401
+ }
402
+
403
+ // src/drivers/agent37-client.ts
404
+ var INSTANCE_STATUSES = [
405
+ "provisioning",
406
+ "running",
407
+ "stopping",
408
+ "stopped",
409
+ "starting",
410
+ "restarting",
411
+ "updating",
412
+ "sleeping",
413
+ "waking",
414
+ "failed",
415
+ "deleting",
416
+ "deleted"
417
+ ];
418
+ var DEFAULT_BASE_URL = "https://api.agent37.com/v1";
419
+ var DEFAULT_TIMEOUT_MS = 12e4;
420
+ var CREATE_TIMEOUT_MS = 5 * 6e4;
421
+ var REGISTER_TIMEOUT_MS = 10 * 6e4;
422
+ var KEY_REFUSED_CODES = /* @__PURE__ */ new Set(["invalid_api_key", "ip_not_allowed"]);
423
+ var Agent37ApiError = class extends Error {
424
+ constructor(message, code, status) {
425
+ super(message);
426
+ this.code = code;
427
+ this.status = status;
428
+ this.name = "Agent37ApiError";
429
+ this.providerKeyRefused = KEY_REFUSED_CODES.has(code);
430
+ }
431
+ /** Agent37 answers these to the key itself; an instance of another workspace reads as `not_found`. */
432
+ providerKeyRefused;
433
+ };
434
+ function invalidAgent37Response() {
435
+ throw new Agent37ApiError("Agent37 API returned an invalid response", "invalid_response");
436
+ }
437
+ function responseCount(value) {
438
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : invalidAgent37Response();
439
+ }
440
+ function instance(body) {
441
+ const id = body?.id;
442
+ const status = INSTANCE_STATUSES.find((name) => name === body?.status);
443
+ const resources = asRecord(body?.resources);
444
+ if (typeof id !== "string" || !/^[a-z0-9]{10}$/.test(id) || !status || !resources) invalidAgent37Response();
445
+ const ports = Array.isArray(body?.public_ports) ? body.public_ports : [];
446
+ return {
447
+ id,
448
+ status,
449
+ ...typeof body?.image_ref === "string" ? { imageRef: body.image_ref } : {},
450
+ resources: { cpu: responseCount(resources.cpu), memory: responseCount(resources.memory), disk: responseCount(resources.disk) },
451
+ metadata: Object.fromEntries(Object.entries(asRecord(body?.metadata) ?? {}).flatMap(([key, value]) => typeof value === "string" ? [[key, value]] : [])),
452
+ publicPorts: ports.flatMap((entry) => {
453
+ const port = asRecord(entry);
454
+ return typeof port?.port === "number" && typeof port.url === "string" && port.url.startsWith("https://") ? [{ port: port.port, url: port.url }] : [];
455
+ })
456
+ };
457
+ }
458
+ function createAgent37Client(options) {
459
+ const base = new URL(options.baseUrl ?? DEFAULT_BASE_URL);
460
+ if (base.protocol !== "https:" || base.username || base.password || base.search || base.hash) {
461
+ throw new Error("Agent37 endpoint requires HTTPS without credentials, query or fragment");
462
+ }
463
+ const baseUrl = base.href.replace(/\/+$/, "");
464
+ const fetchImpl = options.fetchImpl ?? fetch;
465
+ async function api(path2, init) {
466
+ const method = init?.method ?? "GET";
467
+ return withDeadlineSignal(init?.timeoutMs ?? options.timeoutMs ?? DEFAULT_TIMEOUT_MS, async (signal) => {
468
+ let response;
469
+ try {
470
+ response = await fetchImpl(`${baseUrl}${path2}`, {
471
+ method,
472
+ redirect: "manual",
473
+ headers: {
474
+ Authorization: `Bearer ${options.apiKey}`,
475
+ ...init?.body === void 0 ? {} : { "Content-Type": "application/json" }
476
+ },
477
+ ...init?.body === void 0 ? {} : { body: JSON.stringify(init.body) },
478
+ signal
479
+ });
480
+ } catch {
481
+ throw new Agent37ApiError(`Agent37 ${method} ${path2} failed before acknowledgement`, "transport_failed");
482
+ }
483
+ const body = asRecord(await response.json().catch(() => void 0));
484
+ if (!response.ok) {
485
+ const code = asRecord(body?.error)?.code ?? body?.error;
486
+ throw new Agent37ApiError(`Agent37 ${method} ${path2} was rejected (${typeof code === "string" ? code : `http_${response.status}`})`, typeof code === "string" ? code : `http_${response.status}`, response.status);
487
+ }
488
+ return body;
489
+ });
490
+ }
491
+ const path = (id) => `/instances/${encodeURIComponent(id)}`;
492
+ const lifecycle = async (id, action, body, timeoutMs) => {
493
+ const answer = await api(`${path(id)}/${action}`, { method: "POST", ...body === void 0 ? {} : { body }, ...timeoutMs ? { timeoutMs } : {} });
494
+ return INSTANCE_STATUSES.find((name) => name === answer?.status) ?? invalidAgent37Response();
495
+ };
496
+ return {
497
+ async templateExists(name) {
498
+ try {
499
+ await api(`/templates/${encodeURIComponent(name)}`);
500
+ return true;
501
+ } catch (err) {
502
+ if (err instanceof Agent37ApiError && err.code === "not_found") return false;
503
+ throw err;
504
+ }
505
+ },
506
+ /** A name another registration took first holds the same image and command, since the name is derived from both. */
507
+ async registerTemplate(input) {
508
+ await api("/templates", { method: "POST", body: { name: input.name, image_ref: input.imageRef, entrypoint: input.entrypoint }, timeoutMs: REGISTER_TIMEOUT_MS }).catch((err) => {
509
+ if (!(err instanceof Agent37ApiError && err.code === "template_conflict")) throw err;
510
+ });
511
+ },
512
+ async create(input) {
513
+ return instance(await api("/instances", {
514
+ method: "POST",
515
+ timeoutMs: CREATE_TIMEOUT_MS,
516
+ body: {
517
+ template: input.template,
518
+ name: input.name,
519
+ metadata: input.metadata,
520
+ env: input.env,
521
+ network: input.network,
522
+ resources: input.resources,
523
+ public_ports: [{ port: input.publicPort }],
524
+ mount_path: input.mountPath
525
+ }
526
+ }));
527
+ },
528
+ async get(id) {
529
+ const value = instance(await api(path(id)));
530
+ return value.id === id ? value : invalidAgent37Response();
531
+ },
532
+ async list() {
533
+ const body = await api("/instances");
534
+ return (Array.isArray(body?.data) ? body.data : invalidAgent37Response()).map((entry) => instance(asRecord(entry)));
535
+ },
536
+ /** `env` reaches the container at its next start, restart, update or resize; `network` applies to a running one within seconds; `metadata` replaces what is stored. */
537
+ async edit(id, input) {
538
+ return instance(await api(path(id), { method: "PATCH", body: input }));
539
+ },
540
+ start: (id) => lifecycle(id, "start"),
541
+ stop: (id) => lifecycle(id, "stop"),
542
+ restart: (id) => lifecycle(id, "restart"),
543
+ /** Moves the instance onto `template`, keeping only its mount path; a stopped one stays stopped and boots that image at its next start. */
544
+ update: (id, template) => lifecycle(id, "update", { template }, CREATE_TIMEOUT_MS),
545
+ /** Recreates the running container at the new size. Disk only grows. */
546
+ resize: (id, resources) => lifecycle(id, "resize", resources, CREATE_TIMEOUT_MS),
547
+ async publishPort(id, port) {
548
+ const body = await api(`${path(id)}/public-ports`, { method: "POST", body: { port } });
549
+ return typeof body?.url === "string" && body.url.startsWith("https://") ? body.url : invalidAgent37Response();
550
+ },
551
+ async delete(id) {
552
+ await api(path(id), { method: "DELETE" });
553
+ },
554
+ /** Runs through `sh -c` as the image's user; a command that exits nonzero is an answer, not an error. */
555
+ async exec(id, command) {
556
+ const body = await api(`${path(id)}/exec`, { method: "POST", body: { command } });
557
+ return { exitCode: responseCount(body?.exit_code), stdout: typeof body?.stdout === "string" ? body.stdout : invalidAgent37Response() };
558
+ },
559
+ /** Answers in any state; Agent37 starts a container that exited again, and `restartCount` counts each boot after the first. */
560
+ async containerHealth(id, tail) {
561
+ const body = await api(`${path(id)}/logs?tail=${tail}`);
562
+ const health = asRecord(body?.health);
563
+ return {
564
+ logs: typeof body?.logs === "string" ? body.logs : "",
565
+ running: health?.running === true,
566
+ restartCount: typeof health?.restart_count === "number" ? health.restart_count : 0
567
+ };
568
+ }
569
+ };
570
+ }
571
+
572
+ // src/drivers/transient-error.ts
573
+ function textOf(input) {
574
+ return typeof input === "string" ? input : "";
575
+ }
576
+ function driverErrorSignals(err) {
577
+ const shaped = isRecord(err) ? err : {};
578
+ const nested = isRecord(shaped.response) ? shaped.response : {};
579
+ return {
580
+ // `response.status` is the axios shape; `statusCode` is what SDK-native
581
+ // error classes expose instead.
582
+ status: asNumber(nested.status) ?? asNumber(shaped.status) ?? asNumber(shaped.statusCode),
583
+ text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
584
+ };
585
+ }
586
+ function isTransientDriverError(err, markers) {
587
+ const { status, text } = driverErrorSignals(err);
588
+ if (status !== void 0 && status >= 500) return true;
589
+ return markers.some((marker) => text.includes(marker));
590
+ }
591
+ var TRANSIENT_DRIVER_RETRY_MS = 2e3;
592
+
593
+ // src/drivers/agent37.ts
594
+ var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
595
+ var DEFAULT_WORKSPACE_DIR = "/workspace";
596
+ var DEFAULT_PROVISION_TIMEOUT_MS = 12e4;
597
+ var STATUS_POLL_MS = 1e3;
598
+ var DEFAULT_HEALTH_TIMEOUT_MS = 10 * 6e4;
599
+ var DEFAULT_HEALTH_INTERVAL_MS = 500;
600
+ var HEALTH_PROBE_TIMEOUT_MS = 1e4;
601
+ var DEFAULT_CONTAINER_CHECK_MS = 5e3;
602
+ var DIAGNOSTIC_LOG_LINES = 40;
603
+ var DIAGNOSTIC_CHARS = 600;
604
+ var PERSISTENT_ROOT = "/claxedo-persistent";
605
+ var PERSISTENT_HOMES = ["claxedo", "workspace-runtime"];
606
+ var RUNTIME_ENV_VARIABLE = "CLAXEDO_RUNTIME_ENV";
607
+ var NETWORK_ALLOW_LIMIT = 100;
608
+ var INSTANCE_NAME_MAX_CHARS = 60;
609
+ var TRANSIENT_MARKERS = ["transport_failed", "try_again", "no_capacity", "capacity_unavailable"];
610
+ var SERVING_STATUSES = /* @__PURE__ */ new Set(["provisioning", "running", "starting", "restarting", "updating", "waking"]);
611
+ var SETTLING_STATUSES = /* @__PURE__ */ new Set(["provisioning", "starting", "restarting", "updating", "waking", "stopping"]);
612
+ var GONE_STATUSES = /* @__PURE__ */ new Set(["deleting", "deleted"]);
613
+ var Agent37DriverError = class extends Error {
614
+ constructor(message) {
615
+ super(message);
616
+ this.name = "Agent37DriverError";
617
+ }
618
+ };
619
+ function instanceGoneOrThrow(err) {
620
+ if (err instanceof Agent37ApiError && err.code === "not_found") throw new SandboxHostGoneError(err.message);
621
+ throw err;
622
+ }
623
+ function runtimeEntrypoint(runtimeCommand) {
624
+ return ["/bin/bash", "-lc", [
625
+ `eval "$${RUNTIME_ENV_VARIABLE}" || exit 1`,
626
+ `unset ${RUNTIME_ENV_VARIABLE}`,
627
+ `link() { mkdir -p "${PERSISTENT_ROOT}/$1" "$(dirname "$2")" && { [ -L "$2" ] || ln -s "${PERSISTENT_ROOT}/$1" "$2"; }; }`,
628
+ `[ -L "$WORKSPACE_RUNTIME_DIRECTORY" ] || [ ! -e "$WORKSPACE_RUNTIME_DIRECTORY" ] || rmdir "$WORKSPACE_RUNTIME_DIRECTORY" || exit 1`,
629
+ `link workspace "$WORKSPACE_RUNTIME_DIRECTORY" || exit 1`,
630
+ `for home in ${PERSISTENT_HOMES.join(" ")}; do [ -L "$HOME/.$home" ] || rm -rf "$HOME/.$home"; link "$home" "$HOME/.$home" || exit 1; done`,
631
+ `cd "$WORKSPACE_RUNTIME_DIRECTORY" || exit 1`,
632
+ `${runtimeCommand} &`,
633
+ `child=$!`,
634
+ `trap 'kill -TERM "$child" 2>/dev/null' TERM INT`,
635
+ `while kill -0 "$child" 2>/dev/null; do wait "$child"; status=$?; done`,
636
+ `exit "$status"`
637
+ ].join("\n")];
638
+ }
639
+ async function templateName(image, entrypoint) {
640
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([image, entrypoint])));
641
+ return `claxedo-runtime-${[...new Uint8Array(digest)].slice(0, 8).map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
642
+ }
643
+ function instanceNetwork(input) {
644
+ if (!input.net || input.net.mode === "allow-all") return "allow-all";
645
+ const allow = [...input.net.hosts ?? [], ...(input.net.cidrs ?? []).filter((cidr) => !cidr.includes(":"))];
646
+ if (allow.length > NETWORK_ALLOW_LIMIT) {
647
+ throw new Agent37DriverError(`Agent37 allows ${NETWORK_ALLOW_LIMIT} network destinations and this workspace names ${allow.length}`);
648
+ }
649
+ return allow.length ? { allow } : "deny-all";
650
+ }
651
+ function createAgent37SandboxDriver(options) {
652
+ const fetchImpl = options.fetchImpl ?? fetch;
653
+ const client = createAgent37Client({
654
+ apiKey: options.apiKey,
655
+ ...options.baseUrl ? { baseUrl: options.baseUrl } : {},
656
+ ...options.operationTimeoutMs ? { timeoutMs: options.operationTimeoutMs } : {},
657
+ fetchImpl
658
+ });
659
+ const entrypoint = runtimeEntrypoint(options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND);
660
+ const provisionTimeoutMs = options.provisionTimeoutMs ?? DEFAULT_PROVISION_TIMEOUT_MS;
661
+ const healthTimeoutMs = options.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;
662
+ const healthIntervalMs = options.healthIntervalMs ?? DEFAULT_HEALTH_INTERVAL_MS;
663
+ const containerCheckMs = options.containerCheckMs ?? DEFAULT_CONTAINER_CHECK_MS;
664
+ const registered = /* @__PURE__ */ new Map();
665
+ function runtimePort(input) {
666
+ return input.workspaceRuntimePort ?? options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
667
+ }
668
+ function resolveImage(input) {
669
+ return assertSandboxImageReference(
670
+ input.bootSource?.kind === "image" ? input.bootSource.image : input.snapshot ?? options.image
671
+ );
672
+ }
673
+ function templateFor(image) {
674
+ const known = registered.get(image);
675
+ if (known) return known;
676
+ const template = (async () => {
677
+ const name = await templateName(image, entrypoint);
678
+ if (!await client.templateExists(name)) await client.registerTemplate({ name, imageRef: image, entrypoint });
679
+ return name;
680
+ })();
681
+ registered.set(image, template);
682
+ template.catch(() => registered.delete(image));
683
+ return template;
684
+ }
685
+ async function bootEnv(input, hostId) {
686
+ const env = {
687
+ ...workspaceRuntimeBootEnv({
688
+ workspaceId: input.workspaceId,
689
+ hostId,
690
+ directory: input.workspaceRoot ?? options.workspaceDir ?? DEFAULT_WORKSPACE_DIR,
691
+ port: runtimePort(input),
692
+ host: "0.0.0.0",
693
+ source: input.source,
694
+ env: input.env,
695
+ nativeHarness: options.nativeHarness,
696
+ controlEnv: options.controlEnv
697
+ }),
698
+ ...await options.env?.(input, { id: hostId })
699
+ };
700
+ return { variables: { [RUNTIME_ENV_VARIABLE]: envFile(env) }, secrets: workspaceRuntimeBootSecrets(env) };
701
+ }
702
+ function metadata(input) {
703
+ return { ...input.labels };
704
+ }
705
+ async function settled(id) {
706
+ const until = Date.now() + provisionTimeoutMs;
707
+ for (; ; ) {
708
+ const instance2 = await client.get(id).catch(instanceGoneOrThrow);
709
+ if (GONE_STATUSES.has(instance2.status)) throw new SandboxHostGoneError(`Agent37 ${id} is ${instance2.status}`);
710
+ if (!SETTLING_STATUSES.has(instance2.status)) return instance2;
711
+ if (Date.now() >= until) return void 0;
712
+ await sleep(STATUS_POLL_MS);
713
+ }
714
+ }
715
+ async function probe(url) {
716
+ try {
717
+ return await withDeadlineSignal(HEALTH_PROBE_TIMEOUT_MS, async (signal) => {
718
+ const response = await fetchImpl(`${url}/global/health`, { signal });
719
+ if (!response.ok) return { answer: `health probe returned ${response.status}` };
720
+ const workspaceId = asRecord(await response.json().catch(() => void 0))?.workspaceId;
721
+ return typeof workspaceId === "string" ? { workspaceId, answer: `health probe answered for ${workspaceId}` } : { answer: "health probe named no workspace" };
722
+ });
723
+ } catch {
724
+ return { answer: "health probe got no response" };
725
+ }
726
+ }
727
+ async function waitForHealth(id, url, input, secrets, bootRestarts) {
728
+ const until = Date.now() + healthTimeoutMs;
729
+ let last = "workspace runtime not ready";
730
+ let containerReadAt = Date.now();
731
+ while (Date.now() < until) {
732
+ const answered = await probe(url);
733
+ if (answered.workspaceId === input.workspaceId) return;
734
+ last = answered.answer;
735
+ if (Date.now() - containerReadAt >= containerCheckMs) {
736
+ const container = await client.containerHealth(id, 1);
737
+ containerReadAt = Date.now();
738
+ if (!container.running || container.restartCount > bootRestarts) {
739
+ last = "the runtime exited";
740
+ break;
741
+ }
742
+ }
743
+ await sleep(healthIntervalMs);
744
+ }
745
+ const diagnostic = await client.containerHealth(id, DIAGNOSTIC_LOG_LINES).then(
746
+ (container) => runtimeBootDiagnostic(redactWorkspaceRuntimeBootSecrets(container.logs, secrets), DIAGNOSTIC_CHARS),
747
+ () => "container diagnostics unavailable"
748
+ );
749
+ throw new SandboxRuntimeBootError(`Agent37 ${id} runtime did not become healthy: ${last}; ${diagnostic}`);
750
+ }
751
+ async function serve(running, input, hostId, secrets) {
752
+ const port = runtimePort(input);
753
+ const { restartCount } = await client.containerHealth(running.id, 1);
754
+ await input.onMachineReady?.();
755
+ await input.onImageReady?.();
756
+ const url = running.publicPorts.find((entry) => entry.port === port)?.url ?? await client.publishPort(running.id, port);
757
+ await waitForHealth(running.id, url, input, secrets, restartCount);
758
+ return {
759
+ workspaceId: input.workspaceId,
760
+ sandboxId: running.id,
761
+ url,
762
+ hostId,
763
+ driverResourceId: running.id,
764
+ driver: { id: "agent37", resourceId: running.id },
765
+ labels: { ...input.labels, ...resolveImage(input) === options.image && running.imageRef ? { [SANDBOX_IMAGE_LABEL]: running.imageRef } : {} }
766
+ };
767
+ }
768
+ function shape(machineClass) {
769
+ return agent37MachineShapes[machineClass ?? "default"];
770
+ }
771
+ async function ensureHost(input) {
772
+ const hostId = input.hostId ?? `agent37-${input.workspaceId}`;
773
+ const policy = instanceNetwork(input);
774
+ const { variables, secrets } = await bootEnv(input, hostId);
775
+ const created = await templateFor(resolveImage(input)).then((template) => client.create({
776
+ template,
777
+ name: `claxedo-${input.workspaceId}`.slice(0, INSTANCE_NAME_MAX_CHARS),
778
+ metadata: metadata(input),
779
+ env: variables,
780
+ network: policy,
781
+ resources: shape(input.machineClass),
782
+ publicPort: runtimePort(input),
783
+ mountPath: PERSISTENT_ROOT
784
+ })).catch((err) => {
785
+ if (isTransientDriverError(err, TRANSIENT_MARKERS)) return void 0;
786
+ throw err;
787
+ });
788
+ if (!created) return { provisioning: true, retryAfterMs: TRANSIENT_DRIVER_RETRY_MS };
789
+ const resource = { sandboxId: created.id, hostId, driverResourceId: created.id, labels: input.labels };
790
+ const failures = [];
791
+ try {
792
+ await input.onResource?.(resource);
793
+ } catch (handoffError) {
794
+ failures.push(handoffError);
795
+ try {
796
+ await destroy({ ...resource, workspaceId: input.workspaceId });
797
+ } catch (deleteError) {
798
+ failures.push(deleteError);
799
+ }
800
+ }
801
+ if (failures.length === 1) throw failures[0];
802
+ if (failures.length > 1) throw new AggregateError(failures, `Agent37 ${created.id} resource handoff and deletion failed`);
803
+ const running = created.status === "running" ? created : await settled(created.id);
804
+ if (!running) return { provisioning: true, retryAfterMs: 0 };
805
+ if (running.status !== "running") throw new Agent37DriverError(`Agent37 ${created.id} entered non-running state: ${running.status}`);
806
+ return serve(running, input, hostId, secrets);
807
+ }
808
+ async function resumeHost(input) {
809
+ const id = input.lease.sandboxId;
810
+ if (!id) throw new Agent37DriverError("Cannot resume Agent37 without its instance id");
811
+ const ensure = input.ensure;
812
+ const hostId = ensure.hostId ?? input.lease.hostId ?? `agent37-${ensure.workspaceId}`;
813
+ const policy = instanceNetwork(ensure);
814
+ const held = await settled(id);
815
+ if (!held) return { provisioning: true, retryAfterMs: 0 };
816
+ const image = resolveImage(ensure);
817
+ const { variables, secrets } = await bootEnv(ensure, hostId);
818
+ await client.edit(id, { env: variables, network: policy, metadata: metadata(ensure) }).catch(instanceGoneOrThrow);
819
+ const keepsPreviousImage = held.imageRef !== image && image === options.image && !ensure.updateImage;
820
+ const moves = held.imageRef !== image && !keepsPreviousImage;
821
+ let status = held.status;
822
+ if (status === "sleeping") status = await client.start(id);
823
+ if (moves || status === "failed") status = await client.update(id, await templateFor(image));
824
+ else if (status === "running") status = await client.restart(id);
825
+ if (status === "stopped") status = await client.start(id);
826
+ let running = status === "running" ? await client.get(id) : await settled(id);
827
+ if (!running) return { provisioning: true, retryAfterMs: 0 };
828
+ if (running.status !== "running") throw new Agent37DriverError(`Agent37 ${id} entered non-running state: ${running.status}`);
829
+ if (keepsPreviousImage) {
830
+ const served = await client.exec(id, servedRuntimeVersionTest(`/${SANDBOX_IMAGE_RUNTIME_VERSION_FILE}`));
831
+ if (served.exitCode !== 0) {
832
+ await client.update(id, await templateFor(image));
833
+ running = await settled(id);
834
+ if (!running) return { provisioning: true, retryAfterMs: 0 };
835
+ }
836
+ }
837
+ const wanted = ensure.machineClass ? shape(ensure.machineClass) : void 0;
838
+ if (wanted && (running.resources.cpu !== wanted.cpu || running.resources.memory !== wanted.memory)) {
839
+ await client.resize(id, { cpu: wanted.cpu, memory: wanted.memory, disk: Math.max(running.resources.disk, wanted.disk) });
840
+ running = await settled(id);
841
+ if (!running) return { provisioning: true, retryAfterMs: 0 };
842
+ }
843
+ return serve(running, ensure, hostId, secrets);
844
+ }
845
+ async function stop(target) {
846
+ await client.stop(target.sandboxId).catch(instanceGoneOrThrow);
847
+ }
848
+ async function destroy(target) {
849
+ await client.delete(target.sandboxId).catch((err) => {
850
+ if (!(err instanceof Agent37ApiError && err.code === "not_found")) throw err;
851
+ });
852
+ }
853
+ async function inspect(target) {
854
+ try {
855
+ const { status } = await client.get(target.sandboxId);
856
+ if (GONE_STATUSES.has(status)) return "gone";
857
+ return SERVING_STATUSES.has(status) ? "serving" : "stopped";
858
+ } catch (err) {
859
+ if (err instanceof Agent37ApiError && err.code === "not_found") return "gone";
860
+ throw err;
861
+ }
862
+ }
863
+ async function list() {
864
+ return (await client.list()).flatMap((instance2) => {
865
+ const url = instance2.publicPorts[0]?.url;
866
+ return url && !GONE_STATUSES.has(instance2.status) ? [{ sandboxId: instance2.id, url, hostId: `agent37-${instance2.metadata.workspaceId ?? instance2.id}`, driverResourceId: instance2.id, labels: instance2.metadata, driver: { id: "agent37", resourceId: instance2.id } }] : [];
867
+ });
868
+ }
869
+ return {
870
+ id: "agent37",
871
+ metadata: sandboxDriverCatalog.agent37.metadata,
872
+ image: options.image,
873
+ ensureHost,
874
+ resumeHost,
875
+ list,
876
+ async touch() {
877
+ },
878
+ suspend: stop,
879
+ stop,
880
+ destroy,
881
+ inspect
882
+ };
883
+ }
884
+ export {
885
+ createAgent37SandboxDriver
886
+ };