bare-agent 0.46.5 → 0.47.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/README.md +49 -272
- package/bareagent.context.md +37 -2
- package/examples/with-bareguard.mjs +12 -5
- package/package.json +2 -2
- package/primitives.json +13 -4
- package/src/retry.d.ts +1 -1
- package/src/retry.js +1 -1
- package/src/tools.d.ts +2 -1
- package/src/tools.js +2 -1
- package/tools/shell.d.ts +136 -6
- package/tools/shell.js +406 -59
package/README.md
CHANGED
|
@@ -17,292 +17,62 @@
|
|
|
17
17
|
<img src="https://img.shields.io/badge/license-Apache%202.0-2a4f8c" alt="license: Apache 2.0">
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
|
-
**Lightweight agent
|
|
20
|
+
**Lightweight agent primitives. Zero required deps — optional [bareguard](https://npmjs.com/package/bareguard) peer for one-gate governance.**
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
bare-agent started as primitives for building agents by hand: your code is the deterministic body, an LLM call sits in wherever you need judgment — the LangChain / CrewAI / AutoGen job, provider-agnostic, without a framework.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
LLMs got good at wiring things themselves, so the same primitives are now offered as tool calls too: the model wires them up, bare-agent runs the loop, retries, budgets, and governance underneath. It's what [bareloop](https://github.com/hamr0/bareloop) and [fwdloop](https://github.com/hamr0/fwdloop) run on.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
npm install bare-agent
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
**1. Give your AI assistant the integration guide**
|
|
26
|
+
Some things an LLM can't judge about its own work. Building a harness or an external arbiter? Every primitive is listed in `primitives.json` — when to use it, import, signature, failure modes, a runnable example — plus a decisive return-time `judge` and the Jev gut-check tier.
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
Read bareagent.context.md from node_modules/bare-agent/bareagent.context.md
|
|
34
|
-
```
|
|
28
|
+
## Start here
|
|
35
29
|
|
|
36
|
-
|
|
30
|
+
- **Building by hand** → `npm install bare-agent`, then hand your assistant `bareagent.context.md` (ships in the package — the complete contract).
|
|
31
|
+
- **AI agent / tool-calling** → read `primitives.json` first (`unpkg.com/bare-agent/primitives.json` before you install, or `require('bare-agent/primitives.json')`); generated from source, never drifts.
|
|
32
|
+
- **Harness / judge builder** → `primitives.json` + `judge` + Jev + wire bareguard.
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
## Fast gut check — Jev
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
I need an agent that:
|
|
42
|
-
- Takes a user goal and breaks it into steps
|
|
43
|
-
- Runs steps in parallel where possible
|
|
44
|
-
- Retries failed steps twice
|
|
45
|
-
- Streams progress as JSONL events
|
|
36
|
+
An LLM call is slow, deliberate thinking (System 2). Jev, from TypeSafe, is the fast gut check (System 1): a classifier that returns a typed decision with a calibrated probability — roughly 200× faster and 400× cheaper than an LLM, per TypeSafe. bare-agent's `JevProvider` covers all three shapes — **yes/no**, **pick-one**, **score** — so your automation calls it like any other step: act when it's confident, escalate when it isn't. Injection-hardened by default; also usable as the `jev` check in the Evaluator.
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
That's it. The context doc is structured for LLM consumption — your agent reads it once and knows how to wire every component.
|
|
51
|
-
|
|
52
|
-
**Not sure what you need?** Paste this into any AI assistant:
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
I want to build an agent using bare-agent. Read the integration guide at
|
|
56
|
-
node_modules/bare-agent/bareagent.context.md, then ask me up to 5 questions
|
|
57
|
-
about what I need. Based on my answers, tell me which components to use
|
|
58
|
-
and show me the wiring code.
|
|
59
|
-
```
|
|
38
|
+
## Break big tasks down — RLM
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## For AI agents — the menu
|
|
64
|
-
|
|
65
|
-
Building tool-calling automation on top of bare-agent? Read **`primitives.json`** first. It's a compact, machine-readable menu of every building block — the fast path to *using* bare-agent without reading the docs: load it, pick a primitive, wire it. Each entry carries `when` to reach for it, its `import`, `signature`, `fails`, and a runnable `example`:
|
|
66
|
-
|
|
67
|
-
```jsonc
|
|
68
|
-
{
|
|
69
|
-
"name": "AnthropicProvider",
|
|
70
|
-
"category": "providers",
|
|
71
|
-
"when": "you want Claude models as the Loop's provider — native Messages API with opt-in prompt caching and thinking-block passthrough",
|
|
72
|
-
"import": "import { AnthropicProvider } from 'bare-agent/providers'",
|
|
73
|
-
"signature": "new AnthropicProvider(options?: AnthropicOptions)",
|
|
74
|
-
"fails": "…",
|
|
75
|
-
"example": "…"
|
|
76
|
-
}
|
|
77
|
-
// 51 entries across: providers · routing · retrieval · mcp · loop · memory · tools · skills · evaluation · hitl · resilience · governance · orchestration · …
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Browse it on unpkg (`unpkg.com/bare-agent/primitives.json`) before you install, `require` it (`const menu = require('bare-agent/primitives.json')`), or point a tool at it. Generated from the source, so it never drifts.
|
|
81
|
-
|
|
82
|
-
**Then go deeper:** `bareagent.context.md` (the complete contract — every option and the full API) and the rest of this README for the recipes.
|
|
40
|
+
`recurse` brings Recursive Language Models (RLM) to your code in one call: split a hard task into smaller ones, run each in a fresh context window, check the result, and stitch it back together. The model decides when to split (or you force a fixed count), and over a large set of documents it picks the path by question shape — scan everything for "how many", search for a needle. Totals are counted by code, and a dead worker comes back `incomplete`, never a faked pass. Cost is open by design: run it under a budget cap (bareguard) or set `maxDepth: 1`.
|
|
83
41
|
|
|
84
42
|
## What's inside
|
|
85
43
|
|
|
86
|
-
Every piece works alone — take what you need, ignore the rest.
|
|
87
|
-
|
|
88
|
-
### Act — get work done
|
|
89
|
-
|
|
90
|
-
| Component | What it does |
|
|
91
|
-
|---|---|
|
|
92
|
-
| **Loop** | Think → act → observe until done. Any LLM, your tools, per-run cost. Opt-in seams: `policy` (govern), `assemble` (context engineering), `trim` (bound the transcript) |
|
|
93
|
-
| **Planner** | Break a goal into a step DAG. Cached |
|
|
94
|
-
| **assessComplexity** | Rate a goal `simple`→`critical` from its text — no LLM. Gates whether to plan |
|
|
95
|
-
| **runPlan** | Run plan steps in parallel waves. Dependency-aware, per-step retry |
|
|
96
|
-
| **recurse** | RLM decompose→fan-out→verify→synthesize in one call. Model-driven (`spawn_child`) or forced fan-out (`count`); `retrieval:'scan'` answers "how many / all" over a corpus by scanning every slice and code-counting — never a faked pass (honest `{incomplete, missingSlices}`) |
|
|
97
|
-
| **Memory** | Persist + recall across sessions via a swappable `Store` — zero-dep JSON, SQLite, or [litectx](https://npmjs.com/package/litectx) in a one-line swap |
|
|
98
|
-
| **StateMachine** | Task lifecycle: `pending → running → done / failed / waiting / cancelled` |
|
|
99
|
-
| **Scheduler** | Cron or relative triggers. Jobs survive restarts |
|
|
100
|
-
| **Checkpoint** | Human approval gate — bring your own transport |
|
|
101
|
-
| **Spawn** | Fork a child agent. One shared audit log + budget |
|
|
102
|
-
| **Defer** | Queue an action for a waker to fire later. Governed when emitted *and* when it fires |
|
|
103
|
-
| **Retry · CircuitBreaker · Fallback** | Resilience: backoff with jitter, fail-fast, provider failover |
|
|
104
|
-
| **Stream · Errors** | Structured JSONL events; typed error hierarchy |
|
|
105
|
-
| **Browsing · Mobile · Shell** | Hands: web ([barebrowse](https://npmjs.com/package/barebrowse)), Android + iOS ([baremobile](https://npmjs.com/package/baremobile)), cross-platform shell — library tools or token-thrifty CLI sessions |
|
|
106
|
-
| **MCP Bridge** | Auto-discover MCP servers from your IDE configs; expose as tools (bulk or meta-tools) |
|
|
107
|
-
|
|
108
|
-
### Verify — check the work, keep context clean *(the eval-assist suite)*
|
|
109
|
-
|
|
110
|
-
| Component | What it does |
|
|
111
|
-
|---|---|
|
|
112
|
-
| **Evaluator + refine** | Judge output by `predicate` (no tokens), `rubric` (an isolated adversarial grader), `agentic` (a critic that exercises the live artifact), or `jev` (a cheap calibrated classifier tier). `refine` is the bounded generate→evaluate→regenerate loop |
|
|
113
|
-
| **SkillRegistry** | Surface skills on demand: one meta-tool catalog; activating a skill injects its instructions and unlocks its tools |
|
|
114
|
-
| **stash** | Compact finished work out of the live window (restorable), or auto-fold the middle under token pressure |
|
|
44
|
+
Every piece works alone — take what you need, ignore the rest. No required deps — the core imports nothing.
|
|
115
45
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`recurse(task, ctx, opts)` does **decompose → fan-out → verify → synthesize** in one call — Recursive Language Models as a single import, composed *around* the Loop (never a new engine). The default is **model-driven**: the worker is handed a `spawn_child` tool and decides whether to split, bounded by depth + bareguard (no second guard layer). Forced fan-out (`count` / `mode:'fanout'`) and data-driven width (`mode:'partition'`, measured from a corpus) are opt-in. Give workers a stance with `opts.persona` (prepended to every worker, carries down the tree, deliberately kept out of the isolated verifier), and tell them *where they are* with `opts.context` (a read-only paths/cwd blob threaded to every worker so a sliced child can locate its artifact — facts, not a stance). For a leaf that should self-correct, pass `opts.refineLeaf` (opt-in): a definite leaf becomes a bounded generate→sense→regenerate loop driven by *your* deterministic sensor (test/compile/lint — one that judges the *returned* result, never a worker side-effect it could game), feeding the gap back (with escalating temperature on models that accept it; on a temperature-fixed model like `claude-sonnet-5` the gap critique carries recovery, and the receipt records the effective temps). On a temperature-fixed model, `refineLeaf.rejectedBuffer` adds a second lever — it feeds the model's own prior *failed attempts* back verbatim ("write something structurally different"), the directed-diversity complement to temperature's random diversity (adaptive by default; the two are antagonistic, so it holds temperature flat when it engages). A *broken* arbiter — a sensor or a caller `evaluate` that throws or returns a malformed verdict — is named, never blamed on the model: the node stops at the first broken close and returns `{ incomplete, blocker: 'broken-sensor' | 'broken-verifier' }` with the model's last output preserved. The headline guarantee: **aggregation is code, never a model-stated number**, and a dead worker or exhausted guard returns an honest `{ incomplete, missingSlices }` — never a faked pass.
|
|
119
|
-
|
|
120
|
-
Over a corpus, context reaches a worker as a **handle routed by question shape** (`opts.retrieval`):
|
|
121
|
-
|
|
122
|
-
| `retrieval` | Use it for | How |
|
|
46
|
+
| Area | Component | What it does |
|
|
123
47
|
|---|---|---|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
**Tools:** Any function is a tool — REST, MCP, CLI, shell. Built-in web + mobile (optional).
|
|
154
|
-
|
|
155
|
-
**Cross-language:** Run as a subprocess; talk JSONL over stdin/stdout from Python, Go, Rust, Ruby, or Java. Wrappers in [`contrib/`](contrib/README.md).
|
|
156
|
-
|
|
157
|
-
**Deps:** none required — the core imports nothing. Optional peers: `bareguard >=0.9.0 <1.0.0` (governance), `better-sqlite3` (SQLite store); optional: `cron-parser`, `barebrowse`, `baremobile`, `wearehere`.
|
|
158
|
-
|
|
159
|
-
This table is the map, not the manual — per-component wiring and API detail live in the [Integration Guide](bareagent.context.md) and [Usage Guide](docs/archive/usage-guide.md).
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Recipes
|
|
164
|
-
|
|
165
|
-
### Wire bareguard into Loop
|
|
166
|
-
|
|
167
|
-
```js
|
|
168
|
-
const { Gate } = require('bareguard');
|
|
169
|
-
const { Loop, wireGate, defaultActionTranslator } = require('bare-agent');
|
|
170
|
-
|
|
171
|
-
const gate = new Gate({
|
|
172
|
-
budget: { maxCostUsd: 0.50 },
|
|
173
|
-
limits: { maxToolRounds: 20 }, // bareguard 0.4.2+ — N tool rounds, LLM rounds bypass
|
|
174
|
-
audit: { path: './audit.jsonl' },
|
|
175
|
-
});
|
|
176
|
-
await gate.init();
|
|
177
|
-
|
|
178
|
-
const { policy, onLlmResult, onToolResult, filterTools } = wireGate(gate, {
|
|
179
|
-
// Optional: translate tool names → bareguard primitive types for bash/fs/net rules.
|
|
180
|
-
// bareguard 0.4.1+ reads args.command / args.path verbatim, so args passes through.
|
|
181
|
-
actionTranslator: (toolName, args, ctx) => {
|
|
182
|
-
if (toolName === 'shell_exec') return { type: 'bash', args, _ctx: ctx };
|
|
183
|
-
if (toolName === 'shell_read') return { type: 'read', args, _ctx: ctx };
|
|
184
|
-
if (toolName === 'shell_write') return { type: 'write', args, _ctx: ctx }; // gate by fs.writeScope
|
|
185
|
-
if (toolName === 'shell_edit') return { type: 'edit', args, _ctx: ctx }; // same fs.writeScope as write
|
|
186
|
-
return defaultActionTranslator(toolName, args, ctx);
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
const tools = await filterTools(myTools); // drop tools denied by static policy
|
|
190
|
-
|
|
191
|
-
const loop = new Loop({ provider, policy, onLlmResult, onToolResult });
|
|
192
|
-
await loop.run([{ role: 'user', content: 'go' }], tools, { ctx: { userId: 42 } });
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
`onLlmResult` + `onToolResult` are what make `budget.maxCostUsd` actually cover token-heavy workloads — without them, budget only sees tool cost. `ctx` flows through to `gate.record` as `_ctx` for per-principal accounting.
|
|
196
|
-
|
|
197
|
-
### Per-principal bypass (owner / admin role)
|
|
198
|
-
|
|
199
|
-
Wrap the gate policy when a principal is trusted unconditionally:
|
|
200
|
-
|
|
201
|
-
```js
|
|
202
|
-
const { policy: gatePolicy } = wireGate(gate);
|
|
203
|
-
|
|
204
|
-
const policy = async (toolName, args, ctx) => {
|
|
205
|
-
if (ctx?.role === 'owner') return true; // bypass gate entirely
|
|
206
|
-
return gatePolicy(toolName, args, ctx);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
new Loop({ provider, policy, onLlmResult, onToolResult });
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Bypassing the gate also bypasses audit and budget — only do this for principals you trust unconditionally. For partial trust, use ctx-aware rules inside bareguard instead.
|
|
213
|
-
|
|
214
|
-
### Custom deny strings (localize / strip prefix)
|
|
215
|
-
|
|
216
|
-
```js
|
|
217
|
-
const { policy } = wireGate(gate, {
|
|
218
|
-
formatDeny: (decision) => `Sorry — ${decision.reason || 'not allowed'}`,
|
|
219
|
-
});
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Halt-severity decisions bypass `formatDeny` (they throw `HaltError` and exit the loop without ever reaching the LLM).
|
|
223
|
-
|
|
224
|
-
### Catch halts in your app
|
|
225
|
-
|
|
226
|
-
```js
|
|
227
|
-
const result = await loop.run(msgs, tools);
|
|
228
|
-
if (result.error?.startsWith('halt:')) {
|
|
229
|
-
// budget cap, turn cap, or gate terminated. Inspect rule:
|
|
230
|
-
const rule = result.error.slice('halt:'.length);
|
|
231
|
-
// tell the user, schedule retry, escalate, etc.
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Halts also fire `loop:error` on the stream (`source: 'halt'`) and the `onError` callback (with a `HaltError` instance).
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Examples
|
|
240
|
-
|
|
241
|
-
Runnable scripts in [`examples/`](examples/) — each is self-contained and the file's top docstring documents flags and required env vars.
|
|
242
|
-
|
|
243
|
-
| File | What it shows |
|
|
244
|
-
|---|---|
|
|
245
|
-
| [`with-bareguard.mjs`](examples/with-bareguard.mjs) | End-to-end Loop + bareguard wiring: budget cap, fs scope, bash allowlist, audit log, humanChannel. The canonical governed-loop reference. |
|
|
246
|
-
| [`mcp-bridge-poc.js`](examples/mcp-bridge-poc.js) | Auto-discover MCP servers from your IDE configs and expose them as bareagent tools. First run writes `.mcp-bridge.json` (edit to deny tools). |
|
|
247
|
-
| [`mcp-bridge-concurrent.js`](examples/mcp-bridge-concurrent.js) | Soak test: fan out concurrent `barebrowse_browse` calls against real domains (Amazon, Wikipedia, GitHub, a dead host) and verify resilience. |
|
|
248
|
-
| [`orchestrator/`](examples/orchestrator/) | Multi-agent dispatch via `spawn`. Three configs, one system prompt — no orchestrator class, no role types. Roles are JSON files. |
|
|
249
|
-
| [`wake.sh`](examples/wake.sh) + [`wake.md`](examples/wake.md) | Reference cron + jq script for firing deferred actions. The runtime half of `createDeferTool` — bareagent emits, `wake.sh` fires. |
|
|
250
|
-
| [`replay-job.js`](examples/replay-job.js) | Supervised replay POC: record a browser task once with the LLM driving, then replay against fresh snapshots with the LLM as locator-only. Falls back to full reasoning when the locator misses, and patches the trace. |
|
|
251
|
-
| [`litectx-as-store.mjs`](examples/litectx-as-store.mjs) | Mount [litectx](https://npmjs.com/package/litectx) as the `Memory` `Store` — one-line swap from `JsonFileStore` to ranked, graph-aware recall; the host code never changes (RT-3). |
|
|
252
|
-
| [`litectx-mcp-child.mjs`](examples/litectx-mcp-child.mjs) | Give a spawned child agent litectx's reasoning verbs as MCP tools, read-only on its own db, via `liteCtxMcpBridgeConfig` + `cfg.mcp` (RT-4). |
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## Cross-language usage
|
|
257
|
-
|
|
258
|
-
Not using Node.js? Spawn bare-agent as a subprocess from any language. Ready-made wrappers in [`contrib/`](contrib/README.md) for Python, Go, Rust, Ruby, and Java — copy one file, no package registry needed.
|
|
259
|
-
|
|
260
|
-
```python
|
|
261
|
-
# Python — 3 lines to run an agent
|
|
262
|
-
from bareagent import BareAgent
|
|
263
|
-
|
|
264
|
-
agent = BareAgent(provider="openai", model="gpt-4o-mini")
|
|
265
|
-
result = agent.run("What is the capital of France?")
|
|
266
|
-
print(result["text"]) # → "The capital of France is Paris."
|
|
267
|
-
agent.close()
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
```go
|
|
271
|
-
// Go — same pattern
|
|
272
|
-
agent, _ := bareagent.New("anthropic", "claude-haiku-4-5-20251001", "")
|
|
273
|
-
result, _ := agent.Run("What is the capital of France?")
|
|
274
|
-
fmt.Println(result.Text)
|
|
275
|
-
agent.Close()
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
```ruby
|
|
279
|
-
# Ruby — same pattern
|
|
280
|
-
agent = BareAgent.new(provider: "ollama", model: "llama3.2")
|
|
281
|
-
result = agent.run("What is the capital of France?")
|
|
282
|
-
puts result["text"]
|
|
283
|
-
agent.close
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
All wrappers support optional event streaming for intermediate results. See [`contrib/README.md`](contrib/README.md) for Rust, Java, and full protocol reference.
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
## Production-validated
|
|
291
|
-
|
|
292
|
-
| Component | Aurora (SOAR2) | Multis (assistant) |
|
|
293
|
-
|---|:---:|:---:|
|
|
294
|
-
| Loop | ✓ | ✓ |
|
|
295
|
-
| Planner | ✓ | ✓ |
|
|
296
|
-
| runPlan | ✓ | — |
|
|
297
|
-
| Retry | ✓ | ✓ |
|
|
298
|
-
| CircuitBreaker | — | ✓ |
|
|
299
|
-
| Scheduler | — | ✓ |
|
|
300
|
-
| Checkpoint | — | ✓ |
|
|
301
|
-
| CLIPipe | ✓ | — |
|
|
302
|
-
|
|
303
|
-
Aurora replaced ~400 lines of hand-rolled orchestration with ~60 lines of bare-agent wiring — zero workarounds, zero framework plumbing, 100% domain logic.
|
|
304
|
-
|
|
305
|
-
For wiring recipes and API details, see the **[Integration Guide](bareagent.context.md)** (LLM-optimized). For the full human guide — usage patterns, composition examples, and what bare-agent deliberately doesn't build in (with recipes to do it yourself), see the **[Usage Guide](docs/archive/usage-guide.md)**. For error reference, see **[Error Guide](docs/product/errors.md)**. For release history, see **[CHANGELOG](CHANGELOG.md)**.
|
|
48
|
+
| Act | Loop | think → act → observe until done, any provider, opt-in policy/assemble/trim seams |
|
|
49
|
+
| Act | Planner + runPlan | break a goal into a step DAG, run steps in parallel waves |
|
|
50
|
+
| Act | assessComplexity | rate a goal from its text alone, no LLM — gates whether to plan |
|
|
51
|
+
| Act | recurse | RLM in one call — decompose → fan-out → verify → synthesize (see above) |
|
|
52
|
+
| Act | Memory | persist and recall across sessions — JSON, SQLite, or litectx in a one-line swap |
|
|
53
|
+
| Act | StateMachine | task lifecycle: pending → running → done / failed / waiting / cancelled |
|
|
54
|
+
| Act | Scheduler | cron or relative triggers, survives restarts |
|
|
55
|
+
| Act | Checkpoint | human approval gate, bring your own transport |
|
|
56
|
+
| Act | Spawn | fork a child agent, sharing one audit log and budget |
|
|
57
|
+
| Act | Defer | queue an action for a waker to fire later, governed on emit and on fire |
|
|
58
|
+
| Act | Retry · CircuitBreaker · Fallback | backoff with jitter, fail-fast, provider failover |
|
|
59
|
+
| Act | Stream · Errors | structured JSONL events, typed error hierarchy |
|
|
60
|
+
| Verify | Evaluator + refine | judge output by predicate, rubric, an agentic critic, or the Jev tier; refine loops generate → evaluate → regenerate |
|
|
61
|
+
| Verify | judge | a decisive return-time check of the result against the original request |
|
|
62
|
+
| Verify | remember | distill durable facts out of a finished run |
|
|
63
|
+
| Verify | SkillRegistry | surface extra tools on demand instead of loading them all upfront |
|
|
64
|
+
| Verify | stash | compact finished work out of the live context window, restorable |
|
|
65
|
+
| Verify | JevProvider | fast, cheap, calibrated yes/no · pick-one · score decisions — the System-1 gate (see above) |
|
|
66
|
+
| Govern | wireGate → bareguard | one policy, one audit log, one budget cap over every call; stops the spin on repeated denials or a stuck call |
|
|
67
|
+
| Hands | Browsing · Mobile · Shell · MCP Bridge | barebrowse, baremobile, cross-platform shell, and auto-discovered MCP servers — all as tools |
|
|
68
|
+
| Providers | OpenAI-compatible, Anthropic, Gemini, Ollama, CLIPipe, Fallback | swap freely, or bring your own with one `generate` method; CLIPipe runs the loop over a CLI subscription instead of the metered API |
|
|
69
|
+
|
|
70
|
+
## It tells you the truth
|
|
71
|
+
|
|
72
|
+
- A cut-off, refused, or over-context answer is an error, not a success — and its half-written tool calls never run.
|
|
73
|
+
- A cost it can't price is `null`, never a silent `$0`.
|
|
74
|
+
- A dead worker or broken check returns `incomplete`, never a faked pass — and counts are done by code, not by the model.
|
|
75
|
+
- Stuck loops stop: repeated denials or the same failing call end the run cleanly instead of burning the budget.
|
|
306
76
|
|
|
307
77
|
## The bare ecosystem
|
|
308
78
|
|
|
@@ -331,6 +101,13 @@ mix and match, each module works standalone.
|
|
|
331
101
|
|
|
332
102
|
**Why this exists:** Most automation stacks ship 200MB of opinions before you write a line of code. These don't. Install, import, go.
|
|
333
103
|
|
|
104
|
+
## Docs
|
|
105
|
+
|
|
106
|
+
- **Integration Guide** (`bareagent.context.md`) — the complete contract: every option, the full API, wiring recipes. Ships in the package.
|
|
107
|
+
- **Primitives manifest** (`primitives.json`) — every primitive as machine-readable JSON. Ships in the package, generated from source.
|
|
108
|
+
- **[Error Guide](docs/product/errors.md)** — what each error means and how to react to it.
|
|
109
|
+
- **[CHANGELOG](CHANGELOG.md)** — release history. Not on Node? See [`contrib/`](contrib/README.md) for wrappers in Python, Go, Rust, Ruby, and Java.
|
|
110
|
+
|
|
334
111
|
## License
|
|
335
112
|
|
|
336
113
|
Apache License, Version 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
package/bareagent.context.md
CHANGED
|
@@ -19,7 +19,7 @@ Eight entry points:
|
|
|
19
19
|
- `require('bare-agent/providers')` — OpenAI, Anthropic, Ollama, CLIPipe, Fallback (the canonical short names; `*Provider` aliases — `OpenAIProvider`, `AnthropicProvider`, etc. — are also exported and match the class names, so either destructure works, v0.12.1+)
|
|
20
20
|
- `require('bare-agent/stores')` — SQLite (FTS5), JsonFile
|
|
21
21
|
- `require('bare-agent/transports')` — JsonlTransport
|
|
22
|
-
- `require('bare-agent/tools')` — createBrowsingTools, createMobileTools, createShellTools, createSpawnTool, createDeferTool, spawnChild, readDeferQueue, liteCtxMcpBridgeConfig
|
|
22
|
+
- `require('bare-agent/tools')` — createBrowsingTools, createMobileTools, createShellTools, resolveToolPath, createSpawnTool, createDeferTool, spawnChild, readDeferQueue, liteCtxMcpBridgeConfig
|
|
23
23
|
- `require('bare-agent/mcp')` — createMCPBridge (returns `tools` + `metaTools`), discoverServers, buildMetaTools
|
|
24
24
|
- `require('bare-agent/bareguard')` — wireGate (one-line bareguard Gate integration), defaultActionTranslator
|
|
25
25
|
|
|
@@ -526,6 +526,8 @@ const { policy, onToolResult } = wireGate(gate, {
|
|
|
526
526
|
|
|
527
527
|
`onLlmResult` always uses `{type:'llm'}` regardless of the translator (so budget rules match without translator collusion). `defaultActionTranslator` is exported for composition. **A tool is NOT auto-gated by the fs/bash primitives without this translator** — e.g. `shell_write` runs the write but `fs.writeScope` only enforces once `shell_write` → `{type:'write', path}`; the default `{type:'shell_write'}` matches `tools.allow/denylist` only. Verified live: with the translator, `gate.check` ALLOWs `shell_run ["ls","/tmp"]` and DENYs `shell_read /etc/passwd` (`[deny: fs.readScope]`), and an out-of-scope `shell_write` is denied **before** `execute` (nothing touches disk).
|
|
528
528
|
|
|
529
|
+
> **Canonicalize the path before the gate sees it.** bareguard's `fs` primitive checks the path you hand it WITHOUT canonicalizing it — a `~`-prefixed or relative `args.path` reads `fs.readScope`/`fs.writeScope` differently than the absolute path the tool actually opens. Call `resolveToolPath` (exported from `bare-agent/tools` alongside `createShellTools`) on `args.path` in the translator — either put the result in an explicit `path` field (as the snippet above's sibling example, `examples/with-bareguard.mjs`, does) or, if relying on bareguard reading `args.path` directly, replace `args.path` with the canonicalized value before returning. Same string in, same string opened.
|
|
530
|
+
|
|
529
531
|
**Bounding tool rounds — use `limits.maxToolRounds` (bareguard 0.4.2+), not doubled `maxTurns`.** `limits.maxTurns` ticks on every `gate.record` (LLM + tool), so an "N LLM-tool round" cap is `maxTurns: N*2`. `limits.maxToolRounds: N` ticks only on non-`llm` records and gives the natural semantic — pairs cleanly with our split `onLlmResult` / `onToolResult` (the LLM side writes `{type:'llm'}` records which the counter skips). Halt severity, same shape as `maxTurns`, rebuilt from audit on cold-start.
|
|
530
532
|
|
|
531
533
|
**`HaltError` reachable from the public API (v0.10.1+).** `require('bare-agent').HaltError`, `require('bare-agent/errors').HaltError`. Adopters whose policy shim throws `HaltError` get identity-equal class across module boundaries — Loop's `instanceof HaltError` catches it cleanly.
|
|
@@ -572,7 +574,34 @@ await loop.run(messages, tools, {
|
|
|
572
574
|
});
|
|
573
575
|
```
|
|
574
576
|
|
|
575
|
-
For routing rules that don't fit bareguard's primitives (e.g. "owner can do anything; user can only read"), you can layer a custom closure on top of `wireGate(gate).policy` — but the cleaner pattern is one source of truth: encode the rules as bareguard primitives and let the gate evaluate them.
|
|
577
|
+
For routing rules that don't fit bareguard's primitives (e.g. "owner can do anything; user can only read"), you can layer a custom closure on top of `wireGate(gate).policy` — but the cleaner pattern is one source of truth: encode the rules as bareguard primitives and let the gate evaluate them. The recipe below is exactly that kind of closure, for the specific "owner bypasses everything" case.
|
|
578
|
+
|
|
579
|
+
### Per-principal bypass (owner / admin role)
|
|
580
|
+
|
|
581
|
+
Wrap the gate policy when a principal is trusted unconditionally:
|
|
582
|
+
|
|
583
|
+
```js
|
|
584
|
+
const { policy: gatePolicy } = wireGate(gate);
|
|
585
|
+
|
|
586
|
+
const policy = async (toolName, args, ctx) => {
|
|
587
|
+
if (ctx?.role === 'owner') return true; // bypass gate entirely
|
|
588
|
+
return gatePolicy(toolName, args, ctx);
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
new Loop({ provider, policy, onLlmResult, onToolResult });
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Bypassing the gate also bypasses audit and budget — only do this for principals you trust unconditionally. For partial trust, use ctx-aware rules inside bareguard instead.
|
|
595
|
+
|
|
596
|
+
### Custom deny strings (localize / strip prefix)
|
|
597
|
+
|
|
598
|
+
```js
|
|
599
|
+
const { policy } = wireGate(gate, {
|
|
600
|
+
formatDeny: (decision) => `Sorry — ${decision.reason || 'not allowed'}`,
|
|
601
|
+
});
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Halt-severity decisions bypass `formatDeny` (they throw `HaltError` and exit the loop without ever reaching the LLM).
|
|
576
605
|
|
|
577
606
|
### Catalog pre-filter (omit denied tools from the LLM's view)
|
|
578
607
|
|
|
@@ -1387,6 +1416,12 @@ Mobile tools follow the observe-act pattern: action tools auto-return a fresh sn
|
|
|
1387
1416
|
|
|
1388
1417
|
**Zero baked-in allowlist.** The library ships the primitives; gating is bareguard's job via the standard `wireGate(gate)` wiring.
|
|
1389
1418
|
|
|
1419
|
+
> **Path canonicalization + symlink refusal, together.** bareguard's own resolved-path (symlink) containment check is planned for an upcoming release, not yet published; until then the gate matches paths lexically, so a symlinked parent dir is NOT contained even when the link sits inside an allowed scope. bare-agent canonicalizes the path (`~` + resolve, via `resolveToolPath`) before the gate check and opens that same path — this closes a *different* gap (the gate judging a `~`/relative path in a different form than the one the tool opens), not the lexical-match gap above. `noFollowSymlinks` refuses a symlinked final path *component* at open time. Together they narrow but do not close the check-then-open window, and neither covers hardlinks.
|
|
1420
|
+
>
|
|
1421
|
+
> **Opt-in symlink refusal:** `createShellTools({ noFollowSymlinks: true })` makes `shell_read`, `shell_write`, `shell_edit`, and `shell_grep` refuse to open a path whose **final** path component is a symlink — a file, a dir, or dangling — instead of silently following it. Refusal is a **thrown** error with `err.code === 'ELOOP'` (feeds the Loop's `maxIdenticalToolErrors` guard like any other tool error), and for `shell_write` the link's target is never created or touched. Scope is the final component **only** — a symlinked *parent* directory is not covered (per the canonicalization note above), and neither is the `cwd` option of `shell_run`/`shell_exec` — it only gets `~` expansion, never this flag or `resolveToolPath`'s full canonicalization, so a symlinked `cwd` is always followed. `shell_grep` refuses only a symlinked **root** path loudly, via the same atomic open every other tool uses (open-then-fstat, not a look-then-open `lstat`); a file that turns into a symlink partway through the walk is skipped silently, the same as any other read error. The check is atomic (`O_NOFOLLOW`) on linux/macOS; on Windows, which has no `O_NOFOLLOW`-equivalent open flag, it falls back to a non-atomic `lstat`-then-open check — a symlink planted in that gap slips through. A directory listing (`shell_read` on a dir, `shell_grep`'s root when it's a dir) additionally rechecks the path's `dev`+`ino` against the open handle right after listing — Node has no `fdopendir`, so the listing itself still goes by path, and this narrows (does not close) the window where a directory swapped in between the open and the listing would report a different directory's names; a swap-and-swap-back inside that narrow window still passes undetected. Default `false` — omitting the option is byte-identical to the pre-existing behavior.
|
|
1422
|
+
>
|
|
1423
|
+
> **`resolveToolPath(p)`** — exported alongside `createShellTools` from `bare-agent/tools`. THE canonicalizer every shell file tool applies internally before opening: expand `~`/`~/…` via `os.homedir()` (throws rather than silently degrading `~/x` to `/x` when no home directory can be determined), then `path.resolve` against `process.cwd()`. Idempotent; rejects a non-string/empty path (thrown, naming only the type/category of the bad value, never the value itself) — every file tool routes through it, so `shell_read`/`shell_grep` with `""` now error instead of silently reading/searching `process.cwd()`. Call it in your `actionTranslator` on `args.path` before returning the gated action, so the string the gate judges is the exact string the tool opens.
|
|
1424
|
+
|
|
1390
1425
|
> **⚠️ `shell_write` requires `content` — and a gate cannot cover for it (v0.27+).** `content` used to default to `''`, so a tool call that OMITTED it silently overwrote the target with **zero bytes** and returned `"wrote 0 bytes to <path>"` as success. That is the ordinary shape of a model hitting its **output-token cap** mid-generation on a long file — observed live emptying a 1789-line source file. **No policy can catch it:** a 0-byte write is a *legal* write, and bareguard's `fs` primitive judges `{type:'write', path}` without inspecting the body (the gate correctly `allow`s it). `shell_write` now **rejects** an absent, `null`, or non-string `content` and leaves the file byte-identical; the error tells the model to retry with the full content. An explicit `content: ""` still empties the file — that one is deliberate.
|
|
1391
1426
|
|
|
1392
1427
|
> **`shell_edit` — the surgical write (BA-13).** Changing one line of an 800-line file with `shell_write` forces the model to re-emit **all 800 lines** as tool-call JSON: an output-token tax ∝ file size (output is the expensive token class), paid on every revision, and the maximal broken-tree surface (a truncated rewrite mangles the lines it never meant to touch — the BA-4/BA-6 class). `shell_edit({path, oldText, newText})` emits only the anchor + replacement. Semantics worth knowing: `oldText` must match **exactly once** (a 0/2+ match is a refusal *returned as the tool result*, so the loop continues and the model widens the anchor — not a throw, so a repeated-identical miss is bounded by maxTurns/budget, not the spin guard); missing/empty `oldText` or missing/non-string `newText` **throw** (BA-4 guards; `newText:""` is a legal deletion); the write is **atomic** (sibling temp + rename, mode preserved) so an fs failure never leaves a partial file; and `newText` is a **literal splice** (a `$&`/`$1` in it lands verbatim, unlike `String.replace`). Gate it exactly like `shell_write` but as `{type:'edit'}` — **bareguard gates `edit` by the same `fs.writeScope` as `write` with zero config** (its FS primitive's `FS_TYPES` already includes `edit`).
|
|
@@ -21,7 +21,7 @@ import { createRequire } from 'node:module';
|
|
|
21
21
|
const require = createRequire(import.meta.url);
|
|
22
22
|
const { Loop, wireGate } = require('bare-agent');
|
|
23
23
|
const { OpenAI } = require('bare-agent/providers');
|
|
24
|
-
const { createShellTools } = require('bare-agent/tools');
|
|
24
|
+
const { createShellTools, resolveToolPath } = require('bare-agent/tools');
|
|
25
25
|
|
|
26
26
|
// 1. Build the gate. Every primitive is optional with sensible defaults.
|
|
27
27
|
const gate = new Gate({
|
|
@@ -50,18 +50,25 @@ await gate.init();
|
|
|
50
50
|
// primitives: those fire only on `action.type ∈ {bash, read, write, edit}` and read `action.cmd`/`action.path`.
|
|
51
51
|
// So to make the `bash.allow` + `fs.readScope` config above actually enforce, we MUST translate the shell
|
|
52
52
|
// tools into those primitive shapes — otherwise the caps are silently dead (relayfact F7/BA-3).
|
|
53
|
+
//
|
|
54
|
+
// `path` below is `resolveToolPath(args.path)`, not the raw arg: bareguard's fs primitive checks the
|
|
55
|
+
// path you hand it WITHOUT canonicalizing it, so a `~`-prefixed or relative path would be judged
|
|
56
|
+
// against `fs.readScope`/`fs.writeScope` in a different form than the one the tool actually opens —
|
|
57
|
+
// gate string and opened string must be the SAME string. `resolveToolPath` is the one canonicalizer
|
|
58
|
+
// (expand `~` via os.homedir(), then path.resolve) every shell file tool already applies internally
|
|
59
|
+
// before opening; calling it here too means the gate sees exactly what gets opened.
|
|
53
60
|
const actionTranslator = (toolName, args, ctx) => {
|
|
54
61
|
switch (toolName) {
|
|
55
62
|
// shell_run is argv (no shell); bareguard's bash.allow matches `cmd.startsWith(prefix)`, so join argv[0..].
|
|
56
63
|
case 'shell_run': return { type: 'bash', cmd: (args?.argv || []).join(' '), args, _ctx: ctx ?? null };
|
|
57
64
|
case 'shell_exec': return { type: 'bash', cmd: args?.command, args, _ctx: ctx ?? null };
|
|
58
|
-
// shell_read / shell_grep are reads — gate them through fs.readScope.
|
|
65
|
+
// shell_read / shell_grep are reads — gate them through fs.readScope, on the canonicalized path.
|
|
59
66
|
case 'shell_read':
|
|
60
|
-
case 'shell_grep': return { type: 'read', path: args?.path, args, _ctx: ctx ?? null };
|
|
67
|
+
case 'shell_grep': return { type: 'read', path: resolveToolPath(args?.path), args, _ctx: ctx ?? null };
|
|
61
68
|
// shell_write is a write — gate it through fs.writeScope (add writeScope to the Gate config to enforce).
|
|
62
|
-
case 'shell_write': return { type: 'write', path: args?.path, args, _ctx: ctx ?? null };
|
|
69
|
+
case 'shell_write': return { type: 'write', path: resolveToolPath(args?.path), args, _ctx: ctx ?? null };
|
|
63
70
|
// shell_edit is an anchored edit — bareguard gates {type:'edit'} by the SAME fs.writeScope as write.
|
|
64
|
-
case 'shell_edit': return { type: 'edit', path: args?.path, args, _ctx: ctx ?? null };
|
|
71
|
+
case 'shell_edit': return { type: 'edit', path: resolveToolPath(args?.path), args, _ctx: ctx ?? null };
|
|
65
72
|
default: return { type: toolName, args, _ctx: ctx ?? null };
|
|
66
73
|
}
|
|
67
74
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"index.js",
|
|
6
6
|
"index.d.ts",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
"scripts": {
|
|
98
|
-
"test": "node --test test/**/*.test.js",
|
|
98
|
+
"test": "node --test test/**/*.test.js test/**/*.test.mjs",
|
|
99
99
|
"test:unit": "node --test \"test/!(integration*|*mcp*|spawn*).test.js\"",
|
|
100
100
|
"typecheck": "tsc --noEmit",
|
|
101
101
|
"prebuild:types": "node scripts/clean-types.js",
|
package/primitives.json
CHANGED
|
@@ -141,9 +141,9 @@
|
|
|
141
141
|
"category": "tools",
|
|
142
142
|
"when": "you want to give an agent shell/file tools (read, grep, write, edit, run, exec) — cross-platform, pure Node, zero deps",
|
|
143
143
|
"import": "import { createShellTools } from 'bare-agent/tools'",
|
|
144
|
-
"signature": "createShellTools() => {tools: ToolDef[]}",
|
|
145
|
-
"fails": "never throws at creation; gating is the caller's via Loop({ policy }) and fs.writeScope,
|
|
146
|
-
"example": "const { tools } = createShellTools();\nconst loop = new Loop({ provider, tools, policy });"
|
|
144
|
+
"signature": "createShellTools(options?: {noFollowSymlinks?: boolean}) => {tools: ToolDef[]}",
|
|
145
|
+
"fails": "never throws at creation; gating is the caller's via Loop({ policy }) and fs.writeScope, shell_edit refuses a non-unique anchor as a tool result (file untouched), and with noFollowSymlinks:true the four file tools throw ELOOP on a symlinked final path component.",
|
|
146
|
+
"example": "const { tools } = createShellTools();\nconst loop = new Loop({ provider, tools, policy });\nconst { tools: safeTools } = createShellTools({ noFollowSymlinks: true });"
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
"name": "createSpawnTool",
|
|
@@ -361,10 +361,19 @@
|
|
|
361
361
|
"fails": "skips empty spans and never fabricates; a provider HaltError propagates clean. Each pass forwards usage via onLlmResult; a fact counts once via ctx.recordMemoryOp.",
|
|
362
362
|
"example": "const { facts } = await remember(spans, { provider, store });\nconsole.log(`consolidated ${facts.length} durable facts`);"
|
|
363
363
|
},
|
|
364
|
+
{
|
|
365
|
+
"name": "resolveToolPath",
|
|
366
|
+
"category": "tools",
|
|
367
|
+
"when": "you gate shell file tools — canonicalize the tool's path once with this, put it in the action you pass to `gate.check`, so the gate judges exactly the path the tool opens",
|
|
368
|
+
"import": "import { resolveToolPath } from 'bare-agent/tools'",
|
|
369
|
+
"signature": "resolveToolPath(p: string) => string",
|
|
370
|
+
"fails": "throws on a non-string or empty path, and on a `~` path when no home directory can be determined — never turns `~/x` into `/x` or an empty path into cwd",
|
|
371
|
+
"example": "const resolved = resolveToolPath('~/notes.txt');"
|
|
372
|
+
},
|
|
364
373
|
{
|
|
365
374
|
"name": "Retry",
|
|
366
375
|
"category": "resilience",
|
|
367
|
-
"when": "you want backoff-with-jitter around a flaky async call (a provider request, a plan step) — the retry seam Loop and runPlan wrap providers
|
|
376
|
+
"when": "you want backoff-with-jitter around a flaky async call (a provider request, a plan step) — the retry seam that Loop and runPlan wrap around providers",
|
|
368
377
|
"import": "import { Retry } from 'bare-agent'",
|
|
369
378
|
"signature": "new Retry(options?: RetryOptions)",
|
|
370
379
|
"fails": "rethrows the last error once attempts are exhausted; by default only transient errors (429/5xx/ECONNRESET/ETIMEDOUT) are retried.",
|
package/src/retry.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type RetryOptions = {
|
|
|
23
23
|
export class Retry {
|
|
24
24
|
/**
|
|
25
25
|
* @param {RetryOptions} [options={}]
|
|
26
|
-
* @when you want backoff-with-jitter around a flaky async call (a provider request, a plan step) — the retry seam Loop and runPlan wrap providers
|
|
26
|
+
* @when you want backoff-with-jitter around a flaky async call (a provider request, a plan step) — the retry seam that Loop and runPlan wrap around providers
|
|
27
27
|
* @fails rethrows the last error once attempts are exhausted; by default only transient errors (429/5xx/ECONNRESET/ETIMEDOUT) are retried.
|
|
28
28
|
* @example
|
|
29
29
|
* const retry = new Retry({ maxAttempts: 3, jitter: true });
|
package/src/retry.js
CHANGED
|
@@ -25,7 +25,7 @@ const DEFAULT_RETRY_ON = (err) => {
|
|
|
25
25
|
class Retry {
|
|
26
26
|
/**
|
|
27
27
|
* @param {RetryOptions} [options={}]
|
|
28
|
-
* @when you want backoff-with-jitter around a flaky async call (a provider request, a plan step) — the retry seam Loop and runPlan wrap providers
|
|
28
|
+
* @when you want backoff-with-jitter around a flaky async call (a provider request, a plan step) — the retry seam that Loop and runPlan wrap around providers
|
|
29
29
|
* @fails rethrows the last error once attempts are exhausted; by default only transient errors (429/5xx/ECONNRESET/ETIMEDOUT) are retried.
|
|
30
30
|
* @example
|
|
31
31
|
* const retry = new Retry({ maxAttempts: 3, jitter: true });
|
package/src/tools.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createBrowsingTools } from "../tools/browse";
|
|
2
2
|
import { createMobileTools } from "../tools/mobile";
|
|
3
3
|
import { createShellTools } from "../tools/shell";
|
|
4
|
+
import { resolveToolPath } from "../tools/shell";
|
|
4
5
|
import { createSpawnTool } from "../tools/spawn";
|
|
5
6
|
import { spawnChild } from "../tools/spawn";
|
|
6
7
|
import { createDeferTool } from "../tools/defer";
|
|
7
8
|
import { readQueue as readDeferQueue } from "../tools/defer";
|
|
8
9
|
import { liteCtxMcpBridgeConfig } from "../tools/litectx-mcp";
|
|
9
|
-
export { createBrowsingTools, createMobileTools, createShellTools, createSpawnTool, spawnChild, createDeferTool, readDeferQueue, liteCtxMcpBridgeConfig };
|
|
10
|
+
export { createBrowsingTools, createMobileTools, createShellTools, resolveToolPath, createSpawnTool, spawnChild, createDeferTool, readDeferQueue, liteCtxMcpBridgeConfig };
|
package/src/tools.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { createBrowsingTools } = require('../tools/browse');
|
|
4
4
|
const { createMobileTools } = require('../tools/mobile');
|
|
5
|
-
const { createShellTools } = require('../tools/shell');
|
|
5
|
+
const { createShellTools, resolveToolPath } = require('../tools/shell');
|
|
6
6
|
const { createSpawnTool, spawnChild } = require('../tools/spawn');
|
|
7
7
|
const { createDeferTool, readQueue: readDeferQueue } = require('../tools/defer');
|
|
8
8
|
const { liteCtxMcpBridgeConfig } = require('../tools/litectx-mcp');
|
|
@@ -11,6 +11,7 @@ module.exports = {
|
|
|
11
11
|
createBrowsingTools,
|
|
12
12
|
createMobileTools,
|
|
13
13
|
createShellTools,
|
|
14
|
+
resolveToolPath,
|
|
14
15
|
createSpawnTool,
|
|
15
16
|
spawnChild,
|
|
16
17
|
createDeferTool,
|