@evolvingmachines/daytona 0.0.55 → 0.0.56

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.d.ts CHANGED
@@ -1,23 +1,765 @@
1
+ import { CreateSandboxFromImageParams, CreateSandboxFromSnapshotParams, Sandbox, ListSandboxesQuery } from '@daytonaio/sdk';
2
+
3
+ /**
4
+ * GENERATED — DO NOT EDIT. `npm run generate:image-version` (repo root)
5
+ * rewrites this file from the evolve-all image build inputs under
6
+ * assets/docker/ (see assets/docker/image-digest.ts for the derivation).
7
+ * The value is content-addressed: same inputs → same tag, any input
8
+ * change → a new tag. The coherence test in
9
+ * packages/daytona/tests/unit/daytona-image-version.test.ts fails the
10
+ * suite whenever this checked-in copy is stale.
11
+ */
12
+ declare const EVOLVE_IMAGE_VERSION = "c-880c264ce574";
13
+
1
14
  /**
2
15
  * Daytona Sandbox Provider
3
16
  *
4
- * @requires @daytonaio/sdk >= 0.134.0
17
+ * @requires @daytonaio/sdk >= 0.203.0 (cursor-based list pagination; Daytona
18
+ * retired the page-numbered endpoint on 2026-06-25)
5
19
  *
6
20
  * Design principles:
7
21
  * - Mirror E2B provider interface for SDK compatibility
8
22
  * - Uses public Docker images via IMAGE_MAP
9
23
  * - Parallel structure to E2B provider
24
+ *
25
+ * Daytona-specific notes:
26
+ * - Network policy maps to Daytona's three network controls
27
+ * (daytona.io/docs/en/network-limits): networkBlockAll (all egress
28
+ * dropped), networkAllowList (IPv4 CIDRs only, max 10 entries,
29
+ * DAYTONA_MAX_NETWORK_ALLOWLIST) and domainAllowList (DNS domains, `*.`
30
+ * prefix wildcards allowed, max 20 entries, DAYTONA_MAX_DOMAIN_ALLOWLIST).
31
+ * Per the docs "The options are mutually exclusive. Set at most one
32
+ * non-empty value" — so CIDR destinations map to networkAllowList,
33
+ * hostname/wildcard destinations map to domainAllowList, and a policy
34
+ * mixing the two kinds is refused typed (mixed-allowlist) rather than
35
+ * sent to a guaranteed 400. Destinations Daytona can never enforce
36
+ * (IPv6, ports, non-prefix wildcards, over-cap lists) throw
37
+ * DaytonaNetworkPolicyError instead of silently weakening the policy.
38
+ * Hostnames are forwarded AS NAMES — nothing is DNS-resolved or pinned.
39
+ * - The `user` option is a CREATE-TIME OS user (Daytona's osUser field);
40
+ * there is no per-exec user switch — the Daytona daemon runs commands as
41
+ * the container's user (governed by the image's USER directive; default
42
+ * images use "daytona" with passwordless sudo). `user: "root"` keeps the
43
+ * image's default user and elevates every command through a `sudo -n`
44
+ * wrapper instead (mirrors the Modal provider's su wrapper). File
45
+ * operations always go through the Daytona daemon and are NOT elevated.
46
+ * - Private registry images (AWS ECR, GHCR, GCP Artifact Registry, ...)
47
+ * require registry credentials pre-registered in the Daytona dashboard
48
+ * (Registries page) — Daytona has no per-call image pull secret. Pull
49
+ * failures for such images throw DaytonaImagePullError.
50
+ * - getInfo()/list() report the API's real createdAt timestamp (never a
51
+ * fabricated client-side date); Daytona exposes no end timestamp, so
52
+ * endAt is always undefined.
53
+ */
54
+
55
+ /**
56
+ * Daytona's hard cap on network allowlist size (validated server-side too).
57
+ * Policies that resolve to more CIDRs throw DaytonaNetworkPolicyError.
58
+ */
59
+ /**
60
+ * Minutes a STOPPED sandbox is kept before Daytona deletes it. Long enough that
61
+ * a stop nobody ordered leaves something to look at and something to collect
62
+ * from; short enough that it is never a way to hold billable state.
63
+ */
64
+ declare const DAYTONA_AUTO_DELETE_GRACE_MINUTES = 10;
65
+ declare const DAYTONA_MAX_NETWORK_ALLOWLIST = 10;
66
+ /**
67
+ * Daytona's hard cap on domainAllowList size (network-limits docs: "Maximum
68
+ * entries: 20 comma-separated items"). Policies naming more domains throw
69
+ * DaytonaNetworkPolicyError.
70
+ */
71
+ declare const DAYTONA_MAX_DOMAIN_ALLOWLIST = 20;
72
+ /**
73
+ * How long a reactivated snapshot may take to come back before create() gives
74
+ * up. Daytona deactivates any snapshot unused for two weeks (official docs:
75
+ * "Snapshots automatically become inactive after 2 weeks of not being used"),
76
+ * so exists-but-inactive is the steady state of every image that shipped more
77
+ * than a fortnight before its next user — reactivation is a pull, not a build,
78
+ * and one that outlives this bound is a provider incident worth a loud error.
79
+ */
80
+ declare const DAYTONA_SNAPSHOT_ACTIVATE_TIMEOUT_MS = 180000;
81
+ /**
82
+ * How long a create that LOST a snapshot name race waits for the winner's
83
+ * build before giving up. Same budget the build path already spends on its own
84
+ * snapshot create and on the sandbox create that follows it ({ timeout: 600 }),
85
+ * and the same number Harbor waits — see
86
+ * REFERENCES/Harbor/src/harbor/environments/daytona/snapshots.py:306-311
87
+ * (`_wait_for_active`, `timeout: int = 600`).
88
+ */
89
+ declare const DAYTONA_SNAPSHOT_CONFLICT_TIMEOUT_MS = 600000;
90
+ /**
91
+ * Clocks of a STREAMED run (see awaitStreamedExit). The poll that reads the
92
+ * command's exit code backs off from pollMin to pollMax. A follow whose
93
+ * command has already ended is cut once it has been SILENT for drainMs —
94
+ * silence, never elapsed time, so a stream still delivering is never
95
+ * truncated. killGrace is what the caller's timeoutMs is widened by before a
96
+ * streamed run gives up, because withInBoxTimeout kills with `timeout -k 10`:
97
+ * the box may legitimately take ten seconds past the deadline to record a
98
+ * status. And a follow that has CLOSED means the command ended, so its record
99
+ * must catch up within settleMs — the only backstop a caller who passed no
100
+ * timeoutMs has, which is why it is generous rather than tight.
101
+ */
102
+ declare const DAYTONA_STREAM_TIMINGS: {
103
+ pollMinMs: number;
104
+ pollMaxMs: number;
105
+ drainMs: number;
106
+ killGraceMs: number;
107
+ settleMs: number;
108
+ };
109
+ type DaytonaStreamTimings = typeof DAYTONA_STREAM_TIMINGS;
110
+ /**
111
+ * Sandboxes requested per list fetch — the cursor API's per-page size
112
+ * (ListSandboxesQuery.limit bounds one fetch, never the total). Held at the
113
+ * value every provider in this lineup accepts, so one number is valid
114
+ * everywhere a fleet is enumerated.
115
+ */
116
+ declare const DAYTONA_LIST_PAGE_SIZE = 100;
117
+ /**
118
+ * Row ceiling for one enumeration — the same 10,000 sandboxes the old
119
+ * 100-pages-of-100 ceiling allowed, restated in rows because cursor pagination
120
+ * has no page numbers to count. A walk that reaches it stops and reports
121
+ * itself INCOMPLETE, because the one thing a fleet enumeration may never do is
122
+ * return a short list that reads like a whole one.
123
+ */
124
+ declare const DAYTONA_MAX_LIST_SCAN = 10000;
125
+ /** Why a network policy cannot be enforced by Daytona. */
126
+ type DaytonaNetworkPolicyReason =
127
+ /**
128
+ * A wildcard NOT of the documented `*.domain` prefix form. Daytona's
129
+ * domainAllowList supports exactly one wildcard shape ("Prefix a domain
130
+ * with `*.` to allow the base domain and its subdomains"); any other
131
+ * placement cannot be expressed.
132
+ */
133
+ "wildcard-hostname" | "ipv6-unsupported" | "port-unsupported" | "invalid-ipv4"
134
+ /**
135
+ * An empty/whitespace-only destination, or one carrying a comma. Both wire
136
+ * allowlists are single comma-joined strings, so a blank entry would emit
137
+ * an empty allowlist — which Daytona reads, with blockAll:false, as
138
+ * UNRESTRICTED egress — and an embedded comma would smuggle extra entries
139
+ * past the 10/20 caps and corrupt the joined value. Rejected before
140
+ * classification so a sealed policy can never boot an open box.
10
141
  */
