withruntime 0.8.4 → 0.9.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.
Files changed (91) hide show
  1. package/COMPATIBILITY.md +145 -0
  2. package/E2B.md +23 -13
  3. package/README.md +8 -5
  4. package/README.src.md +8 -5
  5. package/compatibility-lock.json +281 -0
  6. package/dist/ai-harness/index.js +2 -2
  7. package/dist/api-defaults.d.ts +17 -0
  8. package/dist/api-defaults.js +22 -0
  9. package/dist/blaxel/preview.d.ts +1 -1
  10. package/dist/blaxel/preview.js +1 -1
  11. package/dist/blaxel/process.d.ts +1 -1
  12. package/dist/blaxel/process.js +11 -2
  13. package/dist/cli-words.d.ts +27 -0
  14. package/dist/cli-words.js +135 -0
  15. package/dist/cli.js +162 -31
  16. package/dist/client.d.ts +4 -1
  17. package/dist/client.js +8 -2
  18. package/dist/cloudflare/index.d.ts +294 -0
  19. package/dist/cloudflare/index.js +554 -0
  20. package/dist/cloudflare/worker.d.ts +415 -0
  21. package/dist/cloudflare/worker.js +297 -0
  22. package/dist/codesandbox/guest-service.d.ts +31 -0
  23. package/dist/codesandbox/guest-service.js +453 -0
  24. package/dist/codesandbox/index.d.ts +386 -0
  25. package/dist/codesandbox/index.js +1313 -0
  26. package/dist/compat/core.d.ts +39 -0
  27. package/dist/compat/core.js +199 -0
  28. package/dist/compat/files.d.ts +26 -0
  29. package/dist/compat/files.js +115 -0
  30. package/dist/compat/shell.d.ts +4 -0
  31. package/dist/compat/shell.js +165 -0
  32. package/dist/daytona/context.js +2 -1
  33. package/dist/daytona/process.d.ts +1 -1
  34. package/dist/daytona/process.js +27 -4
  35. package/dist/e2b/code-interpreter.d.ts +4 -5
  36. package/dist/e2b/code-interpreter.js +54 -56
  37. package/dist/e2b/commands.d.ts +5 -4
  38. package/dist/e2b/commands.js +37 -29
  39. package/dist/e2b/errors.js +1 -1
  40. package/dist/e2b/filesystem.d.ts +8 -6
  41. package/dist/e2b/filesystem.js +127 -28
  42. package/dist/e2b/sandbox.d.ts +4 -3
  43. package/dist/e2b/sandbox.js +10 -10
  44. package/dist/errors.d.ts +8 -0
  45. package/dist/errors.js +15 -2
  46. package/dist/freestyle/index.d.ts +301 -0
  47. package/dist/freestyle/index.js +310 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/mcp.js +2 -2
  50. package/dist/modal/index.d.ts +205 -0
  51. package/dist/modal/index.js +485 -0
  52. package/dist/network-cli.js +1 -1
  53. package/dist/openai-agents/index.js +9 -26
  54. package/dist/products/audit.d.ts +3 -0
  55. package/dist/products/index.d.ts +2 -0
  56. package/dist/products/index.js +2 -0
  57. package/dist/products/interpreter.d.ts +11 -4
  58. package/dist/products/interpreter.js +33 -9
  59. package/dist/products/jobs.js +3 -2
  60. package/dist/products/limits.d.ts +10 -0
  61. package/dist/products/previews.d.ts +10 -2
  62. package/dist/products/previews.js +3 -2
  63. package/dist/products/tailscale.d.ts +57 -0
  64. package/dist/products/tailscale.js +17 -0
  65. package/dist/products/watch.d.ts +25 -4
  66. package/dist/products/watch.js +75 -14
  67. package/dist/runloop/index.d.ts +340 -0
  68. package/dist/runloop/index.js +438 -0
  69. package/dist/runloop/pagination.d.ts +46 -0
  70. package/dist/runloop/pagination.js +82 -0
  71. package/dist/runloop/snapshots.d.ts +52 -0
  72. package/dist/runloop/snapshots.js +244 -0
  73. package/dist/sandbox.d.ts +40 -9
  74. package/dist/sandbox.js +273 -76
  75. package/dist/snapshots.d.ts +11 -1
  76. package/dist/snapshots.js +11 -1
  77. package/dist/sprites/index.d.ts +251 -0
  78. package/dist/sprites/index.js +552 -0
  79. package/dist/tar.d.ts +8 -1
  80. package/dist/tar.js +185 -35
  81. package/dist/tools/index.js +2 -1
  82. package/dist/transport.d.ts +20 -2
  83. package/dist/transport.js +48 -11
  84. package/dist/types.d.ts +52 -3
  85. package/dist/vercel/command.js +12 -1
  86. package/dist/vercel/errors.d.ts +2 -2
  87. package/dist/vercel/errors.js +12 -1
  88. package/dist/vercel/filesystem.d.ts +21 -18
  89. package/dist/vercel/filesystem.js +95 -48
  90. package/dist/vercel/sandbox.js +5 -5
  91. package/package.json +45 -3
