@claxedo/sandbox-manager 0.5.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/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/command.d.ts +3 -0
- package/dist/command.mjs +18 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.mjs +7 -0
- package/dist/daytona-allow-list.d.ts +5 -0
- package/dist/daytona-allow-list.mjs +9 -0
- package/dist/defaults.d.ts +3 -0
- package/dist/defaults.mjs +14 -0
- package/dist/driver-catalog.d.ts +62 -0
- package/dist/driver-catalog.mjs +206 -0
- package/dist/drivers/cloudflare-egress.d.ts +40 -0
- package/dist/drivers/cloudflare-egress.mjs +115 -0
- package/dist/drivers/cloudflare.d.ts +37 -0
- package/dist/drivers/cloudflare.mjs +151 -0
- package/dist/drivers/daytona.d.ts +94 -0
- package/dist/drivers/daytona.mjs +235 -0
- package/dist/drivers/docker.d.ts +36 -0
- package/dist/drivers/docker.mjs +329 -0
- package/dist/drivers/fetch-bridge.d.ts +10 -0
- package/dist/drivers/fetch-bridge.mjs +104 -0
- package/dist/drivers/modal.d.ts +68 -0
- package/dist/drivers/modal.mjs +227 -0
- package/dist/drivers/vercel.d.ts +109 -0
- package/dist/drivers/vercel.mjs +297 -0
- package/dist/image-name.d.ts +7 -0
- package/dist/image.d.ts +8 -0
- package/dist/image.mjs +124 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.mjs +330 -0
- package/dist/lease-policy.d.ts +49 -0
- package/dist/lease-policy.mjs +126 -0
- package/dist/lease-types.d.ts +89 -0
- package/dist/lease-types.mjs +1 -0
- package/dist/runtime-env.d.ts +24 -0
- package/dist/runtime-env.mjs +35 -0
- package/dist/runtime-version.d.ts +1 -0
- package/dist/runtime-version.mjs +24 -0
- package/dist/service-url-exposure.d.ts +10 -0
- package/dist/service-url-exposure.mjs +46 -0
- package/dist/stores/memory.d.ts +6 -0
- package/dist/stores/memory.mjs +117 -0
- package/package.json +178 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
function applySandboxLeasePatch(current, patch, updatedAt) {
|
|
8
|
+
return {
|
|
9
|
+
...current,
|
|
10
|
+
...patch.status === void 0 ? {} : { status: patch.status },
|
|
11
|
+
...patch.sandboxId === void 0 ? {} : { sandboxId: patch.sandboxId },
|
|
12
|
+
...patch.url === void 0 ? {} : { url: patch.url },
|
|
13
|
+
...patch.hostId === void 0 ? {} : { hostId: patch.hostId },
|
|
14
|
+
...patch.driverResourceId === void 0 ? {} : { driverResourceId: patch.driverResourceId },
|
|
15
|
+
...patch.retryCount === void 0 ? {} : { retryCount: patch.retryCount },
|
|
16
|
+
...patch.lastHeartbeatAt === void 0 ? {} : { lastHeartbeatAt: patch.lastHeartbeatAt },
|
|
17
|
+
...patch.lastActivityAt === void 0 ? {} : { lastActivityAt: patch.lastActivityAt },
|
|
18
|
+
...patch.labels === void 0 ? {} : { labels: patch.labels },
|
|
19
|
+
...patch.nextRetryAt === void 0 ? {} : { nextRetryAt: patch.nextRetryAt ?? void 0 },
|
|
20
|
+
...patch.lastError === void 0 ? {} : { lastError: patch.lastError ?? void 0 },
|
|
21
|
+
updatedAt
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
var DEFAULT_APP_LABEL = "claxedo";
|
|
25
|
+
function sandboxId(driver, workspaceId) {
|
|
26
|
+
return `${driver}-${workspaceId}`;
|
|
27
|
+
}
|
|
28
|
+
function ensureHostInput(input) {
|
|
29
|
+
const labels = {
|
|
30
|
+
app: input.appLabel,
|
|
31
|
+
workspaceId: input.workspaceId,
|
|
32
|
+
epoch: String(input.lease.epoch),
|
|
33
|
+
homeRegion: input.homeRegion,
|
|
34
|
+
...input.managerInput?.labels
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
workspaceId: input.workspaceId,
|
|
38
|
+
hostId: input.managerInput?.hostId ?? input.lease.hostId ?? sandboxId(input.driver.id, input.workspaceId),
|
|
39
|
+
homeRegion: input.homeRegion,
|
|
40
|
+
epoch: input.lease.epoch,
|
|
41
|
+
labels,
|
|
42
|
+
bootSource: input.managerInput?.bootSource ?? { kind: "default" },
|
|
43
|
+
workspaceRoot: input.managerInput?.workspaceRoot ?? "/workspace",
|
|
44
|
+
runtimeCwd: input.managerInput?.runtimeCwd,
|
|
45
|
+
workspaceRuntimePort: input.managerInput?.workspaceRuntimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT,
|
|
46
|
+
env: input.managerInput?.env ?? {},
|
|
47
|
+
// Brokered secrets ride their own channel — NEVER merged into labels or env.
|
|
48
|
+
...input.managerInput?.secrets?.length ? { secrets: input.managerInput.secrets } : {},
|
|
49
|
+
source: input.managerInput?.source,
|
|
50
|
+
exposure: input.managerInput?.exposure ?? (input.driver.metadata.targetAccess === "loopback" ? { kind: "loopback" } : { kind: "relay" }),
|
|
51
|
+
net: input.managerInput?.net,
|
|
52
|
+
snapshot: input.managerInput?.snapshot
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function createSandboxManager(options) {
|
|
56
|
+
const staleAfterMs = options.staleAfterMs ?? 6e4;
|
|
57
|
+
const retryAfterMs = options.retryAfterMs ?? 2e3;
|
|
58
|
+
const now = options.now ?? Date.now;
|
|
59
|
+
const retryDelayMs = options.retryDelayMs ?? ((retryCount) => Math.min(6e4, 1e3 * 2 ** Math.max(0, retryCount - 1)));
|
|
60
|
+
const maxRetryCount = options.maxRetryCount ?? Number.POSITIVE_INFINITY;
|
|
61
|
+
const retryCapCooldownMs = options.retryCapCooldownMs ?? 10 * 6e4;
|
|
62
|
+
async function leaseTarget(lease) {
|
|
63
|
+
if (lease.status !== "ready" || !lease.sandboxId || !lease.url || !lease.hostId) {
|
|
64
|
+
return { status: "unavailable", reason: "runtime_lease_not_ready" };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
status: "ready",
|
|
68
|
+
sandboxId: lease.sandboxId,
|
|
69
|
+
url: lease.url,
|
|
70
|
+
workspaceId: lease.workspaceId,
|
|
71
|
+
hostId: lease.hostId,
|
|
72
|
+
driverResourceId: lease.driverResourceId,
|
|
73
|
+
labels: lease.labels,
|
|
74
|
+
driver: {
|
|
75
|
+
id: lease.driver,
|
|
76
|
+
resourceId: lease.driverResourceId ?? lease.sandboxId
|
|
77
|
+
},
|
|
78
|
+
epoch: lease.epoch,
|
|
79
|
+
homeRegion: lease.homeRegion
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function provision(workspaceId, lease, homeRegion, managerInput) {
|
|
83
|
+
try {
|
|
84
|
+
const ensure = ensureHostInput({
|
|
85
|
+
driver: options.driver,
|
|
86
|
+
workspaceId,
|
|
87
|
+
homeRegion,
|
|
88
|
+
lease,
|
|
89
|
+
managerInput,
|
|
90
|
+
appLabel: options.appLabel ?? DEFAULT_APP_LABEL
|
|
91
|
+
});
|
|
92
|
+
if (ensure.secrets?.length && options.driver.metadata.secretBrokering === "none") {
|
|
93
|
+
return {
|
|
94
|
+
status: "unavailable",
|
|
95
|
+
error: "secret_brokering_unsupported",
|
|
96
|
+
epoch: lease.epoch,
|
|
97
|
+
homeRegion
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const target = lease.sandboxId && ensure.bootSource?.kind === "default" && options.driver.resumeHost ? await options.driver.resumeHost({ lease, ensure }) : await options.driver.ensureHost(ensure);
|
|
101
|
+
if ("provisioning" in target) {
|
|
102
|
+
await options.leaseStore.update(workspaceId, lease.epoch, {
|
|
103
|
+
status: "acquiring",
|
|
104
|
+
nextRetryAt: now() + target.retryAfterMs
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
status: "provisioning",
|
|
108
|
+
retryAfterMs: target.retryAfterMs,
|
|
109
|
+
epoch: lease.epoch,
|
|
110
|
+
homeRegion
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const updated = await options.leaseStore.update(workspaceId, lease.epoch, {
|
|
114
|
+
status: "ready",
|
|
115
|
+
sandboxId: target.sandboxId,
|
|
116
|
+
url: target.url,
|
|
117
|
+
hostId: target.hostId,
|
|
118
|
+
driverResourceId: target.driverResourceId,
|
|
119
|
+
retryCount: 0,
|
|
120
|
+
nextRetryAt: null,
|
|
121
|
+
lastError: null,
|
|
122
|
+
labels: target.labels ?? ensure.labels
|
|
123
|
+
});
|
|
124
|
+
const resolved = await leaseTarget(updated ?? lease);
|
|
125
|
+
if (resolved.status === "ready") return resolved;
|
|
126
|
+
return {
|
|
127
|
+
status: "unavailable",
|
|
128
|
+
error: resolved.reason,
|
|
129
|
+
epoch: lease.epoch,
|
|
130
|
+
homeRegion
|
|
131
|
+
};
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
134
|
+
if (lease.status === "ready") {
|
|
135
|
+
const updated = await options.leaseStore.update(workspaceId, lease.epoch, { lastError: error });
|
|
136
|
+
const resolved = await leaseTarget(updated ?? lease);
|
|
137
|
+
if (resolved.status === "ready") return resolved;
|
|
138
|
+
}
|
|
139
|
+
const nextRetryCount = lease.retryCount + 1;
|
|
140
|
+
const retryCapped = nextRetryCount >= maxRetryCount;
|
|
141
|
+
const failed = await options.leaseStore.recordFailure(
|
|
142
|
+
workspaceId,
|
|
143
|
+
lease.epoch,
|
|
144
|
+
error,
|
|
145
|
+
now() + (retryCapped ? retryCapCooldownMs : retryDelayMs(nextRetryCount))
|
|
146
|
+
);
|
|
147
|
+
return {
|
|
148
|
+
status: "unavailable",
|
|
149
|
+
retryAfterMs: failed?.nextRetryAt ? Math.max(0, failed.nextRetryAt - now()) : retryAfterMs,
|
|
150
|
+
error,
|
|
151
|
+
epoch: lease.epoch,
|
|
152
|
+
homeRegion
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
async ensure(workspaceId, input) {
|
|
158
|
+
const existing = await options.leaseStore.get(workspaceId);
|
|
159
|
+
if (existing?.nextRetryAt && existing.nextRetryAt > now()) {
|
|
160
|
+
if (existing.status === "acquiring") {
|
|
161
|
+
return {
|
|
162
|
+
status: "provisioning",
|
|
163
|
+
retryAfterMs: existing.nextRetryAt - now(),
|
|
164
|
+
epoch: existing.epoch,
|
|
165
|
+
homeRegion: existing.homeRegion
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
status: "unavailable",
|
|
170
|
+
retryAfterMs: existing.nextRetryAt - now(),
|
|
171
|
+
error: existing.lastError,
|
|
172
|
+
epoch: existing.epoch,
|
|
173
|
+
homeRegion: existing.homeRegion
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (existing?.status === "unavailable" && existing.retryCount >= maxRetryCount && existing.nextRetryAt === void 0) {
|
|
177
|
+
return {
|
|
178
|
+
status: "unavailable",
|
|
179
|
+
error: existing.lastError ?? "runtime_retry_cap_exceeded",
|
|
180
|
+
epoch: existing.epoch,
|
|
181
|
+
homeRegion: existing.homeRegion
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (existing?.status === "ready") {
|
|
185
|
+
return provision(workspaceId, existing, existing.homeRegion, input);
|
|
186
|
+
}
|
|
187
|
+
if (existing?.status === "acquiring" && existing.nextRetryAt !== void 0 && now() - existing.updatedAt < staleAfterMs) {
|
|
188
|
+
return provision(workspaceId, existing, existing.homeRegion, input);
|
|
189
|
+
}
|
|
190
|
+
const acquired = await options.leaseStore.acquire(workspaceId, {
|
|
191
|
+
homeRegion: input.homeRegion,
|
|
192
|
+
driver: options.driver.id,
|
|
193
|
+
staleAfterMs,
|
|
194
|
+
now: now()
|
|
195
|
+
});
|
|
196
|
+
if (!acquired.acquired) {
|
|
197
|
+
return {
|
|
198
|
+
status: "provisioning",
|
|
199
|
+
retryAfterMs: acquired.retryAfterMs,
|
|
200
|
+
epoch: acquired.lease.epoch,
|
|
201
|
+
homeRegion: acquired.lease.homeRegion
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return provision(workspaceId, acquired.lease, input.homeRegion, input);
|
|
205
|
+
},
|
|
206
|
+
async register(workspaceId, input) {
|
|
207
|
+
return await updateFromRuntimeSnapshot(workspaceId, input);
|
|
208
|
+
},
|
|
209
|
+
async heartbeat(workspaceId, input) {
|
|
210
|
+
return await updateFromRuntimeSnapshot(workspaceId, input);
|
|
211
|
+
},
|
|
212
|
+
async target(workspaceId) {
|
|
213
|
+
const lease = await options.leaseStore.get(workspaceId);
|
|
214
|
+
if (!lease) return { status: "unavailable", reason: "runtime_lease_missing" };
|
|
215
|
+
return leaseTarget(lease);
|
|
216
|
+
},
|
|
217
|
+
async touch(workspaceId) {
|
|
218
|
+
const target = await this.target(workspaceId);
|
|
219
|
+
if (target.status !== "ready") return { touched: false, status: "missing" };
|
|
220
|
+
await options.driver.touch?.(target);
|
|
221
|
+
return { touched: true, status: "ready" };
|
|
222
|
+
},
|
|
223
|
+
async snapshot(workspaceId) {
|
|
224
|
+
const target = await this.target(workspaceId);
|
|
225
|
+
if (target.status !== "ready") return { ok: false, reason: target.reason };
|
|
226
|
+
if (!options.driver.snapshot) return { ok: false, reason: "snapshot_unsupported" };
|
|
227
|
+
return { ok: true, ...await options.driver.snapshot(target) };
|
|
228
|
+
},
|
|
229
|
+
async stop(workspaceId) {
|
|
230
|
+
const target = await this.target(workspaceId);
|
|
231
|
+
if (target.status !== "ready") return { ok: false, reason: target.reason };
|
|
232
|
+
if (options.driver.metadata.hostStopBehavior !== "not-supported") {
|
|
233
|
+
await (options.driver.suspend ?? options.driver.stop)?.(target);
|
|
234
|
+
} else {
|
|
235
|
+
await options.driver.stop?.(target);
|
|
236
|
+
}
|
|
237
|
+
await options.leaseStore.update(workspaceId, target.epoch, { status: "stopped" });
|
|
238
|
+
return { ok: true, status: "stopped" };
|
|
239
|
+
},
|
|
240
|
+
async destroy(workspaceId) {
|
|
241
|
+
const target = await this.target(workspaceId);
|
|
242
|
+
if (target.status !== "ready") return { ok: false, reason: target.reason };
|
|
243
|
+
await options.driver.destroy?.(target);
|
|
244
|
+
await options.leaseStore.update(workspaceId, target.epoch, { status: "destroyed" });
|
|
245
|
+
return { ok: true, status: "destroyed" };
|
|
246
|
+
},
|
|
247
|
+
async release(workspaceId) {
|
|
248
|
+
const lease = await options.leaseStore.get(workspaceId);
|
|
249
|
+
if (!lease) return { released: false };
|
|
250
|
+
await options.leaseStore.release(workspaceId);
|
|
251
|
+
return { released: true };
|
|
252
|
+
},
|
|
253
|
+
async garbageCollect() {
|
|
254
|
+
if (!options.driver.list) {
|
|
255
|
+
return {
|
|
256
|
+
destroyed: [],
|
|
257
|
+
kept: [],
|
|
258
|
+
skipped: [],
|
|
259
|
+
failed: []
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const leases = new Map((await options.leaseStore.list()).map((lease) => [lease.workspaceId, lease]));
|
|
263
|
+
const result = {
|
|
264
|
+
destroyed: [],
|
|
265
|
+
kept: [],
|
|
266
|
+
skipped: [],
|
|
267
|
+
failed: []
|
|
268
|
+
};
|
|
269
|
+
const appLabel = options.appLabel ?? DEFAULT_APP_LABEL;
|
|
270
|
+
for (const target of await options.driver.list()) {
|
|
271
|
+
if (target.labels?.app !== appLabel) {
|
|
272
|
+
result.skipped.push({ target, reason: "unmanaged_app_label" });
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const workspaceId = target.labels.workspaceId;
|
|
276
|
+
const epoch = target.labels.epoch;
|
|
277
|
+
if (!workspaceId || !epoch) {
|
|
278
|
+
result.skipped.push({ target, reason: "missing_runtime_labels" });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const lease = leases.get(workspaceId);
|
|
282
|
+
if (lease?.status === "ready" && String(lease.epoch) === epoch && lease.hostId === target.hostId && lease.sandboxId === target.sandboxId) {
|
|
283
|
+
result.kept.push(target);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (lease?.status === "acquiring" && String(lease.epoch) === epoch) {
|
|
287
|
+
result.kept.push(target);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (!options.driver.destroy) {
|
|
291
|
+
result.skipped.push({ target, reason: "destroy_unsupported" });
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
await options.driver.destroy(target);
|
|
296
|
+
result.destroyed.push(target);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
result.failed.push({ target, error: err instanceof Error ? err.message : String(err) });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
},
|
|
303
|
+
list: () => options.leaseStore.list()
|
|
304
|
+
};
|
|
305
|
+
async function updateFromRuntimeSnapshot(workspaceId, input) {
|
|
306
|
+
const current = await options.leaseStore.get(workspaceId);
|
|
307
|
+
if (!current) return { ok: false, reason: "runtime_lease_missing" };
|
|
308
|
+
const expectedEpoch = input.epoch ?? current.epoch;
|
|
309
|
+
if (current.epoch !== expectedEpoch) return { ok: false, reason: "runtime_lease_epoch_mismatch" };
|
|
310
|
+
const timestamp = input.now ?? now();
|
|
311
|
+
const status = input.status === "unhealthy" || input.ok === false ? "unavailable" : input.ok ? "ready" : current.status;
|
|
312
|
+
const updated = await options.leaseStore.update(workspaceId, expectedEpoch, {
|
|
313
|
+
status,
|
|
314
|
+
...input.sandboxId ? { sandboxId: input.sandboxId } : {},
|
|
315
|
+
...input.url ? { url: input.url } : {},
|
|
316
|
+
...input.hostId ? { hostId: input.hostId } : input.sandboxId ? { hostId: input.sandboxId } : {},
|
|
317
|
+
...input.driverResourceId ? { driverResourceId: input.driverResourceId } : input.sandboxId ? { driverResourceId: input.sandboxId } : {},
|
|
318
|
+
lastHeartbeatAt: timestamp,
|
|
319
|
+
...input.active ? { lastActivityAt: timestamp } : {},
|
|
320
|
+
...input.ok ? { lastError: null } : {}
|
|
321
|
+
});
|
|
322
|
+
if (!updated) return { ok: false, reason: "runtime_lease_epoch_mismatch" };
|
|
323
|
+
return { ok: true, status: updated.status };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
export {
|
|
327
|
+
DEFAULT_WORKSPACE_RUNTIME_PORT,
|
|
328
|
+
applySandboxLeasePatch,
|
|
329
|
+
createSandboxManager
|
|
330
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SandboxLeaseRow } from "./lease-types";
|
|
2
|
+
import type { SandboxDriverID } from "./driver-catalog";
|
|
3
|
+
export type SandboxDecision = {
|
|
4
|
+
action: "skip";
|
|
5
|
+
reason: string;
|
|
6
|
+
} | {
|
|
7
|
+
action: "wait";
|
|
8
|
+
until: number;
|
|
9
|
+
reason: string;
|
|
10
|
+
} | {
|
|
11
|
+
action: "resume";
|
|
12
|
+
reason: string;
|
|
13
|
+
} | {
|
|
14
|
+
action: "restore_snapshot";
|
|
15
|
+
snapshot_id: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
} | {
|
|
18
|
+
action: "start_prepared";
|
|
19
|
+
image_id: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
} | {
|
|
22
|
+
action: "start_fresh";
|
|
23
|
+
reason: string;
|
|
24
|
+
} | {
|
|
25
|
+
action: "stop_idle";
|
|
26
|
+
reason: string;
|
|
27
|
+
} | {
|
|
28
|
+
action: "mark_failed";
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
export type SandboxDecisionConfig = {
|
|
32
|
+
maxRetries: number;
|
|
33
|
+
idleMs: number;
|
|
34
|
+
backoffMaxMs: number;
|
|
35
|
+
healthTimeoutMs: number;
|
|
36
|
+
};
|
|
37
|
+
export type SandboxDriverPlacement = {
|
|
38
|
+
canPauseAndRestartSameResource: boolean;
|
|
39
|
+
canCreateFilesystemSnapshot: boolean;
|
|
40
|
+
canStartFromPreparedImage: boolean;
|
|
41
|
+
canStopExplicitly: boolean;
|
|
42
|
+
hasHealthProbe: boolean;
|
|
43
|
+
};
|
|
44
|
+
export declare const DEFAULT_WORKSPACE_HOST_DECISION_CONFIG: SandboxDecisionConfig;
|
|
45
|
+
export declare function sandboxDriverPlacement(driver: SandboxDriverID): SandboxDriverPlacement;
|
|
46
|
+
export declare function decideSandboxStart(lease: SandboxLeaseRow, placement: SandboxDriverPlacement, now: number, config?: SandboxDecisionConfig): SandboxDecision;
|
|
47
|
+
export declare function decideSandboxHealthFailure(lease: SandboxLeaseRow, _placement: SandboxDriverPlacement, now: number, config?: SandboxDecisionConfig): SandboxDecision;
|
|
48
|
+
export declare function decideSandboxIdle(lease: SandboxLeaseRow, activeHoldCount: number, now: number, config?: SandboxDecisionConfig): SandboxDecision;
|
|
49
|
+
export declare function nextSandboxRetryAt(retryCount: number, now: number, config?: SandboxDecisionConfig): number | null;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/lease-policy.ts
|
|
4
|
+
var DEFAULT_WORKSPACE_HOST_DECISION_CONFIG = {
|
|
5
|
+
maxRetries: 8,
|
|
6
|
+
idleMs: 10 * 60 * 1e3,
|
|
7
|
+
backoffMaxMs: 3e4,
|
|
8
|
+
healthTimeoutMs: 6e4
|
|
9
|
+
};
|
|
10
|
+
var DRIVER_PLACEMENT = {
|
|
11
|
+
daytona: {
|
|
12
|
+
canPauseAndRestartSameResource: true,
|
|
13
|
+
canCreateFilesystemSnapshot: false,
|
|
14
|
+
canStartFromPreparedImage: true,
|
|
15
|
+
canStopExplicitly: true,
|
|
16
|
+
hasHealthProbe: true
|
|
17
|
+
},
|
|
18
|
+
modal: {
|
|
19
|
+
canPauseAndRestartSameResource: false,
|
|
20
|
+
canCreateFilesystemSnapshot: true,
|
|
21
|
+
canStartFromPreparedImage: true,
|
|
22
|
+
canStopExplicitly: true,
|
|
23
|
+
hasHealthProbe: true
|
|
24
|
+
},
|
|
25
|
+
vercel: {
|
|
26
|
+
canPauseAndRestartSameResource: false,
|
|
27
|
+
canCreateFilesystemSnapshot: true,
|
|
28
|
+
canStartFromPreparedImage: false,
|
|
29
|
+
canStopExplicitly: true,
|
|
30
|
+
hasHealthProbe: true
|
|
31
|
+
},
|
|
32
|
+
cloudflare: {
|
|
33
|
+
canPauseAndRestartSameResource: false,
|
|
34
|
+
canCreateFilesystemSnapshot: true,
|
|
35
|
+
canStartFromPreparedImage: false,
|
|
36
|
+
canStopExplicitly: false,
|
|
37
|
+
hasHealthProbe: true
|
|
38
|
+
},
|
|
39
|
+
docker: {
|
|
40
|
+
canPauseAndRestartSameResource: true,
|
|
41
|
+
canCreateFilesystemSnapshot: false,
|
|
42
|
+
canStartFromPreparedImage: true,
|
|
43
|
+
canStopExplicitly: true,
|
|
44
|
+
hasHealthProbe: true
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function sandboxDriverPlacement(driver) {
|
|
48
|
+
return DRIVER_PLACEMENT[driver];
|
|
49
|
+
}
|
|
50
|
+
function decideSandboxStart(lease, placement, now, config = DEFAULT_WORKSPACE_HOST_DECISION_CONFIG) {
|
|
51
|
+
if (lease.status === "ready") {
|
|
52
|
+
return { action: "skip", reason: "already ready" };
|
|
53
|
+
}
|
|
54
|
+
if (lease.status === "failed") {
|
|
55
|
+
return { action: "mark_failed", reason: `terminal failure: ${lease.last_error ?? "unknown"}` };
|
|
56
|
+
}
|
|
57
|
+
if (lease.status === "backoff") {
|
|
58
|
+
if (lease.retry_count >= config.maxRetries) {
|
|
59
|
+
return { action: "mark_failed", reason: `exceeded max retries (${config.maxRetries})` };
|
|
60
|
+
}
|
|
61
|
+
if (lease.next_retry_at && now < lease.next_retry_at) {
|
|
62
|
+
return { action: "wait", until: lease.next_retry_at, reason: "backoff timer not elapsed" };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (lease.status === "acquiring" || lease.status === "starting") {
|
|
66
|
+
return { action: "skip", reason: `already ${lease.status}` };
|
|
67
|
+
}
|
|
68
|
+
return decideStartMethod(lease, placement);
|
|
69
|
+
}
|
|
70
|
+
function decideSandboxHealthFailure(lease, _placement, now, config = DEFAULT_WORKSPACE_HOST_DECISION_CONFIG) {
|
|
71
|
+
if (lease.status !== "ready" && lease.status !== "unhealthy") {
|
|
72
|
+
return { action: "skip", reason: `status is ${lease.status}, health check not applicable` };
|
|
73
|
+
}
|
|
74
|
+
const retryCount = lease.retry_count + 1;
|
|
75
|
+
if (retryCount > config.maxRetries) {
|
|
76
|
+
return { action: "mark_failed", reason: `health failures exceeded max retries (${config.maxRetries})` };
|
|
77
|
+
}
|
|
78
|
+
const backoff = Math.min(config.backoffMaxMs, 1e3 * 2 ** Math.max(0, retryCount - 1));
|
|
79
|
+
return { action: "wait", until: now + backoff, reason: `health failure #${retryCount}, backoff ${backoff}ms` };
|
|
80
|
+
}
|
|
81
|
+
function decideSandboxIdle(lease, activeHoldCount, now, config = DEFAULT_WORKSPACE_HOST_DECISION_CONFIG) {
|
|
82
|
+
if (lease.status !== "ready") {
|
|
83
|
+
return { action: "skip", reason: `status is ${lease.status}, not ready` };
|
|
84
|
+
}
|
|
85
|
+
if (activeHoldCount > 0) {
|
|
86
|
+
return { action: "skip", reason: `${activeHoldCount} active hold(s)` };
|
|
87
|
+
}
|
|
88
|
+
const lastActivity = lease.last_activity_at ?? lease.created_at;
|
|
89
|
+
const idleFor = now - lastActivity;
|
|
90
|
+
if (idleFor < config.idleMs) {
|
|
91
|
+
return { action: "wait", until: lastActivity + config.idleMs, reason: `idle for ${idleFor}ms, need ${config.idleMs}ms` };
|
|
92
|
+
}
|
|
93
|
+
return { action: "stop_idle", reason: `idle for ${idleFor}ms (threshold: ${config.idleMs}ms)` };
|
|
94
|
+
}
|
|
95
|
+
function nextSandboxRetryAt(retryCount, now, config = DEFAULT_WORKSPACE_HOST_DECISION_CONFIG) {
|
|
96
|
+
if (retryCount >= config.maxRetries) return null;
|
|
97
|
+
return now + Math.min(config.backoffMaxMs, 1e3 * 2 ** Math.max(0, retryCount));
|
|
98
|
+
}
|
|
99
|
+
function decideStartMethod(lease, placement) {
|
|
100
|
+
if (lease.sandbox_id && placement.canPauseAndRestartSameResource) {
|
|
101
|
+
return { action: "resume", reason: `resume sandbox ${lease.sandbox_id}` };
|
|
102
|
+
}
|
|
103
|
+
if (lease.accel_snapshot_id && placement.canCreateFilesystemSnapshot) {
|
|
104
|
+
return {
|
|
105
|
+
action: "restore_snapshot",
|
|
106
|
+
snapshot_id: lease.accel_snapshot_id,
|
|
107
|
+
reason: `restore snapshot ${lease.accel_snapshot_id}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (lease.accel_prepared_image_id && placement.canStartFromPreparedImage) {
|
|
111
|
+
return {
|
|
112
|
+
action: "start_prepared",
|
|
113
|
+
image_id: lease.accel_prepared_image_id,
|
|
114
|
+
reason: `start from prepared image ${lease.accel_prepared_image_id}`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return { action: "start_fresh", reason: "no acceleration available, cold start" };
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
DEFAULT_WORKSPACE_HOST_DECISION_CONFIG,
|
|
121
|
+
decideSandboxHealthFailure,
|
|
122
|
+
decideSandboxIdle,
|
|
123
|
+
decideSandboxStart,
|
|
124
|
+
nextSandboxRetryAt,
|
|
125
|
+
sandboxDriverPlacement
|
|
126
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { SandboxDriverID } from "./driver-catalog";
|
|
2
|
+
export type SandboxLeaseRowStatus = "pending" | "acquiring" | "starting" | "ready" | "unhealthy" | "backoff" | "stopping" | "stopped" | "failed";
|
|
3
|
+
export type SandboxComputeClass = "small" | "medium" | "large" | "gpu";
|
|
4
|
+
export type SandboxLeaseRow = {
|
|
5
|
+
workspace_id: string;
|
|
6
|
+
lease_id: string;
|
|
7
|
+
home_region?: string;
|
|
8
|
+
epoch: number;
|
|
9
|
+
status: SandboxLeaseRowStatus;
|
|
10
|
+
driver: SandboxDriverID;
|
|
11
|
+
driver_resource_id: string | null;
|
|
12
|
+
driver_snapshot_id: string | null;
|
|
13
|
+
sandbox_id: string | null;
|
|
14
|
+
url: string | null;
|
|
15
|
+
retry_count: number;
|
|
16
|
+
next_retry_at: number | null;
|
|
17
|
+
last_heartbeat_at: number | null;
|
|
18
|
+
last_activity_at: number | null;
|
|
19
|
+
last_health_failure_at: number | null;
|
|
20
|
+
last_error: string | null;
|
|
21
|
+
compute_class: SandboxComputeClass | null;
|
|
22
|
+
accel_base_image_id: string | null;
|
|
23
|
+
accel_prepared_image_id: string | null;
|
|
24
|
+
accel_snapshot_id: string | null;
|
|
25
|
+
created_at: number;
|
|
26
|
+
updated_at: number;
|
|
27
|
+
};
|
|
28
|
+
export type SandboxHoldRowOwnerType = "session" | "stream" | "pty" | "process" | "system";
|
|
29
|
+
export type SandboxHoldRow = {
|
|
30
|
+
hold_id: string;
|
|
31
|
+
workspace_id: string;
|
|
32
|
+
owner_type: SandboxHoldRowOwnerType;
|
|
33
|
+
owner_id: string;
|
|
34
|
+
reason: string;
|
|
35
|
+
expires_at: number | null;
|
|
36
|
+
updated_at: number;
|
|
37
|
+
};
|
|
38
|
+
export type SandboxEpochEnvelope = {
|
|
39
|
+
workspace_id: string;
|
|
40
|
+
lease_id: string;
|
|
41
|
+
epoch: number;
|
|
42
|
+
sandbox_id: string;
|
|
43
|
+
};
|
|
44
|
+
export type SandboxRowAcquireResult = {
|
|
45
|
+
lease: SandboxLeaseRow;
|
|
46
|
+
bumped_epoch: boolean;
|
|
47
|
+
};
|
|
48
|
+
export type SandboxRowHeartbeatResult = {
|
|
49
|
+
accepted: true;
|
|
50
|
+
} | {
|
|
51
|
+
accepted: false;
|
|
52
|
+
reason: "stale_epoch" | "unknown_workspace" | "wrong_lease";
|
|
53
|
+
};
|
|
54
|
+
export type SandboxRowReconnectResult = {
|
|
55
|
+
accepted: true;
|
|
56
|
+
lease: SandboxLeaseRow;
|
|
57
|
+
} | {
|
|
58
|
+
accepted: false;
|
|
59
|
+
reason: "stale_epoch" | "unknown_workspace" | "wrong_lease" | "wrong_sandbox";
|
|
60
|
+
};
|
|
61
|
+
export type SandboxRowEvent = {
|
|
62
|
+
type: "lease_acquired";
|
|
63
|
+
lease: SandboxLeaseRow;
|
|
64
|
+
} | {
|
|
65
|
+
type: "lease_updated";
|
|
66
|
+
lease: SandboxLeaseRow;
|
|
67
|
+
changed: (keyof SandboxLeaseRow)[];
|
|
68
|
+
} | {
|
|
69
|
+
type: "lease_released";
|
|
70
|
+
workspace_id: string;
|
|
71
|
+
reason: string;
|
|
72
|
+
} | {
|
|
73
|
+
type: "hold_acquired";
|
|
74
|
+
hold: SandboxHoldRow;
|
|
75
|
+
} | {
|
|
76
|
+
type: "hold_released";
|
|
77
|
+
hold_id: string;
|
|
78
|
+
workspace_id: string;
|
|
79
|
+
} | {
|
|
80
|
+
type: "epoch_bumped";
|
|
81
|
+
workspace_id: string;
|
|
82
|
+
old_epoch: number;
|
|
83
|
+
new_epoch: number;
|
|
84
|
+
} | {
|
|
85
|
+
type: "heartbeat_rejected";
|
|
86
|
+
workspace_id: string;
|
|
87
|
+
envelope: SandboxEpochEnvelope;
|
|
88
|
+
reason: string;
|
|
89
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SandboxDriverID } from "./driver-catalog";
|
|
2
|
+
export declare function workspaceRuntimeTargetEnv(input: {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
hostId?: string;
|
|
5
|
+
directory: string;
|
|
6
|
+
port: number;
|
|
7
|
+
host?: string;
|
|
8
|
+
}): Record<string, string>;
|
|
9
|
+
export declare function workspaceRuntimeSourceEnv(input: {
|
|
10
|
+
source?: {
|
|
11
|
+
kind: "git";
|
|
12
|
+
repoUrl: string;
|
|
13
|
+
branch?: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "empty";
|
|
16
|
+
};
|
|
17
|
+
}): Record<string, string>;
|
|
18
|
+
export declare function workspaceRuntimeServiceExposureEnv(input: {
|
|
19
|
+
driver: SandboxDriverID;
|
|
20
|
+
source: "driver-service-url";
|
|
21
|
+
access: "private" | "public" | "driver-authenticated" | "unknown";
|
|
22
|
+
fallbackAccess?: "private" | "public" | "driver-authenticated" | "unknown";
|
|
23
|
+
note?: string;
|
|
24
|
+
}): Record<string, string>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/runtime-env.ts
|
|
4
|
+
function workspaceRuntimeTargetEnv(input) {
|
|
5
|
+
return {
|
|
6
|
+
WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
|
|
7
|
+
...input.hostId ? { WORKSPACE_RUNTIME_HOST_ID: input.hostId } : {},
|
|
8
|
+
WORKSPACE_RUNTIME_DIRECTORY: input.directory,
|
|
9
|
+
WORKSPACE_RUNTIME_PORT: String(input.port),
|
|
10
|
+
...input.host ? { WORKSPACE_RUNTIME_HOST: input.host } : {}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function workspaceRuntimeSourceEnv(input) {
|
|
14
|
+
if (!input.source || input.source.kind === "empty") return { WORKSPACE_RUNTIME_SOURCE_KIND: "empty" };
|
|
15
|
+
return {
|
|
16
|
+
WORKSPACE_RUNTIME_SOURCE_KIND: "git",
|
|
17
|
+
WORKSPACE_RUNTIME_GIT_REPO_URL: input.source.repoUrl,
|
|
18
|
+
...input.source.branch ? { WORKSPACE_RUNTIME_GIT_BRANCH: input.source.branch } : {}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function workspaceRuntimeServiceExposureEnv(input) {
|
|
22
|
+
return {
|
|
23
|
+
...input.driver === "docker" ? { WORKSPACE_RUNTIME_HOST: "0.0.0.0" } : {},
|
|
24
|
+
WORKSPACE_RUNTIME_SERVICE_EXPOSURE_SOURCE: input.source,
|
|
25
|
+
WORKSPACE_RUNTIME_SERVICE_EXPOSURE_ACCESS: input.access,
|
|
26
|
+
WORKSPACE_RUNTIME_SERVICE_EXPOSURE_DRIVER: input.driver,
|
|
27
|
+
...input.fallbackAccess ? { WORKSPACE_RUNTIME_SERVICE_EXPOSURE_FALLBACK_ACCESS: input.fallbackAccess } : {},
|
|
28
|
+
...input.note ? { WORKSPACE_RUNTIME_SERVICE_EXPOSURE_NOTE: input.note } : {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
workspaceRuntimeServiceExposureEnv,
|
|
33
|
+
workspaceRuntimeSourceEnv,
|
|
34
|
+
workspaceRuntimeTargetEnv
|
|
35
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function workspaceRuntimeVersion(): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'module';var require = __createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/runtime-version.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
var require2 = createRequire(import.meta.url);
|
|
6
|
+
var versionCache;
|
|
7
|
+
function workspaceRuntimeVersion() {
|
|
8
|
+
if (versionCache) return versionCache;
|
|
9
|
+
const envVersion = process.env.WORKSPACE_RUNTIME_VERSION;
|
|
10
|
+
if (envVersion) {
|
|
11
|
+
versionCache = envVersion;
|
|
12
|
+
return versionCache;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
versionCache = require2("@claxedo/workspace-runtime/package.json").version;
|
|
16
|
+
return versionCache;
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
versionCache = "0.0.0";
|
|
20
|
+
return versionCache;
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
workspaceRuntimeVersion
|
|
24
|
+
};
|