sandboxedjs 0.1.39 → 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;
@@ -24234,13 +24235,19 @@ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv
24234
24235
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
24235
24236
  return { file: shell, args: ["-c", command] };
24236
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
+ };
24237
24243
  const start2 = (file3, args, options) => {
24238
24244
  const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
24239
24245
  const handle = spawnChild({
24240
24246
  command: resolved.file,
24241
24247
  args: resolved.args,
24242
24248
  cwd: options.cwd ?? defaultCwd(),
24243
- env: environmentFor(options)
24249
+ env: environmentFor(options),
24250
+ ...stdinIgnored(options) ? { stdinIgnored: true } : {}
24244
24251
  });
24245
24252
  return new ChildProcess(handle, resolved.file, resolved.args);
24246
24253
  };
@@ -28001,6 +28008,7 @@ var Container = class _Container {
28001
28008
  const stdout = new Pipe();
28002
28009
  const stderr = new Pipe();
28003
28010
  if (opts.tty) {
28011
+ stdin.isTTY = true;
28004
28012
  stdout.isTTY = true;
28005
28013
  stderr.isTTY = true;
28006
28014
  stdout.columns = opts.columns ?? 80;
@@ -28725,6 +28733,20 @@ function serveContainerOn(port, box) {
28725
28733
  };
28726
28734
  port.start?.();
28727
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
+ }
28728
28750
  async function createPreview(box, options = {}) {
28729
28751
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
28730
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)));
@@ -28760,8 +28782,16 @@ async function createPreview(box, options = {}) {
28760
28782
  serveContainerOn(channel.port1, box);
28761
28783
  worker.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
28762
28784
  const base2 = registration.scope.replace(/\/$/, "");
28785
+ const urlFor = (port) => `${base2}/__sbx__/${port}/`;
28763
28786
  return {
28764
- 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
+ },
28765
28795
  dispose: async () => {
28766
28796
  channel.port1.close();
28767
28797
  await registration.unregister();
@@ -28836,6 +28866,7 @@ exports.builtinNames = builtinNames;
28836
28866
  exports.captureStdio = captureStdio;
28837
28867
  exports.configureCPython = configureCPython;
28838
28868
  exports.configurePython = configurePython;
28869
+ exports.containerTarget = containerTarget;
28839
28870
  exports.createChildProcessModule = createChildProcessModule;
28840
28871
  exports.createContainer = createContainer;
28841
28872
  exports.createContext = createContext;