bare-agent 0.42.0 → 0.44.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/bareagent.context.md +1 -1
- package/package.json +8 -3
- package/primitives.json +447 -0
- package/src/bareguard-adapter.d.ts +6 -0
- package/src/bareguard-adapter.js +6 -0
- package/src/checkpoint.d.ts +5 -0
- package/src/checkpoint.js +5 -0
- package/src/circuit-breaker.d.ts +5 -0
- package/src/circuit-breaker.js +5 -0
- package/src/complexity.d.ts +9 -0
- package/src/complexity.js +9 -0
- package/src/context-units.d.ts +12 -0
- package/src/context-units.js +12 -0
- package/src/evaluator.d.ts +9 -1
- package/src/evaluator.js +9 -1
- package/src/judge-calibration.d.ts +5 -0
- package/src/judge-calibration.js +5 -0
- package/src/judge.d.ts +5 -0
- package/src/judge.js +5 -0
- package/src/loop.d.ts +16 -2
- package/src/loop.js +29 -12
- package/src/mcp-bridge.d.ts +13 -0
- package/src/mcp-bridge.js +13 -0
- package/src/memory.d.ts +5 -0
- package/src/memory.js +5 -0
- package/src/planner.d.ts +6 -0
- package/src/planner.js +6 -0
- package/src/provider-anthropic.d.ts +4 -0
- package/src/provider-anthropic.js +4 -0
- package/src/provider-clipipe.d.ts +4 -0
- package/src/provider-clipipe.js +4 -0
- package/src/provider-fallback.d.ts +4 -0
- package/src/provider-fallback.js +4 -0
- package/src/provider-gemini.d.ts +7 -1
- package/src/provider-gemini.js +7 -1
- package/src/provider-ollama.d.ts +4 -0
- package/src/provider-ollama.js +11 -2
- package/src/provider-openai.d.ts +4 -0
- package/src/provider-openai.js +31 -3
- package/src/provider-toolcalls.d.ts +29 -0
- package/src/provider-toolcalls.js +45 -0
- package/src/recurse-retrieval.d.ts +22 -0
- package/src/recurse-retrieval.js +22 -0
- package/src/recurse.d.ts +6 -0
- package/src/recurse.js +6 -0
- package/src/refine.d.ts +5 -0
- package/src/refine.js +5 -0
- package/src/remember.d.ts +5 -0
- package/src/remember.js +5 -0
- package/src/retry.d.ts +8 -1
- package/src/retry.js +8 -1
- package/src/run-plan.d.ts +5 -0
- package/src/run-plan.js +5 -0
- package/src/scheduler.d.ts +8 -1
- package/src/scheduler.js +8 -1
- package/src/skills.d.ts +6 -0
- package/src/skills.js +6 -0
- package/src/stash.d.ts +5 -0
- package/src/stash.js +5 -0
- package/src/state.d.ts +8 -1
- package/src/state.js +8 -1
- package/src/store-jsonfile.d.ts +5 -0
- package/src/store-jsonfile.js +5 -0
- package/src/store-sqlite.d.ts +5 -0
- package/src/store-sqlite.js +5 -0
- package/src/stream.d.ts +5 -0
- package/src/stream.js +5 -0
- package/src/transport-jsonl.d.ts +4 -0
- package/src/transport-jsonl.js +4 -0
- package/tools/browse.d.ts +5 -0
- package/tools/browse.js +5 -0
- package/tools/defer.d.ts +13 -1
- package/tools/defer.js +13 -1
- package/tools/litectx-mcp.d.ts +6 -0
- package/tools/litectx-mcp.js +6 -0
- package/tools/mobile.d.ts +5 -0
- package/tools/mobile.js +5 -0
- package/tools/shell.d.ts +5 -0
- package/tools/shell.js +5 -0
- package/tools/spawn.d.ts +10 -0
- package/tools/spawn.js +10 -0
- package/types/index.d.ts +10 -0
package/src/complexity.js
CHANGED
|
@@ -89,6 +89,11 @@ const MAX_ASSESS_LEN = 4000;
|
|
|
89
89
|
* Assess the complexity of a goal/prompt from its text alone (no LLM).
|
|
90
90
|
* @param {string} prompt - The goal to classify.
|
|
91
91
|
* @returns {ComplexityResult}
|
|
92
|
+
* @when you want a fast, no-LLM read of how hard a goal is (simple/medium/complex/critical) to decide whether to invoke the Planner
|
|
93
|
+
* @fails never throws — pure text scoring; non-string/blank input scores lowest. Keyword lists are frozen and a critical-safety override always wins.
|
|
94
|
+
* @example
|
|
95
|
+
* const { level, needsPlanning } = assessComplexity(goal);
|
|
96
|
+
* if (needsPlanning) await planner.plan(goal);
|
|
92
97
|
*/
|
|
93
98
|
function assessComplexity(prompt) {
|
|
94
99
|
if (typeof prompt !== 'string' || !prompt.trim()) {
|
|
@@ -169,6 +174,10 @@ function assessComplexity(prompt) {
|
|
|
169
174
|
* non-string / blank input is `false`.
|
|
170
175
|
* @param {string} prompt - The goal to test.
|
|
171
176
|
* @returns {boolean}
|
|
177
|
+
* @when you want the durable critical-safety floor alone (security/production/compliance/financial) to gate extra scrutiny, without the full scorer
|
|
178
|
+
* @fails never throws — deterministic override; non-string/blank input is false. This floor is non-overridable by design.
|
|
179
|
+
* @example
|
|
180
|
+
* if (isCritical(goal)) verdict = await evaluator.evaluate(goal, result, { contract });
|
|
172
181
|
*/
|
|
173
182
|
function isCritical(prompt) {
|
|
174
183
|
if (typeof prompt !== 'string' || !prompt.trim()) return false;
|
package/src/context-units.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export function fromUnits(units: Array<Record<string, any>>): Array<Record<strin
|
|
|
32
32
|
* error (incl. HaltError) is left to the Loop's own fail-open / HaltError handling — not swallowed here.
|
|
33
33
|
* @param {(units: Array<Record<string, any>>, ctx: any) => (any | Promise<any>)} assembleUnits
|
|
34
34
|
* @returns {(msgs: Array<Record<string, any>>, ctx: any) => Promise<Array<Record<string, any>>>}
|
|
35
|
+
* @when you want to adapt a litectx-style neutral-unit assemble(units, ctx) verb into the Loop's assemble(msgs, ctx) context-assembly seam
|
|
36
|
+
* @fails fail-open — any unexpected return shape sends the original msgs unchanged; a thrown error (incl. HaltError) is left to the Loop's own handling.
|
|
37
|
+
* @example
|
|
38
|
+
* const loop = new Loop({ provider, assemble: unitAssembler(litectx.assemble) });
|
|
35
39
|
*/
|
|
36
40
|
export function unitAssembler(assembleUnits: (units: Array<Record<string, any>>, ctx: any) => (any | Promise<any>)): (msgs: Array<Record<string, any>>, ctx: any) => Promise<Array<Record<string, any>>>;
|
|
37
41
|
/**
|
|
@@ -67,6 +71,10 @@ export function unitAssembler(assembleUnits: (units: Array<Record<string, any>>,
|
|
|
67
71
|
* `({ key, content, unit }) => void|Promise` (REQUIRED; the harvest policy point). `policy` — litectx
|
|
68
72
|
* TrimPolicy: `{ keepLastN }` or `{ maxTokens }` (maxTokens wins). Both verbs are runtime-checked.
|
|
69
73
|
* @returns {((msgs: Array<Record<string, any>>, ctx?: any) => Promise<Array<Record<string, any>>>) & { flush: (msgs: Array<Record<string, any>>, ctx?: any) => Promise<void> }}
|
|
74
|
+
* @when you want to adapt litectx's trim(units, policy) verb into the Loop's destructive trim(msgs, ctx) seam — harvest-before-evict with an F2 residual .flush
|
|
75
|
+
* @fails throws if the trim/onHarvest verbs are missing (runtime-checked); the fold is fail-open and a HaltError propagates. `.flush` drains the residual harvest.
|
|
76
|
+
* @example
|
|
77
|
+
* const loop = new Loop({ provider, trim: unitTrimmer({ trim: litectx.trim, onHarvest, policy: { keepLastN: 20 } }) });
|
|
70
78
|
*/
|
|
71
79
|
export function unitTrimmer(opts?: {
|
|
72
80
|
trim?: Function;
|
|
@@ -93,6 +101,10 @@ export function unitTrimmer(opts?: {
|
|
|
93
101
|
* distinct turns). Normal provider ids (`call_…`) round-trip unchanged through the escape.
|
|
94
102
|
* @param {Record<string, any>} unit - a unit from {@link toUnits} (its `_msgs` backing is read).
|
|
95
103
|
* @returns {string}
|
|
104
|
+
* @when you need the stable content-address for a transcript unit (the key harvest-before-evict writes under) — a collision-resistant 64-bit id
|
|
105
|
+
* @fails never throws; normal provider ids round-trip unchanged, and two near-independent hash streams avoid 32-bit birthday collisions.
|
|
106
|
+
* @example
|
|
107
|
+
* const key = harvestKey(unit); // stable id for this turn's harvest
|
|
96
108
|
*/
|
|
97
109
|
export function harvestKey(unit: Record<string, any>): string;
|
|
98
110
|
/** chars/4 token estimate over a list of messages (matches poc2 / the Loop's own heuristic). */
|
package/src/context-units.js
CHANGED
|
@@ -207,6 +207,10 @@ function fromUnits(units) {
|
|
|
207
207
|
* error (incl. HaltError) is left to the Loop's own fail-open / HaltError handling — not swallowed here.
|
|
208
208
|
* @param {(units: Array<Record<string, any>>, ctx: any) => (any | Promise<any>)} assembleUnits
|
|
209
209
|
* @returns {(msgs: Array<Record<string, any>>, ctx: any) => Promise<Array<Record<string, any>>>}
|
|
210
|
+
* @when you want to adapt a litectx-style neutral-unit assemble(units, ctx) verb into the Loop's assemble(msgs, ctx) context-assembly seam
|
|
211
|
+
* @fails fail-open — any unexpected return shape sends the original msgs unchanged; a thrown error (incl. HaltError) is left to the Loop's own handling.
|
|
212
|
+
* @example
|
|
213
|
+
* const loop = new Loop({ provider, assemble: unitAssembler(litectx.assemble) });
|
|
210
214
|
*/
|
|
211
215
|
function unitAssembler(assembleUnits) {
|
|
212
216
|
if (typeof assembleUnits !== 'function') {
|
|
@@ -240,6 +244,10 @@ function unitAssembler(assembleUnits) {
|
|
|
240
244
|
* distinct turns). Normal provider ids (`call_…`) round-trip unchanged through the escape.
|
|
241
245
|
* @param {Record<string, any>} unit - a unit from {@link toUnits} (its `_msgs` backing is read).
|
|
242
246
|
* @returns {string}
|
|
247
|
+
* @when you need the stable content-address for a transcript unit (the key harvest-before-evict writes under) — a collision-resistant 64-bit id
|
|
248
|
+
* @fails never throws; normal provider ids round-trip unchanged, and two near-independent hash streams avoid 32-bit birthday collisions.
|
|
249
|
+
* @example
|
|
250
|
+
* const key = harvestKey(unit); // stable id for this turn's harvest
|
|
243
251
|
*/
|
|
244
252
|
function harvestKey(unit) {
|
|
245
253
|
const back = (unit && unit._msgs) || [];
|
|
@@ -296,6 +304,10 @@ function harvestKey(unit) {
|
|
|
296
304
|
* `({ key, content, unit }) => void|Promise` (REQUIRED; the harvest policy point). `policy` — litectx
|
|
297
305
|
* TrimPolicy: `{ keepLastN }` or `{ maxTokens }` (maxTokens wins). Both verbs are runtime-checked.
|
|
298
306
|
* @returns {((msgs: Array<Record<string, any>>, ctx?: any) => Promise<Array<Record<string, any>>>) & { flush: (msgs: Array<Record<string, any>>, ctx?: any) => Promise<void> }}
|
|
307
|
+
* @when you want to adapt litectx's trim(units, policy) verb into the Loop's destructive trim(msgs, ctx) seam — harvest-before-evict with an F2 residual .flush
|
|
308
|
+
* @fails throws if the trim/onHarvest verbs are missing (runtime-checked); the fold is fail-open and a HaltError propagates. `.flush` drains the residual harvest.
|
|
309
|
+
* @example
|
|
310
|
+
* const loop = new Loop({ provider, trim: unitTrimmer({ trim: litectx.trim, onHarvest, policy: { keepLastN: 20 } }) });
|
|
299
311
|
*/
|
|
300
312
|
function unitTrimmer(opts) {
|
|
301
313
|
const { trim, onHarvest, policy = {} } = opts || {};
|
package/src/evaluator.d.ts
CHANGED
|
@@ -114,7 +114,15 @@ export type EvaluateOptions = {
|
|
|
114
114
|
* Built flagged-and-deletable per D11 — opt-in by import; calibrate the rubric/prompt from execution traces.
|
|
115
115
|
*/
|
|
116
116
|
export class Evaluator {
|
|
117
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* @param {EvaluatorOptions} [options]
|
|
119
|
+
* @when you need to judge an output against a goal or contract — deterministically (predicate), by LLM rubric, or with a tool-running critic that exercises the live artifact
|
|
120
|
+
* @fails never throws for a bad grade — returns a Verdict {status: satisfied|needs_revision|failed}; a provider HaltError propagates clean. Judge tokens forward via onLlmResult.
|
|
121
|
+
* @example
|
|
122
|
+
* const evaluator = new Evaluator({ provider });
|
|
123
|
+
* const verdict = await evaluator.evaluate(goal, result, { rubric });
|
|
124
|
+
* if (!verdict.pass) revise(verdict.critique);
|
|
125
|
+
*/
|
|
118
126
|
constructor(options?: EvaluatorOptions);
|
|
119
127
|
provider: import("../types").Provider | null;
|
|
120
128
|
prompt: string;
|
package/src/evaluator.js
CHANGED
|
@@ -108,7 +108,15 @@ Output your FINAL answer as ONLY this JSON, no markdown, no prose:
|
|
|
108
108
|
* Built flagged-and-deletable per D11 — opt-in by import; calibrate the rubric/prompt from execution traces.
|
|
109
109
|
*/
|
|
110
110
|
class Evaluator {
|
|
111
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* @param {EvaluatorOptions} [options]
|
|
113
|
+
* @when you need to judge an output against a goal or contract — deterministically (predicate), by LLM rubric, or with a tool-running critic that exercises the live artifact
|
|
114
|
+
* @fails never throws for a bad grade — returns a Verdict {status: satisfied|needs_revision|failed}; a provider HaltError propagates clean. Judge tokens forward via onLlmResult.
|
|
115
|
+
* @example
|
|
116
|
+
* const evaluator = new Evaluator({ provider });
|
|
117
|
+
* const verdict = await evaluator.evaluate(goal, result, { rubric });
|
|
118
|
+
* if (!verdict.pass) revise(verdict.critique);
|
|
119
|
+
*/
|
|
112
120
|
constructor(options = /** @type {EvaluatorOptions} */ ({})) {
|
|
113
121
|
this.provider = options.provider || null;
|
|
114
122
|
this.prompt = options.prompt || GRADER_PROMPT;
|
|
@@ -129,6 +129,11 @@ export function gradeRun(runs: Array<{
|
|
|
129
129
|
* a separate admission gate — a leak in any style blocks admission even at a passing clear-case floor.
|
|
130
130
|
* @param {(payload:object)=>any} [opts.onLlmResult] - Budget hook forwarded to each judge call.
|
|
131
131
|
* @returns {Promise<ReturnType<typeof gradeRun> & { reps:number, floor:number, totalCostUsd:number|null, unpricedCalls:number, injectionBattery: { styles: Array<{label:string, usable:number, broke:number, resisted:boolean}>, allResisted:boolean, leaks:number } }>}
|
|
132
|
+
* @when you are admitting an LLM tier to the judge role and need to grade it against the frozen clear-case battery and resist every injection style before trusting it
|
|
133
|
+
* @fails never fabricates a pass — a leak in any injection style blocks admission even above the clear-case floor, and the negative control must fail the set. HaltError propagates clean.
|
|
134
|
+
* @example
|
|
135
|
+
* const report = await calibrate({ provider, reps: 5, floor: 7 });
|
|
136
|
+
* if (!report.injectionBattery.allResisted) reject('injection leak');
|
|
132
137
|
*/
|
|
133
138
|
export function calibrate(opts?: {
|
|
134
139
|
provider: import("../types").Provider;
|
package/src/judge-calibration.js
CHANGED
|
@@ -125,6 +125,11 @@ function gradeRun(runs, floor) {
|
|
|
125
125
|
* a separate admission gate — a leak in any style blocks admission even at a passing clear-case floor.
|
|
126
126
|
* @param {(payload:object)=>any} [opts.onLlmResult] - Budget hook forwarded to each judge call.
|
|
127
127
|
* @returns {Promise<ReturnType<typeof gradeRun> & { reps:number, floor:number, totalCostUsd:number|null, unpricedCalls:number, injectionBattery: { styles: Array<{label:string, usable:number, broke:number, resisted:boolean}>, allResisted:boolean, leaks:number } }>}
|
|
128
|
+
* @when you are admitting an LLM tier to the judge role and need to grade it against the frozen clear-case battery and resist every injection style before trusting it
|
|
129
|
+
* @fails never fabricates a pass — a leak in any injection style blocks admission even above the clear-case floor, and the negative control must fail the set. HaltError propagates clean.
|
|
130
|
+
* @example
|
|
131
|
+
* const report = await calibrate({ provider, reps: 5, floor: 7 });
|
|
132
|
+
* if (!report.injectionBattery.allResisted) reject('injection leak');
|
|
128
133
|
*/
|
|
129
134
|
async function calibrate(opts = /** @type {any} */ ({})) {
|
|
130
135
|
const provider = opts.provider;
|
package/src/judge.d.ts
CHANGED
|
@@ -157,6 +157,11 @@ export type JudgeVerdict = {
|
|
|
157
157
|
* @throws {ValidationError} on bad inputs (missing request/artifact/provider) — stamped `context.lib='bare-agent'`
|
|
158
158
|
* at the throw site (contract 4: typed attribution, never sniffed from prose).
|
|
159
159
|
* @throws {HaltError} propagated clean from the provider (a governance halt is not a judge failure).
|
|
160
|
+
* @when you need a decisive honored/broke verdict on whether one egress artifact honored the verbatim request — a return-time integrity check
|
|
161
|
+
* @fails throws ValidationError on bad inputs; HaltError propagates clean. Cannot-confirm-honored floors to `broke`; truncation/parse-error are distinct flagged outcomes, never laundered to honored.
|
|
162
|
+
* @example
|
|
163
|
+
* const { verdict, where } = await judge({ request, artifact, provider });
|
|
164
|
+
* if (verdict === 'broke') flag(where);
|
|
160
165
|
*/
|
|
161
166
|
export function judge(options?: JudgeOptions): Promise<JudgeVerdict>;
|
|
162
167
|
/**
|
package/src/judge.js
CHANGED
|
@@ -145,6 +145,11 @@ function normalizeWhere(where) {
|
|
|
145
145
|
* @throws {ValidationError} on bad inputs (missing request/artifact/provider) — stamped `context.lib='bare-agent'`
|
|
146
146
|
* at the throw site (contract 4: typed attribution, never sniffed from prose).
|
|
147
147
|
* @throws {HaltError} propagated clean from the provider (a governance halt is not a judge failure).
|
|
148
|
+
* @when you need a decisive honored/broke verdict on whether one egress artifact honored the verbatim request — a return-time integrity check
|
|
149
|
+
* @fails throws ValidationError on bad inputs; HaltError propagates clean. Cannot-confirm-honored floors to `broke`; truncation/parse-error are distinct flagged outcomes, never laundered to honored.
|
|
150
|
+
* @example
|
|
151
|
+
* const { verdict, where } = await judge({ request, artifact, provider });
|
|
152
|
+
* if (verdict === 'broke') flag(where);
|
|
148
153
|
*/
|
|
149
154
|
async function judge(options = /** @type {JudgeOptions} */ ({})) {
|
|
150
155
|
const { request, artifact, provider } = options;
|
package/src/loop.d.ts
CHANGED
|
@@ -105,6 +105,12 @@ export class Loop {
|
|
|
105
105
|
* tool outcomes to `gate.record` (via wireGate) and never kill the loop on error.
|
|
106
106
|
* @param {LoopOptions} options
|
|
107
107
|
* @throws {Error} `[Loop] requires a provider` — when options.provider is missing.
|
|
108
|
+
* @when you are running a model think/act/observe cycle and need round accounting, tool dispatch, spin guards, and a governance chokepoint
|
|
109
|
+
* @fails returns the last assistant text with an `error` tag on any bound (halt/deny-streak/truncation/provider error); throws only if constructed without a provider. A policy HaltError exits clean.
|
|
110
|
+
* @example
|
|
111
|
+
* const loop = new Loop({ provider, policy });
|
|
112
|
+
* const { text, error, metrics } = await loop.run(messages, tools);
|
|
113
|
+
* if (error) handle(error);
|
|
108
114
|
*/
|
|
109
115
|
constructor(options?: LoopOptions);
|
|
110
116
|
provider: import("../types").Provider;
|
|
@@ -161,7 +167,7 @@ export class Loop {
|
|
|
161
167
|
* thunk is re-evaluated each round (D4/eval-assist F2) so a tool set that grows mid-run — e.g. a skill
|
|
162
168
|
* unlocking its tools — is offered on the next round; a static array is resolved once at wire time.
|
|
163
169
|
* @param {Record<string, any>} [options={}] - Per-run overrides (system, temperature, ctx, etc.).
|
|
164
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, temperatureDropped?: boolean}>}
|
|
170
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: {name: string|undefined, error: string}, temperatureDropped?: boolean}>}
|
|
165
171
|
* On halt the returned `error` is `halt:<rule>` (or `halt:unknown` if the
|
|
166
172
|
* thrown HaltError carried no `rule`), and `msgs` is sanitized so any
|
|
167
173
|
* dangling assistant `tool_calls` from the halted round are paired with
|
|
@@ -199,6 +205,10 @@ export class Loop {
|
|
|
199
205
|
model: string | null;
|
|
200
206
|
msgs: Message[];
|
|
201
207
|
metrics: RunMetrics;
|
|
208
|
+
malformedToolCall?: {
|
|
209
|
+
name: string | undefined;
|
|
210
|
+
error: string;
|
|
211
|
+
};
|
|
202
212
|
temperatureDropped?: boolean;
|
|
203
213
|
}>;
|
|
204
214
|
_warnedTruncated: boolean | undefined;
|
|
@@ -228,7 +238,7 @@ export class Loop {
|
|
|
228
238
|
* @param {string} text - User message.
|
|
229
239
|
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
230
240
|
* @param {Record<string, any>} [options={}] - Per-run overrides.
|
|
231
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, temperatureDropped?: boolean}>}
|
|
241
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: {name: string|undefined, error: string}, temperatureDropped?: boolean}>}
|
|
232
242
|
*/
|
|
233
243
|
chat(text: string, tools?: ToolDef[], options?: Record<string, any>): Promise<{
|
|
234
244
|
text: string;
|
|
@@ -240,6 +250,10 @@ export class Loop {
|
|
|
240
250
|
model: string | null;
|
|
241
251
|
msgs: Message[];
|
|
242
252
|
metrics: RunMetrics;
|
|
253
|
+
malformedToolCall?: {
|
|
254
|
+
name: string | undefined;
|
|
255
|
+
error: string;
|
|
256
|
+
};
|
|
243
257
|
temperatureDropped?: boolean;
|
|
244
258
|
}>;
|
|
245
259
|
/**
|
package/src/loop.js
CHANGED
|
@@ -273,6 +273,12 @@ class Loop {
|
|
|
273
273
|
* tool outcomes to `gate.record` (via wireGate) and never kill the loop on error.
|
|
274
274
|
* @param {LoopOptions} options
|
|
275
275
|
* @throws {Error} `[Loop] requires a provider` — when options.provider is missing.
|
|
276
|
+
* @when you are running a model think/act/observe cycle and need round accounting, tool dispatch, spin guards, and a governance chokepoint
|
|
277
|
+
* @fails returns the last assistant text with an `error` tag on any bound (halt/deny-streak/truncation/provider error); throws only if constructed without a provider. A policy HaltError exits clean.
|
|
278
|
+
* @example
|
|
279
|
+
* const loop = new Loop({ provider, policy });
|
|
280
|
+
* const { text, error, metrics } = await loop.run(messages, tools);
|
|
281
|
+
* if (error) handle(error);
|
|
276
282
|
*/
|
|
277
283
|
constructor(options = /** @type {LoopOptions} */ ({})) {
|
|
278
284
|
if (!options.provider) throw new Error('[Loop] requires a provider');
|
|
@@ -449,7 +455,7 @@ class Loop {
|
|
|
449
455
|
* thunk is re-evaluated each round (D4/eval-assist F2) so a tool set that grows mid-run — e.g. a skill
|
|
450
456
|
* unlocking its tools — is offered on the next round; a static array is resolved once at wire time.
|
|
451
457
|
* @param {Record<string, any>} [options={}] - Per-run overrides (system, temperature, ctx, etc.).
|
|
452
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, temperatureDropped?: boolean}>}
|
|
458
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: {name: string|undefined, error: string}, temperatureDropped?: boolean}>}
|
|
453
459
|
* On halt the returned `error` is `halt:<rule>` (or `halt:unknown` if the
|
|
454
460
|
* thrown HaltError carried no `rule`), and `msgs` is sanitized so any
|
|
455
461
|
* dangling assistant `tool_calls` from the halted round are paired with
|
|
@@ -547,6 +553,14 @@ class Loop {
|
|
|
547
553
|
// RESPONSE, not the provider object — a wrapped/fallback provider can lose `.model`). Surfaced on
|
|
548
554
|
// every return so a caller reads which model produced the result without the onLlmResult side channel.
|
|
549
555
|
let lastModel = null;
|
|
556
|
+
// BA-27: the most recent round's malformed-tool-call marker, or null. A model can emit a tool call
|
|
557
|
+
// whose arguments are syntactically-broken JSON; the provider returns NO usable tool calls plus this
|
|
558
|
+
// marker (rather than throwing and losing the billed round). Surfaced on the run's return like
|
|
559
|
+
// lastStopReason, so a Loop caller (e.g. the bareloop adopter — which reads run(), not generate())
|
|
560
|
+
// can tell "the model sent a broken call" apart from "the model sent no call at all" — both present
|
|
561
|
+
// as `toolCalls: []`. Reset each completed round; a malformed round always terminates the run (no
|
|
562
|
+
// usable call to continue with), so it can only be non-null on the round that returns.
|
|
563
|
+
let lastMalformedToolCall = null;
|
|
550
564
|
// BA-10: sticky across rounds — true if ANY round's `temperature` was dropped by the model (400,
|
|
551
565
|
// unsupported/deprecated) and retried without it. Surfaced on the result so an upstream receipt
|
|
552
566
|
// (recurse's refineLeaf) can report the EFFECTIVE temperature rather than the ignored request.
|
|
@@ -583,7 +597,7 @@ class Loop {
|
|
|
583
597
|
sealDanglingToolCalls(msgs, `[halted:${stuckTag}]`);
|
|
584
598
|
this._reportError('stuck', new Error(`tool "${tc.name}" failed ${identicalErrors} times with identical arguments`), { rule: stuckTag, attempts: identicalErrors });
|
|
585
599
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, stuck: true, rule: stuckTag, cost: totalCost } });
|
|
586
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: stuckTag, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
600
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: stuckTag, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
587
601
|
}
|
|
588
602
|
return null;
|
|
589
603
|
};
|
|
@@ -830,7 +844,7 @@ class Loop {
|
|
|
830
844
|
this._reportError('provider', err, { round });
|
|
831
845
|
if (this.throwOnError) throw err;
|
|
832
846
|
// BA-5: a mid-run provider failure must not erase the work of the rounds that succeeded.
|
|
833
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: err.message, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
847
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: err.message, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
834
848
|
}
|
|
835
849
|
|
|
836
850
|
lastUsage = result.usage || lastUsage;
|
|
@@ -843,6 +857,9 @@ class Loop {
|
|
|
843
857
|
// path reads lastStopReason). Non-string / absent ⇒ null (the provider's pre-BA-6 degrade).
|
|
844
858
|
lastStopReason = typeof result.stopReason === 'string' ? result.stopReason : null;
|
|
845
859
|
if (typeof result.model === 'string' && result.model) lastModel = result.model;
|
|
860
|
+
// BA-27: capture (and reset) this round's malformed-tool-call marker for surfacing on the return.
|
|
861
|
+
lastMalformedToolCall = (result.malformedToolCall && typeof result.malformedToolCall === 'object')
|
|
862
|
+
? result.malformedToolCall : null;
|
|
846
863
|
if (result.temperatureDropped) temperatureDropped = true;
|
|
847
864
|
// Publish the latest measured usage to ctx (non-enumerable, fail-open) so a transcript-bound seam —
|
|
848
865
|
// e.g. F2 stash auto-compaction — can read EXACT provider-counted `inputTokens` to gauge context
|
|
@@ -927,7 +944,7 @@ class Loop {
|
|
|
927
944
|
sealDanglingToolCalls(msgs, `[halted:${session.error}]`);
|
|
928
945
|
this._reportError('session', new Error(`provider session terminated: ${session.error}`), { rule: session.error, sessionTurns: session.turns ?? null });
|
|
929
946
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, rule: session.error, sessionTurns: session.turns ?? null, cost: totalCost } });
|
|
930
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: session.error, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
947
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: session.error, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
931
948
|
}
|
|
932
949
|
|
|
933
950
|
// BA-13: classify this round's terminal signal against the neutral stop-reason vocabulary. BA-6
|
|
@@ -992,7 +1009,7 @@ class Loop {
|
|
|
992
1009
|
msgs.push({ role: 'assistant', content: result.text });
|
|
993
1010
|
}
|
|
994
1011
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, ...(terminal === 'truncated' && { truncated: true }), terminal, stopReason: lastStopReason, droppedToolCalls: dropped, cost: totalCost } });
|
|
995
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: errorTag, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1012
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: errorTag, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
996
1013
|
}
|
|
997
1014
|
|
|
998
1015
|
// No tool calls — LLM gave a final text response
|
|
@@ -1012,7 +1029,7 @@ class Loop {
|
|
|
1012
1029
|
try { await flush(msgs, ctx); }
|
|
1013
1030
|
catch (err) { if (err instanceof HaltError) throw err; this._reportError('trim-flush', err, { round }); }
|
|
1014
1031
|
}
|
|
1015
|
-
return { text: result.text, toolCalls: [], usage: lastUsage, cost: totalCost, error: null, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1032
|
+
return { text: result.text, toolCalls: [], usage: lastUsage, cost: totalCost, error: null, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1016
1033
|
}
|
|
1017
1034
|
|
|
1018
1035
|
// Execute tool calls
|
|
@@ -1121,7 +1138,7 @@ class Loop {
|
|
|
1121
1138
|
sealDanglingToolCalls(msgs, `[halted:${denyTag}]`);
|
|
1122
1139
|
this._reportError('denied', new Error(`policy denied ${consecutiveDenials} consecutive tool calls (${tc.name})`), { rule: denyTag, denials: consecutiveDenials });
|
|
1123
1140
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, denied: true, rule: denyTag, cost: totalCost } });
|
|
1124
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: denyTag, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1141
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: denyTag, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1125
1142
|
}
|
|
1126
1143
|
continue;
|
|
1127
1144
|
}
|
|
@@ -1195,7 +1212,7 @@ class Loop {
|
|
|
1195
1212
|
// BA-5: the rule tag survives on `error`; so does the work. A halt is how a bounded attempt is
|
|
1196
1213
|
// SUPPOSED to end — the caller reads `error` to know it was bounded and `text` to learn from it.
|
|
1197
1214
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, halted: true, rule, cost: totalCost } });
|
|
1198
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: `halt:${rule}`, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1215
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: `halt:${rule}`, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1199
1216
|
}
|
|
1200
1217
|
throw err;
|
|
1201
1218
|
}
|
|
@@ -1225,20 +1242,20 @@ class Loop {
|
|
|
1225
1242
|
const rule = err.rule || 'unknown';
|
|
1226
1243
|
this._reportError('halt', err, { rule, reason: err.decision?.reason ?? null });
|
|
1227
1244
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, halted: true, rule, cost: totalCost } });
|
|
1228
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: `halt:${rule}`, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1245
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: `halt:${rule}`, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1229
1246
|
}
|
|
1230
1247
|
this._reportError('trim-flush', err, { phase: 'stop' });
|
|
1231
1248
|
}
|
|
1232
1249
|
}
|
|
1233
1250
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, stopped: true, cost: totalCost } });
|
|
1234
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: null, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1251
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: null, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1235
1252
|
}
|
|
1236
1253
|
|
|
1237
1254
|
// Hard safety limit — should never fire under normal usage; bareguard's
|
|
1238
1255
|
// limits.maxTurns (or the LLM's natural completion) ends the loop first.
|
|
1239
1256
|
const warning = `[Loop] hit internal safety limit of ${HARD_ROUND_LIMIT} rounds. Wire bareguard for proper governance — see bare-agent/bareguard.`;
|
|
1240
1257
|
this._safeEmit({ type: 'loop:done', data: { text: lastText, warning, cost: totalCost } });
|
|
1241
|
-
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: warning, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1258
|
+
return { text: lastText, toolCalls: [], usage: lastUsage, cost: totalCost, error: warning, stopReason: lastStopReason, model: lastModel, msgs, metrics: finalizeMetrics(), ...(lastMalformedToolCall && { malformedToolCall: lastMalformedToolCall }), ...(temperatureDropped && { temperatureDropped: true }) };
|
|
1242
1259
|
}
|
|
1243
1260
|
|
|
1244
1261
|
/**
|
|
@@ -1311,7 +1328,7 @@ class Loop {
|
|
|
1311
1328
|
* @param {string} text - User message.
|
|
1312
1329
|
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
1313
1330
|
* @param {Record<string, any>} [options={}] - Per-run overrides.
|
|
1314
|
-
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, temperatureDropped?: boolean}>}
|
|
1331
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, stopReason: string|null, model: string|null, msgs: Message[], metrics: RunMetrics, malformedToolCall?: {name: string|undefined, error: string}, temperatureDropped?: boolean}>}
|
|
1315
1332
|
*/
|
|
1316
1333
|
async chat(text, tools = [], options = {}) {
|
|
1317
1334
|
this._history.push({ role: 'user', content: text });
|
package/src/mcp-bridge.d.ts
CHANGED
|
@@ -92,6 +92,11 @@ export type RpcClient = {
|
|
|
92
92
|
* of this hook also opts default discovery into the project-cwd `./.mcp.json`,
|
|
93
93
|
* since each command is then vetted regardless of source.
|
|
94
94
|
* @returns {Promise<{tools: ToolDef[], metaTools?: ToolDef[], servers: string[], systemContext: string, denied: DeniedTool[], errors?: Array<{server: string, error: string}>, close: Function}>}
|
|
95
|
+
* @when you want to auto-discover MCP servers and expose them as bareagent tools in one call — with a trust hook gating command execution
|
|
96
|
+
* @fails a trust hook returning false skips a server (its command never runs) and a throw is fail-closed (deny); returns tools plus a close() to shut servers down.
|
|
97
|
+
* @example
|
|
98
|
+
* const { tools, close } = await createMCPBridge();
|
|
99
|
+
* const loop = new Loop({ provider, tools });
|
|
95
100
|
*/
|
|
96
101
|
export function createMCPBridge(opts?: {
|
|
97
102
|
bridgePath?: string | undefined;
|
|
@@ -120,6 +125,10 @@ export function createMCPBridge(opts?: {
|
|
|
120
125
|
* @param {{ includeProjectConfig?: boolean }} [opts] - When no explicit `configPaths` are given,
|
|
121
126
|
* set `includeProjectConfig: true` to also scan `./.mcp.json`. Default false — see PROJECT_CONFIG_PATH.
|
|
122
127
|
* @returns {Map<string, ServerDef>}
|
|
128
|
+
* @when you want to find configured MCP servers from the trusted $HOME/IDE config paths (or explicit paths) — discovery without invoking them
|
|
129
|
+
* @fails honors explicit configPaths verbatim and scans trusted defaults otherwise; returns a Map and never executes a server command.
|
|
130
|
+
* @example
|
|
131
|
+
* const servers = discoverServers();
|
|
123
132
|
*/
|
|
124
133
|
export function discoverServers(configPaths?: string[], { includeProjectConfig }?: {
|
|
125
134
|
includeProjectConfig?: boolean;
|
|
@@ -142,5 +151,9 @@ export function discoverServers(configPaths?: string[], { includeProjectConfig }
|
|
|
142
151
|
* @param {ToolDef[]} tools - The bulk-loaded, name-prefixed tools array.
|
|
143
152
|
* @param {string} [discoveredAt] - ISO timestamp from .mcp-bridge.json.
|
|
144
153
|
* @returns {ToolDef[]} [mcp_discover, mcp_invoke]
|
|
154
|
+
* @when you want the two bulk MCP meta-tools (mcp_discover, mcp_invoke) instead of exposing every discovered tool individually — one gate-check per invocation
|
|
155
|
+
* @fails returns [mcp_discover, mcp_invoke]; a tool name does not travel as action.type (a deliberate v0.9 trade for one gate-check per call).
|
|
156
|
+
* @example
|
|
157
|
+
* const metaTools = buildMetaTools(tools);
|
|
145
158
|
*/
|
|
146
159
|
export function buildMetaTools(tools: ToolDef[], discoveredAt?: string): ToolDef[];
|
package/src/mcp-bridge.js
CHANGED
|
@@ -80,6 +80,10 @@ const TRUSTED_CONFIG_PATHS = [
|
|
|
80
80
|
* @param {{ includeProjectConfig?: boolean }} [opts] - When no explicit `configPaths` are given,
|
|
81
81
|
* set `includeProjectConfig: true` to also scan `./.mcp.json`. Default false — see PROJECT_CONFIG_PATH.
|
|
82
82
|
* @returns {Map<string, ServerDef>}
|
|
83
|
+
* @when you want to find configured MCP servers from the trusted $HOME/IDE config paths (or explicit paths) — discovery without invoking them
|
|
84
|
+
* @fails honors explicit configPaths verbatim and scans trusted defaults otherwise; returns a Map and never executes a server command.
|
|
85
|
+
* @example
|
|
86
|
+
* const servers = discoverServers();
|
|
83
87
|
*/
|
|
84
88
|
function discoverServers(configPaths, { includeProjectConfig = false } = {}) {
|
|
85
89
|
let paths;
|
|
@@ -523,6 +527,10 @@ function buildSystemContext(servers, tools, denied) {
|
|
|
523
527
|
* @param {ToolDef[]} tools - The bulk-loaded, name-prefixed tools array.
|
|
524
528
|
* @param {string} [discoveredAt] - ISO timestamp from .mcp-bridge.json.
|
|
525
529
|
* @returns {ToolDef[]} [mcp_discover, mcp_invoke]
|
|
530
|
+
* @when you want the two bulk MCP meta-tools (mcp_discover, mcp_invoke) instead of exposing every discovered tool individually — one gate-check per invocation
|
|
531
|
+
* @fails returns [mcp_discover, mcp_invoke]; a tool name does not travel as action.type (a deliberate v0.9 trade for one gate-check per call).
|
|
532
|
+
* @example
|
|
533
|
+
* const metaTools = buildMetaTools(tools);
|
|
526
534
|
*/
|
|
527
535
|
function buildMetaTools(tools, discoveredAt) {
|
|
528
536
|
// Catalog descriptors: same info the LLM would see for bulk-loaded tools,
|
|
@@ -637,6 +645,11 @@ function buildMetaTools(tools, discoveredAt) {
|
|
|
637
645
|
* of this hook also opts default discovery into the project-cwd `./.mcp.json`,
|
|
638
646
|
* since each command is then vetted regardless of source.
|
|
639
647
|
* @returns {Promise<{tools: ToolDef[], metaTools?: ToolDef[], servers: string[], systemContext: string, denied: DeniedTool[], errors?: Array<{server: string, error: string}>, close: Function}>}
|
|
648
|
+
* @when you want to auto-discover MCP servers and expose them as bareagent tools in one call — with a trust hook gating command execution
|
|
649
|
+
* @fails a trust hook returning false skips a server (its command never runs) and a throw is fail-closed (deny); returns tools plus a close() to shut servers down.
|
|
650
|
+
* @example
|
|
651
|
+
* const { tools, close } = await createMCPBridge();
|
|
652
|
+
* const loop = new Loop({ provider, tools });
|
|
640
653
|
*/
|
|
641
654
|
async function createMCPBridge(opts = {}) {
|
|
642
655
|
if ('policy' in opts) {
|
package/src/memory.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export class Memory {
|
|
|
19
19
|
/**
|
|
20
20
|
* @param {{ store?: Store }} [options] - Store backend (must implement store/search/get/delete).
|
|
21
21
|
* @throws {Error} `[Memory] requires options.store` — when options.store is missing.
|
|
22
|
+
* @when you want thin store-backed memory (store/search/get/delete) an agent can write to and recall from, over any swappable backend
|
|
23
|
+
* @fails throws if constructed without a store; recall/store metering is opt-in via ctx. All persistence delegates to the backend.
|
|
24
|
+
* @example
|
|
25
|
+
* const memory = new Memory({ store: new JsonFile({ path: './mem.json' }) });
|
|
26
|
+
* await memory.store({ text: 'a durable fact' });
|
|
22
27
|
*/
|
|
23
28
|
constructor(options?: {
|
|
24
29
|
store?: Store;
|
package/src/memory.js
CHANGED
|
@@ -21,6 +21,11 @@ class Memory {
|
|
|
21
21
|
/**
|
|
22
22
|
* @param {{ store?: Store }} [options] - Store backend (must implement store/search/get/delete).
|
|
23
23
|
* @throws {Error} `[Memory] requires options.store` — when options.store is missing.
|
|
24
|
+
* @when you want thin store-backed memory (store/search/get/delete) an agent can write to and recall from, over any swappable backend
|
|
25
|
+
* @fails throws if constructed without a store; recall/store metering is opt-in via ctx. All persistence delegates to the backend.
|
|
26
|
+
* @example
|
|
27
|
+
* const memory = new Memory({ store: new JsonFile({ path: './mem.json' }) });
|
|
28
|
+
* await memory.store({ text: 'a durable fact' });
|
|
24
29
|
*/
|
|
25
30
|
constructor(options = {}) {
|
|
26
31
|
if (!options.store) throw new Error('[Memory] requires options.store');
|
package/src/planner.d.ts
CHANGED
|
@@ -46,6 +46,12 @@ export class Planner {
|
|
|
46
46
|
/**
|
|
47
47
|
* @param {PlannerOptions} options
|
|
48
48
|
* @throws {Error} `[Planner] requires a provider` — when options.provider is missing.
|
|
49
|
+
* @when you need to turn a goal into an ordered step DAG for an LLM to execute — optionally forcing exactly N independent steps for fan-out
|
|
50
|
+
* @fails throws if constructed without a provider; a plan call's HaltError (governance cap) propagates clean. Plan-call usage forwards via onLlmResult.
|
|
51
|
+
* @example
|
|
52
|
+
* const planner = new Planner({ provider });
|
|
53
|
+
* const steps = await planner.plan('ship the release', { count: 4 });
|
|
54
|
+
* await runPlan(steps, ctx);
|
|
49
55
|
*/
|
|
50
56
|
constructor(options?: PlannerOptions);
|
|
51
57
|
provider: import("../types").Provider;
|
package/src/planner.js
CHANGED
|
@@ -39,6 +39,12 @@ class Planner {
|
|
|
39
39
|
/**
|
|
40
40
|
* @param {PlannerOptions} options
|
|
41
41
|
* @throws {Error} `[Planner] requires a provider` — when options.provider is missing.
|
|
42
|
+
* @when you need to turn a goal into an ordered step DAG for an LLM to execute — optionally forcing exactly N independent steps for fan-out
|
|
43
|
+
* @fails throws if constructed without a provider; a plan call's HaltError (governance cap) propagates clean. Plan-call usage forwards via onLlmResult.
|
|
44
|
+
* @example
|
|
45
|
+
* const planner = new Planner({ provider });
|
|
46
|
+
* const steps = await planner.plan('ship the release', { count: 4 });
|
|
47
|
+
* await runPlan(steps, ctx);
|
|
42
48
|
*/
|
|
43
49
|
constructor(options = /** @type {PlannerOptions} */ ({})) {
|
|
44
50
|
if (!options.provider) throw new Error('[Planner] requires a provider');
|
|
@@ -62,6 +62,10 @@ export class AnthropicProvider {
|
|
|
62
62
|
/**
|
|
63
63
|
* @param {AnthropicOptions} [options]
|
|
64
64
|
* @throws {Error} `[AnthropicProvider] requires apiKey` — when apiKey is missing.
|
|
65
|
+
* @when you want Claude models as the Loop's provider — native Messages API with opt-in prompt caching and thinking-block passthrough
|
|
66
|
+
* @fails throws on a missing apiKey; normalizes stopReason and usage (usage:null when the API omits it); a socket idle/deadline/transport cut rejects with a retryable Timeout/ProviderError.
|
|
67
|
+
* @example
|
|
68
|
+
* const provider = new AnthropicProvider({ apiKey, model: 'claude-sonnet-5' });
|
|
65
69
|
*/
|
|
66
70
|
constructor(options?: AnthropicOptions);
|
|
67
71
|
apiKey: string;
|
|
@@ -41,6 +41,10 @@ class AnthropicProvider {
|
|
|
41
41
|
/**
|
|
42
42
|
* @param {AnthropicOptions} [options]
|
|
43
43
|
* @throws {Error} `[AnthropicProvider] requires apiKey` — when apiKey is missing.
|
|
44
|
+
* @when you want Claude models as the Loop's provider — native Messages API with opt-in prompt caching and thinking-block passthrough
|
|
45
|
+
* @fails throws on a missing apiKey; normalizes stopReason and usage (usage:null when the API omits it); a socket idle/deadline/transport cut rejects with a retryable Timeout/ProviderError.
|
|
46
|
+
* @example
|
|
47
|
+
* const provider = new AnthropicProvider({ apiKey, model: 'claude-sonnet-5' });
|
|
44
48
|
*/
|
|
45
49
|
constructor(options = {}) {
|
|
46
50
|
if (!options.apiKey) throw new Error('[AnthropicProvider] requires apiKey');
|
|
@@ -123,6 +123,10 @@ export class CLIPipeProvider {
|
|
|
123
123
|
* Provider that pipes prompts to a CLI command via stdin and reads stdout.
|
|
124
124
|
* @param {CLIPipeOptions} [options]
|
|
125
125
|
* @throws {Error} `[CLIPipeProvider] requires command` — when options.command is missing.
|
|
126
|
+
* @when you want to drive a CLI (e.g. the claude CLI) as the Loop's provider — native MCP tools or a JSON emulation envelope
|
|
127
|
+
* @fails throws on a missing command; in native mode it owns its own cycle (ownsCycle), so per-round Loop seams are refused at construction rather than left silently dead.
|
|
128
|
+
* @example
|
|
129
|
+
* const provider = new CLIPipeProvider({ command: 'claude' });
|
|
126
130
|
*/
|
|
127
131
|
constructor(options?: CLIPipeOptions);
|
|
128
132
|
command: string;
|
package/src/provider-clipipe.js
CHANGED
|
@@ -112,6 +112,10 @@ class CLIPipeProvider {
|
|
|
112
112
|
* Provider that pipes prompts to a CLI command via stdin and reads stdout.
|
|
113
113
|
* @param {CLIPipeOptions} [options]
|
|
114
114
|
* @throws {Error} `[CLIPipeProvider] requires command` — when options.command is missing.
|
|
115
|
+
* @when you want to drive a CLI (e.g. the claude CLI) as the Loop's provider — native MCP tools or a JSON emulation envelope
|
|
116
|
+
* @fails throws on a missing command; in native mode it owns its own cycle (ownsCycle), so per-round Loop seams are refused at construction rather than left silently dead.
|
|
117
|
+
* @example
|
|
118
|
+
* const provider = new CLIPipeProvider({ command: 'claude' });
|
|
115
119
|
*/
|
|
116
120
|
constructor(options = {}) {
|
|
117
121
|
if (!options.command) throw new Error('[CLIPipeProvider] requires command');
|
|
@@ -27,6 +27,10 @@ export class FallbackProvider {
|
|
|
27
27
|
* @param {Provider[]} providers - Ordered list of providers with generate().
|
|
28
28
|
* @param {FallbackOptions} [options={}]
|
|
29
29
|
* @throws {Error} `[FallbackProvider] requires at least one provider` — when providers is empty.
|
|
30
|
+
* @when you want to try several providers in order, failing over to the next when one errors — resilience across tiers or vendors
|
|
31
|
+
* @fails throws if given no providers; returns the first provider's success, else propagates the last provider's error.
|
|
32
|
+
* @example
|
|
33
|
+
* const provider = new FallbackProvider([primary, backup]);
|
|
30
34
|
*/
|
|
31
35
|
constructor(providers: Provider[], options?: FallbackOptions);
|
|
32
36
|
providers: import("../types").Provider[];
|
package/src/provider-fallback.js
CHANGED
|
@@ -17,6 +17,10 @@ class FallbackProvider {
|
|
|
17
17
|
* @param {Provider[]} providers - Ordered list of providers with generate().
|
|
18
18
|
* @param {FallbackOptions} [options={}]
|
|
19
19
|
* @throws {Error} `[FallbackProvider] requires at least one provider` — when providers is empty.
|
|
20
|
+
* @when you want to try several providers in order, failing over to the next when one errors — resilience across tiers or vendors
|
|
21
|
+
* @fails throws if given no providers; returns the first provider's success, else propagates the last provider's error.
|
|
22
|
+
* @example
|
|
23
|
+
* const provider = new FallbackProvider([primary, backup]);
|
|
20
24
|
*/
|
|
21
25
|
constructor(providers, options = {}) {
|
|
22
26
|
if (!Array.isArray(providers) || providers.length === 0) {
|
package/src/provider-gemini.d.ts
CHANGED
|
@@ -45,7 +45,13 @@ export type GeminiOptions = {
|
|
|
45
45
|
* cache-read tier populates with no opt-in.
|
|
46
46
|
*/
|
|
47
47
|
export class GeminiProvider {
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* @param {GeminiOptions} [options]
|
|
50
|
+
* @when you want Gemini models as the Loop's provider — native generateContent with implicit prompt caching
|
|
51
|
+
* @fails throws on a missing apiKey; normalizes stopReason (promoting a complete tool call) and usage; a socket idle/deadline/transport cut rejects with a retryable error.
|
|
52
|
+
* @example
|
|
53
|
+
* const provider = new GeminiProvider({ apiKey, model: 'gemini-2.5-pro' });
|
|
54
|
+
*/
|
|
49
55
|
constructor(options?: GeminiOptions);
|
|
50
56
|
apiKey: string | undefined;
|
|
51
57
|
model: string;
|
package/src/provider-gemini.js
CHANGED
|
@@ -40,7 +40,13 @@ function isLoopbackHost(hostname) {
|
|
|
40
40
|
* cache-read tier populates with no opt-in.
|
|
41
41
|
*/
|
|
42
42
|
class GeminiProvider {
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* @param {GeminiOptions} [options]
|
|
45
|
+
* @when you want Gemini models as the Loop's provider — native generateContent with implicit prompt caching
|
|
46
|
+
* @fails throws on a missing apiKey; normalizes stopReason (promoting a complete tool call) and usage; a socket idle/deadline/transport cut rejects with a retryable error.
|
|
47
|
+
* @example
|
|
48
|
+
* const provider = new GeminiProvider({ apiKey, model: 'gemini-2.5-pro' });
|
|
49
|
+
*/
|
|
44
50
|
constructor(options = {}) {
|
|
45
51
|
this.apiKey = options.apiKey?.trim();
|
|
46
52
|
this.model = options.model || 'gemini-2.5-flash';
|
package/src/provider-ollama.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export type OllamaOptions = {
|
|
|
28
28
|
export class OllamaProvider {
|
|
29
29
|
/**
|
|
30
30
|
* @param {OllamaOptions} [options]
|
|
31
|
+
* @when you want local models via Ollama as the Loop's provider — no API key, self-hosted
|
|
32
|
+
* @fails normalizes stopReason (promoting a complete tool call) and usage; a malformed tool-call JSON returns no usable calls with usage metered; a socket idle/deadline cut rejects with a retryable error.
|
|
33
|
+
* @example
|
|
34
|
+
* const provider = new OllamaProvider({ model: 'llama3' });
|
|
31
35
|
*/
|
|
32
36
|
constructor(options?: OllamaOptions);
|
|
33
37
|
model: string;
|