agent.libx.js 0.94.7 → 0.94.8
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 +2 -1
- package/dist/{Agent-uWtu_WFY.d.ts → Agent-COa80xYy.d.ts} +4 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +175 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +33 -6
- package/dist/index.js.map +1 -1
- package/dist/mcp.client.d.ts +2 -0
- package/dist/mcp.client.js +1 -1
- package/dist/mcp.client.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,8 @@ 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. (`/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 /clear /sessions /resume /commands /init`; 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).
|
|
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.
|
|
110
111
|
- **Project instructions** — `./AGENTS.md` (or `CLAUDE.md`) auto-loads into every run; `/init` scaffolds one.
|
|
111
112
|
- **Any provider** — set `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `GROQ_API_KEY`; choose with `-m provider/model`.
|
|
112
113
|
- **@-file mentions & headless JSON** — reference files inline in a prompt with `@path` (e.g. `explain @src/Agent.ts`); script with `-p --output-format json` to get one machine-readable result object on stdout (activity stays on stderr).
|
|
@@ -311,6 +311,7 @@ declare class Agent {
|
|
|
311
311
|
options: AgentOptions;
|
|
312
312
|
transcript: Message[];
|
|
313
313
|
private ctx;
|
|
314
|
+
private lastTrimNotified;
|
|
314
315
|
private activeTools;
|
|
315
316
|
private activeHooks?;
|
|
316
317
|
private prepared;
|
|
@@ -362,8 +363,10 @@ declare class Agent {
|
|
|
362
363
|
* Fold the conversation in place (manual `/compact`): keep the system message + the
|
|
363
364
|
* most-recent window, summarizing the dropped middle (deterministic, no LLM call).
|
|
364
365
|
* No-op when the transcript already fits. Returns the number of messages removed.
|
|
366
|
+
* `focus` (e.g. from `/compact keep the API details`) preserves matching lines from the
|
|
367
|
+
* dropped span verbatim in the summary, instead of losing them to the generic recap.
|
|
365
368
|
*/
|
|
366
|
-
compactNow(maxMessages?: number): number;
|
|
369
|
+
compactNow(maxMessages?: number, focus?: string): number;
|
|
367
370
|
private runLoop;
|
|
368
371
|
/**
|
|
369
372
|
* Drain a streamed chat() response: emit each text delta to the host
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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-COa80xYy.js';
|
|
3
3
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
4
4
|
import { M as Message, c as ContentPart } from './tools-GPWp7oXq.js';
|
|
5
5
|
|
package/dist/cli.js
CHANGED
|
@@ -2769,6 +2769,8 @@ var Agent = class _Agent {
|
|
|
2769
2769
|
transcript = [];
|
|
2770
2770
|
ctx;
|
|
2771
2771
|
// built in the ctor when `fs` is provided, else lazily in ensureFs()
|
|
2772
|
+
lastTrimNotified = 0;
|
|
2773
|
+
// last auto-trim drop count surfaced via host.notify (dedup)
|
|
2772
2774
|
activeTools = [];
|
|
2773
2775
|
activeHooks;
|
|
2774
2776
|
// composed: user hooks + plan-mode + permissions
|
|
@@ -2932,13 +2934,15 @@ var Agent = class _Agent {
|
|
|
2932
2934
|
* Fold the conversation in place (manual `/compact`): keep the system message + the
|
|
2933
2935
|
* most-recent window, summarizing the dropped middle (deterministic, no LLM call).
|
|
2934
2936
|
* No-op when the transcript already fits. Returns the number of messages removed.
|
|
2937
|
+
* `focus` (e.g. from `/compact keep the API details`) preserves matching lines from the
|
|
2938
|
+
* dropped span verbatim in the summary, instead of losing them to the generic recap.
|
|
2935
2939
|
*/
|
|
2936
|
-
compactNow(maxMessages = 12) {
|
|
2940
|
+
compactNow(maxMessages = 12, focus) {
|
|
2937
2941
|
const max = Math.max(2, maxMessages);
|
|
2938
2942
|
if (this.transcript.length <= max) return 0;
|
|
2939
2943
|
void this.activeHooks?.onPreCompact?.(this.transcript);
|
|
2940
2944
|
const before = this.transcript.length;
|
|
2941
|
-
this.transcript = compact(this.transcript, max);
|
|
2945
|
+
this.transcript = compact(this.transcript, max, focus);
|
|
2942
2946
|
return before - this.transcript.length;
|
|
2943
2947
|
}
|
|
2944
2948
|
async runLoop() {
|
|
@@ -3168,7 +3172,15 @@ ${out}`;
|
|
|
3168
3172
|
if (o.compaction?.maxMessages && m.length > o.compaction.maxMessages) out = compact(m, o.compaction.maxMessages);
|
|
3169
3173
|
else if (o.maxContextMessages && m.length > o.maxContextMessages) out = dropOldest(m, o.maxContextMessages);
|
|
3170
3174
|
if (o.keepToolOutputs) out = stubOldToolResults(out ?? m, o.keepToolOutputs);
|
|
3171
|
-
if (o.maxContextTokens)
|
|
3175
|
+
if (o.maxContextTokens) {
|
|
3176
|
+
const pre = (out ?? m).length;
|
|
3177
|
+
out = fitTokenBudget(out ?? m, o.maxContextTokens);
|
|
3178
|
+
const dropped = pre - out.length;
|
|
3179
|
+
if (dropped > 0 && dropped !== this.lastTrimNotified) {
|
|
3180
|
+
this.lastTrimNotified = dropped;
|
|
3181
|
+
o.host?.notify?.({ kind: "compaction", message: `context full \u2014 auto-trimmed ${dropped} oldest message(s) to fit ~${Math.round(o.maxContextTokens / 1e3)}k tokens` });
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3172
3184
|
return dropOrphanToolResults(out ?? m);
|
|
3173
3185
|
}
|
|
3174
3186
|
};
|
|
@@ -3228,16 +3240,16 @@ function fitTokenBudget(messages, maxTokens) {
|
|
|
3228
3240
|
log3.warn(`context ~${estimateTokens([...head, ...body])} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
3229
3241
|
return [...head, ...body];
|
|
3230
3242
|
}
|
|
3231
|
-
function compact(m, max) {
|
|
3243
|
+
function compact(m, max, focus) {
|
|
3232
3244
|
const hasSystem = m[0]?.role === "system";
|
|
3233
3245
|
const head = hasSystem ? [m[0]] : [];
|
|
3234
3246
|
const tailCount = Math.max(1, max - head.length - 1);
|
|
3235
3247
|
const tail = m.slice(head.length).slice(-tailCount);
|
|
3236
3248
|
const dropped = m.slice(head.length, m.length - tail.length);
|
|
3237
3249
|
if (dropped.length === 0) return [...head, ...tail];
|
|
3238
|
-
return [...head, { role: "system", content: summarize(dropped) }, ...tail];
|
|
3250
|
+
return [...head, { role: "system", content: summarize(dropped, focus) }, ...tail];
|
|
3239
3251
|
}
|
|
3240
|
-
function summarize(messages) {
|
|
3252
|
+
function summarize(messages, focus) {
|
|
3241
3253
|
const tools = /* @__PURE__ */ new Set();
|
|
3242
3254
|
const files = /* @__PURE__ */ new Set();
|
|
3243
3255
|
let lastAssistant = "";
|
|
@@ -3256,6 +3268,21 @@ function summarize(messages) {
|
|
|
3256
3268
|
if (tools.size) lines.push(`tools used: ${[...tools].join(", ")}`);
|
|
3257
3269
|
if (files.size) lines.push(`files touched: ${[...files].join(", ")}`);
|
|
3258
3270
|
if (lastAssistant) lines.push(`last assistant: ${lastAssistant}`);
|
|
3271
|
+
if (focus?.trim()) {
|
|
3272
|
+
const words = focus.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
3273
|
+
const kept = [];
|
|
3274
|
+
outer: for (const msg of messages) {
|
|
3275
|
+
if (msg.role === "tool") continue;
|
|
3276
|
+
for (const line of contentText(msg.content).split("\n")) {
|
|
3277
|
+
const l = line.toLowerCase();
|
|
3278
|
+
if (line.trim() && words.some((w) => l.includes(w))) {
|
|
3279
|
+
kept.push(line.trim().slice(0, 200));
|
|
3280
|
+
if (kept.length >= 12) break outer;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
if (kept.length) lines.push(`preserved (re: ${focus.trim()}):`, ...kept.map((l) => ` ${l}`));
|
|
3285
|
+
}
|
|
3259
3286
|
return lines.join("\n");
|
|
3260
3287
|
}
|
|
3261
3288
|
function lastAssistantText(messages) {
|
|
@@ -5605,7 +5632,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
5605
5632
|
const init = await client.connect();
|
|
5606
5633
|
const specs = await client.listTools();
|
|
5607
5634
|
const tools = mcpToolsToAgentTools(specs, (tool, a) => client.callTool(tool, a), `mcp__${name}__`);
|
|
5608
|
-
return { name, client, tools, specs, serverInfo: init?.serverInfo };
|
|
5635
|
+
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
5609
5636
|
})(), mountTimeoutMs, name);
|
|
5610
5637
|
} catch (e) {
|
|
5611
5638
|
await client.close().catch((err2) => log11.debug(`close after failed mount of "${name}": ${err2}`));
|
|
@@ -7386,7 +7413,24 @@ var EditorState = class _EditorState {
|
|
|
7386
7413
|
this.menuOpen = r.hits.length > 0 && !(r.hits.length === 1 && r.hits[0] === r.token);
|
|
7387
7414
|
if (this.sel >= r.hits.length) this.sel = 0;
|
|
7388
7415
|
}
|
|
7416
|
+
// ── Editor undo (Ctrl-_): snapshot before every mutation, pop to restore ──
|
|
7417
|
+
undoStack = [];
|
|
7418
|
+
snapshot() {
|
|
7419
|
+
const top = this.undoStack[this.undoStack.length - 1];
|
|
7420
|
+
if (top?.buf === this.buf) return;
|
|
7421
|
+
this.undoStack.push({ buf: this.buf, cursor: this.cursor });
|
|
7422
|
+
if (this.undoStack.length > 200) this.undoStack.shift();
|
|
7423
|
+
}
|
|
7424
|
+
undo() {
|
|
7425
|
+
const p = this.undoStack.pop();
|
|
7426
|
+
if (!p) return;
|
|
7427
|
+
this.buf = p.buf;
|
|
7428
|
+
this.cursor = Math.min(p.cursor, p.buf.length);
|
|
7429
|
+
this.histIdx = -1;
|
|
7430
|
+
this.refresh();
|
|
7431
|
+
}
|
|
7389
7432
|
insert(s) {
|
|
7433
|
+
this.snapshot();
|
|
7390
7434
|
this.buf = this.buf.slice(0, this.cursor) + s + this.buf.slice(this.cursor);
|
|
7391
7435
|
this.cursor += s.length;
|
|
7392
7436
|
this.histIdx = -1;
|
|
@@ -7394,6 +7438,7 @@ var EditorState = class _EditorState {
|
|
|
7394
7438
|
}
|
|
7395
7439
|
backspace() {
|
|
7396
7440
|
if (this.cursor === 0) return;
|
|
7441
|
+
this.snapshot();
|
|
7397
7442
|
this.buf = this.buf.slice(0, this.cursor - 1) + this.buf.slice(this.cursor);
|
|
7398
7443
|
this.cursor--;
|
|
7399
7444
|
this.histIdx = -1;
|
|
@@ -7401,6 +7446,7 @@ var EditorState = class _EditorState {
|
|
|
7401
7446
|
}
|
|
7402
7447
|
del() {
|
|
7403
7448
|
if (this.cursor >= this.buf.length) return;
|
|
7449
|
+
this.snapshot();
|
|
7404
7450
|
this.buf = this.buf.slice(0, this.cursor) + this.buf.slice(this.cursor + 1);
|
|
7405
7451
|
this.refresh();
|
|
7406
7452
|
}
|
|
@@ -7437,6 +7483,7 @@ var EditorState = class _EditorState {
|
|
|
7437
7483
|
return j;
|
|
7438
7484
|
}
|
|
7439
7485
|
cut(from, to) {
|
|
7486
|
+
this.snapshot();
|
|
7440
7487
|
this.killed = this.buf.slice(from, to);
|
|
7441
7488
|
this.buf = this.buf.slice(0, from) + this.buf.slice(to);
|
|
7442
7489
|
this.cursor = from;
|
|
@@ -7748,6 +7795,7 @@ function applyKey(s, key, str) {
|
|
|
7748
7795
|
if (key?.ctrl && k === "u") return s.killToStart(), "none";
|
|
7749
7796
|
if (key?.ctrl && k === "k") return s.killToEnd(), "none";
|
|
7750
7797
|
if (key?.ctrl && k === "y") return s.yank(), "none";
|
|
7798
|
+
if (str === "") return s.undo(), "none";
|
|
7751
7799
|
if (key?.meta && k === "b") return s.wordLeft(), "none";
|
|
7752
7800
|
if (key?.meta && k === "f") return s.wordRight(), "none";
|
|
7753
7801
|
if (key?.meta && k === "d") return s.killWordForward(), "none";
|
|
@@ -8408,10 +8456,17 @@ var spinner = /* @__PURE__ */ (() => {
|
|
|
8408
8456
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
8409
8457
|
let timer;
|
|
8410
8458
|
let i = 0;
|
|
8459
|
+
let t0 = 0;
|
|
8411
8460
|
return {
|
|
8461
|
+
/** Anchor for the elapsed counter; runTurn sets it once per turn so tool-call stop/start cycles don't reset it. */
|
|
8462
|
+
turnStart: 0,
|
|
8412
8463
|
start(label = "thinking\u2026") {
|
|
8413
8464
|
if (!tty || timer) return;
|
|
8414
|
-
|
|
8465
|
+
t0 = this.turnStart || Date.now();
|
|
8466
|
+
timer = setInterval(() => {
|
|
8467
|
+
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
8468
|
+
err("\r\x1B[2K" + dim(` ${frames[i = (i + 1) % frames.length]} ${label} ${secs ? `${secs}s \xB7 ` : ""}esc to interrupt`));
|
|
8469
|
+
}, 90);
|
|
8415
8470
|
},
|
|
8416
8471
|
stop() {
|
|
8417
8472
|
if (timer) {
|
|
@@ -8422,6 +8477,9 @@ var spinner = /* @__PURE__ */ (() => {
|
|
|
8422
8477
|
}
|
|
8423
8478
|
};
|
|
8424
8479
|
})();
|
|
8480
|
+
var setTermTitle = (t) => {
|
|
8481
|
+
if (tty) err(`\x1B]0;${t.replace(/[\x00-\x1f]/g, " ").slice(0, 80)}\x07`);
|
|
8482
|
+
};
|
|
8425
8483
|
var activeTurn = null;
|
|
8426
8484
|
var exitRequested = false;
|
|
8427
8485
|
var inputStash = [];
|
|
@@ -8571,12 +8629,12 @@ Project instructions: ./AGENTS.md or ./CLAUDE.md are auto-loaded (scaffold with
|
|
|
8571
8629
|
Auto-loaded from ./.agent/: commands/, skills/, memory/, agents/.
|
|
8572
8630
|
|
|
8573
8631
|
REPL shortcuts: !<cmd> runs a shell command inline \xB7 #<note> saves a memory \xB7 @path inlines a file
|
|
8574
|
-
REPL slash commands: /help /version /tools /permissions /status /cost /context /cwd /model /reasoning /config /rename /compact /rewind /undo /clear /sessions /resume /commands /skills /mcp /init /export /paste /goal /exit (duplex: /act /think /tasks /voice /voice-model /think-model)
|
|
8632
|
+
REPL slash commands: /help /version /tools /permissions /status /cost /context /cwd /model /reasoning /config /rename /compact /memory /rewind /undo /clear /sessions /resume /commands /skills /mcp /init /export /paste /goal /exit (duplex: /act /think /tasks /voice /voice-model /think-model)
|
|
8575
8633
|
REPL completion: type / (commands+skills) or @ (files) for a LIVE menu \u2014 \u2191/\u2193 select, \u23CE/Tab accept, Esc dismiss.
|
|
8576
8634
|
REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to continue. Esc cancels a running turn / clears the input line; double-Esc jumps back to edit a previous message.
|
|
8577
8635
|
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.
|
|
8578
8636
|
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.
|
|
8579
|
-
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 Alt-D kill word fwd \xB7 Ctrl-L clear screen. Set editorMode:'vim' (or /config) for modal vim editing.
|
|
8637
|
+
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.
|
|
8580
8638
|
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).`;
|
|
8581
8639
|
function newestModel() {
|
|
8582
8640
|
return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
|
|
@@ -8635,6 +8693,16 @@ function makeHost(format = "text", opts) {
|
|
|
8635
8693
|
return {
|
|
8636
8694
|
flushText,
|
|
8637
8695
|
notify(e) {
|
|
8696
|
+
if (e.kind === "turn_start") {
|
|
8697
|
+
if (e.message !== "step 1") {
|
|
8698
|
+
spinner.stop();
|
|
8699
|
+
closeReasonLine();
|
|
8700
|
+
err(dim(` \xB7 ${e.message}
|
|
8701
|
+
`));
|
|
8702
|
+
spinner.start();
|
|
8703
|
+
}
|
|
8704
|
+
return;
|
|
8705
|
+
}
|
|
8638
8706
|
spinner.stop();
|
|
8639
8707
|
if (e.kind === "text_delta") {
|
|
8640
8708
|
if (streamJson) process.stdout.write(JSON.stringify({ type: "text", text: e.message }) + "\n");
|
|
@@ -9161,6 +9229,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9161
9229
|
agent.options.signal = ctrl.signal;
|
|
9162
9230
|
const content = images.length ? [{ type: "text", text }, ...images] : text;
|
|
9163
9231
|
let res;
|
|
9232
|
+
spinner.turnStart = t0;
|
|
9164
9233
|
spinner.start(sendFn ? "voice\u2026" : void 0);
|
|
9165
9234
|
try {
|
|
9166
9235
|
res = await (sendFn ? sendFn(content) : agent.send(content));
|
|
@@ -9177,6 +9246,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9177
9246
|
return { ok: false };
|
|
9178
9247
|
} finally {
|
|
9179
9248
|
spinner.stop();
|
|
9249
|
+
spinner.turnStart = 0;
|
|
9180
9250
|
activeTurn = null;
|
|
9181
9251
|
agent.options.signal = void 0;
|
|
9182
9252
|
}
|
|
@@ -9193,6 +9263,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9193
9263
|
if (!silentAbort)
|
|
9194
9264
|
err("\n" + (process.stderr.isTTY ? "\r\x1B[0J" : "") + (ok ? green(" \u2713 done") : red(` \u2717 ${res.finishReason}`)) + dim(` \xB7 ${res.steps} steps \xB7 ${tools} tools \xB7 ${tok}${secs}s \xB7 ${shortId}
|
|
9195
9265
|
`));
|
|
9266
|
+
if (!silentAbort && process.stderr.isTTY && Date.now() - t0 > 1e4) err("\x07");
|
|
9196
9267
|
if (res.finishReason === "error" && res.error) {
|
|
9197
9268
|
const e = res.error;
|
|
9198
9269
|
err(red(` ${e?.message ?? e}`) + (e?.statusCode ? dim(` (${e.statusCode}${e.code ? " " + e.code : ""})`) : "") + "\n");
|
|
@@ -9208,7 +9279,10 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9208
9279
|
const ev = { ts: t0, durationMs: Date.now() - t0, model: agent.options.model, finishReason: res.finishReason, steps: res.steps, tools, tokens: res.usage?.totalTokens, costUsd: cost, estimated: res.usageEstimated };
|
|
9209
9280
|
if (res.finishReason === "error" && res.error) ev.error = errInfo(res.error);
|
|
9210
9281
|
(session.meta.events ??= []).push(ev);
|
|
9211
|
-
if (!session.meta.title)
|
|
9282
|
+
if (!session.meta.title) {
|
|
9283
|
+
session.meta.title = titleOf(agent.transcript);
|
|
9284
|
+
if (session.meta.title) setTermTitle(`agentx \xB7 ${session.meta.title}`);
|
|
9285
|
+
}
|
|
9212
9286
|
try {
|
|
9213
9287
|
store.save(session);
|
|
9214
9288
|
} catch (e) {
|
|
@@ -9651,6 +9725,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9651
9725
|
installCancelGuards(mounted);
|
|
9652
9726
|
const store = new SessionStore(cwd);
|
|
9653
9727
|
let session = startSession(args, store, face, cwd);
|
|
9728
|
+
setTermTitle(`agentx \xB7 ${session.meta.title || cwd.split("/").pop() || "session"}`);
|
|
9654
9729
|
if (session.meta.scheduledJobs?.length) {
|
|
9655
9730
|
scheduler.restore(session.meta.scheduledJobs);
|
|
9656
9731
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
@@ -10210,19 +10285,60 @@ ${extra}` : body);
|
|
|
10210
10285
|
store.save(session);
|
|
10211
10286
|
} catch {
|
|
10212
10287
|
}
|
|
10288
|
+
setTermTitle(`agentx \xB7 ${t}`);
|
|
10213
10289
|
err(dim(" renamed \u2192 " + t + "\n"));
|
|
10214
10290
|
}
|
|
10215
10291
|
},
|
|
10216
10292
|
compact: {
|
|
10217
|
-
desc: "summarize older context to free up the window",
|
|
10218
|
-
run: () => {
|
|
10219
|
-
const
|
|
10293
|
+
desc: "summarize older context to free up the window \u2014 /compact [what to preserve]",
|
|
10294
|
+
run: (a) => {
|
|
10295
|
+
const focus = a.join(" ").trim() || void 0;
|
|
10296
|
+
const n = face.compactNow(12, focus);
|
|
10220
10297
|
session.messages = face.transcript;
|
|
10221
10298
|
try {
|
|
10222
10299
|
store.save(session);
|
|
10223
10300
|
} catch {
|
|
10224
10301
|
}
|
|
10225
|
-
err(dim(` compacted \u2014 folded ${n} message(s)
|
|
10302
|
+
err(dim(` compacted \u2014 folded ${n} message(s)${focus && n ? ` (preserving: ${focus})` : ""}
|
|
10303
|
+
`));
|
|
10304
|
+
}
|
|
10305
|
+
},
|
|
10306
|
+
memory: {
|
|
10307
|
+
desc: "open the memory index in $EDITOR (.agent/memory/MEMORY.md)",
|
|
10308
|
+
run: async () => {
|
|
10309
|
+
const dir = primaryMemDir(face.options.memoryDir, adot("memory"));
|
|
10310
|
+
const idx = `${dir}/MEMORY.md`;
|
|
10311
|
+
const fs2 = face.options.fs;
|
|
10312
|
+
if (args.vfs || args.boddb) {
|
|
10313
|
+
try {
|
|
10314
|
+
err(dim(await fs2.readFile(idx)) + "\n");
|
|
10315
|
+
} catch {
|
|
10316
|
+
err(dim(" (no memory yet \u2014 save one with `#<note>`)\n"));
|
|
10317
|
+
}
|
|
10318
|
+
return;
|
|
10319
|
+
}
|
|
10320
|
+
try {
|
|
10321
|
+
await fs2.readFile(idx);
|
|
10322
|
+
} catch {
|
|
10323
|
+
try {
|
|
10324
|
+
await mkdirp(fs2, dir);
|
|
10325
|
+
await fs2.writeFile(idx, "# Memory\n\n");
|
|
10326
|
+
} catch (e) {
|
|
10327
|
+
err(red(` can't create ${idx}: ${e?.message ?? e}
|
|
10328
|
+
`));
|
|
10329
|
+
return;
|
|
10330
|
+
}
|
|
10331
|
+
}
|
|
10332
|
+
const ed = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
10333
|
+
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
10334
|
+
if (wasRaw) process.stdin.setRawMode(false);
|
|
10335
|
+
try {
|
|
10336
|
+
const { spawnSync: spawnSync3 } = await import("child_process");
|
|
10337
|
+
spawnSync3(ed, [idx], { stdio: "inherit" });
|
|
10338
|
+
} finally {
|
|
10339
|
+
if (wasRaw) process.stdin.setRawMode(true);
|
|
10340
|
+
}
|
|
10341
|
+
err(dim(` \u270E ${idx}
|
|
10226
10342
|
`));
|
|
10227
10343
|
}
|
|
10228
10344
|
},
|
|
@@ -10289,7 +10405,7 @@ ${extra}` : body);
|
|
|
10289
10405
|
commands: { desc: "pick a custom slash command to run (./.agent/commands)", run: () => pickAndRun("command") },
|
|
10290
10406
|
skills: { desc: "pick a skill to run (./.agent/skills)", run: () => pickAndRun("skill") },
|
|
10291
10407
|
mcp: {
|
|
10292
|
-
desc: "manage MCP servers \u2014 /mcp [add <name> <cmd|url>] [login <name>] [remove <name>] [tools [name]] [resources [name]]",
|
|
10408
|
+
desc: "manage MCP servers \u2014 /mcp [add <name> <cmd|url>] [login <name>] [reconnect <name>] [remove <name>] [tools [name]] [resources [name]]",
|
|
10293
10409
|
run: async (a) => {
|
|
10294
10410
|
const sub = a[0]?.toLowerCase();
|
|
10295
10411
|
if (sub === "login") {
|
|
@@ -10342,6 +10458,36 @@ ${extra}` : body);
|
|
|
10342
10458
|
`));
|
|
10343
10459
|
} catch (e) {
|
|
10344
10460
|
err(red(` failed to mount "${name}": ${e?.message ?? e}
|
|
10461
|
+
`));
|
|
10462
|
+
}
|
|
10463
|
+
return;
|
|
10464
|
+
}
|
|
10465
|
+
if (sub === "reconnect") {
|
|
10466
|
+
const name = a[1];
|
|
10467
|
+
if (!name) {
|
|
10468
|
+
err(yellow(" usage: /mcp reconnect <name>\n"));
|
|
10469
|
+
return;
|
|
10470
|
+
}
|
|
10471
|
+
const idx = mounted.findIndex((m) => m.name === name);
|
|
10472
|
+
const conf = idx >= 0 ? mounted[idx].config : cfg.mcpServers?.[name];
|
|
10473
|
+
if (!conf) {
|
|
10474
|
+
err(yellow(` MCP "${name}" not found (not mounted and not in config)
|
|
10475
|
+
`));
|
|
10476
|
+
return;
|
|
10477
|
+
}
|
|
10478
|
+
if (idx >= 0) {
|
|
10479
|
+
const old = mounted.splice(idx, 1)[0];
|
|
10480
|
+
removeWorkTools(old.tools.map((t) => t.name));
|
|
10481
|
+
await old.client.close().catch((e) => log17.debug("mcp close failed", e));
|
|
10482
|
+
}
|
|
10483
|
+
try {
|
|
10484
|
+
const m = await mountMcpServer(name, conf);
|
|
10485
|
+
mounted.push(m);
|
|
10486
|
+
addWorkTools(m.tools);
|
|
10487
|
+
err(green(` \u2713 reconnected "${name}"`) + dim(` \u2014 ${m.tools.length} tool(s)
|
|
10488
|
+
`));
|
|
10489
|
+
} catch (e) {
|
|
10490
|
+
err(red(` reconnect failed: ${e?.message ?? e}
|
|
10345
10491
|
`));
|
|
10346
10492
|
}
|
|
10347
10493
|
return;
|
|
@@ -10425,6 +10571,7 @@ ${extra}` : body);
|
|
|
10425
10571
|
{ label: "add", value: "add", desc: "mount a new MCP server" },
|
|
10426
10572
|
...mounted.length ? [
|
|
10427
10573
|
{ label: "tools", value: "tools", desc: "list a server's tools" },
|
|
10574
|
+
{ label: "reconnect", value: "reconnect", desc: "remount a server (hung/restarted)" },
|
|
10428
10575
|
{ label: "remove", value: "remove", desc: "unmount an MCP server" },
|
|
10429
10576
|
{ label: "resources", value: "resources", desc: "list server resources" }
|
|
10430
10577
|
] : []
|
|
@@ -10449,10 +10596,10 @@ ${extra}` : body);
|
|
|
10449
10596
|
} finally {
|
|
10450
10597
|
io.close();
|
|
10451
10598
|
}
|
|
10452
|
-
} else if (picked === "remove") {
|
|
10453
|
-
const rv = await selectMenu(process.stderr, { title:
|
|
10599
|
+
} else if (picked === "remove" || picked === "reconnect") {
|
|
10600
|
+
const rv = await selectMenu(process.stderr, { title: `${picked} server`, items: mounted.map((m) => ({ label: m.name, value: m.name })) });
|
|
10454
10601
|
if (!rv) return;
|
|
10455
|
-
a = [
|
|
10602
|
+
a = [picked, rv];
|
|
10456
10603
|
} else if (picked === "tools" || picked === "resources") {
|
|
10457
10604
|
if (mounted.length === 1) {
|
|
10458
10605
|
a = [picked, mounted[0].name];
|
|
@@ -10797,6 +10944,7 @@ ${extra}` : body);
|
|
|
10797
10944
|
editorRef?.redrawNow();
|
|
10798
10945
|
};
|
|
10799
10946
|
if (args.voice && duplex && process.stdin.isTTY) await startVoice(true);
|
|
10947
|
+
let ctxWarned = 0;
|
|
10800
10948
|
while (true) {
|
|
10801
10949
|
if (pendingRewind) {
|
|
10802
10950
|
pendingRewind = false;
|
|
@@ -10811,6 +10959,14 @@ ${extra}` : body);
|
|
|
10811
10959
|
prefill = void 0;
|
|
10812
10960
|
const ctxTok = estimateTranscriptTokens(face.transcript);
|
|
10813
10961
|
const ctxCap = face.options.maxTokens || 2e5;
|
|
10962
|
+
{
|
|
10963
|
+
const pct = Math.round(ctxTok / ctxCap * 100);
|
|
10964
|
+
const step = pct >= 90 ? 90 : pct >= 80 ? 80 : 0;
|
|
10965
|
+
if (step > ctxWarned) {
|
|
10966
|
+
ctxWarned = step;
|
|
10967
|
+
err(yellow(` \u26A0 context ${pct}% full`) + dim(" \u2014 /compact folds older messages (oldest are auto-trimmed at the cap)\n"));
|
|
10968
|
+
} else if (!step) ctxWarned = 0;
|
|
10969
|
+
}
|
|
10814
10970
|
const usd = session.meta.costUsd ?? 0;
|
|
10815
10971
|
const computeFooter = () => {
|
|
10816
10972
|
const parts = [];
|