agent.libx.js 0.94.9 → 0.94.11
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 +3 -0
- package/dist/cli.d.ts +13 -1
- package/dist/cli.js +272 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.js +18 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,9 @@ agentx --resume <id> "…" # resume a specific session
|
|
|
108
108
|
- **Diffs** — every `Edit`/`Write`/`MultiEdit` renders a colorized `+/-` diff (TTY-gated; plain when piped).
|
|
109
109
|
- **Slash commands** — `/help /tools /model /compact /memory /clear /sessions /resume /commands /init`; `/compact <focus>` preserves matching lines from the folded span; `/memory` opens the memory index in `$EDITOR`; user-defined `./.agent/commands/<name>.md` are invokable directly as `/<name>` (the same registry the model's `SlashCommand` tool uses).
|
|
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
|
+
- **`/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
|
+
- **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.
|
|
113
|
+
- **`/agents`** — list subagent types from `./.agent/agents` (description, model, tool scope); `/agents new <name>` scaffolds a frontmatter'd definition for the `Task` tool's `agentType`. **`!<partial>` + menu** completes from past `!` shell commands. **`@server:uri`** mentions inline an MCP resource body into the prompt. Transient network drops mid-step retry automatically (2 attempts, backoff) instead of failing the turn.
|
|
111
114
|
- **Project instructions** — `./AGENTS.md` (or `CLAUDE.md`) auto-loads into every run; `/init` scaffolds one.
|
|
112
115
|
- **Any provider** — set `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `GROQ_API_KEY`; choose with `-m provider/model`.
|
|
113
116
|
- **@-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).
|
package/dist/cli.d.ts
CHANGED
|
@@ -100,6 +100,14 @@ declare function condenseReplay(t: string): string;
|
|
|
100
100
|
* prompts, assistant narration, and a condensed line per tool action. Tool *results* are omitted
|
|
101
101
|
* (verbose) and inlined @-mention blocks are stripped from prompts. Pure → unit-testable. */
|
|
102
102
|
declare function formatHistory(messages: Message[]): string;
|
|
103
|
+
/** Render the FULL transcript — user prompts, assistant text, every tool call AND its complete
|
|
104
|
+
* result body — for `/transcript` (the past-turn equivalent of Ctrl-O live verbose; CC's
|
|
105
|
+
* transcript-viewer parity). `lastTurns` bounds output to the most recent N user turns.
|
|
106
|
+
* Each tool result is capped at `resultLines` lines with an elision marker. Pure → unit-testable. */
|
|
107
|
+
declare function formatTranscriptFull(messages: Message[], opts?: {
|
|
108
|
+
lastTurns?: number;
|
|
109
|
+
resultLines?: number;
|
|
110
|
+
}): string;
|
|
103
111
|
/** Render a transcript to portable Markdown (no ANSI) for `/export`. Same shape as formatHistory —
|
|
104
112
|
* user prompts + assistant narration + one quoted line per tool action; tool results omitted. Pure → unit-testable. */
|
|
105
113
|
declare function exportMarkdown(meta: {
|
|
@@ -175,6 +183,10 @@ declare function readImageParts(cwd: string, line: string): ContentPart[];
|
|
|
175
183
|
* path (so ordinary text pastes are unaffected). Paths with spaces are skipped — the `@\S+` pipeline can't carry them. */
|
|
176
184
|
declare function pastePathClassifier(cwd: string): PasteClassifier;
|
|
177
185
|
/** Inline `@path` file mentions: append referenced files' contents so the model sees them (missing → warn, leave as-is). */
|
|
186
|
+
/** Resolves an `@server:uri` MCP-resource mention to its text body; null = not an MCP ref (fall through
|
|
187
|
+
* to file resolution). Set by the REPL once servers are mounted; headless runs leave it unset. */
|
|
188
|
+
declare let mcpMentionResolver: ((ref: string) => Promise<string | null>) | undefined;
|
|
189
|
+
declare function setMcpMentionResolver(fn: typeof mcpMentionResolver): void;
|
|
178
190
|
declare function expandMentions(fs: IFilesystem, line: string): Promise<{
|
|
179
191
|
text: string;
|
|
180
192
|
loaded: string[];
|
|
@@ -204,4 +216,4 @@ declare function jsonResult(res: RunResult, session: SessionData): {
|
|
|
204
216
|
*/
|
|
205
217
|
declare function readMultiline(readLine: (continuing: boolean) => Promise<string | null>): Promise<string | null>;
|
|
206
218
|
|
|
207
|
-
export { type PermMode, appendMemoryNote, cacheMultipliers, condenseReplay, costOf, estimateTranscriptTokens, expandMentions, exportMarkdown, fmtUsd, formatHistory, formatStatus, jsonResult, parseArgs, pastePathClassifier, readImageParts, readMultiline, resolvePermMode, runShellLine };
|
|
219
|
+
export { type PermMode, appendMemoryNote, cacheMultipliers, condenseReplay, costOf, estimateTranscriptTokens, expandMentions, exportMarkdown, fmtUsd, formatHistory, formatStatus, formatTranscriptFull, jsonResult, mcpMentionResolver, parseArgs, pastePathClassifier, readImageParts, readMultiline, resolvePermMode, runShellLine, setMcpMentionResolver };
|
package/dist/cli.js
CHANGED
|
@@ -1619,7 +1619,7 @@ var init_tools_shell = __esm({
|
|
|
1619
1619
|
|
|
1620
1620
|
// cli/cli.ts
|
|
1621
1621
|
import { createInterface } from "readline/promises";
|
|
1622
|
-
import { existsSync as existsSync8, readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
1622
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
1623
1623
|
import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
|
|
1624
1624
|
|
|
1625
1625
|
// cli/clipboard.ts
|
|
@@ -2985,12 +2985,24 @@ var Agent = class _Agent {
|
|
|
2985
2985
|
...o.providerOptions || cursorPo ? { providerOptions: { ...frag.providerOptions, ...o.providerOptions, ...cursorPo } } : {}
|
|
2986
2986
|
};
|
|
2987
2987
|
try {
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2988
|
+
for (let attempt = 0; ; attempt++) {
|
|
2989
|
+
try {
|
|
2990
|
+
if (useStream) {
|
|
2991
|
+
const r = await o.ai.chat({ model: o.model, messages: sent, tools: wireTools, stream: true, signal: o.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
|
|
2992
|
+
res = await this.consumeStream(r);
|
|
2993
|
+
} else {
|
|
2994
|
+
const r = await o.ai.chat({ model: o.model, messages: sent, tools: wireTools, stream: false, signal: o.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
|
|
2995
|
+
res = r;
|
|
2996
|
+
}
|
|
2997
|
+
break;
|
|
2998
|
+
} catch (err2) {
|
|
2999
|
+
const transient = !o.signal?.aborted && !isAbortError(err2) && !err2?.statusCode && attempt < 2 && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|socket hang up|fetch failed|network|terminated|UND_ERR/i.test(String(err2?.message ?? err2?.code ?? err2));
|
|
3000
|
+
if (!transient) throw err2;
|
|
3001
|
+
const waitMs = 1e3 * (attempt + 1);
|
|
3002
|
+
log3.warn(`network drop mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
3003
|
+
o.host?.notify?.({ kind: "retry", message: `connection dropped \u2014 retrying step (#${attempt + 1})` });
|
|
3004
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
3005
|
+
}
|
|
2994
3006
|
}
|
|
2995
3007
|
} catch (err2) {
|
|
2996
3008
|
if (err2?.code === "budget") return kill("budget");
|
|
@@ -7135,6 +7147,12 @@ function completeLine(line, ctx) {
|
|
|
7135
7147
|
const hits = rank(ctx.commands.filter((c) => fuzzy(want, c)), want).map((c) => "/" + c);
|
|
7136
7148
|
return [hits, token];
|
|
7137
7149
|
}
|
|
7150
|
+
if (line.startsWith("!") && line.length > 1 && ctx.bangHistory?.length) {
|
|
7151
|
+
const want = line.slice(1);
|
|
7152
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7153
|
+
const hits = ctx.bangHistory.filter((h) => h.startsWith("!") && h.length > 1 && fuzzy(want, h.slice(1)) && !seen.has(h) && !!seen.add(h));
|
|
7154
|
+
return [rank(hits, line), line];
|
|
7155
|
+
}
|
|
7138
7156
|
if (token.startsWith("@")) return [completePath(ctx.listDir, token.slice(1)), token];
|
|
7139
7157
|
return [[], line];
|
|
7140
7158
|
}
|
|
@@ -8307,6 +8325,39 @@ function readPlainLine() {
|
|
|
8307
8325
|
|
|
8308
8326
|
// cli/markdown.ts
|
|
8309
8327
|
var vis = (s) => needsBidi(s) ? bidiLine(s).visual : s;
|
|
8328
|
+
var C_KW = new Set("const let var function return if else for while do switch case break continue class new async await try catch finally throw import export from default type interface extends implements public private protected readonly static void null undefined true false this super yield typeof instanceof in of enum namespace struct fn pub mut match impl use go defer chan func package var range nil".split(" "));
|
|
8329
|
+
var PY_KW = new Set("def return if elif else for while class import from as with try except finally raise lambda pass break continue global nonlocal yield async await None True False not and or in is del assert print self".split(" "));
|
|
8330
|
+
var SH_KW = new Set("if then else elif fi for while do done case esac function local export return echo exit set source in".split(" "));
|
|
8331
|
+
var HASH_COMMENT = /^(sh|bash|zsh|shell|py|python|rb|ruby|yaml|yml|toml|ini|make|makefile|dockerfile|r)$/;
|
|
8332
|
+
function kwFor(lang) {
|
|
8333
|
+
if (!lang) return null;
|
|
8334
|
+
const l = lang.toLowerCase();
|
|
8335
|
+
if (/^(js|jsx|ts|tsx|javascript|typescript|java|c|h|cpp|cc|cs|go|rust|rs|swift|kotlin|kt|php|scala|dart)$/.test(l)) return C_KW;
|
|
8336
|
+
if (/^(py|python)$/.test(l)) return PY_KW;
|
|
8337
|
+
if (/^(sh|bash|zsh|shell|console)$/.test(l)) return SH_KW;
|
|
8338
|
+
if (/^(json|yaml|yml|toml|sql|html|xml|css)$/.test(l)) return /* @__PURE__ */ new Set();
|
|
8339
|
+
return null;
|
|
8340
|
+
}
|
|
8341
|
+
function highlightCode(line, lang, p) {
|
|
8342
|
+
const kws = kwFor(lang);
|
|
8343
|
+
if (!kws) return p.cyan(line);
|
|
8344
|
+
const cm = (HASH_COMMENT.test(lang.toLowerCase()) ? /(^|\s)#.*$/ : /\/\/.*$|\/\*.*$/).exec(line);
|
|
8345
|
+
let code = line, comment = "";
|
|
8346
|
+
if (cm) {
|
|
8347
|
+
const at = cm.index + (cm[1]?.length ?? 0);
|
|
8348
|
+
comment = line.slice(at);
|
|
8349
|
+
code = line.slice(0, at);
|
|
8350
|
+
}
|
|
8351
|
+
const str = p.green ?? p.cyan;
|
|
8352
|
+
const re = /("(?:[^"\\]|\\.)*"?|'(?:[^'\\]|\\.)*'?|`[^`]*`?)|\b(\d+(?:\.\d+)?)\b|\b([A-Za-z_]\w*)\b/g;
|
|
8353
|
+
const out = code.replace(re, (m, s, num, word) => {
|
|
8354
|
+
if (s) return str(s);
|
|
8355
|
+
if (num) return p.cyan(num);
|
|
8356
|
+
if (word && kws.has(word)) return p.bold(word);
|
|
8357
|
+
return m;
|
|
8358
|
+
});
|
|
8359
|
+
return out + (comment ? p.dim(comment) : "");
|
|
8360
|
+
}
|
|
8310
8361
|
function displayText(s) {
|
|
8311
8362
|
return s.replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/~~([^~]+)~~/g, "$1").replace(/(?<![\w*])[*_]([^*_\s][^*_]*?)[*_](?![\w*])/g, "$1");
|
|
8312
8363
|
}
|
|
@@ -8326,9 +8377,12 @@ function mdInline(line, p) {
|
|
|
8326
8377
|
return (p.italic ?? ((s) => s))(italic2.slice(1, -1));
|
|
8327
8378
|
});
|
|
8328
8379
|
}
|
|
8329
|
-
function renderMdLine(line, inFence, p, cols = 80) {
|
|
8330
|
-
if (/^\s*```/.test(line))
|
|
8331
|
-
|
|
8380
|
+
function renderMdLine(line, inFence, p, cols = 80, fenceLang) {
|
|
8381
|
+
if (/^\s*```/.test(line)) {
|
|
8382
|
+
const lang = inFence ? void 0 : /^\s*```\s*(\w+)/.exec(line)?.[1];
|
|
8383
|
+
return { out: p.dim(line), inFence: !inFence, fenceLang: lang };
|
|
8384
|
+
}
|
|
8385
|
+
if (inFence) return { out: highlightCode(line, fenceLang, p), inFence, fenceLang };
|
|
8332
8386
|
if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) return { out: p.dim("\u2500".repeat(cols)), inFence };
|
|
8333
8387
|
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
8334
8388
|
if (h) return { out: p.bold(vis(h[2])), inFence };
|
|
@@ -8352,6 +8406,8 @@ var MarkdownStream = class {
|
|
|
8352
8406
|
buf = "";
|
|
8353
8407
|
// the current (uncommitted) logical line's text
|
|
8354
8408
|
inFence = false;
|
|
8409
|
+
fenceLang;
|
|
8410
|
+
// language tag of the open fence (drives syntax highlighting)
|
|
8355
8411
|
lineRows = 0;
|
|
8356
8412
|
// physical rows the in-progress line occupied last draw (for the next clear)
|
|
8357
8413
|
tableBuf = [];
|
|
@@ -8400,14 +8456,15 @@ var MarkdownStream = class {
|
|
|
8400
8456
|
continue;
|
|
8401
8457
|
}
|
|
8402
8458
|
if (this.tableBuf.length) out += this.flushTable();
|
|
8403
|
-
const r = renderMdLine(line, this.inFence, this.p, this.cols);
|
|
8459
|
+
const r = renderMdLine(line, this.inFence, this.p, this.cols, this.fenceLang);
|
|
8404
8460
|
this.inFence = r.inFence;
|
|
8461
|
+
this.fenceLang = r.fenceLang;
|
|
8405
8462
|
out += r.out + "\r\n";
|
|
8406
8463
|
}
|
|
8407
8464
|
const tailIsTableRow = this.tableBuf.length > 0 && !this.inFence && /^\s*\|/.test(this.buf);
|
|
8408
8465
|
if (this.buf && !tailIsTableRow) {
|
|
8409
8466
|
if (this.tableBuf.length) out += this.flushTable();
|
|
8410
|
-
out += renderMdLine(this.buf, this.inFence, this.p, this.cols).out;
|
|
8467
|
+
out += renderMdLine(this.buf, this.inFence, this.p, this.cols, this.fenceLang).out;
|
|
8411
8468
|
this.lineRows = Math.max(1, Math.ceil(this.buf.length / Math.max(1, this.cols)));
|
|
8412
8469
|
}
|
|
8413
8470
|
return out;
|
|
@@ -8422,6 +8479,7 @@ var MarkdownStream = class {
|
|
|
8422
8479
|
out += this.tableBuf.length ? this.flushTable() : "";
|
|
8423
8480
|
this.buf = "";
|
|
8424
8481
|
this.inFence = false;
|
|
8482
|
+
this.fenceLang = void 0;
|
|
8425
8483
|
this.lineRows = 0;
|
|
8426
8484
|
return out;
|
|
8427
8485
|
}
|
|
@@ -8485,6 +8543,7 @@ var activeTurn = null;
|
|
|
8485
8543
|
var exitRequested = false;
|
|
8486
8544
|
var inputStash = [];
|
|
8487
8545
|
var stashBuf = "";
|
|
8546
|
+
var latestTodos = [];
|
|
8488
8547
|
function numFlag(raw, flag) {
|
|
8489
8548
|
const n = Number(raw);
|
|
8490
8549
|
if (!Number.isFinite(n) || n < 0) throw new Error(`invalid ${flag}: ${raw ?? "(missing value)"}`);
|
|
@@ -8630,7 +8689,7 @@ Project instructions: ./AGENTS.md or ./CLAUDE.md are auto-loaded (scaffold with
|
|
|
8630
8689
|
Auto-loaded from ./.agent/: commands/, skills/, memory/, agents/.
|
|
8631
8690
|
|
|
8632
8691
|
REPL shortcuts: !<cmd> runs a shell command inline \xB7 #<note> saves a memory \xB7 @path inlines a file
|
|
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)
|
|
8692
|
+
REPL slash commands: /help /version /tools /permissions /status /cost /context /transcript /doctor /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)
|
|
8634
8693
|
REPL completion: type / (commands+skills) or @ (files) for a LIVE menu \u2014 \u2191/\u2193 select, \u23CE/Tab accept, Esc dismiss.
|
|
8635
8694
|
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.
|
|
8636
8695
|
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.
|
|
@@ -8679,7 +8738,7 @@ function apiKeysFromEnv() {
|
|
|
8679
8738
|
function makeHost(format = "text", opts) {
|
|
8680
8739
|
const streamJson = format === "stream-json";
|
|
8681
8740
|
const cleanStdout = format === "json";
|
|
8682
|
-
const md = format === "text" && useColor && opts?.stream ? new MarkdownStream({ bold, dim, cyan, italic, strike, link }, process.stdout.columns ?? 80) : null;
|
|
8741
|
+
const md = format === "text" && useColor && opts?.stream ? new MarkdownStream({ bold, dim, cyan, italic, strike, green, link }, process.stdout.columns ?? 80) : null;
|
|
8683
8742
|
if (md) process.on("SIGWINCH", () => md.resize(process.stdout.columns ?? 80));
|
|
8684
8743
|
const flushText = () => {
|
|
8685
8744
|
if (md && md.pending()) process.stdout.write(md.flush());
|
|
@@ -8774,6 +8833,7 @@ function displayHooks(fs, opts) {
|
|
|
8774
8833
|
};
|
|
8775
8834
|
return {
|
|
8776
8835
|
async preToolUse(call) {
|
|
8836
|
+
if (call.name === "TodoWrite" && Array.isArray(call.args?.todos)) latestTodos = call.args.todos;
|
|
8777
8837
|
if (!on()) return;
|
|
8778
8838
|
if (bg) err("\r\x1B[0J");
|
|
8779
8839
|
else spinner.stop();
|
|
@@ -8860,6 +8920,44 @@ function formatHistory(messages) {
|
|
|
8860
8920
|
out.push(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
8861
8921
|
return out.join("");
|
|
8862
8922
|
}
|
|
8923
|
+
function formatTranscriptFull(messages, opts) {
|
|
8924
|
+
const cap = opts?.resultLines ?? 200;
|
|
8925
|
+
let shown = messages.filter((m) => m.role !== "system");
|
|
8926
|
+
if (opts?.lastTurns) {
|
|
8927
|
+
const userIdxs = shown.map((m, i) => m.role === "user" ? i : -1).filter((i) => i >= 0);
|
|
8928
|
+
if (userIdxs.length > opts.lastTurns) shown = shown.slice(userIdxs[userIdxs.length - opts.lastTurns]);
|
|
8929
|
+
}
|
|
8930
|
+
if (!shown.length) return dim(" (empty transcript)\n");
|
|
8931
|
+
const results = /* @__PURE__ */ new Map();
|
|
8932
|
+
for (const m of shown) if (m.role === "tool" && m.tool_call_id) results.set(m.tool_call_id, m);
|
|
8933
|
+
const clip = (s) => {
|
|
8934
|
+
const lines = s.split("\n");
|
|
8935
|
+
return lines.length > cap ? lines.slice(0, cap).join("\n") + `
|
|
8936
|
+
\u2026 (+${lines.length - cap} more lines)` : s;
|
|
8937
|
+
};
|
|
8938
|
+
const out = [dim("\n \u2500\u2500 transcript \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n")];
|
|
8939
|
+
for (const m of shown) {
|
|
8940
|
+
if (m.role === "user") {
|
|
8941
|
+
const t = contentText(m.content).trim();
|
|
8942
|
+
if (t) out.push("\n" + bold(cyan(" \u203A ")) + t.replace(/\n/g, "\n ") + "\n");
|
|
8943
|
+
} else if (m.role === "assistant") {
|
|
8944
|
+
const at = contentText(m.content).trim();
|
|
8945
|
+
if (at) out.push(" " + at.replace(/\n/g, "\n ") + "\n");
|
|
8946
|
+
for (const tc of m.tool_calls ?? []) {
|
|
8947
|
+
let args = {};
|
|
8948
|
+
try {
|
|
8949
|
+
args = JSON.parse(tc.function.arguments || "{}");
|
|
8950
|
+
} catch {
|
|
8951
|
+
}
|
|
8952
|
+
out.push(cyan(" \u2699 ") + tc.function.name + dim(" " + summarizeCall(tc.function.name, args)) + "\n");
|
|
8953
|
+
const r = results.get(tc.id);
|
|
8954
|
+
if (r) out.push(dim(" " + clip(contentText(r.content).trimEnd()).replace(/\n/g, "\n ")) + "\n");
|
|
8955
|
+
}
|
|
8956
|
+
}
|
|
8957
|
+
}
|
|
8958
|
+
out.push(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
8959
|
+
return out.join("");
|
|
8960
|
+
}
|
|
8863
8961
|
function exportMarkdown(meta, messages) {
|
|
8864
8962
|
const shown = messages.filter((m) => m.role !== "system");
|
|
8865
8963
|
const stamp = meta.created ? new Date(meta.created).toISOString() : "";
|
|
@@ -9163,6 +9261,10 @@ function pastePathClassifier(cwd) {
|
|
|
9163
9261
|
return { display: isImg ? "Image" : `File ${basename2(abs)}`, ref: "@" + abs };
|
|
9164
9262
|
};
|
|
9165
9263
|
}
|
|
9264
|
+
var mcpMentionResolver;
|
|
9265
|
+
function setMcpMentionResolver(fn) {
|
|
9266
|
+
mcpMentionResolver = fn;
|
|
9267
|
+
}
|
|
9166
9268
|
async function expandMentions(fs, line) {
|
|
9167
9269
|
const refs = [...line.matchAll(/(?:^|\s)@(\S+)/g)].map((m) => m[1].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
9168
9270
|
if (!refs.length) return { text: line, loaded: [], missing: [] };
|
|
@@ -9170,6 +9272,18 @@ async function expandMentions(fs, line) {
|
|
|
9170
9272
|
for (const ref of refs) {
|
|
9171
9273
|
if (IMG_EXT[extname(ref).toLowerCase()]) continue;
|
|
9172
9274
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
9275
|
+
if (ref.includes(":") && mcpMentionResolver) {
|
|
9276
|
+
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
9277
|
+
log17.debug("mcp mention resolve failed", e);
|
|
9278
|
+
return null;
|
|
9279
|
+
});
|
|
9280
|
+
if (body != null) {
|
|
9281
|
+
blocks.push(`--- @${ref} ---
|
|
9282
|
+
${body}`);
|
|
9283
|
+
loaded.push(ref);
|
|
9284
|
+
continue;
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9173
9287
|
try {
|
|
9174
9288
|
if (await fs.exists(ref)) {
|
|
9175
9289
|
blocks.push(`--- @${ref} ---
|
|
@@ -9724,6 +9838,16 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9724
9838
|
forceQuit();
|
|
9725
9839
|
});
|
|
9726
9840
|
installCancelGuards(mounted);
|
|
9841
|
+
setMcpMentionResolver(async (ref) => {
|
|
9842
|
+
const i = ref.indexOf(":");
|
|
9843
|
+
if (i <= 0) return null;
|
|
9844
|
+
const m = mounted.find((x) => x.name === ref.slice(0, i));
|
|
9845
|
+
if (!m) return null;
|
|
9846
|
+
const r = await m.client.readResource(ref.slice(i + 1));
|
|
9847
|
+
const parts = Array.isArray(r?.contents) ? r.contents : [];
|
|
9848
|
+
const texts = parts.map((c) => c?.text ?? (c?.blob ? `[binary ${c.mimeType ?? "blob"}]` : "")).filter(Boolean);
|
|
9849
|
+
return texts.length ? texts.join("\n") : JSON.stringify(r);
|
|
9850
|
+
});
|
|
9727
9851
|
const store = new SessionStore(cwd);
|
|
9728
9852
|
let session = startSession(args, store, face, cwd);
|
|
9729
9853
|
setTermTitle(`agentx \xB7 ${session.meta.title || cwd.split("/").pop() || "session"}`);
|
|
@@ -10045,6 +10169,65 @@ ${extra}` : body);
|
|
|
10045
10169
|
`));
|
|
10046
10170
|
}
|
|
10047
10171
|
},
|
|
10172
|
+
transcript: {
|
|
10173
|
+
desc: "full session transcript incl. complete tool results \u2014 /transcript [n] (last n turns), paged via less",
|
|
10174
|
+
run: async (a) => {
|
|
10175
|
+
const n = a[0] ? Math.max(1, Number(a[0]) || 1) : void 0;
|
|
10176
|
+
const text = formatTranscriptFull(face.transcript, { lastTurns: n });
|
|
10177
|
+
if (tty && text.split("\n").length > (process.stderr.rows ?? 40)) {
|
|
10178
|
+
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
10179
|
+
if (wasRaw) process.stdin.setRawMode(false);
|
|
10180
|
+
try {
|
|
10181
|
+
const { spawnSync: spawnSync3 } = await import("child_process");
|
|
10182
|
+
const r = spawnSync3("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
|
|
10183
|
+
if (r.error) err(text);
|
|
10184
|
+
} finally {
|
|
10185
|
+
if (wasRaw) process.stdin.setRawMode(true);
|
|
10186
|
+
}
|
|
10187
|
+
} else err(text);
|
|
10188
|
+
}
|
|
10189
|
+
},
|
|
10190
|
+
doctor: {
|
|
10191
|
+
desc: "environment sanity check \u2014 keys, config, sessions, memory, MCP, model pricing",
|
|
10192
|
+
run: async () => {
|
|
10193
|
+
const ok = (s) => err(green(" \u2713 ") + s + "\n");
|
|
10194
|
+
const warn = (s) => err(yellow(" \u26A0 ") + s + "\n");
|
|
10195
|
+
const bad = (s) => err(red(" \u2717 ") + s + "\n");
|
|
10196
|
+
err(dim(` agentx v${VERSION} \xB7 bun ${process.versions.bun ?? "?"} \xB7 ${process.platform}
|
|
10197
|
+
`));
|
|
10198
|
+
const keys = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY", "GROQ_API_KEY"].filter((k) => process.env[k]);
|
|
10199
|
+
keys.length ? ok(`provider keys: ${keys.join(", ")}`) : bad("no provider keys set (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY)");
|
|
10200
|
+
const info = getModelInfo(work.model);
|
|
10201
|
+
info?.pricing ? ok(`model ${work.model} \u2014 priced (${info.pricing.inputCostPer1K}/${info.pricing.outputCostPer1K} per 1k in/out)`) : warn(`model ${work.model} \u2014 no pricing in the catalog (costs will show ~$0; verify the id)`);
|
|
10202
|
+
const cfgFiles = ["ts", "js", "json"].flatMap((e) => [`${cwd}/.agent/config.${e}`, `${homedir6()}/.agent/config.${e}`]).filter((p) => existsSync8(p));
|
|
10203
|
+
cfgFiles.length ? ok(`config: ${cfgFiles.join(", ")}`) : warn("no .agent/config.* found (project or ~) \u2014 running on defaults");
|
|
10204
|
+
try {
|
|
10205
|
+
const probe = `${cwd}/.agent/sessions/.doctor-probe`;
|
|
10206
|
+
mkdirSync7(`${cwd}/.agent/sessions`, { recursive: true });
|
|
10207
|
+
writeFileSync6(probe, "ok");
|
|
10208
|
+
unlinkSync2(probe);
|
|
10209
|
+
ok(`session store writable (${cwd}/.agent/sessions)`);
|
|
10210
|
+
} catch (e) {
|
|
10211
|
+
bad(`session store not writable: ${e?.message ?? e}`);
|
|
10212
|
+
}
|
|
10213
|
+
const memDir = primaryMemDir(face.options.memoryDir, adot("memory"));
|
|
10214
|
+
try {
|
|
10215
|
+
const idx = await face.options.fs.readFile(`${memDir}/MEMORY.md`);
|
|
10216
|
+
ok(`memory: ${memDir} (${idx.split("\n").filter((l) => l.startsWith("- ")).length} pointer(s))`);
|
|
10217
|
+
} catch {
|
|
10218
|
+
warn(`memory: ${memDir} \u2014 no MEMORY.md yet (save one with #<note>)`);
|
|
10219
|
+
}
|
|
10220
|
+
const conf = Object.keys(cfg.mcpServers ?? {});
|
|
10221
|
+
const up = new Set(mounted.map((m) => m.name));
|
|
10222
|
+
const down = conf.filter((n) => !up.has(n) && !cfg.mcpServers?.[n]?.disabled);
|
|
10223
|
+
if (!conf.length && !mounted.length) ok("mcp: none configured");
|
|
10224
|
+
else if (down.length) bad(`mcp: ${down.length} configured server(s) not mounted: ${down.join(", ")} (try /mcp reconnect <name>)`);
|
|
10225
|
+
else ok(`mcp: ${mounted.length} server(s) mounted${conf.length ? ` (${conf.length} configured)` : ""}`);
|
|
10226
|
+
ok(`fs: ${args.vfs ? "sandbox (VFS)" : args.boddb ? `boddb (${args.boddb})` : "disk"} \xB7 checkpoints: ${args.vfs || args.boddb ? "in-memory/db" : "git-backed"}`);
|
|
10227
|
+
const hookCount = Object.values(cfg.hooks ?? {}).reduce((n, v) => n + (Array.isArray(v) ? v.length : 0), 0);
|
|
10228
|
+
if (hookCount) ok(`hooks: ${hookCount} configured`);
|
|
10229
|
+
}
|
|
10230
|
+
},
|
|
10048
10231
|
cwd: {
|
|
10049
10232
|
desc: "print the working directory (to switch, relaunch with -C <dir>)",
|
|
10050
10233
|
run: (a) => {
|
|
@@ -10400,6 +10583,71 @@ ${extra}` : body);
|
|
|
10400
10583
|
const data = store.load(a[0]) ?? globalSessionLoad(a[0]);
|
|
10401
10584
|
if (data) resumeInto(data);
|
|
10402
10585
|
else err(red(` no such session
|
|
10586
|
+
`));
|
|
10587
|
+
}
|
|
10588
|
+
},
|
|
10589
|
+
agents: {
|
|
10590
|
+
desc: "list subagent types (./.agent/agents) \u2014 /agents new <name> scaffolds one",
|
|
10591
|
+
run: async (a) => {
|
|
10592
|
+
const fs2 = face.options.fs;
|
|
10593
|
+
if (a[0] === "new") {
|
|
10594
|
+
let name = a[1];
|
|
10595
|
+
if (!name) {
|
|
10596
|
+
const io = createInterface({ input: process.stdin, output: process.stderr });
|
|
10597
|
+
try {
|
|
10598
|
+
name = (await io.question(yellow(" agent name: "))).trim();
|
|
10599
|
+
} finally {
|
|
10600
|
+
io.close();
|
|
10601
|
+
}
|
|
10602
|
+
}
|
|
10603
|
+
name = (name ?? "").replace(/[^A-Za-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
10604
|
+
if (!name) {
|
|
10605
|
+
err(yellow(" usage: /agents new <name>\n"));
|
|
10606
|
+
return;
|
|
10607
|
+
}
|
|
10608
|
+
const dir = adot("agents");
|
|
10609
|
+
const p = `${dir}/${name}.md`;
|
|
10610
|
+
if (await fs2.exists(p)) {
|
|
10611
|
+
err(yellow(` ${p} already exists
|
|
10612
|
+
`));
|
|
10613
|
+
return;
|
|
10614
|
+
}
|
|
10615
|
+
await mkdirp(fs2, dir);
|
|
10616
|
+
await fs2.writeFile(p, [
|
|
10617
|
+
"---",
|
|
10618
|
+
`description: What "${name}" is for \u2014 the Task tool picks agents by this line`,
|
|
10619
|
+
"# model: anthropic/claude-haiku-4-5 # optional per-agent model override",
|
|
10620
|
+
"# tools: Read, Grep, Glob # optional tool allowlist (omit = all tools)",
|
|
10621
|
+
"---",
|
|
10622
|
+
"",
|
|
10623
|
+
`You are the "${name}" subagent.`,
|
|
10624
|
+
"",
|
|
10625
|
+
"Describe the persona, scope, and output contract here \u2014 this body becomes the",
|
|
10626
|
+
`child agent's system prompt when a Task is delegated with agentType: "` + name + '".',
|
|
10627
|
+
""
|
|
10628
|
+
].join("\n"));
|
|
10629
|
+
err(green(` \u2713 ${p}`) + dim(` \u2014 used via Task(agentType:"${name}")${args.subagents ? "" : " (enable with --subagents)"}
|
|
10630
|
+
`));
|
|
10631
|
+
return;
|
|
10632
|
+
}
|
|
10633
|
+
const seen = /* @__PURE__ */ new Map();
|
|
10634
|
+
for (const d of adots("agents")) {
|
|
10635
|
+
try {
|
|
10636
|
+
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
10637
|
+
} catch (e) {
|
|
10638
|
+
log17.debug(`loadAgents(${d}) failed`, e);
|
|
10639
|
+
}
|
|
10640
|
+
}
|
|
10641
|
+
if (!seen.size) {
|
|
10642
|
+
err(dim(" (no subagents defined \u2014 scaffold one with /agents new <name>)\n"));
|
|
10643
|
+
return;
|
|
10644
|
+
}
|
|
10645
|
+
for (const { def, from } of seen.values()) {
|
|
10646
|
+
const extras = [def.model, def.tools?.length ? `tools: ${def.tools.join(",")}` : ""].filter(Boolean).join(" \xB7 ");
|
|
10647
|
+
err(` ${cyan(def.name)} ${dim(`\u2014 ${def.description || "(no description)"}${extras ? ` (${extras})` : ""} \xB7 ${from}`)}
|
|
10648
|
+
`);
|
|
10649
|
+
}
|
|
10650
|
+
err(dim(` delegated via the Task tool (agentType)${args.subagents ? "" : " \u2014 enable with --subagents"} \xB7 /agents new <name> to scaffold
|
|
10403
10651
|
`));
|
|
10404
10652
|
}
|
|
10405
10653
|
},
|
|
@@ -10715,7 +10963,7 @@ ${extra}` : body);
|
|
|
10715
10963
|
const commandNames = () => [...Object.keys(builtins).filter((k) => k !== "quit"), ...cmds.map((c) => c.name), ...skills.map((s) => s.name)];
|
|
10716
10964
|
const describe = (hit) => hit.startsWith("/") ? builtins[hit.slice(1)]?.desc ?? cmds.find((c) => c.name === hit.slice(1))?.description ?? skills.find((s) => s.name === hit.slice(1))?.description : void 0;
|
|
10717
10965
|
const suggest = (lineToCursor) => {
|
|
10718
|
-
const [hits, token] = completeLine(lineToCursor, { commands: commandNames(), listDir });
|
|
10966
|
+
const [hits, token] = completeLine(lineToCursor, { commands: commandNames(), listDir, bangHistory: history });
|
|
10719
10967
|
return { hits, token, describe };
|
|
10720
10968
|
};
|
|
10721
10969
|
const editor = createLineEditor(process.stderr);
|
|
@@ -10982,6 +11230,11 @@ ${extra}` : body);
|
|
|
10982
11230
|
if (r && r !== "off") parts.push(`reasoning:${r}`);
|
|
10983
11231
|
if (verboseOutput) parts.push("verbose");
|
|
10984
11232
|
if (goalCondition) parts.push(`\u25CE goal (${goalTurns} turns)`);
|
|
11233
|
+
if (latestTodos.length) {
|
|
11234
|
+
const done = latestTodos.filter((t) => t.status === "completed").length;
|
|
11235
|
+
const cur = latestTodos.find((t) => t.status === "in_progress")?.content;
|
|
11236
|
+
if (done < latestTodos.length) parts.push(`\u2611 ${done}/${latestTodos.length}${cur ? ` \xB7 ${cur.slice(0, 48)}` : ""}`);
|
|
11237
|
+
}
|
|
10985
11238
|
if (scheduler.size) parts.push(`\u23F0 ${scheduler.size} scheduled`);
|
|
10986
11239
|
if (inputStash.length) parts.push(`${inputStash.length} stashed (\u2303S to pop)`);
|
|
10987
11240
|
const taskLines = [];
|
|
@@ -11222,12 +11475,15 @@ export {
|
|
|
11222
11475
|
fmtUsd,
|
|
11223
11476
|
formatHistory,
|
|
11224
11477
|
formatStatus,
|
|
11478
|
+
formatTranscriptFull,
|
|
11225
11479
|
jsonResult,
|
|
11480
|
+
mcpMentionResolver,
|
|
11226
11481
|
parseArgs,
|
|
11227
11482
|
pastePathClassifier,
|
|
11228
11483
|
readImageParts,
|
|
11229
11484
|
readMultiline,
|
|
11230
11485
|
resolvePermMode,
|
|
11231
|
-
runShellLine
|
|
11486
|
+
runShellLine,
|
|
11487
|
+
setMcpMentionResolver
|
|
11232
11488
|
};
|
|
11233
11489
|
//# sourceMappingURL=cli.js.map
|