@cloudflare/sandbox 0.0.0-d86b60e → 0.0.0-da2cfb8

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 (104) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/Dockerfile +91 -50
  3. package/README.md +88 -772
  4. package/dist/chunk-BFVUNTP4.js +104 -0
  5. package/dist/chunk-BFVUNTP4.js.map +1 -0
  6. package/dist/chunk-EKSWCBCA.js +86 -0
  7. package/dist/chunk-EKSWCBCA.js.map +1 -0
  8. package/dist/chunk-JXZMAU2C.js +559 -0
  9. package/dist/chunk-JXZMAU2C.js.map +1 -0
  10. package/dist/chunk-PG2V52M2.js +2420 -0
  11. package/dist/chunk-PG2V52M2.js.map +1 -0
  12. package/dist/chunk-QDBKO3CL.js +7 -0
  13. package/dist/chunk-QDBKO3CL.js.map +1 -0
  14. package/dist/chunk-Z532A7QC.js +78 -0
  15. package/dist/chunk-Z532A7QC.js.map +1 -0
  16. package/dist/file-stream.d.ts +43 -0
  17. package/dist/file-stream.js +9 -0
  18. package/dist/file-stream.js.map +1 -0
  19. package/dist/index.d.ts +9 -0
  20. package/dist/index.js +67 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/interpreter.d.ts +33 -0
  23. package/dist/interpreter.js +8 -0
  24. package/dist/interpreter.js.map +1 -0
  25. package/dist/request-handler.d.ts +18 -0
  26. package/dist/request-handler.js +13 -0
  27. package/dist/request-handler.js.map +1 -0
  28. package/dist/sandbox-DMlNr93l.d.ts +596 -0
  29. package/dist/sandbox.d.ts +4 -0
  30. package/dist/sandbox.js +13 -0
  31. package/dist/sandbox.js.map +1 -0
  32. package/dist/security.d.ts +31 -0
  33. package/dist/security.js +13 -0
  34. package/dist/security.js.map +1 -0
  35. package/dist/sse-parser.d.ts +28 -0
  36. package/dist/sse-parser.js +11 -0
  37. package/dist/sse-parser.js.map +1 -0
  38. package/dist/version.d.ts +8 -0
  39. package/dist/version.js +7 -0
  40. package/dist/version.js.map +1 -0
  41. package/package.json +12 -4
  42. package/src/clients/base-client.ts +280 -0
  43. package/src/clients/command-client.ts +115 -0
  44. package/src/clients/file-client.ts +269 -0
  45. package/src/clients/git-client.ts +92 -0
  46. package/src/clients/index.ts +64 -0
  47. package/src/{interpreter-client.ts → clients/interpreter-client.ts} +148 -171
  48. package/src/clients/port-client.ts +105 -0
  49. package/src/clients/process-client.ts +177 -0
  50. package/src/clients/sandbox-client.ts +41 -0
  51. package/src/clients/types.ts +84 -0
  52. package/src/clients/utility-client.ts +119 -0
  53. package/src/errors/adapter.ts +180 -0
  54. package/src/errors/classes.ts +469 -0
  55. package/src/errors/index.ts +105 -0
  56. package/src/file-stream.ts +164 -0
  57. package/src/index.ts +81 -63
  58. package/src/interpreter.ts +17 -8
  59. package/src/request-handler.ts +69 -43
  60. package/src/sandbox.ts +781 -531
  61. package/src/security.ts +14 -23
  62. package/src/sse-parser.ts +4 -8
  63. package/src/version.ts +6 -0
  64. package/startup.sh +3 -0
  65. package/tests/base-client.test.ts +328 -0
  66. package/tests/command-client.test.ts +407 -0
  67. package/tests/file-client.test.ts +643 -0
  68. package/tests/file-stream.test.ts +306 -0
  69. package/tests/get-sandbox.test.ts +110 -0
  70. package/tests/git-client.test.ts +328 -0
  71. package/tests/port-client.test.ts +301 -0
  72. package/tests/process-client.test.ts +658 -0
  73. package/tests/sandbox.test.ts +465 -0
  74. package/tests/sse-parser.test.ts +290 -0
  75. package/tests/utility-client.test.ts +332 -0
  76. package/tests/version.test.ts +16 -0
  77. package/tests/wrangler.jsonc +35 -0
  78. package/tsconfig.json +9 -1
  79. package/vitest.config.ts +31 -0
  80. package/container_src/bun.lock +0 -76
  81. package/container_src/circuit-breaker.ts +0 -121
  82. package/container_src/control-process.ts +0 -784
  83. package/container_src/handler/exec.ts +0 -185
  84. package/container_src/handler/file.ts +0 -406
  85. package/container_src/handler/git.ts +0 -130
  86. package/container_src/handler/ports.ts +0 -314
  87. package/container_src/handler/process.ts +0 -568
  88. package/container_src/handler/session.ts +0 -92
  89. package/container_src/index.ts +0 -592
  90. package/container_src/interpreter-service.ts +0 -276
  91. package/container_src/isolation.ts +0 -1049
  92. package/container_src/mime-processor.ts +0 -255
  93. package/container_src/package.json +0 -18
  94. package/container_src/runtime/executors/javascript/node_executor.ts +0 -123
  95. package/container_src/runtime/executors/python/ipython_executor.py +0 -338
  96. package/container_src/runtime/executors/typescript/ts_executor.ts +0 -138
  97. package/container_src/runtime/process-pool.ts +0 -464
  98. package/container_src/shell-escape.ts +0 -42
  99. package/container_src/startup.sh +0 -11
  100. package/container_src/types.ts +0 -131
  101. package/src/client.ts +0 -1009
  102. package/src/errors.ts +0 -219
  103. package/src/interpreter-types.ts +0 -390
  104. package/src/types.ts +0 -502
