@livx.cc/agentx 0.98.2 → 0.99.1

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
@@ -35,7 +35,7 @@ Claude Code is the floor; running isolated, on the edge, or hybrid is the ceilin
35
35
  Plus things Claude Code simply doesn't do:
36
36
 
37
37
  - **Runs where CC can't** — the *same* agent loop runs on real disk, an in-memory **sandbox**, the **browser/edge** (no Node, no `/bin/sh`), or a **database-backed** workspace. Swap the filesystem, not the agent.
38
- - **Keyless web search, built in** — `WebSearch` works in any deployment with no API key (DuckDuckGo; auto-upgrades to Tavily if you set one). CC's search is Anthropic-server-bound.
38
+ - **Keyless web search, built in** — `WebSearch` works in any deployment with no API key (DuckDuckGo; auto-upgrades to Firecrawl or Tavily if you set a key). Plus optional `WebSearchAnthropic` — Anthropic's native search via a cheap model, on by default when an Anthropic key is set (API-billed; opt out with `anthropicWebSearch: false`).
39
39
  - **Context-safe by default** — a 1 MB `Grep`/`Read`/MCP result is auto-paginated and can't blow the window; buried detail is recovered via a cheap context-isolated `Ask` peek — **~5.3× cheaper and more accurate** than re-fetching, in a head-to-head.
40
40
  - **It improves its own efficiency** — an autonomous evolution loop cut its own tool-use **~50% (32 → 15** on the core suite, denoised), self-discovered, not hand-tuned — the same lever behind the efficiency lead above.
41
41
 
@@ -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; auto-upgrades to Tavily when `TAVILY_API_KEY` is set) 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']`.)
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`.
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
@@ -645,13 +645,48 @@ function formatHits(hits) {
645
645
  ${r.url}
646
646
  ${r.snippet.replace(/\s+/g, " ").slice(0, 240)}`).join("\n\n");
647
647
  }
