sandboxedjs 0.1.39 → 0.1.41

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/README.md CHANGED
@@ -37,6 +37,50 @@ npm install sandboxedjs
37
37
 
38
38
  Node 18.17+. Everything is pure JavaScript and WebAssembly — no compilation step.
39
39
 
40
+ ## Deep Agents
41
+
42
+ `SandboxedJsBackend` lets [LangChain Deep Agents](https://github.com/langchain-ai/deepagents)
43
+ use a `sandboxedjs` container as its execution and filesystem sandbox. Install the two packages
44
+ in the host application (plus the LangChain model adapter for your provider):
45
+
46
+ ```bash
47
+ npm install sandboxedjs deepagents
48
+ ```
49
+
50
+ Create a container, pass its backend to `createDeepAgent`, and dispose the container when the run
51
+ is finished:
52
+
53
+ ```ts
54
+ import { createContainer } from "sandboxedjs";
55
+ import { SandboxedJsBackend, installSandboxSkills } from "sandboxedjs/agent";
56
+ import { createDeepAgent } from "deepagents";
57
+
58
+ const box = await createContainer({
59
+ cwd: "/app",
60
+ network: { allowOutbound: true }, // required for npm installs or other downloads
61
+ });
62
+
63
+ await installSandboxSkills(box); // optional: installs the bundled sandbox workflow skills
64
+
65
+ // `model` is any chat model supported by Deep Agents, configured by your application.
66
+ const agent = createDeepAgent({
67
+ model,
68
+ backend: new SandboxedJsBackend(box, { cwd: "/app" }),
69
+ });
70
+
71
+ const result = await agent.invoke({
72
+ messages: [{ role: "user", content: "Create and test a small Node.js service in /app." }],
73
+ });
74
+
75
+ box.dispose();
76
+ ```
77
+
78
+ The adapter mirrors `deepagents@1.13.2`'s `SandboxBackendProtocolV2` and provides shell execution plus
79
+ `ls`, `read`, `readRaw`, `write`, `edit`, `grep`, `glob`, `delete`, file upload, and file download
80
+ operations. Paths passed to filesystem tools must be absolute. `deepagents` is intentionally not a
81
+ runtime dependency of `sandboxedjs`; applications that use this integration install and configure
82
+ it alongside their model provider.
83
+
40
84
  ## What's inside
41
85
 
42
86
  | | |
package/dist/agent.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as Container } from './container-BsPKqY9R.cjs';
1
+ import { C as Container } from './container-CZn9USBQ.cjs';
2
2
 
3
3
  /**
4
4
  * Structural copies of the LangChain Deep Agents backend contract.
package/dist/agent.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as Container } from './container-BsPKqY9R.js';
1
+ import { C as Container } from './container-CZn9USBQ.js';
2
2
 
3
3
  /**
4
4
  * Structural copies of the LangChain Deep Agents backend contract.
@@ -30,6 +30,13 @@ interface ChildSpawnConfig {
30
30
  * types and one that reads to end-of-input and stops.
31
31
  */
32
32
  inheritStdio?: boolean;
33
+ /**
34
+ * The child was told to ignore its input (`stdio: "ignore"`).
35
+ *
36
+ * It then has no input at all, so its stdin is closed at once rather than
37
+ * left open on a parent that will never write to it.
38
+ */
39
+ stdinIgnored?: boolean;
33
40
  }
34
41
  type SpawnChild = (config: ChildSpawnConfig) => ChildHandle;
35
42
  /**
@@ -440,6 +447,21 @@ declare class Pipe implements InputStream, OutputStream {
440
447
  * every character appears twice.
441
448
  */
442
449
  private raw;
450
+ /**
451
+ * Translate carriage returns on the way in, the way `ICRNL` does.
452
+ *
453
+ * A terminal sends CR when Enter is pressed — xterm and every browser
454
+ * terminal emulator do — while everything that reads a line looks for LF.
455
+ * A real tty reconciles the two in its line discipline, which is on by
456
+ * default; without it `read name` waits forever on input the user already
457
+ * typed, and the only visible symptom is a hang.
458
+ *
459
+ * Deliberately opt-in rather than implied by `isTTY`, because stdout is a
460
+ * tty too and a bare CR there is how every progress bar and menu redraw
461
+ * returns to the start of the line. Translating those would corrupt exactly
462
+ * the output this runtime exists to render faithfully.
463
+ */
464
+ icrnl: boolean;
443
465
  onRawMode?: (enabled: boolean) => void;
444
466
  get rawMode(): boolean;
445
467
  set rawMode(enabled: boolean);
@@ -30,6 +30,13 @@ interface ChildSpawnConfig {
30
30
  * types and one that reads to end-of-input and stops.
31
31
  */
32
32
  inheritStdio?: boolean;
33
+ /**
34
+ * The child was told to ignore its input (`stdio: "ignore"`).
35
+ *
36
+ * It then has no input at all, so its stdin is closed at once rather than
37
+ * left open on a parent that will never write to it.
38
+ */
39
+ stdinIgnored?: boolean;
33
40
  }
