Releases: BlockRunAI/Franklin
v3.18.0 — Phone & Voice panel + CSRF defense
Phone & Voice in the panel
A new Phone tab in franklin panel for managing the wallet-bound numbers BlockRun provisions:
- Color-coded countdown chip per number (green → amber ≤7d → red ≤2d → expired)
- Sidebar nav badge lights up when any number is in the warning band, visible from every tab
- One-click renew ($5 USDC, charged from the panel's wallet)
- Buy is explicitly additive — "this adds a new number alongside any you already own" — no surprise replacements
- Browser notifications at T-7d / T-3d / T-1d / expired, deduped per session
- No auto-renew toggle by design — a wallet that runs dry would fail the renewal silently; manual click keeps the action in the user's hands
New panel server endpoints (loopback + same-origin only):
GET /api/phone/numbers # cached read (~/.blockrun/phone-numbers.json, 6h TTL)
POST /api/phone/numbers/refresh # force-refetch (\$0.001)
POST /api/phone/numbers/buy # provision (\$5)
POST /api/phone/numbers/renew # extend 30d (\$5)
POST /api/phone/numbers/release # release (free)
The cache file is shared with the upcoming terminal status bar so both surfaces show the same state.
CSRF defense for the panel server
Loopback binding stopped LAN exposure but didn't stop a malicious website in your browser from POSTing to localhost. Spendful and wallet-mutating routes now require either no Origin (curl, direct navigation) or the exact same local origin that served the panel page.
Hardened routes:
/api/wallet/secret/api/wallet/import/api/chain- All five
/api/phone/*endpoints
The wildcard Access-Control-Allow-Origin is also removed from the panel's JSON helper — it was defeating the same-origin defense for cross-origin requests.
v3.15.103 — cost-log dedupe uses Math.floor for second buckets
Follow-up to 3.15.102. The dedupe bucketed by Math.round(ts/1000), which splits same-second duplicates when ms values straddle the 0.5s boundary (e.g. ts=499 → bucket 0 vs ts=500 → bucket 1 — same physical second, routed to different buckets, dedupe misses).
Math.floor correctly maps every ms within second N to bucket N.
Tests: new regression pinning both edges (same-second straddling 0.5s mark collapses; adjacent seconds near boundary stay distinct). 391/391 pass.
Real-data total unchanged — correctness improvement only.
v3.15.102 — cost_log reader dedupes SDK double-writes + filters Anvil test wallets
Real-data bug found in a routine log review. The SDK was writing the same call up to 3× to cost_log.jsonl (LLMClient + AsyncLLMClient hitting appendCostLog on the same fetch), plus a $1 row had leaked under Anvil's first deterministic test wallet (publicly-known private key).
Verified impact (real user, 2026-05-13):
| Metric | Before | After | Wallet truth |
|---|---|---|---|
| 24h cost_log | $12.48 (52 rows) | $7.48 (47 rows) | $7.54 ✓ |
Stats now match the wallet drop within rounding noise ($0.06).
Fix: read-time guards in src/stats/cost-log.ts:loadSdkSettlements — dedupe by (round(ts to second), endpoint, model, cost-in-micro-USDC), drop rows signed by Anvil's 10 deterministic accounts. No SDK upgrade required.
Tests: 2 new fixtures. 390/390 pass.
Upstream SDK fixes (stop the double-write, stop signing with Anvil key) belong in @blockrun/llm. Read-time guards stay even after — historical rows are already polluted, and the same bug class would otherwise catch users by surprise if it recurred.
v3.15.101 — [a] always now persists to disk (no more re-prompting across franklin start restarts)
User-reported UX bug. The permission prompt's [a] always was a misnomer — it only added the tool to in-memory sessionAllowed which evaporated on every process exit. Users who hit [a] thinking they granted permanent permission got re-prompted on every franklin start restart.
Verified on a real machine (2026-05-12): ~/.blockrun/franklin-permissions.json did not exist at all, yet the user reported approving Bash / Write / Edit repeatedly across the day.
Fix: new persistAllowRule() method writes to disk on [a]. Both prompt paths (Ink UI + readline) updated. Ink hint now says [a] always (saved across sessions) so the contract is explicit.
Undo: edit ~/.blockrun/franklin-permissions.json and remove the tool from the allow array.
388/388 tests pass. Includes:
- New regression test pinning the disk write (idempotent, subprocess-sandboxed)
- Updated pre-existing bash-guard test to snapshot+restore the real config file so the in-process test doesn't pollute the developer's allow list
v3.15.100 — brand rebrand follow-through (CLI help, upgrade nudge, doctor label)
Cleanup pass on 3.15.99. Three user-facing CLI surfaces were missed in the marketing rebrand; this release brings them in line.
| Surface | Before | After |
|---|---|---|
franklin --help |
Franklin — The AI agent… |
Franklin Agent — The AI agent… |
| Upgrade nudge | ⟳ Franklin X.Y.Z available |
⟳ Franklin Agent X.Y.Z available |
franklin doctor label |
✓ Franklin v3.15.X |
✓ Franklin Agent v3.15.X |
Found by running the binary after 3.15.99 shipped and grepping for remaining "Franklin" strings. Tool descriptions seen by the LLM are intentionally left as shortform — they're internal context, not user-facing marketing.
387/387 tests pass.
v3.15.99 — Franklin Agent brand rebrand (marketing copy only)
Naming evolution. Product is now consistently called Franklin Agent in user-facing surfaces.
What changed
- README H1 =
Franklin Agent; pitch / YOPO / table use full name on first mention - Banner subtitle:
FRANKLIN Agent · blockrun.ai · vX.Y.Z(ASCII wordmark stays) - package.json description prefixed
Franklin Agent — AGENTS.mdstale# RunCodeheader finally updated to# Franklin Agent- PHILOSOPHY / CONTEXT / CONTRIBUTING / plugin-sdk / examples H1 rebranded
- New CHANGELOG entries use
## Franklin Agent X.Y.Z — titlestyle
What did NOT change (deliberately)
- npm package name
@blockrun/franklin— wouldn't break user installs - CLI command
franklin— wouldn't break user scripts - File paths (
franklin-audit.jsonl,~/.blockrun/) — technical artifacts - Agent's self-identity in code
- Historical changelog entries / release notes — factual history
Shortform "Franklin" remains the conversational nickname after first mention in any given doc (same pattern as "MacBook Pro" → "MacBook").
387/387 tests pass.
v3.15.98 — image-bearing context-token counters across the codebase (PR #54 + 3 missed siblings)
Lands PR #54 from KillerQueen-Z plus three sibling sites caught in review.
The bug class: JSON.stringify(tool_result.content) was tokenizing image base64 as text. A 140KB image → ~70K phantom chars / ~35K phantom tokens. We've been fixing this site by site since 3.15.89; this release closes the last 4.
Empirical proof (from PR #54): same session with one 100KB image showed:
- Before:
/context = 75K/200K (37.8%) - After:
/context = 1.9K/200K (1.0%)— matches Anthropic's true count
40× over-count, which also triggered premature /compact calls.
8 sites total in this bug class — 5 fixed previously, 3 plus 4 sub-fixes landed here:
| Site | Status |
|---|---|
tokens.ts:estimateContentPartTokens |
PR #54 |
tokens.ts:getAnchoredTokenCount (contextUsagePct: 0 always) |
PR #54 |
loop.ts:contextPct integer-rounded → ring frozen |
PR #54 |
reduce.ts:estimateChars |
review |
compact.ts:tool_result preview (base64 in summary prompt) |
review |
commands.ts:/context tool char count (UI display) |
review |
3 new regression tests. 387/387 tests pass.
Credits: KillerQueen-Z (PR #54) — empirical 40× reproduction and clean three-part fix. Same contributor as PR #53.
Full write-up: docs/release-notes/2026-05-12-context-token-sweep.md
v3.15.97 — log entries are one physical line (embedded newlines collapse to ↵)
Format-integrity fix. A real franklin-debug.log entry leaked a python heredoc onto its own untimestamped line because the bash command preview contained an embedded newline. Any parser splitting on /^\[timestamp\]/ (the new franklin doctor --anomaly, future cost-spike detection, plain grep) broke on the orphan line.
Fix: src/logger.ts now collapses \n / \r / \r\n to a literal ↵ marker before writing. Critically, this runs BEFORE the ANSI strip (which itself eats bare \r) so \r-only line breaks are preserved as markers. streaming-executor.ts also strips newlines from the slow-tool preview as belt-and-braces.
Coverage: every existing logger.info/warn/error/debug callsite is now newline-safe.
384/384 tests pass.
v3.15.96 — franklin doctor warns on low balance (< $1) instead of 'all clear'
Real-machine bug. Doctor's USDC balance check was binary (> 0 = green). Verified 2026-05-11 on a live wallet: $0.37 USDC showed green check, yet any paid Opus call costs $0.50+ — the next paid turn would have failed mid-stream.
Fix: tiered status with a $1.00 floor.
$0.00→ warn, "free-tier models only"$0.01–$0.99→ warn, "low; paid calls likely to fail mid-stream"$1.00+→ ok
Both warning paths now surface the deposit address AND the localhost wallet panel URL.
383/383 tests pass.
v3.15.95 — audit captures cache_creation/cache_read tokens (vision calls no longer look 28× over-billed)
Observability fix. The SSE parser was reading input_tokens from Anthropic's usage object but ignoring two cache fields that vision and prompt-cached calls return: cache_creation_input_tokens (billed at 1.25× base) and cache_read_input_tokens (0.1× base). The wallet charged correctly; the audit log didn't see the cache portion.
Verified case: Opus 4.7 call with inputTokens=3653, outputTokens=56, costUsd=$0.567. At $5/M input, $0.567 implies ~113K real billed tokens. The missing 109K were cache-creation tokens.
Fix: CompletionUsage and AuditEntry gain optional cacheCreationInputTokens / cacheReadInputTokens. SSE parser captures both. Loop threads them into audit entries.
Purely additive observability. Wallet charges unaffected. Stats totals unaffected. Unlocks cache-hit-rate visibility and a future cost-spike detector.
383/383 tests pass.
Full write-up: docs/release-notes/2026-05-11-audit-cache-tokens.md