142
+ | "invalid-destination"
11
143
  /**
12
- * Wrap command with cwd and envs shell prefixes.
144
+ * The policy names both CIDR and domain destinations. Daytona keeps two
145
+ * allowlists — networkAllowList (CIDRs) and domainAllowList (names) — and
146
+ * its docs make them mutually exclusive: "Set at most one non-empty value.
147
+ * Sending a conflicting combination returns a 400 error." One policy can
148
+ * therefore hold IPs/CIDRs or hostnames, not both.
149
+ */
150
+ | "mixed-allowlist" | "allowlist-too-large"
151
+ /**
152
+ * The ORGANIZATION is not allowed to set a sandbox-level network policy at
153
+ * all, whatever the policy says. Daytona gates this by plan tier:
154
+ * "Organizations on Tier 1 or Tier 2 cannot override network policy at the
155
+ * sandbox level" (Daytona docs, sandbox network configuration). It is a
156
+ * property of the account, not of the request, so no rewriting of the policy
157
+ * makes it succeed — the caller needs a tier upgrade or another provider.
158
+ */
159
+ | "org-tier-forbidden";
160
+ /**
161
+ * Typed error for network policies Daytona cannot enforce.
162
+ *
163
+ * Daytona's controls (network-limits docs) are networkBlockAll, an IPv4-CIDR
164
+ * networkAllowList (max 10) and a DNS domainAllowList (max 20, `*.` prefix
165
+ * wildcards) — mutually exclusive, at most one non-empty. Anything that fits
166
+ * none of them is rejected loudly instead of silently weakening the
167
+ * sandbox's egress policy.
168
+ */
169
+ declare class DaytonaNetworkPolicyError extends Error {
170
+ readonly reason: DaytonaNetworkPolicyReason;
171
+ /** The offending destination (absent for list-level violations). */
172
+ readonly destination?: string;
173
+ constructor(reason: DaytonaNetworkPolicyReason, message: string, destination?: string);
174
+ }
175
+ /**
176
+ * Typed error for image pull failures on private registries.
177
+ *
178
+ * Daytona has no per-call image pull secret: the registry must be
179
+ * pre-registered in the Daytona dashboard BEFORE creating the sandbox
180
+ * (dashboard → Registries → Add Registry) — username/password for GHCR, GCP
181
+ * Artifact Registry or private Docker Hub, and for AWS ECR a cross-account
182
+ * IAM role ARN Daytona assumes server-side (its docs: "Password is not used
183
+ * for ECR").
184
+ */
185
+ declare class DaytonaImagePullError extends Error {
186
+ /** The image reference that failed to pull. */
187
+ readonly image: string;
188
+ constructor(image: string, cause?: unknown);
189
+ }
190
+ /**
191
+ * Typed error for create-time sizing requests an EXISTING snapshot cannot
192
+ * honor. Daytona pins cpu/memory/disk on the SNAPSHOT at build time;
193
+ * create-from-snapshot has no resources parameter, so a `resources` request
194
+ * against a cached snapshot would be silently ignored. Per the provider law
195
+ * (reject what you cannot enforce) it is refused loudly instead: pre-build a
196
+ * snapshot with the desired sizing under its own name, or drop `resources`.
197
+ */
198
+ /**
199
+ * @deprecated No longer thrown. @daytonaio/sdk 0.203.0 grew a real
200
+ * `Resources.gpuType` field, so `resources.gpuTypes` is now FORWARDED on the
201
+ * snapshot-build path instead of refused (Daytona validates the type names
202
+ * server-side). Sizing against an EXISTING snapshot — GPU type included —
203
+ * still throws DaytonaResourcesError like every other pinned field. The class
204
+ * stays exported so callers with `instanceof` checks keep compiling.
205
+ */
206
+ declare class DaytonaGpuTypeError extends Error {
207
+ /** The snapshot/image the create named. */
208
+ readonly snapshot: string;
209
+ constructor(snapshot: string);
210
+ }
211
+ declare class DaytonaResourcesError extends Error {
212
+ /** The existing snapshot whose pinned sizing cannot be overridden. */
213
+ readonly snapshot: string;
214
+ constructor(snapshot: string);
215
+ }
216
+ /**
217
+ * Typed error for an idle timeout Daytona cannot take as a SEPARATE bound.
218
+ * Daytona has no absolute lifetime at all: auto-stop is an inactivity clock and
219
+ * it is the only one there is, so `timeoutMs` is already mapped onto it. A
220
+ * second option pointing at the same knob could only contradict the first.
221
+ */
222
+ declare class DaytonaBootCommandError extends Error {
223
+ constructor();
224
+ }
225
+ declare class DaytonaIdleTimeoutError extends Error {
226
+ constructor();
227
+ }
228
+ /**
229
+ * Where a boot is, read off Daytona's own sandbox state. 'image_pull' is
230
+ * every state in which the runner is still MATERIALIZING THE IMAGE —
231
+ * `pending_build` (queued for the builder), `building_snapshot` (the
232
+ * declarative build, whose FROM step is where the base image is pulled) and
233
+ * `pulling_snapshot` (a built snapshot being pulled onto the runner) — and
234
+ * 'boot' is everything else: the container start, and the stretch before the
235
+ * provider has reported any state at all. Two words only, because those are
236
+ * the two facts a caller acts on differently: a pull that is moving is
237
+ * allowed to be slow; a container that is silent is not.
238
+ */
239
+ type DaytonaBootPhase = "image_pull" | "boot";
240
+ /** The boot phase a reported sandbox state belongs to (see DaytonaBootPhase). */
241
+ declare function daytonaBootPhaseOf(state: string | null | undefined): DaytonaBootPhase;
242
+ /**
243
+ * One thing the provider reported while a create was in flight. `signal`
244
+ * names WHICH fact moved: the sandbox row's state changed, or the build log
245
+ * delivered a line (`line`, one line, trimmed — a chunk from the stream is
246
+ * split on newlines so a multi-line chunk is several events).
247
+ */
248
+ interface DaytonaBootProgress {
249
+ signal: "state" | "build_log";
250
+ phase: DaytonaBootPhase;
251
+ /** Daytona's sandbox state as last read; null until the row has been found. */
252
+ state: string | null;
253
+ sandboxId: string | null;
254
+ line?: string;
255
+ }
256
+ /**
257
+ * Thrown by createSandboxObserved when its `signal` aborted: the create was
258
+ * abandoned, the half-made box deleted best-effort, and this names where the
259
+ * boot was at that moment. The DECISION to abort is the caller's — this
260
+ * package reports progress and honours the abort; it never judges silence.
261
+ */
262
+ declare class DaytonaBootAbortedError extends Error {
263
+ readonly phase: DaytonaBootPhase;
264
+ readonly state: string | null;
265
+ readonly sandboxId: string | null;
266
+ constructor(phase: DaytonaBootPhase, state: string | null, sandboxId: string | null);
267
+ }
268
+ /**
269
+ * The label createSandboxObserved stamps on the box it creates — a fresh UUID
270
+ * per create — so the box is FINDABLE (daytona.list({ labels })) from the
271
+ * moment the control plane has a row for it, while the SDK's create promise
272
+ * is still opaque. Beside the caller's own labels, never in place of them.
273
+ */
274
+ declare const DAYTONA_BOOT_LABEL = "evolve_boot";
275
+ /** How often the in-flight box's state is re-read. Measured boots move on a scale of seconds, not milliseconds. */
276
+ declare const DAYTONA_BOOT_POLL_MS = 5000;
277
+ interface DaytonaObservedCreateOptions {
278
+ /** Every state change and build-log line, as it is observed. Must not throw. */
279
+ onProgress?: (progress: DaytonaBootProgress) => void;
280
+ /**
281
+ * Aborting it ends the wait: the create promise is abandoned, the box (if
282
+ * the row exists yet) is deleted best-effort, and the call rejects with
283
+ * DaytonaBootAbortedError. Without it the wait is unbounded — the SDK's own
284
+ * wall clock is OFF here (see createSandboxObserved), so a caller that
285
+ * passes no signal must be sure it wants to wait forever.
286
+ */
287
+ signal?: AbortSignal;
288
+ /** Poll period for the state read; default DAYTONA_BOOT_POLL_MS. */
289
+ pollMs?: number;
290
+ }
291
+ /** The slice of the Daytona client an observed create drives — structural, so tests can fake it. */
292
+ interface DaytonaBootClient {
293
+ create(params: CreateSandboxFromImageParams | CreateSandboxFromSnapshotParams, options: {
294
+ timeout: number;
295
+ onSnapshotCreateLogs?: (chunk: string) => void;
296
+ }): Promise<Sandbox>;
297
+ list(query: ListSandboxesQuery): AsyncIterable<Sandbox>;
298
+ }
299
+ /**
300
+ * THE BOOT-PROGRESS HOOK. `Daytona.create()` is one opaque promise: it POSTs
301
+ * the sandbox, polls its state, streams the build log and waits for
302
+ * 'started' (@daytonaio/sdk 0.203 Daytona.create) — and says nothing to its
303
+ * caller until it resolves or its own wall clock (`timeout`, default 60 s)
304
+ * gives up. That silence is the whole problem for a caller that must tell a
305
+ * WEDGED boot from a SLOW one: a cold declarative build of a multi-GB image
306
+ * legitimately runs for ten minutes (the runner pulls the base image inside
307
+ * the build's FROM step), and a fixed wall wide enough to survive it is a
308
+ * wall that also waits ten minutes on a request that will never answer.
309
+ *
310
+ * This runs the same create and, beside it, READS what the provider reports:
311
+ * the sandbox row — found by the DAYTONA_BOOT_LABEL stamped at create, then
312
+ * refreshed every `pollMs` — and the build-log stream (the SDK's
313
+ * onSnapshotCreateLogs). Every state change and every log line is one
314
+ * DaytonaBootProgress to `onProgress`; the caller decides what silence means
315
+ * and says so through `signal`. The SDK's own wall clock is switched OFF
316
+ * (`timeout: 0` — "0 means no timeout", daytona.io/docs/en/typescript-sdk/
317
+ * daytona) because the caller watching progress must be the only clock.
318
+ *
319
+ * ON ABORT the create promise is abandoned and the box is deleted
320
+ * best-effort — the row known now, and whatever the abandoned create still
321
+ * yields later. Deleting it is also what ends the SDK's orphaned wait: its
322
+ * state polls 404 and reject. The SDK's own timeout leaves the box behind
323
+ * instead (a trial that timed out its boot on 2026-09-01 settled with no
324
+ * sandbox id while the box went on building).
325
+ *
326
+ * MEASURED 2026-09-01 (alpine:3.20, declarative, one unique layer): the row
327
+ * was listable by label 2.5 s after the request, in state 'building_snapshot';
328
+ * the log callback carried BuildKit's lines including the base-image pull
329
+ * step ('#4 [1/1] FROM docker.io/library/alpine:3.20@sha256:…'); the state
330
+ * moved 'building_snapshot' → 'started' at 11 s and the create resolved at
331
+ * 14 s. The row's `updatedAt` moved only with the state, so it is not read.
332
+ */
333
+ declare function createSandboxObserved(client: DaytonaBootClient, params: CreateSandboxFromImageParams | CreateSandboxFromSnapshotParams, options?: DaytonaObservedCreateOptions): Promise<Sandbox>;
334
+ /**
335
+ * Typed error for a snapshot that exists but could not be brought back to
336
+ * `active`. This is a final verdict, not a build trigger: the snapshot IS
337
+ * there, so rebuilding under the same name can only fail on the name conflict
338
+ * and then mask the real problem behind a slow direct image pull.
339
+ */
340
+ declare class DaytonaSnapshotActivationError extends Error {
341
+ /** The snapshot that would not activate. */
342
+ readonly snapshot: string;
343
+ constructor(snapshot: string, detail: string);
344
+ }
345
+ /**
346
+ * The slice of the Daytona client the reactivation path touches — structural,
347
+ * so the unit tests can drive it with a plain mock.
348
+ */
349
+ interface SnapshotActivationClient {
350
+ snapshot: {
351
+ get(name: string): Promise<{
352
+ state?: string;
353
+ }>;
354
+ activate(snapshot: unknown): Promise<{
355
+ state?: string;
356
+ }>;
357
+ };
358
+ }
359
+ /**
360
+ * Bring an exists-but-inactive snapshot back to `active`, or say loudly why
361
+ * not. Activation is asynchronous on Daytona's side — the activate call may
362
+ * answer with a transitional state (`pulling`) — so the result is polled until
363
+ * it lands on `active`, a terminal failure state, or the deadline.
364
+ */
365
+ declare function activateSnapshot(client: SnapshotActivationClient, name: string, snapshot: {
366
+ state?: string;
367
+ }, timing?: {
368
+ timeoutMs?: number;
369
+ pollMs?: number;
370
+ }): Promise<{
371
+ state?: string;
372
+ }>;
373
+ /**
374
+ * Typed error for a name race this run could not wait out: the winner never
375
+ * finished inside the budget, or the wait itself became impossible (rejected
376
+ * credentials, a control plane that stopped answering). Either way there is
377
+ * nothing to reuse and nothing has failed either — the only honest answer is
378
+ * to say so.
379
+ *
380
+ * Deliberately NOT a direct-pull trigger: a second copy of a build that is
381
+ * still running would double the spend on the same image and hide a provider
382
+ * incident behind a slow success.
383
+ */
384
+ declare class DaytonaSnapshotConflictError extends Error {
385
+ /** The contended snapshot name. */
386
+ readonly snapshot: string;
387
+ constructor(snapshot: string, detail: string);
388
+ }
389
+ /**
390
+ * The wait's answer when the NAME IS GONE. A healer (this one, or the same code
391
+ * in another process) can delete a dead snapshot while a second caller is
392
+ * waiting on it, and that waiter must not read the resulting 404s as the
393
+ * control plane failing — the thing it was waiting for cannot arrive, and the
394
+ * name is now free to build. A symbol rather than a state string so it can
395
+ * never collide with a state Daytona invents later.
396
+ */
397
+ declare const DAYTONA_SNAPSHOT_GONE: unique symbol;
398
+ /**
399
+ * The slice of the Daytona client the conflict wait touches — structural, so
400
+ * the unit tests can drive it with a plain mock.
401
+ */
402
+ interface SnapshotWaitClient {
403
+ snapshot: {
404
+ get(name: string): Promise<{
405
+ state?: string;
406
+ } | undefined>;
407
+ };
408
+ }
409
+ /**
410
+ * MAY THIS PROVIDER DELETE THE SNAPSHOT BEHIND THIS NAME? — our answer to the
411
+ * question Harbor answers with SnapshotPolicy.
412
+ *
413
+ * Harbor deletes an ERROR-state snapshot and rebuilds it under AUTO, and
414
+ * refuses loudly under EXPLICIT, where the name is one the USER supplied for a
415
+ * snapshot they manage
416
+ * (REFERENCES/Harbor/src/harbor/environments/daytona/snapshots.py:200-212).
417
+ * This provider has no policy flag, so the split has to be derived from the
418
+ * name itself — and there is a sharper criterion available than "who typed it":
419
+ * CAN WE PUT IT BACK?
420
+ *
421
+ * THE TEST IS ON THE RESOLVED IMAGE, NOT THE NAME, and that distinction is the
422
+ * whole of it. The build path creates a snapshot from
423
+ * `IMAGE_MAP[imageName] ?? imageName`, so the question "can we rebuild it" is a
424
+ * question about THAT ref, and Daytona will only build one carrying a real tag
425
+ * or digest. Testing the name instead got the platform's own legacy alias
426
+ * exactly wrong: `evolve-all` is an IMAGE_MAP key, which looked like proof we
427
+ * own it — but it resolves to the UNTAGGED `evolvingmachines/evolve-all`, which
428
+ * Daytona refuses to build. Delete would have succeeded, the rebuild would have
429
+ * been refused, and the name would be gone: the precise harm this predicate
430
+ * exists to prevent, committed on our own default. Reading the resolved ref
431
+ * also closes the blind spot a bare `includes("/")` had, since a registry path
432
+ * with no tag is just as unbuildable.
433
+ *
434
+ * When the ref does not qualify, the name belongs to a snapshot record that
435
+ * exists only in the user's account — built by their own tooling,
436
+ * unreproducible from here — and deleting it would throw away something nothing
437
+ * in this process could recreate. That is exactly the harm Harbor's EXPLICIT
438
+ * branch refuses, arrived at without a policy the caller must remember to set.
439
+ *
440
+ * Bare labels (`eval-env-<hash>`, `my-team-env`) are excluded and STILL HEALED,
441
+ * just not here: the layer that authored the name owns rebuilding it. The eval
442
+ * platform deletes and rebuilds its own `eval-env-*` aliases in its image
443
+ * preparation path (swarm_dashboard lib/evaluations/worker/templates.ts,
444
+ * commit 3f51b8d, merged to project-sable as 6ee52ed) — the same rule this
445
+ * function states, applied one floor up.
446
+ *
447
+ * A REFINEMENT DELIBERATELY NOT BUILT: the sharpest provenance seam available
448
+ * is `config.snapshotName` — a name the CALLER pinned is explicit by
449
+ * construction, where the derived default (`evolve-all-<version>`) is ours by
450
+ * construction. Threading that distinction from the constructor to here would
451
+ * beat any inference from the ref, and it is where this should go if the
452
+ * question is ever reopened. It is not built today because the resolved-ref
453
+ * test already refuses everything unbuildable, which is the harm that matters.
454
+ */
455
+ declare function providerCanRebuildSnapshot(imageName: string): boolean;
456
+ /**
457
+ * Does this snapshot-create failure mean "someone else already owns this
458
+ * name"? Harbor decides the same question on the same evidence — lowercased
459
+ * message contains "already exists" or "conflict"
460
+ * (REFERENCES/Harbor/src/harbor/environments/daytona/snapshots.py:281-283) —
461
+ * and this adds the HTTP status the TS SDK carries on its error objects, which
462
+ * the Python side does not surface as plainly.
463
+ *
464
+ * Kept to those signals on purpose: anything looser (a bare "409" anywhere in
465
+ * the text, say) would classify an unrelated build failure as a race and make
466
+ * this create wait ten minutes for a winner that does not exist.
467
+ */
468
+ declare function isSnapshotNameConflict(err: unknown): boolean;
469
+ /**
470
+ * Wait for the winner of a snapshot name race to finish, and report which way
471
+ * it went.
472
+ *
473
+ * WAIT-ON-CONFLICT, Harbor's law: when snapshot.create loses the name, the
474
+ * image is already being built by the process that won it, so the right move
475
+ * is to wait for that build and then use it — never to start a second, slower
476
+ * copy of the same work. Harbor does exactly this at
477
+ * REFERENCES/Harbor/src/harbor/environments/daytona/snapshots.py:281-288
478
+ * (conflict -> `_wait_for_active`), with the poll loop at :306-332.
479
+ *
480
+ * Four deliberate adaptations to this provider's idiom:
481
+ * - GET FIRST, then sleep. Harbor sleeps a full interval before its first
482
+ * look; most races here are lost to a build that finished seconds ago, and
483
+ * a wait that starts by looking costs nothing when the answer is already
484
+ * "active". Same loop shape as activateSnapshot() above.
485
+ * - A failed GET is not fatal, EXCEPT a not-found, which is an answer. The
486
+ * winner's snapshot can be briefly unreadable mid-build, so an ordinary
487
+ * poll error is logged and retried (Harbor warns and continues too,
488
+ * :326-327) — unlike the activation poll, where a failed GET IS the
489
+ * verdict. A 404 is different in kind: since this file gained a healer,
490
+ * the ordinary reason a contended name stops resolving is that another
491
+ * process cleared a corpse, so the FIRST not-found resolves the wait as
492
+ * DAYTONA_SNAPSHOT_GONE rather than counting toward the
493
+ * control-plane-is-down limit.
494
+ *
495
+ * THE COST OF THAT CHOICE, ACCEPTED KNOWINGLY: a TRANSIENT 404 during a
496
+ * legitimate build ends the wait early, and the caller builds a name whose
497
+ * real owner still holds it — one doomed create, then the existing
498
+ * fallback. Self-correcting, and cheap. Requiring two consecutive 404s
499
+ * would remove it, at the price of an extra poll interval on what is now
500
+ * the COMMON case; the heal is meant to be fast, so the rare doomed create
501
+ * is the better trade.
502
+ * - WAIT ONLY ON A WHITELIST of in-flight states, where Harbor waits on
503
+ * everything that is not ACTIVE or ERROR (:316-323). Harbor's shape leaves
504
+ * `inactive`, `removing` and any future state polling for the whole budget
505
+ * to no purpose; here anything outside DAYTONA_SNAPSHOT_IN_FLIGHT_STATES
506
+ * is returned as an answer for the caller to route.
507
+ * - A DEAD WINNER IS RETURNED, NOT RAISED, where Harbor raises
508
+ * SandboxBuildFailedError (:321-323). Harbor's caller is wrapped in an
509
+ * outer retry that owns the recovery; this provider's caller owns it
510
+ * directly, and its recovery is the direct image pull — so the state has
511
+ * to come back as data. What IS raised here is the opposite case: a wait
512
+ * that could not be completed at all (budget exhausted, credentials
513
+ * refused, control plane silent), because none of those leave a sane
514
+ * fallback.
515
+ *
516
+ * Returns the resolving snapshot record: `active` means reuse it, `inactive`
517
+ * means reuse it after reactivation, anything else means the winner's build is
518
+ * not going to produce a snapshot and the caller may fall back.
519
+ * Throws DaytonaSnapshotConflictError when the wait itself cannot finish.
520
+ */
521
+ declare function waitForSnapshotConflictWinner(client: SnapshotWaitClient, name: string, timing?: {
522
+ timeoutMs?: number;
523
+ pollMs?: number;
524
+ }): Promise<{
525
+ state?: string;
526
+ } | typeof DAYTONA_SNAPSHOT_GONE>;
527
+ /**
528
+ * Wrap command with cwd and envs shell prefixes, and (for user "root") a
529
+ * `sudo -n` elevation wrapper.
13
530
  *
14
531
  * Daytona's executeSessionCommand doesn't support cwd or envs natively,
15
532
  * so we inline them as shell commands. Values are single-quoted to handle
16
533
  * spaces and special characters safely.
534
+ *
535
+ * Daytona has no per-exec user switch: commands run as the container's OS
536
+ * user (image USER directive; default "daytona" with passwordless sudo).
537
+ * When the sandbox user is "root", the fully wrapped command is base64
538
+ * encoded and piped through `sudo -n bash` — base64 avoids escaping issues
539
+ * and `-n` fails fast (typed non-zero exit) instead of hanging on a
540
+ * password prompt if the image lacks passwordless sudo.
17
541
  */
18
- declare function wrapCommand(command: string, cwd?: string, envs?: Record<string, string>): string;
19
- /** @internal Test-only export for unit testing wrapCommand logic. */
20
- declare const _testWrapCommand: typeof wrapCommand;
542
+ /**
543
+ * Enforce the timeout INSIDE the box, on BOTH execution paths. Daytona has no
544
+ * fixed sandbox lifetime, and its auto-stop timer measures INACTIVITY over SDK
545
+ * interactions ("no new events ... state changes or interactions with the
546
+ * Sandbox through the sdk"), so it bounds nothing that matters here: while a
547
+ * client is polling, that polling is itself an interaction and resets the clock,
548
+ * and once the client is gone the box still runs for the whole remaining
549
+ * interval before it merely STOPS. e2b and Modal both take an absolute,
550
+ * server-side lifetime; Daytona takes none.
551
+ *
552
+ * The session API's timeout argument does not close the hole on either path.
553
+ * spawn launches with runAsync:true, where that argument bounds the *call*, not
554
+ * the process, and the only deadline is a client-side poll in wait(). run blocks
555
+ * with runAsync:false, where the argument is documented as how long to WAIT for
556
+ * the command — and it is this process that waits, so a wait that ends because
557
+ * this process died is not a kill.
558
+ *
559
+ * coreutils `timeout` closes it for both: the kernel kills the command whether
560
+ * or not anything is still watching, and exits 124 — the same code wait()'s own
561
+ * deadline and the e2b adapter return, so a timeout means one thing across every
562
+ * provider and both paths. The script is passed base64 -> file so no quoting of
563
+ * the caller's command is ever attempted, and a box without coreutils degrades
564
+ * to the un-timed run rather than failing outright (the client-side deadlines
565
+ * still cover the case where a client is alive to enforce them).
566
+ *
567
+ * IT FAILS CLOSED ON A BAD DECODE, and that is not a nicety. `>` creates the
568
+ * file before the pipeline runs, so on an image with no `base64` the decode
569
+ * fails and leaves a ZERO-BYTE script — and `bash <empty>` exits 0 having
570
+ * printed nothing. This wrapper would then have turned a command that used to
571
+ * fail loudly into one that reports success with empty output, on exactly the
572
+ * images where it breaks: the eval artifact listing is itself a
573
+ * `find ... | base64 -w0`, so a box missing base64 failed that listing outright
574
+ * before this wrapper existed, and would afterwards have returned an empty
575
+ * listing instead — no files, no patch, and a clean exit all the way up. So the
576
+ * decode is chained with `&&` and the script is size-checked before bash is
577
+ * handed it; either way out is exit 126, which is nonzero and therefore visible
578
+ * to every caller that checks.
579
+ */
580
+ declare function withInBoxTimeout(wrapped: string, timeoutSec?: number): string;
581
+ /**
582
+ * NOTHING IS APPENDED TO THE CALLER'S LAST LINE. The command goes inside a
583
+ * brace group whose closing brace opens a line of its own, so what follows is
584
+ * never read as a continuation of whatever the command ended with. Appending
585
+ * `; ...` directly broke three shapes, all measured:
586
+ *
587
+ * a command ending in a NEWLINE (every multi-line template literal) —
588
+ * `\n; __evolve_eos=$?` is a syntax error, exit 2
589
+ * a command ending in `&` — `& ;` is a syntax error and the command NEVER RAN
590
+ * a HEREDOC whose terminator is the command's last line — the appended text
591
+ * became part of the heredoc BODY: no error, exit 0, corrupt payload. That
592
+ * is the shape of the managed-secret proxy readiness probe (agent.ts), and
593
+ * it turned a healthy proxy into "failed to start".
594
+ *
595
+ * The group needs two guards of its own, both measured against /bin/sh,
596
+ * /bin/bash and /bin/dash:
597
+ *
598
+ * a leading `:` so the body is never EMPTY. A comment-only command
599
+ * (`# nothing`) otherwise leaves `{ # nothing\n}` — "syntax error near
600
+ * unexpected token `}'", exit 2, where the caller's own shell would have
601
+ * done nothing and exited 0. It runs BEFORE the command, so the status the
602
+ * group reports is still the command's own.
603
+ * a BLANK LINE before the closing brace, to be eaten by a command that ends
604
+ * in a dangling backslash. That backslash continues onto whatever line
605
+ * comes next; the blank line is what it consumes instead of the `}`, and
606
+ * `echo HI \` then runs as the caller's shell would run it — exit 0, "HI",
607
+ * with the exit code of a failing one (`false \` -> 1) still its own.
608
+ *
609
+ * WHAT STILL FAILS, LOUDLY: an UNTERMINATED heredoc (`cat <<'EOF'` with no
610
+ * EOF line). Its body swallows everything that follows, including the closing
611
+ * brace, so the command is a syntax error — exit 2, no output, the shell's own
612
+ * message on stderr. No text can fix that from out here: anything added lands
613
+ * inside the heredoc. A shell run of the same input without a wrapper prints
614
+ * the body and exits 0, so this is the one shape the sentinel changes, and it
615
+ * changes it to a loud failure rather than a quiet wrong answer.
616
+ */
617
+ declare function withEndOfOutputSentinel(command: string, token: string): string;
618
+ /**
619
+ * Shed the sentinel, and the terminator the transport put after it, from a
620
+ * settled stream.
621
+ *
622
+ * ONLY AT THE END, because that is the only place the sentinel can be: it is
623
+ * the last thing the command prints. A token anywhere else in the output is
624
+ * the caller's own bytes — `ps` shows this very command line, and `sh -x`
625
+ * traces the printf that writes it — and deleting those would be corruption,
626
+ * silent and impossible to debug.
627
+ */
628
+ declare function stripEndOfOutputSentinel(text: string, token: string): string;
629
+ /**
630
+ * A settled log read with the sentinel shed — from STDOUT only, because that is
631
+ * the only stream the command prints one to. Shedding anything from stderr
632
+ * could only ever delete bytes the command itself wrote (`sh -x` traces the
633
+ * printf that writes the token, and that trace goes to stderr).
634
+ */
635
+ declare function settledStreams(logs: unknown, token: string): {
636
+ stdout: string;
637
+ stderr: string;
638
+ };
639
+ /**
640
+ * Shed the sentinel from a stream still arriving, without holding back output
641
+ * that is merely on its way.
642
+ *
643
+ * Same rule as the settled read — only the end of the stream can be the
644
+ * sentinel — applied to bytes still coming: what could still GROW INTO it (the
645
+ * longest tail that is a prefix of `<token>\n`) waits for the rest, and
646
+ * everything else goes straight through, so a chunk that cannot be the start
647
+ * of a sentinel is never delayed. Nothing mid-stream is ever dropped.
648
+ */
649
+ declare function createSentinelFilter(token: string, emit: (chunk: string) => void): {
650
+ push(chunk: string): void;
651
+ /** The stream is over: what was held is the sentinel, or it was output after all. */
652
+ flush(): void;
653
+ };
654
+ declare function wrapCommand(command: string, cwd?: string, envs?: Record<string, string>, user?: string): string;
655
+ /** Daytona create() params derived from Evolve's provider-neutral network policy. */
656
+ interface DaytonaNetworkCreateParams {
657
+ networkBlockAll?: boolean;
658
+ networkAllowList?: string;
659
+ domainAllowList?: string;
660
+ }
661
+ /**
662
+ * Daytona `updateNetworkSettings()` params. Every field is stated on this
663
+ * path, never omitted — see mapNetworkPolicyForUpdate for why an omitted field
664
+ * is a stale field here, which is not true at create.
665
+ */
666
+ interface DaytonaNetworkUpdateParams {
667
+ networkBlockAll: boolean;
668
+ networkAllowList: string;
669
+ domainAllowList: string;
670
+ }
671
+ /**
672
+ * Map Evolve's provider-neutral network policy onto Daytona create() params
673
+ * (daytona.io/docs/en/network-limits).
674
+ *
675
+ * - outbound "open" (or no policy) → no restrictions
676
+ * - outbound "blocked", no allowlist → networkBlockAll: true (all egress dropped)
677
+ * - outbound "blocked" + IP/CIDR list → networkAllowList (comma-separated IPv4
678
+ * CIDRs; bare IPv4 gets /32; max 10 entries)
679
+ * - outbound "blocked" + hostname list → domainAllowList (comma-separated DNS
680
+ * names, forwarded VERBATIM — Daytona enforces the domain layer itself, so
681
+ * nothing is DNS-resolved or pinned here; `*.domain` prefix wildcards allow
682
+ * the base domain and its subdomains; max 20 entries)
683
+ *
684
+ * The two allowlists are mutually exclusive on Daytona's wire ("Set at most
685
+ * one non-empty value. Sending a conflicting combination returns a 400
686
+ * error"), so a policy mixing CIDR and hostname destinations is refused typed
687
+ * (mixed-allowlist) with the fix named, rather than sent to a guaranteed 400.
688
+ * Destinations no list can express — IPv6, host:port, malformed IPv4, a
689
+ * wildcard anywhere but the `*.` prefix, an empty/whitespace or
690
+ * comma-carrying entry, an over-cap list — throw
691
+ * DaytonaNetworkPolicyError; the policy is never silently weakened.
692
+ *
693
+ * PUBLIC EXPORT (a security seal, not a helper): external callers that build
694
+ * Daytona create() params themselves — the eval worker's declarative boot
695
+ * path is one — reuse this as the ONE opinion on how destinations become
696
+ * wire fields, rather than growing a driftable second mapper.
697
+ */
698
+ declare function mapNetworkPolicy(network?: SandboxCreateOptions["network"]): DaytonaNetworkCreateParams;
699
+ /**
700
+ * The same policy expressed for the RUNTIME switch
701
+ * (`sandbox.updateNetworkSettings`), which differs from create in one way that
702
+ * matters: it edits a box that already HAS a policy, so every field the new
703
+ * policy does not set must be cleared by hand or the old value survives.
704
+ *
705
+ * Daytona keeps two independent allowlists (`networkAllowList` for CIDRs,
706
+ * `domainAllowList` for names), and mapNetworkPolicy writes exactly one of
707
+ * them per policy — but the box may have been created elsewhere, and the OTHER
708
+ * allowlist left behind would silently widen a policy we believe is narrow.
709
+ * So the update always states BOTH: the one it means, and "" for the one it
710
+ * does not — which also keeps the wire on the docs' "at most one non-empty
711
+ * value" law.
712
+ *
713
+ * Upstream does exactly this, and only on the apply path: harbor
714
+ * daytona/environment.py:1313-1343 (`_network_kwargs(..., clear_public_allowlist=True)`,
715
+ * called from `_apply_network_policy` at :1508-1514) with the comment
716
+ * "Daytona treats block-all as authoritative over stored allowlists. Clear
717
+ * stale allowlist fields when reopening public access or switching between
718
+ * domain and CIDR allowlists."
719
+ */
720
+ declare function mapNetworkPolicyForUpdate(network?: SandboxCreateOptions["network"]): DaytonaNetworkUpdateParams;
721
+ /**
722
+ * Does this refusal mean "your organization may not set sandbox network
723
+ * policy" rather than "this request was malformed"?
724
+ *
725
+ * Deliberately narrow. A bare 403 is NOT enough on its own: the same status
726
+ * covers a revoked API key, and calling that a tier problem would send a
727
+ * caller to the billing page over a credential. The verdict needs the status
728
+ * AND language naming the tier/plan/permission for this specific capability —
729
+ * or, without a status, an unmistakable phrase. Anything else stays
730
+ * unclassified and propagates verbatim, which is the honest outcome for a
731
+ * refusal we do not recognize.
732
+ */
733
+ declare function isNetworkPolicyTierRefusal(err: unknown): boolean;
734
+ /**
735
+ * Registry host of an image reference, or undefined for Docker Hub images.
736
+ * A reference carries a registry host only when its first path segment
737
+ * contains "." or ":" or is "localhost" (Docker's own heuristic).
738
+ */
739
+ declare function imageRegistryHost(image: string): string | undefined;
740
+ /** Fields we read off a Daytona SDK sandbox to build a SandboxInfo. */
741
+ interface DaytonaSandboxLike {
742
+ id: string;
743
+ name?: string;
744
+ snapshot?: string;
745
+ labels?: Record<string, string>;
746
+ createdAt?: string;
747
+ }
748
+ /**
749
+ * Build a SandboxInfo from a Daytona sandbox entity. Timestamps come from
750
+ * the API's createdAt (never fabricated client-side; empty string when the
751
+ * API omits it). Daytona exposes no end timestamp, so endAt is always
752
+ * undefined.
753
+ */
754
+ declare function toSandboxInfo(sandbox: DaytonaSandboxLike): SandboxInfo;
755
+ /**
756
+ * Map a Daytona sandbox state onto Evolve's list() filter states.
757
+ * "started" → running; "stopped"/"archived"/"paused" → paused (our pause()
758
+ * stops the sandbox; Daytona 0.203.0 added a native "paused" state for
759
+ * pause-capable sandbox classes, and a box resting in it is paused by any
760
+ * reading); transitional and terminal states match no filter.
761
+ */
762
+ declare function daytonaStateToEvolveState(state?: string): "running" | "paused" | undefined;
21
763
  /** Result of a completed sandbox command */