34
41
  type SpawnChild = (config: ChildSpawnConfig) => ChildHandle;
35
42
  /**
@@ -440,6 +447,21 @@ declare class Pipe implements InputStream, OutputStream {
440
447
  * every character appears twice.
441
448
  */
442
449
  private raw;
450
+ /**
451
+ * Translate carriage returns on the way in, the way `ICRNL` does.
452
+ *
453
+ * A terminal sends CR when Enter is pressed — xterm and every browser
454
+ * terminal emulator do — while everything that reads a line looks for LF.
455
+ * A real tty reconciles the two in its line discipline, which is on by
456
+ * default; without it `read name` waits forever on input the user already
457
+ * typed, and the only visible symptom is a hang.
458
+ *
459
+ * Deliberately opt-in rather than implied by `isTTY`, because stdout is a
460
+ * tty too and a bare CR there is how every progress bar and menu redraw
461
+ * returns to the start of the line. Translating those would corrupt exactly
462
+ * the output this runtime exists to render faithfully.
463
+ */
464
+ icrnl: boolean;
443
465
  onRawMode?: (enabled: boolean) => void;
444
466
  get rawMode(): boolean;
445
467
  set rawMode(enabled: boolean);
package/dist/index.cjs CHANGED
@@ -4768,6 +4768,8 @@ init_path();
4768
4768
  init_errno();
4769
4769
  var encoder2 = new TextEncoder();
4770
4770
  var decoder2 = new TextDecoder();
4771
+ var CR = 13;
4772
+ var LF = 10;
4771
4773
  function toBytes(data) {
4772
4774
  return typeof data === "string" ? encoder2.encode(data) : data;
4773
4775
  }
