@livx.cc/agentx 0.99.84 → 0.99.85

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
@@ -64,7 +64,7 @@ await fs.createDir('/src');
64
64
  await fs.writeFile('/src/x.ts', 'export const add = (a,b) => a - b;\n');
65
65
 
66
66
  const ai = new AIClient({ apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY } });
67
- const res = await new Agent({ ai, fs, model: 'anthropic/claude-sonnet-4-6' })
67
+ const res = await new Agent({ ai, fs, model: 'anthropic/sonnet' })
68
68
  .run('Fix the add bug in /src/x.ts');
69
69
 
70
70
  console.log(res.finishReason, await fs.readFile('/src/x.ts'));
@@ -77,7 +77,7 @@ console.log(res.finishReason, await fs.readFile('/src/x.ts'));
77
77
  - **`Edit`** — exact unique-substring replace, with a read-before-edit staleness guard.
78
78
  - **`Grep`/`Glob`/`Write`/`MultiEdit`** — structured, typed results straight from the VFS (no `bash` parsing). The selectable tool set the self-evolution loop mutates over.
79
79
  - **`TodoWrite`** — a planning scratchpad; **`Task`** — spawn a depth-limited child agent over the VFS (`subagents: true`); **`SlashCommand`** — reusable prompt templates from `<dir>/*.md` (`commandsDir`); plus a real **MCP client** (`src/mcp.client.ts`, node-only — stdio/HTTP JSON-RPC handshake + discovery) that feeds the edge-safe **MCP adapter** (`mcpToolsToAgentTools`), so any MCP server's tools become agent tools.
