@claxedo/sandbox-manager 0.5.1 → 0.5.2

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.
package/README.md CHANGED
@@ -13,6 +13,55 @@ The package owns the generic pieces:
13
13
 
14
14
  It deliberately does not own Claxedo product auth, billing, Convex schema, SQLite app storage, routes, or relay tokens. Applications provide those through adapters and call `createSandboxManager`.
15
15
 
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install @claxedo/sandbox-manager
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ A `SandboxManager` is three parts wired together: a `SandboxLeaseStore` (where
25
+ lease state lives), a `SandboxDriver` (how a sandbox is actually placed), and
26
+ `createSandboxManager` (the epoch/retry orchestration on top of both). This
27
+ example uses the in-memory lease store and the Docker driver, so it runs
28
+ end-to-end with only a local Docker daemon — no provider account needed:
29
+
30
+ ```ts
31
+ import { createSandboxManager } from "@claxedo/sandbox-manager"
32
+ import { createMemoryLeaseStore } from "@claxedo/sandbox-manager/stores/memory"
33
+ import { createDockerSandboxDriver } from "@claxedo/sandbox-manager/drivers/docker"
34
+ import { SANDBOX_IMAGE } from "@claxedo/sandbox-manager/image"
35
+
36
+ const manager = createSandboxManager({
37
+ leaseStore: createMemoryLeaseStore(),
38
+ driver: createDockerSandboxDriver({ image: SANDBOX_IMAGE }),
39
+ })
40
+
41
+ const result = await manager.ensure("workspace-1", { homeRegion: "local" })
42
+
43
+ if (result.status === "ready") {
44
+ console.log(`sandbox ready at ${result.url} (sandboxId: ${result.sandboxId})`)
45
+ } else if (result.status === "provisioning") {
46
+ console.log(`still provisioning, retry in ${result.retryAfterMs}ms`)
47
+ } else {
48
+ console.error(`unavailable: ${result.error}`)
49
+ }
50
+
51
+ // Later, on the same workspace: `ensure` re-resolves the existing lease
52
+ // instead of placing a new sandbox.
53
+ await manager.stop("workspace-1")
54
+ ```
55
+
56
+ Swap `createDockerSandboxDriver` for `createDaytonaSandboxDriver`,
57
+ `createModalSandboxDriver`, `createVercelSandboxDriver`,
58
+ `createCloudflareSandboxDriver`, or `createBoxSandboxDriver` (all under
59
+ `@claxedo/sandbox-manager/drivers/*`) to place on a hosted provider instead —
60
+ see [`docs/architecture.md`](docs/architecture.md) for the full driver
61
+ comparison and each provider's required options. Swap `createMemoryLeaseStore`
62
+ for a persisted `SandboxLeaseStore` implementation (e.g. backed by SQLite or
63
+ Convex) to survive process restarts.
64
+
16
65
  ## Credentials & secrets
17
66
 
18
67
  There are two distinct channels for getting values into a sandbox, chosen by
package/dist/command.mjs CHANGED
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/command.ts
4
2
  import path from "path";
