agent.libx.js 0.94.18 → 0.94.20
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/{Agent-COa80xYy.d.ts → Agent-DmsB5hzp.d.ts} +7 -2
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +98 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.js +50 -12
- package/dist/index.js.map +1 -1
- package/dist/{mcp-C5GuDinb.d.ts → mcp-D00OuccC.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{tools-GPWp7oXq.d.ts → tools-9AUK6SG2.d.ts} +4 -0
- package/dist/tools.shell.d.ts +1 -1
- package/dist/tools.shell.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.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
2
|
-
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, e as MessageContent } from './tools-
|
|
2
|
+
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, e as MessageContent } from './tools-9AUK6SG2.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Hooks — deterministic interception points around tool execution, run by the
|
|
@@ -209,7 +209,8 @@ declare class AgentOptions {
|
|
|
209
209
|
systemPrompt: string;
|
|
210
210
|
tools: AgentTool[];
|
|
211
211
|
maxSteps: number;
|
|
212
|
-
/** Hard ceiling on accumulated tokens (prompt+completion) across the run
|
|
212
|
+
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
213
|
+
* (their real price) so a fat cached context doesn't trip the guard on healthy turns. 0 = unbounded. */
|
|
213
214
|
maxTokens: number;
|
|
214
215
|
/** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
|
|
215
216
|
timeoutMs: number;
|
|
@@ -317,6 +318,10 @@ declare class Agent {
|
|
|
317
318
|
private prepared;
|
|
318
319
|
private systemPromptCache;
|
|
319
320
|
private started;
|
|
321
|
+
private parkedMs;
|
|
322
|
+
/** Time a human-blocking await (a permission/plan prompt) and bank it in `parkedMs` so idle prompt
|
|
323
|
+
* time never trips the wall-clock kill-switch. The agent did no work while parked on the user. */
|
|
324
|
+
private park;
|
|
320
325
|
/** Force the next `send()`/`run()` to rebuild the system prompt, tools, plan-mode and permission hooks
|
|
321
326
|
* from `options` — apply mid-conversation changes to `planMode`/`permissions`/`model` etc. (prepare()
|
|
322
327
|
* is otherwise memoized per conversation). */
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { h as RunResult, R as ReasoningEffort } from './Agent-
|
|
2
|
+
import { h as RunResult, R as ReasoningEffort } from './Agent-DmsB5hzp.js';
|
|
3
3
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
4
|
-
import { M as Message, c as ContentPart } from './tools-
|
|
4
|
+
import { M as Message, c as ContentPart } from './tools-9AUK6SG2.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* On-disk session store for the CLI: each conversation is one JSON file at
|
package/dist/cli.js
CHANGED
|
@@ -1984,7 +1984,7 @@ function parseFrontmatter(md) {
|
|
|
1984
1984
|
const b = block || md.slice(0, 600);
|
|
1985
1985
|
return { name: grabField(b, "name"), description: grabField(b, "description") };
|
|
1986
1986
|
}
|
|
1987
|
-
async function
|
|
1987
|
+
async function scanSkills(fs, dir) {
|
|
1988
1988
|
const skills = [];
|
|
1989
1989
|
const seen = /* @__PURE__ */ new Set();
|
|
1990
1990
|
for (const d of Array.isArray(dir) ? dir : [dir]) {
|
|
@@ -1999,6 +1999,10 @@ async function loadSkills(fs, dir, opts = {}) {
|
|
|
1999
1999
|
}
|
|
2000
2000
|
}
|
|
2001
2001
|
}
|
|
2002
|
+
return skills;
|
|
2003
|
+
}
|
|
2004
|
+
async function loadSkills(fs, dir, opts = {}) {
|
|
2005
|
+
const skills = await scanSkills(fs, dir);
|
|
2002
2006
|
if (skills.length === 0) return { skills, catalog: "" };
|
|
2003
2007
|
const { kept, rest } = topByRelevance(skills, opts.relevanceHint ?? "", (s) => `${s.name} ${s.description}`, opts.max ?? MAX_CATALOG);
|
|
2004
2008
|
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 +2012,13 @@ async function loadSkills(fs, dir, opts = {}) {
|
|
|
2008
2012
|
description: "Load a skill by name \u2014 returns its full instructions (SKILL.md). Use when a task matches a listed skill.",
|
|
2009
2013
|
parameters: { type: "object", required: ["name"], properties: { name: { type: "string" } } },
|
|
2010
2014
|
async run({ name }, ctx) {
|
|
2011
|
-
|
|
2012
|
-
if (!s)
|
|
2015
|
+
let s = skills.find((x) => x.name === name);
|
|
2016
|
+
if (!s) {
|
|
2017
|
+
const fresh = await scanSkills(ctx.fs, dir);
|
|
2018
|
+
s = fresh.find((x) => x.name === name);
|
|
2019
|
+
if (!s) return `Error: no skill named '${name}'. Available: ${fresh.map((x) => x.name).join(", ")}`;
|
|
2020
|
+
skills.push(s);
|
|
2021
|
+
}
|
|
2013
2022
|
return ctx.fs.readFile(s.path);
|
|
2014
2023
|
}
|
|
2015
2024
|
};
|
|
@@ -2050,7 +2059,7 @@ async function expandCommand(fs, cmd, args) {
|
|
|
2050
2059
|
const { body } = parseFrontmatter2(await fs.readFile(cmd.path));
|
|
2051
2060
|
return embedFiles(expandTemplate(body, args), fs);
|
|
2052
2061
|
}
|
|
2053
|
-
async function
|
|
2062
|
+
async function scanCommands(fs, dir) {
|
|
2054
2063
|
const commands = [];
|
|
2055
2064
|
const seen = /* @__PURE__ */ new Set();
|
|
2056
2065
|
for (const d of Array.isArray(dir) ? dir : [dir]) {
|
|
@@ -2065,6 +2074,10 @@ async function loadCommands(fs, dir, opts = {}) {
|
|
|
2065
2074
|
commands.push({ name, description: fm.description || "", path });
|
|
2066
2075
|
}
|
|
2067
2076
|
}
|
|
2077
|
+
return commands;
|
|
2078
|
+
}
|
|
2079
|
+
async function loadCommands(fs, dir, opts = {}) {
|
|
2080
|
+
const commands = await scanCommands(fs, dir);
|
|
2068
2081
|
if (commands.length === 0) return { commands, catalog: "" };
|
|
2069
2082
|
const { kept, rest } = topByRelevance(commands, opts.relevanceHint ?? "", (c) => `${c.name} ${c.description}`, opts.max ?? MAX_CATALOG2);
|
|
2070
2083
|
const catalog = "## Slash commands (reusable prompt templates)\n" + kept.map((c) => `- **/${c.name}** \u2014 ${c.description} (\`${c.path}\`)`).join("\n") + (rest.length ? `
|
|
@@ -2079,8 +2092,13 @@ async function loadCommands(fs, dir, opts = {}) {
|
|
|
2079
2092
|
},
|
|
2080
2093
|
async run({ name, args }, ctx) {
|
|
2081
2094
|
const slug2 = String(name ?? "").replace(/^\//, "");
|
|
2082
|
-
|
|
2083
|
-
if (!c)
|
|
2095
|
+
let c = commands.find((x) => x.name === slug2);
|
|
2096
|
+
if (!c) {
|
|
2097
|
+
const fresh = await scanCommands(ctx.fs, dir);
|
|
2098
|
+
c = fresh.find((x) => x.name === slug2);
|
|
2099
|
+
if (!c) return `Error: no command named '${slug2}'. Available: ${fresh.map((x) => x.name).join(", ")}`;
|
|
2100
|
+
commands.push(c);
|
|
2101
|
+
}
|
|
2084
2102
|
return expandCommand(ctx.fs, c, String(args ?? ""));
|
|
2085
2103
|
}
|
|
2086
2104
|
};
|
|
@@ -2590,9 +2608,10 @@ function planMode(opts) {
|
|
|
2590
2608
|
parameters: { type: "object", required: ["plan"], properties: { plan: { type: "string", description: "the concrete steps you will take" } } },
|
|
2591
2609
|
async run({ plan }, _ctx) {
|
|
2592
2610
|
if (opts?.host?.confirm) {
|
|
2593
|
-
const
|
|
2611
|
+
const confirm = opts.host.confirm(`Approve this plan?
|
|
2594
2612
|
|
|
2595
2613
|
${String(plan ?? "")}`);
|
|
2614
|
+
const ok = await (_ctx?.parkHuman ? _ctx.parkHuman(confirm) : confirm);
|
|
2596
2615
|
if (!ok) return "Plan not approved. Revise it and call ExitPlanMode again.";
|
|
2597
2616
|
}
|
|
2598
2617
|
approved = true;
|
|
@@ -2748,7 +2767,8 @@ var AgentOptions = class {
|
|
|
2748
2767
|
tools = defaultTools();
|
|
2749
2768
|
maxSteps = 25;
|
|
2750
2769
|
// --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
|
|
2751
|
-
/** Hard ceiling on accumulated tokens (prompt+completion) across the run
|
|
2770
|
+
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
2771
|
+
* (their real price) so a fat cached context doesn't trip the guard on healthy turns. 0 = unbounded. */
|
|
2752
2772
|
maxTokens = 2e5;
|
|
2753
2773
|
/** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
|
|
2754
2774
|
timeoutMs = 12e4;
|
|
@@ -2850,6 +2870,18 @@ var Agent = class _Agent {
|
|
|
2850
2870
|
// the assembled system prompt from the last prepare()
|
|
2851
2871
|
started = false;
|
|
2852
2872
|
// session-start lifecycle hook fires once per conversation
|
|
2873
|
+
parkedMs = 0;
|
|
2874
|
+
// cumulative time blocked on the HUMAN (permission/plan prompts) — excluded from the timeout
|
|
2875
|
+
/** Time a human-blocking await (a permission/plan prompt) and bank it in `parkedMs` so idle prompt
|
|
2876
|
+
* time never trips the wall-clock kill-switch. The agent did no work while parked on the user. */
|
|
2877
|
+
async park(p) {
|
|
2878
|
+
const t = Date.now();
|
|
2879
|
+
try {
|
|
2880
|
+
return await p;
|
|
2881
|
+
} finally {
|
|
2882
|
+
this.parkedMs += Date.now() - t;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2853
2885
|
/** Force the next `send()`/`run()` to rebuild the system prompt, tools, plan-mode and permission hooks
|
|
2854
2886
|
* from `options` — apply mid-conversation changes to `planMode`/`permissions`/`model` etc. (prepare()
|
|
2855
2887
|
* is otherwise memoized per conversation). */
|
|
@@ -2878,6 +2910,7 @@ var Agent = class _Agent {
|
|
|
2878
2910
|
if (this.options.lintOnWrite) this.ctx.lint = checkSyntax;
|
|
2879
2911
|
this.ctx.ai = this.options.ai;
|
|
2880
2912
|
this.ctx.model = this.options.model;
|
|
2913
|
+
this.ctx.parkHuman = (p) => this.park(p);
|
|
2881
2914
|
if (this.options.backgroundJobs) this.ctx.jobs = new SandboxJobRegistry();
|
|
2882
2915
|
}
|
|
2883
2916
|
/**
|
|
@@ -3023,19 +3056,21 @@ var Agent = class _Agent {
|
|
|
3023
3056
|
const usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
3024
3057
|
let usageEstimated = false;
|
|
3025
3058
|
const start = Date.now();
|
|
3059
|
+
this.parkedMs = 0;
|
|
3026
3060
|
let toolCallsTotal = 0;
|
|
3027
3061
|
let lastFp = "";
|
|
3028
3062
|
let repeats = 0;
|
|
3029
3063
|
const kill = (finishReason) => {
|
|
3030
|
-
log3.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, ms=${Date.now() - start})`);
|
|
3064
|
+
log3.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
|
|
3031
3065
|
this.ctx.jobs?.killAll();
|
|
3032
3066
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
|
|
3033
3067
|
};
|
|
3034
3068
|
while (true) {
|
|
3035
3069
|
if (o.signal?.aborted) return kill("aborted");
|
|
3036
3070
|
if (steps >= o.maxSteps) return kill("max_steps");
|
|
3037
|
-
if (o.timeoutMs && Date.now() - start >= o.timeoutMs) return kill("timeout");
|
|
3038
|
-
|
|
3071
|
+
if (o.timeoutMs && Date.now() - start - this.parkedMs >= o.timeoutMs) return kill("timeout");
|
|
3072
|
+
const budgetTokens = usage.totalTokens - 0.9 * usage.cacheReadTokens;
|
|
3073
|
+
if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
|
|
3039
3074
|
steps++;
|
|
3040
3075
|
this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
|
|
3041
3076
|
let res;
|
|
@@ -3170,7 +3205,7 @@ var Agent = class _Agent {
|
|
|
3170
3205
|
const hooks = this.activeHooks;
|
|
3171
3206
|
const call = { name: tc.function.name, args };
|
|
3172
3207
|
const meta = { id: tc.id };
|
|
3173
|
-
const decision = await hooks?.preToolUse?.(call, meta);
|
|
3208
|
+
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
3174
3209
|
if (decision?.block) {
|
|
3175
3210
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
3176
3211
|
log3.debug(`${tc.function.name} -> ${blocked}`);
|
|
@@ -3212,6 +3247,7 @@ var Agent = class _Agent {
|
|
|
3212
3247
|
this.ctx.emit = void 0;
|
|
3213
3248
|
}
|
|
3214
3249
|
if (!threw) result = await this.maybeAutoTest(tc.function.name, result);
|
|
3250
|
+
if (images?.length && !result) result = `[${images.length} image${images.length > 1 ? "s" : ""} attached]`;
|
|
3215
3251
|
const cap = this.options.maxToolResultBytes ?? 0;
|
|
3216
3252
|
if (!threw && cap > 0 && result.length > cap) {
|
|
3217
3253
|
const info = { tool: tc.function.name, args };
|
|
@@ -7457,6 +7493,12 @@ var EditorState = class _EditorState {
|
|
|
7457
7493
|
pasteChar(c) {
|
|
7458
7494
|
this.pasteBuf += c;
|
|
7459
7495
|
}
|
|
7496
|
+
/** Actively grab a clipboard image and attach it (Ctrl-V; same hook as the empty-paste path). */
|
|
7497
|
+
grabClipboard() {
|
|
7498
|
+
const att = this.onEmptyPaste?.();
|
|
7499
|
+
if (att) this.attach(att.display, att.ref);
|
|
7500
|
+
return !!att;
|
|
7501
|
+
}
|
|
7460
7502
|
endPaste() {
|
|
7461
7503
|
this.pasting = false;
|
|
7462
7504
|
const text = this.pasteBuf;
|
|
@@ -7887,6 +7929,10 @@ function applyKey(s, key, str) {
|
|
|
7887
7929
|
if (key?.ctrl && k === "u") return s.killToStart(), "none";
|
|
7888
7930
|
if (key?.ctrl && k === "k") return s.killToEnd(), "none";
|
|
7889
7931
|
if (key?.ctrl && k === "y") return s.yank(), "none";
|
|
7932
|
+
if (key?.ctrl && k === "v") {
|
|
7933
|
+
s.grabClipboard();
|
|
7934
|
+
return "none";
|
|
7935
|
+
}
|
|
7890
7936
|
if (str === "") return s.undo(), "none";
|
|
7891
7937
|
if (key?.meta && k === "b") return s.wordLeft(), "none";
|
|
7892
7938
|
if (key?.meta && k === "f") return s.wordRight(), "none";
|
|
@@ -8774,7 +8820,7 @@ REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to co
|
|
|
8774
8820
|
REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
|
|
8775
8821
|
REPL stash: type while a turn is running \u2192 Enter queues it (auto-submits when the turn finishes). Alt+S (or Ctrl+S) with text stashes it; on an empty prompt pops the next entry for editing.
|
|
8776
8822
|
REPL editing (emacs/readline): Ctrl-A/E line start/end \xB7 Ctrl-B/F char \xB7 Alt-B/F or Alt/Ctrl-\u2190/\u2192 word \xB7 Ctrl-W kill word \xB7 Ctrl-U/K kill to start/end \xB7 Ctrl-Y yank \xB7 Ctrl-_ undo \xB7 Alt-D kill word fwd \xB7 Ctrl-L clear screen. Set editorMode:'vim' (or /config) for modal vim editing.
|
|
8777
|
-
REPL paste: large/multi-line pastes collapse to a [Pasted text +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; /paste grabs a clipboard image (macOS).`;
|
|
8823
|
+
REPL paste: large/multi-line pastes collapse to a [Pasted text +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
|
|
8778
8824
|
function newestModel() {
|
|
8779
8825
|
return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
|
|
8780
8826
|
}
|
|
@@ -9972,6 +10018,25 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9972
10018
|
const adots = (sub) => [adot(sub), `${fsBase}/.claude/${sub}`, `${homedir6()}/.agent/${sub}`, `${homedir6()}/.claude/${sub}`];
|
|
9973
10019
|
const cmds = (await loadCommands(fs, adots("commands"))).commands;
|
|
9974
10020
|
const skills = (await loadSkills(fs, adots("skills"))).skills;
|
|
10021
|
+
const refreshCatalogs = async () => {
|
|
10022
|
+
const [freshSkills, freshCmds] = await Promise.all([scanSkills(fs, adots("skills")), scanCommands(fs, adots("commands"))]);
|
|
10023
|
+
const diff = (kind, old, fresh) => {
|
|
10024
|
+
const had = new Set(old.map((x) => x.name)), has = new Set(fresh.map((x) => x.name));
|
|
10025
|
+
const added = fresh.filter((x) => !had.has(x.name)), removed = old.filter((x) => !has.has(x.name));
|
|
10026
|
+
old.splice(0, old.length, ...fresh);
|
|
10027
|
+
return [
|
|
10028
|
+
...added.map((x) => `+ ${kind} **${x.name}** \u2014 ${x.description}`),
|
|
10029
|
+
...removed.map((x) => `- ${kind} ${x.name} (removed)`)
|
|
10030
|
+
];
|
|
10031
|
+
};
|
|
10032
|
+
const lines = [...diff("skill", skills, freshSkills), ...diff("command", cmds, freshCmds)];
|
|
10033
|
+
if (!lines.length) return "";
|
|
10034
|
+
return `<system-reminder>
|
|
10035
|
+
The skill/command catalog changed on disk since the session started:
|
|
10036
|
+
${lines.join("\n")}
|
|
10037
|
+
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
10038
|
+
</system-reminder>`;
|
|
10039
|
+
};
|
|
9975
10040
|
const histPath = join9(cwd, ".agent", "history");
|
|
9976
10041
|
const history = existsSync8(histPath) ? readFileSync5(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
9977
10042
|
const remember = (line) => {
|
|
@@ -10076,6 +10141,14 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
10076
10141
|
};
|
|
10077
10142
|
const announcedTasks = /* @__PURE__ */ new Set();
|
|
10078
10143
|
const turn = async (task) => {
|
|
10144
|
+
const delta = await refreshCatalogs().catch((e) => {
|
|
10145
|
+
log17.debug("catalog refresh failed", e);
|
|
10146
|
+
return "";
|
|
10147
|
+
});
|
|
10148
|
+
if (delta) {
|
|
10149
|
+
err(dim(" \u27F3 skill/command catalog changed \u2014 delta attached to this turn\n"));
|
|
10150
|
+
task += "\n\n" + delta;
|
|
10151
|
+
}
|
|
10079
10152
|
const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia);
|
|
10080
10153
|
if (voiceIO) {
|
|
10081
10154
|
voiceEchoEnd();
|
|
@@ -10314,6 +10387,17 @@ ${extra}` : body);
|
|
|
10314
10387
|
if (hookCount) ok(`hooks: ${hookCount} configured`);
|
|
10315
10388
|
}
|
|
10316
10389
|
},
|
|
10390
|
+
reload: {
|
|
10391
|
+
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
10392
|
+
run: async () => {
|
|
10393
|
+
await refreshCatalogs().catch((e) => {
|
|
10394
|
+
log17.debug("catalog refresh failed", e);
|
|
10395
|
+
});
|
|
10396
|
+
face.reprepare();
|
|
10397
|
+
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
10398
|
+
`));
|
|
10399
|
+
}
|
|
10400
|
+
},
|
|
10317
10401
|
cwd: {
|
|
10318
10402
|
desc: "print the working directory (to switch, relaunch with -C <dir>)",
|
|
10319
10403
|
run: (a) => {
|
|
@@ -10949,7 +11033,7 @@ ${extra}` : body);
|
|
|
10949
11033
|
},
|
|
10950
11034
|
init: { desc: "scaffold ./AGENTS.md project instructions", run: () => initInstructions(cwd) },
|
|
10951
11035
|
paste: {
|
|
10952
|
-
desc: "attach an image from the clipboard (macOS) \u2014 /paste [message] sends now, /paste alone attaches to your next message",
|
|
11036
|
+
desc: "attach an image from the clipboard (macOS, = Ctrl-V) \u2014 /paste [message] sends now, /paste alone attaches to your next message",
|
|
10953
11037
|
run: async (a) => {
|
|
10954
11038
|
const att = grabClipboardAttachment();
|
|
10955
11039
|
if (!att) {
|