@blockrun/franklin 3.15.55 → 3.15.56

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.
@@ -6,10 +6,10 @@
6
6
  * - Auth errors (401) get special handling (token refresh, not retry)
7
7
  * - EPIPE/connection reset handled as network errors (retryable)
8
8
  */
9
- export type AgentErrorCategory = 'rate_limit' | 'payment' | 'network' | 'timeout' | 'context_limit' | 'overloaded' | 'server' | 'auth' | 'schema' | 'unknown';
9
+ export type AgentErrorCategory = 'rate_limit' | 'payment' | 'payment_rejected' | 'network' | 'timeout' | 'context_limit' | 'overloaded' | 'server' | 'auth' | 'schema' | 'unknown';
10
10
  export interface AgentErrorInfo {
11
11
  category: AgentErrorCategory;
12
- label: 'RateLimit' | 'Payment' | 'Network' | 'Timeout' | 'Context' | 'Overloaded' | 'Server' | 'Auth' | 'Schema' | 'Unknown';
12
+ label: 'RateLimit' | 'Payment' | 'PaymentRejected' | 'Network' | 'Timeout' | 'Context' | 'Overloaded' | 'Server' | 'Auth' | 'Schema' | 'Unknown';
13
13
  isTransient: boolean;
14
14
  /** Max retries for this error type (overrides default). undefined = use default. */
15
15
  maxRetries?: number;
@@ -11,10 +11,31 @@ function includesAny(text, patterns) {
11
11
  }
12
12
  export function classifyAgentError(message) {
13
13
  const err = message.toLowerCase();
14
+ // payment_rejected — the gateway received a SIGNED payment header and
15
+ // rejected it during verification (signature mismatch, replay-nonce
16
+ // reuse, clock skew, wrong-chain wallet). Different remedy from
17
+ // payment_required: re-presenting the same signature won't help.
18
+ // Verified 2026-05-04 in a live session: ExaSearch returned
19
+ // `Exa /v1/exa/search failed (402): {"error":"Payment verification failed",...}`.
20
+ // Classify BEFORE the generic 'payment' branch below since the body
21
+ // contains both 'payment' and 'verification failed'.
22
+ if (includesAny(err, [
23
+ 'verification failed',
24
+ 'payment verification',
25
+ 'signature mismatch',
26
+ 'invalid payment signature',
27
+ 'invalid x-payment',
28
+ 'nonce reuse',
29
+ 'replay protection',
30
+ ])) {
31
+ return {
32
+ category: 'payment_rejected', label: 'PaymentRejected', isTransient: false, maxRetries: 0,
33
+ suggestion: 'The gateway rejected your signed payment. Run `franklin balance` to confirm funds + chain. Common causes: clock skew (resync system clock), wrong chain selected (use `/chain` to switch), or stale nonce (the same retry will fail). Switch to a free model with `/model free` to keep working.',
34
+ };
35
+ }
14
36
  if (includesAny(err, [
15
37
  'insufficient',
16
38
  'payment',
17
- 'verification failed',
18
39
  'balance',
19
40
  '402',
20
41
  'free tier',
@@ -1172,8 +1172,12 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
1172
1172
  }
1173
1173
  // ── Payment failure: auto-fallback to free models ──
1174
1174
  // Track payment-failed models for the entire session — unlike transient errors,
1175
- // 402s will keep failing until the user adds funds.
1176
- if (classified.category === 'payment') {
1175
+ // 402s will keep failing until the user adds funds. Also handles
1176
+ // payment_rejected (signature verified-and-rejected by gateway):
1177
+ // same fallback path, but the suggestion text in classifier guides
1178
+ // the user toward clock-skew / chain-mismatch fixes rather than
1179
+ // "add funds."
1180
+ if (classified.category === 'payment' || classified.category === 'payment_rejected') {
1177
1181
  turnFailedModels.add(config.model);
1178
1182
  paymentFailedModels.set(config.model, Date.now());
1179
1183
  // Bound the Map so long sessions don't leak. LRU-evict oldest by timestamp.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.55",
3
+ "version": "3.15.56",
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": {