@evolvingmachines/daytona 0.0.55 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.cjs +8 -2
- package/dist/index.d.cts +1207 -8
- package/dist/index.d.ts +1207 -8
- package/dist/index.js +8 -2
- package/package.json +4 -4
package/dist/index.d.cts
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
/**
|
|
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 };
|