@claxedo/sandbox-manager 0.5.2 → 0.7.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,56 +1,220 @@
1
- // src/lease-policy.ts
2
- var DEFAULT_WORKSPACE_HOST_DECISION_CONFIG = {
3
- maxRetries: 8,
4
- idleMs: 10 * 60 * 1e3,
5
- backoffMaxMs: 3e4,
6
- healthTimeoutMs: 6e4
7
- };
8
- var DRIVER_PLACEMENT = {
1
+ // src/hosted-network-policy.ts
2
+ var SANDBOX_MODEL_PROVIDER_HOSTS = [
3
+ "api.anthropic.com",
4
+ "api.openai.com",
5
+ "generativelanguage.googleapis.com",
6
+ "openrouter.ai",
7
+ // Model catalog + engine config, fetched on runner startup.
8
+ "models.dev",
9
+ "opencode.ai"
10
+ ];
11
+ var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
12
+ "registry.npmjs.org",
13
+ "nodejs.org",
14
+ "pypi.org",
15
+ "files.pythonhosted.org",
16
+ "crates.io",
17
+ "static.crates.io",
18
+ "index.crates.io",
19
+ "proxy.golang.org",
20
+ "sum.golang.org"
21
+ ];
22
+ var SANDBOX_BASELINE_EGRESS_HOSTS = [
23
+ ...SANDBOX_MODEL_PROVIDER_HOSTS,
24
+ ...SANDBOX_PACKAGE_REGISTRY_HOSTS
25
+ ];
26
+
27
+ // src/driver-catalog.ts
28
+ var sandboxDriverCatalog = {
29
+ exe: {
30
+ id: "exe",
31
+ label: "exe.dev",
32
+ credentialFields: [{ key: "api_token", label: "API Token", secret: true }],
33
+ metadata: {
34
+ driverRunsIn: ["worker", "node"],
35
+ hostStopBehavior: "not-supported",
36
+ hostResumeBehavior: "same-host",
37
+ targetAccess: "relay",
38
+ secretBrokering: "none",
39
+ egressControl: "none",
40
+ persistence: {
41
+ resume: "same-sandbox",
42
+ capture: "none",
43
+ clone: true,
44
+ captureSource: "not-applicable",
45
+ retention: "provider-managed",
46
+ restoreMount: "same-resource"
47
+ }
48
+ }
49
+ },
9
50
  daytona: {
10
- canPauseAndRestartSameResource: true,
11
- canCreateFilesystemSnapshot: false,
12
- canStartFromPreparedImage: true,
13
- canStopExplicitly: true,
14
- hasHealthProbe: true
51
+ id: "daytona",
52
+ label: "Daytona",
53
+ credentialFields: [{ key: "api_key", label: "API Key", secret: true }],
54
+ metadata: {
55
+ driverRunsIn: ["worker", "node"],
56
+ hostStopBehavior: "suspends-host",
57
+ hostResumeBehavior: "same-host",
58
+ targetAccess: "relay",
59
+ secretBrokering: "native",
60
+ // Daytona is the only driver that can filter egress by NAME as well as
61
+ // by address: `domainAllowList` (names) alongside `networkAllowList`
62
+ // (CIDRs), with `networkBlockAll` as the deny-all floor.
63
+ egressControl: "hosts-and-cidrs",
64
+ persistence: {
65
+ resume: "same-sandbox",
66
+ capture: "filesystem",
67
+ clone: false,
68
+ captureSource: "preserved",
69
+ retention: "provider-managed",
70
+ restoreMount: "new-resource"
71
+ }
72
+ }
15
73
  },
16
74
  modal: {
17
- canPauseAndRestartSameResource: false,
18
- canCreateFilesystemSnapshot: true,
19
- canStartFromPreparedImage: true,
20
- canStopExplicitly: true,
21
- hasHealthProbe: true
75
+ id: "modal",
76
+ label: "Modal",
77
+ credentialFields: [
78
+ { key: "token_id", label: "Token ID", secret: true },
79
+ { key: "token_secret", label: "Token Secret", secret: true }
80
+ ],
81
+ metadata: {
82
+ driverRunsIn: ["node"],
83
+ hostStopBehavior: "terminates-host",
84
+ hostResumeBehavior: "replacement-host",
85
+ targetAccess: "relay",
86
+ secretBrokering: "none",
87
+ // Modal can cut the network entirely (`blockNetwork`) but cannot express
88
+ // an allowlist, and its driver throws on a host policy. A blackout is
89
+ // not containment for a workspace that has to clone and reach a model.
90
+ egressControl: "none",
91
+ persistence: {
92
+ resume: "replacement-restore",
93
+ capture: "filesystem",
94
+ clone: false,
95
+ captureSource: "preserved",
96
+ retention: "provider-managed",
97
+ restoreMount: "new-resource"
98
+ }
99
+ }
22
100
  },
23
101
  vercel: {
24
- canPauseAndRestartSameResource: false,
25
- canCreateFilesystemSnapshot: true,
26
- canStartFromPreparedImage: false,
27
- canStopExplicitly: true,
28
- hasHealthProbe: true
102
+ id: "vercel",
103
+ label: "Vercel",
104
+ credentialFields: [
105
+ { key: "access_token", label: "Access Token", secret: true },
106
+ { key: "team_id", label: "Team ID" },
107
+ { key: "project_id", label: "Project ID" }
108
+ ],
109
+ metadata: {
110
+ driverRunsIn: ["node"],
111
+ hostStopBehavior: "terminates-host",
112
+ hostResumeBehavior: "replacement-host",
113
+ targetAccess: "relay",
114
+ secretBrokering: "native",
115
+ // Vercel's sandbox firewall takes a hostname allow list (or "deny-all"),
116
+ // never CIDRs.
117
+ egressControl: "hosts",
118
+ persistence: {
119
+ resume: "replacement-restore",
120
+ capture: "filesystem",
121
+ clone: false,
122
+ captureSource: "stopped",
123
+ retention: "explicit",
124
+ restoreMount: "new-resource"
125
+ }
126
+ }
29
127
  },
30
128
  cloudflare: {
31
- canPauseAndRestartSameResource: false,
32
- canCreateFilesystemSnapshot: true,
33
- canStartFromPreparedImage: false,
34
- canStopExplicitly: false,
35
- hasHealthProbe: true
129
+ id: "cloudflare",
130
+ label: "Cloudflare",
131
+ credentialFields: [
132
+ { key: "api_token", label: "API Token", secret: true },
133
+ { key: "worker_url", label: "Worker URL" }
134
+ ],
135
+ metadata: {
136
+ driverRunsIn: ["worker"],
137
+ hostStopBehavior: "not-supported",
138
+ hostResumeBehavior: "same-host",
139
+ targetAccess: "relay",
140
+ secretBrokering: "proxy",
141
+ // The Cloudflare Sandbox Worker exposes no egress filter: its broker
142
+ // (drivers/cloudflare-egress.ts) is an OPT-IN proxy the sandbox chooses
143
+ // to route brokered-credential requests through, not a boundary. The
144
+ // driver therefore drops `net` entirely — which is exactly why this
145
+ // must be declared, so the manager refuses instead of provisioning a
146
+ // sandbox the caller believes is contained.
147
+ egressControl: "none",
148
+ persistence: {
149
+ resume: "replacement-restore",
150
+ capture: "directories",
151
+ clone: false,
152
+ captureSource: "preserved",
153
+ retention: "provider-managed",
154
+ restoreMount: "copy-on-write"
155
+ }
156
+ }
36
157
  },
37
158
  box: {
38
- canPauseAndRestartSameResource: true,
39
- canCreateFilesystemSnapshot: false,
40
- canStartFromPreparedImage: true,
41
- canStopExplicitly: true,
42
- hasHealthProbe: true
159
+ id: "box",
160
+ label: "Box",
161
+ credentialFields: [{ key: "api_key", label: "API Key", secret: true }],
162
+ metadata: {
163
+ driverRunsIn: ["node"],
164
+ hostStopBehavior: "suspends-host",
165
+ hostResumeBehavior: "same-host",
166
+ targetAccess: "relay",
167
+ secretBrokering: "none",
168
+ egressControl: "none",
169
+ persistence: {
170
+ resume: "same-sandbox",
171
+ capture: "none",
172
+ clone: false,
173
+ captureSource: "not-applicable",
174
+ retention: "provider-managed",
175
+ restoreMount: "same-resource"
176
+ }
177
+ }
43
178
  },
44
179
  docker: {
45
- canPauseAndRestartSameResource: true,
46
- canCreateFilesystemSnapshot: false,
47
- canStartFromPreparedImage: true,
48
- canStopExplicitly: true,
49
- hasHealthProbe: true
180
+ id: "docker",
181
+ label: "Docker",
182
+ credentialFields: [{ key: "image", label: "Image" }],
183
+ metadata: {
184
+ driverRunsIn: ["local"],
185
+ hostStopBehavior: "terminates-host",
186
+ hostResumeBehavior: "same-host",
187
+ targetAccess: "loopback",
188
+ secretBrokering: "none",
189
+ egressControl: "none",
190
+ persistence: {
191
+ resume: "replacement-restore",
192
+ capture: "same-resource",
193
+ clone: false,
194
+ captureSource: "preserved",
195
+ retention: "provider-managed",
196
+ restoreMount: "same-resource"
197
+ }
198
+ }
50
199
  }
51
200
  };
