## Summary
- **`@plumbus/voice` 0.4.5 — hearing repair ownership split.** New
`onHearingRepair` hook on `defineVoice`: the framework keeps only the
mechanism (signal-level detection — empty transcript after speech
energy, or transcript below the confidence threshold — plus timing,
playback, and cost recording), while the app owns the content. The hook
receives `{ reason, transcript?, confidence?, language?, sessionId }`
and returns the text to speak (bare string or `{ text, tone }`), or
`undefined`/`null` to suppress. A suppressed/hookless `low_confidence`
signal now lets the turn proceed with what was heard instead of
swallowing the transcript. Hook `tone` is normalized against
`toneProfiles` and mapped through the TTS adapter, so repair utterances
can carry per-turn `targetGender`/`voiceId` delivery controls. Absent
hook → built-in `empty` default line (backwards compatible); throwing
hook → logged fallback.
- **Framework no longer judges content.**
`looksLikeUncertainProperName`, the `uncertain_name` reason, and the
built-in "spell the name" lines are removed — deciding a transcript is a
*name* is app-level judgment. Migration note in the changelog for apps
that relied on it.
- **`@plumbus/voice-deepdub` 0.1.4 — Deepdub billed like the vendor
bills.** Usage is metered in seconds of generated audio (the vendor's
billing dimension: ~1,000 chars ≈ 1 minute) instead of input characters;
the ledger pricing entry moves to `audioOutputSeconds`. The rate is
env-driven via `DEEPDUB_USD_PER_MINUTE` (contract pricing has no public
rate card; default 0.143 = smallest published AI-agents tier's effective
rate), resolved per lookup so import order doesn't matter.
- **`@plumbus/voice-soniox` 0.1.4** — realtime STT rate corrected to the
vendor list price: $0.12/hour (was $0.60/hour, 5× over).
- **`@plumbus/voice-livekit` 0.1.4** — transport rate corrected to the
Cloud Ship-tier WebRTC overage rate: $0.0005/participant-minute (was
$0.02, ~40× over; marginal cost is $0 below the plan's included
allotment).
- **`calculateVoiceCost`** falls back to a ~1,000 chars/min estimate for
per-minute-audio pricing when only text length is known, so
direct-utterance cost rows (hearing repair, replay) keep a nonzero
estimate.
## Test plan
- [x] `pnpm format` / `lint` / `format:check` / `typecheck` / `build` —
green
- [x] Voice packages: 267 tests green (voice 176, deepdub 27, soniox 19,
livekit 46), new coverage mutation-checked (reverting each mechanism
fails its test)
- [x] Hook: hook text spoken instead of default; args carry
transcript/confidence/language/sessionId; undefined suppresses; tone
params reach TTS; hook throw falls back; hookless low-confidence
proceeds with the turn
- [x] Pricing: env override applies through the registered entry;
invalid env falls back with a warning; Deepdub usage meters seconds (96k
bytes = 1s at 48 kHz PCM16); LiveKit math updated; Soniox rate pinned to
the vendor page
- [ ] Note for local runs: root `pnpm test` is red on two untracked WIP
test files in plumbus-core (`listen-ports`, `load-extensions`) whose
sources don't exist — not part of this PR, not in the committed tree
Made with [Cursor](https://cursor.com)