agent.libx.js 0.94.19 → 0.94.21
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.js +64 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +28 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ agentx --resume <id> "…" # resume a specific session
|
|
|
106
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
|
-
- **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).
|
|
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). Skills/commands created **mid-session** are picked up automatically each turn (delivered as a cache-friendly `<system-reminder>` delta, like Claude Code) and the `Skill`/`SlashCommand` tools rescan on a name miss; `/reload` forces a full catalog + system-prompt rebuild.
|
|
110
110
|
- **Live chrome** — the thinking spinner shows elapsed seconds + `esc to interrupt`; the terminal tab title tracks the session topic; a bell rings when a long (>10s) turn finishes in a backgrounded tab; the footer warns at 80%/90% context pressure and auto-trims announce themselves.
|
|
111
111
|
- **`/transcript [n]`** — the full session transcript including complete tool-result bodies (the past-turn equivalent of Ctrl-O live verbose), paged through `less`; **`/doctor`** — one-shot environment sanity check (keys, model pricing, config, session-store writability, memory, MCP mounts).
|
|
112
112
|
- **Syntax-highlighted code fences** — ```` ```ts ```` (and js/py/sh/go/rust/…) blocks render with keywords bold, strings green, numbers cyan, comments dim; unknown languages keep the plain cyan body. **TodoWrite plans** pin a compact `☑ 2/5 · current step` line into the idle footer.
|
package/dist/cli.js
CHANGED
|
@@ -1890,7 +1890,8 @@ var askUserQuestionTool = {
|
|
|
1890
1890
|
const fallback = args.options?.[0]?.label ?? "";
|
|
1891
1891
|
return `No interactive user is available \u2014 proceed using your best judgment.${fallback ? ` (Closest default: "${fallback}")` : ""}`;
|
|
1892
1892
|
}
|
|
1893
|
-
|
|
1893
|
+
const answer = ctx.host.ask(args);
|
|
1894
|
+
return ctx.parkHuman ? ctx.parkHuman(answer) : answer;
|
|
1894
1895
|
}
|
|
1895
1896
|
};
|
|
1896
1897
|
|
|
@@ -1984,7 +1985,7 @@ function parseFrontmatter(md) {
|
|
|
1984
1985
|
const b = block || md.slice(0, 600);
|
|
1985
1986
|
return { name: grabField(b, "name"), description: grabField(b, "description") };
|
|
1986
1987
|
}
|
|
1987
|
-
async function
|
|
1988
|
+
async function scanSkills(fs, dir) {
|
|
1988
1989
|
const skills = [];
|
|
1989
1990
|
const seen = /* @__PURE__ */ new Set();
|
|
1990
1991
|
for (const d of Array.isArray(dir) ? dir : [dir]) {
|
|
@@ -1999,6 +2000,10 @@ async function loadSkills(fs, dir, opts = {}) {
|
|
|
1999
2000
|
}
|
|
2000
2001
|
}
|
|
2001
2002
|
}
|
|
2003
|
+
return skills;
|
|
2004
|
+
}
|
|
2005
|
+
async function loadSkills(fs, dir, opts = {}) {
|
|
2006
|
+
const skills = await scanSkills(fs, dir);
|
|
2002
2007
|
if (skills.length === 0) return { skills, catalog: "" };
|
|
2003
2008
|
const { kept, rest } = topByRelevance(skills, opts.relevanceHint ?? "", (s) => `${s.name} ${s.description}`, opts.max ?? MAX_CATALOG);
|
|
2004
2009
|
const catalog = "## Skills (load one before acting on a matching task)\n" + kept.map((s) => `- **${s.name}** \u2014 ${s.description} (\`${s.path}\`)`).join("\n") + (rest.length ? `
|
|
@@ -2008,8 +2013,13 @@ async function loadSkills(fs, dir, opts = {}) {
|
|
|
2008
2013
|
description: "Load a skill by name \u2014 returns its full instructions (SKILL.md). Use when a task matches a listed skill.",
|
|
2009
2014
|
parameters: { type: "object", required: ["name"], properties: { name: { type: "string" } } },
|
|
2010
2015
|
async run({ name }, ctx) {
|
|
2011
|
-
|
|
2012
|
-
if (!s)
|
|
2016
|
+
let s = skills.find((x) => x.name === name);
|
|
2017
|
+
if (!s) {
|
|
2018
|
+
const fresh = await scanSkills(ctx.fs, dir);
|
|
2019
|
+
s = fresh.find((x) => x.name === name);
|
|
2020
|
+
if (!s) return `Error: no skill named '${name}'. Available: ${fresh.map((x) => x.name).join(", ")}`;
|
|
2021
|
+
skills.push(s);
|
|
2022
|
+
}
|
|
2013
2023
|
return ctx.fs.readFile(s.path);
|
|
2014
2024
|
}
|
|
2015
2025
|
};
|
|
@@ -2050,7 +2060,7 @@ async function expandCommand(fs, cmd, args) {
|
|
|
2050
2060
|
const { body } = parseFrontmatter2(await fs.readFile(cmd.path));
|
|
2051
2061
|
return embedFiles(expandTemplate(body, args), fs);
|
|
2052
2062
|
}
|
|
2053
|
-
async function
|
|
2063
|
+
async function scanCommands(fs, dir) {
|
|
2054
2064
|
const commands = [];
|
|
2055
2065
|
const seen = /* @__PURE__ */ new Set();
|
|
2056
2066
|
for (const d of Array.isArray(dir) ? dir : [dir]) {
|
|
@@ -2065,6 +2075,10 @@ async function loadCommands(fs, dir, opts = {}) {
|
|
|
2065
2075
|
commands.push({ name, description: fm.description || "", path });
|
|
2066
2076
|
}
|
|
2067
2077
|
}
|
|
2078
|
+
return commands;
|
|
2079
|
+
}
|
|
2080
|
+
async function loadCommands(fs, dir, opts = {}) {
|
|
2081
|
+
const commands = await scanCommands(fs, dir);
|
|
2068
2082
|
if (commands.length === 0) return { commands, catalog: "" };
|
|
2069
2083
|
const { kept, rest } = topByRelevance(commands, opts.relevanceHint ?? "", (c) => `${c.name} ${c.description}`, opts.max ?? MAX_CATALOG2);
|
|
2070
2084
|
const catalog = "## Slash commands (reusable prompt templates)\n" + kept.map((c) => `- **/${c.name}** \u2014 ${c.description} (\`${c.path}\`)`).join("\n") + (rest.length ? `
|
|
@@ -2079,8 +2093,13 @@ async function loadCommands(fs, dir, opts = {}) {
|
|
|
2079
2093
|
},
|
|
2080
2094
|
async run({ name, args }, ctx) {
|
|
2081
2095
|
const slug2 = String(name ?? "").replace(/^\//, "");
|
|
2082
|
-
|
|
2083
|
-
if (!c)
|
|
2096
|
+
let c = commands.find((x) => x.name === slug2);
|
|
2097
|
+
if (!c) {
|
|
2098
|
+
const fresh = await scanCommands(ctx.fs, dir);
|
|
2099
|
+
c = fresh.find((x) => x.name === slug2);
|
|
2100
|
+
if (!c) return `Error: no command named '${slug2}'. Available: ${fresh.map((x) => x.name).join(", ")}`;
|
|
2101
|
+
commands.push(c);
|
|
2102
|
+
}
|
|
2084
2103
|
return expandCommand(ctx.fs, c, String(args ?? ""));
|
|
2085
2104
|
}
|
|
2086
2105
|
};
|
|
@@ -10000,6 +10019,25 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10000
10019
|
const adots = (sub) => [adot(sub), `${fsBase}/.claude/${sub}`, `${homedir6()}/.agent/${sub}`, `${homedir6()}/.claude/${sub}`];
|
|
10001
10020
|
const cmds = (await loadCommands(fs, adots("commands"))).commands;
|
|
10002
10021
|
const skills = (await loadSkills(fs, adots("skills"))).skills;
|
|
10022
|
+
const refreshCatalogs = async () => {
|
|
10023
|
+
const [freshSkills, freshCmds] = await Promise.all([scanSkills(fs, adots("skills")), scanCommands(fs, adots("commands"))]);
|
|
10024
|
+
const diff = (kind, old, fresh) => {
|
|
10025
|
+
const had = new Set(old.map((x) => x.name)), has = new Set(fresh.map((x) => x.name));
|
|
10026
|
+
const added = fresh.filter((x) => !had.has(x.name)), removed = old.filter((x) => !has.has(x.name));
|
|
10027
|
+
old.splice(0, old.length, ...fresh);
|
|
10028
|
+
return [
|
|
10029
|
+
...added.map((x) => `+ ${kind} **${x.name}** \u2014 ${x.description}`),
|
|
10030
|
+
...removed.map((x) => `- ${kind} ${x.name} (removed)`)
|
|
10031
|
+
];
|
|
10032
|
+
};
|
|
10033
|
+
const lines = [...diff("skill", skills, freshSkills), ...diff("command", cmds, freshCmds)];
|
|
10034
|
+
if (!lines.length) return "";
|
|
10035
|
+
return `<system-reminder>
|
|
10036
|
+
The skill/command catalog changed on disk since the session started:
|
|
10037
|
+
${lines.join("\n")}
|
|
10038
|
+
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
10039
|
+
</system-reminder>`;
|
|
10040
|
+
};
|
|
10003
10041
|
const histPath = join9(cwd, ".agent", "history");
|
|
10004
10042
|
const history = existsSync8(histPath) ? readFileSync5(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
10005
10043
|
const remember = (line) => {
|
|
@@ -10104,6 +10142,14 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10104
10142
|
};
|
|
10105
10143
|
const announcedTasks = /* @__PURE__ */ new Set();
|
|
10106
10144
|
const turn = async (task) => {
|
|
10145
|
+
const delta = await refreshCatalogs().catch((e) => {
|
|
10146
|
+
log17.debug("catalog refresh failed", e);
|
|
10147
|
+
return "";
|
|
10148
|
+
});
|
|
10149
|
+
if (delta) {
|
|
10150
|
+
err(dim(" \u27F3 skill/command catalog changed \u2014 delta attached to this turn\n"));
|
|
10151
|
+
task += "\n\n" + delta;
|
|
10152
|
+
}
|
|
10107
10153
|
const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia);
|
|
10108
10154
|
if (voiceIO) {
|
|
10109
10155
|
voiceEchoEnd();
|
|
@@ -10342,6 +10388,17 @@ ${extra}` : body);
|
|
|
10342
10388
|
if (hookCount) ok(`hooks: ${hookCount} configured`);
|
|
10343
10389
|
}
|
|
10344
10390
|
},
|
|
10391
|
+
reload: {
|
|
10392
|
+
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
10393
|
+
run: async () => {
|
|
10394
|
+
await refreshCatalogs().catch((e) => {
|
|
10395
|
+
log17.debug("catalog refresh failed", e);
|
|
10396
|
+
});
|
|
10397
|
+
face.reprepare();
|
|
10398
|
+
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
10399
|
+
`));
|
|
10400
|
+
}
|
|
10401
|
+
},
|
|
10345
10402
|
cwd: {
|
|
10346
10403
|
desc: "print the working directory (to switch, relaunch with -C <dir>)",
|
|
10347
10404
|
run: (a) => {
|