Doppelvoice v0.2.2 — 20-agent adversarial fix sweep
20 parallel sub-agent reviews (architect / security / performance / Python /
code / refactor / docs / tdd / first-install UX / 24h stability / cross-platform /
deps / concurrency / WS protocol / audio math / error UX / dist UX / i18n /
malicious-server / .env edges) drove this patch release.
Tests: 46 → 86. Coverage: 14% → 24%. No public API changes.
Download
Doppelvoice-v0.2.2-win64.zip (61 MB)
SHA256: 58F3CA7ED81BAB9B9A9005C91394ECE6FF48CEDFE6C3338F901E026F48DDB5B1
Extract → copy .env.example → .env → fill in DOUBAO_APP_KEY / DOUBAO_ACCESS_KEY from Volcengine Console → double-click Doppelvoice.exe.
What changed since v0.2.1
Fixed (CRITICAL)
audio/capture.pyreferenced_HAS_SOXRwithout importing it —NameErroron every sample-rate-mismatch path, silently breaking capture when device's native rate ≠ 16 kHz.audio/opus_decoder.pyhad unbounded_chunksgrowth if the server emittedTTSSentenceStartwithoutTTSSentenceEnd(malicious or stuck server) → OOM. Now hard-capped at 64 MB;size()is O(1).gui/env_io.pyparsed.envas utf-8 strict — Windows Notepad's default UTF-8 save adds a BOM, which made the GUI think it had a key called\ufeffDOUBAO_APP_KEYwhile runtime got nothing. Nowutf-8-sig.
Fixed (HIGH)
- Log redaction missed bare-keyword fields (
password=,secret=,token=) because the leading[A-Za-z]consumed the first character before the keyword could match. - Status badge displayed literal
"status.busy"after a language switch (i18n key reverse-lookup used the mapped state value instead of the original key). - Resample fallback returned the input array at the wrong sample rate when
n_out == 0. - Reconnect counter only reset after a complete session, so 5+ short network drops climbed the exponential backoff to the 30s cap (30 s of black audio per drop). Now resets as soon as
SessionStartedis acknowledged. closeEventdidn't stop_metrics_timerfirst — 150 ms window where the GUI could touch a stopped capture.- Settings menu (Ctrl+,) wasn't disabled while a session was running — user changes silently ignored (because
Orchestratorholds an immutable snapshot). cli.py --jitter-mshelp text said "default 240"; actually 120 since v0.2.0.
Added
__post_init__validation onAudioConfig+NetworkConfig— clearValueErrorinstead of cryptic PortAudio-9997.- VB-Cable startup pre-check in
gui/app.py— finally fires the i18n-translated dialog (keys existed since v0.1 but were never wired). THIRD_PARTY_LICENSES.md+ LICENSE in dist — LGPL compliance for bundled PySide6 (Qt 6), libsoxr, libsndfile.- Bilingual
.env.examplewith explicit "App Key vs Access Token" guidance — first-install UX testing confirmed users routinely swap the two. - UTF-8 stdio reconfigure on Windows — Chinese error messages no longer show as GBK mojibake in CMD.
Tests (46 → 86)
- New:
test_env_io.py,test_opus_decoder.py,test_orchestrator_units.py. - Expanded:
test_doubao_classify.pyadds_validate_ws_url;test_config.pyaddsAppConfig.loadenv-var matrix.
Cleanup
requirements.txtpinned upper bounds (numpy<3,websockets<17,protobuf<7, etc.);grpcio-toolsdropped from runtime (lives in[dev]extras).- 11 dead i18n keys removed (
wizard.*never wired;config.modeetc. never displayed). - Unused
bus.metricsandbus.usagesignals removed.
Full changelog: CHANGELOG.md