648
+ async function firecrawlSearch(q2, opts) {
649
+ const res = await opts.fetch(opts.endpoint, {
650
+ method: "POST",
651
+ signal: opts.signal,
652
+ headers: { authorization: `Bearer ${opts.key}`, "content-type": "application/json" },
653
+ body: JSON.stringify({ query: q2, limit: opts.maxResults })
654
+ });
655
+ if (!res.ok) return `Error: Firecrawl search returned ${res.status} ${res.statusText}`;
656
+ const data = await res.json();
657
+ const results = Array.isArray(data?.data) ? data.data.slice(0, opts.maxResults) : [];
658
+ return formatHits(results.map((r) => ({ title: r.title ?? "(untitled)", url: r.url ?? "", snippet: String(r.description ?? r.markdown ?? "") })));
659
+ }
660
+ async function anthropicSearch(q2, opts) {
661
+ const res = await opts.fetch("https://api.anthropic.com/v1/messages", {
662
+ method: "POST",
663
+ signal: opts.signal,
664
+ headers: { "x-api-key": opts.key, "anthropic-version": "2023-06-01", "content-type": "application/json" },
665
+ body: JSON.stringify({
666
+ model: opts.model,
667
+ max_tokens: 1024,
668
+ // Basic variant: Haiku-tier doesn't support the _20260209 dynamic-filtering variant (Opus 4.6+/Sonnet 4.6 only).
669
+ tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 5 }],
670
+ messages: [{ role: "user", content: `Search the web for: ${q2}
671
+
672
+ Return only the relevant findings as concise bullet points, each with its source URL in parentheses. Do not add a preamble, conclusion, opinion, or commentary. If sources conflict, list each claim with its source rather than resolving it.` }]
673
+ })
674
+ });
675
+ if (!res.ok) return `Error: Anthropic search returned ${res.status} ${res.statusText}`;
676
+ const data = await res.json();
677
+ if (data?.stop_reason === "refusal") return "Error: Anthropic search refused the query";
678
+ let text = "";
679
+ for (const block of data?.content ?? []) if (block?.type === "text") text += block.text;
680
+ return text.trim() || "(no results)";
681
+ }
648
682
  function makeWebSearchTool(options = {}) {
649
683
  const tavilyEndpoint = options.endpoint ?? "https://api.tavily.com/search";
684
+ const firecrawlEndpoint = options.firecrawlEndpoint ?? "https://api.firecrawl.dev/v1/search";
650
685
  const maxResults = options.maxResults ?? 5;
651
686
  const timeoutMs = options.timeoutMs ?? 15e3;
652
687
  return {
653
- name: "WebSearch",
654
- description: "Search the web by query; returns ranked results (title, URL, snippet). Use to look things up, find pages, or research a topic \u2014 then WebFetch a result URL to read it in full.",
688
+ name: options.name ?? "WebSearch",
689
+ description: options.description ?? "Search the web by query; returns ranked results (title, URL, snippet). Use to look things up, find pages, or research a topic \u2014 then WebFetch a result URL to read it in full.",
655
690
  parameters: { type: "object", required: ["query"], properties: { query: { type: "string" } } },
656
691
  async run({ query }) {
657
692
  const doFetch = options.fetch ?? globalThis.fetch;
@@ -659,11 +694,22 @@ function makeWebSearchTool(options = {}) {
659
694
  const q2 = String(query ?? "").trim();
660
695
  if (!q2) return "Error: empty query";
661
696
  const key = options.apiKey ?? process.env.TAVILY_API_KEY;
697
+ const fcKey = options.firecrawlApiKey ?? process.env.FIRECRAWL_API_KEY;
662
698
  const provider = options.provider ?? "auto";
663
- const useTavily = provider === "tavily" || provider === "auto" && !!key;
699
+ const useFirecrawl = provider === "firecrawl" || provider === "auto" && !!fcKey;
700
+ const useTavily = provider === "tavily" || provider === "auto" && !useFirecrawl && !!key;
664
701
  const ctl = new AbortController();
665
702
  const timer = setTimeout(() => ctl.abort(), timeoutMs);
666
703
  try {
704
+ if (provider === "anthropic") {
705
+ const akey = options.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY;
706
+ if (!akey) return "Error: WebSearchAnthropic requires ANTHROPIC_API_KEY (set in env)";
707
+ return await anthropicSearch(q2, { key: akey, model: options.model ?? "claude-haiku-4-5", fetch: doFetch, signal: ctl.signal });
708
+ }
709
+ if (useFirecrawl) {
710
+ if (!fcKey) return "Error: Firecrawl provider selected but FIRECRAWL_API_KEY is not set";
711
+ return await firecrawlSearch(q2, { key: fcKey, endpoint: firecrawlEndpoint, maxResults, fetch: doFetch, signal: ctl.signal });
712
+ }
667
713
  if (useTavily) {
668
714
  if (!key) return "Error: Tavily provider selected but TAVILY_API_KEY is not set";
669
715
  const res2 = await doFetch(tavilyEndpoint, {
@@ -692,7 +738,10 @@ function makeWebSearchTool(options = {}) {
692
738
  }
693
739
  };
694
740
  }
695
- var log2, _dnsLookup, webFetchTool, webSearchTool;
741
+ function makeWebSearchAnthropicTool(opts = {}) {
742
+ return makeWebSearchTool({ provider: "anthropic", name: "WebSearchAnthropic", description: ANTHROPIC_SEARCH_DESC, anthropicApiKey: opts.anthropicApiKey, model: opts.model });
743
+ }
744
+ var log2, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
696
745
  var init_tools_web = __esm({
697
746
  "src/tools.web.ts"() {
698
747
  "use strict";
@@ -700,6 +749,8 @@ var init_tools_web = __esm({
700
749
  log2 = forComponent("web");
701
750
  webFetchTool = makeWebFetchTool();
702
751
  webSearchTool = makeWebSearchTool();
752
+ ANTHROPIC_SEARCH_DESC = "High-quality web search via Anthropic's native search index. Returns concise, sourced findings \u2014 one claim per line, each with its source URL. Prefer this over WebSearch when accuracy and citations matter; it is slower (~3\u20138s) and bills the Anthropic API account per call (one cheap-model turn + search fee).";
753
+ webSearchAnthropicTool = makeWebSearchAnthropicTool();
703
754
  }
704
755
  });
705
756
 
@@ -925,7 +976,7 @@ function defaultTools() {
925
976
  return [bashTool, readTool, editTool];
926
977
  }
927
978
  function toolRegistry() {
928
- const all = [bashTool, readTool, editTool, grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool(), todoWriteTool, webFetchTool, webSearchTool];
979
+ const all = [bashTool, readTool, editTool, grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool(), todoWriteTool, webFetchTool, webSearchTool, webSearchAnthropicTool];
929
980
  return Object.fromEntries(all.map((t) => [t.name, t]));
930
981
  }
931
982
  function toolsByName(names) {
@@ -7413,8 +7464,12 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
7413
7464
  return { systemPrompt: basePrompt + "\n\n" + extra };
7414
7465
  })(),
7415
7466
  tools: (() => {
7416
- const base = toolsByName([...o.tools ?? DEFAULT_TOOLS, ...autoWebTools()]);
7467
+ const requested = o.tools ?? DEFAULT_TOOLS;
7468
+ const base = toolsByName([...requested, ...autoWebTools()]);
7417
7469
  const tail = [...o.extraTools ?? []];
7470
+ if (o.anthropicWebSearch !== false && o.anthropicKey && !requested.includes("WebSearchAnthropic")) {
7471
+ tail.push(makeWebSearchAnthropicTool({ anthropicApiKey: o.anthropicKey }));
7472
+ }
7418
7473
  if (scratch) tail.push(makeAskTool({ fs, ai: o.ai, model: o.scratchAskModel ?? o.model ?? "anthropic/claude-sonnet-4-6", dir: scratchDir }));
7419
7474
  tail.push(makeNotifyTool());
7420
7475
  if (!realShell.length) return [...base, ...tail];
@@ -10810,7 +10865,7 @@ Providers: set any of ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ
10810
10865
  Env files: .env (CWD, bun auto-loads) > install-dir .env > ~/.agent/.env (user-wide).
10811
10866
  Bodify secrets: set BODIFY_API_KEY + BODIFY_APP_ID (in ~/.agent/.env) to pull keys from a Bodify app.
10812
10867
  Config: ./.agent/config.{ts,js,json} (project) or ~/.agent/config.* (user).
10813
- export default { model, maxSteps, reasoning, permissionMode, editorMode, tools, apiKeys, baseUrls, hooks, permissions, mcpServers, maxTokens,
10868
+ export default { model, maxSteps, reasoning, permissionMode, editorMode, tools, anthropicWebSearch, apiKeys, baseUrls, hooks, permissions, mcpServers, maxTokens,
10814
10869
  timeoutMs, maxRepeats, maxToolCalls, keepToolOutputs, maxContextTokens,
10815
10870
  learnFromMistakes, reflectOnFailure, budget: {\u2026} }
10816
10871
  hooks: { preToolUse|postToolUse|onStop: [{ tool?, command, block? }] } \u2014 shell hooks
@@ -11410,7 +11465,10 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
11410
11465
  learnFromMistakes: cfg.learnFromMistakes,
11411
11466
  // Forwarded to cursor/* delegations for environment parity (chat-model providers ignore it).
11412
11467
  // Raw config (pre-OAuth): unresolved-oauth http servers are skipped by the cursor mapper.
11413
- mcpServers: cfg.mcpServers
11468
+ mcpServers: cfg.mcpServers,
11469
+ // Gates + powers default-on WebSearchAnthropic. Same precedence as the main client (env wins).
11470
+ anthropicKey: apiKeysFromEnv().anthropic ?? cfg.apiKeys?.anthropic,
11471
+ anthropicWebSearch: cfg.anthropicWebSearch
11414
11472
  };
11415
11473
  }
11416
11474
  async function makeAgent(args, ai, cfg, extraTools = []) {