Skip to content

Releases: BlockRunAI/Franklin

v3.21.9 — VoiceCall: interruption_threshold + model controls

23 May 16:14

Choose a tag to compare

External contributor @BeneficialVast1048 shipped PR #66 closing #65 (filed by @KillerQueen-Z) — adds two optional pass-throughs to `VoiceCall` that the BlockRun gateway already accepted but Franklin wasn't surfacing.

New fields

Field Range / values What it controls
`interruption_threshold` integer 50–500 (ms) How long the AI waits before talking over the recipient. Lower = polite, higher = AI dominates. One of the biggest factors in whether the call feels natural vs rude.
`model` `base` / `enhanced` / `turbo` Bland model tier — trades latency, quality, cost.

Both optional and only forwarded when provided. Existing calls completely unchanged.

Bonus tidy

PR also extracts body-building into a reusable `buildVoiceCallBody()` helper so future optional pass-throughs land in one obvious place instead of scattering between schema + execute. Test covers the new fields and the refactor.

Gateway verification

Probed `/v1/voice/call` with both new fields, got past `.strict()` schema validation (hit the `task` length check downstream — proving unknown-field rejection didn't fire). Cross-checked against `blockrun/src/lib/bland.ts:39-40`.

Tests

406/406 pass (one new VoiceCall regression).

```
npm i -g @blockrun/franklin@3.21.9
```

v3.21.8 — strip RealFace from VideoGen (upstream pulled the surface)

23 May 04:05

Choose a tag to compare

Regression fix following BlockRun gateway commit `f527c3b` — "drop real-person video entirely." KYC at the upstream verification provider conflicts with BlockRun's wallet-only stance, so the gateway no longer accepts `real_face_asset_id` on `/v1/videos/generations`. Calls with it now 400.

Franklin shipped `real_face_asset_id` in v3.21.2 (matching the then-active gateway commit `b86d5e9`). v3.21.8 strips it:

  • `VideoGenInput.real_face_asset_id` field removed
  • `REAL_FACE_ASSET_ID_REGEX` + `REAL_FACE_MODELS` constants removed
  • Client-side validation block (regex / model-gate / image_url-mutex) removed
  • Body-forwarding line removed
  • `input_schema.properties.real_face_asset_id` removed

Net: ~50 LOC removed. Existing video calls without `real_face_asset_id` are unaffected.

Other upstream Seedance changes — not actioned in this patch

Server-side now defaults to 720p + audio (commit `e6dc1f1`). Cost-per-call goes up unless the caller explicitly passes a lower resolution. New optional body fields (`generate_audio` / `resolution` / `seed` / `watermark` / `return_last_frame`, commit `4564119`) are accepted by the gateway but not yet surfaced through Franklin. Both deferred to a separate plan.

For Token360's RealFace path going forward: BlockRun's docs at `/docs/video/real-person-ip` walk users through enrollment + `ta_xxxx` asset id; users call Token360 directly. Not something Franklin should re-add as long as the gateway stays wallet-only.

Tests

405/405 pass.

```
npm i -g @blockrun/franklin@3.21.8
```

v3.21.7 — PredictionMarket schema realignment + agent-loop retry guard

21 May 14:28

Choose a tag to compare

External contributor @KillerQueen-Z shipped PR #62 — 9 distinct bugs fixed across all 10 `PredictionMarket` actions, plus a small agent-loop retry-guard fix.

What was broken

`PredictionMarket` was modeled on the public Polymarket Gamma / Kalshi API conventions, but data actually comes from Predexon's normalized v2 schema behind the BlockRun gateway. Hand-written types cast from `unknown` gave no compile-time signal, so every field/param mismatch was invisible until a live call.

# Action Symptom Fix
1 searchPolymarket 422 every call status enum is `{open, closed}`, not `active`
2 searchPolymarket / searchKalshi metrics `n/a` use `total_volume_usd` / `outcomes[].price` (PM), `last_price` (Kalshi)
3 crossPlatform blank titles venues are UPPERCASE nested objects
4 leaderboard "no data" + n/a rows under `entries`, stats under `metrics.*`, `sort_by` enum
5 smartActivity 400 every call requires ≥1 smart-wallet criterion
6 smartMoney 400 → wrong shape requires criterion; response is single `positioning` aggregate
7 smartMoney 404 when chained `condition_id` was truncated to 14 chars in search output
8 searchAll / searchKalshi status rejected `active` normalization missing
9 agent loop spun to 50-call cap on Predexon 422 external-wall guard didn't treat 400/422 as retry-useless

Agent loop guard fix

`EXTERNAL_WALL_FAILURE_PATTERN` now matches `400` and `422` too — "the same bad payload won't recover by hammering the endpoint." Predexon 422s aren't billed, so the cost guard never kicked in either; the agent ran up to the 50-call `HARD_TOOL_CAP` before stopping. `404` stays out of the pattern (legitimate "retry with a different query" signal).

Verified live

PR author re-ran the 4 article-example workflows (compare Fed-cut odds, leaderboard read, smart-activity, smart-money) end-to-end through `franklin start -p`. Before: 422s, `n/a`s, runaway loops costing ~$0.30. After: real volumes, implied odds, and the 1.8% vs ~5% Polymarket-vs-Kalshi arbitrage read.

405/405 tests pass.

```
npm i -g @blockrun/franklin@3.21.7
```

v3.21.6 — VoiceCall: voicemail controls

21 May 14:18

Choose a tag to compare

External contributor @KillerQueen-Z shipped PR #61 adding two optional params to the `VoiceCall` tool so the agent can control voicemail behavior from natural language.

What

  • `voicemail_action`: `hangup` | `leave_message` | `ignore`
  • `voicemail_message`: the monologue spoken when `leave_message` is set (1–1000 chars)

Before this, a request like "call my client, and if it goes to voicemail leave this message..." had nowhere structured to go — it could only be stuffed into the free-text `task` prompt as fragile if-then logic.

Tool spec note: voicemail is one-way. `leave_message` speaks the message once and hangs up, no back-and-forth.

What stays the same

Both fields are optional and only forwarded when provided. Ordinary calls are completely unchanged — Bland.ai still hangs up on voicemail by default unless the caller explicitly opts in.

Gateway dependency

Required matching change on the BlockRun gateway side (blockrun#26) because the call body is validated with `.strict()`. That PR landed + deployed before this Franklin release shipped; gateway acceptance verified live via the 402 schema-response probe before merge.

Tests

405/405 pass. No regressions.

```
npm i -g @blockrun/franklin@3.21.6
```

v3.21.5 — UI: inline short pastes (≥5 line threshold)

19 May 05:35

Choose a tag to compare

External contributor @KillerQueen-Z shipped PR #60 fixing a real bracketed-paste UX bug.

The bug

Every paste — even a single-sentence prompt — was being replaced in the input box with a `[Pasted ~N lines]` placeholder, so the user couldn't see what they pasted. Root cause: `findPasteBlocks(...) > 0` triggered the collapse unconditionally with no line-count threshold.

The fix

New `PASTE_COLLAPSE_LINE_THRESHOLD = 5` constant. Pastes shorter than 5 lines render inline as plain text; longer pastes still collapse to a placeholder. Decode at submit time is unchanged — both branches expand any preserved placeholder back to the original content before the model sees it.

Paste Before After
1-line, 230-char prompt `[Pasted ~1 line]` inline
4-line stack trace `[Pasted ~4 lines]` inline
5-line code block `[Pasted ~5 lines]` `[Pasted ~5 lines]`
50-line log dump `[Pasted ~50 lines]` `[Pasted ~50 lines]`

Single-file change to `src/ui/app.tsx`. 405/405 tests pass.

```
npm i -g @blockrun/franklin@3.21.5
```

v3.21.4 — Phone/Voice tool perms fix + VoiceStatus internal polling

18 May 20:26

Choose a tag to compare

External contributor @KillerQueen-Z shipped PR #59 fixing two real bugs from a session today, and on top of that refactored `VoiceStatus` to block-and-poll internally. Bundling with a `/phone-call` skill update so the agent's mental model lines up with the new tool shape.

Bug 1: spammy "Allow?" prompts on every VoiceStatus poll

PR #58 wired 8 typed Phone/Voice tools but skipped the permissions classifier — so every `VoiceStatus` poll during an in-progress call triggered an interactive prompt. 11 prompts during a single call in the repro.

Fixed by classifying each tool by side-effect, not by price:

Tool Category Why
`ListPhoneNumbers`, `PhoneLookup`, `PhoneFraudCheck`, `VoiceStatus` READ_ONLY Info queries — don't change the world. Same treatment as `ImageGen` / `ExaSearch` which also cost USDC.
`VoiceCall` ASK Dials a real human, irreversible
`BuyPhoneNumber`, `RenewPhoneNumber` ASK Holds / extends number for 30 days, costs $5
`ReleasePhoneNumber` ASK Permanently returns number to pool

Bug 2: signature-loop guard killed manual polling

Franklin's anti-infinite-loop guard kills turns at 5 identical inputs. `VoiceStatus` poll cadence (every 30 s while a call ran) hit that wall reliably. PR #59 refactored `VoiceStatus` to block-and-poll internally — 5 s interval, 35 min ceiling, returns when call reaches a terminal state (`completed` / `failed` / `cancelled` / `busy` / `no-answer` / `voicemail`).

Same pattern as `videogen.ts pollUntilReady` + `imagegen.ts pollImageJob`. Agent mental model collapses to "fire VoiceCall, then VoiceStatus once, get transcript when it ends."

`/phone-call` skill updated

Step 6 was "loop VoiceStatus every 30 s for up to 10 min." Now: "call VoiceStatus once and wait for completion." Without the skill update the LLM would still try to loop — unnecessary AND harmful (signature guard).

Tests

405/405 pass. No regressions.

```
npm i -g @blockrun/franklin@3.21.4
```

v3.21.3 — call cost displays correctly + Calls tab XSS hardening

18 May 18:51

Choose a tag to compare

Two issues found reviewing v3.21.2 against real call data.

Bug: call cost showed $0 instead of $0.54 after completion

`VoiceCall` POST writes a "queued" row with `paid_usd: 0.54` on initiation. Subsequent `VoiceStatus` polls (free) write update rows with `paid_usd: 0`. The panel's `/api/calls` endpoint used `log.summary()` / `log.byCallId()`, which returned the latest row per `call_id` — so the completed-status row's `paid_usd: 0` overwrote the initial $0.54 in the UI.

Fixed two ways (belt-and-suspenders):

  • `src/phone/call-log.ts` — `summary()` and `byCallId()` now take `Math.max` of `paid_usd` across all rows for a given `call_id`. Largest charge wins regardless of row order or update timing.
  • `src/tools/voice.ts` — `VoiceStatus` update writes carry `prior.paid_usd` instead of `0`. Non-aggregating readers also see the right value.

Two new test assertions pin the behavior. 405/405 tests pass (was 404).

Hardening: Calls tab XSS

Call data (transcripts especially, which flow unfiltered from Bland.ai) is now sanitized end-to-end:

  • New `escapeHtml()` helper covers `& < > " '` (was previously only `& < >`)
  • New `safeHttpUrl()` validates `http:` / `https:` protocol on recording URLs before injecting as `` — blocks `javascript:` URL smuggling
  • All Calls-tab user-data renders now go through `escapeHtml`; recording href goes through `safeHttpUrl` + `escapeHtml`

Immediate risk is theoretical (the journal is local-only), but the panel renders content directly to the DOM and the discipline is worth having.

Also bundled

Deep-link from URL hash to the Calls tab — `localhost:3100/#calls` now auto-loads the list. Previously rendered empty until you clicked the sidebar.

```
npm i -g @blockrun/franklin@3.21.3
```

v3.21.2 — VideoGen RealFace + panel rebrand

18 May 13:09

Choose a tag to compare

Aligns Franklin with two recent BlockRun gateway changes.

RealFace asset support in VideoGen

Matches BlockRun b86d5e9. The gateway's `/v1/videos/generations` route now accepts an optional `real_face_asset_id` for Seedance 2.0 variants — BytePlus RealFace seeds the first frame from a real-person asset for cross-frame character consistency.

VideoGen adds the field with full client-side validation:

  • Regex `^ta_[A-Za-z0-9]+$`
  • Model gate: `bytedance/seedance-2.0` and `bytedance/seedance-2.0-fast` only — 1.5 Pro + non-Seedance reject with a clear error
  • Mutually exclusive with `image_url` (both seed the first frame; client refuses both at once)

Client-side checks save an x402 round-trip; the gateway returns 400 on the same conditions anyway.

Users get asset IDs (`ta_`) from token360's Asset UI after H5 verification.

Panel rebrand: "Franklin" → "Franklin Agent"

Matches BlockRun `f69ffdb`. Two strings updated in `franklin panel`:

  • Sidebar `

    `: "Franklin" → "Franklin Agent"

  • Browser tab title: "Franklin Panel" → "Franklin Agent Panel"

Terminal banner already said "Franklin Agent v3.X.X" since v3.8.17 — the visible brand is now consistent across panel + CLI.

The big watermark behind the panel content stays "FRANKLIN" — styled hero element, renaming breaks its sizing.

```
npm i -g @blockrun/franklin@3.21.2
```

v3.21.1 — fix: typed Phone + Voice tools now report cost

18 May 04:29

Choose a tag to compare

The bug

PR #58 (v3.20.2) shipped the typed Phone + Voice tools without wiring `recordUsage()` telemetry. Real-world repro after v3.21.0 shipped:

Fired `VoiceCall` for a $0.54 outbound call. The x402 payment settled on Base correctly (recipient picked up, transcript delivered, journal row wrote to `~/.blockrun/calls.jsonl`) — but the status bar only showed `-$0.0039` (the LLM cost). The $0.54 never landed in `franklin-stats.json`, so the per-turn spend delta lied.

The fix

Thread `{ tool, priceUsd }` through `postWithPayment` / `getNoPayment` in `src/tools/phone.ts` and `src/tools/voice.ts`. Call `recordUsage(tool, 0, 0, priceUsd, latencyMs)` on every successful response. Errors don't record (gateway doesn't charge on 4xx). Telemetry is best-effort — never blocks a paid call.

Costs now reported

Tool Price
`ListPhoneNumbers` $0.001
`BuyPhoneNumber` $5.00
`RenewPhoneNumber` $5.00
`ReleasePhoneNumber` free
`PhoneLookup` $0.01
`PhoneFraudCheck` $0.05
`VoiceCall` $0.54
`VoiceStatus` free

`franklin` status bar, panel Audit tab, and `franklin stats` now all show the real x402 spend per Phone/Voice tool call. 404/404 tests pass.

```
npm i -g @blockrun/franklin@3.21.1
```

v3.21.0 — /phone-call skill + call journal + panel Calls tab

18 May 02:34

Choose a tag to compare

Phone-call orchestration

PR #58 (v3.20.2) shipped the thin typed `VoiceCall` / `VoiceStatus` tools. v3.21.0 layers the orchestration on top so calling a phone is one skill invocation away.

`/phone-call` skill — seven-step workflow:

  1. Extract recipient + task from the user's request
  2. List wallet-owned numbers via `ListPhoneNumbers` ($0.001); refuse if 0
  3. Compose the task script using a reusable template
  4. Confirm the full plan (to, from, cost, voice, max_duration, task summary)
  5. Fire `VoiceCall` ($0.54) — async, returns `call_id`
  6. Auto-poll `VoiceStatus` (free) every ~30s until terminal status or 10min cap
  7. Surface transcript, duration, recording URL, total cost

Compliance baked in. US/CA only. Daytime preference flagged in the confirmation step. TCPA prior-consent requirement for any task script that reads like outbound marketing/sales — refused without an explicit user attestation. No auto-fired follow-ups; the user reconfirms every call.

Call journal

`~/.blockrun/calls.jsonl` — append-only. `VoiceCall` writes a queued row on initiation; `VoiceStatus` appends a fresh row on every poll with updated status / transcript / recording / duration. `summary()` returns one row per `call_id` (latest wins) — the canonical "recent calls" view.

Panel "Calls" tab

`franklin panel` → new sidebar nav between Phone and Sessions. Lists recent calls with:

  • Status badge: green (completed), amber (queued / in_progress), red (failed / no-answer / busy / voicemail)
  • To / from numbers, duration (m:ss), cost ($0.54), timestamp, recording link
  • Expandable `
    ` block for the full transcript

Two read-only panel endpoints (loopback + same-origin guarded):

```
GET /api/calls?limit=50 — summary list
GET /api/calls/:callId — single-call detail
```

Read-only by design — placing calls from the panel is deferred to a future release (would need to render the confirmation gate the agent has).

What didn't ship

  • Placing calls from the panel UI — deferred.
  • Inbound call handling — Bland.ai webhooks not yet wired by BlockRun upstream.
  • Voice cloning, conference calls — separate plans.
  • Auto-renewal of caller-ID when ≤2 days from lease expiry — separate plan.
  • Per-token chat billing & re-enable `/surf-chat` — BlockRun's call, not Franklin's.

Verification

  • 404/404 tests pass — 5 new CallLog tests + isTerminalStatus + no regressions.
  • Live e2e gated behind `VERIFY_CALL_E2E=1` in `scripts/verify-call.mjs` (real $0.54 — opt-in only).

```
npm i -g @blockrun/franklin@3.21.0
franklin skills # lists 8 bundled skills including phone-call
```