@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.
package/dist/index.mjs CHANGED
@@ -1,7 +1,211 @@
1
1
  // src/constants.ts
2
2
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
3
3
 
4
+ // src/checkpoint-manager.ts
5
+ async function captureSandboxCheckpoint(input) {
6
+ const lease = await requireLease(input.leaseStore, input.workspaceId);
7
+ if (lease.status !== "ready") throw new Error("workspace_checkpoint_lease_not_ready");
8
+ const persistence = lease.persistence;
9
+ if (!persistence || persistence.capture === "none") throw new Error("workspace_checkpoint_unsupported");
10
+ const target = await input.target(input.workspaceId);
11
+ if (target.status !== "ready") throw new Error(target.reason);
12
+ await input.request.runtime.freeze(input.request.policy ?? "drain");
13
+ let capturedSource = false;
14
+ try {
15
+ await input.request.runtime.flush();
16
+ await input.request.runtime.scrub();
17
+ const captured = persistence.capture === "same-resource" ? { ok: true, snapshotId: target.driverResourceId ?? target.sandboxId } : await input.snapshot(input.workspaceId);
18
+ if (!captured.ok) throw new Error(captured.reason);
19
+ capturedSource = true;
20
+ const capturedAt = (input.now ?? Date.now)();
21
+ const checkpoint = {
22
+ id: input.checkpointId?.() ?? `cp_${lease.epoch}_${crypto.randomUUID()}`,
23
+ providerReference: captured.snapshotId,
24
+ sourceEpoch: lease.epoch,
25
+ capturedAt,
26
+ metadata: {
27
+ scope: persistence.capture,
28
+ sourceBehavior: requireCaptureSource(persistence.captureSource),
29
+ ...input.request.retentionExpiresAt === void 0 ? {} : { retentionExpiresAt: input.request.retentionExpiresAt },
30
+ restoreMount: requireRestoreMount(persistence.restoreMount)
31
+ }
32
+ };
33
+ const updated = await input.leaseStore.update(input.workspaceId, lease.epoch, {
34
+ checkpoint,
35
+ restore: null,
36
+ ...persistence.captureSource === "stopped" || persistence.captureSource === "deleted" ? { status: "stopped" } : {}
37
+ });
38
+ if (!updated) throw new Error("workspace_checkpoint_epoch_fenced");
39
+ return { status: "ready", lease: updated, checkpoint };
40
+ } finally {
41
+ if (!capturedSource || persistence.captureSource === "preserved") {
42
+ await input.request.runtime.resume();
43
+ }
44
+ }
45
+ }
46
+ async function restoreSandboxCheckpoint(input) {
47
+ const lease = await requireLease(input.leaseStore, input.workspaceId);
48
+ const checkpoint = lease.checkpoint;
49
+ if (!checkpoint || input.request.checkpointId && checkpoint.id !== input.request.checkpointId) {
50
+ throw new Error("workspace_checkpoint_not_found");
51
+ }
52
+ if (lease.restore?.state === "ready" && lease.restore.checkpointId === checkpoint.id) {
53
+ return { status: "ready", lease, checkpoint };
54
+ }
55
+ const requestedAt = lease.restore?.checkpointId === checkpoint.id ? lease.restore.requestedAt : (input.now ?? Date.now)();
56
+ const restoring = lease.restore?.checkpointId === checkpoint.id && lease.restore.state !== "pending" ? lease.restore : {
57
+ checkpointId: checkpoint.id,
58
+ sourceEpoch: checkpoint.sourceEpoch,
59
+ state: "restoring",
60
+ requestedAt,
61
+ startedAt: (input.now ?? Date.now)()
62
+ };
63
+ const staged = lease.status === "ready" && lease.epoch === checkpoint.sourceEpoch ? await input.leaseStore.update(input.workspaceId, lease.epoch, { status: "stopped", restore: restoring }) : await input.leaseStore.update(input.workspaceId, lease.epoch, { restore: restoring });
64
+ if (!staged) throw new Error("workspace_restore_epoch_fenced");
65
+ const result = await input.ensure(input.workspaceId, {
66
+ homeRegion: staged.homeRegion,
67
+ ...input.request.ensure,
68
+ bootSource: checkpoint.metadata.restoreMount === "same-resource" ? { kind: "default" } : { kind: "driver-snapshot", snapshotId: checkpoint.providerReference }
69
+ });
70
+ const current = await requireLease(input.leaseStore, input.workspaceId);
71
+ if (result.status === "provisioning") {
72
+ return { status: "provisioning", lease: current, checkpoint, retryAfterMs: result.retryAfterMs };
73
+ }
74
+ if (result.status !== "ready") {
75
+ const failedAt = (input.now ?? Date.now)();
76
+ await input.leaseStore.update(input.workspaceId, current.epoch, {
77
+ restore: {
78
+ checkpointId: checkpoint.id,
79
+ sourceEpoch: checkpoint.sourceEpoch,
80
+ state: "failed",
81
+ requestedAt,
82
+ startedAt: "startedAt" in restoring ? restoring.startedAt : void 0,
83
+ failedAt,
84
+ error: result.error ?? "workspace_restore_unavailable"
85
+ }
86
+ });
87
+ throw new Error(result.error ?? "workspace_restore_unavailable");
88
+ }
89
+ await input.request.runtime.reconcile({ epoch: result.epoch, checkpointId: checkpoint.id });
90
+ const ready = await input.leaseStore.update(input.workspaceId, result.epoch, {
91
+ restore: {
92
+ checkpointId: checkpoint.id,
93
+ sourceEpoch: checkpoint.sourceEpoch,
94
+ state: "ready",
95
+ requestedAt,
96
+ startedAt: "startedAt" in restoring && restoring.startedAt !== void 0 ? restoring.startedAt : (input.now ?? Date.now)(),
97
+ completedAt: (input.now ?? Date.now)()
98
+ }
99
+ });
100
+ if (!ready) throw new Error("workspace_restore_epoch_fenced");
101
+ return { status: "ready", lease: ready, checkpoint };
102
+ }
103
+ async function requireLease(store, workspaceId) {
104
+ const lease = await store.get(workspaceId);
105
+ if (!lease) throw new Error("workspace_checkpoint_lease_missing");
106
+ return lease;
107
+ }
108
+ function requireCaptureSource(value) {
109
+ if (value === "preserved" || value === "stopped" || value === "deleted") return value;
110
+ throw new Error("workspace_checkpoint_capabilities_invalid");
111
+ }
112
+ function requireRestoreMount(value) {
113
+ if (value === "same-resource" || value === "copy-on-write" || value === "new-resource") return value;
114
+ throw new Error("workspace_checkpoint_capabilities_invalid");
115
+ }
116
+
117
+ // src/hosted-network-policy.ts
118
+ var SANDBOX_MODEL_PROVIDER_HOSTS = [
119
+ "api.anthropic.com",
120
+ "api.openai.com",
121
+ "generativelanguage.googleapis.com",
122
+ "openrouter.ai",
123
+ // Model catalog + engine config, fetched on runner startup.
124
+ "models.dev",
125
+ "opencode.ai"
126
+ ];
127
+ var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
128
+ "registry.npmjs.org",
129
+ "nodejs.org",
130
+ "pypi.org",
131
+ "files.pythonhosted.org",
132
+ "crates.io",
133
+ "static.crates.io",
134
+ "index.crates.io",
135
+ "proxy.golang.org",
136
+ "sum.golang.org"
137
+ ];
138
+ var SANDBOX_BASELINE_EGRESS_HOSTS = [
139
+ ...SANDBOX_MODEL_PROVIDER_HOSTS,
140
+ ...SANDBOX_PACKAGE_REGISTRY_HOSTS
141
+ ];
142
+ function hostOf(value) {
143
+ const raw = value?.trim();
144
+ if (!raw) return;
145
+ try {
146
+ const host = new URL(raw).hostname.trim().toLowerCase();
147
+ return host ? host : void 0;
148
+ } catch {
149
+ return;
150
+ }
151
+ }
152
+ function sandboxSourceHost(source) {
153
+ if (!source || source.kind !== "git") return;
154
+ return hostOf(source.repoUrl);
155
+ }
156
+ function hostedSandboxNetworkPolicy(input) {
157
+ const hosts = /* @__PURE__ */ new Set();
158
+ for (const entry of input.controlPlane) {
159
+ const host = hostOf(entry) ?? entry?.trim().toLowerCase();
160
+ if (host) hosts.add(host);
161
+ }
162
+ const sourceHost = sandboxSourceHost(input.source);
163
+ if (sourceHost) hosts.add(sourceHost);
164
+ for (const host of SANDBOX_BASELINE_EGRESS_HOSTS) hosts.add(host);
165
+ for (const host of input.extraHosts ?? []) {
166
+ const trimmed = host.trim().toLowerCase();
167
+ if (trimmed) hosts.add(trimmed);
168
+ }
169
+ return { mode: "restricted", hosts: [...hosts] };
170
+ }
171
+
4
172
  // src/index.ts
