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