201
+
202
+ // src/lease-policy.ts
203
+ var DEFAULT_WORKSPACE_HOST_DECISION_CONFIG = {
204
+ maxRetries: 8,
205
+ idleMs: 10 * 60 * 1e3,
206
+ backoffMaxMs: 3e4,
207
+ healthTimeoutMs: 6e4
208
+ };
52
209
  function sandboxDriverPlacement(driver) {
53
- return DRIVER_PLACEMENT[driver];
210
+ const metadata = sandboxDriverCatalog[driver].metadata;
211
+ return {
212
+ canPauseAndRestartSameResource: metadata.persistence.resume === "same-sandbox",
213
+ canCreateFilesystemSnapshot: metadata.persistence.capture === "filesystem" || metadata.persistence.capture === "directories",
214
+ canStartFromPreparedImage: driver === "exe" || driver === "daytona" || driver === "modal" || driver === "box" || driver === "docker",
215
+ canStopExplicitly: metadata.hostStopBehavior !== "not-supported",
216
+ hasHealthProbe: true
217
+ };
54
218
  }
55
219
  function decideSandboxStart(lease, placement, now, config = DEFAULT_WORKSPACE_HOST_DECISION_CONFIG) {
56
220
  if (lease.status === "ready") {
@@ -83,20 +247,6 @@ function decideSandboxHealthFailure(lease, _placement, now, config = DEFAULT_WOR
83
247
  const backoff = Math.min(config.backoffMaxMs, 1e3 * 2 ** Math.max(0, retryCount - 1));
84
248
  return { action: "wait", until: now + backoff, reason: `health failure #${retryCount}, backoff ${backoff}ms` };
85
249
  }
86
- function decideSandboxIdle(lease, activeHoldCount, now, config = DEFAULT_WORKSPACE_HOST_DECISION_CONFIG) {
87
- if (lease.status !== "ready") {
88
- return { action: "skip", reason: `status is ${lease.status}, not ready` };
89
- }
90
- if (activeHoldCount > 0) {
91
- return { action: "skip", reason: `${activeHoldCount} active hold(s)` };
92
- }
93
- const lastActivity = lease.last_activity_at ?? lease.created_at;
94
- const idleFor = now - lastActivity;
95
- if (idleFor < config.idleMs) {
96
- return { action: "wait", until: lastActivity + config.idleMs, reason: `idle for ${idleFor}ms, need ${config.idleMs}ms` };
97
- }
98
- return { action: "stop_idle", reason: `idle for ${idleFor}ms (threshold: ${config.idleMs}ms)` };
99
- }
100
250
  function nextSandboxRetryAt(retryCount, now, config = DEFAULT_WORKSPACE_HOST_DECISION_CONFIG) {
101
251
  if (retryCount >= config.maxRetries) return null;
102
252
  return now + Math.min(config.backoffMaxMs, 1e3 * 2 ** Math.max(0, retryCount));
@@ -124,7 +274,6 @@ function decideStartMethod(lease, placement) {
124
274
  export {
125
275
  DEFAULT_WORKSPACE_HOST_DECISION_CONFIG,
126
276
  decideSandboxHealthFailure,
127
- decideSandboxIdle,
128
277
  decideSandboxStart,
129
278
  nextSandboxRetryAt,
130
279
  sandboxDriverPlacement
@@ -1,5 +1,6 @@
1
1
  import type { SandboxDriverID } from "./driver-catalog";
2
- export type SandboxLeaseRowStatus = "pending" | "acquiring" | "starting" | "ready" | "unhealthy" | "backoff" | "stopping" | "stopped" | "failed";
2
+ import type { SandboxCheckpointReference, SandboxPersistenceCapabilities, SandboxRestoreStatus } from "./index";
3
+ export type SandboxLeaseRowStatus = "pending" | "acquiring" | "starting" | "ready" | "unhealthy" | "backoff" | "stopping" | "stopped" | "destroyed" | "failed";
3
4
  export type SandboxComputeClass = "small" | "medium" | "large" | "gpu";
4
5
  export type SandboxLeaseRow = {
5
6
  workspace_id: string;
@@ -22,6 +23,10 @@ export type SandboxLeaseRow = {
22
23
  accel_base_image_id: string | null;
23
24
  accel_prepared_image_id: string | null;
24
25
  accel_snapshot_id: string | null;
26
+ labels?: Record<string, string> | null;
27
+ checkpoint: SandboxCheckpointReference | null;
28
+ persistence: SandboxPersistenceCapabilities | null;
29
+ restore: SandboxRestoreStatus | null;
25
30
  created_at: number;
26
31
  updated_at: number;
27
32
  };
@@ -1,15 +1,7 @@
1
1
  // src/runtime-version.ts
2
2
  var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
3
- var versionCache;
4
3
  function workspaceRuntimeVersion() {
5
- if (versionCache) return versionCache;
6
- const envVersion = process.env.WORKSPACE_RUNTIME_VERSION;
7
- if (envVersion) {
8
- versionCache = envVersion;
9
- return versionCache;
10
- }
11
- versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
12
- return versionCache;
4
+ return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
13
5
  }
14
6
  export {
15
7
  workspaceRuntimeVersion
@@ -1,3 +1,29 @@
1
+ // src/hosted-network-policy.ts
2
+ var SANDBOX_MODEL_PROVIDER_HOSTS = [
3
+ "api.anthropic.com",
4
+ "api.openai.com",
5
+ "generativelanguage.googleapis.com",
6
+ "openrouter.ai",
7
+ // Model catalog + engine config, fetched on runner startup.
8
+ "models.dev",
9
+ "opencode.ai"
10
+ ];
11
+ var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
12
+ "registry.npmjs.org",
13
+ "nodejs.org",
14
+ "pypi.org",
15
+ "files.pythonhosted.org",
16
+ "crates.io",
17
+ "static.crates.io",
18
+ "index.crates.io",
19
+ "proxy.golang.org",
20
+ "sum.golang.org"
21
+ ];
22
+ var SANDBOX_BASELINE_EGRESS_HOSTS = [
23
+ ...SANDBOX_MODEL_PROVIDER_HOSTS,
24
+ ...SANDBOX_PACKAGE_REGISTRY_HOSTS
25
+ ];
26
+
1
27
  // src/index.ts
2
28
  function applySandboxLeasePatch(current, patch, updatedAt) {
3
29
  return {
@@ -13,6 +39,9 @@ function applySandboxLeasePatch(current, patch, updatedAt) {
13
39
  ...patch.labels === void 0 ? {} : { labels: patch.labels },
14
40
  ...patch.nextRetryAt === void 0 ? {} : { nextRetryAt: patch.nextRetryAt ?? void 0 },
15
41
  ...patch.lastError === void 0 ? {} : { lastError: patch.lastError ?? void 0 },
42
+ ...patch.checkpoint === void 0 ? {} : { checkpoint: patch.checkpoint ?? void 0 },
43
+ ...patch.persistence === void 0 ? {} : { persistence: patch.persistence ?? void 0 },
44
+ ...patch.restore === void 0 ? {} : { restore: patch.restore ?? void 0 },
16
45
  updatedAt
17
46
  };
18
47
  }
@@ -50,7 +79,10 @@ function createMemoryLeaseStore(seed = []) {
50
79
  driverResourceId: resumable ? current.driverResourceId : void 0,
51
80
  lastHeartbeatAt: resumable ? current.lastHeartbeatAt : void 0,
52
81
  lastActivityAt: resumable ? current.lastActivityAt : void 0,
53
- labels: resumable ? current.labels : void 0
82
+ labels: resumable ? current.labels : void 0,
83
+ checkpoint: current?.checkpoint,
84
+ persistence: current?.persistence,
85
+ restore: current?.restore
54
86
  };
55
87
  leases.set(workspaceId, next);
56
88
  return { acquired: true, lease: next };
@@ -106,7 +138,10 @@ function sandboxLease(input) {
106
138
  lastError: input.lastError,
107
139
  lastHeartbeatAt: input.lastHeartbeatAt,
108
140
  lastActivityAt: input.lastActivityAt,
109
- labels: input.labels
141
+ labels: input.labels,
142
+ checkpoint: input.checkpoint,
143
+ persistence: input.persistence,
144
+ restore: input.restore
110
145
  };
111
146
  }
112
147
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claxedo/sandbox-manager",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
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",
@@ -44,6 +44,13 @@
44
44
  "import": "./dist/constants.mjs",
45
45
  "default": "./dist/constants.mjs"
46
46
  },
47
+ "./checkpoint-manager": {
48
+ "types": "./dist/checkpoint-manager.d.ts",
49
+ "bun": "./dist/checkpoint-manager.mjs",
50
+ "development": "./dist/checkpoint-manager.mjs",
51
+ "import": "./dist/checkpoint-manager.mjs",
52
+ "default": "./dist/checkpoint-manager.mjs"
53
+ },
47
54
  "./defaults": {
48
55
  "types": "./dist/defaults.d.ts",
49
56
  "bun": "./dist/defaults.mjs",
@@ -65,6 +72,13 @@
65
72
  "import": "./dist/drivers/box.mjs",
66
73
  "default": "./dist/drivers/box.mjs"
67
74
  },
75
+ "./drivers/exe": {
76
+ "types": "./dist/drivers/exe.d.ts",
77
+ "bun": "./dist/drivers/exe.mjs",
78
+ "development": "./dist/drivers/exe.mjs",
79
+ "import": "./dist/drivers/exe.mjs",
80
+ "default": "./dist/drivers/exe.mjs"
81
+ },
68
82
  "./drivers/cloudflare": {
69
83
  "types": "./dist/drivers/cloudflare.d.ts",
70
84
  "bun": "./dist/drivers/cloudflare.mjs",
@@ -174,7 +188,7 @@
174
188
  "build": "tsx scripts/build.ts",
175
189
  "prepublishOnly": "npm run build",
176
190
  "typecheck": "tsc --noEmit -p tsconfig.json",
177
- "test": "bun test src"
191
+ "test": "mkdir -p .artifacts/unit && bun test src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml"
178
192
  },
179
193
  "devDependencies": {
180
194
  "@tsconfig/node-lts": "22.0.4",