@blockrun/franklin 3.15.0 → 3.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ export declare function normalizeSearchQuery(query: string): {
3
3
  normalized: string;
4
4
  tokens: string[];
5
5
  };
6
+ export declare function isToolClassFailure(name: string, result: CapabilityResult): boolean;
6
7
  export declare class SessionToolGuard {
7
8
  private turn;
8
9
  private webSearchesThisTurn;
@@ -128,6 +128,35 @@ function readKey(resolved, offset, limit) {
128
128
  function fetchKey(url, maxLength) {
129
129
  return `${url}::${maxLength ?? 12288}`;
130
130
  }
131
+ // Circuit-breaker classifier for the per-tool kill switch.
132
+ //
133
+ // `isError: true` covers everything from "tool itself broke" (network, parse,
134
+ // timeout) to "agent fed me a bad input" (404 on a guessed URL, malformed URL).
135
+ // Only the first category should count toward disabling the tool — otherwise
136
+ // three hallucinated URLs in one prompt permanently kill WebFetch for the
137
+ // session, even though the tool worked correctly each time.
138
+ export function isToolClassFailure(name, result) {
139
+ if (!result.isError)
140
+ return false;
141
+ const out = String(result.output ?? '');
142
+ if (name === 'WebFetch') {
143
+ // HTTP 4xx/5xx — the URL was real-but-wrong or the upstream had issues.
144
+ // Either way, the tool worked; the agent should pick a different URL.
145
+ if (/^HTTP \d{3}\b/.test(out))
146
+ return false;
147
+ // Bad URL syntax / unsupported protocol / missing arg — agent input error.
148
+ if (out.startsWith('Error: invalid URL'))
149
+ return false;
150
+ if (out.startsWith('Error: only http'))
151
+ return false;
152
+ if (out.startsWith('Error: url is required'))
153
+ return false;
154
+ // User interrupt — not a tool failure.
155
+ if (out.startsWith('Error: request aborted'))
156
+ return false;
157
+ }
158
+ return true;
159
+ }
131
160
  export class SessionToolGuard {
132
161
  turn = 0;
133
162
  webSearchesThisTurn = 0;
@@ -227,10 +256,15 @@ export class SessionToolGuard {
227
256
  return null;
228
257
  }
229
258
  afterExecute(invocation, result) {
230
- // Track per-tool error counts across the session
231
- if (result.isError) {
259
+ // Per-tool circuit breaker: count consecutive tool-class failures, reset on
260
+ // any success. Agent-input errors (e.g. WebFetch 404 on a guessed URL) are
261
+ // not tool failures and must not trip the breaker.
262
+ if (isToolClassFailure(invocation.name, result)) {
232
263
  this.toolErrorCounts.set(invocation.name, (this.toolErrorCounts.get(invocation.name) ?? 0) + 1);
233
264
  }
265
+ else if (!result.isError) {
266
+ this.toolErrorCounts.delete(invocation.name);
267
+ }
234
268
  switch (invocation.name) {
235
269
  case 'WebSearch':
236
270
  case 'SearchX':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.0",
3
+ "version": "3.15.1",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {