@cloudflare/sandbox 0.0.0-c87db11 → 0.0.0-cdb8197

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 (35) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/Dockerfile +32 -29
  3. package/README.md +127 -12
  4. package/container_src/bun.lock +31 -77
  5. package/container_src/control-process.ts +784 -0
  6. package/container_src/handler/exec.ts +99 -254
  7. package/container_src/handler/file.ts +253 -640
  8. package/container_src/handler/git.ts +28 -80
  9. package/container_src/handler/process.ts +443 -515
  10. package/container_src/handler/session.ts +92 -0
  11. package/container_src/index.ts +108 -163
  12. package/container_src/interpreter-service.ts +276 -0
  13. package/container_src/isolation.ts +1213 -0
  14. package/container_src/mime-processor.ts +1 -1
  15. package/container_src/package.json +4 -4
  16. package/container_src/runtime/executors/javascript/node_executor.ts +123 -0
  17. package/container_src/runtime/executors/python/ipython_executor.py +338 -0
  18. package/container_src/runtime/executors/typescript/ts_executor.ts +138 -0
  19. package/container_src/runtime/process-pool.ts +464 -0
  20. package/container_src/shell-escape.ts +42 -0
  21. package/container_src/startup.sh +6 -79
  22. package/container_src/types.ts +35 -12
  23. package/package.json +2 -2
  24. package/src/client.ts +214 -187
  25. package/src/errors.ts +15 -14
  26. package/src/file-stream.ts +162 -0
  27. package/src/index.ts +43 -16
  28. package/src/{jupyter-client.ts → interpreter-client.ts} +6 -3
  29. package/src/interpreter-types.ts +102 -95
  30. package/src/interpreter.ts +8 -8
  31. package/src/sandbox.ts +314 -336
  32. package/src/types.ts +194 -24
  33. package/container_src/jupyter-server.ts +0 -579
  34. package/container_src/jupyter-service.ts +0 -458
  35. package/container_src/jupyter_config.py +0 -48
package/src/client.ts CHANGED
@@ -2,21 +2,22 @@ import type { ExecuteRequest } from "../container_src/types";
2
2
  import type { Sandbox } from "./index";
3
3
  import type {
4
4
  BaseExecOptions,
5
+ DeleteFileResponse,
6
+ ExecuteResponse,
5
7
  GetProcessLogsResponse,
6
8
  GetProcessResponse,
9
+ GitCheckoutResponse,
10
+ ListFilesResponse,
7
11
  ListProcessesResponse,
12
+ MkdirResponse,
13
+ MoveFileResponse,
14
+ ReadFileResponse,
15
+ RenameFileResponse,
8
16
  StartProcessRequest,
9
17
  StartProcessResponse,
18
+ WriteFileResponse,
10
19
  } from "./types";
11
20
 
12
- export interface ExecuteResponse {
13
- success: boolean;
14
- stdout: string;
15
- stderr: string;
16
- exitCode: number;
17
- command: string;
18
- timestamp: string;
19
- }
20
21
 
