sandlot 0.1.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 +616 -0
- package/dist/bundler.d.ts +148 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/commands.d.ts +179 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/fs.d.ts +125 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2920 -0
- package/dist/internal.d.ts +74 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +1897 -0
- package/dist/loader.d.ts +164 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/packages.d.ts +199 -0
- package/dist/packages.d.ts.map +1 -0
- package/dist/react.d.ts +159 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +149 -0
- package/dist/sandbox-manager.d.ts +249 -0
- package/dist/sandbox-manager.d.ts.map +1 -0
- package/dist/sandbox.d.ts +193 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/shared-modules.d.ts +129 -0
- package/dist/shared-modules.d.ts.map +1 -0
- package/dist/shared-resources.d.ts +105 -0
- package/dist/shared-resources.d.ts.map +1 -0
- package/dist/ts-libs.d.ts +98 -0
- package/dist/ts-libs.d.ts.map +1 -0
- package/dist/typechecker.d.ts +127 -0
- package/dist/typechecker.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/bundler.ts +513 -0
- package/src/commands.ts +733 -0
- package/src/fs.ts +935 -0
- package/src/index.ts +149 -0
- package/src/internal.ts +116 -0
- package/src/loader.ts +229 -0
- package/src/packages.ts +936 -0
- package/src/react.tsx +331 -0
- package/src/sandbox-manager.ts +490 -0
- package/src/sandbox.ts +402 -0
- package/src/shared-modules.ts +210 -0
- package/src/shared-resources.ts +169 -0
- package/src/ts-libs.ts +320 -0
- package/src/typechecker.ts +635 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxManager - Manages shared resources for multiple concurrent sandboxes.
|
|
3
|
+
*
|
|
4
|
+
* When running many sandboxes concurrently (e.g., for multiple AI agents),
|
|
5
|
+
* this manager ensures heavy resources are loaded once and shared:
|
|
6
|
+
*
|
|
7
|
+
* - TypeScript lib files (~5MB) - loaded once, shared across all sandboxes
|
|
8
|
+
* - esbuild WASM (~10MB) - already singleton, but pre-initialized here
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ```ts
|
|
12
|
+
* const manager = await createSandboxManager();
|
|
13
|
+
*
|
|
14
|
+
* // Create multiple sandboxes - all share the same libs and bundler
|
|
15
|
+
* const sandbox1 = await manager.createSandbox({ ... });
|
|
16
|
+
* const sandbox2 = await manager.createSandbox({ ... });
|
|
17
|
+
*
|
|
18
|
+
* // ... use sandboxes ...
|
|
19
|
+
*
|
|
20
|
+
* // Clean up
|
|
21
|
+
* manager.destroyAll();
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Bash, defineCommand } from "just-bash/browser";
|
|
26
|
+
import { IndexedDbFs, type IndexedDbFsOptions } from "./fs";
|
|
27
|
+
import { type BundleResult } from "./bundler";
|
|
28
|
+
import { getDefaultBrowserLibs } from "./ts-libs";
|
|
29
|
+
import {
|
|
30
|
+
createSharedResources,
|
|
31
|
+
type SharedResources,
|
|
32
|
+
type SharedResourcesOptions,
|
|
33
|
+
} from "./shared-resources";
|
|
34
|
+
import { createDefaultCommands, type CommandDeps } from "./commands";
|
|
35
|
+
import type { Sandbox } from "./sandbox";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Simple typed event emitter for build results.
|
|
39
|
+
* Caches the last result so waitFor() can be called after build completes.
|
|
40
|
+
*/
|
|
41
|
+
class BuildEmitter {
|
|
42
|
+
private listeners = new Set<(result: BundleResult) => void | Promise<void>>();
|
|
43
|
+
private lastResult: BundleResult | null = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Emit a build result to all listeners and cache it
|
|
47
|
+
*/
|
|
48
|
+
emit = async (result: BundleResult): Promise<void> => {
|
|
49
|
+
this.lastResult = result;
|
|
50
|
+
const promises: Promise<void>[] = [];
|
|
51
|
+
for (const listener of this.listeners) {
|
|
52
|
+
const ret = listener(result);
|
|
53
|
+
if (ret instanceof Promise) {
|
|
54
|
+
promises.push(ret);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await Promise.all(promises);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Subscribe to build events. Returns an unsubscribe function.
|
|
62
|
+
*/
|
|
63
|
+
on(callback: (result: BundleResult) => void | Promise<void>): () => void {
|
|
64
|
+
this.listeners.add(callback);
|
|
65
|
+
return () => {
|
|
66
|
+
this.listeners.delete(callback);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get the last build result, or wait for the next one if none exists.
|
|
72
|
+
* Clears the cached result after returning, so subsequent calls wait for new builds.
|
|
73
|
+
*/
|
|
74
|
+
waitFor(): Promise<BundleResult> {
|
|
75
|
+
if (this.lastResult) {
|
|
76
|
+
const result = this.lastResult;
|
|
77
|
+
this.lastResult = null;
|
|
78
|
+
return Promise.resolve(result);
|
|
79
|
+
}
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
const unsub = this.on((result) => {
|
|
82
|
+
unsub();
|
|
83
|
+
this.lastResult = null;
|
|
84
|
+
resolve(result);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Re-export for convenience
|
|
91
|
+
export type { BundleResult } from "./bundler";
|
|
92
|
+
export type { TypecheckResult } from "./typechecker";
|
|
93
|
+
export type { SharedResources, TypesCache } from "./shared-resources";
|
|
94
|
+
export type { PackageManifest, InstallResult } from "./packages";
|
|
95
|
+
export { installPackage, uninstallPackage, listPackages, getPackageManifest } from "./packages";
|
|
96
|
+
export { InMemoryTypesCache } from "./shared-resources";
|
|
97
|
+
|
|
98
|
+
// Loader utilities
|
|
99
|
+
export {
|
|
100
|
+
loadModule,
|
|
101
|
+
loadExport,
|
|
102
|
+
loadDefault,
|
|
103
|
+
getExportNames,
|
|
104
|
+
hasExport,
|
|
105
|
+
createModuleUrl,
|
|
106
|
+
revokeModuleUrl,
|
|
107
|
+
ModuleLoadError,
|
|
108
|
+
ExportNotFoundError,
|
|
109
|
+
} from "./loader";
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Options for creating a sandbox via the manager
|
|
113
|
+
*/
|
|
114
|
+
export interface ManagedSandboxOptions {
|
|
115
|
+
/**
|
|
116
|
+
* Unique identifier for this sandbox.
|
|
117
|
+
*
|
|
118
|
+
* Also used as the IndexedDB database name if `fsOptions.dbName` is not provided
|
|
119
|
+
* and `inMemory` is false. If both are provided, `fsOptions.dbName` takes precedence.
|
|
120
|
+
*
|
|
121
|
+
* @default Auto-generated as "sandbox-1", "sandbox-2", etc.
|
|
122
|
+
*/
|
|
123
|
+
id?: string;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Options for the IndexedDB filesystem.
|
|
127
|
+
*
|
|
128
|
+
* Note: `fsOptions.dbName` takes precedence over `id` for the database name.
|
|
129
|
+
* If neither is provided, the auto-generated `id` is used.
|
|
130
|
+
*/
|
|
131
|
+
fsOptions?: IndexedDbFsOptions;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Initial files to populate the filesystem with
|
|
135
|
+
*/
|
|
136
|
+
initialFiles?: Record<string, string>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Path to tsconfig.json in the virtual filesystem.
|
|
140
|
+
* Default: "/tsconfig.json"
|
|
141
|
+
*/
|
|
142
|
+
tsconfigPath?: string;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Callback invoked when a build succeeds.
|
|
146
|
+
* Receives the bundle result with the compiled code.
|
|
147
|
+
*/
|
|
148
|
+
onBuild?: (result: BundleResult) => void | Promise<void>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Additional custom commands to add to the bash environment
|
|
152
|
+
*/
|
|
153
|
+
customCommands?: ReturnType<typeof defineCommand>[];
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* If true, use in-memory filesystem only (no IndexedDB persistence).
|
|
157
|
+
* Default: true
|
|
158
|
+
*/
|
|
159
|
+
inMemory?: boolean;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Module IDs that should be resolved from the host's SharedModuleRegistry
|
|
163
|
+
* instead of esm.sh CDN. Overrides manager-level sharedModules for this sandbox.
|
|
164
|
+
*
|
|
165
|
+
* @see SandboxOptions.sharedModules for full documentation
|
|
166
|
+
*/
|
|
167
|
+
sharedModules?: string[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A sandbox instance managed by the SandboxManager.
|
|
172
|
+
*
|
|
173
|
+
* Extends the base Sandbox interface with an `id` property for
|
|
174
|
+
* identification within the manager. This ensures ManagedSandbox
|
|
175
|
+
* has full feature parity with Sandbox.
|
|
176
|
+
*/
|
|
177
|
+
export interface ManagedSandbox extends Sandbox {
|
|
178
|
+
/**
|
|
179
|
+
* Unique identifier for this sandbox within the manager.
|
|
180
|
+
*/
|
|
181
|
+
id: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Statistics about the sandbox manager
|
|
186
|
+
*/
|
|
187
|
+
export interface SandboxManagerStats {
|
|
188
|
+
/**
|
|
189
|
+
* Whether shared resources have been initialized
|
|
190
|
+
*/
|
|
191
|
+
initialized: boolean;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Number of currently active sandboxes
|
|
195
|
+
*/
|
|
196
|
+
activeSandboxes: number;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Number of TypeScript lib files loaded
|
|
200
|
+
*/
|
|
201
|
+
libFilesCount: number;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* IDs of active sandboxes
|
|
205
|
+
*/
|
|
206
|
+
sandboxIds: string[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Options for creating a SandboxManager
|
|
211
|
+
*/
|
|
212
|
+
export interface SandboxManagerOptions extends SharedResourcesOptions {
|
|
213
|
+
/**
|
|
214
|
+
* TypeScript libs to load. Defaults to browser libs (ES2020 + DOM).
|
|
215
|
+
*/
|
|
216
|
+
libs?: string[];
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Default shared modules for all sandboxes created by this manager.
|
|
220
|
+
* Individual sandboxes can override with their own sharedModules option.
|
|
221
|
+
*
|
|
222
|
+
* Module IDs that should be resolved from the host's SharedModuleRegistry
|
|
223
|
+
* instead of esm.sh CDN. The host must have registered these modules
|
|
224
|
+
* using `registerSharedModules()` before loading dynamic code.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* const manager = await createSandboxManager({
|
|
229
|
+
* sharedModules: ['react', 'react-dom/client'],
|
|
230
|
+
* });
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
sharedModules?: string[];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Manager for creating and managing multiple sandboxes with shared resources.
|
|
238
|
+
*/
|
|
239
|
+
export class SandboxManager {
|
|
240
|
+
private resources: SharedResources | null = null;
|
|
241
|
+
private sandboxes: Map<string, ManagedSandbox> = new Map();
|
|
242
|
+
private initialized = false;
|
|
243
|
+
private initPromise: Promise<void> | null = null;
|
|
244
|
+
private nextId = 1;
|
|
245
|
+
private options: SandboxManagerOptions;
|
|
246
|
+
|
|
247
|
+
constructor(options: SandboxManagerOptions = {}) {
|
|
248
|
+
this.options = {
|
|
249
|
+
libs: options.libs ?? getDefaultBrowserLibs(),
|
|
250
|
+
...options,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Initialize shared resources (libs and bundler).
|
|
256
|
+
* Called automatically on first sandbox creation, but can be called
|
|
257
|
+
* explicitly to pre-warm.
|
|
258
|
+
*/
|
|
259
|
+
async initialize(): Promise<void> {
|
|
260
|
+
if (this.initialized) return;
|
|
261
|
+
|
|
262
|
+
if (this.initPromise) {
|
|
263
|
+
await this.initPromise;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this.initPromise = this.doInitialize();
|
|
268
|
+
await this.initPromise;
|
|
269
|
+
this.initialized = true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private async doInitialize(): Promise<void> {
|
|
273
|
+
this.resources = await createSharedResources({
|
|
274
|
+
libs: this.options.libs,
|
|
275
|
+
skipLibs: this.options.skipLibs,
|
|
276
|
+
skipBundler: this.options.skipBundler,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Create a new managed sandbox.
|
|
282
|
+
* Shares TypeScript libs and bundler with all other sandboxes.
|
|
283
|
+
*/
|
|
284
|
+
async createSandbox(options: ManagedSandboxOptions = {}): Promise<ManagedSandbox> {
|
|
285
|
+
// Ensure resources are initialized
|
|
286
|
+
await this.initialize();
|
|
287
|
+
|
|
288
|
+
const {
|
|
289
|
+
id = `sandbox-${this.nextId++}`,
|
|
290
|
+
fsOptions = {},
|
|
291
|
+
initialFiles,
|
|
292
|
+
tsconfigPath = "/tsconfig.json",
|
|
293
|
+
onBuild,
|
|
294
|
+
customCommands = [],
|
|
295
|
+
inMemory = true,
|
|
296
|
+
sharedModules = this.options.sharedModules,
|
|
297
|
+
} = options;
|
|
298
|
+
|
|
299
|
+
// Create filesystem
|
|
300
|
+
let fs: IndexedDbFs;
|
|
301
|
+
if (inMemory) {
|
|
302
|
+
fs = IndexedDbFs.createInMemory({
|
|
303
|
+
initialFiles,
|
|
304
|
+
maxSizeBytes: fsOptions.maxSizeBytes,
|
|
305
|
+
});
|
|
306
|
+
} else {
|
|
307
|
+
fs = await IndexedDbFs.create({
|
|
308
|
+
dbName: fsOptions.dbName ?? id,
|
|
309
|
+
initialFiles,
|
|
310
|
+
maxSizeBytes: fsOptions.maxSizeBytes,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Create build event emitter
|
|
315
|
+
const buildEmitter = new BuildEmitter();
|
|
316
|
+
|
|
317
|
+
// If a callback was provided in options, subscribe it
|
|
318
|
+
if (onBuild) {
|
|
319
|
+
buildEmitter.on(onBuild);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Create commands using shared resources
|
|
323
|
+
const commandDeps: CommandDeps = {
|
|
324
|
+
fs,
|
|
325
|
+
libFiles: this.resources!.libFiles,
|
|
326
|
+
tsconfigPath,
|
|
327
|
+
onBuild: buildEmitter.emit,
|
|
328
|
+
typesCache: this.resources!.typesCache,
|
|
329
|
+
sharedModules,
|
|
330
|
+
};
|
|
331
|
+
const defaultCommands = createDefaultCommands(commandDeps);
|
|
332
|
+
|
|
333
|
+
// Create bash environment
|
|
334
|
+
const bash = new Bash({
|
|
335
|
+
fs,
|
|
336
|
+
cwd: "/",
|
|
337
|
+
customCommands: [...defaultCommands, ...customCommands],
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const sandbox: ManagedSandbox = {
|
|
341
|
+
id,
|
|
342
|
+
fs,
|
|
343
|
+
bash,
|
|
344
|
+
isDirty: () => fs.isDirty(),
|
|
345
|
+
save: () => fs.save(),
|
|
346
|
+
close: () => {
|
|
347
|
+
fs.close();
|
|
348
|
+
this.sandboxes.delete(id);
|
|
349
|
+
},
|
|
350
|
+
onBuild: (callback) => buildEmitter.on(callback),
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
this.sandboxes.set(id, sandbox);
|
|
354
|
+
return sandbox;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get a sandbox by ID
|
|
359
|
+
*/
|
|
360
|
+
getSandbox(id: string): ManagedSandbox | undefined {
|
|
361
|
+
return this.sandboxes.get(id);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get all active sandboxes
|
|
366
|
+
*/
|
|
367
|
+
getAllSandboxes(): ManagedSandbox[] {
|
|
368
|
+
return Array.from(this.sandboxes.values());
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Close a specific sandbox
|
|
373
|
+
*/
|
|
374
|
+
closeSandbox(id: string): boolean {
|
|
375
|
+
const sandbox = this.sandboxes.get(id);
|
|
376
|
+
if (sandbox) {
|
|
377
|
+
sandbox.close();
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Close all sandboxes and release resources
|
|
385
|
+
*/
|
|
386
|
+
destroyAll(): void {
|
|
387
|
+
for (const sandbox of this.sandboxes.values()) {
|
|
388
|
+
sandbox.fs.close();
|
|
389
|
+
}
|
|
390
|
+
this.sandboxes.clear();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Save all sandboxes that have unsaved changes.
|
|
395
|
+
*
|
|
396
|
+
* @returns Map of sandbox ID to save result (true if saved, false if nothing to save)
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* ```ts
|
|
400
|
+
* const results = await manager.saveAll();
|
|
401
|
+
* for (const [id, saved] of results) {
|
|
402
|
+
* console.log(`${id}: ${saved ? 'saved' : 'no changes'}`);
|
|
403
|
+
* }
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
async saveAll(): Promise<Map<string, boolean>> {
|
|
407
|
+
const results = new Map<string, boolean>();
|
|
408
|
+
for (const [id, sandbox] of this.sandboxes) {
|
|
409
|
+
const saved = await sandbox.save();
|
|
410
|
+
results.set(id, saved);
|
|
411
|
+
}
|
|
412
|
+
return results;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get IDs of sandboxes with unsaved changes.
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* const dirtyIds = manager.getDirtySandboxes();
|
|
421
|
+
* if (dirtyIds.length > 0) {
|
|
422
|
+
* console.log('Unsaved sandboxes:', dirtyIds.join(', '));
|
|
423
|
+
* }
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
getDirtySandboxes(): string[] {
|
|
427
|
+
return Array.from(this.sandboxes.entries())
|
|
428
|
+
.filter(([_, sandbox]) => sandbox.isDirty())
|
|
429
|
+
.map(([id]) => id);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get statistics about the manager
|
|
434
|
+
*/
|
|
435
|
+
getStats(): SandboxManagerStats {
|
|
436
|
+
return {
|
|
437
|
+
initialized: this.initialized,
|
|
438
|
+
activeSandboxes: this.sandboxes.size,
|
|
439
|
+
libFilesCount: this.resources?.libFiles.size ?? 0,
|
|
440
|
+
sandboxIds: Array.from(this.sandboxes.keys()),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Get the shared resources (for advanced use cases)
|
|
446
|
+
*/
|
|
447
|
+
getResources(): SharedResources | null {
|
|
448
|
+
return this.resources;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get the shared lib files (for advanced use cases)
|
|
453
|
+
* @deprecated Use getResources().libFiles instead
|
|
454
|
+
*/
|
|
455
|
+
getLibFiles(): Map<string, string> {
|
|
456
|
+
return this.resources?.libFiles ?? new Map();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Create a new SandboxManager instance.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* const manager = await createSandboxManager();
|
|
466
|
+
*
|
|
467
|
+
* // Create multiple sandboxes concurrently
|
|
468
|
+
* const [sandbox1, sandbox2] = await Promise.all([
|
|
469
|
+
* manager.createSandbox({ id: "agent-1", initialFiles: { ... } }),
|
|
470
|
+
* manager.createSandbox({ id: "agent-2", initialFiles: { ... } }),
|
|
471
|
+
* ]);
|
|
472
|
+
*
|
|
473
|
+
* // Run operations in parallel
|
|
474
|
+
* const [result1, result2] = await Promise.all([
|
|
475
|
+
* sandbox1.bash.exec("build"),
|
|
476
|
+
* sandbox2.bash.exec("build"),
|
|
477
|
+
* ]);
|
|
478
|
+
*
|
|
479
|
+
* // Clean up
|
|
480
|
+
* manager.destroyAll();
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
export async function createSandboxManager(
|
|
484
|
+
options: SandboxManagerOptions = {}
|
|
485
|
+
): Promise<SandboxManager> {
|
|
486
|
+
const manager = new SandboxManager(options);
|
|
487
|
+
await manager.initialize();
|
|
488
|
+
return manager;
|
|
489
|
+
}
|
|
490
|
+
|