@livx.cc/agentx 0.94.38
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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/Agent-1DRfsYaK.d.ts +408 -0
- package/dist/cli.d.ts +293 -0
- package/dist/cli.js +12796 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +1319 -0
- package/dist/index.js +5998 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-CnzmQ8JE.d.ts +101 -0
- package/dist/mcp.client.d.ts +228 -0
- package/dist/mcp.client.js +617 -0
- package/dist/mcp.client.js.map +1 -0
- package/dist/tools-DtpN8Agv.d.ts +274 -0
- package/dist/tools.shell.d.ts +115 -0
- package/dist/tools.shell.js +539 -0
- package/dist/tools.shell.js.map +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elya Livshitz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# agentx
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@livx.cc/agentx)
|
|
4
|
+
[](https://github.com/Livshitz/agentx/actions/workflows/publish.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
**A coding agent that matches Claude Code on correctness — then beats it on cost, tokens, and tool-efficiency, and runs where Claude Code can't (sandbox, browser/edge, database).**
|
|
10
|
+
|
|
11
|
+
<p align="center"><img src="docs/demo.gif" alt="agentx reading, diffing, and fixing a bug in one prompt" width="760"></p>
|
|
12
|
+
|
|
13
|
+
By default it's a full-strength terminal coding agent: real disk, real shell, and the same Read/Edit/Grep/permissions/streaming-DX surface you'd expect from Claude Code. The difference is its two host couplings are swappable seams:
|
|
14
|
+
|
|
15
|
+
- **LLM** → any model via [`ai.libx.js`](https://github.com/Livshitz/ai.libx.js) (`AIClient.chat`, OpenAI-style tools/streaming).
|
|
16
|
+
- **Filesystem** → a pluggable `IFilesystem` (real disk, in-memory, IndexedDB, a database, hybrid mounts) from [`wcli`](https://github.com/Livshitz/wcli)'s headless core.
|
|
17
|
+
|
|
18
|
+
So the *same* agent loop also runs **sandboxed** (in-memory VFS, real disk untouched), on the **edge / browser** (no Node, no `/bin/sh`), or **hybrid** (mount real dirs + a database + remote storage side by side, with transactional overlays for checkpoint/rollback).
|
|
19
|
+
|
|
20
|
+
Claude Code is the floor; running isolated, on the edge, or hybrid is the ceiling.
|
|
21
|
+
|
|
22
|
+
## How it stacks up vs Claude Code
|
|
23
|
+
|
|
24
|
+
**Correctness parity — efficiency, cost, and reach are the lead.** Hard 7-task coding suite, Sonnet, *denoised* (each task ×3, no lucky run promotes; `SUITE=hard bun compare/run.ts`):
|
|
25
|
+
|
|
26
|
+
| | agentx | Claude Code |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Correctness | 7/7 | 7/7 — **parity** |
|
|
29
|
+
| Tool-calls | **16** | 28 — **−43%** |
|
|
30
|
+
| Tokens | **69k** | 171k — **2.5× fewer** |
|
|
31
|
+
| Wall-time | **~100s** | 133s — **~25% faster** |
|
|
32
|
+
|
|
33
|
+
**Cost** (9-task hard suite, USD-metered, vs CC-on-Opus): **$0.49** single-tier Sonnet (**5.4× cheaper**) · **$0.82** three-tier voice/duplex (**3.3× cheaper**) vs CC-Opus **$2.67** — at quality parity (16/18 vs 17/18 passes).
|
|
34
|
+
|
|
35
|
+
Plus things Claude Code simply doesn't do:
|
|
36
|
+
|
|
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.
|
|
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
|
+
- **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
|
+
|
|
42
|
+
*Honest scope:* the win is **efficiency / cost / reach**, not a claim of smarter reasoning — correctness is parity. All figures are denoised and reproducible (see [Eval & compare](#eval--compare)); full boards in [`mind/09-outperform.md`](./mind/09-outperform.md).
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
Point it at your project — no clone needed (requires [Bun](https://bun.sh)):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export ANTHROPIC_API_KEY=… # or OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY
|
|
50
|
+
bunx @livx.cc/agentx "find and fix the failing test" # run once in the current directory
|
|
51
|
+
bunx @livx.cc/agentx # …or open the interactive REPL
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Want a permanent command? `bun add -g @livx.cc/agentx`, then just `agentx` (and `agentx --duplex` for voice). The agent has full real-disk + shell access by default (like Claude Code); add `--sandbox` to work on an in-memory copy instead. See [The `agentx` CLI](#the-agentx-cli) for flags, sessions, and slash commands.
|
|
55
|
+
|
|
56
|
+
## Use it as a library
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { AIClient } from 'ai.libx.js';
|
|
60
|
+
import { Agent, MemFilesystem } from '@livx.cc/agentx';
|
|
61
|
+
|
|
62
|
+
const fs = new MemFilesystem(); // or NodeDiskFilesystem(dir) — interchangeable
|
|
63
|
+
await fs.createDir('/src');
|
|
64
|
+
await fs.writeFile('/src/x.ts', 'export const add = (a,b) => a - b;\n');
|
|
65
|
+
|
|
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' })
|
|
68
|
+
.run('Fix the add bug in /src/x.ts');
|
|
69
|
+
|
|
70
|
+
console.log(res.finishReason, await fs.readFile('/src/x.ts'));
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Tools the agent gets
|
|
74
|
+
|
|
75
|
+
- **`Shell`** (CLI disk mode) — a real `/bin/sh`: run any installed binary (git, bun, node, curl, scripts, …). **`bash`** (library / sandbox mode) — `ls/cat/grep/find/head/tail/echo/mkdir/rm/mv/wc`, pipes, redirects, chaining — over the VFS (wcli's sandboxed JS interpreter).
|
|
76
|
+
- **`Read`** — 1-indexed numbered lines, `offset`/`limit`.
|
|
77
|
+
- **`Edit`** — exact unique-substring replace, with a read-before-edit staleness guard.
|
|
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
|
+
- **`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']`.)
|
|
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
|
+
|
|
83
|
+
## Agentic subsystems
|
|
84
|
+
|
|
85
|
+
Beyond file tools, the runtime ships the higher-altitude pieces too — each an `AgentOptions`/loop extension over the two seams (see [`mind/06`](./mind/06-agent-features.md)):
|
|
86
|
+
|
|
87
|
+
- **Skills + memory** — VFS-backed (`skillsDir`/`memoryDir`); persistence is just the backend choice.
|
|
88
|
+
- **Subagents** (`subagents`; **typed agents** via `agentsDir` — `<dir>/<name>.md` defines a persona + model + scoped tools, selected with the `Task` `agentType`), **hooks** (`hooks`: preToolUse/postToolUse/onStop — block or audit any tool call), **slash-commands** (`commandsDir`), **TodoWrite**, **MCP** (`mcpToolsToAgentTools`).
|
|
89
|
+
- **Streaming** (`stream: true` → `text_delta` via `HostBridge.notify`) and **context compaction** (`compaction: { maxMessages }` → edge-safe summarize-and-boundary). Defaults preserve the original non-stream, drop-oldest behavior.
|
|
90
|
+
- **Multi-turn + project context** — `Agent.send()` continues a conversation across turns (vs `run()`, which starts fresh); **project instructions** (`instructionFiles`: `AGENTS.md`/`CLAUDE.md` at the FS root) inject into the system prompt.
|
|
91
|
+
- **DuplexAgent** (`src/duplex.ts`) — voice-optimized three-tier engine (reflex/act/think): a fast reflex agent streams instant replies and self-selects escalation — `Act` for standard tool work (Sonnet-class), `Think` for deep reasoning (Opus-class, configurable, default on). Results are pushed back and re-voiced by the reflex (turn mutex, coalesced completions, `TaskStatus`/`CancelTask`). See [`mind/10`](./mind/10-duplex.md).
|
|
92
|
+
- **Scheduler** (`src/scheduler.ts` + `cli/osScheduler.ts`) — one-off (`{at}`), interval (`{everyMs}`), cron (`{cron}`) via `ScheduleTask`/`ScheduleList`/`ScheduleCancel`/`Wakeup`. In-session jobs fire while the session is alive (persisted, re-armed on `--resume`); far one-offs (or `backend:'os'`) register with the OS scheduler (launchd / crontab / at) and **survive quitting** — the fired job headless-resumes the session (`agentx -p … --resume <id> --yes`). The `PushNotification` tool (osascript / notify-send) alerts the user out-of-band; `Read` on a `.pdf` returns extracted text (poppler's pdftotext, disk mode). **`RemoteTrigger`** invokes another agentx session on this machine: a session open in a live terminal receives the prompt as an injected turn (per-session unix socket, same-user only); otherwise it's resumed headless and the final answer comes back. See [`mind/12`](./mind/12-scheduler.md).
|
|
93
|
+
- **Budget kill-switches** — always-on per-run guards (`maxTokens`/`timeoutMs`/`maxRepeats`/`maxToolCalls`/`signal` → `finishReason` `budget`/`timeout`/`loop`/`max_tool_calls`/`aborted`) protect the API spend against runaway loops. The *enforceable* billing cap is server-side in the web key-proxy: a VFS-backed budget config (`/.agent/budget.json`, USD-metered, hot-reloaded, $100/wk default) a browser client can't bypass. See [`web/`](./web) and [`mind/06`](./mind/06-agent-features.md).
|
|
94
|
+
|
|
95
|
+
## The `agentx` CLI
|
|
96
|
+
|
|
97
|
+
A dependency-light `readline` REPL (plus headless `-p` mode) over the runtime:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
agentx # interactive REPL in the current dir
|
|
101
|
+
agentx "fix the bug in x" # run once and exit
|
|
102
|
+
agentx -c "keep going" # continue the most recent session
|
|
103
|
+
agentx --resume <id> "…" # resume a specific session
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- **Filesystem + Shell** — by default the CLI has **full real-filesystem access like Claude Code** (root `/` is the machine root, the launch dir is the working dir, absolute host paths and above-cwd reach both work) with a **real `/bin/sh`** (`Shell` tool) so the agent can run git, bun, node, curl, and any installed binary. Secrets (`.env`, `.ssh`, keys, `.git`) stay hidden by the jail; env secrets are scrubbed from the child shell. `--sandbox` instead operates over an in-memory copy of the working dir with a VFS-only `bash` — the real disk is never touched. `--boddb <dir>` runs over a **persistent database workspace** (a bod-db store at `<dir>` — `meta.db` tree + `files/` bytes) that survives across runs while the real disk stays untouched; DB-native by default, or add `--seed` to hydrate it from cwd on the first run. `--no-shell` forces the VFS bash in disk mode. `--harden` OS-sandboxes the real shell (macOS `sandbox-exec` / Linux `bwrap`): writes confined to cwd+tmp, outbound network blocked (`--harden-net` keeps network); commands fail closed when no wrapper exists. (`/sandbox` shows the active mode.)
|
|
107
|
+
- **Sessions** — every conversation persists to `./.agent/sessions/<id>.json`, flushed at **every tool step** (a crash, hang, or Ctrl-C mid-turn loses at most the in-flight step, never the transcript); `--continue`/`--resume` (and `/sessions`, `/resume`) pick it back up, *with memory across turns* — a REPL turn sees the previous one. A global symlink index at `~/.agent/sessions/` enables cross-project lookup: `--resume 090715-myproject` resolves from any directory, and `/sessions all` lists every project's sessions in one picker.
|
|
108
|
+
- **Diffs** — every `Edit`/`Write`/`MultiEdit` renders a colorized `+/-` diff (TTY-gated; plain when piped).
|
|
109
|
+
- **Slash commands** — `/help /tools /model /compact /copy /diff /memory /clear /sessions /resume /commands /init`; `/compact <focus>` preserves matching lines from the folded span; `/copy [code]` puts the last reply (or its last code block) on the OS clipboard; `/diff` shows everything the session changed (oldest checkpoint → now); `/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). Skills/commands created **mid-session** are picked up automatically each turn (delivered as a cache-friendly `<system-reminder>` delta, like Claude Code) and the `Skill`/`SlashCommand` tools rescan on a name miss; `/reload` forces a full catalog + system-prompt rebuild.
|
|
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.
|
|
114
|
+
- **Project instructions** — `./AGENTS.md` (or `CLAUDE.md`) auto-loads into every run; `/init` scaffolds one.
|
|
115
|
+
- **Any provider** — set `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `GROQ_API_KEY`; choose with `-m provider/model`.
|
|
116
|
+
- **@-file mentions & headless JSON** — reference files inline in a prompt with `@path` (e.g. `explain @src/Agent.ts`; `~/` expands to the home directory; quote paths with spaces as `@"…"` — drag-dropped files, e.g. macOS screenshots, quote themselves automatically); script with `-p --output-format json` to get one machine-readable result object on stdout (activity stays on stderr).
|
|
117
|
+
- **Tab-completion** — `Tab` completes `/<command>` names and `@<path>` file/dir references (descends subdirs, dotfiles hidden unless typed) straight from the working tree.
|
|
118
|
+
- **Duplex mode** — `agentx --duplex` runs the full standard REPL (slash commands, sessions, postures, rewind, MCP) with the three-tier engine driving turns: a fast voice model (`--voice-model`, default `groq/openai/gpt-oss-120b`) answers every line instantly and delegates real work to background workers built with the same wiring as a normal run (fs mode, permissions, MCP); worker activity shows as dim chrome and results are re-voiced when ready. Switch any tier live with `/model` (opens a reflex/act/think picker), or the `/voice-model` · `/think-model` shortcuts. `/tasks` lists background tasks, inspects a task's live output tail, and cancels a running one from a picker (Esc mid-turn cancels the foreground turn; Esc again at the idle prompt cancels running workers).
|
|
119
|
+
- **MCP servers** — declare `mcpServers: { name: { command, args } | { url } }` in config and they're auto-mounted at startup (in parallel, with an optional `mountTimeoutMs` deadline so one slow/dead server never blocks the rest): the client does the JSON-RPC handshake (stdio or HTTP) + `tools/list`, and the discovered tools appear as `mcp__<name>__<tool>` in `/tools` (inspect with `/mcp`). A bad server is logged and skipped, never blocking the agent. For large tool sets, **deferred mode** (`makeMcpToolSearch` / `mountMcpDeferred`) exposes just two bounded tools (`ToolSearch` + `McpCall`) instead of N defs — dodging the provider tool-cap and improving selection accuracy; the CLI applies this automatically past 12 mounted tools (a 42-tool server was costing ~80k tok/turn in schema alone), and permission rules written against the real `mcp__<name>__<tool>` names still match through `McpCall`. **`mountMcpCatalog`** goes further: a cached, hash-keyed catalog + lazy connect means a turn that uses no MCP tool opens **zero** connections, and one that uses a tool connects exactly that server — latency scales with tools-used, not servers-configured. A down server is **negative-cached** (`failureCooldownMs`) so it never re-floors a later turn at the deadline. For zero turn-path latency even on a cold process, call **`warmMcpCatalog`** at boot + on a timer (off-turn discovery) and mount with **`{ discover: 'cache-only' }`** — the turn then never synchronously connects: it serves the warmed catalog and discovers any miss in the background.
|
|
120
|
+
|
|
121
|
+
## 🧬 It improves itself
|
|
122
|
+
|
|
123
|
+
The agent is a coding agent that operates over a swappable filesystem — so it can be pointed at **its own repo** and evolve its own configuration. `evolve/` is an autonomous loop:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
champion → propose patch → jailed + sandboxed eval → per-task no-regression gate → ledger → repeat
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
An LLM is the mutation operator; a **behavioral** fitness function (run the produced code) is natural selection. Correctness is a hard gate, the rule files are **hash-pinned** (the agent can't edit what judges it), and every candidate runs under two containment boundaries — a `JailedFilesystem` (secret denylist, symlink-escape defense) and a **sandboxed grader** (scrubbed env, nonce-authenticated result, default-on `sandbox-exec`). Those guardrails were hardened against a **22-agent adversarial red-team (14 findings fixed)** before the loop was allowed to run.
|
|
130
|
+
|
|
131
|
+
**Result (Sonnet 4.6):** the loop autonomously drove **baseline 32 → 15 tool-calls (53% fewer), 5/5 pass held** — **parity with Claude Code** (head-to-head **15 vs 15 tools, 1.8× faster, 2.8× fewer tokens**), the efficiency gap we'd only described before. This is the *denoised* figure (each candidate averaged over 3 runs so no lucky run promotes); a single un-averaged run reached 14. It **generalizes** to held-out tasks (24 → 12, no overfit) and discovered the human-authored parity plan on its own: *use structured Grep/MultiEdit, stop over-exploring.*
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
GENERATIONS=8 bun evolve/loop.ts # evolve → evolve/champion.json + ledger.jsonl
|
|
135
|
+
bun evolve/report.ts # instant replay of the arc (no tokens)
|
|
136
|
+
EVOLVED=1 bun compare/run.ts # evolved champion vs Claude Code
|
|
137
|
+
bun evolve/generalize.ts # baseline vs champion on UNSEEN tasks
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Full design + threat model + results: [`mind/08-self-evolve.md`](./mind/08-self-evolve.md).
|
|
141
|
+
|
|
142
|
+
## Status
|
|
143
|
+
|
|
144
|
+
**v1 (done):** loop + hybrid tools + Mem/Disk backends + deterministic `FakeAIClient` tests + real-model run. **5/5 pass@1** on the behavioral eval (Sonnet 4.6); the head-to-head started at correctness parity with Claude Code but ~2× the tool calls (≈28 vs 15) — a gap the **self-evolution loop has now closed autonomously**: it drove its own baseline from 32 → 15 tool-calls (denoised over 3 runs) and ties Claude Code in a fresh head-to-head (15 vs 15). **820+ tests green.**
|
|
145
|
+
|
|
146
|
+
See [`mind/`](./mind/) for the full vision, architecture, decision journal, roadmap, eval + head-to-head results, the [parity plan](./mind/05-parity.md), and the [self-evolution design](./mind/08-self-evolve.md).
|
|
147
|
+
|
|
148
|
+
## Develop & evaluate
|
|
149
|
+
|
|
150
|
+
Hacking on the runtime itself (from a clone):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
bun install # links wcli (file:), ai.libx.js + libx.js (bun link)
|
|
154
|
+
bun test # 820+ unit/integration tests (offline via FakeAIClient, no key)
|
|
155
|
+
ANTHROPIC_API_KEY=… bun examples/run-sonnet.ts # drive a real model end-to-end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Eval & head-to-head (real model):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bun eval/run.ts # behavioral scorecard (our agent over MemFilesystem)
|
|
162
|
+
bun compare/seed-tasks.ts # materialize task specs into .tmp/tasks/
|
|
163
|
+
bun compare/run.ts # head-to-head vs Claude Code (needs the `claude` CLI)
|
|
164
|
+
```
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
2
|
+
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, e as MessageContent } from './tools-DtpN8Agv.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hooks — deterministic interception points around tool execution, run by the
|
|
6
|
+
* harness/host (mirrors Claude Code hooks). The seam stays edge-safe: hooks are
|
|
7
|
+
* plain async callbacks, no node builtins, injected via AgentOptions.hooks.
|
|
8
|
+
*
|
|
9
|
+
* - preToolUse: fires BEFORE a tool runs. Return `{ block: true, reason }` to
|
|
10
|
+
* skip the tool — Agent.dispatch returns `Blocked by hook: <reason>`
|
|
11
|
+
* as the tool result (the model sees it and can adapt).
|
|
12
|
+
* - postToolUse: fires AFTER a tool produced its result string (observe/audit).
|
|
13
|
+
* - onStop: fires once when the loop terminates with finishReason 'stop'.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface ToolUse {
|
|
17
|
+
name: string;
|
|
18
|
+
args: any;
|
|
19
|
+
}
|
|
20
|
+
interface PreToolUseDecision {
|
|
21
|
+
block?: boolean;
|
|
22
|
+
reason?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Per-call metadata threaded through dispatch — `id` is the model-supplied tool_call id,
|
|
25
|
+
* letting a host correlate a result to its call without assuming serial dispatch order. */
|
|
26
|
+
interface ToolUseMeta {
|
|
27
|
+
id?: string;
|
|
28
|
+
}
|
|
29
|
+
interface Hooks {
|
|
30
|
+
/** Guard a tool call before it runs; return `{ block, reason }` to skip it. */
|
|
31
|
+
preToolUse?(call: ToolUse, meta?: ToolUseMeta): Promise<PreToolUseDecision | void> | PreToolUseDecision | void;
|
|
32
|
+
/** Observe a tool's result after it ran (audit, metrics, side-channel). */
|
|
33
|
+
postToolUse?(call: ToolUse, result: string, meta?: ToolUseMeta): void | Promise<void>;
|
|
34
|
+
/** Observe a tool's INCREMENTAL output while it runs (only tools that stream, e.g. the real
|
|
35
|
+
* Shell). Fire-and-forget — sync, never awaited, never alters the result. */
|
|
36
|
+
onToolOutput?(call: ToolUse, chunk: string, meta?: ToolUseMeta): void;
|
|
37
|
+
/** Fired once when the agent loop stops cleanly with the model's final text. */
|
|
38
|
+
onStop?(finalText: string): void;
|
|
39
|
+
/** Fired once at session start (a fresh `run()`, or the first `send()`). Return a string to inject
|
|
40
|
+
* as extra system context (e.g. environment facts, project status). Multiple hooks concatenate. */
|
|
41
|
+
onSessionStart?(): string | void | Promise<string | void>;
|
|
42
|
+
/** Fired per user turn BEFORE the model call. Return a string to replace the submitted text
|
|
43
|
+
* (rewrite/annotate the prompt); chained across hooks. Return void to leave it unchanged. */
|
|
44
|
+
onUserPromptSubmit?(text: string): string | void | Promise<string | void>;
|
|
45
|
+
/** Fired before a manual `compactNow()` folds older messages — observe (e.g. persist a summary). */
|
|
46
|
+
onPreCompact?(messages: Message[]): void | Promise<void>;
|
|
47
|
+
/** Fired when a `Task`/`TaskBatch` child agent finishes, with its summary + label. */
|
|
48
|
+
onSubagentStop?(summary: string, info?: {
|
|
49
|
+
label?: string;
|
|
50
|
+
agentType?: string;
|
|
51
|
+
}): void | Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
/** Deterministic Hooks for tests/dev: records calls + scripts block decisions by tool name. */
|
|
54
|
+
declare class RecordingHooks implements Hooks {
|
|
55
|
+
private blocks;
|
|
56
|
+
pre: Array<{
|
|
57
|
+
call: ToolUse;
|
|
58
|
+
meta?: ToolUseMeta;
|
|
59
|
+
}>;
|
|
60
|
+
post: Array<{
|
|
61
|
+
call: ToolUse;
|
|
62
|
+
result: string;
|
|
63
|
+
meta?: ToolUseMeta;
|
|
64
|
+
}>;
|
|
65
|
+
outputs: Array<{
|
|
66
|
+
call: ToolUse;
|
|
67
|
+
chunk: string;
|
|
68
|
+
meta?: ToolUseMeta;
|
|
69
|
+
}>;
|
|
70
|
+
stops: string[];
|
|
71
|
+
/** tool name -> reason; a matching preToolUse call is blocked with that reason. */
|
|
72
|
+
constructor(blocks?: Record<string, string>);
|
|
73
|
+
preToolUse(call: ToolUse, meta?: ToolUseMeta): PreToolUseDecision | void;
|
|
74
|
+
postToolUse(call: ToolUse, result: string, meta?: ToolUseMeta): void;
|
|
75
|
+
onToolOutput(call: ToolUse, chunk: string, meta?: ToolUseMeta): void;
|
|
76
|
+
onStop(finalText: string): void;
|
|
77
|
+
}
|
|
78
|
+
/** Recording lifecycle hooks for tests: capture session-start/prompt-submit/pre-compact + script transforms. */
|
|
79
|
+
declare class RecordingLifecycle implements Hooks {
|
|
80
|
+
private startContext?;
|
|
81
|
+
private rewrite?;
|
|
82
|
+
starts: number;
|
|
83
|
+
prompts: string[];
|
|
84
|
+
compactions: number[];
|
|
85
|
+
/** @param startContext injected at session start; @param rewrite maps a submitted prompt to a new one. */
|
|
86
|
+
constructor(startContext?: string | undefined, rewrite?: ((t: string) => string) | undefined);
|
|
87
|
+
subagentStops: Array<{
|
|
88
|
+
summary: string;
|
|
89
|
+
label?: string;
|
|
90
|
+
}>;
|
|
91
|
+
onSessionStart(): string | void;
|
|
92
|
+
onUserPromptSubmit(text: string): string | void;
|
|
93
|
+
onPreCompact(messages: Message[]): void;
|
|
94
|
+
onSubagentStop(summary: string, info?: {
|
|
95
|
+
label?: string;
|
|
96
|
+
}): void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Permissions & plan-mode — safe-autonomy controls built ENTIRELY on the hooks +
|
|
101
|
+
* host seams (no Agent-core changes needed). Two pieces:
|
|
102
|
+
* - PermissionPolicy → Hooks: allow / ask (host.confirm) / deny a tool call by tool
|
|
103
|
+
* name + path glob. First matching rule wins; default decision otherwise.
|
|
104
|
+
* - planMode(): blocks mutating tools until the agent presents a plan via `ExitPlanMode`
|
|
105
|
+
* and the host approves — Claude-Code-style plan gating.
|
|
106
|
+
*/
|
|
107
|
+
type Decision = 'allow' | 'ask' | 'deny';
|
|
108
|
+
interface PermissionRule {
|
|
109
|
+
/** tool name to match (omit = any tool). */
|
|
110
|
+
tool?: string;
|
|
111
|
+
/** glob on the call's `path` arg to match (omit = any/none). */
|
|
112
|
+
pathGlob?: string;
|
|
113
|
+
decision: Decision;
|
|
114
|
+
}
|
|
115
|
+
declare class PermissionOptions {
|
|
116
|
+
rules: PermissionRule[];
|
|
117
|
+
/** decision when no rule matches. */
|
|
118
|
+
default: Decision;
|
|
119
|
+
/** host channel for `ask` (confirm). If absent, `ask` is treated as `deny` (fail-closed). */
|
|
120
|
+
host?: HostBridge;
|
|
121
|
+
/** Richer ask resolver (CLI surfaces a Yes/No/Always menu). Returns the decision + whether to
|
|
122
|
+
* remember it — on remember, the policy adds a session rule so the same tool isn't re-asked.
|
|
123
|
+
* If unset, falls back to `host.confirm` (plain yes/no). */
|
|
124
|
+
ask?(call: ToolUse): Promise<{
|
|
125
|
+
decision: 'allow' | 'deny';
|
|
126
|
+
remember?: boolean;
|
|
127
|
+
} | undefined>;
|
|
128
|
+
}
|
|
129
|
+
declare class PermissionPolicy {
|
|
130
|
+
options: PermissionOptions;
|
|
131
|
+
constructor(options?: Partial<PermissionOptions>);
|
|
132
|
+
/** Resolve the decision for a tool call (first matching rule, else default). */
|
|
133
|
+
decide(call: ToolUse): Decision;
|
|
134
|
+
/** A preToolUse hook enforcing this policy (deny/ask → block; allow → proceed). */
|
|
135
|
+
hooks(): Hooks;
|
|
136
|
+
}
|
|
137
|
+
/** Tools the agent must not run before the plan is approved (sensible default). */
|
|
138
|
+
declare const DEFAULT_MUTATING: string[];
|
|
139
|
+
/**
|
|
140
|
+
* Plan-mode: mutating tools are BLOCKED until the agent calls `ExitPlanMode` with a plan
|
|
141
|
+
* and (if a host is present) the user approves it. Returns the gating hook + the tool to
|
|
142
|
+
* add to the agent. Compose the hook with others via composeHooks().
|
|
143
|
+
*/
|
|
144
|
+
declare function planMode(opts?: {
|
|
145
|
+
mutating?: string[];
|
|
146
|
+
host?: HostBridge;
|
|
147
|
+
}): {
|
|
148
|
+
hooks: Hooks;
|
|
149
|
+
tool: AgentTool;
|
|
150
|
+
};
|
|
151
|
+
/** Run several Hooks as one: preToolUse stops at the first block; post/stop all fire. */
|
|
152
|
+
declare function composeHooks(...list: (Hooks | undefined)[]): Hooks;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Normalized reasoning/thinking control → provider-specific request fragment.
|
|
156
|
+
*
|
|
157
|
+
* Pure & dependency-free (no `ai.libx.js` import — provider is detected from the
|
|
158
|
+
* `provider/model` prefix), so it stays inside the Agent's structural-typing boundary.
|
|
159
|
+
* The returned fragment is spread into `ai.chat()`; callers never hand-craft the
|
|
160
|
+
* per-provider encoding (Anthropic `thinking.budget_tokens`, OpenAI `reasoning_effort`).
|
|
161
|
+
*/
|
|
162
|
+
/** `'off'`/undefined = no reasoning; named buckets; or a raw token budget (budget-based providers). */
|
|
163
|
+
type ReasoningEffort = 'off' | 'low' | 'medium' | 'high' | number;
|
|
164
|
+
interface ChatFragment {
|
|
165
|
+
providerOptions?: Record<string, unknown>;
|
|
166
|
+
/** Request `max_tokens`. Raised for budget-based providers so `budget_tokens < max_tokens` holds. */
|
|
167
|
+
maxTokens?: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Map a normalized effort to a partial `ChatOptions` fragment for `model`'s provider.
|
|
171
|
+
* Returns `{}` (safe no-op) for `'off'`, undefined, or an unsupported provider.
|
|
172
|
+
*
|
|
173
|
+
* Supported: `anthropic/*` (extended thinking), `openai/*` (reasoning_effort).
|
|
174
|
+
* `google/*` is intentionally NOT mapped here — its adapter `Object.assign`s
|
|
175
|
+
* providerOptions over the built `generationConfig`, clobbering it; pass an
|
|
176
|
+
* explicit `providerOptions` if you need Gemini thinking.
|
|
177
|
+
*/
|
|
178
|
+
declare function reasoningToChatFragment(model: string, effort?: ReasoningEffort): ChatFragment;
|
|
179
|
+
|
|
180
|
+
interface RunResult {
|
|
181
|
+
text: string;
|
|
182
|
+
steps: number;
|
|
183
|
+
/** Why the loop ended. The middle group are automatic kill-switches (budget/abuse guards). */
|
|
184
|
+
finishReason: 'stop' | 'max_steps' | 'budget' | 'timeout' | 'loop' | 'max_tool_calls' | 'aborted' | 'error';
|
|
185
|
+
messages: Message[];
|
|
186
|
+
/** Accumulated token usage across all turns (non-stream path). With prompt caching,
|
|
187
|
+
* promptTokens includes cached reads/writes; the cache splits ride along for exact pricing. */
|
|
188
|
+
usage?: {
|
|
189
|
+
promptTokens: number;
|
|
190
|
+
completionTokens: number;
|
|
191
|
+
totalTokens: number;
|
|
192
|
+
cacheCreationTokens?: number;
|
|
193
|
+
cacheReadTokens?: number;
|
|
194
|
+
};
|
|
195
|
+
/** True if ANY turn's usage was estimated (provider gave none) rather than exact — lets the UI mark cost `~`. */
|
|
196
|
+
usageEstimated?: boolean;
|
|
197
|
+
error?: unknown;
|
|
198
|
+
}
|
|
199
|
+
declare class AgentOptions {
|
|
200
|
+
/** Any ai.libx.js AIClient (or a FakeAIClient). */
|
|
201
|
+
ai: ChatLike;
|
|
202
|
+
/** Filesystem backend — MemFilesystem, NodeDiskFilesystem, IndexedDbFilesystem, …
|
|
203
|
+
* OPTIONAL: if omitted, the agent lazily defaults to a JAILED real disk rooted at `process.cwd()`
|
|
204
|
+
* (secrets hidden by DEFAULT_DENY) — resolved via a dynamic node import at first run, so the edge/
|
|
205
|
+
* browser build (which always passes its own fs) never pulls in `node:fs`. Pass `fs` explicitly to
|
|
206
|
+
* use any other backend (and to keep full control of the jail). */
|
|
207
|
+
fs?: IFilesystem;
|
|
208
|
+
model: string;
|
|
209
|
+
systemPrompt: string;
|
|
210
|
+
tools: AgentTool[];
|
|
211
|
+
maxSteps: number;
|
|
212
|
+
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
213
|
+
* (their real price) so a fat cached context doesn't trip the guard on healthy turns. 0 = unbounded. */
|
|
214
|
+
maxTokens: number;
|
|
215
|
+
/** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
|
|
216
|
+
timeoutMs: number;
|
|
217
|
+
/** Stop if the identical tool-call batch (name+args) repeats this many times in a row. 0 = off. */
|
|
218
|
+
maxRepeats: number;
|
|
219
|
+
/** Cumulative cap on tool calls dispatched across the run. 0 = unbounded. */
|
|
220
|
+
maxToolCalls: number;
|
|
221
|
+
/** External cancellation — abort the loop between steps (e.g. a UI "Cancel" button). */
|
|
222
|
+
signal?: AbortSignal;
|
|
223
|
+
/** 0 = never trim. Otherwise cap the messages sent per turn (system + most recent). */
|
|
224
|
+
maxContextMessages: number;
|
|
225
|
+
/** Note-taking: keep the most-recent N tool-result outputs verbatim; collapse OLDER ones to a one-line
|
|
226
|
+
* stub in the sent context (the model already consumed them — it can re-Read/re-run). The stored
|
|
227
|
+
* transcript is never mutated. 0 = keep all verbatim. */
|
|
228
|
+
keepToolOutputs: number;
|
|
229
|
+
/** Token-aware backstop (~4 chars/token estimate). After note-taking, drop oldest messages from the
|
|
230
|
+
* sent context until the estimate is under this ceiling (pairing-safe). 0 = off. */
|
|
231
|
+
maxContextTokens: number;
|
|
232
|
+
/** Pagination ceiling for a SINGLE tool result (bytes). A result over this is cropped to page 1 with
|
|
233
|
+
* a marker telling the model it was cropped (refine the query, or page further). Guards against one
|
|
234
|
+
* Grep/Read/MCP call blowing the whole context window. 0 = off. Default 60k (~15k tokens). */
|
|
235
|
+
maxToolResultBytes: number;
|
|
236
|
+
/** Hook to handle an oversized tool result instead of the default lossy crop: receives the FULL output
|
|
237
|
+
* and returns the (cropped) string to put in context — e.g. spill to scratch and return a recoverable,
|
|
238
|
+
* paginated stub. Called only when a result exceeds `maxToolResultBytes`. */
|
|
239
|
+
capToolResult?: (full: string, info: {
|
|
240
|
+
tool: string;
|
|
241
|
+
args: any;
|
|
242
|
+
}) => string | Promise<string>;
|
|
243
|
+
/** VFS dir(s) of skills (`<dir>/<id>/SKILL.md`). If set: inject a catalog + add the `Skill` tool. Multiple dirs are merged (first wins on name collisions). */
|
|
244
|
+
skillsDir?: string | string[];
|
|
245
|
+
/** VFS dir(s) of slash-command templates (`<dir>/<name>.md`). If set: inject a catalog + add the `SlashCommand` tool. Multiple dirs are merged (first wins). */
|
|
246
|
+
commandsDir?: string | string[];
|
|
247
|
+
/** VFS dir(s) of memory (`<dir>/MEMORY.md`). If set: inject the index at run start (persistence = backend).
|
|
248
|
+
* Multiple dirs are merged (reads search all; writes go to first). */
|
|
249
|
+
memoryDir?: string | string[];
|
|
250
|
+
/** User-scope memory dir for global facts (type=user/feedback). Remember routes by type when set. */
|
|
251
|
+
memoryUserDir?: string;
|
|
252
|
+
/** Filenames to discover as project instructions (e.g. `AGENT.md`, `AGENTS.md`, `CLAUDE.md`).
|
|
253
|
+
* Walks the VFS tree and merges all found files (general → specific, like Claude Code).
|
|
254
|
+
* `true` (default) = auto-discover standard names. `string[]` = custom names. `false` = skip. */
|
|
255
|
+
instructionFiles: boolean | string[];
|
|
256
|
+
/** Host interaction channel (human-in-the-loop). If set: adds the `AskUserQuestion` tool. */
|
|
257
|
+
host?: HostBridge;
|
|
258
|
+
/** Add the `AskUserQuestion` tool when a host is present (default true). Set false for an agent that
|
|
259
|
+
* must never block a turn on a structured question — e.g. a voice reflex that confirms inline. */
|
|
260
|
+
askUserQuestion: boolean;
|
|
261
|
+
/** Deterministic interception points around tool execution (pre/post/stop). */
|
|
262
|
+
hooks?: Hooks;
|
|
263
|
+
/** If true: add the `Task` tool so the agent can spawn depth-limited child agents over the VFS. */
|
|
264
|
+
subagents: boolean;
|
|
265
|
+
/** VFS dir of typed-subagent defs (`<dir>/<name>.md`). If set with `subagents`: inject a catalog + enable the `Task` `agentType` param. */
|
|
266
|
+
agentsDir?: string;
|
|
267
|
+
/** Current recursion depth (0 = top-level); spawned children run at depth+1. */
|
|
268
|
+
depth: number;
|
|
269
|
+
/** Hard ceiling on subagent nesting (beyond it the `Task` tool refuses to spawn). */
|
|
270
|
+
maxDepth: number;
|
|
271
|
+
/** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
|
|
272
|
+
stream: boolean;
|
|
273
|
+
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
274
|
+
compaction?: {
|
|
275
|
+
maxMessages: number;
|
|
276
|
+
};
|
|
277
|
+
/** Add `Checkpoint`/`Rollback` tools (requires the fs to be an OverlayFilesystem). */
|
|
278
|
+
checkpoints: boolean;
|
|
279
|
+
/** Enable `bash({background:true})` + JobOutput/JobStatus/JobKill — sandbox background jobs (overlay-isolated,
|
|
280
|
+
* committed on completion, drained at turn end). Useful when the VFS backend is slow (remote) or for sub-agents. */
|
|
281
|
+
backgroundJobs: boolean;
|
|
282
|
+
/** Plan mode: block mutating tools until the agent calls `ExitPlanMode` (host-approved). */
|
|
283
|
+
planMode: boolean;
|
|
284
|
+
/** Permission policy gating each tool call (allow / ask / deny). */
|
|
285
|
+
permissions?: PermissionPolicy;
|
|
286
|
+
/** Opt-in syntax guardrail: refuse to persist a syntactically-broken code-file write/edit. Default off. */
|
|
287
|
+
lintOnWrite?: boolean;
|
|
288
|
+
/** Optional PDF text extraction for Read on .pdf files (node hosts wire pdftotext); absent => Read explains. */
|
|
289
|
+
pdfText?: (path: string) => Promise<string>;
|
|
290
|
+
/** Opt-in: after a write-class tool runs, run `command` over the VFS and append any failure to the tool result.
|
|
291
|
+
* `tools` defaults to ['Write','Edit','MultiEdit','ApplyEdits']. */
|
|
292
|
+
autoTest?: {
|
|
293
|
+
command: string;
|
|
294
|
+
tools?: string[];
|
|
295
|
+
};
|
|
296
|
+
/** Provider-specific options forwarded to ai.chat() (e.g. cursor mcpServers, cwd). */
|
|
297
|
+
providerOptions?: Record<string, unknown>;
|
|
298
|
+
/** Prompt caching (providers that support it, e.g. Anthropic): cache tools/system/conversation
|
|
299
|
+
* prefix across the loop's steps — reads cost 0.1x, writes 1.25x. A multi-step agent loop
|
|
300
|
+
* re-sends its whole prefix every step, so this is a large net cost cut. Default on. */
|
|
301
|
+
promptCache: boolean;
|
|
302
|
+
/** Tool selection mode: 'auto' = model decides (needed for Groq); undefined = provider default. */
|
|
303
|
+
toolChoice?: 'auto' | 'required' | 'none';
|
|
304
|
+
/** Extended-thinking / reasoning effort, normalized across providers (anthropic, openai).
|
|
305
|
+
* `'off'`/undefined = none; `'low'|'medium'|'high'` or a raw token budget. Mapped to the
|
|
306
|
+
* provider-specific request shape via {@link reasoningToChatFragment}; explicit `providerOptions` wins. */
|
|
307
|
+
reasoning?: ReasoningEffort;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The agentic loop: chat() -> dispatch tool_calls over the VFS -> thread results
|
|
311
|
+
* back -> repeat until the model stops (no tool calls) or maxSteps is hit.
|
|
312
|
+
*/
|
|
313
|
+
declare class Agent {
|
|
314
|
+
options: AgentOptions;
|
|
315
|
+
transcript: Message[];
|
|
316
|
+
private ctx;
|
|
317
|
+
private lastTrimNotified;
|
|
318
|
+
private activeTools;
|
|
319
|
+
private activeHooks?;
|
|
320
|
+
private prepared;
|
|
321
|
+
private systemPromptCache;
|
|
322
|
+
private started;
|
|
323
|
+
private parkedMs;
|
|
324
|
+
/** Time a human-blocking await (a permission/plan prompt) and bank it in `parkedMs` so idle prompt
|
|
325
|
+
* time never trips the wall-clock kill-switch. The agent did no work while parked on the user. */
|
|
326
|
+
private park;
|
|
327
|
+
/** Force the next `send()`/`run()` to rebuild the system prompt, tools, plan-mode and permission hooks
|
|
328
|
+
* from `options` — apply mid-conversation changes to `planMode`/`permissions`/`model` etc. (prepare()
|
|
329
|
+
* is otherwise memoized per conversation). */
|
|
330
|
+
reprepare(): void;
|
|
331
|
+
/** Tools injected via addTools(); kept separate from options.tools so prepare() rebuilds don't drop them. */
|
|
332
|
+
private injectedTools;
|
|
333
|
+
/** Current tool surface for hosts/UIs (configured + injected, deduped by name) — options.tools
|
|
334
|
+
* alone misses addTools()-injected ones (e.g. MCP servers mounted after startup). */
|
|
335
|
+
get toolNames(): string[];
|
|
336
|
+
/** Inject tools into a running agent (e.g. dynamically mounted MCP servers). Takes effect on the next turn
|
|
337
|
+
* and survives prepare() rebuilds (reprepare(), new conversations). */
|
|
338
|
+
addTools(tools: AgentTool[]): void;
|
|
339
|
+
/** Remove tools by name from a running agent. Returns the count removed. Also filters
|
|
340
|
+
* `options.tools` — otherwise a constructor-passed tool resurrects on the next prepare()
|
|
341
|
+
* rebuild (and re-adding it via addTools() would then DUPLICATE it in the wire schema). */
|
|
342
|
+
removeTools(names: Set<string> | string[]): number;
|
|
343
|
+
constructor(options?: Partial<AgentOptions>);
|
|
344
|
+
/** Build the tool context from the resolved fs + options. Idempotent-safe: called once (ctor or ensureFs). */
|
|
345
|
+
private buildCtx;
|
|
346
|
+
/**
|
|
347
|
+
* Resolve the filesystem + build the tool context if the ctor couldn't (no `fs` was passed). The disk
|
|
348
|
+
* default lives HERE, not in the ctor: the ctor can't await, and we must avoid a STATIC `node:fs` import
|
|
349
|
+
* in the core (edge/browser would break). The dynamic import only fires when `fs` is omitted — edge code
|
|
350
|
+
* always passes its own `MemFilesystem`, so the node module is never reached. Default = jailed disk @ cwd.
|
|
351
|
+
*/
|
|
352
|
+
private ensureFs;
|
|
353
|
+
/**
|
|
354
|
+
* Assemble the system prompt + active tools/hooks ONCE per conversation (memoized).
|
|
355
|
+
* `run()` resets the memo to start fresh; `send()` reuses it, so multi-turn state —
|
|
356
|
+
* plan-mode approval, the tool set, the skills/commands/memory snapshot — stays stable
|
|
357
|
+
* across turns (and the frozen prompt pairs well with prompt caching). Does NOT touch
|
|
358
|
+
* the transcript; `run`/`send` own that.
|
|
359
|
+
*/
|
|
360
|
+
private prepare;
|
|
361
|
+
/** Single-shot: reset all per-conversation state and run `task` to completion. `task` may be plain
|
|
362
|
+
* text or multimodal content parts (text + images). */
|
|
363
|
+
run(task: MessageContent): Promise<RunResult>;
|
|
364
|
+
/** Apply onUserPromptSubmit to a turn: rewrite plain text; pass multimodal content through untouched. */
|
|
365
|
+
private applyPromptSubmit;
|
|
366
|
+
/** Fire onSessionStart once per conversation; returns any injected context. */
|
|
367
|
+
private fireSessionStart;
|
|
368
|
+
/** Fire onUserPromptSubmit; returns the (possibly rewritten) prompt text. */
|
|
369
|
+
private fireUserPromptSubmit;
|
|
370
|
+
/**
|
|
371
|
+
* Multi-turn: continue the existing conversation by appending a user turn instead of
|
|
372
|
+
* resetting — this is what makes an interactive REPL feel like one conversation. The
|
|
373
|
+
* leading system message is (re)synced to the current prompt, so a resumed session whose
|
|
374
|
+
* tools were rebuilt gets a matching catalog rather than a stale one.
|
|
375
|
+
*/
|
|
376
|
+
send(task: MessageContent): Promise<RunResult>;
|
|
377
|
+
/**
|
|
378
|
+
* Fold the conversation in place (manual `/compact`): keep the system message + the
|
|
379
|
+
* most-recent window, summarizing the dropped middle (deterministic, no LLM call).
|
|
380
|
+
* No-op when the transcript already fits. Returns the number of messages removed.
|
|
381
|
+
* `focus` (e.g. from `/compact keep the API details`) preserves matching lines from the
|
|
382
|
+
* dropped span verbatim in the summary, instead of losing them to the generic recap.
|
|
383
|
+
*/
|
|
384
|
+
compactNow(maxMessages?: number, focus?: string): number;
|
|
385
|
+
private runLoop;
|
|
386
|
+
/**
|
|
387
|
+
* Drain a streamed chat() response: emit each text delta to the host
|
|
388
|
+
* (`{kind:'text_delta'}`) and fold all chunks back into the single
|
|
389
|
+
* ChatResponse the non-stream path would have returned.
|
|
390
|
+
*/
|
|
391
|
+
private consumeStream;
|
|
392
|
+
private dispatch;
|
|
393
|
+
private static readonly WRITE_CLASS;
|
|
394
|
+
/** Append an autoTest failure section to a write-class tool result, if configured. */
|
|
395
|
+
private maybeAutoTest;
|
|
396
|
+
/**
|
|
397
|
+
* Shape the per-turn context (a VIEW — the stored transcript is never mutated). Layered, each off by default:
|
|
398
|
+
* 1. coarse reduction — `compaction` (fold the dropped middle into a synthetic summary) OR
|
|
399
|
+
* `maxContextMessages` (pure drop-oldest), keeping the system message + most-recent window.
|
|
400
|
+
* 2. note-taking (`keepToolOutputs`) — collapse all-but-the-recent-N tool-result bodies to one-line stubs
|
|
401
|
+
* (kills "immortal tool results": a big Read/Grep stops costing its full weight once it's old).
|
|
402
|
+
* 3. token-aware backstop (`maxContextTokens`) — drop oldest messages until under an estimated token ceiling.
|
|
403
|
+
* With every knob off, returns the transcript unchanged (same reference).
|
|
404
|
+
*/
|
|
405
|
+
trimContext(): Message[];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export { Agent as A, type ChatFragment as C, DEFAULT_MUTATING as D, type Hooks as H, PermissionOptions as P, type ReasoningEffort as R, type ToolUse as T, AgentOptions as a, type Decision as b, PermissionPolicy as c, type PermissionRule as d, type PreToolUseDecision as e, RecordingHooks as f, RecordingLifecycle as g, type RunResult as h, type ToolUseMeta as i, composeHooks as j, planMode as p, reasoningToChatFragment as r };
|