You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phone & Voice integration — BlockRun's phone capability stack is now first-class in ClawRouter. BlockRun shipped 8 phone endpoints earlier this cycle (Twilio for number intelligence + provisioning, Bland.ai for AI-powered outbound voice calls), all x402-gated behind blockrun.ai/api/v1/phone/* and blockrun.ai/api/v1/voice/*. ClawRouter had zero integration — agents reasoning about phone tasks would skip ClawRouter entirely (per the feedback_skill_dual_layer rule: if skills/clawrouter/SKILL.md doesn't list a BlockRun capability, AI agents ignore the local proxy and hit the gateway directly, losing wallet/telemetry/local visibility). This release closes the gap on every surface.
Proxy paths. Extended the partner-route regex at src/proxy.ts:2782 to match /v1/phone/* and /v1/voice/*. Both flow through the existing proxyPaidApiRequest (x402 handled transparently). New isPhone branch in the telemetry hook emits tier: "PHONE" with model = phone/<operation> (so clawrouter stats and clawrouter report see phone usage as a distinct line). PHONE_PRICING table mirrors server-side twilio.ts + bland.ts pricing (longest-prefix match handles /voice/call/{id} poll URLs correctly) and is used only as the telemetry fallback when the x402 paymentStore is empty — actual settlement is always server-dictated.
Tool registry. Eight new entries in src/partners/registry.ts (PartnerCategory union extended with "Communications"):
blockrun_phone_lookup ($0.01) — carrier + line type
blockrun_phone_numbers_list ($0.001) — wallet's active numbers
blockrun_phone_numbers_release (free) — release a number back to the pool
blockrun_voice_call ($0.54 flat, ≤30 min) — outbound AI voice call via Bland.ai
blockrun_voice_status (free) — poll call status / transcript / recording
Voice-call tool description carries an explicit safety guardrail: "places a REAL outbound phone call to a real number — only invoke when the user has explicitly asked." Server enforces an emergency-number blocklist; ClawRouter trusts upstream rather than duplicating the list.
/cr-call slash command in src/index.ts, registered alongside /cr-imagegen and /videogen. Syntax: /cr-call +1<E.164> "<task>" [--voice nat] [--max-duration 5] [--from +1<owned-number>] [--language en-US]. New parseCallArgs helper handles both --key=value and --key value flag forms, recognizes the first +E.164-shaped token as the destination, and packs the rest as the natural-language task. Mode is fire-and-forget: the command POSTs to /v1/voice/call, returns call_id + poll_url immediately, and tells the user to poll for transcript when the call completes. The cr- prefix is mandatory — /call and /phone are even more commonly reserved by chat platforms than /imagegen was when v0.12.190 had to rename it; we don't register either bare form.
clawrouter phone CLI subcommand in src/cli.ts covers the wallet-resource operations that don't make sense as chat slash commands:
clawrouter phone numbers list — formatted table with E.164, country, expiry-in-days, ⚠ renew soon flag for ≤2 days remaining
All subcommands POST to the running proxy at 127.0.0.1:8402; payment flows through the existing wallet. 402 errors render with a friendly "fund your wallet" hint.
SKILL.md double-layer update, per feedback_skill_dual_layer rule:
skills/clawrouter/SKILL.md — added "Phone & Voice (Twilio + Bland.ai)" section after Image & Video, with the full 8-tool table; updated frontmatter description and triggers to mention phone capabilities. Without this headline update, AI agents would route around ClawRouter when reasoning about phone tasks even with the partner registry populated — they need to see the capability surfaced where they're already looking.
skills/phone/SKILL.md (new) — dedicated reference: full HTTP API for each endpoint, parameter tables, fire-and-forget polling explanation, three example agentic flows (verify-before-text, appointment confirmation, acquire-caller-ID).
README — new "Phone & Voice Calls" section between Image Editing and Models & Pricing, with the pricing table, slash command + CLI examples, raw curl HTTP usage, and the same safety guardrail surfaced in the tool description.
openclaw.plugin.json description bump — mentions phone + voice capability so the OpenClaw plugin browser surfaces it.
Out of scope (deferred): local recording/transcript download (recordings can be large; returning Bland.ai's hosted URL is sufficient for v1), auto-polling voice-call status to completion in the slash command (user opted for fire-and-forget so the chat experience returns immediately), SMS/MMS (BlockRun hasn't exposed yet), auto-renew on lease expiry (CLI surfaces the warning, user decides).
Two telemetry bugs surfaced and fixed during real-call smoke testing (placed an actual $0.54 call to +15707043521 via the patched dist; tx 0xfe6c6b5e... settled on Base; wallet reconciliation correct: $84.49 → $83.95 = exactly one $0.54 debit). Both bugs were pure logging artifacts — wallet was never wrongly debited — but they would have given misleading numbers in clawrouter stats and clawrouter report. Both fixes consolidated into a new exported pure helper resolvePhoneTelemetryCost (in src/proxy.ts) with 8 unit tests locking down the gates:
Bug 1 — phantom $0.54 charge on 4xx voice POST. First smoke test POSTed /v1/voice/call with empty {} body to exercise routing without spending money. BlockRun returned 400 (Zod validation: "expected string, received undefined"). The wallet wasn't charged, but the telemetry hook saw paymentStore.amountUsd = 0 and fell back to estimatePhoneCost("/v1/voice/call") = $0.54. Stats would record a phantom voice call. Fix: gate the fallback on upstream.status being 2xx — any 4xx/5xx skips the fallback and logs $0.
Bug 2 — GET poll miscounted as another $0.54 voice call. After placing a real call, polling GET /v1/voice/call/{call_id} for transcript status (free upstream) was being logged at $0.54 because the longest-prefix match on voice/call/ triggered the same fallback row as the initiating POST. Every 30s poll would inflate stats by $0.54. Fix: also gate the fallback on req.method === "POST" — GET polls log $0.
Refactor: gate logic was originally inline inside proxyPaidApiRequest. Pulled it out into resolvePhoneTelemetryCost(args) so the rules are independently testable (the call site is now four lines passing an args bag through the helper). Adds 8 vitest cases covering: paid-amount-wins, 4xx phantom guard, GET poll guard, 5xx guard, missing-method guard, non-phone-passthrough, and the original "successful POST with empty paymentStore → fallback" path. Without the helper extraction, locking these gates in tests would have required a full integration test with a mocked upstream — too heavy for telemetry-only logic.
Tests — new src/proxy.phone-routing.test.ts (regex matching for /v1/phone/, /v1/voice/, /v1/voice/call/{id} poll, plus negative case for /v1/phonebook), src/proxy.phone-pricing.test.ts (longest-prefix matching + the 8 resolvePhoneTelemetryCost gate cases above), src/parse-call-args.test.ts (both flag forms, quoted task spans, E.164 first-token detection). Total 31 new test cases; all 569 vitest tests green; typecheck + lint clean.
Smoke test record (free-tier verification before the real call): list-numbers ($0.001) returned an existing wallet-owned number +15707043521 (PA, expires 2026-06-15); lookup ($0.01) on that same number returned full Twilio carrier metadata (type: nonFixedVoip, carrier_name: Twilio - SMS/MMS-SVR); negative test /v1/phonebook/test correctly rejected by the partner regex (502 from chat-completion fallback rather than partner routing); CLI table formatting + expiry-warning logic verified by clawrouter phone numbers list.