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 +44 -0
- package/dist/agent.d.cts +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/{container-BsPKqY9R.d.cts → container-CZn9USBQ.d.cts} +22 -0
- package/dist/{container-BsPKqY9R.d.ts → container-CZn9USBQ.d.ts} +22 -0
- package/dist/index.cjs +74 -10
- 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 +74 -11
- 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
|
/**
|
|
@@ -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
|
|
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
|
-
...
|
|
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 (
|
|
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
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
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
|
|
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;
|