@blockrun/franklin 3.15.70 → 3.15.71

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
  }
@@ -401,6 +408,45 @@ async function execute(input, ctx) {
401
408
  lines.push('', `_$0.005 paid via x402._`);
402
409
  return { output: lines.join('\n') };
403
410
  }
411
+ case 'smartMoney': {
412
+ if (!conditionId) {
413
+ return {
414
+ output: 'Error: conditionId is required for smartMoney (Polymarket condition_id from a prior searchPolymarket or smartActivity call)',
415
+ isError: true,
416
+ };
417
+ }
418
+ // Per-market drill-down. Official live registry:
419
+ // /api/v1/pm/polymarket/market/:condition_id/smart-money
420
+ const path = `/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
421
+ const data = await getWithPayment(path, {}, ctx);
422
+ const buyers = (data.buyers ?? []).slice(0, 5);
423
+ const sellers = (data.sellers ?? []).slice(0, 5);
424
+ const lines = [
425
+ `## Smart money — \`${conditionId.slice(0, 14)}…\``,
426
+ ];
427
+ if (data.net_yes_size != null || data.net_no_size != null) {
428
+ lines.push(`**Net flow:** YES ${formatUsd(data.net_yes_size)} / NO ${formatUsd(data.net_no_size)}`);
429
+ }
430
+ if (buyers.length > 0) {
431
+ lines.push('', '**Top buyers**');
432
+ buyers.forEach((b, i) => {
433
+ const w = b.wallet ? `${b.wallet.slice(0, 8)}…${b.wallet.slice(-4)}` : 'unknown';
434
+ lines.push(`${i + 1}. ${w} — ${formatUsd(b.size)} on ${b.outcome ?? 'unknown side'}`);
435
+ });
436
+ }
437
+ if (sellers.length > 0) {
438
+ lines.push('', '**Top sellers**');
439
+ sellers.forEach((s, i) => {
440
+ const w = s.wallet ? `${s.wallet.slice(0, 8)}…${s.wallet.slice(-4)}` : 'unknown';
441
+ lines.push(`${i + 1}. ${w} — ${formatUsd(s.size)} on ${s.outcome ?? 'unknown side'}`);
442
+ });
443
+ }
444
+ if (buyers.length === 0 && sellers.length === 0) {
445
+ lines.push('No smart-money flow recorded for this market yet.');
446
+ }
447
+ lines.push('', `_$0.005 paid via x402._`);
448
+ return { output: lines.join('\n') };
449
+ }
404
450
  case 'searchPolymarket': {
405
451
  const raw = await getWithPayment('/v1/pm/polymarket/markets', {
406
452
  search,
@@ -486,7 +532,7 @@ async function execute(input, ctx) {
486
532
  }
487
533
  default:
488
534
  return {
489
- output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, smartActivity`,
535
+ output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, smartActivity, smartMoney`,
490
536
  isError: true,
491
537
  };
492
538
  }
@@ -506,12 +552,14 @@ export const predictionMarketCapability = {
506
552
  '`crossPlatform` (matched market pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
507
553
  '`leaderboard` (global top wallets by P&L on Polymarket — $0.001), ' +
508
554
  '`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). ' +
555
+ '`smartActivity` (markets where high-P&L wallets are positioning right now — $0.005), ' +
556
+ '`smartMoney` (smart-money positioning on one Polymarket condition_id — $0.005). ' +
510
557
  'Default routing: ' +
511
558
  '"is there a market on X anywhere" → searchAll. ' +
512
559
  '"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
513
560
  '"how is wallet 0xabc doing / show me their P&L" → walletProfile with that address. ' +
514
561
  '"what are smart traders betting on right now" → smartActivity. ' +
562
+ '"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
515
563
  '"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
516
564
  input_schema: {
517
565
  type: 'object',
@@ -526,12 +574,13 @@ export const predictionMarketCapability = {
526
574
  'leaderboard',
527
575
  'walletProfile',
528
576
  'smartActivity',
577
+ 'smartMoney',
529
578
  ],
530
579
  description: 'Which prediction-market query to run. See tool description for cost per action.',
531
580
  },
532
581
  search: {
533
582
  type: 'string',
534
- description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile.',
583
+ description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile/smartMoney.',
535
584
  },
536
585
  status: {
537
586
  type: 'string',
@@ -549,6 +598,10 @@ export const predictionMarketCapability = {
549
598
  type: 'string',
550
599
  description: 'For walletProfile: a single Polymarket wallet address, or a comma-separated list of addresses for batch lookup.',
551
600
  },
601
+ conditionId: {
602
+ type: 'string',
603
+ description: 'For smartMoney: Polymarket condition_id from searchPolymarket or smartActivity.',
604
+ },
552
605
  },
553
606
  required: ['action'],
554
607
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.70",
3
+ "version": "3.15.71",
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": {