@@ -1,25 +1,17 @@
1
- import { HttpClient } from "./client.js";
2
- import { isRetryableError, parseErrorResponse } from "./errors.js";
3
- import type {
4
- CodeContext,
5
- CreateContextOptions,
6
- ExecutionError,
7
- OutputMessage,
8
- Result,
9
- } from "./interpreter-types.js";
10
-
11
- // API Response types
12
- interface ContextResponse {
13
- id: string;
14
- language: string;
15
- cwd: string;
16
- createdAt: string; // ISO date string from JSON
17
- lastUsed: string; // ISO date string from JSON
18
- }
19
-
20
- interface ContextListResponse {
21
- contexts: ContextResponse[];
22
- }
1
+ import {
2
+ type CodeContext,
3
+ type ContextCreateResult,
4
+ type ContextListResult,
5
+ type CreateContextOptions,
6
+ type ExecutionError,
7
+ type OutputMessage,
8
+ type Result,
9
+ ResultImpl,
10
+ } from '@repo/shared';
11
+ import type { ErrorResponse } from '../errors';
12
+ import { createErrorFromResponse, ErrorCode, InterpreterNotReadyError } from '../errors';
13
+ import { BaseHttpClient } from './base-client.js';
14
+ import type { HttpClientOptions } from './types.js';
23
15
 
24
16
  // Streaming execution data from the server
25
17
  interface StreamingExecutionData {
@@ -62,7 +54,7 @@ export interface ExecutionCallbacks {
62
54
  onError?: (error: ExecutionError) => void | Promise<void>;
63
55
  }
64
56
 
65
- export class InterpreterClient extends HttpClient {
57
+ export class InterpreterClient extends BaseHttpClient {
66
58
  private readonly maxRetries = 3;
67
59
  private readonly retryDelayMs = 1000;
68
60
 
@@ -81,16 +73,21 @@ export class InterpreterClient extends HttpClient {
81
73
  });
82
74
 
83
75
  if (!response.ok) {
84
- throw await parseErrorResponse(response);
76
+ const error = await this.parseErrorResponse(response);
77
+ throw error;
78
+ }
79
+
80
+ const data = (await response.json()) as ContextCreateResult;
81
+ if (!data.success) {
82
+ throw new Error(`Failed to create context: ${JSON.stringify(data)}`);
85
83
  }
86
84
 
87
- const data = (await response.json()) as ContextResponse;
88
85
  return {
89
- id: data.id,
86
+ id: data.contextId,
90
87
  language: data.language,
91
- cwd: data.cwd,
92
- createdAt: new Date(data.createdAt),
93
- lastUsed: new Date(data.lastUsed),
88
+ cwd: data.cwd || '/workspace',
89
+ createdAt: new Date(data.timestamp),
90
+ lastUsed: new Date(data.timestamp),
94
91
  };
95
92
  });