22
764
  interface SandboxCommandResult {
23
765
  exitCode: number;
@@ -46,6 +788,7 @@ interface SandboxInfo {
46
788
  name?: string;
47
789
  metadata: Record<string, string>;
48
790
  startedAt: string;
791
+ /** End time (undefined for running sandboxes; Daytona never exposes one) */
49
792
  endAt?: string;
50
793
  }
51
794
  /** File or directory entry info */
@@ -74,6 +817,19 @@ interface SandboxResources {
74
817
  memory?: number;
75
818
  /** Disk in GB (default: 10) */
76
819
  disk?: number;
820
+ /**
821
+ * GPU reservation (count). Applied when a SNAPSHOT IS BUILT (Daytona
822
+ * allocates GPU at snapshot build; GPU machines are tier-gated on Daytona's
823
+ * side) — against an existing snapshot it throws DaytonaResourcesError like
824
+ * every other pinned sizing field.
825
+ */
826
+ gpu?: number;
827
+ /**
828
+ * Acceptable GPU types (Daytona's GpuType names, e.g. "H100", "H200",
829
+ * "RTX-PRO-6000"). Forwarded on the build path — Daytona validates the
830
+ * names server-side — and refused on an existing snapshot alongside `gpu`.
831
+ */
832
+ gpuTypes?: string[];
77
833
  }
78
834
  /** Options for creating a sandbox */
79
835
  interface SandboxCreateOptions {
@@ -81,16 +837,106 @@ interface SandboxCreateOptions {
81
837
  envs?: Record<string, string>;
82
838
  metadata?: Record<string, string>;
83
839
  timeoutMs?: number;
840
+ /**
841
+ * REJECTED with DaytonaIdleTimeoutError — not for want of an idle clock, but
842
+ * because auto-stop is the ONLY clock Daytona has and `timeoutMs` is already
843
+ * mapped onto it (autoStopInterval). Two options, one knob.
844
+ */
845
+ idleTimeoutMs?: number;
846
+ /**
847
+ * REJECTED with DaytonaBootCommandError. Daytona never runs an image
848
+ * entrypoint or any argv at boot, so a create-time boot command cannot be
849
+ * honored. The boot is already inert; exec the process after create.
850
+ */
851
+ bootCommand?: string[];
84
852
  workingDirectory?: string;
85
- /** Resource allocation for the sandbox */
853
+ /**
854
+ * Resource allocation (cpu cores, memory GiB, disk GiB), applied when a
855
+ * SNAPSHOT IS BUILT for the image (Daytona pins sizing on the snapshot).
856
+ * When the named snapshot ALREADY exists it cannot be resized at create —
857
+ * declaring resources then throws DaytonaResourcesError rather than
858
+ * silently ignoring them (pre-build a sizing-addressed snapshot instead).
859
+ */
86
860
  resources?: SandboxResources;
861
+ /**
862
+ * Provider-neutral outbound network policy
863
+ * (daytona.io/docs/en/network-limits). "blocked" with no
864
+ * allowedDestinations drops all egress. With allowedDestinations, the list
865
+ * maps onto exactly one of Daytona's two mutually exclusive allowlists:
866
+ * IPs/CIDRs → networkAllowList (IPv4 CIDRs, bare IPs sent as /32, max 10);
867
+ * hostnames and `*.domain` prefix wildcards → domainAllowList (max 20),
868
+ * forwarded VERBATIM as names — Daytona enforces the domain layer itself,
869
+ * nothing is DNS-resolved or pinned here. A list mixing CIDRs with
870
+ * hostnames, and any destination neither list can express — IPv6,
871
+ * host:port, malformed IPv4, a wildcard anywhere but the `*.` prefix, a
872
+ * blank or comma-carrying entry, an over-cap list — throws
873
+ * DaytonaNetworkPolicyError rather than silently weakening the policy.
874
+ * Note: on Daytona orgs below Tier 3, org network policy overrides
875
+ * per-sandbox settings server-side.
876
+ */
877
+ network?: {
878
+ outbound: "open" | "blocked";
879
+ allowedDestinations?: string[];
880
+ };
881
+ /**
882
+ * Every policy `updateNetwork()` may later be asked for on this box.
883
+ *
884
+ * ACCEPTED AND IGNORED HERE, deliberately. It exists because on modal the
885
+ * create call decides whether switching is possible at all, and a box built
886
+ * the blunt way can never be widened. Daytona has no such constraint — it
887
+ * switches freely at any time — so this changes nothing about the sandbox
888
+ * it creates.
889
+ *
890
+ * Declared anyway so the option means the SAME thing in every provider
891
+ * package: a caller reading these types must not conclude that Daytona
892
+ * cannot do phase switching because the option is missing here.
893
+ */
894
+ phaseNetworkPolicies?: Array<{
895
+ outbound: "open" | "blocked";
896
+ allowedDestinations?: string[];
897
+ }>;
898
+ /**
899
+ * OS user for the sandbox, applied at CREATE time (Daytona's osUser field)
900
+ * — Daytona has no per-exec user switch, so the user commands actually run
901
+ * as is governed by the sandbox image (USER directive; default Daytona
902
+ * images use "daytona" with passwordless sudo). A non-root value must
903
+ * exist in the image. Pass "root" to keep the image's default user and
904
+ * elevate every command through a `sudo -n` wrapper instead (requires
905
+ * passwordless sudo in the image; default images have it). File operations
906
+ * go through the Daytona daemon and are NOT elevated.
907
+ */
908
+ user?: string;
909
+ /** Home directory used by the SDK for agent config paths; not consumed by the provider. */
910
+ homeDir?: string;
87
911
  }
88
912
  /** Options for listing sandboxes */
89
913
  interface SandboxListOptions {
914
+ /** "running" matches Daytona "started"; "paused" matches "stopped"/"archived". */
90
915
  state?: ("running" | "paused")[];
91
916
  metadata?: Record<string, string>;
92
917
  limit?: number;
93
918
  }
919
+ /**
920
+ * A COMPLETE (or admittedly incomplete) enumeration of the organization's fleet.
921
+ *
922
+ * `complete` is the load-bearing field. Callers that need a whole fleet —
923
+ * orphan sweeps, lifecycle reconciliation — read a sandbox's ABSENCE from the
924
+ * list as evidence it is gone, so a truncated page and a small fleet must never
925
+ * be the same answer. `complete: false` means leave every row alone.
926
+ */
927
+ interface SandboxListPage {
928
+ sandboxes: SandboxInfo[];
929
+ complete: boolean;
930
+ /**
931
+ * Fetch units consumed: rows scanned from the cursor stream (before
932
+ * filtering). Cursor pagination has no page numbers to count — and the modal
933
+ * provider already reports item counts here — so "pages" reads as "units of
934
+ * enumeration work", kept under this name because the field is shared
935
+ * provider-neutral surface.
936
+ */
937
+ pagesFetched: number;
938
+ error?: string;
939
+ }
94
940
  /** Command execution capabilities */
95
941
  interface SandboxCommands {
96
942
  run(command: string, options?: SandboxRunOptions): Promise<SandboxCommandResult>;
@@ -106,6 +952,8 @@ interface SandboxFiles {
106
952
  path: string;
107
953
  data: string | Buffer | ArrayBuffer | Uint8Array;
108
954
  }>): Promise<void>;
955
+ /** Upload a local file by path, without buffering it whole */
956
+ writeFromPath(sandboxPath: string, localPath: string): Promise<void>;
109
957
  makeDir(path: string): Promise<void>;
110
958
  exists(path: string): Promise<boolean>;
111
959
  list(path: string): Promise<FileInfo[]>;
@@ -122,6 +970,8 @@ interface SandboxInstance {
122
970
  getInfo(): Promise<SandboxInfo>;
123
971
  kill(): Promise<void>;
124
972
  pause(): Promise<void>;
973
+ /** Replace the outbound network policy of the running sandbox. */
974
+ updateNetwork(network: SandboxCreateOptions["network"]): Promise<void>;
125
975
  }
126
976
  /** Sandbox lifecycle management */
127
977
  interface SandboxProvider {
@@ -129,7 +979,10 @@ interface SandboxProvider {
129
979
  readonly name?: string;
130
980
  create(options: SandboxCreateOptions): Promise<SandboxInstance>;
131
981
  connect(sandboxId: string, timeoutMs?: number): Promise<SandboxInstance>;
982
+ /** List sandboxes, paginating to exhaustion. `limit` bounds items returned. */
132
983
  list(options?: SandboxListOptions): Promise<SandboxInfo[]>;
984
+ /** The same enumeration for fleet bookkeeping: never throws, reports completeness. */
985
+ listAll(options?: SandboxListOptions): Promise<SandboxListPage>;
133
986
  }
134
987
  interface DaytonaConfig {
135
988
  /** Daytona API key. Default: reads from DAYTONA_API_KEY env var */
@@ -140,8 +993,22 @@ interface DaytonaConfig {
140
993
  target?: string;
141
994
  /** Default timeout in ms */
142
995
  defaultTimeoutMs?: number;
143
- /** Daytona snapshot name (default: 'evolve-all'). Create custom snapshots via `cd assets && ./build.sh daytona` */
996
+ /** Daytona snapshot name (default: 'evolve-all-<EVOLVE_IMAGE_VERSION>' direct, the platform's stable 'evolve-all' managed). Explicit names pass through untouched. Create custom snapshots via `cd assets && ./build.sh daytona` */
144
997
  snapshotName?: string;
998
+ /**
999
+ * Evolve-managed toolbox base URL. Setting it puts the provider in MANAGED
1000
+ * mode, where `apiKey` is an Evolve API key rather than a Daytona one and
1001
+ * both planes ride the Dashboard.
1002
+ *
1003
+ * One field rather than a separate `managed: true` because the two effects
1004
+ * have one cause: if the toolbox belongs to the platform, so do the
1005
+ * snapshots behind it, so managed creates name an existing platform snapshot
1006
+ * and never build one. Resolved by the Evolve SDK; direct/BYO callers leave
1007
+ * it unset.
1008
+ *
1009
+ * @internal
1010
+ */
1011
+ managedToolboxUrl?: string;
145
1012
  }
146
1013
  interface ResolvedDaytonaConfig {
147
1014
  apiKey: string;
@@ -149,6 +1016,180 @@ interface ResolvedDaytonaConfig {
149
1016
  target?: string;
150
1017
  defaultTimeoutMs?: number;
151
1018
  snapshotName?: string;
1019
+ managedToolboxUrl?: string;
1020
+ }
1021
+ /** What a managed sandbox's streaming log follow needs to reach the Dashboard. */
1022
+ interface ManagedStreamContext {
1023
+ toolboxUrl: string;
1024
+ apiKey: string;
1025
+ }
1026
+ /**
1027
+ * Split Daytona's multiplexed command log into stdout and stderr as the bytes
1028
+ * arrive.
1029
+ *
1030
+ * The wire format is one byte stream with 3-byte markers announcing which
1031
+ * stream the following bytes belong to (STDOUT_PREFIX_BYTES /
1032
+ * STDERR_PREFIX_BYTES, both exported by the Daytona SDK — this reads their
1033
+ * framing, it does not invent one). Two things make a streaming demux
1034
+ * different from the SDK's whole-buffer one: a marker can be split across two
1035
+ * chunks, so the last MAX_PREFIX_LEN-1 bytes are always held back rather than
1036
+ * emitted; and a multi-byte UTF-8 character can be split too, so each stream
1037
+ * keeps its own decoder in streaming mode.
1038
+ */
1039
+ declare function createLogDemuxer(onStdout: (chunk: string) => void, onStderr: (chunk: string) => void): {
1040
+ push(chunk: Uint8Array): void;
1041
+ flush(): void;
1042
+ };
1043
+ /**
1044
+ * Follow a session command's logs over plain HTTP.
1045
+ *
1046
+ * The Daytona SDK follows logs over a WEBSOCKET (Process.js:289 rewrites the
1047
+ * toolbox base to ws:// and opens a socket). A managed sandbox's toolbox base
1048
+ * is a Next.js route handler, and a Next route handler never sees a websocket
1049
+ * upgrade — the codebase's one websocket proxy lives in a separate custom
1050
+ * server for exactly that reason. So managed mode cannot use that transport.
1051
+ *
1052
+ * It does not have to. MEASURED 2026-07-26 against a live sandbox: the same
1053
+ * endpoint with `?follow=true` over ordinary HTTP answers 200 with
1054
+ * `transfer-encoding: chunked` and `content-type: application/octet-stream`,
1055
+ * and chunks arrive as the command produces them — 95 ms, 1045 ms, 2127 ms,
1056
+ * 2984 ms, 3976 ms for a command printing one line per second. That is real
1057
+ * streaming through a plain response body, which is what the Dashboard route
1058
+ * pipes through unbuffered.
1059
+ */
1060
+ declare function followManagedSessionLogs(context: ManagedStreamContext, sandboxId: string, sessionId: string, commandId: string, onStdout: (chunk: string) => void, onStderr: (chunk: string) => void, signal?: AbortSignal): Promise<void>;
1061
+ /**
1062
+ * Read a command's streams out of whatever shape Daytona returned.
1063
+ *
1064
+ * Session logs are USUALLY framed with per-stream markers, and the Daytona
1065
+ * SDK's demux returns "" for a stream whose marker never appears. An empty
1066
+ * string is not "this command printed nothing" — it means "this daemon build
1067
+ * did not frame the output" — and `??` does not fall through an empty string,
1068
+ * so reading `stdout ?? output` silently reports every such command as silent.
1069
+ *
1070
+ * Measured 2026-07-26 on a live sandbox booted from ubuntu:22.04: a command
1071
+ * printing one line to each stream came back as
1072
+ * {output: "hello-managed\noops\n", stdout: null, exitCode: 0} with no marker
1073
+ * bytes anywhere in the log body, and the provider reported exit 0 with empty
1074
+ * stdout. Unframed output goes to stdout, because that is what the combined
1075
+ * `output` field is; a framed response is untouched.
1076
+ */
1077
+ declare function readCommandStreams(source: {
1078
+ stdout?: string | null;
1079
+ stderr?: string | null;
1080
+ output?: string | null;
1081
+ }): {
1082
+ stdout: string;
1083
+ stderr: string;
1084
+ };
1085
+ declare class DaytonaCommands implements SandboxCommands {
1086
+ private sandbox;
1087
+ private user?;
1088
+ private managedStream?;
1089
+ /** The streamed-run clocks, shrinkable by a subclass so a test need not wait them out. */
1090
+ protected streamTimings: DaytonaStreamTimings;
1091
+ constructor(sandbox: Sandbox, user?: string | undefined, managedStream?: ManagedStreamContext | undefined);
1092
+ /**
1093
+ * Stream a command's output to callbacks. Direct mode uses the Daytona
1094
+ * SDK's own follow, which is a websocket; managed mode uses the HTTP
1095
+ * chunked follow, because a Dashboard route handler cannot terminate a
1096
+ * websocket upgrade (see followManagedSessionLogs).
1097
+ *
1098
+ * Only the managed follow takes the abandon signal — the SDK's websocket
1099
+ * follow exposes none, so a direct-mode stall is stopped from waiting on
1100
+ * but not closed; the ephemeral session's delete is what ends it.
1101
+ */
1102
+ private followLogs;
1103
+ run(command: string, options?: SandboxRunOptions): Promise<SandboxCommandResult>;
1104
+ /**
1105
+ * WHAT SAYS A STREAMED RUN IS OVER: the command's record, never the follow.
1106
+ *
1107
+ * A chunked follow can stall open long after its command exited — nothing
1108
+ * acks a response body — and awaiting it first, then polling for an exit
1109
+ * code 20 times at 500ms, gave a streaming run() two failure modes the
1110
+ * blocking path never had: a stalled socket hung run() forever (with no
1111
+ * timeoutMs, nothing in the box or out of it bounds the wait), and a follow
1112
+ * that closed a moment early threw "no exit code" on a command that had
1113
+ * already succeeded.
1114
+ *
1115
+ * So the poll and the follow run TOGETHER. The poll decides when the run
1116
+ * ended; the follow then gets as long as it keeps delivering and is cut
1117
+ * only once it has been SILENT for the drain window, so a live stream is
1118
+ * never truncated and a dead one is never waited on. Two ceilings bound the
1119
+ * wait: the caller's timeoutMs widened by the in-box kill grace, and — for
1120
+ * the caller who passed none — the settle bound measured from the moment
1121
+ * the follow closed, because a closed stream means the command ended and a
1122
+ * record that never catches up is a provider incident, not a long run.
1123
+ * A run with no timeoutMs whose command is genuinely still streaming is
1124
+ * still waited on indefinitely: that is what asking for no bound means.
1125
+ */
1126
+ private awaitStreamedExit;
1127
+ spawn(command: string, options?: SandboxSpawnOptions): Promise<SandboxCommandHandle>;
1128
+ list(): Promise<ProcessInfo[]>;
1129
+ kill(processId: string): Promise<boolean>;
1130
+ }
1131
+ declare class DaytonaFiles implements SandboxFiles {
1132
+ private sandbox;
1133
+ constructor(sandbox: Sandbox);
1134
+ read(path: string): Promise<string | Uint8Array>;
1135
+ write(path: string, content: string | Buffer | ArrayBuffer | Uint8Array): Promise<void>;
1136
+ writeBatch(files: Array<{
1137
+ path: string;
1138
+ data: string | Buffer | ArrayBuffer | Uint8Array;
1139
+ }>): Promise<void>;
1140
+ /**
1141
+ * Upload a local file by PATH. Daytona's own uploadFile has a local-path
1142
+ * overload (FileSystem.d.ts: `uploadFile(localPath: string, remotePath:
1143
+ * string, timeout?)`), so the bytes never pass through this process's heap —
1144
+ * which is what makes a large artifact safe to upload under concurrency.
1145
+ */
1146
+ writeFromPath(sandboxPath: string, localPath: string): Promise<void>;
1147
+ makeDir(path: string): Promise<void>;
1148
+ exists(path: string): Promise<boolean>;
1149
+ list(path: string): Promise<FileInfo[]>;
1150
+ remove(path: string): Promise<void>;
1151
+ rename(oldPath: string, newPath: string): Promise<void>;
1152
+ }
1153
+ declare class DaytonaSandboxImpl implements SandboxInstance {
1154
+ private sandbox;
1155
+ readonly commands: SandboxCommands;
1156
+ readonly files: SandboxFiles;
1157
+ constructor(sandbox: Sandbox, user?: string, managedStream?: ManagedStreamContext);
1158
+ get sandboxId(): string;
1159
+ getHost(port: number): Promise<string>;
1160
+ isRunning(): Promise<boolean>;
1161
+ getInfo(): Promise<SandboxInfo>;
1162
+ /**
1163
+ * Replace the running sandbox's outbound policy — Daytona's
1164
+ * `sandbox.updateNetworkSettings`, which "maps to the same mechanism as
1165
+ * creating a sandbox with `networkBlockAll` / `networkAllowList` /
1166
+ * `domainAllowList`: the runner applies iptables rules to the sandbox
1167
+ * container" (@daytonaio/sdk@0.203.0 Sandbox.d.ts:495-511).
1168
+ *
1169
+ * TIER GATE, surfaced and never swallowed. Daytona allows sandbox-level
1170
+ * network policy only on the higher plan tiers — "Organizations on Tier 1 or
1171
+ * Tier 2 cannot override network policy at the sandbox level". Upstream does
1172
+ * not model this: its validation passes and the runtime call then fails, so
1173
+ * a task that declares a phase switch looks supported right up to the moment
1174
+ * the agent is already running. Here the refusal becomes
1175
+ * DaytonaNetworkPolicyError("org-tier-forbidden") — a typed answer a caller
1176
+ * can act on, and never a quiet return, because a switch that silently does
1177
+ * nothing leaves the agent running under the WRONG policy with no signal
1178
+ * that anything went wrong.
1179
+ *
1180
+ * Refusals that do not match the tier signature propagate untouched: a
1181
+ * revoked key and a plan limit both come back 403, and mislabelling the
1182
+ * first as the second sends the caller to the billing page over a
1183
+ * credential.
1184
+ *
1185
+ * Hostname destinations forward VERBATIM into domainAllowList, exactly as
1186
+ * at create — nothing is DNS-resolved or pinned, Daytona enforces the
1187
+ * domain layer itself — so create and switch express the same policy the
1188
+ * same way.
1189
+ */
1190
+ updateNetwork(network: SandboxCreateOptions["network"]): Promise<void>;
1191
+ kill(): Promise<void>;
1192
+ pause(): Promise<void>;
152
1193
  }
153
1194
  declare class DaytonaProvider implements SandboxProvider {
154
1195
  readonly providerType: "daytona";
@@ -156,11 +1197,138 @@ declare class DaytonaProvider implements SandboxProvider {
156
1197
  private readonly client;
157
1198
  private readonly defaultTimeoutMs;
158
1199
  private readonly snapshotName;
1200
+ /**
1201
+ * Sandbox user configured at create time, reapplied on connect() so the
1202
+ * root sudo wrapper keeps applying. In-memory only: a connect() from a
1203
+ * fresh process falls back to the sandbox's create-time OS user without
1204
+ * the wrapper — callers reconnecting across processes must recreate the
1205
+ * provider and sandbox with the same user, or root-only operations fail
1206
+ * loudly.
1207
+ */
1208
+ private readonly sandboxUsers;
1209
+ /** Set only in Evolve-managed mode; see the EVOLVE-MANAGED MODE section. */
1210
+ private readonly managedStream?;
1211
+ /**
1212
+ * Clocks of the WAIT-ON-CONFLICT path (see waitForSnapshotConflictWinner).
1213
+ * A field rather than a constant so tests can drive the shape of the wait
1214
+ * without spending its production budget, the same way streamTimings does.
1215
+ */
1216
+ protected snapshotConflictTiming: {
1217
+ timeoutMs: number;
1218
+ pollMs: number;
1219
+ };
1220
+ /**
1221
+ * Clocks of the DELETE-CONFIRMATION poll (see deleteDeadSnapshot). Separate
1222
+ * from snapshotConflictTiming because the two wait for different things on
1223
+ * different scales: the conflict clock waits out another process's IMAGE
1224
+ * BUILD, which legitimately takes minutes, while this one waits for a record
1225
+ * to stop resolving after Daytona acknowledged its deletion — seconds of
1226
+ * bookkeeping. Handing the delete poll the conflict budget let the fast path
1227
+ * block for ten minutes on a lingering corpse and left this function's own
1228
+ * constants unreachable.
1229
+ */
1230
+ protected snapshotDeleteTiming: {
1231
+ timeoutMs: number;
1232
+ pollMs: number;
1233
+ };
159
1234
  constructor(config: ResolvedDaytonaConfig);
160
1235
  create(options: SandboxCreateOptions): Promise<SandboxInstance>;
1236
+ /**
1237
+ * Join a snapshot build another process is already running, and leave the
1238
+ * name usable — or say why it is not.
1239
+ *
1240
+ * Returns FALSE when the snapshot can be used by name as it stands, and TRUE
1241
+ * when the name was found dead, deleted, and now needs building: the caller
1242
+ * runs the build it was waiting for. That is Harbor's _SnapshotNeedsCreate
1243
+ * signal, which its resolve raises after deleting an ERROR-state snapshot
1244
+ * (REFERENCES/Harbor/src/harbor/environments/daytona/snapshots.py:200-212).
1245
+ *
1246
+ * DELIBERATELY STRICTER THAN UPSTREAM ON ONE POINT. Harbor raises
1247
+ * _SnapshotNeedsCreate whether or not its delete succeeded — _delete_snapshot
1248
+ * logs a failure and returns, and the caller creates regardless (:213-224).
1249
+ * This returns NEEDS-CREATE only when the name is CONFIRMED clear. A create
1250
+ * fired over a corpse that is still there loses the name and lands back in
1251
+ * the conflict wait, which is a slower way of reaching the same fallback with
1252
+ * one wasted round trip; when the name is genuinely gone, the create is the
1253
+ * whole point. Swallowing a FAILED delete, by contrast, is not a divergence
1254
+ * at all — that matches Harbor exactly.
1255
+ *
1256
+ * Throws a plain Error when the in-flight build produced nothing usable AND
1257
+ * the name could not be cleared, which is the ONE case create()'s direct
1258
+ * image pull is still right: nobody is going to produce this snapshot, so
1259
+ * waiting longer buys nothing. The typed errors (conflict, activation) pass
1260
+ * straight through create()'s fallback as final verdicts.
1261
+ */
1262
+ private joinInFlightSnapshotBuild;
161
1263
  connect(sandboxId: string, _timeoutMs?: number): Promise<SandboxInstance>;
1264
+ /**
1265
+ * List sandboxes, walking the cursor stream to exhaustion.
1266
+ *
1267
+ * An early version requested page 1 and stopped, discarding the rest — an
1268
+ * organization with more than one page of sandboxes was silently truncated,
1269
+ * and nothing in the return value said so. For any caller that reads absence
1270
+ * from the list as "this sandbox is gone", that is a correctness bug. Cursor
1271
+ * pagination (mandatory since Daytona retired page numbers on 2026-06-25)
1272
+ * changes the mechanics, not the law: the walk still runs to the end or says
1273
+ * it could not.
1274
+ *
1275
+ * ORDER OF OPERATIONS, because it is observable: `limit` bounds the
1276
+ * sandboxes RETURNED, and the state filter runs client-side on every row
1277
+ * BEFORE the limit is counted. Asking for 10 running sandboxes therefore
1278
+ * keeps walking until ten running ones have been found, rather than
1279
+ * filtering ten arbitrary rows down to whatever survives.
1280
+ */
162
1281
  list(options?: SandboxListOptions): Promise<SandboxInfo[]>;
1282
+ /**
1283
+ * The fleet-bookkeeping enumeration: same walk, never throws.
1284
+ *
1285
+ * The distinction from `list()` is what a failure MEANS to the caller. An
1286
+ * orphan sweep treats a missing sandbox as a terminated one, so it must be
1287
+ * able to tell "the organization has no sandboxes" from "the enumeration
1288
+ * stopped early".
1289
+ */
1290
+ listAll(options?: SandboxListOptions): Promise<SandboxListPage>;
1291
+ private paginate;
1292
+ /**
1293
+ * The cursor stream, asking the API to narrow server-side where it can.
1294
+ *
1295
+ * Since 0.203.0 the SDK's public `list(query)` takes `states` and `labels`
1296
+ * directly and pages by cursor internally (items + nextCursor,
1297
+ * esm/Daytona.js:430-476), so the old reach-around through the private
1298
+ * `sandboxApi.listSandboxesPaginated` field is gone along with the method
1299
+ * itself. The client-side filter in the walk stays the authority regardless:
1300
+ * a server filter that silently stopped applying must never be able to admit
1301
+ * a state the caller excluded.
1302
+ */
1303
+ private listStream;
163
1304
  }
1305
+ /**
1306
+ * Walk the cursor stream into one answer, with an honest completeness verdict.
1307
+ *
1308
+ * Separate from the provider because everything worth getting wrong lives here
1309
+ * and none of it needs a network: the ways a walk can fail to terminate, the
1310
+ * difference between "the caller asked for ten" and "the provider ran out",
1311
+ * and the rule that a failure mid-walk yields the sandboxes seen so far marked
1312
+ * INCOMPLETE rather than either an exception or a short complete list.
1313
+ *
1314
+ * WHAT CURSORS CHANGED HERE, and what they did not. The vendor's iterator ends
1315
+ * itself when nextCursor runs out, so "exhausted" is now the stream ending
1316
+ * rather than a totalPages comparison; a fetch failure mid-walk surfaces as
1317
+ * the iterator throwing. The LAWS are unchanged: a walk that could not finish
1318
+ * reports `complete: false` with what it saw, a caller's limit is reported as
1319
+ * truncation whenever one more row provably exists (the row after the limit is
1320
+ * that proof — the walk holds it, so no extra fetch is spent on it beyond, at
1321
+ * worst, the one the iterator was already making), and a runaway fleet stops
1322
+ * at the scan ceiling as a refusal, never as a short list that reads complete.
1323
+ *
1324
+ * Exported for its test (`_testCollectSandboxStream`).
1325
+ */
1326
+ declare function collectSandboxStream(stream: AsyncIterable<DaytonaSandboxLike & {
1327
+ state?: string;
1328
+ }>, options?: SandboxListOptions): Promise<SandboxListPage & {
1329
+ stoppedAtLimit: boolean;
1330
+ }>;
1331
+ declare const _testCollectSandboxStream: typeof collectSandboxStream;
164
1332
  /**
165
1333
  * Create Daytona sandbox provider.
166
1334
  *
@@ -168,5 +1336,36 @@ declare class DaytonaProvider implements SandboxProvider {
168
1336
  * @throws Error if apiKey cannot be resolved
169
1337
  */
170
1338
  declare function createDaytonaProvider(config?: DaytonaConfig): SandboxProvider;
1339
+ /** @internal Test-only export for unit testing wrapCommand logic. */
1340
+ declare const _testWrapCommand: typeof wrapCommand;
1341
+ declare const _testWithInBoxTimeout: typeof withInBoxTimeout;
1342
+ /** @deprecated The create-side mapper is public API now — import
1343
+ * `mapNetworkPolicy` directly; this alias stays for existing tests only. */
1344
+ declare const _testMapNetworkPolicy: typeof mapNetworkPolicy;
1345
+ declare const _testMapNetworkPolicyForUpdate: typeof mapNetworkPolicyForUpdate;
1346
+ declare const _testIsNetworkPolicyTierRefusal: typeof isNetworkPolicyTierRefusal;
1347
+ declare const _testImageRegistryHost: typeof imageRegistryHost;
1348
+ declare const _testToSandboxInfo: typeof toSandboxInfo;
1349
+ declare const _testDaytonaStateToEvolveState: typeof daytonaStateToEvolveState;
1350
+ declare const _testActivateSnapshot: typeof activateSnapshot;
1351
+ declare const _testWaitForSnapshotConflictWinner: typeof waitForSnapshotConflictWinner;
1352
+ declare const _testIsSnapshotNameConflict: typeof isSnapshotNameConflict;
1353
+ declare const _testProviderCanRebuildSnapshot: typeof providerCanRebuildSnapshot;
1354
+ declare const _testImageMap: Record<string, string>;
1355
+ declare const _testWithEndOfOutputSentinel: typeof withEndOfOutputSentinel;
1356
+ declare const _testStripEndOfOutputSentinel: typeof stripEndOfOutputSentinel;
1357
+ declare const _testSettledStreams: typeof settledStreams;
1358
+ declare const _testCreateSentinelFilter: typeof createSentinelFilter;
1359
+ declare const _testCreateLogDemuxer: typeof createLogDemuxer;
1360
+ declare const _testFollowManagedSessionLogs: typeof followManagedSessionLogs;
1361
+ declare const _testReadCommandStreams: typeof readCommandStreams;
1362
+ /**
1363
+ * TYPE-ONLY handle on the concrete sandbox class, for the contract-conformance
1364
+ * seam. create() is declared to return the local SandboxInstance INTERFACE, so
1365
+ * a seam reading create()'s return type checks the interface and never the
1366
+ * class — which let a narrowed method on the class pass unnoticed. Exporting
1367
+ * the type (never the constructor) gives the seam the real methods to pin.
1368
+ */
1369
+ type _testDaytonaSandboxImpl = DaytonaSandboxImpl;
171
1370
 
172
- export { type DaytonaConfig, DaytonaProvider, type FileInfo, type ProcessInfo, type SandboxCommandHandle, type SandboxCommandResult, type SandboxCommands, type SandboxCreateOptions, type SandboxFiles, type SandboxInfo, type SandboxInstance, type SandboxListOptions, type SandboxProvider, type SandboxResources, type SandboxRunOptions, type SandboxSpawnOptions, _testWrapCommand, createDaytonaProvider };
1371
+ export { DAYTONA_AUTO_DELETE_GRACE_MINUTES, DAYTONA_BOOT_LABEL, DAYTONA_BOOT_POLL_MS, DAYTONA_LIST_PAGE_SIZE, DAYTONA_MAX_DOMAIN_ALLOWLIST, DAYTONA_MAX_LIST_SCAN, DAYTONA_MAX_NETWORK_ALLOWLIST, DAYTONA_SNAPSHOT_ACTIVATE_TIMEOUT_MS, DAYTONA_SNAPSHOT_CONFLICT_TIMEOUT_MS, DAYTONA_SNAPSHOT_GONE, DAYTONA_STREAM_TIMINGS, DaytonaBootAbortedError, type DaytonaBootClient, DaytonaBootCommandError, type DaytonaBootPhase, type DaytonaBootProgress, DaytonaCommands, type DaytonaConfig, DaytonaFiles, DaytonaGpuTypeError, DaytonaIdleTimeoutError, DaytonaImagePullError, DaytonaNetworkPolicyError, type DaytonaNetworkPolicyReason, type DaytonaObservedCreateOptions, DaytonaProvider, DaytonaResourcesError, DaytonaSnapshotActivationError, DaytonaSnapshotConflictError, type DaytonaStreamTimings, EVOLVE_IMAGE_VERSION, type FileInfo, type ProcessInfo, type SandboxCommandHandle, type SandboxCommandResult, type SandboxCommands, type SandboxCreateOptions, type SandboxFiles, type SandboxInfo, type SandboxInstance, type SandboxListOptions, type SandboxListPage, type SandboxProvider, type SandboxResources, type SandboxRunOptions, type SandboxSpawnOptions, _testActivateSnapshot, _testCollectSandboxStream, _testCreateLogDemuxer, _testCreateSentinelFilter, type _testDaytonaSandboxImpl, _testDaytonaStateToEvolveState, _testFollowManagedSessionLogs, _testImageMap, _testImageRegistryHost, _testIsNetworkPolicyTierRefusal, _testIsSnapshotNameConflict, _testMapNetworkPolicy, _testMapNetworkPolicyForUpdate, _testProviderCanRebuildSnapshot, _testReadCommandStreams, _testSettledStreams, _testStripEndOfOutputSentinel, _testToSandboxInfo, _testWaitForSnapshotConflictWinner, _testWithEndOfOutputSentinel, _testWithInBoxTimeout, _testWrapCommand, createDaytonaProvider, createSandboxObserved, daytonaBootPhaseOf, mapNetworkPolicy };