@livx.cc/agentx 0.98.1 → 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 +2 -2
- package/dist/cli.js +72 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -5
- package/dist/index.js +64 -6
- package/dist/index.js.map +1 -1
- package/dist/tools.shell.js +54 -3
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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) {
|
|
@@ -4937,6 +4988,8 @@ var DuplexAgent = class _DuplexAgent {
|
|
|
4937
4988
|
// briefs dispatched this turn (detect identical re-dispatch)
|
|
4938
4989
|
spokeThisTurn = false;
|
|
4939
4990
|
// any non-empty text_delta streamed this turn
|
|
4991
|
+
heldThisTurn = false;
|
|
4992
|
+
// Hold called this turn → turn is INTENTIONALLY silent (suppress reflex text + no dead-air ack)
|
|
4940
4993
|
nudging = false;
|
|
4941
4994
|
// re-ack pass in flight: block ALL tools, prevent recursion
|
|
4942
4995
|
reflexBuf = "";
|
|
@@ -4999,6 +5052,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4999
5052
|
confirm: host.confirm ? (p, m) => host.confirm(p, m) : void 0,
|
|
5000
5053
|
notify: (ev) => {
|
|
5001
5054
|
if (ev?.kind === "text_delta" && typeof ev.message === "string") {
|
|
5055
|
+
if (this.heldThisTurn) return;
|
|
5002
5056
|
if (this.fabricationCut) return;
|
|
5003
5057
|
const msg = ev.message;
|
|
5004
5058
|
this.reflexBuf += msg;
|
|
@@ -5068,6 +5122,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
5068
5122
|
this.turnDispatched = false;
|
|
5069
5123
|
this.turnBriefs.clear();
|
|
5070
5124
|
this.spokeThisTurn = false;
|
|
5125
|
+
this.heldThisTurn = false;
|
|
5071
5126
|
this.reflexBuf = "";
|
|
5072
5127
|
this.reflexForwarded = 0;
|
|
5073
5128
|
this.fabricationCut = false;
|
|
@@ -5096,7 +5151,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
5096
5151
|
* voice) and emits an empty `final`, so no text_delta ever streams. Both ship silence; both repair.
|
|
5097
5152
|
* Requires a host: without one there's no stream to detect speech on (and no one to speak to). */
|
|
5098
5153
|
get silentTurn() {
|
|
5099
|
-
return !!this.options.host && !this.spokeThisTurn;
|
|
5154
|
+
return !!this.options.host && !this.spokeThisTurn && !this.heldThisTurn;
|
|
5100
5155
|
}
|
|
5101
5156
|
/** A turn that voiced nothing is dead air. Re-prompt the reflex ONCE so the LLM itself voices a short
|
|
5102
5157
|
* line (no template). If it STILL says nothing, fall back to a minimal line so silence never ships.
|
|
@@ -5657,6 +5712,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
5657
5712
|
}
|
|
5658
5713
|
},
|
|
5659
5714
|
run: async ({ filler }) => {
|
|
5715
|
+
this.heldThisTurn = true;
|
|
5660
5716
|
if (filler) this.notify("hold_filler", String(filler));
|
|
5661
5717
|
return "Holding \u2014 listening for the rest of the user's thought. Do not respond further this turn.";
|
|
5662
5718
|
}
|
|
@@ -7408,8 +7464,12 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
7408
7464
|
return { systemPrompt: basePrompt + "\n\n" + extra };
|
|
7409
7465
|
})(),
|
|
7410
7466
|
tools: (() => {
|
|
7411
|
-
const
|
|
7467
|
+
const requested = o.tools ?? DEFAULT_TOOLS;
|
|
7468
|
+
const base = toolsByName([...requested, ...autoWebTools()]);
|
|
7412
7469
|
const tail = [...o.extraTools ?? []];
|
|
7470
|
+
if (o.anthropicWebSearch !== false && o.anthropicKey && !requested.includes("WebSearchAnthropic")) {
|
|
7471
|
+
tail.push(makeWebSearchAnthropicTool({ anthropicApiKey: o.anthropicKey }));
|
|
7472
|
+
}
|
|
7413
7473
|
if (scratch) tail.push(makeAskTool({ fs, ai: o.ai, model: o.scratchAskModel ?? o.model ?? "anthropic/claude-sonnet-4-6", dir: scratchDir }));
|
|
7414
7474
|
tail.push(makeNotifyTool());
|
|
7415
7475
|
if (!realShell.length) return [...base, ...tail];
|
|
@@ -10805,7 +10865,7 @@ Providers: set any of ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ
|
|
|
10805
10865
|
Env files: .env (CWD, bun auto-loads) > install-dir .env > ~/.agent/.env (user-wide).
|
|
10806
10866
|
Bodify secrets: set BODIFY_API_KEY + BODIFY_APP_ID (in ~/.agent/.env) to pull keys from a Bodify app.
|
|
10807
10867
|
Config: ./.agent/config.{ts,js,json} (project) or ~/.agent/config.* (user).
|
|
10808
|
-
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,
|
|
10809
10869
|
timeoutMs, maxRepeats, maxToolCalls, keepToolOutputs, maxContextTokens,
|
|
10810
10870
|
learnFromMistakes, reflectOnFailure, budget: {\u2026} }
|
|
10811
10871
|
hooks: { preToolUse|postToolUse|onStop: [{ tool?, command, block? }] } \u2014 shell hooks
|
|
@@ -11405,7 +11465,10 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
11405
11465
|
learnFromMistakes: cfg.learnFromMistakes,
|
|
11406
11466
|
// Forwarded to cursor/* delegations for environment parity (chat-model providers ignore it).
|
|
11407
11467
|
// Raw config (pre-OAuth): unresolved-oauth http servers are skipped by the cursor mapper.
|
|
11408
|
-
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
|
|
11409
11472
|
};
|
|
11410
11473
|
}
|
|
11411
11474
|
async function makeAgent(args, ai, cfg, extraTools = []) {
|