96
93
  }
@@ -99,7 +96,8 @@ export class InterpreterClient extends HttpClient {
99
96
  contextId: string | undefined,
100
97
  code: string,
101
98
  language: string | undefined,
102
- callbacks: ExecutionCallbacks
99
+ callbacks: ExecutionCallbacks,
100
+ timeoutMs?: number
103
101
  ): Promise<void> {
104
102
  return this.executeWithRetry(async () => {
105
103
  const response = await this.doFetch("/api/execute/code", {
@@ -112,11 +110,13 @@ export class InterpreterClient extends HttpClient {
112
110
  context_id: contextId,
113
111
  code,
114
112
  language,
113
+ ...(timeoutMs !== undefined && { timeout_ms: timeoutMs })
115
114
  }),
116
115
  });
117
116
 
118
117
  if (!response.ok) {
119
- throw await parseErrorResponse(response);
118
+ const error = await this.parseErrorResponse(response);
119
+ throw error;
120
120
  }
121
121
 
122
122
  if (!response.body) {
@@ -130,6 +130,112 @@ export class InterpreterClient extends HttpClient {
130
130
  });
131
131
  }
132
132
 
133
+ async listCodeContexts(): Promise<CodeContext[]> {
134
+ return this.executeWithRetry(async () => {
135
+ const response = await this.doFetch("/api/contexts", {
136
+ method: "GET",
137
+ headers: { "Content-Type": "application/json" },
138
+ });
139
+
140
+ if (!response.ok) {
141
+ const error = await this.parseErrorResponse(response);
142
+ throw error;
143
+ }
144
+
145
+ const data = (await response.json()) as ContextListResult;
146
+ if (!data.success) {
147
+ throw new Error(`Failed to list contexts: ${JSON.stringify(data)}`);
148
+ }
149
+
150
+ return data.contexts.map((ctx) => ({
151
+ id: ctx.id,
152
+ language: ctx.language,
153
+ cwd: ctx.cwd || '/workspace',
154
+ createdAt: new Date(data.timestamp),
155
+ lastUsed: new Date(data.timestamp),
156
+ }));
157
+ });
158
+ }
159
+
160
+ async deleteCodeContext(contextId: string): Promise<void> {
161
+ return this.executeWithRetry(async () => {
162
+ const response = await this.doFetch(`/api/contexts/${contextId}`, {
163
+ method: "DELETE",
164
+ headers: { "Content-Type": "application/json" },
165
+ });
166
+
167
+ if (!response.ok) {
168
+ const error = await this.parseErrorResponse(response);
169
+ throw error;
170
+ }
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Execute an operation with automatic retry for transient errors
176
+ */
177
+ private async executeWithRetry<T>(operation: () => Promise<T>): Promise<T> {
178
+ let lastError: Error | undefined;
179
+
180
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
181
+ try {
182
+ return await operation();
183
+ } catch (error) {
184
+ this.logError('executeWithRetry', error);
185
+ lastError = error as Error;
186
+
187
+ // Check if it's a retryable error (interpreter not ready)
188
+ if (this.isRetryableError(error)) {
189
+ // Don't retry on the last attempt
190
+ if (attempt < this.maxRetries - 1) {
191
+ // Exponential backoff with jitter
192
+ const delay =
193
+ this.retryDelayMs * 2 ** attempt + Math.random() * 1000;
194
+ await new Promise((resolve) => setTimeout(resolve, delay));
195
+ continue;
196
+ }
197
+ }
198
+
199
+ // Not retryable or last attempt - throw the error
200
+ throw error;
201
+ }
202
+ }
203
+
204
+ throw lastError || new Error("Execution failed after retries");
205
+ }
206
+
207
+ private isRetryableError(error: unknown): boolean {
208
+ if (error instanceof InterpreterNotReadyError) {
209
+ return true;
210
+ }
211
+
212
+ if (error instanceof Error) {
213
+ return (
214
+ error.message.includes("not ready") ||
215
+ error.message.includes("initializing")
216
+ );
217
+ }
218
+
219
+ return false;
220
+ }
221
+
222
+ private async parseErrorResponse(response: Response): Promise<Error> {
223
+ try {
224
+ const errorData = await response.json() as ErrorResponse;
225
+ return createErrorFromResponse(errorData);
226
+ } catch {
227
+ // Fallback if response isn't JSON
228
+ const errorResponse: ErrorResponse = {
229
+ code: ErrorCode.INTERNAL_ERROR,
230
+ message: `HTTP ${response.status}: ${response.statusText}`,
231
+ context: {},
232
+ httpStatus: response.status,
233
+ timestamp: new Date().toISOString()
234
+ };
235
+ return createErrorFromResponse(errorResponse);
236
+ }
237
+ }
238
+
133
239
  private async *readLines(
134
240
  stream: ReadableStream<Uint8Array>
135
241
  ): AsyncGenerator<string> {
@@ -167,8 +273,13 @@ export class InterpreterClient extends HttpClient {
167
273
  ) {
168
274
  if (!line.trim()) return;
169
275
 
276
+ // Skip lines that don't start with "data: " (SSE format)
277
+ if (!line.startsWith('data: ')) return;
278
+
170
279
  try {
171
- const data = JSON.parse(line) as StreamingExecutionData;
280
+ // Strip "data: " prefix and parse JSON
281
+ const jsonData = line.substring(6); // "data: " is 6 characters
282
+ const data = JSON.parse(jsonData) as StreamingExecutionData;
172
283
 
173
284
  switch (data.type) {
174
285
  case "stdout":
@@ -191,34 +302,8 @@ export class InterpreterClient extends HttpClient {
191
302
 
192
303
  case "result":
193
304
  if (callbacks.onResult) {
194
- // Convert raw result to Result interface
195
- const result: Result = {
196
- text: data.text,
197
- html: data.html,
198
- png: data.png,
199
- jpeg: data.jpeg,
200
- svg: data.svg,
201
- latex: data.latex,
202
- markdown: data.markdown,
203
- javascript: data.javascript,
204
- json: data.json,
205
- chart: data.chart,
206
- data: data.data,
207
- formats: () => {
208
- const formats: string[] = [];
209
- if (data.text) formats.push("text");
210
- if (data.html) formats.push("html");
211
- if (data.png) formats.push("png");
212
- if (data.jpeg) formats.push("jpeg");
213
- if (data.svg) formats.push("svg");
214
- if (data.latex) formats.push("latex");
215
- if (data.markdown) formats.push("markdown");
216
- if (data.javascript) formats.push("javascript");
217
- if (data.json) formats.push("json");
218
- if (data.chart) formats.push("chart");
219
- return formats;
220
- },
221
- };
305
+ // Create a ResultImpl instance from the raw data
306
+ const result = new ResultImpl(data);
222
307
  await callbacks.onResult(result);
223
308
  }
224
309
  break;
@@ -227,126 +312,18 @@ export class InterpreterClient extends HttpClient {
227
312
  if (callbacks.onError) {
228
313
  await callbacks.onError({
229
314
  name: data.ename || "Error",
230
- value: data.evalue || data.text || "Unknown error",
315
+ message: data.evalue || "Unknown error",
231
316
  traceback: data.traceback || [],
232
- lineNumber: data.lineNumber,
233
317
  });
234
318
  }
235
319
  break;
236
320
 
237
321
  case "execution_complete":
238
- // Execution completed successfully
322
+ // Signal completion - callbacks can handle cleanup if needed
239
323
  break;
240
324
  }
241
325
  } catch (error) {
242
- console.error(
243
- "[InterpreterClient] Error parsing execution result:",
244
- error
245
- );
326
+ this.logError('parseExecutionResult', error);
246
327
  }
247
328
  }
248
-
249
- async listCodeContexts(): Promise<CodeContext[]> {
250
- return this.executeWithRetry(async () => {
251
- const response = await this.doFetch("/api/contexts", {
252
- method: "GET",
253
- headers: { "Content-Type": "application/json" },
254
- });
255
-
256
- if (!response.ok) {
257
- throw await parseErrorResponse(response);
258
- }
259
-
260
- const data = (await response.json()) as ContextListResponse;
261
- return data.contexts.map((ctx) => ({
262
- id: ctx.id,
263
- language: ctx.language,
264
- cwd: ctx.cwd,
265
- createdAt: new Date(ctx.createdAt),
266
- lastUsed: new Date(ctx.lastUsed),
267
- }));
268
- });
269
- }
270
-
271
- async deleteCodeContext(contextId: string): Promise<void> {
272
- return this.executeWithRetry(async () => {
273
- const response = await this.doFetch(`/api/contexts/${contextId}`, {
274
- method: "DELETE",
275
- headers: { "Content-Type": "application/json" },
276
- });
277
-
278
- if (!response.ok) {
279
- throw await parseErrorResponse(response);
280
- }
281
- });
282
- }
283
-
284
- // Override parent doFetch to be public for this class
285
- public async doFetch(path: string, options?: RequestInit): Promise<Response> {
286
- return super.doFetch(path, options);
287
- }
288
-
289
- /**
290
- * Execute an operation with automatic retry for transient errors
291
- */
292
- private async executeWithRetry<T>(operation: () => Promise<T>): Promise<T> {
293
- let lastError: Error | undefined;
294
-
295
- for (let attempt = 0; attempt < this.maxRetries; attempt++) {
296
- try {
297
- return await operation();
298
- } catch (error) {
299
- lastError = error as Error;
300
-
301
- // Check if it's a retryable error (circuit breaker or interpreter not ready)
302
- if (this.isRetryableError(error)) {
303
- // Don't retry on the last attempt
304
- if (attempt < this.maxRetries - 1) {
305
- // Exponential backoff with jitter
306
- const delay =
307
- this.retryDelayMs * 2 ** attempt + Math.random() * 1000;
308
- await new Promise((resolve) => setTimeout(resolve, delay));
309
- continue;
310
- }
311
- }
312
-
313
- // Non-retryable error or last attempt - throw immediately
314
- throw error;
315
- }
316
- }
317
-
318
- // All retries exhausted - throw a clean error without implementation details
319
- if (lastError?.message.includes("Code execution")) {
320
- // If the error already has a clean message about code execution, use it
321
- throw lastError;
322
- }
323
-
324
- // Otherwise, throw a generic but user-friendly error
325
- throw new Error("Unable to execute code at this time");
326
- }
327
-
328
- /**
329
- * Check if an error is retryable
330
- */
331
- private isRetryableError(error: unknown): boolean {
332
- // Use the SDK's built-in retryable check
333
- if (isRetryableError(error)) {
334
- return true;
335
- }
336
-
337
- // Also check for circuit breaker specific errors
338
- if (error instanceof Error) {
339
- // Circuit breaker errors (from the container's response)
340
- if (error.message.includes("Circuit breaker is open")) {
341
- return true;
342
- }
343
-
344
- // Check if error has a status property
345
- if ("status" in error && error.status === "circuit_open") {
346
- return true;
347
- }
348
- }
349
-
350
- return false;
351
- }
352
329
  }
@@ -0,0 +1,105 @@
1
+ import type {
2
+ PortCloseResult,
3
+ PortExposeResult,
4
+ PortListResult,
5
+ } from '@repo/shared';
6
+ import { BaseHttpClient } from './base-client';
7
+ import type { HttpClientOptions } from './types';
8
+
9
+ // Re-export for convenience
10
+ export type {
11
+ PortExposeResult,
12
+ PortCloseResult,
13
+ PortListResult,
14
+ };
15
+
16
+ /**
17
+ * Request interface for exposing ports
18
+ */
19
+ export interface ExposePortRequest {
20
+ port: number;
21
+ name?: string;
22
+ }
23
+
24
+ /**
25
+ * Request interface for unexposing ports
26
+ */
27
+ export interface UnexposePortRequest {
28
+ port: number;
29
+ }
30
+
31
+ /**
32
+ * Client for port management and preview URL operations
33
+ */
34
+ export class PortClient extends BaseHttpClient {
35
+
36
+ /**
37
+ * Expose a port and get a preview URL
38
+ * @param port - Port number to expose
39
+ * @param sessionId - The session ID for this operation
40
+ * @param name - Optional name for the port
41
+ */
42
+ async exposePort(
43
+ port: number,
44
+ sessionId: string,
45
+ name?: string
46
+ ): Promise<PortExposeResult> {
47
+ try {
48
+ const data = { port, sessionId, name };
49
+
50
+ const response = await this.post<PortExposeResult>(
51
+ '/api/expose-port',
52
+ data
53
+ );
54
+
55
+ this.logSuccess(
56
+ 'Port exposed',
57
+ `${port} exposed at ${response.url}${name ? ` (${name})` : ''}`
58
+ );
59
+
60
+ return response;
61
+ } catch (error) {
62
+ this.logError('exposePort', error);
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Unexpose a port and remove its preview URL
69
+ * @param port - Port number to unexpose
70
+ * @param sessionId - The session ID for this operation
71
+ */
72
+ async unexposePort(port: number, sessionId: string): Promise<PortCloseResult> {
73
+ try {
74
+ const url = `/api/exposed-ports/${port}?session=${encodeURIComponent(sessionId)}`;
75
+ const response = await this.delete<PortCloseResult>(url);
76
+
77
+ this.logSuccess('Port unexposed', `${port}`);
78
+ return response;
79
+ } catch (error) {
80
+ this.logError('unexposePort', error);
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Get all currently exposed ports
87
+ * @param sessionId - The session ID for this operation
88
+ */
89
+ async getExposedPorts(sessionId: string): Promise<PortListResult> {
90
+ try {
91
+ const url = `/api/exposed-ports?session=${encodeURIComponent(sessionId)}`;
92
+ const response = await this.get<PortListResult>(url);
93
+
94
+ this.logSuccess(
95
+ 'Exposed ports retrieved',
96
+ `${response.ports.length} ports exposed`
97
+ );
98
+
99
+ return response;
100
+ } catch (error) {
101
+ this.logError('getExposedPorts', error);
102
+ throw error;
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,177 @@
1
+ import type {
2
+ ProcessCleanupResult,
3
+ ProcessInfoResult,
4
+ ProcessKillResult,
5
+ ProcessListResult,
6
+ ProcessLogsResult,
7
+ ProcessStartResult,
8
+ StartProcessRequest,
9
+ } from '@repo/shared';
10
+ import { BaseHttpClient } from './base-client';
11
+ import type { HttpClientOptions } from './types';
12
+
13
+ // Re-export for convenience
14
+ export type {
15
+ StartProcessRequest,
16
+ ProcessStartResult,
17
+ ProcessListResult,
18
+ ProcessInfoResult,
19
+ ProcessKillResult,
20
+ ProcessLogsResult,
21
+ ProcessCleanupResult,
22
+ };
23
+
24
+
25
+ /**
26
+ * Client for background process management
27
+ */
28
+ export class ProcessClient extends BaseHttpClient {
29
+
30
+ /**
31
+ * Start a background process
32
+ * @param command - Command to execute as a background process
33
+ * @param sessionId - The session ID for this operation
34
+ * @param options - Optional settings (processId)
35
+ */
36
+ async startProcess(
37
+ command: string,
38
+ sessionId: string,
39
+ options?: { processId?: string }
40
+ ): Promise<ProcessStartResult> {
41
+ try {
42
+ const data: StartProcessRequest = {
43
+ command,
44
+ sessionId,
45
+ processId: options?.processId,
46
+ };
47
+
48
+ const response = await this.post<ProcessStartResult>(
49
+ '/api/process/start',
50
+ data
51
+ );
52
+
53
+ this.logSuccess(
54
+ 'Process started',
55
+ `${command} (ID: ${response.processId})`
56
+ );
57
+
58
+ return response;
59
+ } catch (error) {
60
+ this.logError('startProcess', error);
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * List all processes (sandbox-scoped, not session-scoped)
67
+ */
68
+ async listProcesses(): Promise<ProcessListResult> {
69
+ try {
70
+ const url = `/api/process/list`;
71
+ const response = await this.get<ProcessListResult>(url);
72
+
73
+ this.logSuccess('Processes listed', `${response.processes.length} processes`);
74
+ return response;
75
+ } catch (error) {
76
+ this.logError('listProcesses', error);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get information about a specific process (sandbox-scoped, not session-scoped)
83
+ * @param processId - ID of the process to retrieve
84
+ */
85
+ async getProcess(processId: string): Promise<ProcessInfoResult> {
86
+ try {
87
+ const url = `/api/process/${processId}`;
88
+ const response = await this.get<ProcessInfoResult>(url);
89
+
90
+ this.logSuccess('Process retrieved', `ID: ${processId}`);
91
+ return response;
92
+ } catch (error) {
93
+ this.logError('getProcess', error);
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Kill a specific process (sandbox-scoped, not session-scoped)
100
+ * @param processId - ID of the process to kill
101
+ */
102
+ async killProcess(processId: string): Promise<ProcessKillResult> {
103
+ try {
104
+ const url = `/api/process/${processId}`;
105
+ const response = await this.delete<ProcessKillResult>(url);
106
+
107
+ this.logSuccess('Process killed', `ID: ${processId}`);
108
+ return response;
109
+ } catch (error) {
110
+ this.logError('killProcess', error);
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Kill all running processes (sandbox-scoped, not session-scoped)
117
+ */
118
+ async killAllProcesses(): Promise<ProcessCleanupResult> {
119
+ try {
120
+ const url = `/api/process/kill-all`;
121
+ const response = await this.delete<ProcessCleanupResult>(url);
122
+
123
+ this.logSuccess(
124
+ 'All processes killed',
125
+ `${response.cleanedCount} processes terminated`
126
+ );
127
+
128
+ return response;
129
+ } catch (error) {
130
+ this.logError('killAllProcesses', error);
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Get logs from a specific process (sandbox-scoped, not session-scoped)
137
+ * @param processId - ID of the process to get logs from
138
+ */
139
+ async getProcessLogs(processId: string): Promise<ProcessLogsResult> {
140
+ try {
141
+ const url = `/api/process/${processId}/logs`;
142
+ const response = await this.get<ProcessLogsResult>(url);
143
+
144
+ this.logSuccess(
145
+ 'Process logs retrieved',
146
+ `ID: ${processId}, stdout: ${response.stdout.length} chars, stderr: ${response.stderr.length} chars`
147
+ );
148
+
149
+ return response;
150
+ } catch (error) {
151
+ this.logError('getProcessLogs', error);
152
+ throw error;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Stream logs from a specific process (sandbox-scoped, not session-scoped)
158
+ * @param processId - ID of the process to stream logs from
159
+ */
160
+ async streamProcessLogs(processId: string): Promise<ReadableStream<Uint8Array>> {
161
+ try {
162
+ const url = `/api/process/${processId}/stream`;
163
+ const response = await this.doFetch(url, {
164
+ method: 'GET',
165
+ });
166
+
167
+ const stream = await this.handleStreamResponse(response);
168
+
169
+ this.logSuccess('Process log stream started', `ID: ${processId}`);
170
+
171
+ return stream;
172
+ } catch (error) {
173
+ this.logError('streamProcessLogs', error);
174
+ throw error;
175
+ }
176
+ }
177
+ }