@@ -4800,6 +4802,21 @@ var Pipe = class _Pipe {
4800
4802
  * every character appears twice.
4801
4803
  */
4802
4804
  raw = false;
4805
+ /**
4806
+ * Translate carriage returns on the way in, the way `ICRNL` does.
4807
+ *
4808
+ * A terminal sends CR when Enter is pressed — xterm and every browser
4809
+ * terminal emulator do — while everything that reads a line looks for LF.
4810
+ * A real tty reconciles the two in its line discipline, which is on by
4811
+ * default; without it `read name` waits forever on input the user already
4812
+ * typed, and the only visible symptom is a hang.
4813
+ *
4814
+ * Deliberately opt-in rather than implied by `isTTY`, because stdout is a
4815
+ * tty too and a bare CR there is how every progress bar and menu redraw
4816
+ * returns to the start of the line. Translating those would corrupt exactly
4817
+ * the output this runtime exists to render faithfully.
4818
+ */
4819
+ icrnl = false;
4803
4820
  onRawMode;
4804
4821
  get rawMode() {
4805
4822
  return this.raw;
@@ -4822,7 +4839,7 @@ var Pipe = class _Pipe {
4822
4839
  write(data) {
4823
4840
  if (this.readerClosed) throw new exports.SysError("EPIPE", "write");
4824
4841
  if (this.writerClosed) return;
4825
- const bytes2 = toBytes(data);
4842
+ const bytes2 = this.icrnl && !this.raw ? translateCarriageReturns(toBytes(data)) : toBytes(data);
4826
4843
  if (bytes2.length === 0) return;
4827
4844
  this.chunks.push(bytes2);
4828
4845
  this.buffered += bytes2.length;
@@ -4915,6 +4932,21 @@ var Pipe = class _Pipe {
4915
4932
  return p;
4916
4933
  }
4917
4934
  };
4935
+ function translateCarriageReturns(bytes2) {
4936
+ if (!bytes2.includes(CR)) return bytes2;
4937
+ const out = new Uint8Array(bytes2.length);
4938
+ let length = 0;
4939
+ for (let index = 0; index < bytes2.length; index += 1) {
4940
+ const byte = bytes2[index];
4941
+ if (byte === CR) {
4942
+ out[length++] = LF;
4943
+ if (bytes2[index + 1] === LF) index += 1;
4944
+ } else {
4945
+ out[length++] = byte;
4946
+ }
4947
+ }
4948
+ return out.subarray(0, length);
4949
+ }
4918
4950
  var NullOutput = class {
4919
4951
  closed = false;
4920
4952
  isTTY = false;
@@ -17313,7 +17345,8 @@ ${stdinPath === null ? "" : `(function () {
17313
17345
  }
17314
17346
  async function execute(ctx, invocation) {
17315
17347
  const { pod } = ctx.kernel;
17316
- const interactive = ctx.stdin.interactive || ctx.stdin.isTTY;
17348
+ const live = ctx.stdin.interactive || ctx.stdin.isTTY;
17349
+ const tty = ctx.stdin.isTTY;
17317
17350
  let exitCode = 0;
17318
17351
  try {
17319
17352
  const proc = await pod.spawn("node", [invocation.script], {
@@ -17325,7 +17358,8 @@ async function execute(ctx, invocation) {
17325
17358
  * program's stdin has to stay open and be fed as it arrives — and be
17326
17359
  * reported as a terminal, since that is what decides whether a CLI
17327
17360
  * prompts or takes its defaults. */
17328
- ...interactive ? { interactiveStdin: true, tty: true } : {}
17361
+ ...live ? { interactiveStdin: true } : {},
17362
+ ...tty ? { tty: true } : {}
17329
17363
  });
17330
17364
  proc.on("output", (chunk) => {
17331
17365
  try {
@@ -17343,7 +17377,7 @@ async function execute(ctx, invocation) {
17343
17377
  ctx.stdin.rawMode = enabled;
17344
17378
  });
17345
17379
  let forwarding = false;
17346
- if (interactive) {
17380
+ if (live) {
17347
17381
  forwarding = true;
17348
17382
  void (async () => {
17349
17383
  while (forwarding) {
@@ -21635,10 +21669,9 @@ var KernelChildProcess = class {
21635
21669
  started = false;
21636
21670
  cancelled = false;
21637
21671
  constructor(kernel, cred, config, pid) {
21638
- if (config.inheritStdio) {
21639
- this.stdin.isTTY = true;
21640
- this.stdin.interactive = true;
21641
- }
21672
+ this.stdin.interactive = true;
21673
+ if (config.inheritStdio) this.stdin.isTTY = true;
21674
+ if (config.stdinIgnored) this.stdin.end();
21642
21675
  this.stdin.onRawMode = (enabled) => this.emit("rawmode", enabled);
21643
21676
  this.kernel = kernel;
21644
21677
  this.cred = cred;
@@ -24234,13 +24267,19 @@ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv
24234
24267
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
24235
24268
  return { file: shell, args: ["-c", command] };
24236
24269
  };
24270
+ const stdinIgnored = (options) => {
24271
+ const stdio = options.stdio;
24272
+ if (stdio === "ignore") return true;
24273
+ return Array.isArray(stdio) && stdio[0] === "ignore";
24274
+ };
24237
24275
  const start2 = (file3, args, options) => {
24238
24276
  const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
24239
24277
  const handle = spawnChild({
24240
24278
  command: resolved.file,
24241
24279
  args: resolved.args,
24242
24280
  cwd: options.cwd ?? defaultCwd(),
24243
- env: environmentFor(options)
24281
+ env: environmentFor(options),
24282
+ ...stdinIgnored(options) ? { stdinIgnored: true } : {}
24244
24283
  });
24245
24284
  return new ChildProcess(handle, resolved.file, resolved.args);
24246
24285
  };
@@ -28001,6 +28040,8 @@ var Container = class _Container {
28001
28040
  const stdout = new Pipe();
28002
28041
  const stderr = new Pipe();
28003
28042
  if (opts.tty) {
28043
+ stdin.isTTY = true;
28044
+ stdin.icrnl = true;
28004
28045
  stdout.isTTY = true;
28005
28046
  stderr.isTTY = true;
28006
28047
  stdout.columns = opts.columns ?? 80;
@@ -28725,6 +28766,20 @@ function serveContainerOn(port, box) {
28725
28766
  };
28726
28767
  port.start?.();
28727
28768
  }
28769
+ var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?)$/i;
28770
+ function containerTarget(value) {
28771
+ let url;
28772
+ try {
28773
+ url = new URL(value.trim());
28774
+ } catch {
28775
+ return null;
28776
+ }
28777
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
28778
+ if (!LOOPBACK_HOST.test(url.hostname)) return null;
28779
+ const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
28780
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
28781
+ return { port, path: `${url.pathname}${url.search}` };
28782
+ }
28728
28783
  async function createPreview(box, options = {}) {
28729
28784
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
28730
28785
  const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js?no-inline", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
@@ -28760,8 +28815,16 @@ async function createPreview(box, options = {}) {
28760
28815
  serveContainerOn(channel.port1, box);
28761
28816
  worker.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
28762
28817
  const base2 = registration.scope.replace(/\/$/, "");
28818
+ const urlFor = (port) => `${base2}/__sbx__/${port}/`;
28763
28819
  return {
28764
- urlFor: (port) => `${base2}/__sbx__/${port}/`,
28820
+ urlFor,
28821
+ resolve: (value) => {
28822
+ const target = containerTarget(value);
28823
+ if (!target) return null;
28824
+ if (!box.net.listening().some((entry) => entry.port === target.port)) return null;
28825
+ const path = target.path.startsWith("/") ? target.path : `/${target.path}`;
28826
+ return `${urlFor(target.port).replace(/\/$/, "")}${path}`;
28827
+ },
28765
28828
  dispose: async () => {
28766
28829
  channel.port1.close();
28767
28830
  await registration.unregister();
@@ -28836,6 +28899,7 @@ exports.builtinNames = builtinNames;
28836
28899
  exports.captureStdio = captureStdio;
28837
28900
  exports.configureCPython = configureCPython;
28838
28901
  exports.configurePython = configurePython;
28902
+ exports.containerTarget = containerTarget;
28839
28903
  exports.createChildProcessModule = createChildProcessModule;
28840
28904
  exports.createContainer = createContainer;
28841
28905
  exports.createContext = createContext;