agent.libx.js 0.86.0
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 +113 -0
- package/dist/Agent-WTkHB8RY.d.ts +319 -0
- package/dist/cli.d.ts +164 -0
- package/dist/cli.js +6876 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +621 -0
- package/dist/index.js +3605 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-Dg3vA1Uj.d.ts +42 -0
- package/dist/mcp.client.d.ts +121 -0
- package/dist/mcp.client.js +261 -0
- package/dist/mcp.client.js.map +1 -0
- package/dist/tools-Ch-OzOU8.d.ts +255 -0
- package/dist/tools.shell.d.ts +80 -0
- package/dist/tools.shell.js +402 -0
- package/dist/tools.shell.js.map +1 -0
- package/package.json +79 -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,113 @@
|
|
|
1
|
+
# agent.libx.js
|
|
2
|
+
|
|
3
|
+
**A coding agent on par with Claude Code — that also runs where Claude Code can't.**
|
|
4
|
+
|
|
5
|
+
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:
|
|
6
|
+
|
|
7
|
+
- **LLM** → any model via [`ai.libx.js`](https://github.com/Livshitz/ai.libx.js) (`AIClient.chat`, OpenAI-style tools/streaming).
|
|
8
|
+
- **Filesystem** → a pluggable `IFilesystem` (real disk, in-memory, IndexedDB, a database, hybrid mounts) from [`wcli`](https://github.com/Livshitz/wcli)'s headless core.
|
|
9
|
+
|
|
10
|
+
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).
|
|
11
|
+
|
|
12
|
+
Claude Code is the floor; running isolated, on the edge, or hybrid is the ceiling.
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun install # links wcli (file:), ai.libx.js + libx.js (bun link)
|
|
18
|
+
bun test # 34 unit/integration tests (no API key needed)
|
|
19
|
+
ANTHROPIC_API_KEY=… bun examples/run-sonnet.ts # drive a real model
|
|
20
|
+
bun eval/run.ts # quantitative eval scorecard (real model)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use it
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { AIClient } from 'ai.libx.js';
|
|
27
|
+
import { Agent, MemFilesystem } from 'agent.libx.js';
|
|
28
|
+
|
|
29
|
+
const fs = new MemFilesystem(); // or NodeDiskFilesystem(dir) — interchangeable
|
|
30
|
+
await fs.createDir('/src');
|
|
31
|
+
await fs.writeFile('/src/x.ts', 'export const add = (a,b) => a - b;\n');
|
|
32
|
+
|
|
33
|
+
const ai = new AIClient({ apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY } });
|
|
34
|
+
const res = await new Agent({ ai, fs, model: 'anthropic/claude-sonnet-4-6' })
|
|
35
|
+
.run('Fix the add bug in /src/x.ts');
|
|
36
|
+
|
|
37
|
+
console.log(res.finishReason, await fs.readFile('/src/x.ts'));
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Tools the agent gets
|
|
41
|
+
|
|
42
|
+
- **`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).
|
|
43
|
+
- **`Read`** — 1-indexed numbered lines, `offset`/`limit`.
|
|
44
|
+
- **`Edit`** — exact unique-substring replace, with a read-before-edit staleness guard.
|
|
45
|
+
- **`Grep`/`Glob`/`Write`/`MultiEdit`** — structured, typed results straight from the VFS (no `bash` parsing). The selectable tool set the self-evolution loop mutates over.
|
|
46
|
+
- **`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.
|
|
47
|
+
- **`WebFetch`/`WebSearch`** — opt-in network tools (not in the default set): fetch a URL as readable text, or search via a configured provider (`TAVILY_API_KEY`). Enable per project with `tools: [...,'WebFetch']` in config. Factory-built with an injectable `fetch`, so they stay edge-portable and testable.
|
|
48
|
+
|
|
49
|
+
## Agentic subsystems
|
|
50
|
+
|
|
51
|
+
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)):
|
|
52
|
+
|
|
53
|
+
- **Skills + memory** — VFS-backed (`skillsDir`/`memoryDir`); persistence is just the backend choice.
|
|
54
|
+
- **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`).
|
|
55
|
+
- **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.
|
|
56
|
+
- **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.
|
|
57
|
+
- **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).
|
|
58
|
+
|
|
59
|
+
## The `agentx` CLI
|
|
60
|
+
|
|
61
|
+
A dependency-light `readline` REPL (plus headless `-p` mode) over the runtime:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
agentx # interactive REPL in the current dir
|
|
65
|
+
agentx "fix the bug in x" # run once and exit
|
|
66
|
+
agentx -c "keep going" # continue the most recent session
|
|
67
|
+
agentx --resume <id> "…" # resume a specific session
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **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. (`/sandbox` shows the active mode.)
|
|
71
|
+
- **Sessions** — every conversation persists to `./.agent/sessions/<id>.json`; `--continue`/`--resume` (and `/sessions`, `/resume`) pick it back up, *with memory across turns* — a REPL turn sees the previous one.
|
|
72
|
+
- **Diffs** — every `Edit`/`Write`/`MultiEdit` renders a colorized `+/-` diff (TTY-gated; plain when piped).
|
|
73
|
+
- **Slash commands** — `/help /tools /model /compact /clear /sessions /resume /commands /init`; user-defined `./.agent/commands/<name>.md` are invokable directly as `/<name>` (the same registry the model's `SlashCommand` tool uses).
|
|
74
|
+
- **Project instructions** — `./AGENTS.md` (or `CLAUDE.md`) auto-loads into every run; `/init` scaffolds one.
|
|
75
|
+
- **Any provider** — set `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `GROQ_API_KEY`; choose with `-m provider/model`.
|
|
76
|
+
- **@-file mentions & headless JSON** — reference files inline in a prompt with `@path` (e.g. `explain @src/Agent.ts`); script with `-p --output-format json` to get one machine-readable result object on stdout (activity stays on stderr).
|
|
77
|
+
- **Tab-completion** — `Tab` completes `/<command>` names and `@<path>` file/dir references (descends subdirs, dotfiles hidden unless typed) straight from the working tree.
|
|
78
|
+
- **MCP servers** — declare `mcpServers: { name: { command, args } | { url } }` in config and they're auto-mounted at startup: 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.
|
|
79
|
+
|
|
80
|
+
## 🧬 It improves itself
|
|
81
|
+
|
|
82
|
+
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:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
champion → propose patch → jailed + sandboxed eval → per-task no-regression gate → ledger → repeat
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
**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.*
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
GENERATIONS=8 bun evolve/loop.ts # evolve → evolve/champion.json + ledger.jsonl
|
|
94
|
+
bun evolve/report.ts # instant replay of the arc (no tokens)
|
|
95
|
+
EVOLVED=1 bun compare/run.ts # evolved champion vs Claude Code
|
|
96
|
+
bun evolve/generalize.ts # baseline vs champion on UNSEEN tasks
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Full design + threat model + results: [`mind/08-self-evolve.md`](./mind/08-self-evolve.md).
|
|
100
|
+
|
|
101
|
+
## Status
|
|
102
|
+
|
|
103
|
+
**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). **112 tests green.**
|
|
104
|
+
|
|
105
|
+
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).
|
|
106
|
+
|
|
107
|
+
## Eval & compare
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bun eval/run.ts # behavioral scorecard (our agent over MemFilesystem)
|
|
111
|
+
bun compare/seed-tasks.ts # materialize task specs into .tmp/tasks/
|
|
112
|
+
bun compare/run.ts # head-to-head vs Claude Code (needs `claude` CLI)
|
|
113
|
+
```
|
|
@@ -0,0 +1,319 @@
|
|
|
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-Ch-OzOU8.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
|
+
/** Fired once when the agent loop stops cleanly with the model's final text. */
|
|
35
|
+
onStop?(finalText: string): void;
|
|
36
|
+
/** Fired once at session start (a fresh `run()`, or the first `send()`). Return a string to inject
|
|
37
|
+
* as extra system context (e.g. environment facts, project status). Multiple hooks concatenate. */
|
|
38
|
+
onSessionStart?(): string | void | Promise<string | void>;
|
|
39
|
+
/** Fired per user turn BEFORE the model call. Return a string to replace the submitted text
|
|
40
|
+
* (rewrite/annotate the prompt); chained across hooks. Return void to leave it unchanged. */
|
|
41
|
+
onUserPromptSubmit?(text: string): string | void | Promise<string | void>;
|
|
42
|
+
/** Fired before a manual `compactNow()` folds older messages — observe (e.g. persist a summary). */
|
|
43
|
+
onPreCompact?(messages: Message[]): void | Promise<void>;
|
|
44
|
+
/** Fired when a `Task`/`TaskBatch` child agent finishes, with its summary + label. */
|
|
45
|
+
onSubagentStop?(summary: string, info?: {
|
|
46
|
+
label?: string;
|
|
47
|
+
agentType?: string;
|
|
48
|
+
}): void | Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/** Deterministic Hooks for tests/dev: records calls + scripts block decisions by tool name. */
|
|
51
|
+
declare class RecordingHooks implements Hooks {
|
|
52
|
+
private blocks;
|
|
53
|
+
pre: Array<{
|
|
54
|
+
call: ToolUse;
|
|
55
|
+
meta?: ToolUseMeta;
|
|
56
|
+
}>;
|
|
57
|
+
post: Array<{
|
|
58
|
+
call: ToolUse;
|
|
59
|
+
result: string;
|
|
60
|
+
meta?: ToolUseMeta;
|
|
61
|
+
}>;
|
|
62
|
+
stops: string[];
|
|
63
|
+
/** tool name -> reason; a matching preToolUse call is blocked with that reason. */
|
|
64
|
+
constructor(blocks?: Record<string, string>);
|
|
65
|
+
preToolUse(call: ToolUse, meta?: ToolUseMeta): PreToolUseDecision | void;
|
|
66
|
+
postToolUse(call: ToolUse, result: string, meta?: ToolUseMeta): void;
|
|
67
|
+
onStop(finalText: string): void;
|
|
68
|
+
}
|
|
69
|
+
/** Recording lifecycle hooks for tests: capture session-start/prompt-submit/pre-compact + script transforms. */
|
|
70
|
+
declare class RecordingLifecycle implements Hooks {
|
|
71
|
+
private startContext?;
|
|
72
|
+
private rewrite?;
|
|
73
|
+
starts: number;
|
|
74
|
+
prompts: string[];
|
|
75
|
+
compactions: number[];
|
|
76
|
+
/** @param startContext injected at session start; @param rewrite maps a submitted prompt to a new one. */
|
|
77
|
+
constructor(startContext?: string | undefined, rewrite?: ((t: string) => string) | undefined);
|
|
78
|
+
subagentStops: Array<{
|
|
79
|
+
summary: string;
|
|
80
|
+
label?: string;
|
|
81
|
+
}>;
|
|
82
|
+
onSessionStart(): string | void;
|
|
83
|
+
onUserPromptSubmit(text: string): string | void;
|
|
84
|
+
onPreCompact(messages: Message[]): void;
|
|
85
|
+
onSubagentStop(summary: string, info?: {
|
|
86
|
+
label?: string;
|
|
87
|
+
}): void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Permissions & plan-mode — safe-autonomy controls built ENTIRELY on the hooks +
|
|
92
|
+
* host seams (no Agent-core changes needed). Two pieces:
|
|
93
|
+
* - PermissionPolicy → Hooks: allow / ask (host.confirm) / deny a tool call by tool
|
|
94
|
+
* name + path glob. First matching rule wins; default decision otherwise.
|
|
95
|
+
* - planMode(): blocks mutating tools until the agent presents a plan via `ExitPlanMode`
|
|
96
|
+
* and the host approves — Claude-Code-style plan gating.
|
|
97
|
+
*/
|
|
98
|
+
type Decision = 'allow' | 'ask' | 'deny';
|
|
99
|
+
interface PermissionRule {
|
|
100
|
+
/** tool name to match (omit = any tool). */
|
|
101
|
+
tool?: string;
|
|
102
|
+
/** glob on the call's `path` arg to match (omit = any/none). */
|
|
103
|
+
pathGlob?: string;
|
|
104
|
+
decision: Decision;
|
|
105
|
+
}
|
|
106
|
+
declare class PermissionOptions {
|
|
107
|
+
rules: PermissionRule[];
|
|
108
|
+
/** decision when no rule matches. */
|
|
109
|
+
default: Decision;
|
|
110
|
+
/** host channel for `ask` (confirm). If absent, `ask` is treated as `deny` (fail-closed). */
|
|
111
|
+
host?: HostBridge;
|
|
112
|
+
/** Richer ask resolver (CLI surfaces a Yes/No/Always menu). Returns the decision + whether to
|
|
113
|
+
* remember it — on remember, the policy adds a session rule so the same tool isn't re-asked.
|
|
114
|
+
* If unset, falls back to `host.confirm` (plain yes/no). */
|
|
115
|
+
ask?(call: ToolUse): Promise<{
|
|
116
|
+
decision: 'allow' | 'deny';
|
|
117
|
+
remember?: boolean;
|
|
118
|
+
} | undefined>;
|
|
119
|
+
}
|
|
120
|
+
declare class PermissionPolicy {
|
|
121
|
+
options: PermissionOptions;
|
|
122
|
+
constructor(options?: Partial<PermissionOptions>);
|
|
123
|
+
/** Resolve the decision for a tool call (first matching rule, else default). */
|
|
124
|
+
decide(call: ToolUse): Decision;
|
|
125
|
+
/** A preToolUse hook enforcing this policy (deny/ask → block; allow → proceed). */
|
|
126
|
+
hooks(): Hooks;
|
|
127
|
+
}
|
|
128
|
+
/** Tools the agent must not run before the plan is approved (sensible default). */
|
|
129
|
+
declare const DEFAULT_MUTATING: string[];
|
|
130
|
+
/**
|
|
131
|
+
* Plan-mode: mutating tools are BLOCKED until the agent calls `ExitPlanMode` with a plan
|
|
132
|
+
* and (if a host is present) the user approves it. Returns the gating hook + the tool to
|
|
133
|
+
* add to the agent. Compose the hook with others via composeHooks().
|
|
134
|
+
*/
|
|
135
|
+
declare function planMode(opts?: {
|
|
136
|
+
mutating?: string[];
|
|
137
|
+
host?: HostBridge;
|
|
138
|
+
}): {
|
|
139
|
+
hooks: Hooks;
|
|
140
|
+
tool: AgentTool;
|
|
141
|
+
};
|
|
142
|
+
/** Run several Hooks as one: preToolUse stops at the first block; post/stop all fire. */
|
|
143
|
+
declare function composeHooks(...list: (Hooks | undefined)[]): Hooks;
|
|
144
|
+
|
|
145
|
+
interface RunResult {
|
|
146
|
+
text: string;
|
|
147
|
+
steps: number;
|
|
148
|
+
/** Why the loop ended. The middle group are automatic kill-switches (budget/abuse guards). */
|
|
149
|
+
finishReason: 'stop' | 'max_steps' | 'budget' | 'timeout' | 'loop' | 'max_tool_calls' | 'aborted' | 'error';
|
|
150
|
+
messages: Message[];
|
|
151
|
+
/** Accumulated token usage across all turns (non-stream path). */
|
|
152
|
+
usage?: {
|
|
153
|
+
promptTokens: number;
|
|
154
|
+
completionTokens: number;
|
|
155
|
+
totalTokens: number;
|
|
156
|
+
};
|
|
157
|
+
/** True if ANY turn's usage was estimated (provider gave none) rather than exact — lets the UI mark cost `~`. */
|
|
158
|
+
usageEstimated?: boolean;
|
|
159
|
+
error?: unknown;
|
|
160
|
+
}
|
|
161
|
+
declare class AgentOptions {
|
|
162
|
+
/** Any ai.libx.js AIClient (or a FakeAIClient). */
|
|
163
|
+
ai: ChatLike;
|
|
164
|
+
/** Filesystem backend — MemFilesystem, NodeDiskFilesystem, IndexedDbFilesystem, …
|
|
165
|
+
* OPTIONAL: if omitted, the agent lazily defaults to a JAILED real disk rooted at `process.cwd()`
|
|
166
|
+
* (secrets hidden by DEFAULT_DENY) — resolved via a dynamic node import at first run, so the edge/
|
|
167
|
+
* browser build (which always passes its own fs) never pulls in `node:fs`. Pass `fs` explicitly to
|
|
168
|
+
* use any other backend (and to keep full control of the jail). */
|
|
169
|
+
fs?: IFilesystem;
|
|
170
|
+
model: string;
|
|
171
|
+
systemPrompt: string;
|
|
172
|
+
tools: AgentTool[];
|
|
173
|
+
maxSteps: number;
|
|
174
|
+
/** Hard ceiling on accumulated tokens (prompt+completion) across the run. 0 = unbounded. */
|
|
175
|
+
maxTokens: number;
|
|
176
|
+
/** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
|
|
177
|
+
timeoutMs: number;
|
|
178
|
+
/** Stop if the identical tool-call batch (name+args) repeats this many times in a row. 0 = off. */
|
|
179
|
+
maxRepeats: number;
|
|
180
|
+
/** Cumulative cap on tool calls dispatched across the run. 0 = unbounded. */
|
|
181
|
+
maxToolCalls: number;
|
|
182
|
+
/** External cancellation — abort the loop between steps (e.g. a UI "Cancel" button). */
|
|
183
|
+
signal?: AbortSignal;
|
|
184
|
+
/** 0 = never trim. Otherwise cap the messages sent per turn (system + most recent). */
|
|
185
|
+
maxContextMessages: number;
|
|
186
|
+
/** Note-taking: keep the most-recent N tool-result outputs verbatim; collapse OLDER ones to a one-line
|
|
187
|
+
* stub in the sent context (the model already consumed them — it can re-Read/re-run). The stored
|
|
188
|
+
* transcript is never mutated. 0 = keep all verbatim. */
|
|
189
|
+
keepToolOutputs: number;
|
|
190
|
+
/** Token-aware backstop (~4 chars/token estimate). After note-taking, drop oldest messages from the
|
|
191
|
+
* sent context until the estimate is under this ceiling (pairing-safe). 0 = off. */
|
|
192
|
+
maxContextTokens: number;
|
|
193
|
+
/** 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). */
|
|
194
|
+
skillsDir?: string | string[];
|
|
195
|
+
/** 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). */
|
|
196
|
+
commandsDir?: string | string[];
|
|
197
|
+
/** VFS dir of memory (`<dir>/MEMORY.md`). If set: inject the index at run start (persistence = backend). */
|
|
198
|
+
memoryDir?: string;
|
|
199
|
+
/** Filenames to discover as project instructions (e.g. `AGENT.md`, `AGENTS.md`, `CLAUDE.md`).
|
|
200
|
+
* Walks the VFS tree and merges all found files (general → specific, like Claude Code).
|
|
201
|
+
* `true` (default) = auto-discover standard names. `string[]` = custom names. `false` = skip. */
|
|
202
|
+
instructionFiles: boolean | string[];
|
|
203
|
+
/** Host interaction channel (human-in-the-loop). If set: adds the `AskUserQuestion` tool. */
|
|
204
|
+
host?: HostBridge;
|
|
205
|
+
/** Deterministic interception points around tool execution (pre/post/stop). */
|
|
206
|
+
hooks?: Hooks;
|
|
207
|
+
/** If true: add the `Task` tool so the agent can spawn depth-limited child agents over the VFS. */
|
|
208
|
+
subagents: boolean;
|
|
209
|
+
/** VFS dir of typed-subagent defs (`<dir>/<name>.md`). If set with `subagents`: inject a catalog + enable the `Task` `agentType` param. */
|
|
210
|
+
agentsDir?: string;
|
|
211
|
+
/** Current recursion depth (0 = top-level); spawned children run at depth+1. */
|
|
212
|
+
depth: number;
|
|
213
|
+
/** Hard ceiling on subagent nesting (beyond it the `Task` tool refuses to spawn). */
|
|
214
|
+
maxDepth: number;
|
|
215
|
+
/** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
|
|
216
|
+
stream: boolean;
|
|
217
|
+
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
218
|
+
compaction?: {
|
|
219
|
+
maxMessages: number;
|
|
220
|
+
};
|
|
221
|
+
/** Add `Checkpoint`/`Rollback` tools (requires the fs to be an OverlayFilesystem). */
|
|
222
|
+
checkpoints: boolean;
|
|
223
|
+
/** Enable `bash({background:true})` + JobOutput/JobStatus/JobKill — sandbox background jobs (overlay-isolated,
|
|
224
|
+
* committed on completion, drained at turn end). Useful when the VFS backend is slow (remote) or for sub-agents. */
|
|
225
|
+
backgroundJobs: boolean;
|
|
226
|
+
/** Plan mode: block mutating tools until the agent calls `ExitPlanMode` (host-approved). */
|
|
227
|
+
planMode: boolean;
|
|
228
|
+
/** Permission policy gating each tool call (allow / ask / deny). */
|
|
229
|
+
permissions?: PermissionPolicy;
|
|
230
|
+
/** Opt-in syntax guardrail: refuse to persist a syntactically-broken code-file write/edit. Default off. */
|
|
231
|
+
lintOnWrite?: boolean;
|
|
232
|
+
/** Opt-in: after a write-class tool runs, run `command` over the VFS and append any failure to the tool result.
|
|
233
|
+
* `tools` defaults to ['Write','Edit','MultiEdit','ApplyEdits']. */
|
|
234
|
+
autoTest?: {
|
|
235
|
+
command: string;
|
|
236
|
+
tools?: string[];
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The agentic loop: chat() -> dispatch tool_calls over the VFS -> thread results
|
|
241
|
+
* back -> repeat until the model stops (no tool calls) or maxSteps is hit.
|
|
242
|
+
*/
|
|
243
|
+
declare class Agent {
|
|
244
|
+
options: AgentOptions;
|
|
245
|
+
transcript: Message[];
|
|
246
|
+
private ctx;
|
|
247
|
+
private activeTools;
|
|
248
|
+
private activeHooks?;
|
|
249
|
+
private prepared;
|
|
250
|
+
private systemPromptCache;
|
|
251
|
+
private started;
|
|
252
|
+
/** Inject tools into a running agent (e.g. dynamically mounted MCP servers). Takes effect on the next turn. */
|
|
253
|
+
addTools(tools: AgentTool[]): void;
|
|
254
|
+
/** Remove tools by name from a running agent. Returns the count removed. */
|
|
255
|
+
removeTools(names: Set<string> | string[]): number;
|
|
256
|
+
constructor(options?: Partial<AgentOptions>);
|
|
257
|
+
/** Build the tool context from the resolved fs + options. Idempotent-safe: called once (ctor or ensureFs). */
|
|
258
|
+
private buildCtx;
|
|
259
|
+
/**
|
|
260
|
+
* Resolve the filesystem + build the tool context if the ctor couldn't (no `fs` was passed). The disk
|
|
261
|
+
* default lives HERE, not in the ctor: the ctor can't await, and we must avoid a STATIC `node:fs` import
|
|
262
|
+
* in the core (edge/browser would break). The dynamic import only fires when `fs` is omitted — edge code
|
|
263
|
+
* always passes its own `MemFilesystem`, so the node module is never reached. Default = jailed disk @ cwd.
|
|
264
|
+
*/
|
|
265
|
+
private ensureFs;
|
|
266
|
+
/**
|
|
267
|
+
* Assemble the system prompt + active tools/hooks ONCE per conversation (memoized).
|
|
268
|
+
* `run()` resets the memo to start fresh; `send()` reuses it, so multi-turn state —
|
|
269
|
+
* plan-mode approval, the tool set, the skills/commands/memory snapshot — stays stable
|
|
270
|
+
* across turns (and the frozen prompt pairs well with prompt caching). Does NOT touch
|
|
271
|
+
* the transcript; `run`/`send` own that.
|
|
272
|
+
*/
|
|
273
|
+
private prepare;
|
|
274
|
+
/** Single-shot: reset all per-conversation state and run `task` to completion. `task` may be plain
|
|
275
|
+
* text or multimodal content parts (text + images). */
|
|
276
|
+
run(task: MessageContent): Promise<RunResult>;
|
|
277
|
+
/** Apply onUserPromptSubmit to a turn: rewrite plain text; pass multimodal content through untouched. */
|
|
278
|
+
private applyPromptSubmit;
|
|
279
|
+
/** Fire onSessionStart once per conversation; returns any injected context. */
|
|
280
|
+
private fireSessionStart;
|
|
281
|
+
/** Fire onUserPromptSubmit; returns the (possibly rewritten) prompt text. */
|
|
282
|
+
private fireUserPromptSubmit;
|
|
283
|
+
/**
|
|
284
|
+
* Multi-turn: continue the existing conversation by appending a user turn instead of
|
|
285
|
+
* resetting — this is what makes an interactive REPL feel like one conversation. The
|
|
286
|
+
* leading system message is (re)synced to the current prompt, so a resumed session whose
|
|
287
|
+
* tools were rebuilt gets a matching catalog rather than a stale one.
|
|
288
|
+
*/
|
|
289
|
+
send(task: MessageContent): Promise<RunResult>;
|
|
290
|
+
/**
|
|
291
|
+
* Fold the conversation in place (manual `/compact`): keep the system message + the
|
|
292
|
+
* most-recent window, summarizing the dropped middle (deterministic, no LLM call).
|
|
293
|
+
* No-op when the transcript already fits. Returns the number of messages removed.
|
|
294
|
+
*/
|
|
295
|
+
compactNow(maxMessages?: number): number;
|
|
296
|
+
private runLoop;
|
|
297
|
+
/**
|
|
298
|
+
* Drain a streamed chat() response: emit each text delta to the host
|
|
299
|
+
* (`{kind:'text_delta'}`) and fold all chunks back into the single
|
|
300
|
+
* ChatResponse the non-stream path would have returned.
|
|
301
|
+
*/
|
|
302
|
+
private consumeStream;
|
|
303
|
+
private dispatch;
|
|
304
|
+
private static readonly WRITE_CLASS;
|
|
305
|
+
/** Append an autoTest failure section to a write-class tool result, if configured. */
|
|
306
|
+
private maybeAutoTest;
|
|
307
|
+
/**
|
|
308
|
+
* Shape the per-turn context (a VIEW — the stored transcript is never mutated). Layered, each off by default:
|
|
309
|
+
* 1. coarse reduction — `compaction` (fold the dropped middle into a synthetic summary) OR
|
|
310
|
+
* `maxContextMessages` (pure drop-oldest), keeping the system message + most-recent window.
|
|
311
|
+
* 2. note-taking (`keepToolOutputs`) — collapse all-but-the-recent-N tool-result bodies to one-line stubs
|
|
312
|
+
* (kills "immortal tool results": a big Read/Grep stops costing its full weight once it's old).
|
|
313
|
+
* 3. token-aware backstop (`maxContextTokens`) — drop oldest messages until under an estimated token ceiling.
|
|
314
|
+
* With every knob off, returns the transcript unchanged (same reference).
|
|
315
|
+
*/
|
|
316
|
+
trimContext(): Message[];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export { Agent as A, DEFAULT_MUTATING as D, type Hooks as H, PermissionOptions as P, RecordingHooks as R, type ToolUse as T, AgentOptions as a, type Decision as b, PermissionPolicy as c, type PermissionRule as d, type PreToolUseDecision as e, RecordingLifecycle as f, type RunResult as g, type ToolUseMeta as h, composeHooks as i, planMode as p };
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { g as RunResult } from './Agent-WTkHB8RY.js';
|
|
3
|
+
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
4
|
+
import { M as Message, c as ContentPart } from './tools-Ch-OzOU8.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* On-disk session store for the CLI: each conversation is one JSON file at
|
|
8
|
+
* `<cwd>/.agent/sessions/<id>.json`. Writes are atomic (temp + rename) so a crash
|
|
9
|
+
* mid-write never corrupts a prior session. This is what gives the REPL memory across
|
|
10
|
+
* turns and powers `--continue` / `--resume`.
|
|
11
|
+
*/
|
|
12
|
+
interface SessionMeta {
|
|
13
|
+
id: string;
|
|
14
|
+
created: number;
|
|
15
|
+
updated: number;
|
|
16
|
+
cwd: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
turns: number;
|
|
19
|
+
title: string;
|
|
20
|
+
/** Cumulative tokens across all turns in this session (for `/cost`). */
|
|
21
|
+
tokens?: number;
|
|
22
|
+
/** Cumulative USD cost across all turns (per-turn model pricing; for `/cost`). */
|
|
23
|
+
costUsd?: number;
|
|
24
|
+
/** Sticky: true if any turn's usage was estimated (streamed without provider usage) → cost is approximate. */
|
|
25
|
+
costEstimated?: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface SessionData {
|
|
28
|
+
meta: SessionMeta;
|
|
29
|
+
messages: Message[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Classify a pasted blob (e.g. a drag-dropped file path) into an attachment: `display` is shown in the
|
|
33
|
+
* in-buffer placeholder, `ref` is what the placeholder expands to on submit (e.g. an `@/abs/path` mention). */
|
|
34
|
+
type PasteClassifier = (text: string) => {
|
|
35
|
+
display: string;
|
|
36
|
+
ref: string;
|
|
37
|
+
} | null;
|
|
38
|
+
|
|
39
|
+
interface Args {
|
|
40
|
+
task?: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
cwd?: string;
|
|
43
|
+
stream: boolean;
|
|
44
|
+
plan: boolean;
|
|
45
|
+
ask: boolean;
|
|
46
|
+
yes: boolean;
|
|
47
|
+
vfs: boolean;
|
|
48
|
+
shell: boolean | undefined;
|
|
49
|
+
boddb?: string;
|
|
50
|
+
seed: boolean;
|
|
51
|
+
subagents: boolean;
|
|
52
|
+
maxSteps?: number;
|
|
53
|
+
maxTokens?: number;
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
help: boolean;
|
|
56
|
+
version: boolean;
|
|
57
|
+
cont: boolean;
|
|
58
|
+
resume?: string;
|
|
59
|
+
outputFormat: 'text' | 'json' | 'stream-json';
|
|
60
|
+
allowedTools?: string[];
|
|
61
|
+
disallowedTools?: string[];
|
|
62
|
+
appendSystemPrompt?: string;
|
|
63
|
+
addDirs?: string[];
|
|
64
|
+
print?: boolean;
|
|
65
|
+
debug?: boolean;
|
|
66
|
+
}
|
|
67
|
+
declare function parseArgs(argv: string[]): Args;
|
|
68
|
+
/** Render a resumed conversation (like CC) so the user sees the context they're continuing: user
|
|
69
|
+
* prompts, assistant narration, and a condensed line per tool action. Tool *results* are omitted
|
|
70
|
+
* (verbose) and inlined @-mention blocks are stripped from prompts. Pure → unit-testable. */
|
|
71
|
+
declare function formatHistory(messages: Message[]): string;
|
|
72
|
+
/** Render a transcript to portable Markdown (no ANSI) for `/export`. Same shape as formatHistory —
|
|
73
|
+
* user prompts + assistant narration + one quoted line per tool action; tool results omitted. Pure → unit-testable. */
|
|
74
|
+
declare function exportMarkdown(meta: {
|
|
75
|
+
id: string;
|
|
76
|
+
title?: string;
|
|
77
|
+
model?: string;
|
|
78
|
+
turns?: number;
|
|
79
|
+
created?: number;
|
|
80
|
+
tokens?: number;
|
|
81
|
+
costUsd?: number;
|
|
82
|
+
costEstimated?: boolean;
|
|
83
|
+
}, messages: Message[]): string;
|
|
84
|
+
/** USD cost from a model's per-1K pricing (ai.libx.js ModelPricing) + token usage. 0 if unpriced. */
|
|
85
|
+
declare function costOf(pricing: {
|
|
86
|
+
inputCostPer1K: number;
|
|
87
|
+
outputCostPer1K: number;
|
|
88
|
+
} | undefined, promptTokens?: number, completionTokens?: number): number;
|
|
89
|
+
/** Format a USD amount: 2 decimals at $1+, 4 below (agent turns are sub-cent). */
|
|
90
|
+
declare function fmtUsd(n: number): string;
|
|
91
|
+
/** ~4 chars/token estimate over a transcript (matches the Agent's context-budget heuristic). */
|
|
92
|
+
declare function estimateTranscriptTokens(messages: Message[]): number;
|
|
93
|
+
/** One-screen session status (model, dir, fs-mode, tools, permission posture, turns/tokens). Pure. */
|
|
94
|
+
declare function formatStatus(s: {
|
|
95
|
+
model: string;
|
|
96
|
+
cwd: string;
|
|
97
|
+
mode: string;
|
|
98
|
+
tools: string[];
|
|
99
|
+
permissions: string;
|
|
100
|
+
turns: number;
|
|
101
|
+
tokens: number;
|
|
102
|
+
sessionId: string;
|
|
103
|
+
/** Whether the token count is estimated (any turn streamed without provider usage). Defaults to estimated. */
|
|
104
|
+
estimated?: boolean;
|
|
105
|
+
}): string;
|
|
106
|
+
/** Run a `!cmd` line over the VFS (works on real disk in disk mode) and return its output — no model
|
|
107
|
+
* call. Mirrors the bash tool's contract so `!ls`/`!git status` behave like the agent's own bash. */
|
|
108
|
+
declare function runShellLine(fs: IFilesystem, cmd: string): Promise<string>;
|
|
109
|
+
/** Append a `#note` to the memory index (creating the dir/file if needed). Returns the file path. */
|
|
110
|
+
declare function appendMemoryNote(fs: IFilesystem, dir: string, text: string): Promise<string>;
|
|
111
|
+
interface PermMode {
|
|
112
|
+
gate: 'ask' | 'allow';
|
|
113
|
+
notice?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Default permission posture — CC-inspired: **ask when a human can answer, allow when unattended.**
|
|
117
|
+
* Pure + exported so the decision is unit-testable without a TTY. Precedence:
|
|
118
|
+
* --yes → allow (explicit trust, no prompts)
|
|
119
|
+
* --ask → ask (explicit gate, even headless)
|
|
120
|
+
* interactive (TTY & not json) → ask (the safe default)
|
|
121
|
+
* else (headless/piped) → allow, with a one-line notice so it's never silent.
|
|
122
|
+
*/
|
|
123
|
+
declare function resolvePermMode(args: {
|
|
124
|
+
yes?: boolean;
|
|
125
|
+
ask?: boolean;
|
|
126
|
+
outputFormat?: string;
|
|
127
|
+
}, interactiveCapable: boolean): PermMode;
|
|
128
|
+
/** Read `@image.png` mentions from `line` as base64 image content parts (real files under cwd; binary,
|
|
129
|
+
* so read via node fs — the VFS readFile is utf8). Unreadable/missing images are skipped silently
|
|
130
|
+
* (expandMentions reports the missing @ref separately). */
|
|
131
|
+
declare function readImageParts(cwd: string, line: string): ContentPart[];
|
|
132
|
+
/** Classify a pasted blob as a file attachment when it's a drag-dropped/typed path to an existing file.
|
|
133
|
+
* Returns an `@<abs>` ref the existing mention pipeline attaches (images) or inlines (other files); the
|
|
134
|
+
* in-buffer placeholder shows `[Image #N]` / `[File name #N]`. Returns null for anything not a clear single
|
|
135
|
+
* path (so ordinary text pastes are unaffected). Paths with spaces are skipped — the `@\S+` pipeline can't carry them. */
|
|
136
|
+
declare function pastePathClassifier(cwd: string): PasteClassifier;
|
|
137
|
+
/** Inline `@path` file mentions: append referenced files' contents so the model sees them (missing → warn, leave as-is). */
|
|
138
|
+
declare function expandMentions(fs: IFilesystem, line: string): Promise<{
|
|
139
|
+
text: string;
|
|
140
|
+
loaded: string[];
|
|
141
|
+
missing: string[];
|
|
142
|
+
}>;
|
|
143
|
+
/** The headless `--output-format json` result object for a turn. */
|
|
144
|
+
declare function jsonResult(res: RunResult, session: SessionData): {
|
|
145
|
+
ok: boolean;
|
|
146
|
+
finishReason: "error" | "budget" | "stop" | "max_steps" | "timeout" | "loop" | "max_tool_calls" | "aborted";
|
|
147
|
+
text: string;
|
|
148
|
+
steps: number;
|
|
149
|
+
tools: number;
|
|
150
|
+
usage: {
|
|
151
|
+
promptTokens: number;
|
|
152
|
+
completionTokens: number;
|
|
153
|
+
totalTokens: number;
|
|
154
|
+
} | undefined;
|
|
155
|
+
sessionId: string;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Read one logical input, supporting `\`-continuation for multi-line prompts: a line ending in
|
|
159
|
+
* a backslash drops the `\` and keeps reading; the parts are joined with newlines. `readLine`
|
|
160
|
+
* is injected (the REPL passes a readline `question`; tests pass a scripted reader).
|
|
161
|
+
*/
|
|
162
|
+
declare function readMultiline(readLine: (continuing: boolean) => Promise<string | null>): Promise<string | null>;
|
|
163
|
+
|
|
164
|
+
export { type PermMode, appendMemoryNote, costOf, estimateTranscriptTokens, expandMentions, exportMarkdown, fmtUsd, formatHistory, formatStatus, jsonResult, parseArgs, pastePathClassifier, readImageParts, readMultiline, resolvePermMode, runShellLine };
|