@blockrun/franklin 3.15.87 → 3.15.89

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.
@@ -1,24 +1,26 @@
1
1
  /**
2
- * Reader for `~/.blockrun/cost_log.jsonl` — the SDK-owned ledger of every
3
- * settled x402 payment.
2
+ * Reader (and limited writer) for `~/.blockrun/cost_log.jsonl` — the
3
+ * append-only ledger of every settled x402 payment.
4
4
  *
5
- * Franklin's own `franklin-stats.json` and `franklin-audit.jsonl` only
6
- * capture calls that pass through specific code paths (the main agent
7
- * loop and the proxy). Helper LLM calls (analyzeTurn, prefetchForIntent,
8
- * compaction, evaluator, verification, MoA, subagent, learning extraction,
9
- * etc.) all settle x402 payments through the SDK those payments DO get
10
- * recorded in cost_log.jsonl by `@blockrun/llm` itself, but Franklin's
11
- * stats infra had been ignoring this file entirely.
5
+ * History: this file was originally SDK-only territory. `@blockrun/llm`'s
6
+ * internal `appendCostLog` writes one line per micropayment when callers
7
+ * use SDK helper methods (modal sandbox, prediction market, exa, etc.).
8
+ * But Franklin's main LLM stream both the in-process agent loop
9
+ * (`src/agent/llm.ts`) and the proxy server (`src/proxy/server.ts`)
10
+ * have **their own** x402 signers that bypass the SDK entirely. Verified
11
+ * 2026-05-09 on a real machine: a single paid agent turn dropped the
12
+ * wallet by $0.001 and updated `franklin-stats.json` correctly, but
13
+ * cost_log.jsonl gained zero entries. So cost_log was never the
14
+ * "wallet truth" it advertised — it was an SDK-subset.
12
15
  *
13
- * Verified 2026-05-06 against a real machine: cost_log.jsonl is written
14
- * by the SDK with snake_case keys (`cost_usd`, `ts` in unix seconds with
15
- * subsecond precision Python convention) and Franklin's reads/writes
16
- * use camelCase + ms. This module bridges the format gap so stats /
17
- * insights / `franklin balance` can surface the wallet-truth total
18
- * alongside the recorded total.
16
+ * Fix (2026-05-09): expose `appendSettlementRow` so the agent and proxy
17
+ * signers can write the same shape the SDK does. The format contract
18
+ * (snake_case `cost_usd`, `ts` in unix seconds with subsecond precision,
19
+ * one JSON object per line) is preserved exactly so both writers
20
+ * interleave cleanly. Order in the file follows wall-clock arrival.
19
21
  *
20
- * Responsibility: read-only. We never write or trim cost_log.jsonl —
21
- * the SDK owns it.
22
+ * Responsibility: read + append-only write. We never trim or rotate
23
+ * cost_log.jsonl — that contract still belongs to the SDK / hygiene.
22
24
  */
23
25
  import fs from 'node:fs';
24
26
  import path from 'node:path';
@@ -79,6 +81,54 @@ export function loadSdkSettlements(opts) {
79
81
  }
80
82
  return rows;
81
83
  }
84
+ /**
85
+ * Append one settlement row to ~/.blockrun/cost_log.jsonl in the same
86
+ * shape `@blockrun/llm`'s internal `appendCostLog` writes. Best-effort:
87
+ * silently swallows fs errors so a logging failure never breaks the
88
+ * paid call that just succeeded. Costs <= 0 are treated as no-op (no
89
+ * point logging $0 — the file's purpose is "what was actually paid").
90
+ *
91
+ * Honors FRANKLIN_NO_AUDIT=1 the same way `appendAudit` and `recordUsage`
92
+ * do, so test runs (test/e2e.mjs sets this) don't pollute the user's
93
+ * real cost_log. Verified 2026-05-10 on a real machine: two
94
+ * `/v1/messages: $0.000001` rows leaked into the user's cost_log from
95
+ * a paid e2e run because this gate was missing — paid e2e was hitting
96
+ * the real gateway with a real wallet, but the test framework expected
97
+ * NO writes to land. Restoring the gate keeps cost_log a clean ledger
98
+ * of REAL traffic.
99
+ */
100
+ export function appendSettlementRow(endpoint, costUsd, meta) {
101
+ if (process.env.FRANKLIN_NO_AUDIT === '1' || process.env.FRANKLIN_NO_PERSIST === '1')
102
+ return;
103
+ if (!Number.isFinite(costUsd) || costUsd <= 0)
104
+ return;
105
+ if (typeof endpoint !== 'string' || endpoint.length === 0)
106
+ return;
107
+ try {
108
+ fs.mkdirSync(path.dirname(getCostLogPath()), { recursive: true });
109
+ }
110
+ catch { /* best-effort */ }
111
+ // Match SDK conventions exactly: snake_case keys, ts in unix seconds
112
+ // with subsecond precision (Python convention — divide ms epoch by 1e3
113
+ // so the SDK reader and our reader agree on the timestamp).
114
+ const entry = {
115
+ ts: Date.now() / 1e3,
116
+ endpoint,
117
+ cost_usd: costUsd,
118
+ };
119
+ if (meta?.model)
120
+ entry.model = meta.model;
121
+ if (meta?.wallet)
122
+ entry.wallet = meta.wallet;
123
+ if (meta?.network)
124
+ entry.network = meta.network;
125
+ if (meta?.client_kind)
126
+ entry.client_kind = meta.client_kind;
127
+ try {
128
+ fs.appendFileSync(getCostLogPath(), JSON.stringify(entry) + '\n');
129
+ }
130
+ catch { /* best-effort */ }
131
+ }
82
132
  /** Aggregate the SDK ledger into a single summary object. */
