@claxedo/sandbox-manager 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,28 @@
1
+ // ../claxedo-helpers/dist/string.mjs
2
+ function isString(value) {
3
+ return typeof value === "string";
4
+ }
5
+ function trimToUndefined(value) {
6
+ if (!isString(value))
7
+ return;
8
+ const trimmed = value.trim();
9
+ return trimmed.length > 0 ? trimmed : void 0;
10
+ }
11
+
1
12
  // src/runtime-env.ts
13
+ function workspaceRuntimeBootEnv(input) {
14
+ const env = {
15
+ ...workspaceRuntimeTargetEnv(input),
16
+ ...workspaceRuntimeSourceEnv(input),
17
+ ...input.env
18
+ };
19
+ if (input.runner) env.WORKSPACE_RUNTIME_RUNNER = input.runner;
20
+ if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
21
+ if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
22
+ if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
23
+ if (input.controlEnv?.sessionAuthorityUrl) env.WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL = input.controlEnv.sessionAuthorityUrl;
24
+ return env;
25
+ }
2
26
  function workspaceRuntimeTargetEnv(input) {
3
27
  return {
4
28
  WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
@@ -25,9 +49,6 @@ function shell(input) {
25
49
  // src/constants.ts
26
50
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
27
51
 
28
- // src/image.ts
29
- import { Image, DaytonaNotFoundError } from "@daytona/sdk";
30
-
31
52
  // src/runtime-version.ts
32
53
  var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
33
54
  function workspaceRuntimeVersion() {
@@ -55,6 +76,22 @@ function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env =
55
76
  return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
56
77
  }
57
78
 
79
+ // ../claxedo-helpers/dist/guards.mjs
80
+ function isRecord(value) {
81
+ return value !== null && typeof value === "object" && !Array.isArray(value);
82
+ }
83
+ function asRecord(value) {
84
+ return isRecord(value) ? value : void 0;
85
+ }
86
+
87
+ // src/json.ts
88
+ function text(input) {
89
+ return typeof input === "string" && input.length > 0 ? input : void 0;
90
+ }
91
+ function num(input) {
92
+ return typeof input === "number" ? input : void 0;
93
+ }
94
+
58
95
  // src/image.ts
59
96
  var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
60
97
  var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
@@ -86,12 +123,42 @@ var SANDBOX_BASELINE_EGRESS_HOSTS = [
86
123
  ...SANDBOX_PACKAGE_REGISTRY_HOSTS
87
124
  ];
88
125
 
126
+ // ../sandbox-contract/dist/index.mjs
127
+ var sandboxDriverCredentialFields = {
128
+ exe: [{ key: "api_token", label: "API Token", secret: true }],
129
+ daytona: [{ key: "api_key", label: "API Key", secret: true }],
130
+ modal: [
131
+ { key: "token_id", label: "Token ID", secret: true },
132
+ { key: "token_secret", label: "Token Secret", secret: true }
133
+ ],
134
+ vercel: [
135
+ { key: "access_token", label: "Access Token", secret: true },
136
+ { key: "team_id", label: "Team ID" },
137
+ { key: "project_id", label: "Project ID" }
138
+ ],
139
+ cloudflare: [
140
+ { key: "api_token", label: "API Token", secret: true },
141
+ { key: "worker_url", label: "Worker URL" }
142
+ ],
143
+ box: [{ key: "api_key", label: "API Key", secret: true }],
144
+ docker: [{ key: "image", label: "Image" }]
145
+ };
146
+ var sandboxDriverLabels = {
147
+ exe: "exe.dev",
148
+ daytona: "Daytona",
149
+ modal: "Modal",
150
+ vercel: "Vercel",
151
+ cloudflare: "Cloudflare",
152
+ box: "Box",
153
+ docker: "Docker"
154
+ };
155
+
89
156
  // src/driver-catalog.ts
90
157
  var sandboxDriverCatalog = {
91
158
  exe: {
92
159
  id: "exe",
93
- label: "exe.dev",
94
- credentialFields: [{ key: "api_token", label: "API Token", secret: true }],
160
+ label: sandboxDriverLabels.exe,
161
+ credentialFields: sandboxDriverCredentialFields.exe,
95
162
  metadata: {
96
163
  driverRunsIn: ["worker", "node"],
97
164
  hostStopBehavior: "not-supported",
@@ -111,17 +178,15 @@ var sandboxDriverCatalog = {
111
178
  },
112
179
  daytona: {
113
180
  id: "daytona",
114
- label: "Daytona",
115
- credentialFields: [{ key: "api_key", label: "API Key", secret: true }],
181
+ label: sandboxDriverLabels.daytona,
182
+ credentialFields: sandboxDriverCredentialFields.daytona,
116
183
  metadata: {
117
184
  driverRunsIn: ["worker", "node"],
118
185
  hostStopBehavior: "suspends-host",
119
186
  hostResumeBehavior: "same-host",
120
187
  targetAccess: "relay",
121
188
  secretBrokering: "native",
122
- // Daytona is the only driver that can filter egress by NAME as well as
123
- // by address: `domainAllowList` (names) alongside `networkAllowList`
124
- // (CIDRs), with `networkBlockAll` as the deny-all floor.
189
+ // The driver translates names to domainAllowList, or addresses to networkAllowList.
125
190
  egressControl: "hosts-and-cidrs",
126
191
  persistence: {
127
192
  resume: "same-sandbox",
@@ -135,20 +200,15 @@ var sandboxDriverCatalog = {
135
200
  },
136
201
  modal: {
137
202
  id: "modal",
138
- label: "Modal",
139
- credentialFields: [
140
- { key: "token_id", label: "Token ID", secret: true },
141
- { key: "token_secret", label: "Token Secret", secret: true }
142
- ],
203
+ label: sandboxDriverLabels.modal,
204
+ credentialFields: sandboxDriverCredentialFields.modal,
143
205
  metadata: {
144
206
  driverRunsIn: ["node"],
145
207
  hostStopBehavior: "terminates-host",
146
208
  hostResumeBehavior: "replacement-host",
147
209
  targetAccess: "relay",
148
210
  secretBrokering: "none",
149
- // Modal can cut the network entirely (`blockNetwork`) but cannot express
150
- // an allowlist, and its driver throws on a host policy. A blackout is
151
- // not containment for a workspace that has to clone and reach a model.
211
+ // The driver does not install Modal's domain allowlist or alpha sidecar.
152
212
  egressControl: "none",
153
213
  persistence: {
154
214
  resume: "replacement-restore",
@@ -162,20 +222,15 @@ var sandboxDriverCatalog = {
162
222
  },
163
223
  vercel: {
164
224
  id: "vercel",
165
- label: "Vercel",
166
- credentialFields: [
167
- { key: "access_token", label: "Access Token", secret: true },
168
- { key: "team_id", label: "Team ID" },
169
- { key: "project_id", label: "Project ID" }
170
- ],
225
+ label: sandboxDriverLabels.vercel,
226
+ credentialFields: sandboxDriverCredentialFields.vercel,
171
227
  metadata: {
172
228
  driverRunsIn: ["node"],
173
229
  hostStopBehavior: "terminates-host",
174
230
  hostResumeBehavior: "replacement-host",
175
231
  targetAccess: "relay",
176
232
  secretBrokering: "native",
177
- // Vercel's sandbox firewall takes a hostname allow list (or "deny-all"),
178
- // never CIDRs.
233
+ // The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
179
234
  egressControl: "hosts",
180
235
  persistence: {
181
236
  resume: "replacement-restore",
@@ -189,23 +244,15 @@ var sandboxDriverCatalog = {
189
244
  },
190
245
  cloudflare: {
191
246
  id: "cloudflare",
192
- label: "Cloudflare",
193
- credentialFields: [
194
- { key: "api_token", label: "API Token", secret: true },
195
- { key: "worker_url", label: "Worker URL" }
196
- ],
247
+ label: sandboxDriverLabels.cloudflare,
248
+ credentialFields: sandboxDriverCredentialFields.cloudflare,
197
249
  metadata: {
198
250
  driverRunsIn: ["worker"],
199
251
  hostStopBehavior: "not-supported",
200
252
  hostResumeBehavior: "same-host",
201
253
  targetAccess: "relay",
202
- secretBrokering: "proxy",
203
- // The Cloudflare Sandbox Worker exposes no egress filter: its broker
204
- // (drivers/cloudflare-egress.ts) is an OPT-IN proxy the sandbox chooses
205
- // to route brokered-credential requests through, not a boundary. The
206
- // driver therefore drops `net` entirely — which is exactly why this
207
- // must be declared, so the manager refuses instead of provisioning a
208
- // sandbox the caller believes is contained.
254
+ secretBrokering: "native",
255
+ // Credential handlers do not restrict unrelated destinations.
209
256
  egressControl: "none",
210
257
  persistence: {
211
258
  resume: "replacement-restore",
@@ -219,8 +266,8 @@ var sandboxDriverCatalog = {
219
266
  },
220
267
  box: {
221
268
  id: "box",
222
- label: "Box",
223
- credentialFields: [{ key: "api_key", label: "API Key", secret: true }],
269
+ label: sandboxDriverLabels.box,
270
+ credentialFields: sandboxDriverCredentialFields.box,
224
271
  metadata: {
225
272
  driverRunsIn: ["node"],
226
273
  hostStopBehavior: "suspends-host",
@@ -240,8 +287,8 @@ var sandboxDriverCatalog = {
240
287
  },
241
288
  docker: {
242
289
  id: "docker",
243
- label: "Docker",
244
- credentialFields: [{ key: "image", label: "Image" }],
290
+ label: sandboxDriverLabels.docker,
291
+ credentialFields: sandboxDriverCredentialFields.docker,
245
292
  metadata: {
246
293
  driverRunsIn: ["local"],
247
294
  hostStopBehavior: "terminates-host",
@@ -261,6 +308,24 @@ var sandboxDriverCatalog = {
261
308
  }
262
309
  };
263
310
 
311
+ // src/drivers/transient-error.ts
312
+ function textOf(input) {
313
+ return typeof input === "string" ? input : "";
314
+ }
315
+ function driverErrorSignals(err) {
316
+ const shaped = isRecord(err) ? err : {};
317
+ const nested = isRecord(shaped.response) ? shaped.response : {};
318
+ return {
319
+ status: num(nested.status) ?? num(shaped.status),
320
+ text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
321
+ };
322
+ }
323
+ function isTransientDriverError(err, markers) {
324
+ const { status, text: text2 } = driverErrorSignals(err);
325
+ if (status !== void 0 && status >= 500) return true;
326
+ return markers.some((marker) => text2.includes(marker));
327
+ }
328
+
264
329
  // src/drivers/box.ts
265
330
  var DEFAULT_BASE_URL = "https://ascii.dev/api/box/v1";
266
331
  var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
@@ -280,11 +345,9 @@ function clean(input) {
280
345
  const txt = input?.trim();
281
346
  return txt ? txt : void 0;
282
347
  }
348
+ var TRANSIENT_MARKERS = ["timeout", "unavailable", "econnreset", "network"];
283
349
  function transientDriverError(err) {
284
- const shaped = err;
285
- if (typeof shaped.status === "number" && shaped.status >= 500) return true;
286
- const text = `${shaped.code ?? ""} ${shaped.name ?? ""} ${shaped.message ?? ""}`.toLowerCase();
287
- return text.includes("timeout") || text.includes("unavailable") || text.includes("econnreset") || text.includes("network");
350
+ return isTransientDriverError(err, TRANSIENT_MARKERS);
288
351
  }
289
352
  var BoxApiError = class extends Error {
290
353
  status;
@@ -322,14 +385,29 @@ function createBoxSandboxDriver(options) {
322
385
  } catch (err) {
323
386
  throw new BoxApiError(err instanceof Error ? err.message : String(err));
324
387
  }
325
- const text = await res.text();
326
- const body = text ? JSON.parse(text) : {};
388
+ const payload = await res.text();
389
+ const body = (payload ? asRecord(JSON.parse(payload)) : void 0) ?? {};
327
390
  if (!res.ok || body.ok === false) {
328
- const detail = clean(body.error) ?? clean(body.message) ?? res.statusText;
391
+ const detail = clean(text(body.error)) ?? clean(text(body.message)) ?? res.statusText;
329
392
  throw new BoxApiError(`Box API ${init?.method ?? "GET"} ${path} failed: ${detail}`, res.status);
330
393
  }
331
394
  return body;
332
395
  }
396
+ function boxOf(body) {
397
+ const box = asRecord(body.box);
398
+ const id = text(box?.id);
399
+ const state = text(box?.state);
400
+ if (!box || !id || !state) throw new BoxApiError("Box API response omitted its box envelope");
401
+ return {
402
+ id,
403
+ state,
404
+ ...text(box.name) ? { name: text(box.name) } : {},
405
+ ...box.url === void 0 ? {} : { url: text(box.url) ?? null },
406
+ ...box.ip === void 0 ? {} : { ip: text(box.ip) ?? null },
407
+ ...box.subdomain === void 0 ? {} : { subdomain: text(box.subdomain) ?? null },
408
+ ...box.createdAt === void 0 ? {} : { createdAt: text(box.createdAt) ?? null }
409
+ };
410
+ }
333
411
  function runtimePort(input) {
334
412
  return input.workspaceRuntimePort ?? options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
335
413
  }
@@ -341,26 +419,23 @@ function createBoxSandboxDriver(options) {
341
419
  return input.snapshot ?? image;
342
420
  }
343
421
  function bootEnv(input, hostId) {
344
- const env = {
345
- ...workspaceRuntimeTargetEnv({
346
- workspaceId: input.workspaceId,
347
- hostId,
348
- directory: workspaceDirectory(input),
349
- port: runtimePort(input),
350
- host: "0.0.0.0"
351
- }),
352
- ...workspaceRuntimeSourceEnv({ source: input.source }),
353
- ...input.env
354
- };
355
- if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
356
- return env;
422
+ return workspaceRuntimeBootEnv({
423
+ workspaceId: input.workspaceId,
424
+ hostId,
425
+ directory: workspaceDirectory(input),
426
+ port: runtimePort(input),
427
+ host: "0.0.0.0",
428
+ source: input.source,
429
+ env: input.env,
430
+ runner: options.runner
431
+ });
357
432
  }
358
433
  async function exec(boxId, command) {
359
434
  const body = await api(`/boxes/${boxId}/commands`, {
360
435
  method: "POST",
361
436
  body: { command }
362
437
  });
363
- const result = body.result ?? body;
438
+ const result = asRecord(body.result) ?? body;
364
439
  return {
365
440
  stdout: typeof result.stdout === "string" ? result.stdout : "",
366
441
  stderr: typeof result.stderr === "string" ? result.stderr : "",
@@ -370,14 +445,14 @@ function createBoxSandboxDriver(options) {
370
445
  async function execOrThrow(boxId, command, label) {
371
446
  const result = await exec(boxId, command);
372
447
  if ((result.exitCode ?? 0) !== 0) {
373
- throw new BoxApiError(`Box ${boxId} ${label} failed (exit ${result.exitCode}): ${clean(result.stderr) ?? clean(result.stdout) ?? ""}`);
448
+ throw new BoxApiError(`Box ${boxId} ${label} failed (exit ${result.exitCode}): ${trimToUndefined(result.stderr) ?? trimToUndefined(result.stdout) ?? ""}`);
374
449
  }
375
450
  return result;
376
451
  }
377
452
  async function waitUntilReady(boxId) {
378
453
  const until = Date.now() + provisionTimeoutMs;
379
454
  for (; ; ) {
380
- const { box } = await api(`/boxes/${boxId}`);
455
+ const box = boxOf(await api(`/boxes/${boxId}`));
381
456
  if (READY_STATES.has(box.state)) return true;
382
457
  if (!PENDING_STATES.has(box.state)) {
383
458
  throw new BoxApiError(`Box ${boxId} entered non-ready state: ${box.state}`);
@@ -403,7 +478,7 @@ function createBoxSandboxDriver(options) {
403
478
  boxId,
404
479
  `curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:${port}/global/health || true`
405
480
  );
406
- const code = clean(probe.stdout);
481
+ const code = trimToUndefined(probe.stdout);
407
482
  if (code === "200") return;
408
483
  last = `health probe returned ${code ?? "no response"}`;
409
484
  await sleep(healthIntervalMs);
@@ -420,7 +495,7 @@ function createBoxSandboxDriver(options) {
420
495
  await execOrThrow(boxId, `host ${port}`, "host publish");
421
496
  await waitForHealth(boxId, input);
422
497
  const urlResult = await execOrThrow(boxId, `host url ${port}`, "host url");
423
- const url = clean(urlResult.stdout?.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).pop());
498
+ const url = trimToUndefined(urlResult.stdout?.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).pop());
424
499
  if (!url) throw new BoxApiError(`Box ${boxId} did not return a public URL for port ${port}`);
425
500
  return {
426
501
  workspaceId: input.workspaceId,
@@ -448,7 +523,7 @@ function createBoxSandboxDriver(options) {
448
523
  throw err;
449
524
  });
450
525
  if (!created) return { provisioning: true, retryAfterMs: provisionIntervalMs };
451
- const boxId = created.box.id;
526
+ const boxId = boxOf(created).id;
452
527
  const ready = await waitUntilReady(boxId);
453
528
  if (!ready) return { provisioning: true, retryAfterMs: provisionIntervalMs };
454
529
  return boot(boxId, input, hostId);
@@ -1,4 +1,5 @@
1
- import type { SandboxDriver, SandboxDriverEnsureInput, SandboxListingUnsupported } from "..";
1
+ import { type SandboxDriver, type SandboxDriverEnsureInput, type SandboxListingUnsupported } from "..";
2
+ import { type WorkspaceRuntimeControlEnv } from "../runtime-env";
2
3
  export type CloudflareSandboxDriverOptions = {
3
4
  /** Base URL of the deployed Cloudflare Sandbox Worker (e.g. https://sbx.example.com). */
4
5
  workerUrl: string;
@@ -18,12 +19,8 @@ export type CloudflareSandboxDriverOptions = {
18
19
  * relayVerifyPem → WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM (fallback to JWKS)
19
20
  * managementJwksUrl → WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL (config apply)
20
21
  */
21
- controlEnv?: {
22
- relayJwksUrl?: string;
23
- relayVerifyPem?: string;
24
- managementJwksUrl?: string;
25
- };
26
- /** Default runner injected as WORKSPACE_RUNTIME_RUNNER, e.g. "opencode" | "claude-acp" | "codex-acp". */
22
+ controlEnv?: WorkspaceRuntimeControlEnv;
23
+ /** Default runner injected as WORKSPACE_RUNTIME_RUNNER, for example "opencode". */
27
24
  runner?: string;
28
25
  /** Dynamic runtime env that needs the sandbox id or current lease. */
29
26
  env?: (input: SandboxDriverEnsureInput, sandbox: {