80
- - **`WebFetch`/`WebSearch`** — fetch a URL as readable text, or search the web. **Keyless by default** (WebSearch uses DuckDuckGo; `provider: 'auto'` upgrades by key presence — Firecrawl `FIRECRAWL_API_KEY` > Tavily `TAVILY_API_KEY`) and **auto-enabled in the CLI**. Factory-built with an injectable `fetch`, so they stay edge-portable and testable. (In the library they're opt-in by name: `tools: [...,'WebSearch']`.) **`WebSearchAnthropic`** is a separate provider-pinned tool — Anthropic's native search relayed through a cheap model (`claude-haiku-4-5`); CLI-default-on when an Anthropic key resolves, API-billed per call, opt out via `anthropicWebSearch: false`.
80
+ - **`WebFetch`/`WebSearch`** — fetch a URL as readable text, or search the web. **Keyless by default** (WebSearch uses DuckDuckGo; `provider: 'auto'` upgrades by key presence — Firecrawl `FIRECRAWL_API_KEY` > Tavily `TAVILY_API_KEY`) and **auto-enabled in the CLI**. Factory-built with an injectable `fetch`, so they stay edge-portable and testable. (In the library they're opt-in by name: `tools: [...,'WebSearch']`.) **`WebSearchAnthropic`** is a separate provider-pinned tool — Anthropic's native search relayed through a cheap model (the newest Haiku); CLI-default-on when an Anthropic key resolves, API-billed per call, opt out via `anthropicWebSearch: false`.
81
81
  - **Oversized-output pagination** — any tool result over a byte ceiling (`maxToolResultBytes`, default 60k) is cropped to page 1 with a marker (refine the query / read further), so one big `Grep`/`Read`/MCP/web result can't blow the context window. In the CLI (**on by default**; `--no-scratch` to disable) the full output instead spills **losslessly** to a **scratch** file and the model recovers specifics via `Grep`/`Read` or **`Ask`** — a cheap, context-isolated peek that returns just the answer (the raw blob never re-enters context).
82
82
 
83
83
  ## Agentic subsystems
package/dist/cli.js CHANGED
@@ -1000,6 +1000,34 @@ var init_todo = __esm({
1000
1000
  }
1001
1001
  });
1002
1002
 
1003
+ // src/models.ts
1004
+ import { claudeModelAliases } from "ai.libx.js";
1005
+ function latestClaude(family, provider = "anthropic") {
1006
+ const id = MODEL_ALIASES[family];
1007
+ if (!id) throw new Error(`ai.libx.js catalog has no Claude ${family} model`);
1008
+ return `${provider}/${id}`;
1009
+ }
1010
+ function resolveModelAlias(input) {
1011
+ const slash = input.indexOf("/");
1012
+ const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
1013
+ const rest = slash === -1 ? input : input.slice(slash + 1);
1014
+ if (prefix === "claude-code/") return input;
1015
+ return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
1016
+ }
1017
+ function modelProvider(model) {
1018
+ const i = model.indexOf("/");
1019
+ if (i !== -1) return model.slice(0, i);
1020
+ if (/^claude-/i.test(model)) return "anthropic";
1021
+ return `?${model}`;
1022
+ }
1023
+ var MODEL_ALIASES;
1024
+ var init_models = __esm({
1025
+ "src/models.ts"() {
1026
+ "use strict";
1027
+ MODEL_ALIASES = { ...claudeModelAliases };
1028
+ }
1029
+ });
1030
+
1003
1031
  // src/tools.web.ts
1004
1032
  function htmlToText(html) {
1005
1033
  let s = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<title[\s\S]*?<\/title>/gi, " ").replace(/<noscript[\s\S]*?<\/noscript>/gi, " ").replace(/<textarea[\s\S]*?<\/textarea>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<\/(p|div|li|h[1-6]|tr|section|article|header|footer|nav)>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, " ");
@@ -1221,7 +1249,7 @@ function makeWebSearchTool(options = {}) {
1221
1249
  if (provider === "anthropic") {
1222
1250
  const akey = options.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY;
1223
1251
  if (!akey) return "Error: WebSearchAnthropic requires ANTHROPIC_API_KEY (set in env)";
1224
- return await anthropicSearch(q2, { key: akey, model: options.model ?? "claude-haiku-4-5", fetch: doFetch, signal: ctl.signal });
1252
+ return await anthropicSearch(q2, { key: akey, model: options.model ?? latestClaude("haiku").slice("anthropic/".length), fetch: doFetch, signal: ctl.signal });
1225
1253
  }
1226
1254
  if (useFirecrawl) {
1227
1255
  if (!fcKey) return "Error: Firecrawl provider selected but FIRECRAWL_API_KEY is not set";
@@ -1263,6 +1291,7 @@ var init_tools_web = __esm({
1263
1291
  "src/tools.web.ts"() {
1264
1292
  "use strict";
1265
1293
  init_logging();
1294
+ init_models();
1266
1295
  log4 = forComponent("web");
1267
1296
  webFetchTool = makeWebFetchTool();
1268
1297
  webSearchTool = makeWebSearchTool();
@@ -3983,36 +4012,10 @@ ${sections.join("\n\n---\n\n")}`;
3983
4012
 
3984
4013
  // src/subagent.ts
3985
4014
  init_tools();
4015
+ init_models();
3986
4016
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3987
4017
  import { join as join4 } from "path";
3988
4018
 
3989
- // src/models.ts
3990
- var MODEL_ALIASES = {
3991
- fable: "claude-fable-5",
3992
- "fable-5": "claude-fable-5",
3993
- opus: "claude-opus-5-5",
3994
- "opus-5-5": "claude-opus-5-5",
3995
- "opus-5": "claude-opus-5",
3996
- "opus-4-8": "claude-opus-4-8",
3997
- "opus-4-7": "claude-opus-4-7",
3998
- "opus-4-6": "claude-opus-4-6",
3999
- sonnet: "claude-sonnet-4-6",
4000
- haiku: "claude-haiku-4-5-20251001"
4001
- };
4002
- function resolveModelAlias(input) {
4003
- const slash = input.indexOf("/");
4004
- const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
4005
- const rest = slash === -1 ? input : input.slice(slash + 1);
4006
- if (prefix === "claude-code/") return input;
4007
- return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
4008
- }
4009
- function modelProvider(model) {
4010
- const i = model.indexOf("/");
4011
- if (i !== -1) return model.slice(0, i);
4012
- if (/^claude-/i.test(model)) return "anthropic";
4013
- return `?${model}`;
4014
- }
4015
-
4016
4019
  // src/permissions.ts
4017
4020
  init_tools_structured();
4018
4021
  function commandMatches(glob, cmd) {
@@ -4500,6 +4503,9 @@ ${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
4500
4503
  };
4501
4504
  }
4502
4505
 
4506
+ // src/Agent.ts
4507
+ init_models();
4508
+
4503
4509
  // src/agents.ts
4504
4510
  function parseAgentFrontmatter(md) {
4505
4511
  const m = md.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
@@ -4635,7 +4641,7 @@ var AgentOptions = class {
4635
4641
  * browser build (which always passes its own fs) never pulls in `node:fs`. Pass `fs` explicitly to
4636
4642
  * use any other backend (and to keep full control of the jail). */
4637
4643
  fs;
4638
- model = "anthropic/claude-sonnet-4-6";
4644
+ model = latestClaude("sonnet");
4639
4645
  systemPrompt = "You are a coding agent operating on a virtual filesystem. Use the `bash` tool to explore (ls, grep, find, cat) and the `Read`/`Edit` tools to view and modify files. Always Read a file before Editing it \u2014 and before overwriting it with `Write` or a whole-file `ApplyEdits`: Write replaces the ENTIRE file, so blindly writing over an existing one destroys its unseen contents. If a file already exists, Read it first, then merge your changes (or Edit the specific part). When multiple tool calls are independent (e.g. several Reads or Greps with no dependency between them), issue them together in a single turn rather than one at a time. When the task is complete, reply with a short summary and make no further tool calls.\nRecovery: every turn's file changes are checkpointed. If the user asks to undo/revert/recover edits (including a file you overwrote), tell them to run `/rewind` (or `/undo` for the last turn) to restore the working tree \u2014 checkpoints cover files you edited even in .gitignored dirs. You cannot run `/rewind` yourself; surface it as the recovery path.\nWhen working with a knowledge base or document workspace: use `RepoMap` with scope \"docs\" to see all document headings and summaries (orient first); use `MemorySearch` to find memories by content when you don't know the exact slug; use `Recall` with multiple slugs to batch-load related facts in one call. Prefer targeted loads over reading everything \u2014 orient \u2192 search \u2192 load \u2192 act.";
4640
4646
  tools = defaultTools();
4641
4647
  /** claude-code/* only: which agentx tools reach it as the in-process `agentx` MCP server (ccBridge.ts). 'memory' =
@@ -6070,6 +6076,7 @@ function toolKey(name) {
6070
6076
  }
6071
6077
 
6072
6078
  // src/index.ts
6079
+ init_models();
6073
6080
  init_NodeDiskFilesystem();
6074
6081
 
6075
6082
  // src/BodDbFilesystem.ts
@@ -6778,6 +6785,7 @@ function digestRun(messages, maxChars) {
6778
6785
  import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
6779
6786
  init_llm();
6780
6787
  init_logging();
6788
+ init_models();
6781
6789
 
6782
6790
  // node_modules/@bod.ee/voice/src/core/logging.ts
6783
6791
  function consoleForComponent(name) {
@@ -9650,9 +9658,9 @@ var DuplexAgentOptions = class {
9650
9658
  // mislabels gpt-oss harmony channels under load, leaking raw analysis into the spoken `final` channel
9651
9659
  // (and misfiring Hold). 120b is the same price tier (~$0.15/$0.60) — the quality/cost trade is free.
9652
9660
  reflexModel = "groq/openai/gpt-oss-120b";
9653
- actModel = "anthropic/claude-sonnet-4-6";
9661
+ actModel = latestClaude("sonnet");
9654
9662
  /** Premium reasoning model. Set to `false` to disable the Think tier entirely. */
9655
- thinkModel = "anthropic/claude-opus-4-8";
9663
+ thinkModel = latestClaude("opus");
9656
9664
  /** Per-worker providerOptions, derived from the worker's actual model at spawn time (IoC — keeps duplex
9657
9665
  * provider-agnostic). Workers override the reflex/main model, so provider-specific options (e.g. cursor's
9658
9666
  * cwd/cursorSession) must be recomputed for the worker's model, never inherited from the main template —
@@ -11230,11 +11238,12 @@ Another agent just implemented the above. Independently check the CURRENT state
11230
11238
  // src/modelRouter.ts
11231
11239
  init_llm();
11232
11240
  init_logging();
11241
+ init_models();
11233
11242
  var log22 = forComponent("ModelRouter");
11234
11243
  var ModelRouterOptions = class {
11235
11244
  classifier = jevClassifier();
11236
11245
  /** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
11237
- models = { basic: "anthropic/claude-haiku-4-5-20251001", standard: "anthropic/claude-sonnet-4-6", premium: "anthropic/claude-opus-5-5" };
11246
+ models = { basic: latestClaude("haiku"), standard: latestClaude("sonnet"), premium: latestClaude("opus") };
11238
11247
  /** Escalate to a stronger tier when it holds at least this much probability mass (upward bias). */
11239
11248
  threshold = 0.25;
11240
11249
  /** P(same thread) at or above which the previous premium side-conversation is resumed. */
@@ -12533,7 +12542,7 @@ Reference files in them by their mount path (the left side).`;
12533
12542
  ai: o.ai,
12534
12543
  jobs: jobRegistry,
12535
12544
  fs,
12536
- model: o.model ?? "anthropic/claude-sonnet-4-6",
12545
+ model: o.model ?? latestClaude("sonnet"),
12537
12546
  // PDF reads (disk mode only — VFS paths aren't real files): poppler's pdftotext when installed.
12538
12547
  ...!virtual ? { pdfText: pdfTextViaPoppler } : {},
12539
12548
  // Anchor cursor to the launch dir (its adapter defaults to TMPDIR otherwise) and forward the
@@ -12584,7 +12593,7 @@ Windows paths: file tools accept ${cwd} style paths and the same path written ${
12584
12593
  if (o.anthropicWebSearch !== false && o.anthropicKey && !requested.includes("WebSearchAnthropic")) {
12585
12594
  tail.push(makeWebSearchAnthropicTool({ anthropicApiKey: o.anthropicKey }));
12586
12595
  }
12587
- if (scratch) tail.push(makeAskTool({ fs, ai: o.ai, model: o.scratchAskModel ?? o.model ?? "anthropic/claude-sonnet-4-6", dir: scratchDir }));
12596
+ if (scratch) tail.push(makeAskTool({ fs, ai: o.ai, model: o.scratchAskModel ?? o.model ?? latestClaude("sonnet"), dir: scratchDir }));
12588
12597
  tail.push(makeNotifyTool());
12589
12598
  if (!realShell.length) return [...base, ...tail];
12590
12599
  const filtered = base.filter((t) => t.name !== "bash");
@@ -16425,7 +16434,7 @@ var HELP = `agentx \u2014 agent runtime CLI
16425
16434
  agentx -c "<task>" continue the most recent session
16426
16435
 
16427
16436
  Flags:
16428
- -m, --model <id> model (default auto: per-turn haiku/sonnet + opus offload when Jev is configured, else anthropic/claude-sonnet-4-6); bare -m or -m list = list all models
16437
+ -m, --model <id> model (default auto: per-turn haiku/sonnet + opus offload when Jev is configured, else the newest anthropic Sonnet); bare -m or -m list = list all models
16429
16438
  -C, --cwd <dir> working dir / project root (default .)
16430
16439
  -c, --continue resume the most recent session in this dir
16431
16440
  -r, --resume [id] resume a session \u2014 with an id, or bare for an interactive picker
@@ -16457,7 +16466,7 @@ Flags:
16457
16466
  with SONIOX_API_KEY + CARTESIA_API_KEY(+VOICE_ID) set: real voice I/O \u2014 mic in,
16458
16467
  spoken replies out (echo-cancelled; speak over it to interrupt)
16459
16468
  --voice-model <id> with --duplex: the fast voice model (default groq/openai/gpt-oss-120b)
16460
- --think-model <id> with --duplex: the premium deep-reasoning model (default anthropic/claude-opus-4-6)
16469
+ --think-model <id> with --duplex: the premium deep-reasoning model (default: the newest anthropic Opus)
16461
16470
  --no-think with --duplex: disable the Think tier (Act handles everything)
16462
16471
  --voice-system-prompt <text> with --duplex: appended to the voice (reflex) prompt only (--append-system-prompt \u2192 workers)
16463
16472
  --add-dir <path> mount another directory into the workspace (repeatable; disk mode only)
@@ -16508,8 +16517,8 @@ REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2
16508
16517
  REPL steer/stash: type while a turn is running \u2192 Enter STEERS it (the text reaches the model at its next step, alongside the next tool result). Alt+S (or Ctrl+S) with text stashes it for AFTER the turn instead; on an empty prompt pops the next stashed entry for editing.
16509
16518
  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.
16510
16519
  REPL paste: large/multi-line pastes collapse to a [Pasted #N "\u2026" +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
16511
- function newestModel() {
16512
- return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
16520
+ function newestModel(provider) {
16521
+ return listModels(provider).slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
16513
16522
  }
16514
16523
  function printModels() {
16515
16524
  const byProvider = /* @__PURE__ */ new Map();
@@ -16533,7 +16542,8 @@ function resolveModelOrNewest(model) {
16533
16542
  const want = model ?? new AgentOptions().model;
16534
16543
  const resolved = resolveModel(want);
16535
16544
  if (isModelSupported(resolved)) return resolved;
16536
- const fallback = newestModel();
16545
+ const provider = getProviderFromModel(want) ?? void 0;
16546
+ const fallback = newestModel(provider && listModels(provider).length ? provider : void 0);
16537
16547
  if (!fallback) return want;
16538
16548
  err(yellow(` \u26A0 model "${want}" not available \u2014 using ${fallback}
16539
16549
  `));
@@ -16603,7 +16613,7 @@ function duplexModels(o) {
16603
16613
  let reflexModel = asked ? resolveModelOrNewest(asked) : void 0;
16604
16614
  const reflex = reflexModel ?? dflt.reflexModel;
16605
16615
  if (!modelUsable(reflex, o.apiKeys)) {
16606
- const to = anthropic ? "anthropic/claude-haiku-4-5" : cc ? "claude-code/haiku" : o.actModel.startsWith("cursor/") ? void 0 : o.actModel;
16616
+ const to = anthropic ? latestClaude("haiku") : cc ? "claude-code/haiku" : o.actModel.startsWith("cursor/") ? void 0 : o.actModel;
16607
16617
  if (to) reflexModel = to;
16608
16618
  notes.push(`voice model ${reflex} can't run here (${why(reflex)})${to ? ` \u2014 using ${to}` : ""}`);
16609
16619
  }
@@ -17060,7 +17070,7 @@ async function evaluateGoal(ai, condition, transcript, log35) {
17060
17070
  }).join("\n---\n");
17061
17071
  try {
17062
17072
  const r = await ai.chat({
17063
- model: "anthropic/claude-haiku-4-5",
17073
+ model: latestClaude("haiku"),
17064
17074
  stream: false,
17065
17075
  messages: [
17066
17076
  { role: "system", content: 'You judge whether a goal condition has been met based on conversation transcript. Respond ONLY with JSON: {"met": boolean, "reason": "one sentence"}. Be strict \u2014 only met:true if there is clear evidence in the transcript.' },
@@ -18000,7 +18010,7 @@ async function repl(args, ai, cfg, cwd) {
18000
18010
  err(green(" \u2713 think tier disabled\n"));
18001
18011
  return;
18002
18012
  }
18003
- const picked2 = await pickModel(dx.options.thinkModel === false ? "anthropic/claude-opus-4-6" : String(dx.options.thinkModel));
18013
+ const picked2 = await pickModel(dx.options.thinkModel === false ? latestClaude("opus") : String(dx.options.thinkModel));
18004
18014
  if (picked2) applyThinkModel(picked2);
18005
18015
  return;
18006
18016
  }
@@ -18641,7 +18651,7 @@ ${task}`;
18641
18651
  return;
18642
18652
  }
18643
18653
  const current = dx.options.thinkModel === false ? void 0 : dx.options.thinkModel;
18644
- const picked = await pickModel(current ?? "anthropic/claude-opus-4-6");
18654
+ const picked = await pickModel(current ?? latestClaude("opus"));
18645
18655
  if (picked) applyThinkModel(picked);
18646
18656
  else err(dim(` think ${dx.options.thinkModel === false ? "off" : dx.options.thinkModel}
18647
18657
  `));
@@ -18997,7 +19007,7 @@ ${task}`;
18997
19007
  await fs2.writeFile(p, [
18998
19008
  "---",
18999
19009
  `description: What "${name}" is for \u2014 the Task tool picks agents by this line`,
19000
- "# model: anthropic/claude-haiku-4-5 # optional per-agent model override",
19010
+ "# model: anthropic/haiku # optional per-agent model override",
19001
19011
  "# tools: Read, Grep, Glob # optional tool allowlist (omit = all tools)",
19002
19012
  "---",
19003
19013
  "",