@blockrun/franklin 3.15.70 → 3.15.72

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.
@@ -336,13 +336,14 @@ Your training data is frozen in the past. Live-world questions MUST be answered
336
336
 
337
337
  If you find yourself about to emit one of these, stop and call the tool instead. If you don't know which ticker the user means, call ExaSearch or AskUser — never deflect.
338
338
 
339
- **Prediction markets (PredictionMarket).** When the user asks about real-world odds — elections, "will X happen by year-end", "Polymarket on Y", "Kalshi market for Z", "what are the odds of recession" — use **PredictionMarket** instead of guessing. Seven actions, route by intent:
339
+ **Prediction markets (PredictionMarket).** When the user asks about real-world odds — elections, "will X happen by year-end", "Polymarket on Y", "Kalshi market for Z", "what are the odds of recession" — use **PredictionMarket** instead of guessing. Eight actions, route by intent:
340
340
  - "is there a market on X anywhere?" / unknown which platform → \`searchAll\` (\$0.005) — single call across Polymarket+Kalshi+Limitless+Opinion+Predict.Fun.
341
341
  - "what are the odds on Polymarket / Kalshi specifically" → \`searchPolymarket\` (\$0.001) and \`searchKalshi\` (\$0.001) **in parallel**; comparing implied probability across the two venues is the high-value answer.
342
342
  - "where do Polymarket and Kalshi disagree / arbitrage" → \`crossPlatform\` (\$0.005) returns pre-matched pairs.
343
343
  - "who's profitable / top traders / who should I follow on Polymarket" → \`leaderboard\` (\$0.001) — global top wallets by P&L.
344
344
  - "how is wallet 0xabc doing / show this trader's P&L / are they profitable" → \`walletProfile\` (\$0.005) with \`wallets="<address>"\` (comma-separated for batch).
345
- - "what are smart traders betting on right now / smart money flow" → \`smartActivity\` (\$0.005) — markets where high-P&L wallets are positioning.
345
+ - "what are smart traders betting on right now / smart money flow across markets" → \`smartActivity\` (\$0.005) — markets where high-P&L wallets are positioning.
346
+ - "show smart money on this specific Polymarket market / this condition_id" → \`smartMoney\` (\$0.005) with \`conditionId="<condition_id>"\`.
346
347
 
347
348
  NEVER answer "what are the odds of X" from training-data memory — these are live markets that move every minute. NEVER claim "no market on this" without running \`searchAll\` (or at least \`searchPolymarket\`) first. If a search returns zero, say so with the query you tried and offer to broaden.
348
349
 
@@ -939,10 +939,20 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
939
939
  // never approached its 1M-token compaction threshold. Compact here
940
940
  // when the turn has accumulated lots of tool calls AND real spend,
941
941
  // even though the context window isn't close to full.
942
+ //
943
+ // Thresholds tightened in 3.15.71. Original 3.15.69 used
944
+ // (>30 calls AND >$0.05) — verified too loose against a real
945
+ // franklin-shorts edit session: 16 deepseek-v4-pro calls for
946
+ // $0.055 ended naturally before the trigger fired, even though
947
+ // by call #4 the per-call input was already 13K tokens (worth
948
+ // compacting). Lowering to (>15 AND >$0.03) catches sessions
949
+ // where input-replay tax has clearly started biting; the
950
+ // fire-once-per-turn flag still bounds the worst case at one
951
+ // extra summary call (~$0.005).
942
952
  if (!bloatCompactedThisTurn &&
943
953
  compactFailures < 3 &&
944
- turnToolCalls > 30 &&
945
- turnCostUsd > 0.05) {
954
+ turnToolCalls > 15 &&
955
+ turnCostUsd > 0.03) {
946
956
  try {
947
957
  const beforeTokens = estimateHistoryTokens(history);
948
958
  const { history: compacted, compacted: didCompact } = await forceCompact(history, config.model, client, config.debug);
@@ -114,7 +114,18 @@ export function extractLastUserPrompt(history) {
114
114
  const text = flattenContent(msg.content);
115
115
  if (!text)
116
116
  continue;
117
- return text.replace(/\s+/g, ' ').trim();
117
+ const cleaned = text.replace(/\s+/g, ' ').trim();
118
+ // Anthropic's message format puts harness-injected context, guardrail
119
+ // warnings, and grounding-retry feedback under role:"user" too. Walking
120
+ // back blindly returns those synthetic strings instead of the real user
121
+ // intent — verified 2026-05-06 in the audit log: 403 entries showed
122
+ // "[FRANKLIN HARNESS PREFETCH] CRCL price..." and 18 showed
123
+ // "[GROUNDING CHECK FAILED] ..." instead of the user's actual question.
124
+ // Skip any message whose first non-whitespace block is a SCREAMING-CASE
125
+ // bracketed label and keep walking back to the real user turn.
126
+ if (/^\[[A-Z][A-Z _-]+\]/.test(cleaned))
127
+ continue;
128
+ return cleaned;
118
129
  }
119
130
  return undefined;
120
131
  }
@@ -21,14 +21,8 @@
21
21
  * wallets — P&L, positions, identity
22
22
  * smartActivity $0.005 discover markets where high-performing wallets
23
23
  * are active right now
24
- *
25
- * Replaces the old `smartMoney` action (3.15.69 and earlier) which hit a
26
- * non-existent path /v1/pm/polymarket/market/<id>/smart-money — that endpoint
27
- * was never on the gateway, so the action was a silent 404 from day one.
28
- * Verified 2026-05-05 against blockrun.ai/openapi.json: Polymarket has no
29
- * per-market path-parameter endpoints; smart-money intelligence lives at
30
- * /v1/pm/polymarket/markets/smart-activity (cross-market discovery) and
31
- * /v1/pm/polymarket/leaderboard (top wallets globally).
24
+ * smartMoney $0.005 smart-money positioning on one Polymarket
25
+ * condition_id (per-market drill-down)
32
26
  *
33
27
  * Output is filtered + truncated on the way back so a single call never
34
28
  * dumps 100 markets into the agent's context. Default 20 rows; agents that
@@ -21,14 +21,8 @@
21
21
  * wallets — P&L, positions, identity
22
22
  * smartActivity $0.005 discover markets where high-performing wallets
23
23
  * are active right now
24
- *
25
- * Replaces the old `smartMoney` action (3.15.69 and earlier) which hit a
26
- * non-existent path /v1/pm/polymarket/market/<id>/smart-money — that endpoint
27
- * was never on the gateway, so the action was a silent 404 from day one.
28
- * Verified 2026-05-05 against blockrun.ai/openapi.json: Polymarket has no
29
- * per-market path-parameter endpoints; smart-money intelligence lives at
30
- * /v1/pm/polymarket/markets/smart-activity (cross-market discovery) and
31
- * /v1/pm/polymarket/leaderboard (top wallets globally).
24
+ * smartMoney $0.005 smart-money positioning on one Polymarket
25
+ * condition_id (per-market drill-down)
32
26
  *
33
27
  * Output is filtered + truncated on the way back so a single call never
34
28
  * dumps 100 markets into the agent's context. Default 20 rows; agents that
@@ -51,6 +45,7 @@ const PATH_PRICES = [
51
45
  { pattern: /\/v1\/pm\/matching-markets/, usd: 0.005 },
52
46
  { pattern: /\/v1\/pm\/polymarket\/wallets\//, usd: 0.005 },
53
47
  { pattern: /\/v1\/pm\/polymarket\/wallet\//, usd: 0.005 },
48
+ { pattern: /\/v1\/pm\/polymarket\/market\/[^/]+\/smart-money$/, usd: 0.005 },
54
49
  { pattern: /\/v1\/pm\/polymarket\/markets\/smart-activity$/, usd: 0.005 },
55
50
  { pattern: /\/v1\/pm\/.+/, usd: 0.001 },
56
51
  ];
@@ -169,21 +164,33 @@ async function extractPaymentReq(response) {
169
164
  return header;
170
165
  }
171
166
  // ─── Formatting helpers ────────────────────────────────────────────────────
167
+ function asNumber(value) {
168
+ if (typeof value === 'number' && Number.isFinite(value))
169
+ return value;
170
+ if (typeof value === 'string' && value.trim() !== '') {
171
+ const n = Number(value);
172
+ if (Number.isFinite(n))
173
+ return n;
174
+ }
175
+ return null;
176
+ }
172
177
  function formatUsd(value) {
173
- if (value == null || !Number.isFinite(value))
178
+ const n = asNumber(value);
179
+ if (n == null)
174
180
  return 'n/a';
175
- if (value >= 1e9)
176
- return `$${(value / 1e9).toFixed(2)}B`;
177
- if (value >= 1e6)
178
- return `$${(value / 1e6).toFixed(2)}M`;
179
- if (value >= 1e3)
180
- return `$${(value / 1e3).toFixed(1)}K`;
181
- return `$${value.toFixed(2)}`;
181
+ if (n >= 1e9)
182
+ return `$${(n / 1e9).toFixed(2)}B`;
183
+ if (n >= 1e6)
184
+ return `$${(n / 1e6).toFixed(2)}M`;
185
+ if (n >= 1e3)
186
+ return `$${(n / 1e3).toFixed(1)}K`;
187
+ return `$${n.toFixed(2)}`;
182
188
  }
183
189
  function formatPct(value, digits = 1) {
184
- if (value == null || !Number.isFinite(value))
190
+ const n = asNumber(value);
191
+ if (n == null)
185
192
  return 'n/a';
186
- return `${(value * 100).toFixed(digits)}%`;
193
+ return `${(n * 100).toFixed(digits)}%`;
187
194
  }
188
195
  // API responses sometimes come wrapped as `{data: [...], pagination: ...}`,
189
196
  // other times as a bare array. Normalise to an array.
@@ -204,11 +211,11 @@ function unwrapList(raw) {
204
211
  return [];
205
212
  }
206
213
  async function execute(input, ctx) {
207
- const { action, search, status, sort, limit, wallets } = input;
214
+ const { action, search, status, sort, limit, conditionId, wallets } = input;
208
215
  const cappedLimit = Math.min(Math.max(1, limit ?? DEFAULT_LIMIT), MAX_LIMIT);
209
216
  if (!action) {
210
217
  return {
211
- output: 'Error: action is required (searchAll | searchPolymarket | searchKalshi | crossPlatform | leaderboard | walletProfile | smartActivity)',
218
+ output: 'Error: action is required (searchAll | searchPolymarket | searchKalshi | crossPlatform | leaderboard | walletProfile | smartActivity | smartMoney)',
212
219
  isError: true,
213
220
  };
214
221
  }
@@ -325,10 +332,15 @@ async function execute(input, ctx) {
325
332
  isError: true,
326
333
  };
327
334
  }
328
- // Predexon's batch endpoint accepts multiple wallets; we forward
329
- // verbatim. Single wallet works too caller passes one address.
335
+ // Predexon's batch endpoint expects the query param `addresses`,
336
+ // NOT `wallets` verified 2026-05-06 from a live 422 in a real
337
+ // user session: `{"detail":[{"type":"missing","loc":["query",
338
+ // "addresses"]}]}`. The 3.15.70 ship guessed the param name from
339
+ // the openapi description ("Batch retrieve wallet profiles") and
340
+ // got it wrong. Public field stays `wallets` for ergonomics —
341
+ // we just rename on the wire.
330
342
  const raw = await getWithPayment('/v1/pm/polymarket/wallets/profiles', {
331
- wallets: wallets.trim(),
343
+ addresses: wallets.trim(),
332
344
  }, ctx);
333
345
  const profiles = unwrapList(raw);
334
346
  if (profiles.length === 0) {
@@ -401,6 +413,45 @@ async function execute(input, ctx) {
401
413
  lines.push('', `_$0.005 paid via x402._`);
402
414
  return { output: lines.join('\n') };
403
415
  }
416
+ case 'smartMoney': {
417
+ if (!conditionId) {
418
+ return {
419
+ output: 'Error: conditionId is required for smartMoney (Polymarket condition_id from a prior searchPolymarket or smartActivity call)',
420
+ isError: true,
421
+ };
422
+ }
423
+ // Per-market drill-down. Official live registry:
424
+ // /api/v1/pm/polymarket/market/:condition_id/smart-money
425
+ const path = `/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
426
+ const data = await getWithPayment(path, {}, ctx);
427
+ const buyers = (data.buyers ?? []).slice(0, 5);
428
+ const sellers = (data.sellers ?? []).slice(0, 5);
429
+ const lines = [
430
+ `## Smart money — \`${conditionId.slice(0, 14)}…\``,
431
+ ];
432
+ if (data.net_yes_size != null || data.net_no_size != null) {
433
+ lines.push(`**Net flow:** YES ${formatUsd(data.net_yes_size)} / NO ${formatUsd(data.net_no_size)}`);
434
+ }
435
+ if (buyers.length > 0) {
436
+ lines.push('', '**Top buyers**');
437
+ buyers.forEach((b, i) => {
438
+ const w = b.wallet ? `${b.wallet.slice(0, 8)}…${b.wallet.slice(-4)}` : 'unknown';
439
+ lines.push(`${i + 1}. ${w} — ${formatUsd(b.size)} on ${b.outcome ?? 'unknown side'}`);
440
+ });
441
+ }
442
+ if (sellers.length > 0) {
443
+ lines.push('', '**Top sellers**');
444
+ sellers.forEach((s, i) => {
445
+ const w = s.wallet ? `${s.wallet.slice(0, 8)}…${s.wallet.slice(-4)}` : 'unknown';
446
+ lines.push(`${i + 1}. ${w} — ${formatUsd(s.size)} on ${s.outcome ?? 'unknown side'}`);
447
+ });
448
+ }
449
+ if (buyers.length === 0 && sellers.length === 0) {
450
+ lines.push('No smart-money flow recorded for this market yet.');
451
+ }
452
+ lines.push('', `_$0.005 paid via x402._`);
453
+ return { output: lines.join('\n') };
454
+ }
404
455
  case 'searchPolymarket': {
405
456
  const raw = await getWithPayment('/v1/pm/polymarket/markets', {
406
457
  search,
@@ -486,7 +537,7 @@ async function execute(input, ctx) {
486
537
  }
487
538
  default:
488
539
  return {
489
- output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, smartActivity`,
540
+ output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, smartActivity, smartMoney`,
490
541
  isError: true,
491
542
  };
492
543
  }
@@ -506,12 +557,14 @@ export const predictionMarketCapability = {
506
557
  '`crossPlatform` (matched market pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
507
558
  '`leaderboard` (global top wallets by P&L on Polymarket — $0.001), ' +
508
559
  '`walletProfile` (P&L + positions for one or more Polymarket wallets — $0.005), ' +
509
- '`smartActivity` (markets where high-P&L wallets are positioning right now — $0.005). ' +
560
+ '`smartActivity` (markets where high-P&L wallets are positioning right now — $0.005), ' +
561
+ '`smartMoney` (smart-money positioning on one Polymarket condition_id — $0.005). ' +
510
562
  'Default routing: ' +
511
563
  '"is there a market on X anywhere" → searchAll. ' +
512
564
  '"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
513
565
  '"how is wallet 0xabc doing / show me their P&L" → walletProfile with that address. ' +
514
566
  '"what are smart traders betting on right now" → smartActivity. ' +
567
+ '"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
515
568
  '"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
516
569
  input_schema: {
517
570
  type: 'object',
@@ -526,12 +579,13 @@ export const predictionMarketCapability = {
526
579
  'leaderboard',
527
580
  'walletProfile',
528
581
  'smartActivity',
582
+ 'smartMoney',
529
583
  ],
530
584
  description: 'Which prediction-market query to run. See tool description for cost per action.',
531
585
  },
532
586
  search: {
533
587
  type: 'string',
534
- description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile.',
588
+ description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile/smartMoney.',
535
589
  },
536
590
  status: {
537
591
  type: 'string',
@@ -549,6 +603,10 @@ export const predictionMarketCapability = {
549
603
  type: 'string',
550
604
  description: 'For walletProfile: a single Polymarket wallet address, or a comma-separated list of addresses for batch lookup.',
551
605
  },
606
+ conditionId: {
607
+ type: 'string',
608
+ description: 'For smartMoney: Polymarket condition_id from searchPolymarket or smartActivity.',
609
+ },
552
610
  },
553
611
  required: ['action'],
554
612
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.70",
3
+ "version": "3.15.72",
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": {