agent.libx.js 0.94.10 → 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 CHANGED
@@ -109,6 +109,8 @@ agentx --resume <id> "…" # resume a specific session
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
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.
112
114
  - **Project instructions** — `./AGENTS.md` (or `CLAUDE.md`) auto-loads into every run; `/init` scaffolds one.
113
115
  - **Any provider** — set `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `GROQ_API_KEY`; choose with `-m provider/model`.
114
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
@@ -183,6 +183,10 @@ declare function readImageParts(cwd: string, line: string): ContentPart[];
183
183
  * path (so ordinary text pastes are unaffected). Paths with spaces are skipped — the `@\S+` pipeline can't carry them. */
184
184
  declare function pastePathClassifier(cwd: string): PasteClassifier;
185
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;
186
190
  declare function expandMentions(fs: IFilesystem, line: string): Promise<{
187
191
  text: string;
188
192
  loaded: string[];
@@ -212,4 +216,4 @@ declare function jsonResult(res: RunResult, session: SessionData): {
212
216
  */
213
217
  declare function readMultiline(readLine: (continuing: boolean) => Promise<string | null>): Promise<string | null>;
214
218
 
215
- export { type PermMode, appendMemoryNote, cacheMultipliers, condenseReplay, costOf, estimateTranscriptTokens, expandMentions, exportMarkdown, fmtUsd, formatHistory, formatStatus, formatTranscriptFull, 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
@@ -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
- if (useStream) {
2989
- const r = await o.ai.chat({ model: o.model, messages: sent, tools: wireTools, stream: true, signal: o.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
2990
- res = await this.consumeStream(r);
2991
- } else {
2992
- const r = await o.ai.chat({ model: o.model, messages: sent, tools: wireTools, stream: false, signal: o.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
2993
- res = r;
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)) return { out: p.dim(line), inFence: !inFence };
8331
- if (inFence) return { out: p.cyan(line), inFence };
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)"}`);
@@ -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();
@@ -9201,6 +9261,10 @@ function pastePathClassifier(cwd) {
9201
9261
  return { display: isImg ? "Image" : `File ${basename2(abs)}`, ref: "@" + abs };
9202
9262
  };
9203
9263
  }
9264
+ var mcpMentionResolver;
9265
+ function setMcpMentionResolver(fn) {
9266
+ mcpMentionResolver = fn;
9267
+ }
9204
9268
  async function expandMentions(fs, line) {
9205
9269
  const refs = [...line.matchAll(/(?:^|\s)@(\S+)/g)].map((m) => m[1].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
9206
9270
  if (!refs.length) return { text: line, loaded: [], missing: [] };
@@ -9208,6 +9272,18 @@ async function expandMentions(fs, line) {
9208
9272
  for (const ref of refs) {
9209
9273
  if (IMG_EXT[extname(ref).toLowerCase()]) continue;
9210
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
+ }
9211
9287
  try {
9212
9288
  if (await fs.exists(ref)) {
9213
9289
  blocks.push(`--- @${ref} ---
@@ -9762,6 +9838,16 @@ async function repl(args, ai, cfg, cwd) {
9762
9838
  forceQuit();
9763
9839
  });
9764
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
+ });
9765
9851
  const store = new SessionStore(cwd);
9766
9852
  let session = startSession(args, store, face, cwd);
9767
9853
  setTermTitle(`agentx \xB7 ${session.meta.title || cwd.split("/").pop() || "session"}`);
@@ -10497,6 +10583,71 @@ ${extra}` : body);
10497
10583
  const data = store.load(a[0]) ?? globalSessionLoad(a[0]);
10498
10584
  if (data) resumeInto(data);
10499
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
10500
10651
  `));
10501
10652
  }
10502
10653
  },
@@ -10812,7 +10963,7 @@ ${extra}` : body);
10812
10963
  const commandNames = () => [...Object.keys(builtins).filter((k) => k !== "quit"), ...cmds.map((c) => c.name), ...skills.map((s) => s.name)];
10813
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;
10814
10965
  const suggest = (lineToCursor) => {
10815
- const [hits, token] = completeLine(lineToCursor, { commands: commandNames(), listDir });
10966
+ const [hits, token] = completeLine(lineToCursor, { commands: commandNames(), listDir, bangHistory: history });
10816
10967
  return { hits, token, describe };
10817
10968
  };
10818
10969
  const editor = createLineEditor(process.stderr);
@@ -11079,6 +11230,11 @@ ${extra}` : body);
11079
11230
  if (r && r !== "off") parts.push(`reasoning:${r}`);
11080
11231
  if (verboseOutput) parts.push("verbose");
11081
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
+ }
11082
11238
  if (scheduler.size) parts.push(`\u23F0 ${scheduler.size} scheduled`);
11083
11239
  if (inputStash.length) parts.push(`${inputStash.length} stashed (\u2303S to pop)`);
11084
11240
  const taskLines = [];
@@ -11321,11 +11477,13 @@ export {
11321
11477
  formatStatus,
11322
11478
  formatTranscriptFull,
11323
11479
  jsonResult,
11480
+ mcpMentionResolver,
11324
11481
  parseArgs,
11325
11482
  pastePathClassifier,
11326
11483
  readImageParts,
11327
11484
  readMultiline,
11328
11485
  resolvePermMode,
11329
- runShellLine
11486
+ runShellLine,
11487
+ setMcpMentionResolver
11330
11488
  };
11331
11489
  //# sourceMappingURL=cli.js.map