173
+ function validateSandboxPersistenceCapabilities(input) {
174
+ if (input.resume === "replacement-restore" && input.capture === "none") {
175
+ return { valid: false, reason: "replacement restore requires a capture source" };
176
+ }
177
+ if (input.capture === "none" && input.captureSource !== "not-applicable") {
178
+ return { valid: false, reason: "capture source behavior requires capture support" };
179
+ }
180
+ if (input.capture !== "none" && input.captureSource === "not-applicable") {
181
+ return { valid: false, reason: "capture support requires source behavior" };
182
+ }
183
+ if (input.capture === "same-resource" && input.restoreMount !== "same-resource") {
184
+ return { valid: false, reason: "same-resource capture requires same-resource restore" };
185
+ }
186
+ if (input.restoreMount === "copy-on-write" && input.capture !== "directories") {
187
+ return { valid: false, reason: "copy-on-write restore requires directory capture" };
188
+ }
189
+ return { valid: true };
190
+ }
191
+ function sandboxEgressDisposition(control, net) {
192
+ if (!net || net.mode === "allow-all") return { action: "enforce" };
193
+ if (control === "none") return { action: "withhold", reason: "sandbox_egress_uncontained" };
194
+ const hosts = net.hosts?.length ?? 0;
195
+ const cidrs = net.cidrs?.length ?? 0;
196
+ if (hosts === 0 && cidrs === 0) return { action: "enforce" };
197
+ if (hosts > 0) return { action: "enforce" };
198
+ return control === "hosts-and-cidrs" ? { action: "enforce" } : { action: "refuse", reason: "sandbox_egress_policy_unenforceable" };
199
+ }
200
+ function egressUnenforcedMessage(input) {
201
+ const scope = input.workspaceId ? `workspace ${input.workspaceId}` : "every sandbox it provisions";
202
+ const withheld = input.requested ? ` The requested allowlist (${(input.requested.hosts ?? []).length} host(s), ${(input.requested.cidrs ?? []).length} cidr(s)) was withheld, not applied.` : "";
203
+ return `[sandbox-manager] SANDBOX EGRESS IS UNRESTRICTED: driver "${input.driver}" declares egressControl: "none", so ${scope} can reach ANY host on the internet, including attacker-controlled buckets \u2014 agent-authored code inside the sandbox has an unmonitored exfiltration path.${withheld} Select a driver that can enforce egress (daytona, vercel) to close it. See public-docs/sandbox-egress.md.`;
204
+ }
205
+ var compositionEgressWarnings = /* @__PURE__ */ new Set();
206
+ function defaultEgressUnenforcedSink(event) {
207
+ console.warn(event.message);
208
+ }
5
209
  function applySandboxLeasePatch(current, patch, updatedAt) {
6
210
  return {
7
211
  ...current,
@@ -16,14 +220,21 @@ function applySandboxLeasePatch(current, patch, updatedAt) {
16
220
  ...patch.labels === void 0 ? {} : { labels: patch.labels },
17
221
  ...patch.nextRetryAt === void 0 ? {} : { nextRetryAt: patch.nextRetryAt ?? void 0 },
18
222
  ...patch.lastError === void 0 ? {} : { lastError: patch.lastError ?? void 0 },
223
+ ...patch.checkpoint === void 0 ? {} : { checkpoint: patch.checkpoint ?? void 0 },
224
+ ...patch.persistence === void 0 ? {} : { persistence: patch.persistence ?? void 0 },
225
+ ...patch.restore === void 0 ? {} : { restore: patch.restore ?? void 0 },
19
226
  updatedAt
20
227
  };
21
228
  }
229
+ function isSandboxListingUnsupported(err) {
230
+ return !!err && typeof err === "object" && err.listingUnsupported === true;
231
+ }
22
232
  var DEFAULT_APP_LABEL = "claxedo";
23
233
  function sandboxId(driver, workspaceId) {
24
234
  return `${driver}-${workspaceId}`;
25
235
  }
26
236
  function ensureHostInput(input) {
237
+ const egress = sandboxEgressDisposition(input.driver.metadata.egressControl, input.managerInput?.net);
27
238
  const labels = {
28
239
  app: input.appLabel,
29
240
  workspaceId: input.workspaceId,
@@ -37,7 +248,7 @@ function ensureHostInput(input) {
37
248
  homeRegion: input.homeRegion,
38
249
  epoch: input.lease.epoch,
39
250
  labels,
40
- bootSource: input.managerInput?.bootSource ?? { kind: "default" },
251
+ bootSource: input.managerInput?.bootSource ?? (input.lease.checkpoint && input.lease.status !== "ready" && input.driver.metadata.persistence.resume === "replacement-restore" ? { kind: "driver-snapshot", snapshotId: input.lease.checkpoint.providerReference } : { kind: "default" }),
41
252
  workspaceRoot: input.managerInput?.workspaceRoot ?? "/workspace",
42
253
  runtimeCwd: input.managerInput?.runtimeCwd,
43
254
  workspaceRuntimePort: input.managerInput?.workspaceRuntimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT,
@@ -46,17 +257,66 @@ function ensureHostInput(input) {
46
257
  ...input.managerInput?.secrets?.length ? { secrets: input.managerInput.secrets } : {},
47
258
  source: input.managerInput?.source,
48
259
  exposure: input.managerInput?.exposure ?? (input.driver.metadata.targetAccess === "loopback" ? { kind: "loopback" } : { kind: "relay" }),
49
- net: input.managerInput?.net,
260
+ // Withheld, never downgraded: `undefined` here is the same shape a caller
261
+ // that asked for no containment produces, so the throwing drivers take
262
+ // their allow-all path and their throws stay intact for anyone who hands
263
+ // them a policy directly.
264
+ ...egress.action === "enforce" && input.managerInput?.net ? { net: input.managerInput.net } : {},
50
265
  snapshot: input.managerInput?.snapshot
51
266
  };
52
267
  }
53
268
  function createSandboxManager(options) {
269
+ const persistence = validateSandboxPersistenceCapabilities(options.driver.metadata.persistence);
270
+ if (!persistence.valid) throw new Error(`Invalid sandbox persistence capabilities: ${persistence.reason}`);
54
271
  const staleAfterMs = options.staleAfterMs ?? 6e4;
55
272
  const retryAfterMs = options.retryAfterMs ?? 2e3;
56
273
  const now = options.now ?? Date.now;
57
274
  const retryDelayMs = options.retryDelayMs ?? ((retryCount) => Math.min(6e4, 1e3 * 2 ** Math.max(0, retryCount - 1)));
58
275
  const maxRetryCount = options.maxRetryCount ?? Number.POSITIVE_INFINITY;
59
276
  const retryCapCooldownMs = options.retryCapCooldownMs ?? 10 * 6e4;
277
+ const egressControl = options.driver.metadata.egressControl;
278
+ const onEgressUnenforced = options.onEgressUnenforced ?? defaultEgressUnenforcedSink;
279
+ const lifecycleOperations = /* @__PURE__ */ new Map();
280
+ function reportEgressUnenforced(input) {
281
+ onEgressUnenforced({
282
+ phase: "ensure",
283
+ reason: "sandbox_egress_uncontained",
284
+ driver: options.driver.id,
285
+ egressControl,
286
+ message: egressUnenforcedMessage({
287
+ phase: "ensure",
288
+ driver: options.driver.id,
289
+ workspaceId: input.workspaceId,
290
+ requested: input.requested
291
+ }),
292
+ workspaceId: input.workspaceId,
293
+ requested: input.requested
294
+ });
295
+ }
296
+ if (egressControl === "none") {
297
+ const key = `${options.driver.id}|${egressControl}`;
298
+ if (!compositionEgressWarnings.has(key)) {
299
+ compositionEgressWarnings.add(key);
300
+ onEgressUnenforced({
301
+ phase: "composition",
302
+ reason: "sandbox_egress_uncontained",
303
+ driver: options.driver.id,
304
+ egressControl,
305
+ message: egressUnenforcedMessage({ phase: "composition", driver: options.driver.id })
306
+ });
307
+ }
308
+ }
309
+ function lifecycle(workspaceId, kind, run) {
310
+ const current = lifecycleOperations.get(workspaceId);
311
+ if (current?.kind === kind) return current.promise;
312
+ const operation = (current?.promise.catch(() => void 0) ?? Promise.resolve()).then(run);
313
+ lifecycleOperations.set(workspaceId, { kind, promise: operation });
314
+ return operation.finally(() => {
315
+ if (lifecycleOperations.get(workspaceId)?.promise === operation) {
316
+ lifecycleOperations.delete(workspaceId);
317
+ }
318
+ });
319
+ }
60
320
  async function leaseTarget(lease) {
61
321
  if (lease.status !== "ready" || !lease.sandboxId || !lease.url || !lease.hostId) {
62
322
  return { status: "unavailable", reason: "runtime_lease_not_ready" };
@@ -117,7 +377,8 @@ function createSandboxManager(options) {
117
377
  retryCount: 0,
118
378
  nextRetryAt: null,
119
379
  lastError: null,
120
- labels: target.labels ?? ensure.labels
380
+ labels: target.labels ?? ensure.labels,
381
+ persistence: options.driver.metadata.persistence
121
382
  });
122
383
  const resolved = await leaseTarget(updated ?? lease);
123
384
  if (resolved.status === "ready") return resolved;
@@ -153,6 +414,13 @@ function createSandboxManager(options) {
153
414
  }
154
415
  return {
155
416
  async ensure(workspaceId, input) {
417
+ const egress = sandboxEgressDisposition(options.driver.metadata.egressControl, input.net);
418
+ if (egress.action === "withhold" && input.net) {
419
+ reportEgressUnenforced({ workspaceId, requested: input.net });
420
+ }
421
+ if (egress.action === "refuse") {
422
+ return { status: "unavailable", error: egress.reason, homeRegion: input.homeRegion };
423
+ }
156
424
  const existing = await options.leaseStore.get(workspaceId);
157
425
  if (existing?.nextRetryAt && existing.nextRetryAt > now()) {
158
426
  if (existing.status === "acquiring") {
@@ -224,23 +492,50 @@ function createSandboxManager(options) {
224
492
  if (!options.driver.snapshot) return { ok: false, reason: "snapshot_unsupported" };
225
493
  return { ok: true, ...await options.driver.snapshot(target) };
226
494
  },
495
+ async checkpoint(workspaceId, input) {
496
+ return await lifecycle(workspaceId, "checkpoint", () => captureSandboxCheckpoint({
497
+ workspaceId,
498
+ request: input,
499
+ leaseStore: options.leaseStore,
500
+ target: (id) => this.target(id),
501
+ snapshot: (id) => this.snapshot(id),
502
+ now
503
+ }));
504
+ },
505
+ async restore(workspaceId, input) {
506
+ return await lifecycle(workspaceId, "restore", () => restoreSandboxCheckpoint({
507
+ workspaceId,
508
+ request: input,
509
+ leaseStore: options.leaseStore,
510
+ ensure: (id, next) => this.ensure(id, next),
511
+ now
512
+ }));
513
+ },
227
514
  async stop(workspaceId) {
228
- const target = await this.target(workspaceId);
229
- if (target.status !== "ready") return { ok: false, reason: target.reason };
230
- if (options.driver.metadata.hostStopBehavior !== "not-supported") {
231
- await (options.driver.suspend ?? options.driver.stop)?.(target);
232
- } else {
233
- await options.driver.stop?.(target);
234
- }
235
- await options.leaseStore.update(workspaceId, target.epoch, { status: "stopped" });
236
- return { ok: true, status: "stopped" };
515
+ return await lifecycle(workspaceId, "stop", async () => {
516
+ const lease = await options.leaseStore.get(workspaceId);
517
+ if (lease?.status === "stopped") return { ok: true, status: "stopped" };
518
+ const target = await this.target(workspaceId);
519
+ if (target.status !== "ready") return { ok: false, reason: target.reason };
520
+ if (options.driver.metadata.hostStopBehavior !== "not-supported") {
521
+ await (options.driver.suspend ?? options.driver.stop)?.(target);
522
+ } else {
523
+ await options.driver.stop?.(target);
524
+ }
525
+ await options.leaseStore.update(workspaceId, target.epoch, { status: "stopped" });
526
+ return { ok: true, status: "stopped" };
527
+ });
237
528
  },
238
529
  async destroy(workspaceId) {
239
- const target = await this.target(workspaceId);
240
- if (target.status !== "ready") return { ok: false, reason: target.reason };
241
- await options.driver.destroy?.(target);
242
- await options.leaseStore.update(workspaceId, target.epoch, { status: "destroyed" });
243
- return { ok: true, status: "destroyed" };
530
+ return await lifecycle(workspaceId, "destroy", async () => {
531
+ const lease = await options.leaseStore.get(workspaceId);
532
+ if (lease?.status === "destroyed") return { ok: true, status: "destroyed" };
533
+ const target = await this.target(workspaceId);
534
+ if (target.status !== "ready") return { ok: false, reason: target.reason };
535
+ await options.driver.destroy?.(target);
536
+ await options.leaseStore.update(workspaceId, target.epoch, { status: "destroyed" });
537
+ return { ok: true, status: "destroyed" };
538
+ });
244
539
  },
245
540
  async release(workspaceId) {
246
541
  const lease = await options.leaseStore.get(workspaceId);
@@ -254,7 +549,23 @@ function createSandboxManager(options) {
254
549
  destroyed: [],
255
550
  kept: [],
256
551
  skipped: [],
257
- failed: []
552
+ failed: [],
553
+ listingUnsupported: true,
554
+ driver: options.driver.id
555
+ };
556
+ }
557
+ let listed;
558
+ try {
559
+ listed = await options.driver.list();
560
+ } catch (err) {
561
+ if (!isSandboxListingUnsupported(err)) throw err;
562
+ return {
563
+ destroyed: [],
564
+ kept: [],
565
+ skipped: [],
566
+ failed: [],
567
+ listingUnsupported: true,
568
+ driver: options.driver.id
258
569
  };
259
570
  }
260
571
  const leases = new Map((await options.leaseStore.list()).map((lease) => [lease.workspaceId, lease]));
@@ -265,7 +576,7 @@ function createSandboxManager(options) {
265
576
  failed: []
266
577
  };
267
578
  const appLabel = options.appLabel ?? DEFAULT_APP_LABEL;
268
- for (const target of await options.driver.list()) {
579
+ for (const target of listed) {
269
580
  if (target.labels?.app !== appLabel) {
270
581
  result.skipped.push({ target, reason: "unmanaged_app_label" });
271
582
  continue;
@@ -323,6 +634,16 @@ function createSandboxManager(options) {
323
634
  }
324
635
  export {
325
636
  DEFAULT_WORKSPACE_RUNTIME_PORT,
637
+ SANDBOX_BASELINE_EGRESS_HOSTS,
638
+ SANDBOX_MODEL_PROVIDER_HOSTS,
639
+ SANDBOX_PACKAGE_REGISTRY_HOSTS,
326
640
  applySandboxLeasePatch,
327
- createSandboxManager
641
+ captureSandboxCheckpoint,
642
+ createSandboxManager,
643
+ hostedSandboxNetworkPolicy,
644
+ isSandboxListingUnsupported,
645
+ restoreSandboxCheckpoint,
646
+ sandboxEgressDisposition,
647
+ sandboxSourceHost,
648
+ validateSandboxPersistenceCapabilities
328
649
  };
@@ -1,5 +1,5 @@
1
1
  import type { SandboxLeaseRow } from "./lease-types";
2
- import type { SandboxDriverID } from "./driver-catalog";
2
+ import { type SandboxDriverID } from "./driver-catalog";
3
3
  export type SandboxDecision = {
4
4
  action: "skip";
5
5
  reason: string;
@@ -45,5 +45,4 @@ export declare const DEFAULT_WORKSPACE_HOST_DECISION_CONFIG: SandboxDecisionConf
45
45
  export declare function sandboxDriverPlacement(driver: SandboxDriverID): SandboxDriverPlacement;
46
46
  export declare function decideSandboxStart(lease: SandboxLeaseRow, placement: SandboxDriverPlacement, now: number, config?: SandboxDecisionConfig): SandboxDecision;
47
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
48
  export declare function nextSandboxRetryAt(retryCount: number, now: number, config?: SandboxDecisionConfig): number | null;