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 +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 +48 -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 +48 -10
- package/dist/index.js.map +1 -1
- package/dist/worker-entry.js +15 -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;
|
|
@@ -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
|
|
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;
|