agent.libx.js 0.94.7 → 0.94.9
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 +177 -20
- 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}`));
|
|
@@ -7383,10 +7410,28 @@ var EditorState = class _EditorState {
|
|
|
7383
7410
|
this.hits = r.hits;
|
|
7384
7411
|
this.token = r.token;
|
|
7385
7412
|
this.describe = r.describe;
|
|
7386
|
-
|
|
7413
|
+
const exact = r.hits.length === 1 && r.hits[0] === r.token;
|
|
7414
|
+
this.menuOpen = r.hits.length > 0 && (!exact || r.token.startsWith("/"));
|
|
7387
7415
|
if (this.sel >= r.hits.length) this.sel = 0;
|
|
7388
7416
|
}
|
|
7417
|
+
// ── Editor undo (Ctrl-_): snapshot before every mutation, pop to restore ──
|
|
7418
|
+
undoStack = [];
|
|
7419
|
+
snapshot() {
|
|
7420
|
+
const top = this.undoStack[this.undoStack.length - 1];
|
|
7421
|
+
if (top?.buf === this.buf) return;
|
|
7422
|
+
this.undoStack.push({ buf: this.buf, cursor: this.cursor });
|
|
7423
|
+
if (this.undoStack.length > 200) this.undoStack.shift();
|
|
7424
|
+
}
|
|
7425
|
+
undo() {
|
|
7426
|
+
const p = this.undoStack.pop();
|
|
7427
|
+
if (!p) return;
|
|
7428
|
+
this.buf = p.buf;
|
|
7429
|
+
this.cursor = Math.min(p.cursor, p.buf.length);
|
|
7430
|
+
this.histIdx = -1;
|
|
7431
|
+
this.refresh();
|
|
7432
|
+
}
|
|
7389
7433
|
insert(s) {
|
|
7434
|
+
this.snapshot();
|
|
7390
7435
|
this.buf = this.buf.slice(0, this.cursor) + s + this.buf.slice(this.cursor);
|
|
7391
7436
|
this.cursor += s.length;
|
|
7392
7437
|
this.histIdx = -1;
|
|
@@ -7394,6 +7439,7 @@ var EditorState = class _EditorState {
|
|
|
7394
7439
|
}
|
|
7395
7440
|
backspace() {
|
|
7396
7441
|
if (this.cursor === 0) return;
|
|
7442
|
+
this.snapshot();
|
|
7397
7443
|
this.buf = this.buf.slice(0, this.cursor - 1) + this.buf.slice(this.cursor);
|
|
7398
7444
|
this.cursor--;
|
|
7399
7445
|
this.histIdx = -1;
|
|
@@ -7401,6 +7447,7 @@ var EditorState = class _EditorState {
|
|
|
7401
7447
|
}
|
|
7402
7448
|
del() {
|
|
7403
7449
|
if (this.cursor >= this.buf.length) return;
|
|
7450
|
+
this.snapshot();
|
|
7404
7451
|
this.buf = this.buf.slice(0, this.cursor) + this.buf.slice(this.cursor + 1);
|
|
7405
7452
|
this.refresh();
|
|
7406
7453
|
}
|
|
@@ -7437,6 +7484,7 @@ var EditorState = class _EditorState {
|
|
|
7437
7484
|
return j;
|
|
7438
7485
|
}
|
|
7439
7486
|
cut(from, to) {
|
|
7487
|
+
this.snapshot();
|
|
7440
7488
|
this.killed = this.buf.slice(from, to);
|
|
7441
7489
|
this.buf = this.buf.slice(0, from) + this.buf.slice(to);
|
|
7442
7490
|
this.cursor = from;
|
|
@@ -7748,6 +7796,7 @@ function applyKey(s, key, str) {
|
|
|
7748
7796
|
if (key?.ctrl && k === "u") return s.killToStart(), "none";
|
|
7749
7797
|
if (key?.ctrl && k === "k") return s.killToEnd(), "none";
|
|
7750
7798
|
if (key?.ctrl && k === "y") return s.yank(), "none";
|
|
7799
|
+
if (str === "") return s.undo(), "none";
|
|
7751
7800
|
if (key?.meta && k === "b") return s.wordLeft(), "none";
|
|
7752
7801
|
if (key?.meta && k === "f") return s.wordRight(), "none";
|
|
7753
7802
|
if (key?.meta && k === "d") return s.killWordForward(), "none";
|
|
@@ -8408,10 +8457,17 @@ var spinner = /* @__PURE__ */ (() => {
|
|
|
8408
8457
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
8409
8458
|
let timer;
|
|
8410
8459
|
let i = 0;
|
|
8460
|
+
let t0 = 0;
|
|
8411
8461
|
return {
|
|
8462
|
+
/** Anchor for the elapsed counter; runTurn sets it once per turn so tool-call stop/start cycles don't reset it. */
|
|
8463
|
+
turnStart: 0,
|
|
8412
8464
|
start(label = "thinking\u2026") {
|
|
8413
8465
|
if (!tty || timer) return;
|
|
8414
|
-
|
|
8466
|
+
t0 = this.turnStart || Date.now();
|
|
8467
|
+
timer = setInterval(() => {
|
|
8468
|
+
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
8469
|
+
err("\r\x1B[2K" + dim(` ${frames[i = (i + 1) % frames.length]} ${label} ${secs ? `${secs}s \xB7 ` : ""}esc to interrupt`));
|
|
8470
|
+
}, 90);
|
|
8415
8471
|
},
|
|
8416
8472
|
stop() {
|
|
8417
8473
|
if (timer) {
|
|
@@ -8422,6 +8478,9 @@ var spinner = /* @__PURE__ */ (() => {
|
|
|
8422
8478
|
}
|
|
8423
8479
|
};
|
|
8424
8480
|
})();
|
|
8481
|
+
var setTermTitle = (t) => {
|
|
8482
|
+
if (tty) err(`\x1B]0;${t.replace(/[\x00-\x1f]/g, " ").slice(0, 80)}\x07`);
|
|
8483
|
+
};
|
|
8425
8484
|
var activeTurn = null;
|
|
8426
8485
|
var exitRequested = false;
|
|
8427
8486
|
var inputStash = [];
|
|
@@ -8571,12 +8630,12 @@ Project instructions: ./AGENTS.md or ./CLAUDE.md are auto-loaded (scaffold with
|
|
|
8571
8630
|
Auto-loaded from ./.agent/: commands/, skills/, memory/, agents/.
|
|
8572
8631
|
|
|
8573
8632
|
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)
|
|
8633
|
+
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
8634
|
REPL completion: type / (commands+skills) or @ (files) for a LIVE menu \u2014 \u2191/\u2193 select, \u23CE/Tab accept, Esc dismiss.
|
|
8576
8635
|
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
8636
|
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
8637
|
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.
|
|
8638
|
+
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
8639
|
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
8640
|
function newestModel() {
|
|
8582
8641
|
return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
|
|
@@ -8635,6 +8694,16 @@ function makeHost(format = "text", opts) {
|
|
|
8635
8694
|
return {
|
|
8636
8695
|
flushText,
|
|
8637
8696
|
notify(e) {
|
|
8697
|
+
if (e.kind === "turn_start") {
|
|
8698
|
+
if (e.message !== "step 1") {
|
|
8699
|
+
spinner.stop();
|
|
8700
|
+
closeReasonLine();
|
|
8701
|
+
err(dim(` \xB7 ${e.message}
|
|
8702
|
+
`));
|
|
8703
|
+
spinner.start();
|
|
8704
|
+
}
|
|
8705
|
+
return;
|
|
8706
|
+
}
|
|
8638
8707
|
spinner.stop();
|
|
8639
8708
|
if (e.kind === "text_delta") {
|
|
8640
8709
|
if (streamJson) process.stdout.write(JSON.stringify({ type: "text", text: e.message }) + "\n");
|
|
@@ -9161,6 +9230,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9161
9230
|
agent.options.signal = ctrl.signal;
|
|
9162
9231
|
const content = images.length ? [{ type: "text", text }, ...images] : text;
|
|
9163
9232
|
let res;
|
|
9233
|
+
spinner.turnStart = t0;
|
|
9164
9234
|
spinner.start(sendFn ? "voice\u2026" : void 0);
|
|
9165
9235
|
try {
|
|
9166
9236
|
res = await (sendFn ? sendFn(content) : agent.send(content));
|
|
@@ -9177,6 +9247,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9177
9247
|
return { ok: false };
|
|
9178
9248
|
} finally {
|
|
9179
9249
|
spinner.stop();
|
|
9250
|
+
spinner.turnStart = 0;
|
|
9180
9251
|
activeTurn = null;
|
|
9181
9252
|
agent.options.signal = void 0;
|
|
9182
9253
|
}
|
|
@@ -9193,6 +9264,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9193
9264
|
if (!silentAbort)
|
|
9194
9265
|
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
9266
|
`));
|
|
9267
|
+
if (!silentAbort && process.stderr.isTTY && Date.now() - t0 > 1e4) err("\x07");
|
|
9196
9268
|
if (res.finishReason === "error" && res.error) {
|
|
9197
9269
|
const e = res.error;
|
|
9198
9270
|
err(red(` ${e?.message ?? e}`) + (e?.statusCode ? dim(` (${e.statusCode}${e.code ? " " + e.code : ""})`) : "") + "\n");
|
|
@@ -9208,7 +9280,10 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9208
9280
|
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
9281
|
if (res.finishReason === "error" && res.error) ev.error = errInfo(res.error);
|
|
9210
9282
|
(session.meta.events ??= []).push(ev);
|
|
9211
|
-
if (!session.meta.title)
|
|
9283
|
+
if (!session.meta.title) {
|
|
9284
|
+
session.meta.title = titleOf(agent.transcript);
|
|
9285
|
+
if (session.meta.title) setTermTitle(`agentx \xB7 ${session.meta.title}`);
|
|
9286
|
+
}
|
|
9212
9287
|
try {
|
|
9213
9288
|
store.save(session);
|
|
9214
9289
|
} catch (e) {
|
|
@@ -9651,6 +9726,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9651
9726
|
installCancelGuards(mounted);
|
|
9652
9727
|
const store = new SessionStore(cwd);
|
|
9653
9728
|
let session = startSession(args, store, face, cwd);
|
|
9729
|
+
setTermTitle(`agentx \xB7 ${session.meta.title || cwd.split("/").pop() || "session"}`);
|
|
9654
9730
|
if (session.meta.scheduledJobs?.length) {
|
|
9655
9731
|
scheduler.restore(session.meta.scheduledJobs);
|
|
9656
9732
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
@@ -10210,19 +10286,60 @@ ${extra}` : body);
|
|
|
10210
10286
|
store.save(session);
|
|
10211
10287
|
} catch {
|
|
10212
10288
|
}
|
|
10289
|
+
setTermTitle(`agentx \xB7 ${t}`);
|
|
10213
10290
|
err(dim(" renamed \u2192 " + t + "\n"));
|
|
10214
10291
|
}
|
|
10215
10292
|
},
|
|
10216
10293
|
compact: {
|
|
10217
|
-
desc: "summarize older context to free up the window",
|
|
10218
|
-
run: () => {
|
|
10219
|
-
const
|
|
10294
|
+
desc: "summarize older context to free up the window \u2014 /compact [what to preserve]",
|
|
10295
|
+
run: (a) => {
|
|
10296
|
+
const focus = a.join(" ").trim() || void 0;
|
|
10297
|
+
const n = face.compactNow(12, focus);
|
|
10220
10298
|
session.messages = face.transcript;
|
|
10221
10299
|
try {
|
|
10222
10300
|
store.save(session);
|
|
10223
10301
|
} catch {
|
|
10224
10302
|
}
|
|
10225
|
-
err(dim(` compacted \u2014 folded ${n} message(s)
|
|
10303
|
+
err(dim(` compacted \u2014 folded ${n} message(s)${focus && n ? ` (preserving: ${focus})` : ""}
|
|
10304
|
+
`));
|
|
10305
|
+
}
|
|
10306
|
+
},
|
|
10307
|
+
memory: {
|
|
10308
|
+
desc: "open the memory index in $EDITOR (.agent/memory/MEMORY.md)",
|
|
10309
|
+
run: async () => {
|
|
10310
|
+
const dir = primaryMemDir(face.options.memoryDir, adot("memory"));
|
|
10311
|
+
const idx = `${dir}/MEMORY.md`;
|
|
10312
|
+
const fs2 = face.options.fs;
|
|
10313
|
+
if (args.vfs || args.boddb) {
|
|
10314
|
+
try {
|
|
10315
|
+
err(dim(await fs2.readFile(idx)) + "\n");
|
|
10316
|
+
} catch {
|
|
10317
|
+
err(dim(" (no memory yet \u2014 save one with `#<note>`)\n"));
|
|
10318
|
+
}
|
|
10319
|
+
return;
|
|
10320
|
+
}
|
|
10321
|
+
try {
|
|
10322
|
+
await fs2.readFile(idx);
|
|
10323
|
+
} catch {
|
|
10324
|
+
try {
|
|
10325
|
+
await mkdirp(fs2, dir);
|
|
10326
|
+
await fs2.writeFile(idx, "# Memory\n\n");
|
|
10327
|
+
} catch (e) {
|
|
10328
|
+
err(red(` can't create ${idx}: ${e?.message ?? e}
|
|
10329
|
+
`));
|
|
10330
|
+
return;
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
const ed = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
10334
|
+
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
10335
|
+
if (wasRaw) process.stdin.setRawMode(false);
|
|
10336
|
+
try {
|
|
10337
|
+
const { spawnSync: spawnSync3 } = await import("child_process");
|
|
10338
|
+
spawnSync3(ed, [idx], { stdio: "inherit" });
|
|
10339
|
+
} finally {
|
|
10340
|
+
if (wasRaw) process.stdin.setRawMode(true);
|
|
10341
|
+
}
|
|
10342
|
+
err(dim(` \u270E ${idx}
|
|
10226
10343
|
`));
|
|
10227
10344
|
}
|
|
10228
10345
|
},
|
|
@@ -10289,7 +10406,7 @@ ${extra}` : body);
|
|
|
10289
10406
|
commands: { desc: "pick a custom slash command to run (./.agent/commands)", run: () => pickAndRun("command") },
|
|
10290
10407
|
skills: { desc: "pick a skill to run (./.agent/skills)", run: () => pickAndRun("skill") },
|
|
10291
10408
|
mcp: {
|
|
10292
|
-
desc: "manage MCP servers \u2014 /mcp [add <name> <cmd|url>] [login <name>] [remove <name>] [tools [name]] [resources [name]]",
|
|
10409
|
+
desc: "manage MCP servers \u2014 /mcp [add <name> <cmd|url>] [login <name>] [reconnect <name>] [remove <name>] [tools [name]] [resources [name]]",
|
|
10293
10410
|
run: async (a) => {
|
|
10294
10411
|
const sub = a[0]?.toLowerCase();
|
|
10295
10412
|
if (sub === "login") {
|
|
@@ -10342,6 +10459,36 @@ ${extra}` : body);
|
|
|
10342
10459
|
`));
|
|
10343
10460
|
} catch (e) {
|
|
10344
10461
|
err(red(` failed to mount "${name}": ${e?.message ?? e}
|
|
10462
|
+
`));
|
|
10463
|
+
}
|
|
10464
|
+
return;
|
|
10465
|
+
}
|
|
10466
|
+
if (sub === "reconnect") {
|
|
10467
|
+
const name = a[1];
|
|
10468
|
+
if (!name) {
|
|
10469
|
+
err(yellow(" usage: /mcp reconnect <name>\n"));
|
|
10470
|
+
return;
|
|
10471
|
+
}
|
|
10472
|
+
const idx = mounted.findIndex((m) => m.name === name);
|
|
10473
|
+
const conf = idx >= 0 ? mounted[idx].config : cfg.mcpServers?.[name];
|
|
10474
|
+
if (!conf) {
|
|
10475
|
+
err(yellow(` MCP "${name}" not found (not mounted and not in config)
|
|
10476
|
+
`));
|
|
10477
|
+
return;
|
|
10478
|
+
}
|
|
10479
|
+
if (idx >= 0) {
|
|
10480
|
+
const old = mounted.splice(idx, 1)[0];
|
|
10481
|
+
removeWorkTools(old.tools.map((t) => t.name));
|
|
10482
|
+
await old.client.close().catch((e) => log17.debug("mcp close failed", e));
|
|
10483
|
+
}
|
|
10484
|
+
try {
|
|
10485
|
+
const m = await mountMcpServer(name, conf);
|
|
10486
|
+
mounted.push(m);
|
|
10487
|
+
addWorkTools(m.tools);
|
|
10488
|
+
err(green(` \u2713 reconnected "${name}"`) + dim(` \u2014 ${m.tools.length} tool(s)
|
|
10489
|
+
`));
|
|
10490
|
+
} catch (e) {
|
|
10491
|
+
err(red(` reconnect failed: ${e?.message ?? e}
|
|
10345
10492
|
`));
|
|
10346
10493
|
}
|
|
10347
10494
|
return;
|
|
@@ -10425,6 +10572,7 @@ ${extra}` : body);
|
|
|
10425
10572
|
{ label: "add", value: "add", desc: "mount a new MCP server" },
|
|
10426
10573
|
...mounted.length ? [
|
|
10427
10574
|
{ label: "tools", value: "tools", desc: "list a server's tools" },
|
|
10575
|
+
{ label: "reconnect", value: "reconnect", desc: "remount a server (hung/restarted)" },
|
|
10428
10576
|
{ label: "remove", value: "remove", desc: "unmount an MCP server" },
|
|
10429
10577
|
{ label: "resources", value: "resources", desc: "list server resources" }
|
|
10430
10578
|
] : []
|
|
@@ -10449,10 +10597,10 @@ ${extra}` : body);
|
|
|
10449
10597
|
} finally {
|
|
10450
10598
|
io.close();
|
|
10451
10599
|
}
|
|
10452
|
-
} else if (picked === "remove") {
|
|
10453
|
-
const rv = await selectMenu(process.stderr, { title:
|
|
10600
|
+
} else if (picked === "remove" || picked === "reconnect") {
|
|
10601
|
+
const rv = await selectMenu(process.stderr, { title: `${picked} server`, items: mounted.map((m) => ({ label: m.name, value: m.name })) });
|
|
10454
10602
|
if (!rv) return;
|
|
10455
|
-
a = [
|
|
10603
|
+
a = [picked, rv];
|
|
10456
10604
|
} else if (picked === "tools" || picked === "resources") {
|
|
10457
10605
|
if (mounted.length === 1) {
|
|
10458
10606
|
a = [picked, mounted[0].name];
|
|
@@ -10797,6 +10945,7 @@ ${extra}` : body);
|
|
|
10797
10945
|
editorRef?.redrawNow();
|
|
10798
10946
|
};
|
|
10799
10947
|
if (args.voice && duplex && process.stdin.isTTY) await startVoice(true);
|
|
10948
|
+
let ctxWarned = 0;
|
|
10800
10949
|
while (true) {
|
|
10801
10950
|
if (pendingRewind) {
|
|
10802
10951
|
pendingRewind = false;
|
|
@@ -10811,6 +10960,14 @@ ${extra}` : body);
|
|
|
10811
10960
|
prefill = void 0;
|
|
10812
10961
|
const ctxTok = estimateTranscriptTokens(face.transcript);
|
|
10813
10962
|
const ctxCap = face.options.maxTokens || 2e5;
|
|
10963
|
+
{
|
|
10964
|
+
const pct = Math.round(ctxTok / ctxCap * 100);
|
|
10965
|
+
const step = pct >= 90 ? 90 : pct >= 80 ? 80 : 0;
|
|
10966
|
+
if (step > ctxWarned) {
|
|
10967
|
+
ctxWarned = step;
|
|
10968
|
+
err(yellow(` \u26A0 context ${pct}% full`) + dim(" \u2014 /compact folds older messages (oldest are auto-trimmed at the cap)\n"));
|
|
10969
|
+
} else if (!step) ctxWarned = 0;
|
|
10970
|
+
}
|
|
10814
10971
|
const usd = session.meta.costUsd ?? 0;
|
|
10815
10972
|
const computeFooter = () => {
|
|
10816
10973
|
const parts = [];
|