@ekairos/sandbox 1.22.4-beta.feature-core-thread-registry-sync.0 → 1.22.5-beta.development.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -464
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +15 -1
- package/dist/schema.js.map +1 -1
- package/dist/service.d.ts +20 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +567 -36
- package/dist/service.js.map +1 -1
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,492 +1,57 @@
|
|
|
1
1
|
# @ekairos/sandbox
|
|
2
2
|
|
|
3
|
-
Provider-agnostic
|
|
4
|
-
This package is **independent** (no workflow runtime dependency). Other packages (e.g. `@ekairos/dataset`, `@ekairos/structure`)
|
|
5
|
-
may depend on it, but `@ekairos/sandbox` does not depend on any workflow framework.
|
|
3
|
+
Provider-agnostic sandbox service with durable sandbox ids stored in InstantDB.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## What it does
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
- creates sandboxes and persists them in `sandbox_sandboxes`
|
|
8
|
+
- reconnects by durable `sandboxId`
|
|
9
|
+
- runs commands
|
|
10
|
+
- reads and writes files
|
|
11
|
+
- supports multiple providers behind one API
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- Create and reconnect sandboxes by a durable `sandboxId`.
|
|
13
|
-
- Run commands and read/write files in a consistent way.
|
|
14
|
-
- Persist sandbox metadata in InstantDB for continuity across runs.
|
|
15
|
-
- Support multiple providers (Vercel, Daytona, etc.) without changing callers.
|
|
13
|
+
## Main APIs
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
- `sandboxDomain`
|
|
16
|
+
- `SandboxService`
|
|
17
|
+
- `createVercelSandbox(...)`
|
|
18
|
+
- `runCommandInSandbox(...)`
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pnpm add @ekairos/sandbox
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Environment variables
|
|
28
|
-
|
|
29
|
-
### InstantDB (admin)
|
|
30
|
-
You must provide an InstantDB **admin** database client so sandbox actions can persist and query sandbox records.
|
|
31
|
-
|
|
32
|
-
### Provider selection
|
|
33
|
-
|
|
34
|
-
By default, provider is `sprites` unless overridden.
|
|
35
|
-
|
|
36
|
-
You can set:
|
|
37
|
-
- `SANDBOX_PROVIDER=sprites` or `SANDBOX_PROVIDER=daytona` or `SANDBOX_PROVIDER=vercel`
|
|
38
|
-
- or pass `provider` in `SandboxConfig`
|
|
39
|
-
|
|
40
|
-
### Sprites.dev
|
|
41
|
-
|
|
42
|
-
- `SPRITES_API_TOKEN` (or `SPRITE_TOKEN`) - required
|
|
43
|
-
- Optional:
|
|
44
|
-
- `SPRITES_API_BASE_URL` / `SPRITES_API_URL` (default: `https://api.sprites.dev`)
|
|
45
|
-
|
|
46
|
-
### Daytona (current)
|
|
47
|
-
|
|
48
|
-
`@daytonaio/sdk` supports these env vars:
|
|
49
|
-
|
|
50
|
-
- `DAYTONA_API_URL` (required) - Daytona API base URL
|
|
51
|
-
- `DAYTONA_API_KEY` (required if not using JWT)
|
|
52
|
-
- `DAYTONA_JWT_TOKEN` + `DAYTONA_ORGANIZATION_ID` (optional auth mode)
|
|
53
|
-
- `DAYTONA_TARGET` (optional)
|
|
54
|
-
- `SANDBOX_DAYTONA_EPHEMERAL` (optional) - default ephemeral for Daytona sandboxes (true unless set to 0/false)
|
|
55
|
-
|
|
56
|
-
### Vercel
|
|
57
|
-
|
|
58
|
-
- `SANDBOX_VERCEL_TEAM_ID`
|
|
59
|
-
- `SANDBOX_VERCEL_PROJECT_ID`
|
|
60
|
-
- `SANDBOX_VERCEL_TOKEN`
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Data model (InstantDB)
|
|
65
|
-
|
|
66
|
-
### `sandboxDomain`
|
|
67
|
-
Defines a single entity:
|
|
68
|
-
|
|
69
|
-
- `sandbox_sandboxes`
|
|
70
|
-
|
|
71
|
-
Fields (high-level):
|
|
72
|
-
- `externalSandboxId` (string, indexed): provider sandbox id (Vercel `sandbox.sandboxId`, Daytona `sandbox.id`)
|
|
73
|
-
- `provider` (string, indexed): e.g. `"vercel"`, `"daytona"`
|
|
74
|
-
- `sandboxUrl` (string, optional): optional URL metadata
|
|
75
|
-
- `status` (string, indexed): `"creating" | "active" | "shutdown" | "error" | ...`
|
|
76
|
-
- `timeout` (number, optional): milliseconds
|
|
77
|
-
- `runtime` (string, optional): `"node22" | "python3" | ...`
|
|
78
|
-
- `vcpus` (number, optional)
|
|
79
|
-
- `ports` (json, optional): array of ports
|
|
80
|
-
- `purpose` (string, optional, indexed)
|
|
81
|
-
- `params` (json, optional)
|
|
82
|
-
- timestamps: `createdAt`, `updatedAt`, `shutdownAt`
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## ID semantics (important)
|
|
87
|
-
|
|
88
|
-
There are two ids involved:
|
|
89
|
-
|
|
90
|
-
### 1) `sandboxId` (internal, durable handle)
|
|
91
|
-
- The **InstantDB record id**: `sandbox_sandboxes[sandboxId]`
|
|
92
|
-
- This is the id you store in durable state and pass around.
|
|
93
|
-
- All sandbox actions and helpers take this `sandboxId`.
|
|
94
|
-
|
|
95
|
-
### 2) `externalSandboxId` (provider id)
|
|
96
|
-
- The id returned by the provider SDK (Vercel `sandbox.sandboxId`, Daytona `sandbox.id`)
|
|
97
|
-
- Stored on the record as `externalSandboxId`
|
|
98
|
-
- Used internally to reconnect via the provider SDK
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Schema integration
|
|
103
|
-
|
|
104
|
-
In your app schema, include `sandboxDomain` along with other domains, then initialize Instant with the composed schema.
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
import { domain } from "@ekairos/domain";
|
|
108
|
-
import { sandboxDomain } from "@ekairos/sandbox";
|
|
109
|
-
// import { storyDomain } from "@ekairos/story" (if you use stories)
|
|
110
|
-
// import { datasetDomain } from "@ekairos/dataset" (if you use dataset)
|
|
111
|
-
|
|
112
|
-
export const appDomain = domain("app")
|
|
113
|
-
.includes(sandboxDomain)
|
|
114
|
-
// .includes(storyDomain)
|
|
115
|
-
// .includes(datasetDomain)
|
|
116
|
-
.schema({
|
|
117
|
-
entities: {},
|
|
118
|
-
links: {},
|
|
119
|
-
rooms: {},
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
export const schema = appDomain.toInstantSchema();
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Exports
|
|
20
|
+
## Providers
|
|
128
21
|
|
|
129
|
-
- `
|
|
130
|
-
- `
|
|
131
|
-
- `
|
|
132
|
-
- Types: `SandboxConfig`, `SandboxRecord`, `ServiceResult<T>`, `CommandResult`
|
|
22
|
+
- `vercel`
|
|
23
|
+
- `daytona`
|
|
24
|
+
- `sprites`
|
|
133
25
|
|
|
134
|
-
|
|
26
|
+
Provider selection:
|
|
135
27
|
|
|
136
|
-
|
|
28
|
+
1. `config.provider`
|
|
29
|
+
2. `SANDBOX_PROVIDER`
|
|
30
|
+
3. default provider
|
|
137
31
|
|
|
138
|
-
|
|
32
|
+
## Quick example
|
|
139
33
|
|
|
140
34
|
```ts
|
|
141
|
-
|
|
142
|
-
import { sandboxDomain } from "@ekairos/sandbox";
|
|
143
|
-
|
|
144
|
-
const db = init({
|
|
145
|
-
appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID,
|
|
146
|
-
adminToken: process.env.INSTANT_APP_ADMIN_TOKEN,
|
|
147
|
-
schema: {}, // your composed schema that includes sandboxDomain
|
|
148
|
-
});
|
|
35
|
+
const service = new SandboxService(db);
|
|
149
36
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
### 2) Create a persisted sandbox
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
const created = await sandboxes.createSandbox({
|
|
157
|
-
provider: "daytona", // or set SANDBOX_PROVIDER=daytona
|
|
37
|
+
const created = await service.createSandbox({
|
|
38
|
+
provider: "vercel",
|
|
158
39
|
runtime: "node22",
|
|
159
40
|
timeoutMs: 10 * 60 * 1000,
|
|
160
|
-
resources: { vcpus: 2 },
|
|
161
|
-
purpose: "dataset-file-parse",
|
|
162
|
-
params: { datasetId: "..." },
|
|
163
41
|
});
|
|
164
42
|
|
|
165
43
|
if (!created.ok) throw new Error(created.error);
|
|
166
|
-
const { sandboxId } = created.data;
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### 3) Run commands by `sandboxId`
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
const sandbox = sandboxes.getSandbox(sandboxId);
|
|
173
|
-
const res = await sandbox.runCommand("node", ["-e", "console.log('hello')"]);
|
|
174
|
-
if (!res.ok) throw new Error(res.error);
|
|
175
|
-
console.log(res.data.exitCode, res.data.output);
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### 4) Reconnect (when you need the runtime object)
|
|
179
44
|
|
|
180
|
-
|
|
181
|
-
const sandbox = sandboxes.getSandbox(sandboxId);
|
|
182
|
-
const rec = await sandbox.reconnect();
|
|
183
|
-
if (!rec.ok) throw new Error(rec.error);
|
|
184
|
-
|
|
185
|
-
const { sandbox: providerSandbox } = rec.data;
|
|
186
|
-
// Use provider-specific SDK features on providerSandbox
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### 5) Stop (optional)
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
const sandbox = sandboxes.getSandbox(sandboxId);
|
|
193
|
-
await sandbox.stop();
|
|
45
|
+
const run = await service.runCommand(created.data.sandboxId, "node", ["-v"]);
|
|
194
46
|
```
|
|
195
47
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
## Provider selection rules
|
|
199
|
-
|
|
200
|
-
Precedence:
|
|
201
|
-
1) `SandboxConfig.provider`
|
|
202
|
-
2) `SANDBOX_PROVIDER`
|
|
203
|
-
3) default: `sprites`
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## Daytona provider details
|
|
48
|
+
## Important ids
|
|
208
49
|
|
|
209
|
-
|
|
50
|
+
- `sandboxId`: durable InstantDB record id
|
|
51
|
+
- `externalSandboxId`: provider-native sandbox id
|
|
210
52
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
{
|
|
215
|
-
provider: "daytona",
|
|
216
|
-
runtime: "node22", // used to infer language when daytona.language not set
|
|
217
|
-
daytona: {
|
|
218
|
-
language: "typescript" | "javascript" | "python",
|
|
219
|
-
snapshot: "snapshot-id",
|
|
220
|
-
image: "debian:12" | "...",
|
|
221
|
-
envVars: { KEY: "value" },
|
|
222
|
-
labels: { "app": "ekairos" },
|
|
223
|
-
public: false,
|
|
224
|
-
ephemeral: true,
|
|
225
|
-
autoStopIntervalMin: 30,
|
|
226
|
-
autoArchiveIntervalMin: 60,
|
|
227
|
-
user: "daytona",
|
|
228
|
-
volumes: [{ volumeId: "vol-123", mountPath: "/home/daytona/volume" }],
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Command execution
|
|
234
|
-
|
|
235
|
-
For Daytona, `runCommand` uses `sandbox.process.executeCommand(...)` and returns:
|
|
236
|
-
|
|
237
|
-
- `exitCode`
|
|
238
|
-
- `output` (stdout)
|
|
239
|
-
- `error` (stderr if available)
|
|
240
|
-
|
|
241
|
-
### File IO
|
|
242
|
-
|
|
243
|
-
- `writeFiles` -> `sandbox.fs.uploadFiles(...)`
|
|
244
|
-
- `readFile` -> `sandbox.fs.downloadFile(...)`
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
|
-
## Volumes (Daytona)
|
|
249
|
-
|
|
250
|
-
Volumes are persistent FUSE mounts that can be attached to multiple sandboxes.
|
|
251
|
-
They are ideal for caching datasets or large artifacts across runs.
|
|
252
|
-
|
|
253
|
-
### Create/get a volume
|
|
254
|
-
|
|
255
|
-
```ts
|
|
256
|
-
import { Daytona } from "@daytonaio/sdk";
|
|
257
|
-
|
|
258
|
-
const daytona = new Daytona();
|
|
259
|
-
const volume = await daytona.volume.get("ekairos-ds-123", true);
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Mount a volume when creating a sandbox
|
|
263
|
-
|
|
264
|
-
```ts
|
|
265
|
-
const created = await sandboxes.createSandbox({
|
|
266
|
-
provider: "daytona",
|
|
267
|
-
daytona: {
|
|
268
|
-
// Either volumeId or volumeName can be provided.
|
|
269
|
-
volumes: [{ volumeId: volume.id, mountPath: "/home/daytona/.ekairos" }],
|
|
270
|
-
// volumes: [{ volumeName: "ekairos-ds-123", mountPath: "/home/daytona/.ekairos" }],
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Use the volume in the sandbox
|
|
276
|
-
|
|
277
|
-
Once mounted, read/write like any other directory.
|
|
278
|
-
Files written to a volume persist even after the sandbox is removed.
|
|
279
|
-
|
|
280
|
-
### Limitations
|
|
281
|
-
|
|
282
|
-
- Volumes are FUSE-based and **slower** than the local sandbox filesystem.
|
|
283
|
-
- Volumes are **not** block storage and are not suitable for databases.
|
|
284
|
-
- For heavy processing, consider copying from the volume to local FS first.
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
## Volumes vs InstantDB files (how they relate)
|
|
289
|
-
|
|
290
|
-
InstantDB storage (`$files`) is **durable object storage** (good for canonical datasets,
|
|
291
|
-
outputs, and sharing data across services). Daytona volumes are **durable shared file mounts**
|
|
292
|
-
optimized for **fast re-use inside sandboxes**.
|
|
293
|
-
|
|
294
|
-
Think of it like this:
|
|
295
|
-
|
|
296
|
-
- **InstantDB `$files`** is the source of truth and can be accessed by any service.
|
|
297
|
-
- **Daytona volumes** are a performance layer to avoid re-downloading the same files into sandboxes.
|
|
298
|
-
|
|
299
|
-
### Recommended pattern
|
|
300
|
-
|
|
301
|
-
1) **Persist canonical files** in InstantDB `$files`.
|
|
302
|
-
2) **On sandbox start**, check a manifest in the volume:
|
|
303
|
-
- If present and hashes match, **use volume data**.
|
|
304
|
-
- If not, **download from InstantDB** and refresh the volume + manifest.
|
|
305
|
-
3) **Write output back to InstantDB** when you need durable sharing or indexing.
|
|
306
|
-
|
|
307
|
-
### Example manifest layout
|
|
308
|
-
|
|
309
|
-
```
|
|
310
|
-
/home/daytona/.ekairos/datasets/{datasetId}/
|
|
311
|
-
manifest.json
|
|
312
|
-
raw/
|
|
313
|
-
normalized/
|
|
314
|
-
cache/
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Why both are needed
|
|
318
|
-
|
|
319
|
-
- InstantDB guarantees durability, queryability, and cross-service access.
|
|
320
|
-
- Volumes reduce cold-start and repeated downloads for sandbox workloads.
|
|
321
|
-
|
|
322
|
-
---
|
|
323
|
-
|
|
324
|
-
## Ephemeral sandboxes (Daytona)
|
|
325
|
-
|
|
326
|
-
Set `daytona.ephemeral = true` to auto-delete a sandbox after it stops.
|
|
327
|
-
Pair this with `autoStopIntervalMin` to avoid quota exhaustion from idle sandboxes.
|
|
328
|
-
|
|
329
|
-
```ts
|
|
330
|
-
const created = await sandboxes.createSandbox({
|
|
331
|
-
provider: "daytona",
|
|
332
|
-
daytona: {
|
|
333
|
-
ephemeral: true,
|
|
334
|
-
autoStopIntervalMin: 5,
|
|
335
|
-
},
|
|
336
|
-
});
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
---
|
|
340
|
-
|
|
341
|
-
## Behavioral notes
|
|
342
|
-
|
|
343
|
-
### `createSandbox(config)`
|
|
344
|
-
- Creates an InstantDB record `sandbox_sandboxes[sandboxId]` with status `"creating"`.
|
|
345
|
-
- Provisions a provider sandbox (provider-specific).
|
|
346
|
-
- Updates the record to status `"active"` and stores `externalSandboxId`.
|
|
347
|
-
|
|
348
|
-
### `reconnectToSandbox(sandboxId)`
|
|
349
|
-
- Loads the record by internal `sandboxId`.
|
|
350
|
-
- Validates:
|
|
351
|
-
- record exists
|
|
352
|
-
- `externalSandboxId` exists
|
|
353
|
-
- Reconnects using the provider SDK/client (provider-specific).
|
|
354
|
-
- Returns `ok: false` if sandbox is not found/not running.
|
|
355
|
-
- If reconnection fails and the record was `"active"`, it may mark the record as `"shutdown"`.
|
|
356
|
-
|
|
357
|
-
### `runCommand(sandboxId, command, args?)`
|
|
358
|
-
- Durable-friendly command execution.
|
|
359
|
-
- Attempts to reconnect; if unavailable it may recreate the sandbox from the stored record configuration and retry.
|
|
360
|
-
- Returns a capped output payload suitable for storing in logs.
|
|
361
|
-
|
|
362
|
-
### `stopSandbox(sandboxId)`
|
|
363
|
-
- Attempts to reconnect and stop the provider sandbox.
|
|
364
|
-
- Marks the record as `"shutdown"` regardless (the provider sandbox may already be gone).
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## Providers
|
|
369
|
-
|
|
370
|
-
### Daytona (current)
|
|
371
|
-
|
|
372
|
-
Environment variables:
|
|
373
|
-
- `DAYTONA_API_URL`
|
|
374
|
-
- `DAYTONA_API_KEY` or `DAYTONA_JWT_TOKEN + DAYTONA_ORGANIZATION_ID`
|
|
375
|
-
- `DAYTONA_TARGET` (optional)
|
|
376
|
-
|
|
377
|
-
Behavior notes:
|
|
378
|
-
- `externalSandboxId` maps to Daytona `sandbox.id`.
|
|
379
|
-
- Reconnect uses `daytona.get(id)` and starts if not running.
|
|
380
|
-
- File IO uses `sandbox.fs`.
|
|
381
|
-
- Command execution uses `sandbox.process.executeCommand`.
|
|
382
|
-
|
|
383
|
-
### Local Daytona OSS (Docker Desktop / Windows)
|
|
384
|
-
|
|
385
|
-
We keep the Daytona OSS repo **outside** this workspace to avoid nested repos.
|
|
386
|
-
Use the helper script in `./scripts/daytona-local.ps1` to clone and run the official
|
|
387
|
-
Docker Compose stack.
|
|
388
|
-
|
|
389
|
-
```powershell
|
|
390
|
-
# clone once (pin a ref for repeatability)
|
|
391
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 init -Ref <tag-or-commit>
|
|
392
|
-
|
|
393
|
-
# start / stop
|
|
394
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 up
|
|
395
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 down
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
Defaults and overrides:
|
|
399
|
-
- Default clone path: `%USERPROFILE%\.ekairos\daytona-oss`
|
|
400
|
-
- Override with `DAYTONA_OSS_HOME`
|
|
401
|
-
- Pin a specific version with `DAYTONA_OSS_REF`
|
|
402
|
-
|
|
403
|
-
Environment variables for local usage:
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
SANDBOX_PROVIDER=daytona
|
|
407
|
-
DAYTONA_API_URL=http://localhost:3000/api
|
|
408
|
-
DAYTONA_API_KEY=... # create in the local Daytona dashboard (http://localhost:3000)
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
Optional: if you need proxy/preview URLs, Windows needs wildcard DNS for `*.proxy.localhost`.
|
|
412
|
-
You can run Daytona's `scripts/setup-proxy-dns.sh` inside WSL or use a local DNS tool.
|
|
413
|
-
This is not required for API-only usage.
|
|
414
|
-
|
|
415
|
-
### Vercel
|
|
416
|
-
|
|
417
|
-
Environment variables:
|
|
418
|
-
- `SANDBOX_VERCEL_TEAM_ID`
|
|
419
|
-
- `SANDBOX_VERCEL_PROJECT_ID`
|
|
420
|
-
- `SANDBOX_VERCEL_TOKEN`
|
|
421
|
-
|
|
422
|
-
Behavior notes:
|
|
423
|
-
- `externalSandboxId` maps to Vercel `sandbox.sandboxId`.
|
|
424
|
-
- Reconnect is done via `Sandbox.get(...)` using the stored `externalSandboxId`.
|
|
425
|
-
|
|
426
|
-
---
|
|
427
|
-
|
|
428
|
-
## Dev local (Daytona OSS en Docker Desktop)
|
|
429
|
-
|
|
430
|
-
### 1) Levantar Daytona local
|
|
431
|
-
|
|
432
|
-
Usa el helper para clonar el repo de Daytona (fuera del workspace) y levantar el stack:
|
|
433
|
-
|
|
434
|
-
```powershell
|
|
435
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 init
|
|
436
|
-
powershell -ExecutionPolicy Bypass -File .\scripts\daytona-local.ps1 up
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
Dashboard local: `http://localhost:3000`
|
|
440
|
-
|
|
441
|
-
Credenciales (dev):
|
|
442
|
-
- usuario: `dev@daytona.io`
|
|
443
|
-
- password: `password`
|
|
444
|
-
|
|
445
|
-
### 2) Crear API key manualmente
|
|
446
|
-
|
|
447
|
-
En el dashboard, ve a **API Keys** y crea una key. Luego configura tu app:
|
|
53
|
+
## Tests
|
|
448
54
|
|
|
449
|
-
```bash
|
|
450
|
-
SANDBOX_PROVIDER=daytona
|
|
451
|
-
DAYTONA_API_URL=http://localhost:3000/api
|
|
452
|
-
DAYTONA_API_KEY=...tu_api_key_local...
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
Notas:
|
|
456
|
-
- Guarda la key localmente; **no la comitees**.
|
|
457
|
-
- Si necesitas proxy/preview en Windows, configura `*.proxy.localhost` (ver README de Daytona OSS).
|
|
458
|
-
|
|
459
|
-
---
|
|
460
|
-
|
|
461
|
-
## Development notes
|
|
462
|
-
|
|
463
|
-
- Prefer `runCommand(sandboxId, ...)` for provider-agnostic usage.
|
|
464
|
-
- Only `reconnect()` when you need direct provider SDK features.
|
|
465
|
-
- Avoid storing secrets in `params`.
|
|
466
|
-
- When using volumes, include a manifest/versioning strategy.
|
|
467
|
-
|
|
468
|
-
---
|
|
469
|
-
|
|
470
|
-
## Testing
|
|
471
|
-
|
|
472
|
-
A smoke test exists in:
|
|
473
|
-
- `packages/sandbox/src/tests/sandbox.temp-app.test.ts`
|
|
474
|
-
|
|
475
|
-
It:
|
|
476
|
-
- Creates a temporary Instant app via `instant-cli`.
|
|
477
|
-
- Pushes a minimal schema with `sandboxDomain`.
|
|
478
|
-
- Creates a sandbox (Daytona) and runs a simple command.
|
|
479
|
-
|
|
480
|
-
Run:
|
|
481
55
|
```bash
|
|
482
56
|
pnpm --filter @ekairos/sandbox test
|
|
483
57
|
```
|
|
484
|
-
|
|
485
|
-
---
|
|
486
|
-
|
|
487
|
-
## Roadmap
|
|
488
|
-
|
|
489
|
-
- Snapshot/image presets for faster cold-start.
|
|
490
|
-
- Volume GC policy.
|
|
491
|
-
- Preview proxy on custom domain.
|
|
492
|
-
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGjE,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGjE,eAAO,MAAM,aAAa,EAAE,kBAkC1B,CAAA"}
|
package/dist/schema.js
CHANGED
|
@@ -5,6 +5,7 @@ export const sandboxDomain = domain("sandbox").schema({
|
|
|
5
5
|
entities: {
|
|
6
6
|
sandbox_sandboxes: i.entity({
|
|
7
7
|
externalSandboxId: i.string().optional().indexed(),
|
|
8
|
+
sandboxUserId: i.string().optional().indexed(),
|
|
8
9
|
provider: i.string().indexed(),
|
|
9
10
|
sandboxUrl: i.string().optional(),
|
|
10
11
|
status: i.string().indexed(),
|
|
@@ -19,7 +20,20 @@ export const sandboxDomain = domain("sandbox").schema({
|
|
|
19
20
|
shutdownAt: i.number().optional().indexed(),
|
|
20
21
|
}),
|
|
21
22
|
},
|
|
22
|
-
links: {
|
|
23
|
+
links: {
|
|
24
|
+
sandbox_user: {
|
|
25
|
+
forward: {
|
|
26
|
+
on: "sandbox_sandboxes",
|
|
27
|
+
has: "one",
|
|
28
|
+
label: "user",
|
|
29
|
+
},
|
|
30
|
+
reverse: {
|
|
31
|
+
on: "$users",
|
|
32
|
+
has: "many",
|
|
33
|
+
label: "sandboxes",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
23
37
|
rooms: {},
|
|
24
38
|
});
|
|
25
39
|
//# sourceMappingURL=schema.js.map
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AACnC,OAAO,EAAE,MAAM,EAA2B,MAAM,iBAAiB,CAAA;AAEjE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,aAAa,GAAuB,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;IACxE,QAAQ,EAAE;QACR,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC;YAC1B,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;YAClD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;YAC9B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACjC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;YAC5B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC5B,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;YAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;YACxC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;YAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;YAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;YAC1C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;SAC5C,CAAC;KACH;IACD,KAAK,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AACnC,OAAO,EAAE,MAAM,EAA2B,MAAM,iBAAiB,CAAA;AAEjE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,aAAa,GAAuB,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;IACxE,QAAQ,EAAE;QACR,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC;YAC1B,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;YAClD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;YAC9C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;YAC9B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACjC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;YAC5B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC5B,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;YAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;YACxC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;YAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;YAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;YAC1C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;SAC5C,CAAC;KACH;IACD,KAAK,EAAE;QACL,YAAY,EAAE;YACZ,OAAO,EAAE;gBACP,EAAE,EAAE,mBAAmB;gBACvB,GAAG,EAAE,KAAK;gBACV,KAAK,EAAE,MAAM;aACd;YACD,OAAO,EAAE;gBACP,EAAE,EAAE,QAAQ;gBACZ,GAAG,EAAE,MAAM;gBACX,KAAK,EAAE,WAAW;aACnB;SACF;KACF;IACD,KAAK,EAAE,EAAE;CACV,CAAC,CAAA"}
|
package/dist/service.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ type SandboxSchemaType = SchemaOf<typeof sandboxDomain>;
|
|
|
9
9
|
export interface SandboxRecord {
|
|
10
10
|
id: string;
|
|
11
11
|
externalSandboxId?: string;
|
|
12
|
+
sandboxUserId?: string;
|
|
12
13
|
provider: string;
|
|
13
14
|
sandboxUrl?: string;
|
|
14
15
|
status: "creating" | "active" | "shutdown" | "error" | "recreating";
|
|
@@ -44,6 +45,23 @@ export declare class SandboxService {
|
|
|
44
45
|
private adminDb;
|
|
45
46
|
constructor(db: InstantAdminDatabase<SandboxSchemaType>);
|
|
46
47
|
private static getVercelCredentials;
|
|
48
|
+
private static getDomainName;
|
|
49
|
+
private static getDomainContextString;
|
|
50
|
+
private static cloneJson;
|
|
51
|
+
private static buildEkairosNetworkPolicy;
|
|
52
|
+
private static buildEkairosManifest;
|
|
53
|
+
private static buildEkairosRuntimeFiles;
|
|
54
|
+
private static resolveInstantUserIdByRefreshToken;
|
|
55
|
+
private static resolveEkairosBootstrap;
|
|
56
|
+
private static bootstrapEkairosFiles;
|
|
57
|
+
private static safeMkDir;
|
|
58
|
+
private static buildSkillInstallSet;
|
|
59
|
+
private static bootstrapSkills;
|
|
60
|
+
private static resolveVercelWorkingDirectory;
|
|
61
|
+
private static findLinkedVercelProjectFile;
|
|
62
|
+
private static readLinkedVercelProject;
|
|
63
|
+
private static pullVercelOidcToken;
|
|
64
|
+
private static resolveVercelCredentials;
|
|
47
65
|
private static provisionVercelSandbox;
|
|
48
66
|
private static getDaytonaConfig;
|
|
49
67
|
private static normalizeBaseUrl;
|
|
@@ -71,7 +89,9 @@ export declare class SandboxService {
|
|
|
71
89
|
reconnectToSandbox(sandboxId: string): Promise<ServiceResult<{
|
|
72
90
|
sandbox: ProviderSandbox;
|
|
73
91
|
}>>;
|
|
92
|
+
private getSandboxRecord;
|
|
74
93
|
stopSandbox(sandboxId: string): Promise<ServiceResult<void>>;
|
|
94
|
+
query(sandboxId: string, query: Record<string, any>): Promise<ServiceResult<any>>;
|
|
75
95
|
runCommand(sandboxId: string, command: string, args?: string[]): Promise<ServiceResult<CommandResult>>;
|
|
76
96
|
writeFiles(sandboxId: string, files: Array<{
|
|
77
97
|
path: string;
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAsB,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EAAsC,KAAK,OAAO,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACnG,OAAO,EAAM,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE1C,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,aAAa,EAA4C,MAAM,YAAY,CAAA;AAQzF,KAAK,iBAAiB,GAAG,QAAQ,CAAC,OAAO,aAAa,CAAC,CAAA;AAGvD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,YAAY,CAAA;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,GAAG,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzF,KAAK,cAAc,GAAG;IACpB,UAAU,EAAE,SAAS,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC3C,CAAA;AAED,KAAK,eAAe,GAAG,aAAa,GAAG,cAAc,GAAG,cAAc,CAAA;AA+EtE,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAyC;gBAE5C,EAAE,EAAE,oBAAoB,CAAC,iBAAiB,CAAC;IAIvD,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAYnC,OAAO,CAAC,MAAM,CAAC,aAAa;IAM5B,OAAO,CAAC,MAAM,CAAC,sBAAsB;IASrC,OAAO,CAAC,MAAM,CAAC,SAAS;IAIxB,OAAO,CAAC,MAAM,CAAC,yBAAyB;IA0BxC,OAAO,CAAC,MAAM,CAAC,oBAAoB;IA0BnC,OAAO,CAAC,MAAM,CAAC,wBAAwB;mBA8FlB,kCAAkC;mBAmClC,uBAAuB;mBAsDvB,qBAAqB;mBAOrB,SAAS;IAY9B,OAAO,CAAC,MAAM,CAAC,oBAAoB;mBA2Bd,eAAe;IA+BpC,OAAO,CAAC,MAAM,CAAC,6BAA6B;IAQ5C,OAAO,CAAC,MAAM,CAAC,2BAA2B;mBAWrB,uBAAuB;mBAwBvB,mBAAmB;mBAoCnB,wBAAwB;mBAyBxB,sBAAsB;IAqB3C,OAAO,CAAC,MAAM,CAAC,gBAAgB;IA2B/B,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAM/B,OAAO,CAAC,MAAM,CAAC,gBAAgB;mBAcV,YAAY;mBAmBZ,WAAW;mBAiBX,WAAW;IAMhC,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAiBlC,OAAO,CAAC,MAAM,CAAC,gBAAgB;mBAoBV,gBAAgB;mBAgBhB,uBAAuB;IAoC5C,OAAO,CAAC,MAAM,CAAC,0BAA0B;mBAuCpB,WAAW;IAgDhC,OAAO,CAAC,MAAM,CAAC,eAAe;IAc9B,OAAO,CAAC,MAAM,CAAC,sBAAsB;mBAShB,qBAAqB;IAgE1C,OAAO,CAAC,MAAM,CAAC,cAAc;IAM7B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAQnC,OAAO,CAAC,MAAM,CAAC,YAAY;IAO3B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAUnC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAoC9B,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAsOnF,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,eAAe,CAAA;KAAE,CAAC,CAAC;YAgJnF,gBAAgB;IAOxB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAyD5D,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAyCjF,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;IAkD1G,UAAU,CACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,GACpD,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IA6CzB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAqDlG,OAAO,CAAC,MAAM,CAAC,kCAAkC;IA0B3C,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5B,OAAO,CAAC,aAAa,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAiD7C,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;QAAE,aAAa,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IA2BvF,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;CA0C/F"}
|