sandboxedjs 0.1.38 → 0.1.40

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-Bg_BuaAv.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-Bg_BuaAv.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
  /**
@@ -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
  /**
package/dist/index.cjs CHANGED
@@ -17313,7 +17313,8 @@ ${stdinPath === null ? "" : `(function () {
17313
17313
  }
17314
17314
  async function execute(ctx, invocation) {
17315
17315
  const { pod } = ctx.kernel;
17316
- const interactive = ctx.stdin.interactive || ctx.stdin.isTTY;
17316
+ const live = ctx.stdin.interactive || ctx.stdin.isTTY;
17317
+ const tty = ctx.stdin.isTTY;
17317
17318
  let exitCode = 0;
17318
17319
  try {
17319
17320
  const proc = await pod.spawn("node", [invocation.script], {
@@ -17325,7 +17326,8 @@ async function execute(ctx, invocation) {
17325
17326
  * program's stdin has to stay open and be fed as it arrives — and be
17326
17327
  * reported as a terminal, since that is what decides whether a CLI
17327
17328
  * prompts or takes its defaults. */
17328
- ...interactive ? { interactiveStdin: true, tty: true } : {}
17329
+ ...live ? { interactiveStdin: true } : {},
17330
+ ...tty ? { tty: true } : {}
17329
17331
  });
17330
17332
  proc.on("output", (chunk) => {
17331
17333
  try {
@@ -17343,7 +17345,7 @@ async function execute(ctx, invocation) {
17343
17345
  ctx.stdin.rawMode = enabled;
17344
17346
  });
17345
17347
  let forwarding = false;
17346
- if (interactive) {
17348
+ if (live) {
17347
17349
  forwarding = true;
17348
17350
  void (async () => {
17349
17351
  while (forwarding) {
@@ -21635,10 +21637,9 @@ var KernelChildProcess = class {
21635
21637
  started = false;
21636
21638
  cancelled = false;
21637
21639
  constructor(kernel, cred, config, pid) {
21638
- if (config.inheritStdio) {
21639
- this.stdin.isTTY = true;
21640
- this.stdin.interactive = true;
21641
- }
21640
+ this.stdin.interactive = true;
21641
+ if (config.inheritStdio) this.stdin.isTTY = true;
21642
+ if (config.stdinIgnored) this.stdin.end();
21642
21643
  this.stdin.onRawMode = (enabled) => this.emit("rawmode", enabled);
21643
21644
  this.kernel = kernel;
21644
21645
  this.cred = cred;
@@ -22293,6 +22294,9 @@ var CommonJsEngine = class {
22293
22294
  return target.exports;
22294
22295
  }
22295
22296
  resolve(specifier, importer, kind = "require") {
22297
+ if (specifier.startsWith("file://")) {
22298
+ specifier = pathFromFileUrl(specifier);
22299
+ }
22296
22300
  const builtin = this.builtin(specifier);
22297
22301
  if (builtin.found) return specifier.replace(/^node:/, "");
22298
22302
  if (specifier.startsWith("#")) {
@@ -22657,6 +22661,11 @@ function ownKeys(target) {
22657
22661
  }
22658
22662
  return keys;
22659
22663
  }
22664
+ function pathFromFileUrl(url) {
22665
+ const withoutScheme = url.slice("file://".length).replace(/^localhost/, "");
22666
+ const path = withoutScheme.split(/[?#]/, 1)[0];
22667
+ return decodeURIComponent(path) || "/";
22668
+ }
22660
22669
  function fileUrl(path) {
22661
22670
  return `file://${path.split("/").map(encodeURIComponent).join("/")}`;
22662
22671
  }
@@ -24226,13 +24235,19 @@ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv
24226
24235
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
24227
24236
  return { file: shell, args: ["-c", command] };
24228
24237
  };
24238
+ const stdinIgnored = (options) => {
24239
+ const stdio = options.stdio;
24240
+ if (stdio === "ignore") return true;
24241
+ return Array.isArray(stdio) && stdio[0] === "ignore";
24242
+ };
24229
24243
  const start2 = (file3, args, options) => {
24230
24244
  const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
24231
24245
  const handle = spawnChild({
24232
24246
  command: resolved.file,
24233
24247
  args: resolved.args,
24234
24248
  cwd: options.cwd ?? defaultCwd(),
24235
- env: environmentFor(options)
24249
+ env: environmentFor(options),
24250
+ ...stdinIgnored(options) ? { stdinIgnored: true } : {}
24236
24251
  });
24237
24252
  return new ChildProcess(handle, resolved.file, resolved.args);
24238
24253
  };
@@ -27993,6 +28008,7 @@ var Container = class _Container {
27993
28008
  const stdout = new Pipe();
27994
28009
  const stderr = new Pipe();
27995
28010
  if (opts.tty) {
28011
+ stdin.isTTY = true;
27996
28012
  stdout.isTTY = true;
27997
28013
  stderr.isTTY = true;
27998
28014
  stdout.columns = opts.columns ?? 80;
@@ -28717,6 +28733,20 @@ function serveContainerOn(port, box) {
28717
28733
  };
28718
28734
  port.start?.();
28719
28735
  }
28736
+ var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?)$/i;
28737
+ function containerTarget(value) {
28738
+ let url;
28739
+ try {
28740
+ url = new URL(value.trim());
28741
+ } catch {
28742
+ return null;
28743
+ }
28744
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
28745
+ if (!LOOPBACK_HOST.test(url.hostname)) return null;
28746
+ const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
28747
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
28748
+ return { port, path: `${url.pathname}${url.search}` };
28749
+ }
28720
28750
  async function createPreview(box, options = {}) {
28721
28751
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
28722
28752
  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)));
@@ -28752,8 +28782,16 @@ async function createPreview(box, options = {}) {
28752
28782
  serveContainerOn(channel.port1, box);
28753
28783
  worker.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
28754
28784
  const base2 = registration.scope.replace(/\/$/, "");
28785
+ const urlFor = (port) => `${base2}/__sbx__/${port}/`;
28755
28786
  return {
28756
- urlFor: (port) => `${base2}/__sbx__/${port}/`,
28787
+ urlFor,
28788
+ resolve: (value) => {
28789
+ const target = containerTarget(value);
28790
+ if (!target) return null;
28791
+ if (!box.net.listening().some((entry) => entry.port === target.port)) return null;
28792
+ const path = target.path.startsWith("/") ? target.path : `/${target.path}`;
28793
+ return `${urlFor(target.port).replace(/\/$/, "")}${path}`;
28794
+ },
28757
28795
  dispose: async () => {
28758
28796
  channel.port1.close();
28759
28797
  await registration.unregister();
@@ -28828,6 +28866,7 @@ exports.builtinNames = builtinNames;
28828
28866
  exports.captureStdio = captureStdio;
28829
28867
  exports.configureCPython = configureCPython;
28830
28868
  exports.configurePython = configurePython;
28869
+ exports.containerTarget = containerTarget;
28831
28870
  exports.createChildProcessModule = createChildProcessModule;
28832
28871
  exports.createContainer = createContainer;
28833
28872
  exports.createContext = createContext;