Skip to content

Doppelvoice v0.2.2 — 20-agent adversarial fix sweep

Choose a tag to compare

@Tianqi-Bu Tianqi-Bu released this 26 Apr 06:09

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.py referenced _HAS_SOXR without importing it — NameError on every sample-rate-mismatch path, silently breaking capture when device's native rate ≠ 16 kHz.
  • audio/opus_decoder.py had unbounded _chunks growth if the server emitted TTSSentenceStart without TTSSentenceEnd (malicious or stuck server) → OOM. Now hard-capped at 64 MB; size() is O(1).
  • gui/env_io.py parsed .env as 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_KEY while runtime got nothing. Now utf-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 SessionStarted is acknowledged.
  • closeEvent didn't stop _metrics_timer first — 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 Orchestrator holds an immutable snapshot).
  • cli.py --jitter-ms help text said "default 240"; actually 120 since v0.2.0.

Added

  • __post_init__ validation on AudioConfig + NetworkConfig — clear ValueError instead 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.example with 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.py adds _validate_ws_url; test_config.py adds AppConfig.load env-var matrix.

Cleanup

  • requirements.txt pinned upper bounds (numpy<3, websockets<17, protobuf<7, etc.); grpcio-tools dropped from runtime (lives in [dev] extras).
  • 11 dead i18n keys removed (wizard.* never wired; config.mode etc. never displayed).
  • Unused bus.metrics and bus.usage signals removed.

Full changelog: CHANGELOG.md