83
133
  export function summarizeSdkSettlements(opts) {
84
134
  const rows = loadSdkSettlements(opts);
@@ -767,7 +767,7 @@ export const predictionMarketCapability = {
767
767
  'Default routing: ' +
768
768
  '"is there a market on X anywhere" → searchAll. ' +
769
769
  '"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
770
- '"analyze this wallet / can I copy this trader / 复制交易 / show me their P&L AND positions" → run walletProfile + walletPnl + walletPositions IN PARALLEL with the same address — three $0.005 calls give the full picture for $0.015. Do not Bash-curl Polymarket directly; the agent has paid tools for this. ' +
770
+ '"analyze this wallet / can I copy this trader / copy trade / show me their P&L AND positions" → run walletProfile + walletPnl + walletPositions IN PARALLEL with the same address — three $0.005 calls give the full picture for $0.015. Do not Bash-curl Polymarket directly; the agent has paid tools for this. ' +
771
771
  '"what are smart traders betting on right now" → smartActivity. ' +
772
772
  '"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
773
773
  '"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
@@ -825,7 +825,7 @@ export const predictionMarketCapability = {
825
825
  'Default routing: ' +
826
826
  '"is there a market on X anywhere" → searchAll. ' +
827
827
  '"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
828
- '"analyze this wallet / can I copy this trader / 复制交易 / show me their P&L AND positions" → run walletProfile + walletPnl + walletPositions IN PARALLEL with the same address — three $0.005 calls give the full picture for $0.015. Do not Bash-curl Polymarket directly; the agent has paid tools for this. ' +
828
+ '"analyze this wallet / can I copy this trader / show me their P&L AND positions" → run walletProfile + walletPnl + walletPositions IN PARALLEL with the same address — three $0.005 calls give the full picture for $0.015. Do not Bash-curl Polymarket directly; the agent has paid tools for this. ' +
829
829
  '"what are smart traders betting on right now" → smartActivity. ' +
830
830
  '"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
831
831
  '"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
@@ -13,14 +13,14 @@ import { detectProduct } from '../social/ai.js';
13
13
  import { loadConfig, isConfigReady } from '../social/config.js';
14
14
  import { browserPool } from '../social/browser-pool.js';
15
15
  // ─── Intent detection (code-level, not LLM-level) ──────────────────────────
16
- // When the user asks "check my @handle mentions/notifications/互动",
17
- // the tool itself routes to x.com/notifications. No LLM judgment needed.
16
+ // When the user asks "check my @handle mentions/notifications", the tool
17
+ // itself routes to x.com/notifications. English-only keyword fast-path;
18
+ // the LLM-level classifier handles non-English queries before this point.
18
19
  const NOTIFICATION_KEYWORDS = [
19
20
  'notification', 'notifications',
20
21
  'mention', 'mentions', 'mentioned',
21
22
  'reply', 'replies',
22
23
  'interact', 'interaction', 'interactions',
23
- '互动', '通知', '提及', '回复', '看看',
24
24
  'check my', 'my account', 'my x',
25
25
  'to:', 'from:', '@',
26
26
  ];
@@ -50,7 +50,7 @@ export const walletCapability = {
50
50
  spec: {
51
51
  name: 'Wallet',
52
52
  description: 'Read Franklin\'s wallet status — chain, address, and USDC balance. ' +
53
- 'Use this for any "what\'s my balance / how much money / 钱包余额 / wallet status" question. ' +
53
+ 'Use this for any "what\'s my balance / how much money / wallet status" question. ' +
54
54
  'Cheaper and more direct than running `franklin balance` via Bash, and never costs USDC.',
55
55
  input_schema: {
56
56
  type: 'object',
package/dist/ui/app.js CHANGED
@@ -309,7 +309,7 @@ function InputBox({ input, setInput, onSubmit, model, balance, chain, walletTail
309
309
  const m = balance.match(/\$([\d.]+)/);
310
310
  const num = m ? parseFloat(m[1]) : null;
311
311
  if (num !== null && num < 0.50) {
312
- return _jsxs(_Fragment, { children: [_jsx(Text, { color: "red", bold: true, children: balance }), _jsx(Text, { color: "red", children: " \u26A0 low \u2014 fund wallet or /model free" })] });
312
+ return _jsxs(_Fragment, { children: [_jsx(Text, { color: "red", bold: true, children: balance }), _jsx(Text, { color: "red", children: " \u26A0 low \u2014 deposit at http://localhost:3100/#wallet or /model free" })] });
313
313
  }
314
314
  if (num !== null && num < 1.00) {
315
315
  return _jsx(Text, { color: "yellow", children: balance });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.87",
3
+ "version": "3.15.89",
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": {