21
22
  interface CommandsResponse {
22
23
  availableCommands: string[];
@@ -27,104 +28,62 @@ interface GitCheckoutRequest {
27
28
  repoUrl: string;
28
29
  branch?: string;
29
30
  targetDir?: string;
30
- sessionId?: string;
31
+ sessionId: string;
31
32
  }
32
33
 
33
- export interface GitCheckoutResponse {
34
- success: boolean;
35
- stdout: string;
36
- stderr: string;
37
- exitCode: number;
38
- repoUrl: string;
39
- branch: string;
40
- targetDir: string;
41
- timestamp: string;
42
- }
43
34
 
44
35
  interface MkdirRequest {
45
36
  path: string;
46
37
  recursive?: boolean;
47
- sessionId?: string;
38
+ sessionId: string;
48
39
  }
49
40
 
50
- export interface MkdirResponse {
51
- success: boolean;
52
- stdout: string;
53
- stderr: string;
54
- exitCode: number;
55
- path: string;
56
- recursive: boolean;
57
- timestamp: string;
58
- }
59
41
 
60
42
  interface WriteFileRequest {
61
43
  path: string;
62
44
  content: string;
63
45
  encoding?: string;
64
- sessionId?: string;
46
+ sessionId: string;
65
47
  }
66
48
 
67
- export interface WriteFileResponse {
68
- success: boolean;
69
- exitCode: number;
70
- path: string;
71
- timestamp: string;
72
- }
73
49
 
74
50
  interface ReadFileRequest {
75
51
  path: string;
76
52
  encoding?: string;
77
- sessionId?: string;
53
+ sessionId: string;
78
54
  }
79
55
 
80
- export interface ReadFileResponse {
81
- success: boolean;
82
- exitCode: number;
83
- path: string;
84
- content: string;
85
- timestamp: string;
86
- }
87
56
 
88
57
  interface DeleteFileRequest {
89
58
  path: string;
90
- sessionId?: string;
59
+ sessionId: string;
91
60
  }
92
61
 
93
- export interface DeleteFileResponse {
94
- success: boolean;
95
- exitCode: number;
96
- path: string;
97
- timestamp: string;
98
- }
99
62
 
100
63
  interface RenameFileRequest {
101
64
  oldPath: string;
102
65
  newPath: string;
103
- sessionId?: string;
66
+ sessionId: string;
104
67
  }
105
68
 
106
- export interface RenameFileResponse {
107
- success: boolean;
108
- exitCode: number;
109
- oldPath: string;
110
- newPath: string;
111
- timestamp: string;
112
- }
113
69
 
114
70
  interface MoveFileRequest {
115
71
  sourcePath: string;
116
72
  destinationPath: string;
117
- sessionId?: string;
73
+ sessionId: string;
118
74
  }
119
75
 
120
- export interface MoveFileResponse {
121
- success: boolean;
122
- exitCode: number;
123
- sourcePath: string;
124
- destinationPath: string;
125
- timestamp: string;
76
+
77
+ interface ListFilesRequest {
78
+ path: string;
79
+ options?: {
80
+ recursive?: boolean;
81
+ includeHidden?: boolean;
82
+ };
83
+ sessionId: string;
126
84
  }
127
85
 
86
+
128
87
  interface PreviewInfo {
129
88
  url: string;
130
89
  port: number;
@@ -184,7 +143,6 @@ interface HttpClientOptions {
184
143
  export class HttpClient {
185
144
  private baseUrl: string;
186
145
  private options: HttpClientOptions;
187
- private sessionId: string | null = null;
188
146
 
189
147
  constructor(options: HttpClientOptions = {}) {
190
148
  this.options = {
@@ -234,25 +192,52 @@ export class HttpClient {
234
192
  }
235
193
  }
236
194
 
237
- async execute(
195
+ async createSession(options: {
196
+ id: string;
197
+ env?: Record<string, string>;
198
+ cwd?: string;
199
+ isolation?: boolean;
200
+ }): Promise<{ success: boolean; id: string; message: string }> {
201
+ try {
202
+ const response = await this.doFetch(`/api/session/create`, {
203
+ method: "POST",
204
+ headers: {
205
+ "Content-Type": "application/json",
206
+ },
207
+ body: JSON.stringify(options),
208
+ });
209
+
210
+ if (!response.ok) {
211
+ const errorData = (await response.json().catch(() => ({}))) as {
212
+ error?: string;
213
+ };
214
+ throw new Error(
215
+ errorData.error || `Failed to create session: ${response.status}`
216
+ );
217
+ }
218
+
219
+ const data = await response.json() as { success: boolean; id: string; message: string };
220
+ console.log(`[HTTP Client] Session created: ${options.id}`);
221
+ return data;
222
+ } catch (error) {
223
+ console.error("[HTTP Client] Error creating session:", error);
224
+ throw error;
225
+ }
226
+ }
227
+
228
+ async exec(
229
+ sessionId: string,
238
230
  command: string,
239
- options: Pick<BaseExecOptions, "sessionId" | "cwd" | "env">
231
+ options?: Pick<BaseExecOptions, "cwd" | "env">
240
232
  ): Promise<ExecuteResponse> {
241
233
  try {
242
- const targetSessionId = options.sessionId || this.sessionId;
243
- const executeRequest = {
244
- command,
245
- sessionId: targetSessionId,
246
- cwd: options.cwd,
247
- env: options.env,
248
- } satisfies ExecuteRequest;
249
-
234
+ // Always use session-specific endpoint
250
235
  const response = await this.doFetch(`/api/execute`, {
251
- body: JSON.stringify(executeRequest),
236
+ method: "POST",
252
237
  headers: {
253
238
  "Content-Type": "application/json",
254
239
  },
255
- method: "POST",
240
+ body: JSON.stringify({ id: sessionId, command }),
256
241
  });
257
242
 
258
243
  if (!response.ok) {
@@ -260,27 +245,34 @@ export class HttpClient {
260
245
  error?: string;
261
246
  };
262
247
  throw new Error(
263
- errorData.error || `HTTP error! status: ${response.status}`
248
+ errorData.error || `Failed to execute in session: ${response.status}`
264
249
  );
265
250
  }
266
251
 
267
- const data: ExecuteResponse = await response.json();
252
+ const data = await response.json() as { stdout: string; stderr: string; exitCode: number; success: boolean };
268
253
  console.log(
269
- `[HTTP Client] Command executed: ${command}, Success: ${data.success}`
254
+ `[HTTP Client] Command executed in session ${sessionId}: ${command}`
270
255
  );
256
+
257
+ // Convert to ExecuteResponse format for consistency
258
+ const executeResponse: ExecuteResponse = {
259
+ ...data,
260
+ command,
261
+ timestamp: new Date().toISOString()
262
+ };
271
263
 
272
264
  // Call the callback if provided
273
265
  this.options.onCommandComplete?.(
274
- data.success,
275
- data.exitCode,
276
- data.stdout,
277
- data.stderr,
278
- data.command
266
+ executeResponse.success,
267
+ executeResponse.exitCode,
268
+ executeResponse.stdout,
269
+ executeResponse.stderr,
270
+ executeResponse.command
279
271
  );
280
272
 
281
- return data;
273
+ return executeResponse;
282
274
  } catch (error) {
283
- console.error("[HTTP Client] Error executing command:", error);
275
+ console.error("[HTTP Client] Error executing in session:", error);
284
276
  this.options.onError?.(
285
277
  error instanceof Error ? error.message : "Unknown error",
286
278
  command
@@ -289,23 +281,21 @@ export class HttpClient {
289
281
  }
290
282
  }
291
283
 
292
- async executeCommandStream(
293
- command: string,
294
- sessionId?: string
284
+ async execStream(
285
+ sessionId: string,
286
+ command: string
295
287
  ): Promise<ReadableStream<Uint8Array>> {
296
288
  try {
297
- const targetSessionId = sessionId || this.sessionId;
298
-
289
+ // Always use session-specific streaming endpoint
299
290
  const response = await this.doFetch(`/api/execute/stream`, {
300
- body: JSON.stringify({
301
- command,
302
- sessionId: targetSessionId,
303
- }),
291
+ method: "POST",
304
292
  headers: {
305
293
  "Content-Type": "application/json",
306
- Accept: "text/event-stream",
307
294
  },
308
- method: "POST",
295
+ body: JSON.stringify({
296
+ id: sessionId,
297
+ command
298
+ }),
309
299
  });
310
300
 
311
301
  if (!response.ok) {
@@ -313,38 +303,37 @@ export class HttpClient {
313
303
  error?: string;
314
304
  };
315
305
  throw new Error(
316
- errorData.error || `HTTP error! status: ${response.status}`
306
+ errorData.error || `Failed to stream execute in session: ${response.status}`
317
307
  );
318
308
  }
319
309
 
320
310
  if (!response.body) {
321
- throw new Error("No response body for streaming request");
311
+ throw new Error("No response body for streaming execution");
322
312
  }
323
313
 
324
- console.log(`[HTTP Client] Started command stream: ${command}`);
325
-
314
+ console.log(
315
+ `[HTTP Client] Started streaming command in session ${sessionId}: ${command}`
316
+ );
326
317
  return response.body;
327
318
  } catch (error) {
328
- console.error("[HTTP Client] Error in command stream:", error);
319
+ console.error("[HTTP Client] Error streaming execute in session:", error);
329
320
  throw error;
330
321
  }
331
322
  }
332
323
 
333
324
  async gitCheckout(
334
325
  repoUrl: string,
326
+ sessionId: string,
335
327
  branch: string = "main",
336
- targetDir?: string,
337
- sessionId?: string
328
+ targetDir?: string
338
329
  ): Promise<GitCheckoutResponse> {
339
330
  try {
340
- const targetSessionId = sessionId || this.sessionId;
341
-
342
331
  const response = await this.doFetch(`/api/git/checkout`, {
343
332
  body: JSON.stringify({
344
333
  branch,
345
334
  repoUrl,
346
- sessionId: targetSessionId,
347
335
  targetDir,
336
+ sessionId,
348
337
  } as GitCheckoutRequest),
349
338
  headers: {
350
339
  "Content-Type": "application/json",
@@ -376,16 +365,14 @@ export class HttpClient {
376
365
  async mkdir(
377
366
  path: string,
378
367
  recursive: boolean = false,
379
- sessionId?: string
368
+ sessionId: string
380
369
  ): Promise<MkdirResponse> {
381
370
  try {
382
- const targetSessionId = sessionId || this.sessionId;
383
-
384
371
  const response = await this.doFetch(`/api/mkdir`, {
385
372
  body: JSON.stringify({
386
373
  path,
387
374
  recursive,
388
- sessionId: targetSessionId,
375
+ sessionId,
389
376
  } as MkdirRequest),
390
377
  headers: {
391
378
  "Content-Type": "application/json",
@@ -404,7 +391,7 @@ export class HttpClient {
404
391
 
405
392
  const data: MkdirResponse = await response.json();
406
393
  console.log(
407
- `[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}`
394
+ `[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}${sessionId ? ` in session: ${sessionId}` : ''}`
408
395
  );
409
396
 
410
397
  return data;
@@ -418,17 +405,15 @@ export class HttpClient {
418
405
  path: string,
419
406
  content: string,
420
407
  encoding: string = "utf-8",
421
- sessionId?: string
408
+ sessionId: string
422
409
  ): Promise<WriteFileResponse> {
423
410
  try {
424
- const targetSessionId = sessionId || this.sessionId;
425
-
426
411
  const response = await this.doFetch(`/api/write`, {
427
412
  body: JSON.stringify({
428
413
  content,
429
414
  encoding,
430
415
  path,
431
- sessionId: targetSessionId,
416
+ sessionId,
432
417
  } as WriteFileRequest),
433
418
  headers: {
434
419
  "Content-Type": "application/json",
@@ -447,7 +432,7 @@ export class HttpClient {
447
432
 
448
433
  const data: WriteFileResponse = await response.json();
449
434
  console.log(
450
- `[HTTP Client] File written: ${path}, Success: ${data.success}`
435
+ `[HTTP Client] File written: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
451
436
  );
452
437
 
453
438
  return data;
@@ -460,16 +445,14 @@ export class HttpClient {
460
445
  async readFile(
461
446
  path: string,
462
447
  encoding: string = "utf-8",
463
- sessionId?: string
448
+ sessionId: string
464
449
  ): Promise<ReadFileResponse> {
465
450
  try {
466
- const targetSessionId = sessionId || this.sessionId;
467
-
468
451
  const response = await this.doFetch(`/api/read`, {
469
452
  body: JSON.stringify({
470
453
  encoding,
471
454
  path,
472
- sessionId: targetSessionId,
455
+ sessionId,
473
456
  } as ReadFileRequest),
474
457
  headers: {
475
458
  "Content-Type": "application/json",
@@ -488,7 +471,7 @@ export class HttpClient {
488
471
 
489
472
  const data: ReadFileResponse = await response.json();
490
473
  console.log(
491
- `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
474
+ `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}${sessionId ? ` in session: ${sessionId}` : ''}`
492
475
  );
493
476
 
494
477
  return data;
@@ -498,17 +481,54 @@ export class HttpClient {
498
481
  }
499
482
  }
500
483
 
484
+ async readFileStream(
485
+ path: string,
486
+ sessionId: string
487
+ ): Promise<ReadableStream<Uint8Array>> {
488
+ try {
489
+ const response = await this.doFetch(`/api/read/stream`, {
490
+ method: "POST",
491
+ headers: {
492
+ "Content-Type": "application/json",
493
+ },
494
+ body: JSON.stringify({
495
+ path,
496
+ sessionId,
497
+ } as ReadFileRequest),
498
+ });
499
+
500
+ if (!response.ok) {
501
+ const errorData = (await response.json().catch(() => ({}))) as {
502
+ error?: string;
503
+ };
504
+ throw new Error(
505
+ errorData.error || `HTTP error! status: ${response.status}`
506
+ );
507
+ }
508
+
509
+ if (!response.body) {
510
+ throw new Error("No response body for file streaming");
511
+ }
512
+
513
+ console.log(
514
+ `[HTTP Client] Started streaming file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`
515
+ );
516
+ return response.body;
517
+ } catch (error) {
518
+ console.error("[HTTP Client] Error streaming file:", error);
519
+ throw error;
520
+ }
521
+ }
522
+
501
523
  async deleteFile(
502
524
  path: string,
503
- sessionId?: string
525
+ sessionId: string
504
526
  ): Promise<DeleteFileResponse> {
505
527
  try {
506
- const targetSessionId = sessionId || this.sessionId;
507
-
508
528
  const response = await this.doFetch(`/api/delete`, {
509
529
  body: JSON.stringify({
510
530
  path,
511
- sessionId: targetSessionId,
531
+ sessionId,
512
532
  } as DeleteFileRequest),
513
533
  headers: {
514
534
  "Content-Type": "application/json",
@@ -527,7 +547,7 @@ export class HttpClient {
527
547
 
528
548
  const data: DeleteFileResponse = await response.json();
529
549
  console.log(
530
- `[HTTP Client] File deleted: ${path}, Success: ${data.success}`
550
+ `[HTTP Client] File deleted: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
531
551
  );
532
552
 
533
553
  return data;
@@ -540,16 +560,14 @@ export class HttpClient {
540
560
  async renameFile(
541
561
  oldPath: string,
542
562
  newPath: string,
543
- sessionId?: string
563
+ sessionId: string
544
564
  ): Promise<RenameFileResponse> {
545
565
  try {
546
- const targetSessionId = sessionId || this.sessionId;
547
-
548
566
  const response = await this.doFetch(`/api/rename`, {
549
567
  body: JSON.stringify({
550
568
  newPath,
551
569
  oldPath,
552
- sessionId: targetSessionId,
570
+ sessionId,
553
571
  } as RenameFileRequest),
554
572
  headers: {
555
573
  "Content-Type": "application/json",
@@ -568,7 +586,7 @@ export class HttpClient {
568
586
 
569
587
  const data: RenameFileResponse = await response.json();
570
588
  console.log(
571
- `[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
589
+ `[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
572
590
  );
573
591
 
574
592
  return data;
@@ -581,16 +599,14 @@ export class HttpClient {
581
599
  async moveFile(
582
600
  sourcePath: string,
583
601
  destinationPath: string,
584
- sessionId?: string
602
+ sessionId: string
585
603
  ): Promise<MoveFileResponse> {
586
604
  try {
587
- const targetSessionId = sessionId || this.sessionId;
588
-
589
605
  const response = await this.doFetch(`/api/move`, {
590
606
  body: JSON.stringify({
591
607
  destinationPath,
592
- sessionId: targetSessionId,
593
608
  sourcePath,
609
+ sessionId,
594
610
  } as MoveFileRequest),
595
611
  headers: {
596
612
  "Content-Type": "application/json",
@@ -609,7 +625,7 @@ export class HttpClient {
609
625
 
610
626
  const data: MoveFileResponse = await response.json();
611
627
  console.log(
612
- `[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
628
+ `[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
613
629
  );
614
630
 
615
631
  return data;
@@ -619,6 +635,48 @@ export class HttpClient {
619
635
  }
620
636
  }
621
637
 
638
+ async listFiles(
639
+ path: string,
640
+ sessionId: string,
641
+ options?: {
642
+ recursive?: boolean;
643
+ includeHidden?: boolean;
644
+ }
645
+ ): Promise<ListFilesResponse> {
646
+ try {
647
+ const response = await this.doFetch(`/api/list-files`, {
648
+ body: JSON.stringify({
649
+ path,
650
+ options,
651
+ sessionId,
652
+ } as ListFilesRequest),
653
+ headers: {
654
+ "Content-Type": "application/json",
655
+ },
656
+ method: "POST",
657
+ });
658
+
659
+ if (!response.ok) {
660
+ const errorData = (await response.json().catch(() => ({}))) as {
661
+ error?: string;
662
+ };
663
+ throw new Error(
664
+ errorData.error || `HTTP error! status: ${response.status}`
665
+ );
666
+ }
667
+
668
+ const data: ListFilesResponse = await response.json();
669
+ console.log(
670
+ `[HTTP Client] Listed ${data.files.length} files in: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
671
+ );
672
+
673
+ return data;
674
+ } catch (error) {
675
+ console.error("[HTTP Client] Error listing files:", error);
676
+ throw error;
677
+ }
678
+ }
679
+
622
680
  async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
623
681
  try {
624
682
  const response = await this.doFetch(`/api/expose-port`, {
@@ -739,48 +797,13 @@ export class HttpClient {
739
797
  }
740
798
  }
741
799
 
742
- async getCommands(): Promise<string[]> {
743
- try {
744
- const response = await fetch(`${this.baseUrl}/api/commands`, {
745
- headers: {
746
- "Content-Type": "application/json",
747
- },
748
- method: "GET",
749
- });
750
-
751
- if (!response.ok) {
752
- throw new Error(`HTTP error! status: ${response.status}`);
753
- }
754
-
755
- const data: CommandsResponse = await response.json();
756
- console.log(
757
- `[HTTP Client] Available commands: ${data.availableCommands.length}`
758
- );
759
- return data.availableCommands;
760
- } catch (error) {
761
- console.error("[HTTP Client] Error getting commands:", error);
762
- throw error;
763
- }
764
- }
765
-
766
- getSessionId(): string | null {
767
- return this.sessionId;
768
- }
769
-
770
- setSessionId(sessionId: string): void {
771
- this.sessionId = sessionId;
772
- }
773
-
774
- clearSession(): void {
775
- this.sessionId = null;
776
- }
777
800
 
778
801
  // Process management methods
779
802
  async startProcess(
780
803
  command: string,
804
+ sessionId: string,
781
805
  options?: {
782
806
  processId?: string;
783
- sessionId?: string;
784
807
  timeout?: number;
785
808
  env?: Record<string, string>;
786
809
  cwd?: string;
@@ -789,15 +812,11 @@ export class HttpClient {
789
812
  }
790
813
  ): Promise<StartProcessResponse> {
791
814
  try {
792
- const targetSessionId = options?.sessionId || this.sessionId;
793
-
794
815
  const response = await this.doFetch("/api/process/start", {
795
816
  body: JSON.stringify({
796
817
  command,
797
- options: {
798
- ...options,
799
- sessionId: targetSessionId,
800
- },
818
+ sessionId,
819
+ options,
801
820
  } as StartProcessRequest),
802
821
  headers: {
803
822
  "Content-Type": "application/json",
@@ -826,9 +845,12 @@ export class HttpClient {
826
845
  }
827
846
  }
828
847
 
829
- async listProcesses(): Promise<ListProcessesResponse> {
848
+ async listProcesses(sessionId?: string): Promise<ListProcessesResponse> {
830
849
  try {
831
- const response = await this.doFetch("/api/process/list", {
850
+ const url = sessionId
851
+ ? `/api/process/list?session=${encodeURIComponent(sessionId)}`
852
+ : "/api/process/list";
853
+ const response = await this.doFetch(url, {
832
854
  headers: {
833
855
  "Content-Type": "application/json",
834
856
  },
@@ -919,13 +941,16 @@ export class HttpClient {
919
941
  }
920
942
  }
921
943
 
922
- async killAllProcesses(): Promise<{
944
+ async killAllProcesses(sessionId?: string): Promise<{
923
945
  success: boolean;
924
946
  killedCount: number;
925
947
  message: string;
926
948
  }> {
927
949
  try {
928
- const response = await this.doFetch("/api/process/kill-all", {
950
+ const url = sessionId
951
+ ? `/api/process/kill-all?session=${encodeURIComponent(sessionId)}`
952
+ : "/api/process/kill-all";
953
+ const response = await this.doFetch(url, {
929
954
  headers: {
930
955
  "Content-Type": "application/json",
931
956
  },
@@ -984,7 +1009,8 @@ export class HttpClient {
984
1009
  }
985
1010
 
986
1011
  async streamProcessLogs(
987
- processId: string
1012
+ processId: string,
1013
+ options?: { signal?: AbortSignal }
988
1014
  ): Promise<ReadableStream<Uint8Array>> {
989
1015
  try {
990
1016
  const response = await this.doFetch(`/api/process/${processId}/stream`, {
@@ -993,6 +1019,7 @@ export class HttpClient {
993
1019
  "Cache-Control": "no-cache",
994
1020
  },
995
1021
  method: "GET",
1022
+ signal: options?.signal,
996
1023
  });
997
1024
 
998
1025
  if (!response.ok) {