@@ -0,0 +1,554 @@
1
+ import { createHash } from "node:crypto";
2
+ import { Runtime } from "../client.js";
3
+ import { NotFoundError } from "../errors.js";
4
+ import { client, closeShell, CompatibilityError, destroy, environment, execOptions, only, pathAt, saveEnvironment, shellCommand, shellRoot, } from "../compat/core.js";
5
+ export { CompatibilityError };
6
+ export class SandboxSecurityError extends Error {
7
+ code;
8
+ constructor(message, code) {
9
+ super(message);
10
+ this.code = code;
11
+ this.name = "SandboxSecurityError";
12
+ }
13
+ }
14
+ export function sanitizeSandboxId(id) {
15
+ if (!id || id.length > 63)
16
+ throw new SandboxSecurityError("Sandbox ID must be 1-63 characters long.", "INVALID_SANDBOX_ID_LENGTH");
17
+ if (id.startsWith("-") || id.endsWith("-"))
18
+ throw new SandboxSecurityError("Sandbox ID cannot start or end with hyphens (DNS requirement).", "INVALID_SANDBOX_ID_HYPHENS");
19
+ if (["www", "api", "admin", "root", "system", "cloudflare", "workers"].includes(id.toLowerCase()))
20
+ throw new SandboxSecurityError(`Reserved sandbox ID '${id}' is not allowed.`, "RESERVED_SANDBOX_ID");
21
+ return id;
22
+ }
23
+ const cleanEnv = (env = {}) => Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
24
+ function sleepSeconds(value = 600) {
25
+ if (typeof value === "number")
26
+ return value;
27
+ const match = /^(\d+)(s|m|h)$/.exec(value);
28
+ if (!match)
29
+ throw new TypeError("sleepAfter must be seconds or a duration such as 5m");
30
+ return Number(match[1]) * ({ s: 1, m: 60, h: 3600 }[match[2]] ?? 1);
31
+ }
32
+ /** Worker bindings are deliberately not inspected or invoked. Supply Runtime credentials
33
+ * through options.runtime in Workers; Node also supports RUNTIME_API_KEY/login. */
34
+ export function getSandbox(namespace, id, options = {}) {
35
+ sanitizeSandboxId(id);
36
+ only("Cloudflare getSandbox", options, ["sleepAfter", "keepAlive", "normalizeId", "runtime"]);
37
+ const runtime = namespace instanceof Runtime ? namespace : client(options.runtime);
38
+ return new Sandbox(runtime, options.normalizeId ? id.toLowerCase() : id, options);
39
+ }
40
+ export class Sandbox {
41
+ runtime;
42
+ options;
43
+ pending;
44
+ id;
45
+ constructor(runtime, id, options = {}) {
46
+ this.runtime = runtime;
47
+ this.options = options;
48
+ this.id = id;
49
+ }
50
+ async configure(options) {
51
+ if (options.sleepAfter !== undefined)
52
+ sleepSeconds(options.sleepAfter);
53
+ const changed = (options.sleepAfter !== undefined && options.sleepAfter !== this.options.sleepAfter) ||
54
+ (options.keepAlive !== undefined && options.keepAlive !== this.options.keepAlive);
55
+ Object.assign(this.options, options);
56
+ if (changed && this.pending) {
57
+ const s = await this.pending;
58
+ await s.update({
59
+ idlePauseSeconds: this.options.keepAlive ? 0 : sleepSeconds(this.options.sleepAfter),
60
+ ...(options.keepAlive !== undefined ? { persistent: options.keepAlive } : {}),
61
+ });
62
+ }
63
+ }
64
+ async native() {
65
+ if (!this.pending) {
66
+ this.pending = (async () => {
67
+ const s = await this.runtime.sandboxes.create({
68
+ name: /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(this.id)
69
+ ? this.id
70
+ : `cf-${createHash("sha256").update(this.id).digest("hex").slice(0, 40)}`,
71
+ getOrCreate: true,
72
+ labels: { "compat.provider": "cloudflare" },
73
+ idlePauseSeconds: this.options.keepAlive ? 0 : sleepSeconds(this.options.sleepAfter),
74
+ persistent: !!this.options.keepAlive,
75
+ });
76
+ if (!s.info.reused) {
77
+ try {
78
+ await saveEnvironment(s, {});
79
+ }
80
+ catch (e) {
81
+ await destroy(s);
82
+ throw e;
83
+ }
84
+ }
85
+ return s;
86
+ })();
87
+ this.pending.catch(() => {
88
+ this.pending = undefined;
89
+ });
90
+ }
91
+ return this.pending;
92
+ }
93
+ session(id = `sandbox-${this.id}`, options = {}) {
94
+ return new ExecutionSession(this, id, options);
95
+ }
96
+ exec(command, options) {
97
+ return this.session().exec(command, options);
98
+ }
99
+ execStream(command, options) {
100
+ return this.session().execStream(command, options);
101
+ }
102
+ startProcess(command, options = {}) {
103
+ return this.session(options.sessionId).startProcess(command, options);
104
+ }
105
+ async createSession(options = {}) {
106
+ only("Cloudflare session", options, [
107
+ "id",
108
+ "name",
109
+ "env",
110
+ "cwd",
111
+ "isolation",
112
+ "commandTimeoutMs",
113
+ ]);
114
+ if (options.isolation)
115
+ throw new CompatibilityError("Cloudflare", "session PID namespace isolation");
116
+ const id = options.id ?? crypto.randomUUID();
117
+ await (await this.native()).files.write(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(id)}.json`, JSON.stringify(options), { mode: 0o600 });
118
+ return this.session(id, options);
119
+ }
120
+ async getSession(id) {
121
+ if (id === `sandbox-${this.id}`)
122
+ return this.session();
123
+ const options = JSON.parse(await (await this.native()).files.readText(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(id)}.json`));
124
+ return this.session(id, options);
125
+ }
126
+ async deleteSession(id) {
127
+ if (id === `sandbox-${this.id}`)
128
+ throw new Error("The default session cannot be deleted; destroy the sandbox instead");
129
+ await closeShell(await this.native(), `cf:${id}`);
130
+ await (await this.native()).files.remove(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(id)}.json`);
131
+ return { success: true, sessionId: id, timestamp: new Date().toISOString() };
132
+ }
133
+ async destroy() {
134
+ await destroy(await this.native());
135
+ this.pending = undefined;
136
+ }
137
+ async setEnvVars(env) {
138
+ const s = await this.native();
139
+ await saveEnvironment(s, { ...(await environment(s)), ...cleanEnv(env) });
140
+ }
141
+ writeFile(path, content, options) {
142
+ return this.session().writeFile(path, content, options);
143
+ }
144
+ readFile(path, options) {
145
+ return options?.encoding === "none"
146
+ ? this.session().readFile(path, { encoding: "none" })
147
+ : this.session().readFile(path, options);
148
+ }
149
+ mkdir(path, options) {
150
+ return this.session().mkdir(path, options);
151
+ }
152
+ deleteFile(path) {
153
+ return this.session().deleteFile(path);
154
+ }
155
+ renameFile(from, to) {
156
+ return this.session().renameFile(from, to);
157
+ }
158
+ moveFile(from, to) {
159
+ return this.renameFile(from, to);
160
+ }
161
+ listFiles(path, options) {
162
+ return this.session().listFiles(path, options);
163
+ }
164
+ exists(path) {
165
+ return this.session().exists(path);
166
+ }
167
+ async listProcesses() {
168
+ const s = await this.native();
169
+ return Promise.all((await s.processes.list()).map((p) => s.processes.get(p.id).then((p) => new Process(this, p))));
170
+ }
171
+ async getProcess(id) {
172
+ try {
173
+ return new Process(this, await (await this.native()).processes.get(id));
174
+ }
175
+ catch (e) {
176
+ if (e instanceof NotFoundError)
177
+ return null;
178
+ throw e;
179
+ }
180
+ }
181
+ async killProcess(id, signal = "SIGTERM") {
182
+ const p = await this.getProcess(id);
183
+ if (!p)
184
+ throw new Error(`Process ${id} not found`);
185
+ await p.kill(signal);
186
+ }
187
+ async killAllProcesses() {
188
+ const processes = (await this.listProcesses()).filter((p) => p.status === "running");
189
+ await Promise.all(processes.map((p) => p.kill()));
190
+ return processes.length;
191
+ }
192
+ async getProcessLogs(id) {
193
+ const s = await this.native();
194
+ let cursor = 0;
195
+ let stdout = "", stderr = "";
196
+ for (;;) {
197
+ const r = await this.runtime.transport.json({
198
+ method: "GET",
199
+ path: `/v1/sandboxes/${encodeURIComponent(s.id)}/processes/${encodeURIComponent(id)}/output`,
200
+ query: { cursor, maxBytes: 196608 },
201
+ });
202
+ if (r.truncated)
203
+ throw new Error("Process logs were truncated by the host");
204
+ for (const c of r.chunks) {
205
+ if (c.stream === "stdout")
206
+ stdout += c.text;
207
+ else
208
+ stderr += c.text;
209
+ }
210
+ if (r.chunks.length === 0 || r.nextCursor <= cursor)
211
+ break;
212
+ cursor = r.nextCursor;
213
+ }
214
+ return { stdout, stderr, processId: id };
215
+ }
216
+ async exposePort(port, options) {
217
+ only("Cloudflare exposePort", options, ["name", "hostname", "token"]);
218
+ if (options.token)
219
+ throw new CompatibilityError("Cloudflare", "caller-chosen preview tokens");
220
+ const p = await (await this.native()).previews.create(port);
221
+ // Cloudflare's hostname routes through its Worker. Runtime returns its own signed URL.
222
+ return { url: p.urlWithToken ?? p.url, port, name: options.name };
223
+ }
224
+ async unexposePort(port) {
225
+ await (await this.native()).previews.delete(port);
226
+ }
227
+ }
228
+ export class ExecutionSession {
229
+ sandbox;
230
+ id;
231
+ options;
232
+ constructor(sandbox, id, options) {
233
+ this.sandbox = sandbox;
234
+ this.id = id;
235
+ this.options = options;
236
+ }
237
+ async context(options = {}) {
238
+ only("Cloudflare exec", options, [
239
+ "timeout",
240
+ "env",
241
+ "cwd",
242
+ "encoding",
243
+ "stream",
244
+ "onOutput",
245
+ "onComplete",
246
+ "onError",
247
+ "signal",
248
+ ]);
249
+ if (options.encoding && !["utf8", "utf-8"].includes(options.encoding))
250
+ throw new CompatibilityError("Cloudflare", `process encoding ${options.encoding}`);
251
+ const s = await this.sandbox.native();
252
+ return {
253
+ s,
254
+ options: await execOptions(s, {
255
+ cwd: options.cwd ?? this.options.cwd,
256
+ env: { ...cleanEnv(this.options.env), ...cleanEnv(options.env) },
257
+ timeoutMs: options.timeout ?? this.options.commandTimeoutMs,
258
+ signal: options.signal,
259
+ onStdout: (data) => {
260
+ if (options.stream)
261
+ options.onOutput?.("stdout", data);
262
+ },
263
+ onStderr: (data) => {
264
+ if (options.stream)
265
+ options.onOutput?.("stderr", data);
266
+ },
267
+ }),
268
+ };
269
+ }
270
+ async exec(command, options = {}) {
271
+ options.signal?.throwIfAborted();
272
+ const started = Date.now();
273
+ try {
274
+ const c = await this.context(options);
275
+ const r = await c.s.exec(await shellCommand(c.s, command, `cf:${this.id}`, {
276
+ env: { ...cleanEnv(this.options.env), ...cleanEnv(options.env) },
277
+ cwd: options.cwd,
278
+ }), c.options);
279
+ const result = {
280
+ success: r.exitCode === 0 && !r.timedOut,
281
+ exitCode: r.exitCode ?? -1,
282
+ stdout: r.stdout,
283
+ stderr: r.stderr,
284
+ command,
285
+ duration: Date.now() - started,
286
+ timestamp: new Date(started).toISOString(),
287
+ sessionId: this.id,
288
+ };
289
+ if (options.stream)
290
+ options.onComplete?.(result);
291
+ return result;
292
+ }
293
+ catch (e) {
294
+ options.onError?.(e instanceof Error ? e : new Error(String(e)));
295
+ throw e;
296
+ }
297
+ }
298
+ async execStream(command, options = {}) {
299
+ const encode = new TextEncoder();
300
+ const started = new Date().toISOString();
301
+ const run = this.exec.bind(this);
302
+ const sessionId = this.id;
303
+ const cancellation = new AbortController();
304
+ const signal = options.signal
305
+ ? AbortSignal.any([options.signal, cancellation.signal])
306
+ : cancellation.signal;
307
+ return new ReadableStream({
308
+ async start(controller) {
309
+ const send = (event) => {
310
+ if (!signal.aborted)
311
+ controller.enqueue(encode.encode(`data: ${JSON.stringify(event)}\n\n`));
312
+ };
313
+ try {
314
+ send({ type: "start", command, timestamp: started, sessionId: sessionId });
315
+ const result = await run(command, {
316
+ ...options,
317
+ signal,
318
+ stream: true,
319
+ onOutput: (type, data) => send({ type, data, timestamp: new Date().toISOString(), sessionId: sessionId }),
320
+ });
321
+ send({
322
+ type: "complete",
323
+ result,
324
+ exitCode: result.exitCode,
325
+ timestamp: new Date().toISOString(),
326
+ });
327
+ if (!cancellation.signal.aborted)
328
+ controller.close();
329
+ }
330
+ catch (error) {
331
+ if (!cancellation.signal.aborted)
332
+ controller.error(error);
333
+ }
334
+ },
335
+ cancel(reason) {
336
+ cancellation.abort(reason);
337
+ },
338
+ });
339
+ }
340
+ async startProcess(command, options = {}) {
341
+ only("Cloudflare startProcess", options, [
342
+ "timeout",
343
+ "env",
344
+ "cwd",
345
+ "encoding",
346
+ "sessionId",
347
+ "onOutput",
348
+ "onExit",
349
+ "onStart",
350
+ "onError",
351
+ ]);
352
+ const c = await this.context({
353
+ timeout: options.timeout,
354
+ env: options.env,
355
+ cwd: options.cwd,
356
+ encoding: options.encoding,
357
+ });
358
+ const { stdin: _stdin, ...spawnOptions } = c.options;
359
+ const p = await c.s.spawn(await shellCommand(c.s, command, `cf:${this.id}`, {
360
+ env: { ...cleanEnv(this.options.env), ...cleanEnv(options.env) },
361
+ cwd: options.cwd,
362
+ persist: false,
363
+ }), spawnOptions);
364
+ const process = new Process(this.sandbox, p, this.id);
365
+ options.onStart?.(process);
366
+ if (options.onOutput || options.onExit || options.onError)
367
+ void (async () => {
368
+ for await (const e of p.output())
369
+ if (e.type === "stdout" || e.type === "stderr")
370
+ options.onOutput?.(e.type, e.data);
371
+ else if (e.type === "exit")
372
+ options.onExit?.(e.exitCode);
373
+ else if (e.type === "truncated")
374
+ throw new Error("Process output was truncated");
375
+ })().catch((e) => options.onError?.(e instanceof Error ? e : new Error(String(e))));
376
+ return process;
377
+ }
378
+ async path(path) {
379
+ if (path.startsWith("/"))
380
+ return path;
381
+ let cwd = this.options.cwd ?? "/workspace";
382
+ try {
383
+ cwd = await (await this.sandbox.native()).files.readText(`${await shellRoot(`cf:${this.id}`)}/cwd`);
384
+ }
385
+ catch (error) {
386
+ if (!(error instanceof NotFoundError))
387
+ throw error;
388
+ }
389
+ return pathAt(path, cwd);
390
+ }
391
+ async writeFile(path, content, options = {}) {
392
+ if (options.encoding && !["utf8", "utf-8", "base64"].includes(options.encoding))
393
+ throw new CompatibilityError("Cloudflare", `file encoding ${options.encoding}`);
394
+ let bytes;
395
+ if (typeof content === "string")
396
+ bytes =
397
+ options.encoding === "base64"
398
+ ? Uint8Array.from(atob(content), (c) => c.charCodeAt(0))
399
+ : new TextEncoder().encode(content);
400
+ else {
401
+ const buffer = await new Response(content).arrayBuffer();
402
+ bytes = new Uint8Array(buffer);
403
+ }
404
+ await (await this.sandbox.native()).files.write(await this.path(path), bytes);
405
+ return { success: true, path, bytesWritten: bytes.length, timestamp: new Date().toISOString() };
406
+ }
407
+ async readFile(path, options = {}) {
408
+ const s = await this.sandbox.native(), bytes = await s.files.read(await this.path(path));
409
+ if (options.encoding === "none")
410
+ return {
411
+ success: true,
412
+ path,
413
+ content: new ReadableStream({
414
+ start(c) {
415
+ c.enqueue(bytes);
416
+ c.close();
417
+ },
418
+ }),
419
+ size: bytes.length,
420
+ mimeType: "application/octet-stream",
421
+ timestamp: new Date().toISOString(),
422
+ };
423
+ const text = new TextDecoder().decode(bytes), binary = text.includes("\0") || text.includes("\ufffd"), encoded = options.encoding === "base64" || (options.encoding === undefined && binary);
424
+ let raw = "";
425
+ if (encoded)
426
+ for (let i = 0; i < bytes.length; i += 32768)
427
+ raw += String.fromCharCode(...bytes.subarray(i, i + 32768));
428
+ return {
429
+ success: true,
430
+ path,
431
+ content: encoded ? btoa(raw) : text,
432
+ encoding: encoded ? "base64" : "utf-8",
433
+ isBinary: binary,
434
+ size: bytes.length,
435
+ timestamp: new Date().toISOString(),
436
+ };
437
+ }
438
+ async mkdir(path, options = {}) {
439
+ await (await this.sandbox.native()).files.mkdir(await this.path(path), { parents: options.recursive });
440
+ return { success: true, path, timestamp: new Date().toISOString() };
441
+ }
442
+ async deleteFile(path) {
443
+ await (await this.sandbox.native()).files.remove(await this.path(path));
444
+ return { success: true, path, timestamp: new Date().toISOString() };
445
+ }
446
+ async renameFile(path, newPath) {
447
+ await (await this.sandbox.native()).files.rename(await this.path(path), await this.path(newPath));
448
+ return { success: true, path, newPath, timestamp: new Date().toISOString() };
449
+ }
450
+ async exists(path) {
451
+ return {
452
+ success: true,
453
+ path,
454
+ exists: await (await this.sandbox.native()).files.exists(await this.path(path)),
455
+ timestamp: new Date().toISOString(),
456
+ };
457
+ }
458
+ async listFiles(path, options = {}) {
459
+ const s = await this.sandbox.native();
460
+ const directory = await this.path(path);
461
+ const entries = await s.files.list(directory, { hidden: options.includeHidden });
462
+ if (options.recursive) {
463
+ for (let i = 0; i < entries.length; i++)
464
+ if (entries[i].type === "directory")
465
+ entries.push(...(await s.files.list(entries[i].path, { hidden: options.includeHidden })));
466
+ }
467
+ const files = entries.map((e) => ({
468
+ ...e,
469
+ absolutePath: e.path,
470
+ relativePath: e.path.slice(directory.replace(/\/$/, "").length + 1),
471
+ permissions: {
472
+ readable: !!(parseInt(e.mode, 8) & 0o444),
473
+ writable: !!(parseInt(e.mode, 8) & 0o222),
474
+ executable: !!(parseInt(e.mode, 8) & 0o111),
475
+ },
476
+ }));
477
+ return { success: true, path, files, count: files.length, timestamp: new Date().toISOString() };
478
+ }
479
+ async setEnvVars(env) {
480
+ this.options = { ...this.options, env: { ...this.options.env, ...cleanEnv(env) } };
481
+ await (await this.sandbox.native()).files.write(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(this.id)}.json`, JSON.stringify(this.options), { mode: 0o600 });
482
+ }
483
+ }
484
+ export class Process {
485
+ sandbox;
486
+ native;
487
+ sessionId;
488
+ constructor(sandbox, native, sessionId) {
489
+ this.sandbox = sandbox;
490
+ this.native = native;
491
+ this.sessionId = sessionId;
492
+ }
493
+ get id() {
494
+ return this.native.id;
495
+ }
496
+ get command() {
497
+ return this.native.info.command;
498
+ }
499
+ get status() {
500
+ return this.native.info.state === "exited"
501
+ ? this.native.info.exitCode === 0
502
+ ? "completed"
503
+ : "failed"
504
+ : this.native.info.state === "timed_out"
505
+ ? "failed"
506
+ : this.native.info.state;
507
+ }
508
+ get startTime() {
509
+ return new Date(this.native.info.startedAt);
510
+ }
511
+ get endTime() {
512
+ return this.native.info.endedAt ? new Date(this.native.info.endedAt) : undefined;
513
+ }
514
+ get exitCode() {
515
+ return this.native.info.exitCode ?? undefined;
516
+ }
517
+ async kill(signal = "SIGTERM") {
518
+ if (!["SIGTERM", "SIGKILL", "SIGINT", "SIGHUP", "SIGQUIT", "SIGUSR1", "SIGUSR2"].includes(signal))
519
+ throw new TypeError(`Unsupported signal ${signal}`);
520
+ await this.native.kill(signal);
521
+ }
522
+ async getStatus() {
523
+ await this.native.refresh();
524
+ return this.status;
525
+ }
526
+ getLogs() {
527
+ return this.sandbox.getProcessLogs(this.id);
528
+ }
529
+ async waitForExit(timeout) {
530
+ const r = await this.native.wait({
531
+ signal: timeout ? AbortSignal.timeout(timeout) : undefined,
532
+ });
533
+ return { exitCode: r.exitCode ?? -1 };
534
+ }
535
+ async waitForLog(pattern, timeout = 30000) {
536
+ let output = "";
537
+ for await (const e of this.native.output({ signal: AbortSignal.timeout(timeout) })) {
538
+ if (e.type === "stdout" || e.type === "stderr") {
539
+ output += e.data;
540
+ const match = typeof pattern === "string" ? output.includes(pattern) : output.match(pattern);
541
+ if (match)
542
+ return {
543
+ line: output
544
+ .split("\n")
545
+ .find((line) => typeof pattern === "string"
546
+ ? line.includes(pattern)
547
+ : new RegExp(pattern.source, pattern.flags.replace("g", "")).test(line)) ?? output,
548
+ ...(typeof pattern === "string" ? {} : { match }),
549
+ };
550
+ }
551
+ }
552
+ throw new Error("Process exited before expected output");
553
+ }
554
+ }