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 +44 -0
- package/dist/agent.d.cts +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/{container-BsPKqY9R.d.cts → container-Bg_BuaAv.d.cts} +7 -0
- package/dist/{container-BsPKqY9R.d.ts → container-Bg_BuaAv.d.ts} +7 -0
- package/dist/index.cjs +40 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -3
- package/dist/index.d.ts +36 -3
- package/dist/index.js +40 -10
- package/dist/index.js.map +1 -1
- package/dist/worker-entry.js +7 -1
- package/dist/worker-entry.js.map +1 -1
- package/package.json +1 -1
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
package/dist/agent.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
...
|
|
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 (
|
|
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
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
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
|
|
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;
|