@langchain/sandbox-standard-tests 0.0.3 → 1.0.0-alpha.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.
@@ -1,3 +1,4 @@
1
+ import { adaptSandboxProtocol } from "deepagents";
1
2
  //#region src/tests/lifecycle.ts
2
3
  /**
3
4
  * Register sandbox lifecycle tests (create, isRunning, close, two-step init).
@@ -7,8 +8,6 @@
7
8
  */
8
9
  function registerLifecycleTests(getShared, config, timeout) {
9
10
  const { describe, it, expect, beforeAll, afterAll } = config.runner;
10
- const describeSkipIf = describe.skipIf ?? ((condition) => condition ? describe.skip ?? (() => {}) : describe);
11
- const itSkipIf = it.skipIf ?? ((condition) => condition ? () => {} : it);
12
11
  describe("sandbox lifecycle", () => {
13
12
  it("should create sandbox and have a valid id", () => {
14
13
  const shared = getShared();
@@ -20,7 +19,7 @@ function registerLifecycleTests(getShared, config, timeout) {
20
19
  it("should have isRunning as true after creation", () => {
21
20
  expect(getShared().isRunning).toBe(true);
22
21
  }, timeout);
23
- describeSkipIf(!(typeof config.closeSandbox === "function"))("close", () => {
22
+ if (typeof config.closeSandbox === "function") describe("close", () => {
24
23
  let tmp;
25
24
  beforeAll(async () => {
26
25
  tmp = await withRetry(() => config.createSandbox());
@@ -36,14 +35,14 @@ function registerLifecycleTests(getShared, config, timeout) {
36
35
  expect(tmp.isRunning).toBe(false);
37
36
  }, timeout);
38
37
  });
39
- describe("two-step initialization", () => {
38
+ if (config.createUninitializedSandbox) describe("two-step initialization", () => {
40
39
  let tmp;
41
40
  afterAll(async () => {
42
41
  try {
43
42
  if (tmp) await config.closeSandbox?.(tmp);
44
43
  } catch {}
45
44
  }, timeout);
46
- itSkipIf(!config.createUninitializedSandbox)("should work with two-step initialization", async () => {
45
+ it("should work with two-step initialization", async () => {
47
46
  tmp = config.createUninitializedSandbox();
48
47
  expect(tmp.isRunning).toBe(false);
49
48
  await withRetry(() => tmp.initialize());
@@ -53,7 +52,6 @@ function registerLifecycleTests(getShared, config, timeout) {
53
52
  });
54
53
  });
55
54
  }
56
-
57
55
  //#endregion
58
56
  //#region src/tests/command-execution.ts
59
57
  /**
@@ -91,7 +89,42 @@ function registerCommandExecutionTests(getShared, config, timeout) {
91
89
  }, timeout);
92
90
  });
93
91
  }
94
-
92
+ //#endregion
93
+ //#region src/adapter.ts
94
+ /**
95
+ * Adapter to convert v1 SandboxInstance to v2 SandboxInstanceV2.
96
+ */
97
+ /**
98
+ * Adapt a sandbox instance (v1 or v2) to SandboxInstanceV2.
99
+ *
100
+ * This builds on {@link adaptSandboxProtocol} from deepagents, which handles
101
+ * the core protocol adaptation (BackendProtocol → BackendProtocolV2 and
102
+ * sandbox properties execute/id).
103
+ *
104
+ * This function additionally preserves test-specific properties:
105
+ * - `isRunning` (required)
106
+ * - `close` (optional)
107
+ * - `initialize` (optional)
108
+ * - `uploadFiles` (required, though optional in base protocol)
109
+ * - `downloadFiles` (required, though optional in base protocol)
110
+ *
111
+ * @param sandbox - Sandbox instance (v1 or v2)
112
+ * @returns SandboxInstanceV2-compatible sandbox
113
+ */
114
+ function adaptSandboxInstance(sandbox) {
115
+ const adapted = adaptSandboxProtocol(sandbox);
116
+ const sb = sandbox;
117
+ if (typeof sb.close === "function") adapted.close = () => sb.close();
118
+ if (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(sandbox), "isRunning")?.get) Object.defineProperty(adapted, "isRunning", {
119
+ get: () => sb.isRunning,
120
+ enumerable: true
121
+ });
122
+ else if ("isRunning" in sandbox) adapted.isRunning = sb.isRunning;
123
+ if (typeof sb.initialize === "function") adapted.initialize = () => sb.initialize();
124
+ if (typeof sb.uploadFiles === "function") adapted.uploadFiles = (files) => sb.uploadFiles(files);
125
+ if (typeof sb.downloadFiles === "function") adapted.downloadFiles = (paths) => sb.downloadFiles(paths);
126
+ return adapted;
127
+ }
95
128
  //#endregion
96
129
  //#region src/tests/file-operations.ts
97
130
  /**
@@ -129,11 +162,11 @@ function registerFileOperationTests(getShared, config, timeout) {
129
162
  expect(results[0].error).toBe("file_not_found");
130
163
  }, timeout);
131
164
  it("should use inherited read method from BaseSandbox", async () => {
132
- const shared = getShared();
165
+ const shared = adaptSandboxInstance(getShared());
133
166
  const filePath = config.resolvePath("read-test.txt");
134
167
  const encoder = new TextEncoder();
135
168
  await shared.uploadFiles([[filePath, encoder.encode("Read test content")]]);
136
- expect(await shared.read(filePath)).toContain("Read test content");
169
+ expect((await shared.read(filePath)).content).toContain("Read test content");
137
170
  }, timeout);
138
171
  it("should use inherited write method from BaseSandbox", async () => {
139
172
  const shared = getShared();
@@ -168,7 +201,6 @@ function registerFileOperationTests(getShared, config, timeout) {
168
201
  }, timeout);
169
202
  });
170
203
  }
171
-
172
204
  //#endregion
173
205
  //#region src/tests/write.ts
174
206
  /**
@@ -253,7 +285,6 @@ function registerWriteTests(getShared, config, timeout) {
253
285
  }, timeout);
254
286
  });
255
287
  }
256
-
257
288
  //#endregion
258
289
  //#region src/tests/read.ts
259
290
  /**
@@ -264,120 +295,119 @@ function registerReadTests(getShared, config, timeout) {
264
295
  const { describe, it, expect } = config.runner;
265
296
  describe("read", () => {
266
297
  it("should read a file with line numbers", async () => {
267
- const shared = getShared();
298
+ const shared = adaptSandboxInstance(getShared());
268
299
  const filePath = config.resolvePath("rd-basic.txt");
269
300
  await shared.write(filePath, "Line 1\nLine 2\nLine 3");
270
301
  const result = await shared.read(filePath);
271
- expect(result).not.toContain("Error:");
272
- expect(result).toContain("Line 1");
273
- expect(result).toContain("Line 2");
274
- expect(result).toContain("Line 3");
302
+ expect(result.error).toBeUndefined();
303
+ expect(result.content).toContain("Line 1");
304
+ expect(result.content).toContain("Line 2");
305
+ expect(result.content).toContain("Line 3");
275
306
  }, timeout);
276
307
  it("should return error for nonexistent file", async () => {
277
308
  const filePath = config.resolvePath("rd-nonexistent-xyz.txt");
278
- const result = await getShared().read(filePath);
279
- expect(result).toContain("Error:");
280
- expect(result.toLowerCase()).toContain("not found");
309
+ const result = await adaptSandboxInstance(getShared()).read(filePath);
310
+ expect(result.error).toBeDefined();
311
+ expect(result.error.toLowerCase()).toContain("not found");
281
312
  }, timeout);
282
313
  it("should handle reading an empty file", async () => {
283
- const shared = getShared();
314
+ const shared = adaptSandboxInstance(getShared());
284
315
  const filePath = config.resolvePath("rd-empty.txt");
285
316
  await shared.write(filePath, "");
286
- expect((await shared.read(filePath)).toLowerCase()).not.toContain("error:");
317
+ expect((await shared.read(filePath)).error).toBeUndefined();
287
318
  }, timeout);
288
319
  it("should read with offset parameter", async () => {
289
- const shared = getShared();
320
+ const shared = adaptSandboxInstance(getShared());
290
321
  const filePath = config.resolvePath("rd-offset.txt");
291
322
  const content = Array.from({ length: 10 }, (_, i) => `Row_${i + 1}_content`).join("\n");
292
323
  await shared.write(filePath, content);
293
324
  const result = await shared.read(filePath, 5);
294
- expect(result).toContain("Row_6_content");
295
- expect(result).not.toContain("Row_1_content");
325
+ expect(result.content).toContain("Row_6_content");
326
+ expect(result.content).not.toContain("Row_1_content");
296
327
  }, timeout);
297
328
  it("should read with limit parameter", async () => {
298
- const shared = getShared();
329
+ const shared = adaptSandboxInstance(getShared());
299
330
  const filePath = config.resolvePath("rd-limit.txt");
300
331
  const content = Array.from({ length: 100 }, (_, i) => `Row_${i + 1}_content`).join("\n");
301
332
  await shared.write(filePath, content);
302
333
  const result = await shared.read(filePath, 0, 5);
303
- expect(result).toContain("Row_1_content");
304
- expect(result).toContain("Row_5_content");
305
- expect(result).not.toContain("Row_6_content");
334
+ expect(result.content).toContain("Row_1_content");
335
+ expect(result.content).toContain("Row_5_content");
336
+ expect(result.content).not.toContain("Row_6_content");
306
337
  }, timeout);
307
338
  it("should read with both offset and limit", async () => {
308
- const shared = getShared();
339
+ const shared = adaptSandboxInstance(getShared());
309
340
  const filePath = config.resolvePath("rd-offset-limit.txt");
310
341
  const content = Array.from({ length: 20 }, (_, i) => `Row_${i + 1}_content`).join("\n");
311
342
  await shared.write(filePath, content);
312
343
  const result = await shared.read(filePath, 10, 5);
313
- expect(result).toContain("Row_11_content");
314
- expect(result).toContain("Row_15_content");
315
- expect(result).not.toContain("Row_10_content");
316
- expect(result).not.toContain("Row_16_content");
344
+ expect(result.content).toContain("Row_11_content");
345
+ expect(result.content).toContain("Row_15_content");
346
+ expect(result.content).not.toContain("Row_10_content");
347
+ expect(result.content).not.toContain("Row_16_content");
317
348
  }, timeout);
318
349
  it("should read unicode content", async () => {
319
- const shared = getShared();
350
+ const shared = adaptSandboxInstance(getShared());
320
351
  const filePath = config.resolvePath("rd-unicode.txt");
321
352
  await shared.write(filePath, "Hello 👋 世界\nПривет мир\nمرحبا العالم");
322
353
  const result = await shared.read(filePath);
323
- expect(result).not.toContain("Error:");
324
- expect(result).toContain("👋");
325
- expect(result).toContain("世界");
326
- expect(result).toContain("Привет");
354
+ expect(result.error).toBeUndefined();
355
+ expect(result.content).toContain("👋");
356
+ expect(result.content).toContain("世界");
357
+ expect(result.content).toContain("Привет");
327
358
  }, timeout);
328
359
  it("should handle files with very long lines", async () => {
329
- const shared = getShared();
360
+ const shared = adaptSandboxInstance(getShared());
330
361
  const filePath = config.resolvePath("rd-long-lines.txt");
331
362
  const content = `Short line\n${"x".repeat(3e3)}\nAnother short line`;
332
363
  await shared.write(filePath, content);
333
364
  const result = await shared.read(filePath);
334
- expect(result).not.toContain("Error:");
335
- expect(result).toContain("Short line");
365
+ expect(result.error).toBeUndefined();
366
+ expect(result.content).toContain("Short line");
336
367
  }, timeout);
337
368
  it("should return nothing with limit=0", async () => {
338
- const shared = getShared();
369
+ const shared = adaptSandboxInstance(getShared());
339
370
  const filePath = config.resolvePath("rd-zero-limit.txt");
340
371
  await shared.write(filePath, "Line 1\nLine 2\nLine 3");
341
- expect(await shared.read(filePath, 0, 0)).not.toContain("Line 1");
372
+ expect((await shared.read(filePath, 0, 0)).content).not.toContain("Line 1");
342
373
  }, timeout);
343
374
  it("should handle offset beyond file length", async () => {
344
- const shared = getShared();
375
+ const shared = adaptSandboxInstance(getShared());
345
376
  const filePath = config.resolvePath("rd-offset-beyond.txt");
346
377
  await shared.write(filePath, "Line 1\nLine 2\nLine 3");
347
378
  const result = await shared.read(filePath, 100, 10);
348
- expect(result).not.toContain("Line 1");
349
- expect(result).not.toContain("Line 2");
350
- expect(result).not.toContain("Line 3");
379
+ expect(result.content ?? "").not.toContain("Line 1");
380
+ expect(result.content ?? "").not.toContain("Line 2");
381
+ expect(result.content ?? "").not.toContain("Line 3");
351
382
  }, timeout);
352
383
  it("should handle offset exactly at file length", async () => {
353
- const shared = getShared();
384
+ const shared = adaptSandboxInstance(getShared());
354
385
  const filePath = config.resolvePath("rd-offset-exact.txt");
355
386
  const content = Array.from({ length: 5 }, (_, i) => `Line ${i + 1}`).join("\n");
356
387
  await shared.write(filePath, content);
357
388
  const result = await shared.read(filePath, 5, 10);
358
- expect(result).not.toContain("Line 1");
359
- expect(result).not.toContain("Line 5");
389
+ expect(result.content ?? "").not.toContain("Line 1");
390
+ expect(result.content ?? "").not.toContain("Line 5");
360
391
  }, timeout);
361
392
  it("should read a large file in chunks", async () => {
362
- const shared = getShared();
393
+ const shared = adaptSandboxInstance(getShared());
363
394
  const filePath = config.resolvePath("rd-chunked.txt");
364
395
  const content = Array.from({ length: 1e3 }, (_, i) => `Line_${String(i).padStart(4, "0")}_content`).join("\n");
365
396
  await shared.write(filePath, content);
366
397
  const chunk1 = await shared.read(filePath, 0, 100);
367
- expect(chunk1).toContain("Line_0000_content");
368
- expect(chunk1).toContain("Line_0099_content");
369
- expect(chunk1).not.toContain("Line_0100_content");
398
+ expect(chunk1.content).toContain("Line_0000_content");
399
+ expect(chunk1.content).toContain("Line_0099_content");
400
+ expect(chunk1.content).not.toContain("Line_0100_content");
370
401
  const chunk2 = await shared.read(filePath, 500, 100);
371
- expect(chunk2).toContain("Line_0500_content");
372
- expect(chunk2).toContain("Line_0599_content");
373
- expect(chunk2).not.toContain("Line_0499_content");
402
+ expect(chunk2.content).toContain("Line_0500_content");
403
+ expect(chunk2.content).toContain("Line_0599_content");
404
+ expect(chunk2.content).not.toContain("Line_0499_content");
374
405
  const chunk3 = await shared.read(filePath, 900, 100);
375
- expect(chunk3).toContain("Line_0900_content");
376
- expect(chunk3).toContain("Line_0999_content");
406
+ expect(chunk3.content).toContain("Line_0900_content");
407
+ expect(chunk3.content).toContain("Line_0999_content");
377
408
  }, timeout);
378
409
  });
379
410
  }
380
-
381
411
  //#endregion
382
412
  //#region src/tests/edit.ts
383
413
  /**
@@ -389,26 +419,26 @@ function registerEditTests(getShared, config, timeout) {
389
419
  const { describe, it, expect } = config.runner;
390
420
  describe("edit", () => {
391
421
  it("should edit a single occurrence", async () => {
392
- const shared = getShared();
422
+ const shared = adaptSandboxInstance(getShared());
393
423
  const filePath = config.resolvePath("ed-single.txt");
394
424
  await shared.write(filePath, "Hello world\nGoodbye world\nHello again");
395
425
  const result = await shared.edit(filePath, "Goodbye", "Farewell");
396
426
  expect(result.error).toBeUndefined();
397
427
  expect(result.occurrences).toBe(1);
398
428
  const content = await shared.read(filePath);
399
- expect(content).toContain("Farewell world");
400
- expect(content).not.toContain("Goodbye");
429
+ expect(content.content).toContain("Farewell world");
430
+ expect(content.content).not.toContain("Goodbye");
401
431
  }, timeout);
402
432
  it("should fail with multiple occurrences without replaceAll", async () => {
403
- const shared = getShared();
433
+ const shared = adaptSandboxInstance(getShared());
404
434
  const filePath = config.resolvePath("ed-multi-fail.txt");
405
435
  await shared.write(filePath, "apple\nbanana\napple\norange\napple");
406
436
  const result = await shared.edit(filePath, "apple", "pear", false);
407
437
  expect(result.error).toBeDefined();
408
438
  expect(result.error.toLowerCase()).toContain("multiple");
409
439
  const content = await shared.read(filePath);
410
- expect(content).toContain("apple");
411
- expect(content).not.toContain("pear");
440
+ expect(content.content).toContain("apple");
441
+ expect(content.content).not.toContain("pear");
412
442
  }, timeout);
413
443
  it("should replace all occurrences with replaceAll=true", async () => {
414
444
  const shared = getShared();
@@ -436,70 +466,70 @@ function registerEditTests(getShared, config, timeout) {
436
466
  expect(result.error.toLowerCase()).toContain("not found");
437
467
  }, timeout);
438
468
  it("should handle special characters and regex metacharacters", async () => {
439
- const shared = getShared();
469
+ const shared = adaptSandboxInstance(getShared());
440
470
  const filePath = config.resolvePath("ed-special.txt");
441
471
  await shared.write(filePath, "Price: $100.00\nPattern: [a-z]*\nPath: /usr/bin");
442
472
  expect((await shared.edit(filePath, "$100.00", "$200.00")).error).toBeUndefined();
443
473
  expect((await shared.edit(filePath, "[a-z]*", "[0-9]+")).error).toBeUndefined();
444
474
  const content = await shared.read(filePath);
445
- expect(content).toContain("$200.00");
446
- expect(content).toContain("[0-9]+");
475
+ expect(content.content).toContain("$200.00");
476
+ expect(content.content).toContain("[0-9]+");
447
477
  }, timeout);
448
478
  it("should handle multiline string replacement", async () => {
449
- const shared = getShared();
479
+ const shared = adaptSandboxInstance(getShared());
450
480
  const filePath = config.resolvePath("ed-multiline.txt");
451
481
  await shared.write(filePath, "Line 1\nLine 2\nLine 3");
452
482
  const result = await shared.edit(filePath, "Line 1\nLine 2", "Combined");
453
483
  expect(result.error).toBeUndefined();
454
484
  expect(result.occurrences).toBe(1);
455
485
  const content = await shared.read(filePath);
456
- expect(content).toContain("Combined");
457
- expect(content).toContain("Line 3");
458
- expect(content).not.toContain("Line 1");
486
+ expect(content.content).toContain("Combined");
487
+ expect(content.content).toContain("Line 3");
488
+ expect(content.content).not.toContain("Line 1");
459
489
  }, timeout);
460
490
  it("should delete content by replacing with empty string", async () => {
461
- const shared = getShared();
491
+ const shared = adaptSandboxInstance(getShared());
462
492
  const filePath = config.resolvePath("ed-delete.txt");
463
493
  await shared.write(filePath, "Keep this\nDelete this part\nKeep this too");
464
494
  const result = await shared.edit(filePath, "Delete this part\n", "");
465
495
  expect(result.error).toBeUndefined();
466
496
  expect(result.occurrences).toBe(1);
467
497
  const content = await shared.read(filePath);
468
- expect(content).toContain("Keep this");
469
- expect(content).toContain("Keep this too");
470
- expect(content).not.toContain("Delete this part");
498
+ expect(content.content).toContain("Keep this");
499
+ expect(content.content).toContain("Keep this too");
500
+ expect(content.content).not.toContain("Delete this part");
471
501
  }, timeout);
472
502
  it("should handle identical old and new strings", async () => {
473
- const shared = getShared();
503
+ const shared = adaptSandboxInstance(getShared());
474
504
  const filePath = config.resolvePath("ed-identical.txt");
475
505
  await shared.write(filePath, "Same text");
476
506
  const result = await shared.edit(filePath, "Same text", "Same text");
477
507
  expect(result.error).toBeUndefined();
478
508
  expect(result.occurrences).toBe(1);
479
- expect(await shared.read(filePath)).toContain("Same text");
509
+ expect((await shared.read(filePath)).content).toContain("Same text");
480
510
  }, timeout);
481
511
  it("should handle unicode content", async () => {
482
- const shared = getShared();
512
+ const shared = adaptSandboxInstance(getShared());
483
513
  const filePath = config.resolvePath("ed-unicode.txt");
484
514
  await shared.write(filePath, "Hello 👋 world\n世界 is beautiful");
485
515
  const result = await shared.edit(filePath, "👋", "🌍");
486
516
  expect(result.error).toBeUndefined();
487
517
  expect(result.occurrences).toBe(1);
488
518
  const content = await shared.read(filePath);
489
- expect(content).toContain("🌍");
490
- expect(content).not.toContain("👋");
519
+ expect(content.content).toContain("🌍");
520
+ expect(content.content).not.toContain("👋");
491
521
  }, timeout);
492
522
  it("should handle whitespace-only strings", async () => {
493
- const shared = getShared();
523
+ const shared = adaptSandboxInstance(getShared());
494
524
  const filePath = config.resolvePath("ed-whitespace.txt");
495
525
  await shared.write(filePath, "Line1 Line2");
496
526
  const result = await shared.edit(filePath, " ", " ");
497
527
  expect(result.error).toBeUndefined();
498
528
  expect(result.occurrences).toBe(1);
499
- expect(await shared.read(filePath)).toContain("Line1 Line2");
529
+ expect((await shared.read(filePath)).content).toContain("Line1 Line2");
500
530
  }, timeout);
501
531
  it("should handle very long old and new strings", async () => {
502
- const shared = getShared();
532
+ const shared = adaptSandboxInstance(getShared());
503
533
  const filePath = config.resolvePath("ed-long.txt");
504
534
  const oldString = "x".repeat(1e3);
505
535
  const newString = "y".repeat(1e3);
@@ -508,18 +538,18 @@ function registerEditTests(getShared, config, timeout) {
508
538
  expect(result.error).toBeUndefined();
509
539
  expect(result.occurrences).toBe(1);
510
540
  const content = await shared.read(filePath);
511
- expect(content).toContain("y".repeat(100));
512
- expect(content).not.toContain("x".repeat(100));
541
+ expect(content.content).toContain("y".repeat(100));
542
+ expect(content.content).not.toContain("x".repeat(100));
513
543
  }, timeout);
514
544
  it("should preserve line endings correctly", async () => {
515
- const shared = getShared();
545
+ const shared = adaptSandboxInstance(getShared());
516
546
  const filePath = config.resolvePath("ed-line-endings.txt");
517
547
  await shared.write(filePath, "Line 1\nLine 2\nLine 3\n");
518
548
  expect((await shared.edit(filePath, "Line 2", "Modified Line 2")).error).toBeUndefined();
519
549
  const content = await shared.read(filePath);
520
- expect(content).toContain("Line 1");
521
- expect(content).toContain("Modified Line 2");
522
- expect(content).toContain("Line 3");
550
+ expect(content.content).toContain("Line 1");
551
+ expect(content.content).toContain("Modified Line 2");
552
+ expect(content.content).toContain("Line 3");
523
553
  }, timeout);
524
554
  it("should edit a substring within a line", async () => {
525
555
  const shared = getShared();
@@ -532,32 +562,35 @@ function registerEditTests(getShared, config, timeout) {
532
562
  }, timeout);
533
563
  });
534
564
  }
535
-
536
565
  //#endregion
537
566
  //#region src/tests/ls-info.ts
538
567
  /**
539
- * Register lsInfo() tests (absolute paths, files + subdirs, empty dir,
568
+ * Register ls() tests (absolute paths, files + subdirs, empty dir,
540
569
  * nonexistent dir, hidden files, spaces, unicode, large dir, trailing slash,
541
570
  * special characters).
542
571
  */
543
572
  function registerLsInfoTests(getShared, config, timeout) {
544
573
  const { describe, it, expect } = config.runner;
545
- describe("lsInfo", () => {
574
+ describe("ls", () => {
546
575
  it("should return absolute paths", async () => {
547
- const shared = getShared();
576
+ const shared = adaptSandboxInstance(getShared());
548
577
  const baseDir = config.resolvePath("li-absolute");
549
578
  await shared.write(`${baseDir}/file.txt`, "content");
550
- const result = await shared.lsInfo(baseDir);
579
+ const lsResult = await shared.ls(baseDir);
580
+ expect(lsResult.error).toBeUndefined();
581
+ const result = lsResult.files || [];
551
582
  expect(result.length).toBe(1);
552
583
  expect(result[0].path).toBe(`${baseDir}/file.txt`);
553
584
  }, timeout);
554
585
  it("should list files and subdirectories", async () => {
555
- const shared = getShared();
586
+ const shared = adaptSandboxInstance(getShared());
556
587
  const baseDir = config.resolvePath("li-basic");
557
588
  await shared.write(`${baseDir}/file1.txt`, "content1");
558
589
  await shared.write(`${baseDir}/file2.txt`, "content2");
559
590
  await shared.execute(`mkdir -p '${baseDir}/subdir'`);
560
- const result = await shared.lsInfo(baseDir);
591
+ const lsResult = await shared.ls(baseDir);
592
+ expect(lsResult.error).toBeUndefined();
593
+ const result = lsResult.files || [];
561
594
  expect(result.length).toBe(3);
562
595
  const paths = result.map((info) => info.path.replace(/\/$/, ""));
563
596
  expect(paths).toContain(`${baseDir}/file1.txt`);
@@ -567,86 +600,101 @@ function registerLsInfoTests(getShared, config, timeout) {
567
600
  else expect(info.is_dir).toBe(false);
568
601
  }, timeout);
569
602
  it("should return empty list for empty directory", async () => {
570
- const shared = getShared();
603
+ const shared = adaptSandboxInstance(getShared());
571
604
  const emptyDir = config.resolvePath("li-empty-dir");
572
605
  await shared.execute(`mkdir -p '${emptyDir}'`);
573
- expect(await shared.lsInfo(emptyDir)).toEqual([]);
606
+ const lsResult = await shared.ls(emptyDir);
607
+ expect(lsResult.error).toBeUndefined();
608
+ expect(lsResult.files).toEqual([]);
574
609
  }, timeout);
575
610
  it("should return empty list for nonexistent directory", async () => {
576
611
  const nonexistentDir = config.resolvePath("li-does-not-exist-12345");
577
- expect(await getShared().lsInfo(nonexistentDir)).toEqual([]);
612
+ const lsResult = await adaptSandboxInstance(getShared()).ls(nonexistentDir);
613
+ expect(lsResult.error).toBeUndefined();
614
+ expect(lsResult.files).toEqual([]);
578
615
  }, timeout);
579
616
  it("should include hidden files", async () => {
580
- const shared = getShared();
617
+ const shared = adaptSandboxInstance(getShared());
581
618
  const baseDir = config.resolvePath("li-hidden");
582
619
  await shared.write(`${baseDir}/.hidden`, "hidden content");
583
620
  await shared.write(`${baseDir}/visible.txt`, "visible content");
584
- const paths = (await shared.lsInfo(baseDir)).map((info) => info.path);
621
+ const lsResult = await shared.ls(baseDir);
622
+ expect(lsResult.error).toBeUndefined();
623
+ const paths = (lsResult.files || []).map((info) => info.path);
585
624
  expect(paths).toContain(`${baseDir}/.hidden`);
586
625
  expect(paths).toContain(`${baseDir}/visible.txt`);
587
626
  }, timeout);
588
627
  it("should handle directories with spaces in names", async () => {
589
- const shared = getShared();
628
+ const shared = adaptSandboxInstance(getShared());
590
629
  const baseDir = config.resolvePath("li-spaces");
591
630
  await shared.write(`${baseDir}/file with spaces.txt`, "content");
592
631
  await shared.execute(`mkdir -p '${baseDir}/dir with spaces'`);
593
- const paths = (await shared.lsInfo(baseDir)).map((info) => info.path.replace(/\/$/, ""));
632
+ const lsResult = await shared.ls(baseDir);
633
+ expect(lsResult.error).toBeUndefined();
634
+ const paths = (lsResult.files || []).map((info) => info.path.replace(/\/$/, ""));
594
635
  expect(paths).toContain(`${baseDir}/file with spaces.txt`);
595
636
  expect(paths).toContain(`${baseDir}/dir with spaces`);
596
637
  }, timeout);
597
638
  it("should handle unicode filenames", async () => {
598
- const shared = getShared();
639
+ const shared = adaptSandboxInstance(getShared());
599
640
  const baseDir = config.resolvePath("li-unicode");
600
641
  await shared.write(`${baseDir}/\u6D4B\u8BD5\u6587\u4EF6.txt`, "content");
601
642
  await shared.write(`${baseDir}/\u0444\u0430\u0439\u043B.txt`, "content");
602
- expect((await shared.lsInfo(baseDir)).length).toBe(2);
643
+ const lsResult = await shared.ls(baseDir);
644
+ expect(lsResult.error).toBeUndefined();
645
+ expect((lsResult.files || []).length).toBe(2);
603
646
  }, timeout);
604
647
  it("should handle large directories", async () => {
605
- const shared = getShared();
648
+ const shared = adaptSandboxInstance(getShared());
606
649
  const baseDir = config.resolvePath("li-large");
607
650
  await shared.execute(`mkdir -p '${baseDir}' && cd '${baseDir}' && for i in \$(seq 0 49); do echo 'content' > file_\$(printf '%03d' \$i).txt; done`);
608
- const result = await shared.lsInfo(baseDir);
651
+ const lsResult = await shared.ls(baseDir);
652
+ expect(lsResult.error).toBeUndefined();
653
+ const result = lsResult.files || [];
609
654
  expect(result.length).toBe(50);
610
655
  const paths = result.map((info) => info.path);
611
656
  expect(paths).toContain(`${baseDir}/file_000.txt`);
612
657
  expect(paths).toContain(`${baseDir}/file_049.txt`);
613
658
  }, timeout);
614
659
  it("should handle trailing slash in path", async () => {
615
- const shared = getShared();
660
+ const shared = adaptSandboxInstance(getShared());
616
661
  const baseDir = config.resolvePath("li-trailing");
617
662
  await shared.write(`${baseDir}/file.txt`, "content");
618
- expect((await shared.lsInfo(`${baseDir}/`)).length).toBeGreaterThanOrEqual(1);
663
+ const lsResult = await shared.ls(`${baseDir}/`);
664
+ expect(lsResult.error).toBeUndefined();
665
+ expect((lsResult.files || []).length).toBeGreaterThanOrEqual(1);
619
666
  }, timeout);
620
667
  it("should handle special characters in filenames", async () => {
621
- const shared = getShared();
668
+ const shared = adaptSandboxInstance(getShared());
622
669
  const baseDir = config.resolvePath("li-special-chars");
623
670
  await shared.write(`${baseDir}/file(1).txt`, "content");
624
671
  await shared.write(`${baseDir}/file-3.txt`, "content");
625
- const paths = (await shared.lsInfo(baseDir)).map((info) => info.path);
672
+ const lsResult = await shared.ls(baseDir);
673
+ expect(lsResult.error).toBeUndefined();
674
+ const paths = (lsResult.files || []).map((info) => info.path);
626
675
  expect(paths).toContain(`${baseDir}/file(1).txt`);
627
676
  expect(paths).toContain(`${baseDir}/file-3.txt`);
628
677
  }, timeout);
629
678
  });
630
679
  }
631
-
632
680
  //#endregion
633
681
  //#region src/tests/grep-raw.ts
634
682
  /**
635
- * Register grepRaw() tests (basic search, glob filter, no matches,
683
+ * Register grep() tests (basic search, glob filter, no matches,
636
684
  * multi matches, literal matching, unicode, case sensitivity, special chars,
637
685
  * empty dir, nested dirs, line numbers).
638
686
  */
639
687
  function registerGrepRawTests(getShared, config, timeout) {
640
688
  const { describe, it, expect } = config.runner;
641
- describe("grepRaw", () => {
689
+ describe("grep", () => {
642
690
  it("should find basic literal pattern matches", async () => {
643
- const shared = getShared();
691
+ const shared = adaptSandboxInstance(getShared());
644
692
  const baseDir = config.resolvePath("gr-basic");
645
693
  await shared.write(`${baseDir}/file1.txt`, "Hello world\nGoodbye world");
646
694
  await shared.write(`${baseDir}/file2.txt`, "Hello there\nGoodbye friend");
647
- const result = await shared.grepRaw("Hello", baseDir);
648
- expect(Array.isArray(result)).toBe(true);
649
- const matches = result;
695
+ const result = await shared.grep("Hello", baseDir);
696
+ expect(result.error).toBeUndefined();
697
+ const matches = result.matches;
650
698
  expect(matches.length).toBe(2);
651
699
  const paths = matches.map((m) => m.path);
652
700
  expect(paths.some((p) => p.includes("file1.txt"))).toBe(true);
@@ -657,32 +705,32 @@ function registerGrepRawTests(getShared, config, timeout) {
657
705
  }
658
706
  }, timeout);
659
707
  it("should filter files with glob pattern", async () => {
660
- const shared = getShared();
708
+ const shared = adaptSandboxInstance(getShared());
661
709
  const baseDir = config.resolvePath("gr-glob");
662
710
  await shared.write(`${baseDir}/test.txt`, "pattern_match");
663
711
  await shared.write(`${baseDir}/test.py`, "pattern_match");
664
712
  await shared.write(`${baseDir}/test.md`, "pattern_match");
665
- const result = await shared.grepRaw("pattern_match", baseDir, "*.py");
666
- expect(Array.isArray(result)).toBe(true);
667
- const matches = result;
713
+ const result = await shared.grep("pattern_match", baseDir, "*.py");
714
+ expect(result.error).toBeUndefined();
715
+ const matches = result.matches;
668
716
  expect(matches.length).toBe(1);
669
717
  expect(matches[0].path).toContain("test.py");
670
718
  }, timeout);
671
- it("should return empty array when no matches found", async () => {
672
- const shared = getShared();
719
+ it("should return empty matches when no matches found", async () => {
720
+ const shared = adaptSandboxInstance(getShared());
673
721
  const baseDir = config.resolvePath("gr-no-match");
674
722
  await shared.write(`${baseDir}/file.txt`, "Hello world");
675
- const result = await shared.grepRaw("nonexistent_str", baseDir);
676
- expect(Array.isArray(result)).toBe(true);
677
- expect(result.length).toBe(0);
723
+ const result = await shared.grep("nonexistent_str", baseDir);
724
+ expect(result.error).toBeUndefined();
725
+ expect(result.matches.length).toBe(0);
678
726
  }, timeout);
679
727
  it("should find multiple matches in a single file", async () => {
680
- const shared = getShared();
728
+ const shared = adaptSandboxInstance(getShared());
681
729
  const baseDir = config.resolvePath("gr-multi");
682
730
  await shared.write(`${baseDir}/fruits.txt`, "apple\nbanana\napple\norange\napple");
683
- const result = await shared.grepRaw("apple", baseDir);
684
- expect(Array.isArray(result)).toBe(true);
685
- const matches = result;
731
+ const result = await shared.grep("apple", baseDir);
732
+ expect(result.error).toBeUndefined();
733
+ const matches = result.matches;
686
734
  expect(matches.length).toBe(3);
687
735
  expect(matches.map((m) => m.line)).toEqual([
688
736
  1,
@@ -691,99 +739,101 @@ function registerGrepRawTests(getShared, config, timeout) {
691
739
  ]);
692
740
  }, timeout);
693
741
  it("should match literal strings not regex", async () => {
694
- const shared = getShared();
742
+ const shared = adaptSandboxInstance(getShared());
695
743
  const baseDir = config.resolvePath("gr-literal");
696
744
  await shared.write(`${baseDir}/numbers.txt`, "test123\ntest456\nabcdef");
697
- const result = await shared.grepRaw("test123", baseDir);
698
- expect(Array.isArray(result)).toBe(true);
699
- const matches = result;
745
+ const result = await shared.grep("test123", baseDir);
746
+ expect(result.error).toBeUndefined();
747
+ const matches = result.matches;
700
748
  expect(matches.length).toBe(1);
701
749
  expect(matches[0].text).toContain("test123");
702
750
  }, timeout);
703
751
  it("should find unicode patterns", async () => {
704
- const shared = getShared();
752
+ const shared = adaptSandboxInstance(getShared());
705
753
  const baseDir = config.resolvePath("gr-unicode");
706
754
  await shared.write(`${baseDir}/unicode.txt`, "Hello 世界\nПривет мир\n测试 pattern");
707
- const result = await shared.grepRaw("世界", baseDir);
708
- expect(Array.isArray(result)).toBe(true);
709
- const matches = result;
755
+ const result = await shared.grep("世界", baseDir);
756
+ expect(result.error).toBeUndefined();
757
+ const matches = result.matches;
710
758
  expect(matches.length).toBe(1);
711
759
  expect(matches[0].text).toContain("世界");
712
760
  }, timeout);
713
761
  it("should be case-sensitive by default", async () => {
714
- const shared = getShared();
762
+ const shared = adaptSandboxInstance(getShared());
715
763
  const baseDir = config.resolvePath("gr-case");
716
764
  await shared.write(`${baseDir}/case.txt`, "Hello\nhello\nHELLO");
717
- const result = await shared.grepRaw("Hello", baseDir);
718
- expect(Array.isArray(result)).toBe(true);
719
- const matches = result;
765
+ const result = await shared.grep("Hello", baseDir);
766
+ expect(result.error).toBeUndefined();
767
+ const matches = result.matches;
720
768
  expect(matches.length).toBe(1);
721
769
  expect(matches[0].text).toContain("Hello");
722
770
  }, timeout);
723
771
  it("should handle special characters as literals", async () => {
724
- const shared = getShared();
772
+ const shared = adaptSandboxInstance(getShared());
725
773
  const baseDir = config.resolvePath("gr-special");
726
774
  await shared.write(`${baseDir}/special.txt`, "Price: $100\nPath: /usr/bin\nPattern: [a-z]*");
727
- const result1 = await shared.grepRaw("$100", baseDir);
728
- expect(Array.isArray(result1)).toBe(true);
729
- const matches1 = result1;
775
+ const result1 = await shared.grep("$100", baseDir);
776
+ expect(result1.error).toBeUndefined();
777
+ const matches1 = result1.matches;
730
778
  expect(matches1.length).toBe(1);
731
779
  expect(matches1[0].text).toContain("$100");
732
- const result2 = await shared.grepRaw("[a-z]*", baseDir);
733
- expect(Array.isArray(result2)).toBe(true);
734
- const matches2 = result2;
780
+ const result2 = await shared.grep("[a-z]*", baseDir);
781
+ expect(result2.error).toBeUndefined();
782
+ const matches2 = result2.matches;
735
783
  expect(matches2.length).toBe(1);
736
784
  expect(matches2[0].text).toContain("[a-z]*");
737
785
  }, timeout);
738
- it("should return empty array for empty directory", async () => {
739
- const shared = getShared();
786
+ it("should return empty matches for empty directory", async () => {
787
+ const shared = adaptSandboxInstance(getShared());
740
788
  const baseDir = config.resolvePath("gr-empty-dir");
741
789
  await shared.execute(`mkdir -p '${baseDir}'`);
742
- const result = await shared.grepRaw("anything", baseDir);
743
- expect(Array.isArray(result)).toBe(true);
744
- expect(result.length).toBe(0);
790
+ const result = await shared.grep("anything", baseDir);
791
+ expect(result.error).toBeUndefined();
792
+ expect(result.matches.length).toBe(0);
745
793
  }, timeout);
746
794
  it("should search recursively across nested directories", async () => {
747
- const shared = getShared();
795
+ const shared = adaptSandboxInstance(getShared());
748
796
  const baseDir = config.resolvePath("gr-nested");
749
797
  await shared.write(`${baseDir}/root.txt`, "target_nested here");
750
798
  await shared.write(`${baseDir}/sub1/level1.txt`, "target_nested here");
751
799
  await shared.write(`${baseDir}/sub1/sub2/level2.txt`, "target_nested here");
752
- const result = await shared.grepRaw("target_nested", baseDir);
753
- expect(Array.isArray(result)).toBe(true);
754
- expect(result.length).toBe(3);
800
+ const result = await shared.grep("target_nested", baseDir);
801
+ expect(result.error).toBeUndefined();
802
+ const matches = result.matches;
803
+ expect(matches.length).toBe(3);
755
804
  }, timeout);
756
805
  it("should report correct line numbers", async () => {
757
- const shared = getShared();
806
+ const shared = adaptSandboxInstance(getShared());
758
807
  const baseDir = config.resolvePath("gr-line-nums");
759
808
  const content = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`).join("\n");
760
809
  await shared.write(`${baseDir}/long.txt`, content);
761
- const result = await shared.grepRaw("Line 50", baseDir);
762
- expect(Array.isArray(result)).toBe(true);
763
- const matches = result;
810
+ const result = await shared.grep("Line 50", baseDir);
811
+ expect(result.error).toBeUndefined();
812
+ const matches = result.matches;
764
813
  expect(matches.length).toBe(1);
765
814
  expect(matches[0].line).toBe(50);
766
815
  }, timeout);
767
816
  });
768
817
  }
769
-
770
818
  //#endregion
771
819
  //#region src/tests/glob-info.ts
772
820
  /**
773
- * Register globInfo() tests (wildcard, recursive, no matches, directories,
821
+ * Register glob() tests (wildcard, recursive, no matches, directories,
774
822
  * extension filter, hidden files, character classes, question mark,
775
823
  * multiple extensions, deeply nested).
776
824
  */
777
825
  function registerGlobInfoTests(getShared, config, timeout) {
778
826
  const { describe, it, expect } = config.runner;
779
- describe("globInfo", () => {
827
+ describe("glob", () => {
780
828
  it("should match basic wildcard pattern", async () => {
781
- const shared = getShared();
829
+ const shared = adaptSandboxInstance(getShared());
782
830
  const baseDir = config.resolvePath("gl-basic");
783
831
  await shared.write(`${baseDir}/file1.txt`, "content");
784
832
  await shared.write(`${baseDir}/file2.txt`, "content");
785
833
  await shared.write(`${baseDir}/file3.py`, "content");
786
- const result = await shared.globInfo("*.txt", baseDir);
834
+ const globResult = await shared.glob("*.txt", baseDir);
835
+ expect(globResult.error).toBeUndefined();
836
+ const result = globResult.files || [];
787
837
  expect(result.length).toBe(2);
788
838
  const paths = result.map((info) => info.path);
789
839
  expect(paths).toContain("file1.txt");
@@ -791,29 +841,35 @@ function registerGlobInfoTests(getShared, config, timeout) {
791
841
  expect(paths.every((p) => !p.endsWith(".py"))).toBe(true);
792
842
  }, timeout);
793
843
  it("should match recursive pattern (**)", async () => {
794
- const shared = getShared();
844
+ const shared = adaptSandboxInstance(getShared());
795
845
  const baseDir = config.resolvePath("gl-recursive");
796
846
  await shared.write(`${baseDir}/root.txt`, "content");
797
847
  await shared.write(`${baseDir}/subdir1/nested1.txt`, "content");
798
848
  await shared.write(`${baseDir}/subdir2/nested2.txt`, "content");
799
- const result = await shared.globInfo("**/*.txt", baseDir);
849
+ const globResult = await shared.glob("**/*.txt", baseDir);
850
+ expect(globResult.error).toBeUndefined();
851
+ const result = globResult.files || [];
800
852
  expect(result.length).toBeGreaterThanOrEqual(2);
801
853
  const paths = result.map((info) => info.path);
802
854
  expect(paths.some((p) => p.includes("nested1.txt"))).toBe(true);
803
855
  expect(paths.some((p) => p.includes("nested2.txt"))).toBe(true);
804
856
  }, timeout);
805
857
  it("should return empty array when no matches", async () => {
806
- const shared = getShared();
858
+ const shared = adaptSandboxInstance(getShared());
807
859
  const baseDir = config.resolvePath("gl-no-match");
808
860
  await shared.write(`${baseDir}/file.txt`, "content");
809
- expect(await shared.globInfo("*.py", baseDir)).toEqual([]);
861
+ const globResult = await shared.glob("*.py", baseDir);
862
+ expect(globResult.error).toBeUndefined();
863
+ expect(globResult.files).toEqual([]);
810
864
  }, timeout);
811
865
  it("should include directories in results", async () => {
812
- const shared = getShared();
866
+ const shared = adaptSandboxInstance(getShared());
813
867
  const baseDir = config.resolvePath("gl-dirs");
814
868
  await shared.execute(`mkdir -p '${baseDir}/dir1' '${baseDir}/dir2'`);
815
869
  await shared.write(`${baseDir}/file.txt`, "content");
816
- const result = await shared.globInfo("*", baseDir);
870
+ const globResult = await shared.glob("*", baseDir);
871
+ expect(globResult.error).toBeUndefined();
872
+ const result = globResult.files || [];
817
873
  expect(result.length).toBe(3);
818
874
  const dirCount = result.filter((info) => info.is_dir).length;
819
875
  const fileCount = result.filter((info) => !info.is_dir).length;
@@ -821,33 +877,39 @@ function registerGlobInfoTests(getShared, config, timeout) {
821
877
  expect(fileCount).toBe(1);
822
878
  }, timeout);
823
879
  it("should match specific file extensions", async () => {
824
- const shared = getShared();
880
+ const shared = adaptSandboxInstance(getShared());
825
881
  const baseDir = config.resolvePath("gl-ext");
826
882
  await shared.write(`${baseDir}/test.py`, "content");
827
883
  await shared.write(`${baseDir}/test.txt`, "content");
828
884
  await shared.write(`${baseDir}/test.md`, "content");
829
- const result = await shared.globInfo("*.py", baseDir);
885
+ const globResult = await shared.glob("*.py", baseDir);
886
+ expect(globResult.error).toBeUndefined();
887
+ const result = globResult.files || [];
830
888
  expect(result.length).toBe(1);
831
889
  expect(result[0].path).toContain("test.py");
832
890
  }, timeout);
833
891
  it("should match hidden files explicitly", async () => {
834
- const shared = getShared();
892
+ const shared = adaptSandboxInstance(getShared());
835
893
  const baseDir = config.resolvePath("gl-hidden");
836
894
  await shared.write(`${baseDir}/.hidden1`, "content");
837
895
  await shared.write(`${baseDir}/.hidden2`, "content");
838
896
  await shared.write(`${baseDir}/visible.txt`, "content");
839
- const paths = (await shared.globInfo(".*", baseDir)).map((info) => info.path);
897
+ const globResult = await shared.glob(".*", baseDir);
898
+ expect(globResult.error).toBeUndefined();
899
+ const paths = (globResult.files || []).map((info) => info.path);
840
900
  expect(paths.some((p) => p.includes(".hidden1") || p.includes(".hidden2"))).toBe(true);
841
901
  expect(paths.every((p) => !p.includes("visible"))).toBe(true);
842
902
  }, timeout);
843
903
  it("should match character class patterns", async () => {
844
- const shared = getShared();
904
+ const shared = adaptSandboxInstance(getShared());
845
905
  const baseDir = config.resolvePath("gl-charclass");
846
906
  await shared.write(`${baseDir}/file1.txt`, "content");
847
907
  await shared.write(`${baseDir}/file2.txt`, "content");
848
908
  await shared.write(`${baseDir}/file3.txt`, "content");
849
909
  await shared.write(`${baseDir}/fileA.txt`, "content");
850
- const result = await shared.globInfo("file[1-2].txt", baseDir);
910
+ const globResult = await shared.glob("file[1-2].txt", baseDir);
911
+ expect(globResult.error).toBeUndefined();
912
+ const result = globResult.files || [];
851
913
  expect(result.length).toBe(2);
852
914
  const paths = result.map((info) => info.path);
853
915
  expect(paths).toContain("file1.txt");
@@ -856,39 +918,46 @@ function registerGlobInfoTests(getShared, config, timeout) {
856
918
  expect(paths).not.toContain("fileA.txt");
857
919
  }, timeout);
858
920
  it("should match single character wildcard (?)", async () => {
859
- const shared = getShared();
921
+ const shared = adaptSandboxInstance(getShared());
860
922
  const baseDir = config.resolvePath("gl-question");
861
923
  await shared.write(`${baseDir}/file1.txt`, "content");
862
924
  await shared.write(`${baseDir}/file2.txt`, "content");
863
925
  await shared.write(`${baseDir}/file10.txt`, "content");
864
- const result = await shared.globInfo("file?.txt", baseDir);
926
+ const globResult = await shared.glob("file?.txt", baseDir);
927
+ expect(globResult.error).toBeUndefined();
928
+ const result = globResult.files || [];
865
929
  expect(result.length).toBe(2);
866
930
  expect(result.map((info) => info.path)).not.toContain("file10.txt");
867
931
  }, timeout);
868
932
  it("should match multiple extensions separately", async () => {
869
- const shared = getShared();
933
+ const shared = adaptSandboxInstance(getShared());
870
934
  const baseDir = config.resolvePath("gl-multi-ext");
871
935
  await shared.write(`${baseDir}/file.txt`, "content");
872
936
  await shared.write(`${baseDir}/file.py`, "content");
873
937
  await shared.write(`${baseDir}/file.md`, "content");
874
938
  await shared.write(`${baseDir}/file.js`, "content");
875
- const resultTxt = await shared.globInfo("*.txt", baseDir);
876
- const resultPy = await shared.globInfo("*.py", baseDir);
939
+ const globResultTxt = await shared.glob("*.txt", baseDir);
940
+ expect(globResultTxt.error).toBeUndefined();
941
+ const resultTxt = globResultTxt.files || [];
942
+ const globResultPy = await shared.glob("*.py", baseDir);
943
+ expect(globResultPy.error).toBeUndefined();
944
+ const resultPy = globResultPy.files || [];
877
945
  expect(resultTxt.length).toBe(1);
878
946
  expect(resultPy.length).toBe(1);
879
947
  }, timeout);
880
948
  it("should match deeply nested patterns", async () => {
881
- const shared = getShared();
949
+ const shared = adaptSandboxInstance(getShared());
882
950
  const baseDir = config.resolvePath("gl-deep");
883
951
  await shared.write(`${baseDir}/a/b/c/d/deep.txt`, "content");
884
952
  await shared.write(`${baseDir}/a/b/other.txt`, "content");
885
- const result = await shared.globInfo("**/deep.txt", baseDir);
953
+ const globResult = await shared.glob("**/deep.txt", baseDir);
954
+ expect(globResult.error).toBeUndefined();
955
+ const result = globResult.files || [];
886
956
  expect(result.length).toBeGreaterThanOrEqual(1);
887
957
  expect(result.some((info) => info.path.includes("deep.txt"))).toBe(true);
888
958
  }, timeout);
889
959
  });
890
960
  }
891
-
892
961
  //#endregion
893
962
  //#region src/tests/initial-files.ts
894
963
  /**
@@ -948,7 +1017,7 @@ function registerInitialFilesTests(config, timeout) {
948
1017
  const filePath = config.resolvePath("init-read-test.txt");
949
1018
  const tmp = await withRetry(() => config.createSandbox({ initialFiles: { [filePath]: "Content for read test" } }));
950
1019
  try {
951
- expect(await tmp.read(filePath)).toContain("Content for read test");
1020
+ expect((await adaptSandboxInstance(tmp).read(filePath)).content).toContain("Content for read test");
952
1021
  } finally {
953
1022
  await config.closeSandbox?.(tmp);
954
1023
  }
@@ -976,19 +1045,20 @@ function registerInitialFilesTests(config, timeout) {
976
1045
  await config.closeSandbox?.(tmp);
977
1046
  }
978
1047
  }, timeout);
979
- it("should make initialFiles in subdirectories visible via lsInfo()", async () => {
1048
+ it("should make initialFiles in subdirectories visible via ls()", async () => {
980
1049
  const dirPath = config.resolvePath("init-ls-dir");
981
1050
  const filePath = `${dirPath}/file.txt`;
982
1051
  const tmp = await withRetry(() => config.createSandbox({ initialFiles: { [filePath]: "ls test content" } }));
983
1052
  try {
984
- expect((await tmp.lsInfo(dirPath)).map((e) => e.path.replace(/\/$/, ""))).toContain(filePath);
1053
+ const lsResult = await adaptSandboxInstance(tmp).ls(dirPath);
1054
+ expect(lsResult.error).toBeUndefined();
1055
+ expect((lsResult.files || []).map((e) => e.path.replace(/\/$/, ""))).toContain(filePath);
985
1056
  } finally {
986
1057
  await config.closeSandbox?.(tmp);
987
1058
  }
988
1059
  }, timeout);
989
1060
  });
990
1061
  }
991
-
992
1062
  //#endregion
993
1063
  //#region src/tests/integration.ts
994
1064
  /**
@@ -998,34 +1068,37 @@ function registerIntegrationTests(getShared, config, timeout) {
998
1068
  const { describe, it, expect } = config.runner;
999
1069
  describe("integration workflows", () => {
1000
1070
  it("should complete a write-read-edit-read workflow", async () => {
1001
- const shared = getShared();
1071
+ const shared = adaptSandboxInstance(getShared());
1002
1072
  const filePath = config.resolvePath("intg-workflow.txt");
1003
1073
  expect((await shared.write(filePath, "Original content")).error).toBeUndefined();
1004
- expect(await shared.read(filePath)).toContain("Original content");
1074
+ expect((await shared.read(filePath)).content).toContain("Original content");
1005
1075
  expect((await shared.edit(filePath, "Original", "Modified")).error).toBeUndefined();
1006
1076
  const updatedContent = await shared.read(filePath);
1007
- expect(updatedContent).toContain("Modified content");
1008
- expect(updatedContent).not.toContain("Original");
1077
+ expect(updatedContent.content).toContain("Modified content");
1078
+ expect(updatedContent.content).not.toContain("Original");
1009
1079
  }, timeout);
1010
1080
  it("should handle complex directory operations", async () => {
1011
- const shared = getShared();
1081
+ const shared = adaptSandboxInstance(getShared());
1012
1082
  const baseDir = config.resolvePath("intg-complex");
1013
1083
  await shared.write(`${baseDir}/root.txt`, "root file");
1014
1084
  await shared.write(`${baseDir}/subdir1/file1.txt`, "file 1");
1015
1085
  await shared.write(`${baseDir}/subdir1/file2.py`, "file 2");
1016
1086
  await shared.write(`${baseDir}/subdir2/file3.txt`, "file 3");
1017
- const lsPaths = (await shared.lsInfo(baseDir)).map((info) => info.path.replace(/\/$/, ""));
1087
+ const lsResult = await shared.ls(baseDir);
1088
+ expect(lsResult.error).toBeUndefined();
1089
+ const lsPaths = (lsResult.files || []).map((info) => info.path.replace(/\/$/, ""));
1018
1090
  expect(lsPaths).toContain(`${baseDir}/root.txt`);
1019
1091
  expect(lsPaths).toContain(`${baseDir}/subdir1`);
1020
1092
  expect(lsPaths).toContain(`${baseDir}/subdir2`);
1021
- expect((await shared.globInfo("**/*.txt", baseDir)).length).toBe(3);
1022
- const grepResult = await shared.grepRaw("file", baseDir);
1023
- expect(Array.isArray(grepResult)).toBe(true);
1024
- expect(grepResult.length).toBeGreaterThanOrEqual(3);
1093
+ const globResult = await shared.glob("**/*.txt", baseDir);
1094
+ expect(globResult.error).toBeUndefined();
1095
+ expect((globResult.files || []).length).toBe(3);
1096
+ const grepResult = await shared.grep("file", baseDir);
1097
+ expect(grepResult.error).toBeUndefined();
1098
+ expect(grepResult.matches.length).toBeGreaterThanOrEqual(3);
1025
1099
  }, timeout);
1026
1100
  });
1027
1101
  }
1028
-
1029
1102
  //#endregion
1030
1103
  //#region src/sandbox.ts
1031
1104
  /**
@@ -1050,9 +1123,9 @@ function registerIntegrationTests(getShared, config, timeout) {
1050
1123
  * - write() (new file, parent dirs, existing file, special chars, unicode, long content)
1051
1124
  * - read() (basic, nonexistent, offset, limit, offset+limit, unicode, chunked)
1052
1125
  * - edit() (single/multi occurrence, replaceAll, not found, special chars, multiline, unicode)
1053
- * - lsInfo() (basic listing, empty dir, hidden files, large dir, absolute paths)
1054
- * - grepRaw() (basic search, glob filter, case sensitivity, nested dirs, unicode)
1055
- * - globInfo() (wildcard, recursive, extension filter, character classes, deeply nested)
1126
+ * - ls() (basic listing, empty dir, hidden files, large dir, absolute paths)
1127
+ * - grep() (basic search, glob filter, case sensitivity, nested dirs, unicode)
1128
+ * - glob() (wildcard, recursive, extension filter, character classes, deeply nested)
1056
1129
  * - Initial files support (basic, nested, empty)
1057
1130
  * - Integration workflows (write-read-edit, complex directory operations)
1058
1131
  * - Error handling (file not found, non-existent command)
@@ -1151,7 +1224,7 @@ function sandboxStandardTests(config) {
1151
1224
  registerIntegrationTests(getShared, config, timeout);
1152
1225
  });
1153
1226
  }
1154
-
1155
1227
  //#endregion
1156
1228
  export { withRetry as n, sandboxStandardTests as t };
1157
- //# sourceMappingURL=sandbox-C1ApHibz.js.map
1229
+
1230
+ //# sourceMappingURL=sandbox-BbDX1T-X.js.map