agent.libx.js 0.94.11 → 0.94.13
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 +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +88 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.js +76 -3
- package/dist/index.js.map +1 -1
- package/dist/tools.shell.d.ts +26 -0
- package/dist/tools.shell.js +65 -2
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ agentx -c "keep going" # continue the most recent session
|
|
|
103
103
|
agentx --resume <id> "…" # resume a specific session
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
- **Filesystem + Shell** — by default the CLI has **full real-filesystem access like Claude Code** (root `/` is the machine root, the launch dir is the working dir, absolute host paths and above-cwd reach both work) with a **real `/bin/sh`** (`Shell` tool) so the agent can run git, bun, node, curl, and any installed binary. Secrets (`.env`, `.ssh`, keys, `.git`) stay hidden by the jail; env secrets are scrubbed from the child shell. `--sandbox` instead operates over an in-memory copy of the working dir with a VFS-only `bash` — the real disk is never touched. `--boddb <dir>` runs over a **persistent database workspace** (a bod-db store at `<dir>` — `meta.db` tree + `files/` bytes) that survives across runs while the real disk stays untouched; DB-native by default, or add `--seed` to hydrate it from cwd on the first run. `--no-shell` forces the VFS bash in disk mode. (`/sandbox` shows the active mode.)
|
|
106
|
+
- **Filesystem + Shell** — by default the CLI has **full real-filesystem access like Claude Code** (root `/` is the machine root, the launch dir is the working dir, absolute host paths and above-cwd reach both work) with a **real `/bin/sh`** (`Shell` tool) so the agent can run git, bun, node, curl, and any installed binary. Secrets (`.env`, `.ssh`, keys, `.git`) stay hidden by the jail; env secrets are scrubbed from the child shell. `--sandbox` instead operates over an in-memory copy of the working dir with a VFS-only `bash` — the real disk is never touched. `--boddb <dir>` runs over a **persistent database workspace** (a bod-db store at `<dir>` — `meta.db` tree + `files/` bytes) that survives across runs while the real disk stays untouched; DB-native by default, or add `--seed` to hydrate it from cwd on the first run. `--no-shell` forces the VFS bash in disk mode. `--harden` OS-sandboxes the real shell (macOS `sandbox-exec` / Linux `bwrap`): writes confined to cwd+tmp, outbound network blocked (`--harden-net` keeps network); commands fail closed when no wrapper exists. (`/sandbox` shows the active mode.)
|
|
107
107
|
- **Sessions** — every conversation persists to `./.agent/sessions/<id>.json`; `--continue`/`--resume` (and `/sessions`, `/resume`) pick it back up, *with memory across turns* — a REPL turn sees the previous one. A global symlink index at `~/.agent/sessions/` enables cross-project lookup: `--resume 090715-myproject` resolves from any directory, and `/sessions all` lists every project's sessions in one picker.
|
|
108
108
|
- **Diffs** — every `Edit`/`Write`/`MultiEdit` renders a colorized `+/-` diff (TTY-gated; plain when piped).
|
|
109
109
|
- **Slash commands** — `/help /tools /model /compact /memory /clear /sessions /resume /commands /init`; `/compact <focus>` preserves matching lines from the folded span; `/memory` opens the memory index in `$EDITOR`; user-defined `./.agent/commands/<name>.md` are invokable directly as `/<name>` (the same registry the model's `SlashCommand` tool uses).
|
package/dist/cli.d.ts
CHANGED
|
@@ -90,6 +90,8 @@ interface Args {
|
|
|
90
90
|
print?: boolean;
|
|
91
91
|
debug?: boolean;
|
|
92
92
|
scratch?: boolean;
|
|
93
|
+
harden?: boolean;
|
|
94
|
+
hardenNet?: boolean;
|
|
93
95
|
}
|
|
94
96
|
declare function parseArgs(argv: string[]): Args;
|
|
95
97
|
/** Synthetic task-event user messages ("[task t2 completed] <multi-KB worker dump>") aren't real user
|
package/dist/cli.js
CHANGED
|
@@ -1388,7 +1388,67 @@ var init_JailedFilesystem = __esm({
|
|
|
1388
1388
|
}
|
|
1389
1389
|
});
|
|
1390
1390
|
|
|
1391
|
+
// src/shell.sandbox.ts
|
|
1392
|
+
function writable(cwd, o, tmpDir) {
|
|
1393
|
+
const set = /* @__PURE__ */ new Set([cwd, "/tmp", "/private/tmp", "/private/var/folders", "/dev", ...tmpDir ? [tmpDir] : [], ...o.writePaths]);
|
|
1394
|
+
return [...set];
|
|
1395
|
+
}
|
|
1396
|
+
function seatbeltProfile(cwd, o, tmpDir) {
|
|
1397
|
+
const allows = writable(cwd, o, tmpDir).map((p) => `(subpath ${sbQuote(p)})`).join(" ");
|
|
1398
|
+
return [
|
|
1399
|
+
"(version 1)",
|
|
1400
|
+
"(allow default)",
|
|
1401
|
+
...o.network ? [] : ["(deny network*)"],
|
|
1402
|
+
"(deny file-write*)",
|
|
1403
|
+
`(allow file-write* ${allows})`
|
|
1404
|
+
].join("\n");
|
|
1405
|
+
}
|
|
1406
|
+
function sandboxArgv(command, cwd, opts = {}, platform2 = process.platform, tmpDir) {
|
|
1407
|
+
const o = { ...new OsSandboxOptions(), ...opts };
|
|
1408
|
+
if (platform2 === "darwin") {
|
|
1409
|
+
return { bin: "/usr/bin/sandbox-exec", args: ["-p", seatbeltProfile(cwd, o, tmpDir), "/bin/sh", "-c", command] };
|
|
1410
|
+
}
|
|
1411
|
+
if (platform2 === "linux") {
|
|
1412
|
+
const binds = writable(cwd, o, tmpDir).filter((p) => p !== "/dev" && !p.startsWith("/private")).flatMap((p) => ["--bind-try", p, p]);
|
|
1413
|
+
return {
|
|
1414
|
+
bin: "bwrap",
|
|
1415
|
+
args: ["--ro-bind", "/", "/", ...binds, "--dev", "/dev", "--proc", "/proc", "--die-with-parent", ...o.network ? [] : ["--unshare-net"], "/bin/sh", "-c", command]
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
async function findSandboxWrapper(platform2 = process.platform) {
|
|
1421
|
+
const { existsSync: existsSync9 } = await import("fs");
|
|
1422
|
+
if (platform2 === "darwin") return existsSync9("/usr/bin/sandbox-exec") ? "/usr/bin/sandbox-exec" : null;
|
|
1423
|
+
if (platform2 === "linux") {
|
|
1424
|
+
for (const dir of (process.env.PATH ?? "/usr/bin:/bin").split(":")) if (dir && existsSync9(`${dir}/bwrap`)) return `${dir}/bwrap`;
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
return null;
|
|
1428
|
+
}
|
|
1429
|
+
var OsSandboxOptions, sbQuote;
|
|
1430
|
+
var init_shell_sandbox = __esm({
|
|
1431
|
+
"src/shell.sandbox.ts"() {
|
|
1432
|
+
"use strict";
|
|
1433
|
+
OsSandboxOptions = class {
|
|
1434
|
+
/** Allow outbound network. Default OFF (Tier-1: no network unless granted). */
|
|
1435
|
+
network = false;
|
|
1436
|
+
/** Extra absolute paths writable beyond cwd + tmp (e.g. a build cache). */
|
|
1437
|
+
writePaths = [];
|
|
1438
|
+
};
|
|
1439
|
+
sbQuote = (p) => `"${p.replace(/(["\\])/g, "\\$1")}"`;
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1391
1443
|
// src/tools.shell.ts
|
|
1444
|
+
async function spawnArgvFor(command, cwd, osSandbox) {
|
|
1445
|
+
if (!osSandbox) return { bin: "/bin/sh", args: ["-c", command] };
|
|
1446
|
+
const opts = osSandbox === true ? {} : osSandbox;
|
|
1447
|
+
const wrapper = await findSandboxWrapper();
|
|
1448
|
+
const wrapped = wrapper ? sandboxArgv(command, cwd, opts, process.platform, process.env.TMPDIR) : null;
|
|
1449
|
+
if (!wrapped) throw new Error(`OS sandbox requested but no wrapper available on ${process.platform} (need sandbox-exec or bwrap)`);
|
|
1450
|
+
return wrapped;
|
|
1451
|
+
}
|
|
1392
1452
|
function childEnv(opts) {
|
|
1393
1453
|
const base = {};
|
|
1394
1454
|
const redact = opts.redactEnv !== false;
|
|
@@ -1421,6 +1481,14 @@ function makeRealShellTool(options) {
|
|
|
1421
1481
|
return `Started background job ${id}. Poll output with ShellOutput({id:"${id}"}), check ShellStatus({id:"${id}"}), stop with ShellKill({id:"${id}"}).`;
|
|
1422
1482
|
}
|
|
1423
1483
|
const spawn3 = options.spawn ?? await nodeSpawn();
|
|
1484
|
+
let argv = { bin: "/bin/sh", args: ["-c", cmd] };
|
|
1485
|
+
if (options.osSandbox) {
|
|
1486
|
+
try {
|
|
1487
|
+
argv = await spawnArgvFor(cmd, options.cwd, options.osSandbox);
|
|
1488
|
+
} catch (e) {
|
|
1489
|
+
return `[exit 1] ${e?.message ?? e}`;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1424
1492
|
const ctl = new AbortController();
|
|
1425
1493
|
const onAbort = () => ctl.abort();
|
|
1426
1494
|
if (ctx.signal) {
|
|
@@ -1455,7 +1523,7 @@ function makeRealShellTool(options) {
|
|
|
1455
1523
|
};
|
|
1456
1524
|
let proc;
|
|
1457
1525
|
try {
|
|
1458
|
-
proc = spawn3(
|
|
1526
|
+
proc = spawn3(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal });
|
|
1459
1527
|
} catch (e) {
|
|
1460
1528
|
return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
|
|
1461
1529
|
}
|
|
@@ -1541,6 +1609,7 @@ var init_tools_shell = __esm({
|
|
|
1541
1609
|
init_tools();
|
|
1542
1610
|
init_redact();
|
|
1543
1611
|
init_logging();
|
|
1612
|
+
init_shell_sandbox();
|
|
1544
1613
|
log12 = forComponent("shell");
|
|
1545
1614
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
1546
1615
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
@@ -1562,7 +1631,8 @@ var init_tools_shell = __esm({
|
|
|
1562
1631
|
};
|
|
1563
1632
|
try {
|
|
1564
1633
|
const spawn3 = this.cfg.spawn ?? await nodeSpawn();
|
|
1565
|
-
const
|
|
1634
|
+
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : { bin: "/bin/sh", args: ["-c", command] };
|
|
1635
|
+
const proc = spawn3(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg) });
|
|
1566
1636
|
job.proc = proc;
|
|
1567
1637
|
proc.stdout?.on("data", append);
|
|
1568
1638
|
proc.stderr?.on("data", append);
|
|
@@ -2996,7 +3066,10 @@ var Agent = class _Agent {
|
|
|
2996
3066
|
}
|
|
2997
3067
|
break;
|
|
2998
3068
|
} catch (err2) {
|
|
2999
|
-
const
|
|
3069
|
+
const sc = err2?.statusCode;
|
|
3070
|
+
const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err2?.message ?? ""));
|
|
3071
|
+
const network = !sc && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|socket hang up|fetch failed|network|terminated|UND_ERR/i.test(String(err2?.message ?? err2?.code ?? err2));
|
|
3072
|
+
const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide);
|
|
3000
3073
|
if (!transient) throw err2;
|
|
3001
3074
|
const waitMs = 1e3 * (attempt + 1);
|
|
3002
3075
|
log3.warn(`network drop mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
@@ -6079,8 +6152,8 @@ Reference files in them by their mount path (the left side).`;
|
|
|
6079
6152
|
let realShell = [];
|
|
6080
6153
|
const useRealShell = o.realShell ?? !virtual;
|
|
6081
6154
|
if (useRealShell && !virtual) {
|
|
6082
|
-
const jobs = new ShellJobRegistry({ cwd, killOnExit: true });
|
|
6083
|
-
realShell = [makeRealShellTool({ cwd, registry: jobs }), ...makeShellJobTools(jobs)];
|
|
6155
|
+
const jobs = new ShellJobRegistry({ cwd, killOnExit: true, osSandbox: o.osSandbox });
|
|
6156
|
+
realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox }), ...makeShellJobTools(jobs)];
|
|
6084
6157
|
}
|
|
6085
6158
|
const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${tmpdir()}/agentx-scratch-${process.pid}`) : void 0;
|
|
6086
6159
|
const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
|
|
@@ -8589,7 +8662,11 @@ function parseArgs(argv) {
|
|
|
8589
8662
|
else if (x === "--seed") a.seed = true;
|
|
8590
8663
|
else if (x === "--shell") a.shell = true;
|
|
8591
8664
|
else if (x === "--no-shell") a.shell = false;
|
|
8592
|
-
else if (x === "--
|
|
8665
|
+
else if (x === "--harden") a.harden = true;
|
|
8666
|
+
else if (x === "--harden-net") {
|
|
8667
|
+
a.harden = true;
|
|
8668
|
+
a.hardenNet = true;
|
|
8669
|
+
} else if (x === "--subagents") a.subagents = true;
|
|
8593
8670
|
else if (x === "--duplex") a.duplex = true;
|
|
8594
8671
|
else if (x === "--conversational" || x === "--convo" || x === "--voice") {
|
|
8595
8672
|
a.voice = true;
|
|
@@ -8644,6 +8721,8 @@ Flags:
|
|
|
8644
8721
|
surviving across runs \u2014 real disk is NEVER modified (DB-native; add --seed below)
|
|
8645
8722
|
--seed with --boddb: hydrate the store from cwd on the first run (empty DB) only
|
|
8646
8723
|
--shell force real /bin/sh on (default in disk mode); --no-shell to use VFS bash only
|
|
8724
|
+
--harden OS-sandbox the real shell (sandbox-exec/bwrap): writes confined to cwd+tmp,
|
|
8725
|
+
outbound network blocked; --harden-net keeps network allowed
|
|
8647
8726
|
--plan plan mode: edits blocked until you approve a plan
|
|
8648
8727
|
--ask confirm each mutating tool (bash/Shell/Write/Edit/\u2026)
|
|
8649
8728
|
--yes, -y auto-approve mutating tools (no prompts) \u2014 for trusted/unattended runs
|
|
@@ -9164,6 +9243,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
9164
9243
|
seed: args.seed,
|
|
9165
9244
|
realShell: args.shell,
|
|
9166
9245
|
// undefined → core.ts defaults (on for disk, off for sandbox/boddb)
|
|
9246
|
+
osSandbox: args.harden ? { network: !!args.hardenNet } : void 0,
|
|
9167
9247
|
scratch: args.scratch,
|
|
9168
9248
|
appendSystemPrompt: args.appendSystemPrompt,
|
|
9169
9249
|
addDirs: args.addDirs,
|
|
@@ -9199,6 +9279,8 @@ async function makeAgent(args, ai, cfg, extraTools = []) {
|
|
|
9199
9279
|
else if (args.boddb) err(dim(` \u229D boddb: files live in a database at ${args.boddb}${args.seed ? " (seeded from cwd on first run)" : ""} \u2014 the real disk will not be modified
|
|
9200
9280
|
`));
|
|
9201
9281
|
else if (args.shell === false) err(dim(" \u2296 --no-shell: VFS bash only (no real /bin/sh)\n"));
|
|
9282
|
+
if (args.harden && !virtual) err(dim(` \u26E8 hardened shell: writes confined to cwd+tmp${args.hardenNet ? "" : ", network blocked"} (sandbox-exec/bwrap)
|
|
9283
|
+
`));
|
|
9202
9284
|
const agent = await buildAgent(optsFor(args, ai, cfg, extraTools));
|
|
9203
9285
|
const display = displayHooks(agent.options.fs);
|
|
9204
9286
|
agent.options.hooks = cfg.hooks ? composeHooks(display, hooksFromConfig(cfg.hooks)) : display;
|