5
3
  function shell(input) {
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/constants.ts
4
2
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
5
3
  export {
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/daytona-allow-list.ts
4
2
  function formatDaytonaAllowList(cidrs) {
5
3
  return cidrs.slice(0, 10).join(",");
package/dist/defaults.mjs CHANGED
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/constants.ts
4
2
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
5
3
 
@@ -1,5 +1,5 @@
1
1
  import type { SandboxDriverMetadata } from "./index";
2
- export declare const sandboxDriverIds: readonly ["daytona", "modal", "vercel", "cloudflare", "docker"];
2
+ export declare const sandboxDriverIds: readonly ["daytona", "modal", "vercel", "cloudflare", "box", "docker"];
3
3
  export type SandboxDriverID = (typeof sandboxDriverIds)[number];
4
4
  export type SandboxDriverAuth = {
5
5
  daytona?: {
@@ -18,6 +18,9 @@ export type SandboxDriverAuth = {
18
18
  api_token?: string;
19
19
  worker_url?: string;
20
20
  };
21
+ box?: {
22
+ api_key?: string;
23
+ };
21
24
  docker?: {
22
25
  image?: string;
23
26
  };
@@ -38,15 +41,15 @@ export type SandboxDriverCatalogEntry = {
38
41
  };
39
42
  export declare const sandboxDriverCatalog: Record<SandboxDriverID, SandboxDriverCatalogEntry>;
40
43
  export declare function isSandboxDriverID(input: string | undefined): input is SandboxDriverID;
41
- export declare function sandboxDriverId(input: string | undefined, cfg?: SandboxDriverConfig, env?: SandboxDriverEnv): "daytona" | "modal" | "vercel" | "cloudflare" | "docker" | undefined;
44
+ export declare function sandboxDriverId(input: string | undefined, cfg?: SandboxDriverConfig, env?: SandboxDriverEnv): "box" | "cloudflare" | "daytona" | "docker" | "modal" | "vercel" | undefined;
42
45
  export declare function defaultSandboxDriverID(cfg?: SandboxDriverConfig, env?: SandboxDriverEnv): SandboxDriverID;
43
46
  export declare function sandboxDriverAuth<T extends SandboxDriverID>(cfg: SandboxDriverConfig | undefined, id: T, env?: SandboxDriverEnv): SandboxDriverAuth[T] | undefined;
44
47
  export declare function hasSandboxDriverAuth(cfg: SandboxDriverConfig | undefined, id: SandboxDriverID, env?: SandboxDriverEnv): boolean;
45
48
  export declare function dockerSandboxDriverEnabled(env?: SandboxDriverEnv): boolean;
46
49
  export declare function listSandboxDrivers(cfg?: SandboxDriverConfig, env?: SandboxDriverEnv, managedDriverIds?: ReadonlySet<string>): {
47
- default_driver: "daytona" | "modal" | "vercel" | "cloudflare" | "docker";
50
+ default_driver: "box" | "cloudflare" | "daytona" | "docker" | "modal" | "vercel";
48
51
  drivers: {
49
- id: "daytona" | "modal" | "vercel" | "cloudflare" | "docker";
52
+ id: "box" | "cloudflare" | "daytona" | "docker" | "modal" | "vercel";
50
53
  label: string;
51
54
  fields: {
52
55
  key: string;
@@ -54,7 +57,7 @@ export declare function listSandboxDrivers(cfg?: SandboxDriverConfig, env?: Sand
54
57
  secret?: boolean;
55
58
  }[];
56
59
  configured: boolean;
57
- source: "none" | "config" | "managed";
60
+ source: "config" | "managed" | "none";
58
61
  default: boolean;
59
62
  }[];
60
63
  };
@@ -1,8 +1,5 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/runtime-version.ts
4
- import { createRequire } from "module";
5
- var require2 = createRequire(import.meta.url);
2
+ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
6
3
  var versionCache;
7
4
  function workspaceRuntimeVersion() {
8
5
  if (versionCache) return versionCache;
@@ -11,12 +8,7 @@ function workspaceRuntimeVersion() {
11
8
  versionCache = envVersion;
12
9
  return versionCache;
13
10
  }
14
- try {
15
- versionCache = require2("@claxedo/workspace-runtime/package.json").version;
16
- return versionCache;
17
- } catch {
18
- }
19
- versionCache = "0.0.0";
11
+ versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
20
12
  return versionCache;
21
13
  }
22
14
 
@@ -39,7 +31,7 @@ function defaultSandboxImage(version = workspaceRuntimeVersion(), buildId, env =
39
31
  }
40
32
 
41
33
  // src/driver-catalog.ts
42
- var sandboxDriverIds = ["daytona", "modal", "vercel", "cloudflare", "docker"];
34
+ var sandboxDriverIds = ["daytona", "modal", "vercel", "cloudflare", "box", "docker"];
43
35
  var sandboxDriverCatalog = {
44
36
  daytona: {
45
37
  id: "daytona",
@@ -99,6 +91,18 @@ var sandboxDriverCatalog = {
99
91
  secretBrokering: "proxy"
100
92
  }
101
93
  },
94
+ box: {
95
+ id: "box",
96
+ label: "Box",
97
+ credentialFields: [{ key: "api_key", label: "API Key", secret: true }],
98
+ metadata: {
99
+ driverRunsIn: ["node"],
100
+ hostStopBehavior: "suspends-host",
101
+ hostResumeBehavior: "same-host",
102
+ targetAccess: "relay",
103
+ secretBrokering: "none"
104
+ }
105
+ },
102
106
  docker: {
103
107
  id: "docker",
104
108
  label: "Docker",
@@ -128,6 +132,7 @@ function sandboxDriverAuth(cfg, id, env = process.env) {
128
132
  if (id === "modal") return authModal(cfg, env);
129
133
  if (id === "vercel") return authVercel(cfg, env);
130
134
  if (id === "cloudflare") return authCloudflare(cfg, env);
135
+ if (id === "box") return authBox(cfg, env);
131
136
  return authDocker(cfg, env);
132
137
  }
133
138
  function hasSandboxDriverAuth(cfg, id, env = process.env) {
@@ -187,6 +192,10 @@ function authCloudflare(cfg, env) {
187
192
  const worker_url = clean(cfg?.auth?.cloudflare?.worker_url) ?? clean(env.CLOUDFLARE_SANDBOX_WORKER_URL);
188
193
  return api_token && worker_url ? { api_token, worker_url } : void 0;
189
194
  }
195
+ function authBox(cfg, env) {
196
+ const api_key = clean(cfg?.auth?.box?.api_key) ?? clean(env.BOX_API_KEY);
197
+ return api_key ? { api_key } : void 0;
198
+ }
190
199
  function authDocker(cfg, env) {
191
200
  if (!dockerSandboxEnabled(env)) return;
192
201
  return {
@@ -0,0 +1,52 @@
1
+ import type { SandboxDriver, SandboxDriverEnsureInput } from "..";
2
+ export type BoxApiBox = {
3
+ id: string;
4
+ name?: string;
5
+ state: string;
6
+ url?: string | null;
7
+ ip?: string | null;
8
+ subdomain?: string | null;
9
+ createdAt?: string | null;
10
+ };
11
+ export type BoxCommandResult = {
12
+ stdout?: string;
13
+ stderr?: string;
14
+ exitCode?: number;
15
+ };
16
+ export type BoxFetch = (input: string, init?: RequestInit) => Promise<Response>;
17
+ export type BoxSandboxDriverOptions = {
18
+ apiKey: string;
19
+ baseUrl?: string;
20
+ image?: string;
21
+ runtimePort?: number;
22
+ runtimeCommand?: string;
23
+ workspaceDir?: string;
24
+ runner?: string;
25
+ /** Container name used for the runtime container inside the box. */
26
+ containerName?: string;
27
+ /**
28
+ * Box auto-archives after this many seconds of inactivity. `null` (default)
29
+ * disables auto-archival so the SandboxManager owns the lifecycle; a number
30
+ * lets Box reclaim idle boxes to save cost.
31
+ */
32
+ ttlSeconds?: number | null;
33
+ /**
34
+ * Optional `docker login` before the pull, for a private runtime registry
35
+ * (the default image lives on ghcr.io). Omit for public images.
36
+ */
37
+ registryAuth?: {
38
+ server: string;
39
+ username: string;
40
+ password: string;
41
+ };
42
+ env?: (input: SandboxDriverEnsureInput, host: {
43
+ id: string;
44
+ }) => Record<string, string> | Promise<Record<string, string>>;
45
+ provisionTimeoutMs?: number;
46
+ provisionIntervalMs?: number;
47
+ healthTimeoutMs?: number;
48
+ healthIntervalMs?: number;
49
+ operationTimeoutMs?: number;
50
+ fetchImpl?: BoxFetch;
51
+ };
52
+ export declare function createBoxSandboxDriver(options: BoxSandboxDriverOptions): SandboxDriver;
@@ -0,0 +1,304 @@
1
+ // src/runtime-env.ts
2
+ function workspaceRuntimeTargetEnv(input) {
3
+ return {
4
+ WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
5
+ ...input.hostId ? { WORKSPACE_RUNTIME_HOST_ID: input.hostId } : {},
6
+ WORKSPACE_RUNTIME_DIRECTORY: input.directory,
7
+ WORKSPACE_RUNTIME_PORT: String(input.port),
8
+ ...input.host ? { WORKSPACE_RUNTIME_HOST: input.host } : {}
9
+ };
10
+ }
11
+ function workspaceRuntimeSourceEnv(input) {
12
+ if (!input.source || input.source.kind === "empty") return { WORKSPACE_RUNTIME_SOURCE_KIND: "empty" };
13
+ return {
14
+ WORKSPACE_RUNTIME_SOURCE_KIND: "git",
15
+ WORKSPACE_RUNTIME_GIT_REPO_URL: input.source.repoUrl,
16
+ ...input.source.branch ? { WORKSPACE_RUNTIME_GIT_BRANCH: input.source.branch } : {}
17
+ };
18
+ }
19
+
20
+ // src/command.ts
21
+ function shell(input) {
22
+ return `'${input.replace(/'/g, `'"'"'`)}'`;
23
+ }
24
+
25
+ // src/constants.ts
26
+ var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
27
+
28
+ // src/image.ts
29
+ import { Image, DaytonaNotFoundError } from "@daytona/sdk";
30
+
31
+ // src/runtime-version.ts
32
+ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
33
+ var versionCache;
34
+ function workspaceRuntimeVersion() {
35
+ if (versionCache) return versionCache;
36
+ const envVersion = process.env.WORKSPACE_RUNTIME_VERSION;
37
+ if (envVersion) {
38
+ versionCache = envVersion;
39
+ return versionCache;
40
+ }
41
+ versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
42
+ return versionCache;
43
+ }
44
+
45
+ // src/image-name.ts
46
+ var SNAPSHOT_SCHEMA_VERSION = 8;
47
+ var DEFAULT_SANDBOX_IMAGE_REPOSITORY = "ghcr.io/kyashrathore/claxedo-sandbox";
48
+ function snapshotVersion(value) {
49
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
50
+ }
51
+ function sandboxImageRepository(env = process.env) {
52
+ const repository = env.CLAXEDO_SANDBOX_IMAGE_REPOSITORY?.trim();
53
+ return repository ? repository : DEFAULT_SANDBOX_IMAGE_REPOSITORY;
54
+ }
55
+ function buildIdSuffix(buildId, env) {
56
+ const id = (buildId ?? env.CLAXEDO_SANDBOX_BUILD_ID)?.trim();
57
+ return id ? `-${snapshotVersion(id)}` : "";
58
+ }
59
+ function defaultSandboxImage(version = workspaceRuntimeVersion(), buildId, env = process.env) {
60
+ return `${sandboxImageRepository(env)}:workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
61
+ }
62
+ function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env = process.env) {
63
+ return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
64
+ }
65
+
66
+ // src/image.ts
67
+ var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
68
+ var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
69
+ var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
70
+
71
+ // src/drivers/box.ts
72
+ var DEFAULT_BASE_URL = "https://ascii.dev/api/box/v1";
73
+ var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
74
+ var DEFAULT_WORKSPACE_DIR = "/workspace";
75
+ var DEFAULT_CONTAINER_NAME = "claxedo-runtime";
76
+ var DEFAULT_PROVISION_TIMEOUT_MS = 12e4;
77
+ var DEFAULT_PROVISION_INTERVAL_MS = 2e3;
78
+ var DEFAULT_HEALTH_TIMEOUT_MS = 6e4;
79
+ var DEFAULT_HEALTH_INTERVAL_MS = 1e3;
80
+ var DEFAULT_OPERATION_TIMEOUT_MS = 12e4;
81
+ var READY_STATES = /* @__PURE__ */ new Set(["ready", "idle", "running"]);
82
+ var PENDING_STATES = /* @__PURE__ */ new Set(["init", "provisioning", "provisioned", "cloning"]);
83
+ function sleep(ms) {
84
+ return new Promise((resolve) => setTimeout(resolve, ms));
85
+ }
86
+ function clean(input) {
87
+ const txt = input?.trim();
88
+ return txt ? txt : void 0;
89
+ }
90
+ function transientDriverError(err) {
91
+ const shaped = err;
92
+ if (typeof shaped.status === "number" && shaped.status >= 500) return true;
93
+ const text = `${shaped.code ?? ""} ${shaped.name ?? ""} ${shaped.message ?? ""}`.toLowerCase();
94
+ return text.includes("timeout") || text.includes("unavailable") || text.includes("econnreset") || text.includes("network");
95
+ }
96
+ var BoxApiError = class extends Error {
97
+ status;
98
+ constructor(message, status) {
99
+ super(message);
100
+ this.name = "BoxApiError";
101
+ this.status = status;
102
+ }
103
+ };
104
+ function createBoxSandboxDriver(options) {
105
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
106
+ const image = options.image ?? SANDBOX_IMAGE;
107
+ const runtimeCommand = options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND;
108
+ const workspaceDir = options.workspaceDir ?? DEFAULT_WORKSPACE_DIR;
109
+ const containerName = options.containerName ?? DEFAULT_CONTAINER_NAME;
110
+ const ttlSeconds = options.ttlSeconds === void 0 ? null : options.ttlSeconds;
111
+ const provisionTimeoutMs = options.provisionTimeoutMs ?? DEFAULT_PROVISION_TIMEOUT_MS;
112
+ const provisionIntervalMs = options.provisionIntervalMs ?? DEFAULT_PROVISION_INTERVAL_MS;
113
+ const healthTimeoutMs = options.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;
114
+ const healthIntervalMs = options.healthIntervalMs ?? DEFAULT_HEALTH_INTERVAL_MS;
115
+ const operationTimeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
116
+ const fetchImpl = options.fetchImpl ?? fetch;
117
+ async function api(path, init) {
118
+ let res;
119
+ try {
120
+ res = await fetchImpl(`${baseUrl}${path}`, {
121
+ method: init?.method ?? "GET",
122
+ headers: {
123
+ Authorization: `Bearer ${options.apiKey}`,
124
+ ...init?.body !== void 0 ? { "Content-Type": "application/json" } : {}
125
+ },
126
+ ...init?.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
127
+ signal: AbortSignal.timeout(operationTimeoutMs)
128
+ });
129
+ } catch (err) {
130
+ throw new BoxApiError(err instanceof Error ? err.message : String(err));
131
+ }
132
+ const text = await res.text();
133
+ const body = text ? JSON.parse(text) : {};
134
+ if (!res.ok || body.ok === false) {
135
+ const detail = clean(body.error) ?? clean(body.message) ?? res.statusText;
136
+ throw new BoxApiError(`Box API ${init?.method ?? "GET"} ${path} failed: ${detail}`, res.status);
137
+ }
138
+ return body;
139
+ }
140
+ function runtimePort(input) {
141
+ return input.workspaceRuntimePort ?? options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
142
+ }
143
+ function workspaceDirectory(input) {
144
+ return input.workspaceRoot ?? workspaceDir;
145
+ }
146
+ function resolveImage(input) {
147
+ if (input.bootSource?.kind === "image") return input.bootSource.image;
148
+ return input.snapshot ?? image;
149
+ }
150
+ function bootEnv(input, hostId) {
151
+ const env = {
152
+ ...workspaceRuntimeTargetEnv({
153
+ workspaceId: input.workspaceId,
154
+ hostId,
155
+ directory: workspaceDirectory(input),
156
+ port: runtimePort(input),
157
+ host: "0.0.0.0"
158
+ }),
159
+ ...workspaceRuntimeSourceEnv({ source: input.source }),
160
+ ...input.env
161
+ };
162
+ if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
163
+ return env;
164
+ }
165
+ async function exec(boxId, command) {
166
+ const body = await api(`/boxes/${boxId}/commands`, {
167
+ method: "POST",
168
+ body: { command }
169
+ });
170
+ const result = body.result ?? body;
171
+ return {
172
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
173
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
174
+ exitCode: typeof result.exitCode === "number" ? result.exitCode : 0
175
+ };
176
+ }
177
+ async function execOrThrow(boxId, command, label) {
178
+ const result = await exec(boxId, command);
179
+ if ((result.exitCode ?? 0) !== 0) {
180
+ throw new BoxApiError(`Box ${boxId} ${label} failed (exit ${result.exitCode}): ${clean(result.stderr) ?? clean(result.stdout) ?? ""}`);
181
+ }
182
+ return result;
183
+ }
184
+ async function waitUntilReady(boxId) {
185
+ const until = Date.now() + provisionTimeoutMs;
186
+ for (; ; ) {
187
+ const { box } = await api(`/boxes/${boxId}`);
188
+ if (READY_STATES.has(box.state)) return true;
189
+ if (!PENDING_STATES.has(box.state)) {
190
+ throw new BoxApiError(`Box ${boxId} entered non-ready state: ${box.state}`);
191
+ }
192
+ if (Date.now() >= until) return false;
193
+ await sleep(provisionIntervalMs);
194
+ }
195
+ }
196
+ function dockerRunScript(input, env) {
197
+ const port = runtimePort(input);
198
+ const directory = workspaceDirectory(input);
199
+ const bootScript = [`mkdir -p ${shell(directory)}`, `cd ${shell(directory)}`, `exec ${runtimeCommand}`].join(" && ");
200
+ const envArgs = Object.entries(env).map(([key, value]) => `--env ${shell(`${key}=${value}`)}`).join(" ");
201
+ const login = options.registryAuth ? `echo ${shell(options.registryAuth.password)} | docker login ${shell(options.registryAuth.server)} --username ${shell(options.registryAuth.username)} --password-stdin && ` : "";
202
+ return `${login}docker rm -f ${containerName} >/dev/null 2>&1 || true; docker run -d --name ${containerName} -p ${port}:${port} ${envArgs} --entrypoint sh ${shell(resolveImage(input))} -lc ${shell(bootScript)}`;
203
+ }
204
+ async function waitForHealth(boxId, input) {
205
+ const port = runtimePort(input);
206
+ const until = Date.now() + healthTimeoutMs;
207
+ let last = "workspace runtime not ready";
208
+ while (Date.now() < until) {
209
+ const probe = await exec(
210
+ boxId,
211
+ `curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:${port}/global/health || true`
212
+ );
213
+ const code = clean(probe.stdout);
214
+ if (code === "200") return;
215
+ last = `health probe returned ${code ?? "no response"}`;
216
+ await sleep(healthIntervalMs);
217
+ }
218
+ throw new BoxApiError(`Box ${boxId} runtime did not become healthy: ${last}`);
219
+ }
220
+ async function boot(boxId, input, hostId) {
221
+ const env = {
222
+ ...bootEnv(input, hostId),
223
+ ...await options.env?.(input, { id: hostId })
224
+ };
225
+ const port = runtimePort(input);
226
+ await execOrThrow(boxId, dockerRunScript(input, env), "docker run");
227
+ await execOrThrow(boxId, `host ${port}`, "host publish");
228
+ await waitForHealth(boxId, input);
229
+ const urlResult = await execOrThrow(boxId, `host url ${port}`, "host url");
230
+ const url = clean(urlResult.stdout?.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).pop());
231
+ if (!url) throw new BoxApiError(`Box ${boxId} did not return a public URL for port ${port}`);
232
+ return {
233
+ workspaceId: input.workspaceId,
234
+ sandboxId: boxId,
235
+ url,
236
+ hostId,
237
+ driverResourceId: boxId,
238
+ driver: { id: "box", resourceId: boxId },
239
+ labels: input.labels
240
+ };
241
+ }
242
+ function assertNetwork(input) {
243
+ if (input.net && input.net.mode !== "allow-all") {
244
+ throw new BoxApiError("Box SandboxDriver cannot enforce host-based network policy");
245
+ }
246
+ }
247
+ async function ensureHost(input) {
248
+ assertNetwork(input);
249
+ const hostId = input.hostId ?? `box-${input.workspaceId}`;
250
+ const created = await api("/boxes", {
251
+ method: "POST",
252
+ body: { ttlSeconds }
253
+ }).catch((err) => {
254
+ if (transientDriverError(err)) return void 0;
255
+ throw err;
256
+ });
257
+ if (!created) return { provisioning: true, retryAfterMs: provisionIntervalMs };
258
+ const boxId = created.box.id;
259
+ const ready = await waitUntilReady(boxId);
260
+ if (!ready) return { provisioning: true, retryAfterMs: provisionIntervalMs };
261
+ return boot(boxId, input, hostId);
262
+ }
263
+ async function resumeHost(input) {
264
+ const boxId = input.lease.sandboxId;
265
+ if (!boxId) return ensureHost(input.ensure);
266
+ assertNetwork(input.ensure);
267
+ const hostId = input.ensure.hostId ?? input.lease.hostId ?? `box-${input.ensure.workspaceId}`;
268
+ try {
269
+ await api(`/boxes/${boxId}/resume`, { method: "POST" });
270
+ const ready = await waitUntilReady(boxId);
271
+ if (!ready) return { provisioning: true, retryAfterMs: provisionIntervalMs };
272
+ return await boot(boxId, input.ensure, hostId);
273
+ } catch (err) {
274
+ if (transientDriverError(err)) return { provisioning: true, retryAfterMs: provisionIntervalMs };
275
+ return ensureHost(input.ensure);
276
+ }
277
+ }
278
+ async function stop(target) {
279
+ await api(`/boxes/${target.sandboxId}/stop`, { method: "POST" });
280
+ }
281
+ async function destroy(target) {
282
+ await api(`/boxes/${target.sandboxId}`, { method: "DELETE" });
283
+ }
284
+ return {
285
+ id: "box",
286
+ metadata: {
287
+ driverRunsIn: ["node"],
288
+ hostStopBehavior: "suspends-host",
289
+ hostResumeBehavior: "same-host",
290
+ targetAccess: "relay",
291
+ secretBrokering: "none"
292
+ },
293
+ ensureHost,
294
+ resumeHost,
295
+ async touch() {
296
+ },
297
+ suspend: stop,
298
+ stop,
299
+ destroy
300
+ };
301
+ }
302
+ export {
303
+ createBoxSandboxDriver
304
+ };
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/drivers/cloudflare-egress.ts
4
2
  function b64urlEncode(bytes) {
5
3
  let binary = "";
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/constants.ts
4
2
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
5
3
 
@@ -25,7 +23,7 @@ function workspaceRuntimeSourceEnv(input) {
25
23
  // src/drivers/cloudflare.ts
26
24
  var DEFAULT_WORKSPACE_DIR = "/workspace";
27
25
  var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
28
- var DEFAULT_TIMEOUT_MS = 12e4;
26
+ var DEFAULT_TIMEOUT_MS = 45e3;
29
27
  function cleanUrl(input) {
30
28
  return input.replace(/\/+$/, "");
31
29
  }
@@ -94,7 +92,7 @@ function createCloudflareSandboxDriver(options) {
94
92
  const sandboxId = sandboxIdFor(input.workspaceId);
95
93
  const hostId = sandboxId;
96
94
  const egress = egressRegistrations(input);
97
- const { status, data } = await call(
95
+ const response = await call(
98
96
  sandboxId,
99
97
  "ensure-runtime",
100
98
  {
@@ -103,7 +101,12 @@ function createCloudflareSandboxDriver(options) {
103
101
  command: runtimeCommand,
104
102
  ...egress ? { egress } : {}
105
103
  }
106
- );
104
+ ).catch((error) => {
105
+ if (error instanceof Error && error.name === "TimeoutError") return void 0;
106
+ throw error;
107
+ });
108
+ if (!response) return { provisioning: true, retryAfterMs: 2e3 };
109
+ const { status, data } = response;
107
110
  if (status >= 500) return { provisioning: true, retryAfterMs: 2e3 };
108
111
  if (status >= 400 || !data?.url) {
109
112
  throw new Error(`Cloudflare ensure-runtime failed (${status}): ${data?.error ?? "no runtime url"}`);
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/drivers/daytona.ts
4
2
  import { Daytona } from "@daytona/sdk";
5
3
 
@@ -5,6 +5,7 @@ export type DockerCommandResult = {
5
5
  stderr?: string | Buffer;
6
6
  };
7
7
  export type DockerCommandRunner = (args: string[], timeoutMs?: number) => Promise<DockerCommandResult>;
8
+ export type DockerHealthFetch = (input: string, init?: RequestInit) => Promise<Response>;
8
9
  export type DockerSandboxDriverOptions = {
9
10
  image: string;
10
11
  runtimePort?: number;
@@ -17,6 +18,10 @@ export type DockerSandboxDriverOptions = {
17
18
  syncLocalAuth?: boolean;
18
19
  authHome?: string;
19
20
  operationTimeoutMs?: number;
21
+ waitForHealth?: boolean;
22
+ healthTimeoutMs?: number;
23
+ healthIntervalMs?: number;
24
+ healthFetch?: DockerHealthFetch;
20
25
  docker?: DockerCommandRunner;
21
26
  };
22
27
  export declare function dockerSandboxSyncLocalAuth(env?: SandboxEnv): boolean;
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/drivers/docker.ts
4
2
  import { execFile } from "node:child_process";
5
3
  import fs from "node:fs/promises";
@@ -38,8 +36,7 @@ var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
38
36
  import { Image, DaytonaNotFoundError } from "@daytona/sdk";
39
37
 
40
38
  // src/runtime-version.ts
41
- import { createRequire } from "module";
42
- var require2 = createRequire(import.meta.url);
39
+ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
43
40
  var versionCache;
44
41
  function workspaceRuntimeVersion() {
45
42
  if (versionCache) return versionCache;
@@ -48,12 +45,7 @@ function workspaceRuntimeVersion() {
48
45
  versionCache = envVersion;
49
46
  return versionCache;
50
47
  }
51
- try {
52
- versionCache = require2("@claxedo/workspace-runtime/package.json").version;
53
- return versionCache;
54
- } catch {
55
- }
56
- versionCache = "0.0.0";
48
+ versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
57
49
  return versionCache;
58
50
  }
59
51
 
@@ -86,6 +78,8 @@ var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
86
78
  // src/drivers/docker.ts
87
79
  var execFileAsync = promisify(execFile);
88
80
  var DEFAULT_OPERATION_TIMEOUT_MS = 6e4;
81
+ var DEFAULT_HEALTH_TIMEOUT_MS = 2e4;
82
+ var DEFAULT_HEALTH_INTERVAL_MS = 250;
89
83
  var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
90
84
  var DEFAULT_WORKSPACE_DIR = "/workspace";
91
85
  var DOCKER_LABEL = "claxedo";
@@ -145,6 +139,9 @@ async function docker(args, timeoutMs = DEFAULT_OPERATION_TIMEOUT_MS) {
145
139
  timeout: timeoutMs
146
140
  });
147
141
  }
142
+ function sleep(ms) {
143
+ return new Promise((resolve) => setTimeout(resolve, ms));
144
+ }
148
145
  async function stageLocalAgentAuth(containerId, input) {
149
146
  if (!input.syncLocalAuth) return;
150
147
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "claxedo-docker-auth-"));
@@ -203,6 +200,9 @@ function createDockerSandboxDriver(options) {
203
200
  const runtimeCommand = options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND;
204
201
  const workspaceDir = options.workspaceDir ?? DEFAULT_WORKSPACE_DIR;
205
202
  const timeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
203
+ const healthTimeoutMs = options.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;
204
+ const healthIntervalMs = options.healthIntervalMs ?? DEFAULT_HEALTH_INTERVAL_MS;
205
+ const healthFetch = options.healthFetch ?? fetch;
206
206
  const syncLocalAuth = options.syncLocalAuth ?? dockerSandboxSyncLocalAuth();
207
207
  const authHome = options.authHome ?? dockerSandboxAuthHome();
208
208
  function runtimePort(input) {
@@ -236,6 +236,7 @@ function createDockerSandboxDriver(options) {
236
236
  const out = await dockerCommand(["port", sandboxId, `${runtimePort(input)}/tcp`], timeoutMs);
237
237
  const url = parseDockerPort(String(out.stdout ?? ""));
238
238
  if (!url) throw new Error(`Docker container ${sandboxId} does not expose workspace runtime port ${runtimePort(input)}`);
239
+ if (options.waitForHealth !== false) await waitForHealth(url);
239
240
  return {
240
241
  workspaceId: input.workspaceId,
241
242
  sandboxId,
@@ -249,6 +250,23 @@ function createDockerSandboxDriver(options) {
249
250
  labels: input.labels
250
251
  };
251
252
  }
253
+ async function waitForHealth(url) {
254
+ const until = Date.now() + healthTimeoutMs;
255
+ let error = "workspace runtime not ready";
256
+ while (Date.now() < until) {
257
+ try {
258
+ const res = await healthFetch(`${url}/global/health`, {
259
+ signal: AbortSignal.timeout(Math.min(2e3, Math.max(1, healthTimeoutMs)))
260
+ });
261
+ if (res.ok) return;
262
+ error = `${res.status} ${res.statusText}`;
263
+ } catch (err) {
264
+ error = err instanceof Error ? err.message : String(err);
265
+ }
266
+ await sleep(healthIntervalMs);
267
+ }
268
+ throw new Error(error);
269
+ }
252
270
  async function ensureHost(input) {
253
271
  if (input.net && input.net.mode !== "allow-all") {
254
272
  throw new Error("Docker SandboxDriver cannot enforce restricted network policies");
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/drivers/fetch-bridge.ts
4
2
  function cleanUrl(input) {
5
3
  return input.replace(/\/+$/, "");
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/drivers/modal.ts
4
2
  import { ModalClient, Probe } from "modal";
5
3
 
@@ -34,8 +32,7 @@ var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
34
32
  import { Image, DaytonaNotFoundError } from "@daytona/sdk";
35
33
 
36
34
  // src/runtime-version.ts
37
- import { createRequire } from "module";
38
- var require2 = createRequire(import.meta.url);
35
+ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
39
36
  var versionCache;
40
37
  function workspaceRuntimeVersion() {
41
38
  if (versionCache) return versionCache;
@@ -44,12 +41,7 @@ function workspaceRuntimeVersion() {
44
41
  versionCache = envVersion;
45
42
  return versionCache;
46
43
  }
47
- try {
48
- versionCache = require2("@claxedo/workspace-runtime/package.json").version;
49
- return versionCache;
50
- } catch {
51
- }
52
- versionCache = "0.0.0";
44
+ versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
53
45
  return versionCache;
54
46
  }
55
47
 
@@ -94,6 +94,7 @@ export type VercelSandboxDriverOptions = {
94
94
  keepAliveMs?: number;
95
95
  sandbox?: VercelSandboxFactoryLike;
96
96
  snapshotCache?: Map<string, string>;
97
+ now?: () => number;
97
98
  };
98
99
  /**
99
100
  * Brokered-secret egress policy: Vercel's firewall injects the credential as
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/drivers/vercel.ts
4
2
  import { Sandbox } from "@vercel/sandbox";
5
3
 
@@ -23,8 +21,7 @@ function workspaceRuntimeSourceEnv(input) {
23
21
  }
24
22
 
25
23
  // src/runtime-version.ts
26
- import { createRequire } from "module";
27
- var require2 = createRequire(import.meta.url);
24
+ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
28
25
  var versionCache;
29
26
  function workspaceRuntimeVersion() {
30
27
  if (versionCache) return versionCache;
@@ -33,12 +30,7 @@ function workspaceRuntimeVersion() {
33
30
  versionCache = envVersion;
34
31
  return versionCache;
35
32
  }
36
- try {
37
- versionCache = require2("@claxedo/workspace-runtime/package.json").version;
38
- return versionCache;
39
- } catch {
40
- }
41
- versionCache = "0.0.0";
33
+ versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
42
34
  return versionCache;
43
35
  }
44
36
 
@@ -63,7 +55,9 @@ var DEFAULT_OPERATION_TIMEOUT_MS = 6e4;
63
55
  var DEFAULT_KEEP_ALIVE_MS = 5 * 60 * 1e3;
64
56
  var EXPECTED_RUNTIME_VERSION = workspaceRuntimeVersion();
65
57
  var RUNTIME_PKG = `@claxedo/workspace-runtime@${EXPECTED_RUNTIME_VERSION}`;
58
+ var SNAPSHOT_CACHE_TTL_MS = 60 * 60 * 1e3;
66
59
  var snapshotCache = /* @__PURE__ */ new Map();
60
+ var snapshotCacheState = /* @__PURE__ */ new WeakMap();
67
61
  function nameFor(workspaceId) {
68
62
  return `claxedo-${workspaceId}`;
69
63
  }
@@ -106,6 +100,12 @@ function createVercelSandboxDriver(options) {
106
100
  const operationTimeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
107
101
  const keepAliveMs = options.keepAliveMs ?? DEFAULT_KEEP_ALIVE_MS;
108
102
  const cache = options.snapshotCache ?? snapshotCache;
103
+ const now = options.now ?? Date.now;
104
+ const cacheState = snapshotCacheState.get(cache) ?? {
105
+ expires: /* @__PURE__ */ new Map(),
106
+ builds: /* @__PURE__ */ new Map()
107
+ };
108
+ snapshotCacheState.set(cache, cacheState);
109
109
  function workspaceDirectory(input) {
110
110
  return input.workspaceRoot ?? workspaceDir;
111
111
  }
@@ -143,56 +143,72 @@ function createVercelSandboxDriver(options) {
143
143
  if (options.baseSnapshotId) return options.baseSnapshotId;
144
144
  const cacheKey = `${options.teamId}:${options.projectId}:${runtime}:${EXPECTED_RUNTIME_VERSION}`;
145
145
  const hit = cache.get(cacheKey);
146
- if (hit) return hit;
147
- const builder = await sandboxFactory.create({
148
- ...credentials(),
149
- runtime,
150
- ports: [runtimePort],
151
- timeout: snapshotBuildTimeoutMs
152
- });
153
- await runSetup(builder, "sudo dnf install -y git make gcc-c++ python3 > /dev/null 2>&1");
154
- await runSetup(builder, [
155
- "npm i -g --min-release-age=2 tsx@4.22.3 opencode-ai@1.15.10 @anthropic-ai/claude-code@2.1.150 @openai/codex@0.133.0 @google/gemini-cli@0.43.0 @earendil-works/pi-coding-agent@0.75.5",
156
- "curl https://cursor.com/install -fsS | bash",
157
- "curl -fsSL https://ampcode.com/install.sh | bash",
158
- "curl -fsSL https://app.factory.ai/cli | sh",
159
- "sudo ln -sf $HOME/.local/bin/agent /usr/local/bin/agent",
160
- "sudo ln -sf $HOME/.local/bin/cursor-agent /usr/local/bin/cursor-agent",
161
- "sudo ln -sf $HOME/.local/bin/amp /usr/local/bin/amp",
162
- "sudo ln -sf $HOME/.local/bin/droid /usr/local/bin/droid",
163
- "command -v opencode",
164
- "command -v claude",
165
- "command -v codex",
166
- "command -v gemini",
167
- "command -v agent",
168
- "command -v cursor-agent",
169
- "command -v amp",
170
- "command -v droid",
171
- "command -v pi"
172
- ].join(" && "));
173
- await runSetup(builder, [
174
- `sudo mkdir -p ${shell(RUNTIME_DIR)}`,
175
- `sudo chown $(whoami) ${shell(RUNTIME_DIR)}`,
176
- `cd ${shell(RUNTIME_DIR)}`,
177
- "npm init -y",
178
- `npm install ${RUNTIME_PKG} --min-release-age=2`,
179
- `node -e "const fs=require('fs');const actual=JSON.parse(fs.readFileSync('${RUNTIME_DIR}/node_modules/@claxedo/workspace-runtime/package.json','utf8')).version;if(actual!=='${EXPECTED_RUNTIME_VERSION}'){throw new Error('workspace-runtime snapshot package version mismatch: expected ${EXPECTED_RUNTIME_VERSION}, got '+actual)}"`,
180
- "test -x node_modules/.bin/workspace-runtime",
181
- "test -x node_modules/.bin/claude-agent-acp",
182
- "test -x node_modules/.bin/codex-acp",
183
- `sudo ln -sf ${RUNTIME_DIR}/node_modules/.bin/workspace-runtime /usr/local/bin/workspace-runtime`,
184
- `sudo ln -sf ${RUNTIME_DIR}/node_modules/.bin/claude-agent-acp /usr/local/bin/claude-agent-acp`,
185
- `sudo ln -sf ${RUNTIME_DIR}/node_modules/.bin/codex-acp /usr/local/bin/codex-acp`,
186
- "command -v workspace-runtime",
187
- "command -v claude-agent-acp",
188
- "command -v codex-acp"
189
- ].join(" && "));
190
- const snapshot = await builder.snapshot({
191
- expiration: 0,
192
- signal: AbortSignal.timeout(snapshotBuildTimeoutMs)
193
- });
194
- cache.set(cacheKey, snapshot.snapshotId);
195
- return snapshot.snapshotId;
146
+ const expiresAt = cacheState.expires.get(cacheKey);
147
+ if (hit && expiresAt === void 0) {
148
+ cacheState.expires.set(cacheKey, now() + SNAPSHOT_CACHE_TTL_MS);
149
+ return hit;
150
+ }
151
+ if (hit && expiresAt !== void 0 && expiresAt > now()) return hit;
152
+ const running = cacheState.builds.get(cacheKey);
153
+ if (running) return running;
154
+ const build = (async () => {
155
+ const builder = await sandboxFactory.create({
156
+ ...credentials(),
157
+ runtime,
158
+ ports: [runtimePort],
159
+ timeout: snapshotBuildTimeoutMs
160
+ });
161
+ await runSetup(builder, "sudo dnf install -y git make gcc-c++ python3 > /dev/null 2>&1");
162
+ await runSetup(builder, [
163
+ "npm i -g --min-release-age=2 tsx@4.22.3 opencode-ai@1.15.10 @anthropic-ai/claude-code@2.1.150 @openai/codex@0.133.0 @google/gemini-cli@0.43.0 @earendil-works/pi-coding-agent@0.75.5",
164
+ "curl https://cursor.com/install -fsS | bash",
165
+ "curl -fsSL https://ampcode.com/install.sh | bash",
166
+ "curl -fsSL https://app.factory.ai/cli | sh",
167
+ "sudo ln -sf $HOME/.local/bin/agent /usr/local/bin/agent",
168
+ "sudo ln -sf $HOME/.local/bin/cursor-agent /usr/local/bin/cursor-agent",
169
+ "sudo ln -sf $HOME/.local/bin/amp /usr/local/bin/amp",
170
+ "sudo ln -sf $HOME/.local/bin/droid /usr/local/bin/droid",
171
+ "command -v opencode",
172
+ "command -v claude",
173
+ "command -v codex",
174
+ "command -v gemini",
175
+ "command -v agent",
176
+ "command -v cursor-agent",
177
+ "command -v amp",
178
+ "command -v droid",
179
+ "command -v pi"
180
+ ].join(" && "));
181
+ await runSetup(builder, [
182
+ `sudo mkdir -p ${shell(RUNTIME_DIR)}`,
183
+ `sudo chown $(whoami) ${shell(RUNTIME_DIR)}`,
184
+ `cd ${shell(RUNTIME_DIR)}`,
185
+ "npm init -y",
186
+ `npm install ${RUNTIME_PKG} --min-release-age=2`,
187
+ `node -e "const fs=require('fs');const actual=JSON.parse(fs.readFileSync('${RUNTIME_DIR}/node_modules/@claxedo/workspace-runtime/package.json','utf8')).version;if(actual!=='${EXPECTED_RUNTIME_VERSION}'){throw new Error('workspace-runtime snapshot package version mismatch: expected ${EXPECTED_RUNTIME_VERSION}, got '+actual)}"`,
188
+ "test -x node_modules/.bin/workspace-runtime",
189
+ "test -x node_modules/.bin/claude-agent-acp",
190
+ "test -x node_modules/.bin/codex-acp",
191
+ `sudo ln -sf ${RUNTIME_DIR}/node_modules/.bin/workspace-runtime /usr/local/bin/workspace-runtime`,
192
+ `sudo ln -sf ${RUNTIME_DIR}/node_modules/.bin/claude-agent-acp /usr/local/bin/claude-agent-acp`,
193
+ `sudo ln -sf ${RUNTIME_DIR}/node_modules/.bin/codex-acp /usr/local/bin/codex-acp`,
194
+ "command -v workspace-runtime",
195
+ "command -v claude-agent-acp",
196
+ "command -v codex-acp"
197
+ ].join(" && "));
198
+ const snapshot = await builder.snapshot({
199
+ expiration: 0,
200
+ signal: AbortSignal.timeout(snapshotBuildTimeoutMs)
201
+ });
202
+ cache.set(cacheKey, snapshot.snapshotId);
203
+ cacheState.expires.set(cacheKey, now() + SNAPSHOT_CACHE_TTL_MS);
204
+ return snapshot.snapshotId;
205
+ })();
206
+ cacheState.builds.set(cacheKey, build);
207
+ try {
208
+ return await build;
209
+ } finally {
210
+ if (cacheState.builds.get(cacheKey) === build) cacheState.builds.delete(cacheKey);
211
+ }
196
212
  }
197
213
  async function source(input) {
198
214
  if (input.bootSource?.kind === "image") {
package/dist/image.mjs CHANGED
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/image.ts
4
2
  import { Image, DaytonaNotFoundError } from "@daytona/sdk";
5
3
 
@@ -12,8 +10,7 @@ var WORKSPACE_DIR = "/workspace";
12
10
  var RUNTIME_DIR = "/opt/workspace-runtime";
13
11
 
14
12
  // src/runtime-version.ts
15
- import { createRequire } from "module";
16
- var require2 = createRequire(import.meta.url);
13
+ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
17
14
  var versionCache;
18
15
  function workspaceRuntimeVersion() {
19
16
  if (versionCache) return versionCache;
@@ -22,12 +19,7 @@ function workspaceRuntimeVersion() {
22
19
  versionCache = envVersion;
23
20
  return versionCache;
24
21
  }
25
- try {
26
- versionCache = require2("@claxedo/workspace-runtime/package.json").version;
27
- return versionCache;
28
- } catch {
29
- }
30
- versionCache = "0.0.0";
22
+ versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
31
23
  return versionCache;
32
24
  }
33
25
 
package/dist/index.mjs CHANGED
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/constants.ts
4
2
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
5
3
 
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/lease-policy.ts
4
2
  var DEFAULT_WORKSPACE_HOST_DECISION_CONFIG = {
5
3
  maxRetries: 8,
@@ -36,6 +34,13 @@ var DRIVER_PLACEMENT = {
36
34
  canStopExplicitly: false,
37
35
  hasHealthProbe: true
38
36
  },
37
+ box: {
38
+ canPauseAndRestartSameResource: true,
39
+ canCreateFilesystemSnapshot: false,
40
+ canStartFromPreparedImage: true,
41
+ canStopExplicitly: true,
42
+ hasHealthProbe: true
43
+ },
39
44
  docker: {
40
45
  canPauseAndRestartSameResource: true,
41
46
  canCreateFilesystemSnapshot: false,
@@ -1 +0,0 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/runtime-env.ts
4
2
  function workspaceRuntimeTargetEnv(input) {
5
3
  return {
@@ -1,8 +1,5 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/runtime-version.ts
4
- import { createRequire } from "module";
5
- var require2 = createRequire(import.meta.url);
2
+ var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
6
3
  var versionCache;
7
4
  function workspaceRuntimeVersion() {
8
5
  if (versionCache) return versionCache;
@@ -11,12 +8,7 @@ function workspaceRuntimeVersion() {
11
8
  versionCache = envVersion;
12
9
  return versionCache;
13
10
  }
14
- try {
15
- versionCache = require2("@claxedo/workspace-runtime/package.json").version;
16
- return versionCache;
17
- } catch {
18
- }
19
- versionCache = "0.0.0";
11
+ versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
20
12
  return versionCache;
21
13
  }
22
14
  export {
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/runtime-env.ts
4
2
  function workspaceRuntimeServiceExposureEnv(input) {
5
3
  return {
@@ -1,5 +1,3 @@
1
- import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
2
-
3
1
  // src/index.ts
4
2
  function applySandboxLeasePatch(current, patch, updatedAt) {
5
3
  return {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@claxedo/sandbox-manager",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
- "description": "Sandbox lifecycle manager with epoch-based leases and pluggable drivers for Daytona, Modal, Vercel Sandbox, Cloudflare, and Docker",
5
+ "description": "Sandbox lifecycle manager with epoch-based leases and pluggable drivers for Daytona, Modal, Vercel Sandbox, Cloudflare, Box, and Docker",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
@@ -14,6 +14,10 @@
14
14
  "sandbox",
15
15
  "daytona",
16
16
  "modal",
17
+ "vercel",
18
+ "cloudflare",
19
+ "docker",
20
+ "box",
17
21
  "agents"
18
22
  ],
19
23
  "main": "./dist/index.mjs",
@@ -54,6 +58,13 @@
54
58
  "import": "./dist/driver-catalog.mjs",
55
59
  "default": "./dist/driver-catalog.mjs"
56
60
  },
61
+ "./drivers/box": {
62
+ "types": "./dist/drivers/box.d.ts",
63
+ "bun": "./dist/drivers/box.mjs",
64
+ "development": "./dist/drivers/box.mjs",
65
+ "import": "./dist/drivers/box.mjs",
66
+ "default": "./dist/drivers/box.mjs"
67
+ },
57
68
  "./drivers/cloudflare": {
58
69
  "types": "./dist/drivers/cloudflare.d.ts",
59
70
  "bun": "./dist/drivers/cloudflare.mjs",
@@ -155,7 +166,6 @@
155
166
  "access": "public"
156
167
  },
157
168
  "dependencies": {
158
- "@claxedo/workspace-runtime": "0.5.1",
159
169
  "@daytona/sdk": "0.192.0",
160
170
  "@vercel/sandbox": "1.10.2",
161
171
  "modal": "0.7.5"
@@ -172,7 +182,7 @@
172
182
  "@types/node": "24.12.2",
173
183
  "esbuild": "0.25.12",
174
184
  "tsx": "4.22.3",
175
- "typescript": "5.8.2",
185
+ "typescript": "catalog:",
176
186
  "vitest": "2.1.9"
177
187
  }
178
188
  }