@isol8/core 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1491,7 +1491,7 @@ class ExecutionManager {
1491
1491
  stream.on("error", reject);
1492
1492
  });
1493
1493
  }
1494
- async runSetupScript(container, script, timeoutMs, volumeManager) {
1494
+ async* runSetupScript(container, script, timeoutMs, volumeManager) {
1495
1495
  const scriptPath = "/sandbox/.isol8-setup.sh";
1496
1496
  await volumeManager.writeFileViaExec(container, scriptPath, script);
1497
1497
  const chmodExec = await container.exec({
@@ -1519,34 +1519,67 @@ class ExecutionManager {
1519
1519
  User: "sandbox"
1520
1520
  });
1521
1521
  const stream = await exec.start({ Detach: false, Tty: false });
1522
- return new Promise((resolve2, reject) => {
1523
- let stderr = "";
1524
- const stdoutStream = new PassThrough;
1525
- const stderrStream = new PassThrough;
1526
- container.modem.demuxStream(stream, stdoutStream, stderrStream);
1527
- stderrStream.on("data", (chunk) => {
1528
- const text = chunk.toString();
1529
- stderr += text;
1530
- logger.debug(`[setup:stderr] ${text.trimEnd()}`);
1531
- });
1532
- stdoutStream.on("data", (chunk) => {
1533
- const text = chunk.toString();
1534
- logger.debug(`[setup:stdout] ${text.trimEnd()}`);
1535
- });
1536
- stream.on("end", async () => {
1537
- try {
1538
- const info = await exec.inspect();
1539
- if (info.ExitCode !== 0) {
1540
- reject(new Error(`Setup script failed (exit code ${info.ExitCode}): ${stderr}`));
1541
- } else {
1542
- resolve2();
1543
- }
1544
- } catch (err) {
1545
- reject(err);
1522
+ const queue = [];
1523
+ let notify = null;
1524
+ let done = false;
1525
+ const push = (event) => {
1526
+ queue.push(event);
1527
+ if (notify) {
1528
+ notify();
1529
+ notify = null;
1530
+ }
1531
+ };
1532
+ const timer = setTimeout(() => {
1533
+ push({ type: "error", data: "SETUP SCRIPT TIMED OUT", phase: "setup" });
1534
+ push({ type: "exit", data: "137", phase: "setup" });
1535
+ done = true;
1536
+ }, timeoutMs);
1537
+ const stdoutStream = new PassThrough;
1538
+ const stderrStream = new PassThrough;
1539
+ container.modem.demuxStream(stream, stdoutStream, stderrStream);
1540
+ stdoutStream.on("data", (chunk) => {
1541
+ const text = chunk.toString("utf-8");
1542
+ logger.debug(`[setup:stdout] ${text.trimEnd()}`);
1543
+ push({ type: "stdout", data: text, phase: "setup" });
1544
+ });
1545
+ stderrStream.on("data", (chunk) => {
1546
+ const text = chunk.toString("utf-8");
1547
+ logger.debug(`[setup:stderr] ${text.trimEnd()}`);
1548
+ push({ type: "stderr", data: text, phase: "setup" });
1549
+ });
1550
+ stream.on("end", async () => {
1551
+ clearTimeout(timer);
1552
+ try {
1553
+ const info = await exec.inspect();
1554
+ const exitCode = info.ExitCode ?? 0;
1555
+ if (exitCode !== 0) {
1556
+ push({
1557
+ type: "error",
1558
+ data: `Setup script failed (exit code ${exitCode})`,
1559
+ phase: "setup"
1560
+ });
1546
1561
  }
1547
- });
1548
- stream.on("error", reject);
1562
+ push({ type: "exit", data: exitCode.toString(), phase: "setup" });
1563
+ } catch {
1564
+ push({ type: "exit", data: "1", phase: "setup" });
1565
+ }
1566
+ done = true;
1549
1567
  });
1568
+ stream.on("error", (err) => {
1569
+ clearTimeout(timer);
1570
+ push({ type: "error", data: err.message, phase: "setup" });
1571
+ push({ type: "exit", data: "1", phase: "setup" });
1572
+ done = true;
1573
+ });
1574
+ while (!done || queue.length > 0) {
1575
+ if (queue.length > 0) {
1576
+ yield queue.shift();
1577
+ } else {
1578
+ await new Promise((r) => {
1579
+ notify = r;
1580
+ });
1581
+ }
1582
+ }
1550
1583
  }
1551
1584
  async* streamExecOutput(stream, exec, container, timeoutMs) {
1552
1585
  const queue = [];
@@ -1560,8 +1593,8 @@ class ExecutionManager {
1560
1593
  }
1561
1594
  };
1562
1595
  const timer = setTimeout(() => {
1563
- push({ type: "error", data: "EXECUTION TIMED OUT" });
1564
- push({ type: "exit", data: "137" });
1596
+ push({ type: "error", data: "EXECUTION TIMED OUT", phase: "code" });
1597
+ push({ type: "exit", data: "137", phase: "code" });
1565
1598
  done = true;
1566
1599
  }, timeoutMs);
1567
1600
  const stdoutStream = new PassThrough;
@@ -1572,29 +1605,29 @@ class ExecutionManager {
1572
1605
  if (Object.keys(this.secrets).length > 0) {
1573
1606
  text = maskSecrets(text, this.secrets);
1574
1607
  }
1575
- push({ type: "stdout", data: text });
1608
+ push({ type: "stdout", data: text, phase: "code" });
1576
1609
  });
1577
1610
  stderrStream.on("data", (chunk) => {
1578
1611
  let text = chunk.toString("utf-8");
1579
1612
  if (Object.keys(this.secrets).length > 0) {
1580
1613
  text = maskSecrets(text, this.secrets);
1581
1614
  }
1582
- push({ type: "stderr", data: text });
1615
+ push({ type: "stderr", data: text, phase: "code" });
1583
1616
  });
1584
1617
  stream.on("end", async () => {
1585
1618
  clearTimeout(timer);
1586
1619
  try {
1587
1620
  const info = await exec.inspect();
1588
- push({ type: "exit", data: (info.ExitCode ?? 0).toString() });
1621
+ push({ type: "exit", data: (info.ExitCode ?? 0).toString(), phase: "code" });
1589
1622
  } catch {
1590
- push({ type: "exit", data: "1" });
1623
+ push({ type: "exit", data: "1", phase: "code" });
1591
1624
  }
1592
1625
  done = true;
1593
1626
  });
1594
1627
  stream.on("error", (err) => {
1595
1628
  clearTimeout(timer);
1596
- push({ type: "error", data: err.message });
1597
- push({ type: "exit", data: "1" });
1629
+ push({ type: "error", data: err.message, phase: "code" });
1630
+ push({ type: "exit", data: "1", phase: "code" });
1598
1631
  done = true;
1599
1632
  });
1600
1633
  while (!done || queue.length > 0) {
@@ -2232,11 +2265,16 @@ class DockerIsol8 {
2232
2265
  async resolveExecutionRequest(req) {
2233
2266
  const inlineCode = req.code?.trim();
2234
2267
  const codeUrl = req.codeUrl?.trim();
2235
- if (inlineCode && codeUrl) {
2236
- throw new Error("ExecutionRequest.code and ExecutionRequest.codeUrl are mutually exclusive.");
2268
+ const cmd = req.cmd?.trim();
2269
+ const sourceCount = [inlineCode, codeUrl, cmd].filter(Boolean).length;
2270
+ if (sourceCount > 1) {
2271
+ throw new Error("ExecutionRequest.code, ExecutionRequest.codeUrl, and ExecutionRequest.cmd are mutually exclusive — provide exactly one.");
2237
2272
  }
2238
- if (!(inlineCode || codeUrl)) {
2239
- throw new Error("ExecutionRequest must include either code or codeUrl.");
2273
+ if (sourceCount === 0) {
2274
+ throw new Error("ExecutionRequest must include exactly one of: code, codeUrl, or cmd.");
2275
+ }
2276
+ if (cmd) {
2277
+ return { ...req, cmd: req.cmd };
2240
2278
  }
2241
2279
  if (inlineCode) {
2242
2280
  return { ...req, code: req.code };
@@ -2358,7 +2396,7 @@ class DockerIsol8 {
2358
2396
  async recordAudit(req, result, startTime, container) {
2359
2397
  try {
2360
2398
  const enc = new TextEncoder;
2361
- const data = enc.encode(req.code);
2399
+ const data = enc.encode(req.code ?? req.cmd ?? "");
2362
2400
  const digest = await crypto.subtle.digest("SHA-256", data);
2363
2401
  const codeHash = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
2364
2402
  let securityEvents;
@@ -2491,73 +2529,171 @@ class DockerIsol8 {
2491
2529
  try {
2492
2530
  this.validateAgentRuntime(req);
2493
2531
  const request = await this.resolveExecutionRequest(req);
2494
- const adapter = this.getAdapter(request.runtime);
2495
- const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
2496
- const resolved = await this.resolveImage(adapter, request.installPackages);
2497
- const image = resolved.image;
2498
- const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
2499
- const container = await this.docker.createContainer({
2500
- Image: image,
2501
- Cmd: ["sleep", "infinity"],
2502
- WorkingDir: SANDBOX_WORKDIR,
2503
- Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
2504
- NetworkDisabled: this.network === "none",
2505
- HostConfig: this.buildHostConfig(),
2506
- StopTimeout: 2
2507
- });
2508
- try {
2509
- await container.start();
2510
- await this.networkManager.startProxy(container);
2511
- await this.networkManager.setupIptables(container);
2512
- const ext = request.fileExtension ?? adapter.getFileExtension();
2513
- const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2514
- await this.volumeManager.writeFileViaExec(container, filePath, request.code);
2515
- if (resolved.remainingPackages.length > 0) {
2516
- await this.executionManager.installPackages(container, request.runtime, resolved.remainingPackages, timeoutMs);
2532
+ if (this.mode === "persistent") {
2533
+ yield* this.executeStreamPersistent(request);
2534
+ } else {
2535
+ yield* this.executeStreamEphemeral(request);
2536
+ }
2537
+ } finally {
2538
+ this.semaphore.release();
2539
+ }
2540
+ }
2541
+ async* executeStreamPersistent(request) {
2542
+ const adapter = this.getAdapter(request.runtime);
2543
+ const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
2544
+ const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
2545
+ let remainingPackages = request.installPackages ?? [];
2546
+ let imageSetupScript;
2547
+ if (!this.container) {
2548
+ const started = await this.startPersistentContainer(adapter, request.installPackages);
2549
+ remainingPackages = started.remainingPackages;
2550
+ imageSetupScript = started.imageSetupScript;
2551
+ } else if (this.persistentRuntime?.name !== adapter.name) {
2552
+ throw new Error(`Cannot switch runtime from "${this.persistentRuntime?.name}" to "${adapter.name}". Each persistent container supports a single runtime. Create a new Isol8 instance for a different runtime.`);
2553
+ }
2554
+ let rawCmd;
2555
+ if (request.cmd) {
2556
+ rawCmd = ["bash", "-c", request.cmd];
2557
+ } else {
2558
+ const ext = request.fileExtension ?? adapter.getFileExtension();
2559
+ const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
2560
+ await this.volumeManager.putFile(this.container, filePath, request.code);
2561
+ rawCmd = this.buildAdapterCommand(adapter, request, filePath);
2562
+ }
2563
+ if (request.files) {
2564
+ for (const [fPath, fContent] of Object.entries(request.files)) {
2565
+ await this.volumeManager.putFile(this.container, fPath, fContent);
2566
+ }
2567
+ }
2568
+ if (remainingPackages.length > 0) {
2569
+ await this.executionManager.installPackages(this.container, request.runtime, remainingPackages, timeoutMs);
2570
+ }
2571
+ if (imageSetupScript) {
2572
+ let setupExitCode = 0;
2573
+ for await (const event of this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager)) {
2574
+ yield event;
2575
+ if (event.type === "exit") {
2576
+ setupExitCode = Number(event.data);
2517
2577
  }
2518
- if (resolved.imageSetupScript) {
2519
- await this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager);
2578
+ }
2579
+ if (setupExitCode !== 0) {
2580
+ return;
2581
+ }
2582
+ }
2583
+ if (request.setupScript) {
2584
+ let setupExitCode = 0;
2585
+ for await (const event of this.executionManager.runSetupScript(this.container, request.setupScript, timeoutMs, this.volumeManager)) {
2586
+ yield event;
2587
+ if (event.type === "exit") {
2588
+ setupExitCode = Number(event.data);
2589
+ }
2590
+ }
2591
+ if (setupExitCode !== 0) {
2592
+ return;
2593
+ }
2594
+ }
2595
+ const timeoutSec = Math.ceil(timeoutMs / 1000);
2596
+ let cmd;
2597
+ if (request.stdin) {
2598
+ const stdinPath = `${SANDBOX_WORKDIR}/_stdin_${Date.now()}`;
2599
+ await this.volumeManager.writeFileViaExec(this.container, stdinPath, request.stdin);
2600
+ const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
2601
+ cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
2602
+ } else {
2603
+ cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
2604
+ }
2605
+ const exec = await this.container.exec({
2606
+ Cmd: cmd,
2607
+ Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
2608
+ AttachStdout: true,
2609
+ AttachStderr: true,
2610
+ WorkingDir: execWorkdir,
2611
+ User: "sandbox"
2612
+ });
2613
+ const execStream = await exec.start({ Tty: false });
2614
+ yield* this.executionManager.streamExecOutput(execStream, exec, this.container, timeoutMs);
2615
+ }
2616
+ async* executeStreamEphemeral(request) {
2617
+ const adapter = this.getAdapter(request.runtime);
2618
+ const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
2619
+ const resolved = await this.resolveImage(adapter, request.installPackages);
2620
+ const image = resolved.image;
2621
+ const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
2622
+ const pool = this.ensurePool();
2623
+ const container = await pool.acquire(image);
2624
+ try {
2625
+ await this.networkManager.startProxy(container);
2626
+ await this.networkManager.setupIptables(container);
2627
+ if (resolved.remainingPackages.length > 0) {
2628
+ await this.executionManager.installPackages(container, request.runtime, resolved.remainingPackages, timeoutMs);
2629
+ }
2630
+ if (resolved.imageSetupScript) {
2631
+ let setupExitCode = 0;
2632
+ for await (const event of this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager)) {
2633
+ yield event;
2634
+ if (event.type === "exit") {
2635
+ setupExitCode = Number(event.data);
2636
+ }
2520
2637
  }
2521
- if (request.setupScript) {
2522
- await this.executionManager.runSetupScript(container, request.setupScript, timeoutMs, this.volumeManager);
2638
+ if (setupExitCode !== 0) {
2639
+ return;
2523
2640
  }
2524
- if (request.files) {
2525
- for (const [fPath, fContent] of Object.entries(request.files)) {
2526
- await this.volumeManager.writeFileViaExec(container, fPath, fContent);
2641
+ }
2642
+ if (request.setupScript) {
2643
+ let setupExitCode = 0;
2644
+ for await (const event of this.executionManager.runSetupScript(container, request.setupScript, timeoutMs, this.volumeManager)) {
2645
+ yield event;
2646
+ if (event.type === "exit") {
2647
+ setupExitCode = Number(event.data);
2527
2648
  }
2528
2649
  }
2529
- const rawCmd = this.buildAdapterCommand(adapter, request, filePath);
2530
- const timeoutSec = Math.ceil(timeoutMs / 1000);
2531
- let cmd;
2532
- if (request.stdin) {
2533
- const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
2534
- await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
2535
- const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
2536
- cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
2537
- } else {
2538
- cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
2650
+ if (setupExitCode !== 0) {
2651
+ return;
2539
2652
  }
2540
- const exec = await container.exec({
2541
- Cmd: cmd,
2542
- Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
2543
- AttachStdout: true,
2544
- AttachStderr: true,
2545
- WorkingDir: execWorkdir,
2546
- User: "sandbox"
2547
- });
2548
- const execStream = await exec.start({ Tty: false });
2549
- yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
2550
- } finally {
2551
- if (this.persist) {
2552
- logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
2553
- } else {
2554
- try {
2555
- await container.remove({ force: true });
2556
- } catch {}
2653
+ }
2654
+ if (request.files) {
2655
+ for (const [fPath, fContent] of Object.entries(request.files)) {
2656
+ await this.volumeManager.writeFileViaExec(container, fPath, fContent);
2557
2657
  }
2558
2658
  }
2659
+ let rawCmd;
2660
+ if (request.cmd) {
2661
+ rawCmd = ["bash", "-c", request.cmd];
2662
+ } else {
2663
+ const ext = request.fileExtension ?? adapter.getFileExtension();
2664
+ const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2665
+ await this.volumeManager.writeFileViaExec(container, filePath, request.code);
2666
+ rawCmd = this.buildAdapterCommand(adapter, request, filePath);
2667
+ }
2668
+ const timeoutSec = Math.ceil(timeoutMs / 1000);
2669
+ let cmd;
2670
+ if (request.stdin) {
2671
+ const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
2672
+ await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
2673
+ const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
2674
+ cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
2675
+ } else {
2676
+ cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
2677
+ }
2678
+ const exec = await container.exec({
2679
+ Cmd: cmd,
2680
+ Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
2681
+ AttachStdout: true,
2682
+ AttachStderr: true,
2683
+ WorkingDir: execWorkdir,
2684
+ User: "sandbox"
2685
+ });
2686
+ const execStream = await exec.start({ Tty: false });
2687
+ yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
2559
2688
  } finally {
2560
- this.semaphore.release();
2689
+ if (this.persist) {
2690
+ logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
2691
+ } else {
2692
+ pool.release(container, image).catch((err) => {
2693
+ logger.debug(`[Pool] release failed: ${err}`);
2694
+ container.remove({ force: true }).catch(() => {});
2695
+ });
2696
+ }
2561
2697
  }
2562
2698
  }
2563
2699
  async resolveImage(adapter, requestedPackages) {
@@ -2684,31 +2820,59 @@ class DockerIsol8 {
2684
2820
  try {
2685
2821
  await this.networkManager.startProxy(container);
2686
2822
  await this.networkManager.setupIptables(container);
2687
- const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
2688
2823
  let rawCmd;
2689
- if (canUseInline) {
2690
- try {
2691
- rawCmd = this.buildAdapterCommand(adapter, req);
2692
- } catch {
2824
+ if (req.cmd) {
2825
+ rawCmd = ["bash", "-c", req.cmd];
2826
+ } else {
2827
+ const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
2828
+ if (canUseInline) {
2829
+ try {
2830
+ rawCmd = this.buildAdapterCommand(adapter, req);
2831
+ } catch {
2832
+ const ext = req.fileExtension ?? adapter.getFileExtension();
2833
+ const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2834
+ await this.volumeManager.writeFileViaExec(container, filePath, req.code);
2835
+ rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2836
+ }
2837
+ } else {
2693
2838
  const ext = req.fileExtension ?? adapter.getFileExtension();
2694
2839
  const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2695
2840
  await this.volumeManager.writeFileViaExec(container, filePath, req.code);
2696
2841
  rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2697
2842
  }
2698
- } else {
2699
- const ext = req.fileExtension ?? adapter.getFileExtension();
2700
- const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2701
- await this.volumeManager.writeFileViaExec(container, filePath, req.code);
2702
- rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2703
2843
  }
2704
2844
  if (resolved.remainingPackages.length > 0) {
2705
2845
  await this.executionManager.installPackages(container, req.runtime, resolved.remainingPackages, timeoutMs);
2706
2846
  }
2707
2847
  if (resolved.imageSetupScript) {
2708
- await this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager);
2848
+ let stderr2 = "";
2849
+ let exitCode = 0;
2850
+ for await (const event of this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager)) {
2851
+ if (event.type === "stderr") {
2852
+ stderr2 += event.data;
2853
+ }
2854
+ if (event.type === "exit") {
2855
+ exitCode = Number(event.data);
2856
+ }
2857
+ }
2858
+ if (exitCode !== 0) {
2859
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
2860
+ }
2709
2861
  }
2710
2862
  if (req.setupScript) {
2711
- await this.executionManager.runSetupScript(container, req.setupScript, timeoutMs, this.volumeManager);
2863
+ let stderr2 = "";
2864
+ let exitCode = 0;
2865
+ for await (const event of this.executionManager.runSetupScript(container, req.setupScript, timeoutMs, this.volumeManager)) {
2866
+ if (event.type === "stderr") {
2867
+ stderr2 += event.data;
2868
+ }
2869
+ if (event.type === "exit") {
2870
+ exitCode = Number(event.data);
2871
+ }
2872
+ }
2873
+ if (exitCode !== 0) {
2874
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
2875
+ }
2712
2876
  }
2713
2877
  const timeoutSec = Math.ceil(timeoutMs / 1000);
2714
2878
  let cmd;
@@ -2800,24 +2964,53 @@ class DockerIsol8 {
2800
2964
  } else if (this.persistentRuntime?.name !== adapter.name) {
2801
2965
  throw new Error(`Cannot switch runtime from "${this.persistentRuntime?.name}" to "${adapter.name}". Each persistent container supports a single runtime. Create a new Isol8 instance for a different runtime.`);
2802
2966
  }
2803
- const ext = req.fileExtension ?? adapter.getFileExtension();
2804
- const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
2805
- await this.volumeManager.putFile(this.container, filePath, req.code);
2967
+ let rawCmd;
2968
+ if (req.cmd) {
2969
+ rawCmd = ["bash", "-c", req.cmd];
2970
+ } else {
2971
+ const ext = req.fileExtension ?? adapter.getFileExtension();
2972
+ const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
2973
+ await this.volumeManager.putFile(this.container, filePath, req.code);
2974
+ rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2975
+ }
2806
2976
  if (req.files) {
2807
2977
  for (const [fPath, fContent] of Object.entries(req.files)) {
2808
2978
  await this.volumeManager.putFile(this.container, fPath, fContent);
2809
2979
  }
2810
2980
  }
2811
- const rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2812
2981
  const timeoutSec = Math.ceil(timeoutMs / 1000);
2813
2982
  if (remainingPackages.length > 0) {
2814
2983
  await this.executionManager.installPackages(this.container, req.runtime, remainingPackages, timeoutMs);
2815
2984
  }
2816
2985
  if (imageSetupScript) {
2817
- await this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager);
2986
+ let stderr2 = "";
2987
+ let exitCode = 0;
2988
+ for await (const event of this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager)) {
2989
+ if (event.type === "stderr") {
2990
+ stderr2 += event.data;
2991
+ }
2992
+ if (event.type === "exit") {
2993
+ exitCode = Number(event.data);
2994
+ }
2995
+ }
2996
+ if (exitCode !== 0) {
2997
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
2998
+ }
2818
2999
  }
2819
3000
  if (req.setupScript) {
2820
- await this.executionManager.runSetupScript(this.container, req.setupScript, timeoutMs, this.volumeManager);
3001
+ let stderr2 = "";
3002
+ let exitCode = 0;
3003
+ for await (const event of this.executionManager.runSetupScript(this.container, req.setupScript, timeoutMs, this.volumeManager)) {
3004
+ if (event.type === "stderr") {
3005
+ stderr2 += event.data;
3006
+ }
3007
+ if (event.type === "exit") {
3008
+ exitCode = Number(event.data);
3009
+ }
3010
+ }
3011
+ if (exitCode !== 0) {
3012
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
3013
+ }
2821
3014
  }
2822
3015
  let cmd;
2823
3016
  if (req.stdin) {
@@ -2920,22 +3113,25 @@ class DockerIsol8 {
2920
3113
  if (req.runtime !== "agent") {
2921
3114
  return;
2922
3115
  }
2923
- if (this.network !== "filtered") {
2924
- throw new Error(`Agent runtime requires network mode "filtered". The AI coding agent needs network access to reach its LLM provider API. Use --net filtered --allow "api.anthropic.com" (or your provider's domain).`);
3116
+ if (this.network === "none") {
3117
+ throw new Error(`Agent runtime requires network access. The AI coding agent needs to reach its LLM provider API. Use --net host, or --net filtered --allow "api.anthropic.com" (or your provider's domain).`);
2925
3118
  }
2926
- const whitelist = this.networkFilter?.whitelist ?? [];
2927
- if (whitelist.length === 0) {
2928
- throw new Error(`Agent runtime requires at least one network whitelist entry. The AI coding agent needs to reach its LLM provider API. Use --allow "api.anthropic.com" (or your provider's domain).`);
3119
+ if (this.network === "filtered") {
3120
+ const whitelist = this.networkFilter?.whitelist ?? [];
3121
+ if (whitelist.length === 0) {
3122
+ throw new Error(`Agent runtime requires at least one network whitelist entry. The AI coding agent needs to reach its LLM provider API. Use --allow "api.anthropic.com" (or your provider's domain).`);
3123
+ }
2929
3124
  }
2930
3125
  }
2931
3126
  buildAdapterCommand(adapter, req, filePath) {
3127
+ const code = req.code ?? "";
2932
3128
  if (adapter.getCommandWithOptions) {
2933
- return adapter.getCommandWithOptions(req.code, {
3129
+ return adapter.getCommandWithOptions(code, {
2934
3130
  filePath,
2935
3131
  agentFlags: req.agentFlags
2936
3132
  });
2937
3133
  }
2938
- return adapter.getCommand(req.code, filePath);
3134
+ return adapter.getCommand(code, filePath);
2939
3135
  }
2940
3136
  buildHostConfig() {
2941
3137
  const config = {
@@ -3045,7 +3241,7 @@ init_logger();
3045
3241
  // package.json
3046
3242
  var package_default = {
3047
3243
  name: "@isol8/core",
3048
- version: "0.19.0",
3244
+ version: "0.20.0",
3049
3245
  description: "Sandboxed code execution engine for AI agents and apps (Docker, runtime and network controls)",
3050
3246
  author: "Illusion47586",
3051
3247
  license: "MIT",
@@ -3130,4 +3326,4 @@ export {
3130
3326
  AgentAdapter
3131
3327
  };
3132
3328
 
3133
- //# debugId=944C4599E61DE68D64756E2164756E21
3329
+ //# debugId=767B2279906CCA3764756E2164756E21