Commit Graph
1775 Commits
Author SHA1 Message Date
Palash Debnath 1d445855d2 fix(dub): make language workflow reliable 2026-08-27 19:43:49 +05:30
Palash Debnath e93b6366e6 fix(dub): preserve voices across SRT imports 2026-08-27 19:14:56 +05:30
Parker Hill 078bad8e0f fix(setup): repair fresh-clone desktop and ROCm source installs (#1664, #1665)
Fix fresh-clone desktop development by creating the required dist placeholder before Tauri starts, and make source setup install the selected CUDA or ROCm PyTorch stack consistently. Adds behavior-level cross-platform regression coverage.\n\nFixes #1664.\nFixes #1665.\n\nThanks @uberclokr for the contribution.
2026-08-27 18:33:55 +05:30
Paolo Antinori 032c5aba86 fix(core): macOS fallback for os.waitid ownership probe (#1656)
Fixes #1656. Adds the safe waitpid fallback, proves child ownership before process-group signals, and credits @paoloantinori.
2026-08-27 17:23:25 +05:30
Palash Debnath a8371baaa8 fix(desktop): serialize backend lifecycle (#1635)
Closes #1635.
2026-08-24 18:11:37 +05:30
Palash Debnath 5a615d2c66 feat(workers): package headless GPU nodes (#1638) (#1648)
Closes #1638.\n\nPackages headless GPU workers with durable enrollment, bounded artifact handling, cross-platform lifecycle cleanup, and regression coverage. Incorporates CodeRabbit, Greptile, CodeQL, and platform-CI findings before merge.
2026-08-24 16:32:56 +05:30
Palash Debnath 12480b81b1 fix(persistence): move longform projects to IndexedDB (#1636) (#1650)
Move unbounded Stories and Audiobook project data to revisioned IndexedDB storage with bounded local fallback, durable clear tombstones, migration/recovery safeguards, and deadline-safe persistence before exits and relaunches.

Closes #1636.
2026-08-24 12:59:24 +05:30
Palash Debnath ef1cb57944 fix: route OmniVoice to ROCm GPUs (#1647) 2026-08-24 03:25:56 +05:30
Palash Debnath 6447788fdf fix(dictation): cancel widget work on unmount (#1645)
Fix the post-merge Vitest failure by invalidating CaptureWidget startup continuations and releasing timers, sockets, recorder/worklet state, and microphone streams on teardown. Adds fail-before/pass-after coverage for active capture, pending fallback, delayed microphone permission, and delayed WebSocket authentication.
2026-08-24 02:44:00 +05:30
Palash Debnath afa361913c fix(generate): surface the real cause on streaming failures (#1633)
Classify and journal local and remote streaming generation failures, return actionable scrubbed guidance, and keep exception details, tokens, and user paths out of logs and NDJSON responses.
2026-08-23 15:55:05 +05:30
Palash Debnath 0d3c81b596 feat(gguf): add Linux ARM64 runtime support (#1641)
Add linux-aarch64 platform detection, Vulkan-preferred source builds with CPU fallback, ARM64-safe PyTorch dependency markers, native artifact CI, regression coverage, and synchronized architecture documentation.
2026-08-23 15:07:52 +05:30
Palash Debnath 98c9e68aae test(worker): carry the capacity limit through the handshake, not a post-connect mutation (#1630)
The at-capacity test set client.config.max_concurrent_tasks = 1 after
connect_worker, racing the server's stream-open ConfigUpdate — which
carries the REGISTERED capacity (2, from the hello). When the frame
lands after the mutation (slow CI runners), the override is clobbered,
the worker honours 2 slots, accepts the second assignment, and the test
reports over-concurrency that never existed. Failed CI twice on
2026-08-21 (#1627 first run + main).

Fix: pass the limit through WorkerConfig so hello -> registration ->
ConfigUpdate all agree from the start; assert the registered capacity
as a regression guard. Demonstrated fail-before: with the old pattern
the server record stays 2 and a delivered config frame reverts the
client gate to 2.
2026-08-21 20:36:12 +00:00
debpalash 772e3e82b4 feat(uninstall): --app/-RemoveApp removes the prebuilt binary install
The default curl|sh and irm|iex installs now put a real app on disk
(/Applications or ~/Applications, ~/.local/bin/VoiceStudio, MSI product).
Both uninstallers gain an opt-in flag that targets exactly those:
- uninstall.sh --app: adds the app bundle / AppImage to the dry-run plan
- uninstall.ps1 -RemoveApp: resolves the MSI product across HKLM/HKCU/
  WOW6432Node and uninstalls it silently under -Yes
2026-08-22 01:58:39 +05:30
debpalash 228019c9a8 fix(install): usage text works through a curl pipe ($0 is not a file) 2026-08-21 21:08:06 +05:30
Palash Debnath e38b5f5741 feat(install): binary-first installer with version picking and --source opt-in (#1628)
* feat(install): prebuilt-app installs by default, --source opt-in, --version picker

- install.sh: default mode now downloads the verified release asset
  (dmg/AppImage + SHA256SUMS check) instead of cloning and building;
  --source keeps the previous clone-and-build flow; --version pins a release
- install.ps1: same split — msi download with checksum verification and
  setup wizard by default; -Source (or VOICESTUDIO_INSTALL_MODE) for the
  source build; VOICESTUDIO_VERSION picks a release
- worker landing page documents the modes

* fix(install): CI smoke covers binary + source modes; hdiutil output parse

- drop -quiet from hdiutil attach (it suppresses the mount-point line the
  script parses — caught by the macOS smoke)
- install.ps1 runs msiexec silently under CI, wizard interactively
- smoke verifies binary installs per OS (app bundle / AppImage / MSI
  registry entry) and keeps full source coverage behind --source

* ci(install): check HKCU/WOW6432Node too — Tauri MSI registers per-user

* fix(install): rename $version — collides with bun installer's $Version under iex
2026-08-21 14:30:33 +00:00
debpalash f60f5f1f59 chore(install): route voicestudio.sh/install* to the installer worker 2026-08-21 17:31:50 +05:30
Palash Debnath 7640f42dce feat(install): one-command installer URL (.sh + .ps1) + 3-OS install smoke (#1627)
* feat(install): one-command installer URL (.sh + .ps1) + 3-OS install smoke

- scripts/install.ps1: Windows source installer (winget deps, uv, bun,
  clone, uv sync, frontend build); honors OMNIVOICE_PYTHON/OMNIVOICE_REGION
- infra/install-redirect: Cloudflare Worker serving /install with
  User-Agent sniffing (curl -> sh, PowerShell -> ps1, browser -> landing
  page); proxies live from main; /install.sh + /install.ps1 aliases
- scripts/install.sh: fix stale advertised URL (main/install.sh never
  existed) and repo-root resolution so a local run no longer clones a
  duplicate repo into ~/VoiceStudio (verified on macOS arm64)
- .github/workflows/install-smoke.yml: run both installers end-to-end on
  ubuntu/macos/windows when they change
- docs-sync: install one-liners lead each platform guide; STRUCTURE.md

(#1626)

* fix(install): don't let a failed bun download pass silently

curl | sh runs an empty script and exits 0 when the download fails, so
a bun.sh hiccup surfaced much later as 'bun: command not found' (seen
on the macos-latest smoke runner). Fetch to a temp file, verify, fall
back to npm -g bun when node exists, and die with the manual command.
Same post-install verification for uv.

* fix(install): UTF-8 BOM for install.ps1 + quiet-style changelog entry

- tests/scripts/test_uninstall_ping.py requires shipped PowerShell
  scripts with non-ASCII text to carry a UTF-8 BOM (Windows PowerShell
  5.1 mis-decodes otherwise); same treatment uninstall.ps1 already gets
- test_changelog_style caps entries at ~400 chars
2026-08-21 11:47:39 +00:00
Palash Debnath 3eeed0cfe9 chore: refresh application dependencies (#1625)
Refreshes frontend, tooling, and Python dependencies; regenerates the frozen lockfiles and worker protocol stubs. Keeps compatibility caps for FastAPI, Oxlint, and Vitest where newer releases break repository contracts. Updates the Uvicorn bind-failure regression test for its new nonzero exit code. Fully tested after merging current main.
2026-08-21 10:30:49 +00:00
Muhammad Ahmad Ali b946fded12 fix(dub): preserve speaker attribution across segment edits (#1624)
Adds per-line subtitle timing, insertion, and bidirectional merge controls while preserving each speaker's attribution across merge/split, restore, translation, and cast edits. Includes regression coverage and localization updates. Closes #1612.
2026-08-21 09:55:04 +00:00
Palash Debnath 7718a7a10b fix: cross-platform dictation delivery (#1610)
Makes dictation delivery, capture, recovery, model fallback, AEC, and localized status behavior reliable across macOS, Windows, and Linux.
2026-08-21 03:33:50 +00:00
Palash Debnath 0687e13b57 test(perf): performance regression budgets as operation-count guards (#1622)
Adds deterministic operation-count regression budgets for streaming TTS, cached dub re-mixes, and native batch dubbing.
2026-08-21 02:53:15 +00:00
Palash Debnath 89d585a36e perf(dub,stream): reuse cached segments, batch the default engine, report real TTFA (#1620)
Reuses verified cached segments, safely batches default-engine dubbing, and reports synthesis-only TTFA/RTF.
2026-08-20 21:12:04 +00:00
Paolo Antinori 3f5114923b feat(pockettts): opt-in 24-layer checkpoints via OMNIVOICE_POCKETTTS_24L (#1613)
Adds an opt-in 24-layer PocketTTS checkpoint path, with French correctly using its required 24-layer model.
2026-08-20 20:42:40 +00:00
Palash Debnath 43f1d46fe6 fix(indextts): accept the config name upstream ships, and keep long text alive (#1619)
* fix(indextts): accept the config name upstream ships, and keep long text alive

Two independent defects, both reported on a working IndexTTS 2.5 install.

Install always failed. IndexTeam/IndexTTS-2.5 ships the model config as
config.yaml — at the pinned revision d0aa86e7 and at HEAD; config_v2_5.yaml
exists in no upstream revision. VoiceStudio demanded that name, so
_weights_floor_ok never found it and the install died claiming 'the download
was likely interrupted' when the download had been perfect. The only way
through was to hand-rename the file. Both names are accepted now, in the
installer and on the load path, so installs created with the workaround keep
working without a reinstall.

Long text was killed at 60s. infer() is one blocking upstream call that puts
nothing on the wire, and IndexTTS was the only sidecar still on the 60s
recv_timeout_s class default while pockettts and omnivoice-subprocess had both
raised theirs. Raising the default alone does not fix it — which is why the
reporter's RECV_TIMEOUT_S=3600 edit didn't help: progress frames are also what
report activity to the GPU pool's execution clock (#1367), so a silent sidecar
still trips the outer generate budget. The sidecar now heartbeats every 5s
while infer() runs (and during the cold model construction), _send takes a
lock so the beat thread can't interleave framing, and the deadline rises to
900s via OMNIVOICE_INDEXTTS_RECV_TIMEOUT_S.

test_indextts25_health_requires_25_config_name asserted the bug — that a
checkout holding only config.yaml is unhealthy — so it is rewritten to the
corrected contract, including that a genuinely truncated download is still
caught.

Fixes #1611

* test(indextts): follow the installed config name in the sidecar loader tests

Two more tests encoded the config_v2_5.yaml assumption, both asserting
cfg_path against a directory where no config existed at all — so they were
pinning the literal name rather than the resolution. They now lay down a real
checkpoints/ tree and assert the resolved path, including that a checkout
carrying the pre-fix hand-renamed config still resolves.

Caught by the full suite; the targeted runs during development did not reach
tests/backend/services/.

* test(indextts): event-driven heartbeat tests, real interleaving proof, precedence pin

Review round on #1619 — all four findings taken.

- The docs line naming 0.5.1 is version-neutral now ('Earlier installs') —
  version labels are the owner's call.
- The heartbeat tests waited on wall-clock sleeps; they now block on a
  per-write Event with a bounded deadline, so scheduler load can't flake them.
- The _send test asserted the lock EXISTS — a tautology. It now drives four
  concurrent writers through a stream that yields between every byte and
  asserts every frame decodes; verified fail-before by removing the lock
  (torn frame) and pass-after.
- The precedence test deleted config.yaml before creating the renamed one, so
  reversed precedence still passed. Both files now coexist for the assertion;
  verified fail-before by reversing _CFG_NAMES.
2026-08-20 19:43:52 +00:00
Paolo Antinorianddebpalash 3223a20f88 fix(openai-compat): reuse cached engine instances in _resolve_engine (#1614)
* fix(openai-compat): reuse cached engine instances in _resolve_engine

The direct engine-ID path in /v1/audio/speech constructed a fresh
backend per request (return cls()). For SubprocessBackend engines that
meant: a new sidecar process, a full torch import and an engine model
reload on EVERY request (measured ~28s floor per pockettts request on
an M3 Pro), plus another atexit hook registration each time — exactly
what get_engine_instance_for()'s docstring warns against.

Route the explicit-ID path through the same cached-singleton seam the
active-engine path already uses. Unknown/unavailable IDs keep their
400s; tts-1/tts-1-hd and the OmniVoiceBackend special case are
unchanged.

* fix(openai-compat): unload the outgoing engine on explicit-ID switches

Review follow-up (Greptile/CodeRabbit on #1614): caching instances without
a switch rule would let each distinct explicit engine ID stay resident,
accumulating sidecars / multi-GB in-process models. Mirror
get_active_tts_backend's MM2-01 switch rule: a different explicit ID
(omnivoice included, which resolves to the active engine) unloads the
outgoing instance first, best-effort.

* fix(openai-compat): evict via the shared single-engine-resident seam, not a router-local cache

The explicit-ID unload cache (13c14e2c) kept its own instance ref keyed by
model id. The shared engine cache is deliberately keyed by CLASS (registry
rebinds, idle sweeps and engine_memory eviction all mutate it), so the
router's id-keyed ref could go stale and keep serving an instance the
lifecycle system no longer tracked — caught by
test_openai_speech_toggle_off_sends_raw_text in full-suite order, and it
also introduced a novel unload path that ignored the
OMNIVOICE_SINGLE_ENGINE_RESIDENT opt-out.

Drop the router-local cache entirely: _resolve_engine returns the shared
cached singleton (get_engine_instance_for), and create_speech calls
evict_other_tts_engines(backend.id) before warming the engine — the exact
seam /generate uses. That covers every transition (explicit id → explicit
id, explicit id → tts-1/omnivoice aliases), honors the policy opt-out, and
leaves no per-router state to drift. Regression pinned at the route level in
test_speech_request_evicts_other_resident_engines.

* chore(changelog): trim the #1614 entry to the one-liner limit

415 chars against the 400 the style test allows — CI would have failed on it.

---------

Co-authored-by: debpalash <4178343+debpalash@users.noreply.github.com>
2026-08-20 19:14:10 +00:00
Palash Debnath 2d37627ab2 fix(setup): tolerate reserved memory in the RAM preflight, add OMNIVOICE_RAM_PREFLIGHT=0 escape hatch (#1621)
* fix(setup): tolerate reserved memory in the RAM preflight, add OMNIVOICE_RAM_PREFLIGHT=0 escape hatch (#1618)

An "8 GB" machine reports ~7.8 GB usable (firmware/iGPU/kernel
reservations), so comparing OS-reported RAM against the marketing-size
8 GB threshold hard-blocked exactly the boundary hardware the minimum is
meant to admit — with no way past the wizard. Both thresholds are now
compared with a 7% reserved-memory allowance, and
OMNIVOICE_RAM_PREFLIGHT=0 downgrades a genuine fail to a warning for
users who accept the OOM risk (same opt-out shape as
OMNIVOICE_ASR_VRAM_PREFLIGHT).

Regression tests: backend/tests/test_ram_preflight_1618.py.
Docs: troubleshooting §1c.

* review: hermetic preflight stubs in tests; correct the escape-hatch doc

Greptile P1: the Settings panel can't set OMNIVOICE_RAM_PREFLIGHT (and the
blocker appears before setup completes anyway) — the doc now points at
PowerShell / shell env only.
CodeRabbit: stub _network_check and media_tools.summary so each RAM
assertion stays fast and offline (26s -> 6s locally).
2026-08-20 19:03:50 +00:00
Palash Debnath 54a88f694b fix(watermark): run AudioSeal eagerly instead of through torch.compile (#1617)
* fix(watermark): run AudioSeal eagerly instead of through torch.compile

AudioSeal vendors moshi's @torch_compile_lazy on SEANetEncoder.forward, so
the first embed of a session — not the model load, which #1576's prefetch
already warms — called torch.compile and dropped into Inductor's C++ codegen.
On a macOS arm64 deployment that compile raised CppCompileError on 10/10
takes: the embed fail-opened and the audio shipped UNMARKED, an EU AI Act
Art. 50(2) provenance gap, after burning 30-40s on the first take and 5-8s on
each later one.

The compile is pure cost even where it succeeds. Measured on an M3 (5s of
24kHz audio, three consecutive embeds): compiled 9.70/0.26/0.23s vs eager
0.30/0.28/0.27s — a ~10s first-embed tax to save ~0.03s afterwards, on CPU
work already bounded by the 30s chunk loop. Both embed and detect now run
inside audioseal's own no_compile() switch, restored on the way out (it is a
process global, and other models are entitled to compile).

Verified end-to-end: first embed 9.70s -> 0.26s, watermark still round-trips
at confidence 1.0 with the OmniVoice message intact.

Fixes #1615

* fix(watermark): collapse the eager-guard globals into one lock-guarded state

CodeQL flagged _eager_saved's module-level initializer as dead, and it was
right: depth 0->1 always writes the field before depth 1->0 reads it, so the
None at import was never observed. Depth and saved-value are only meaningful
together and only under _eager_lock, so they become one dict rather than two
module scalars — which also drops the global statement.

Also splits three semicolon-joined statements in the regression test (Ruff
E702, CodeRabbit).

Mutation re-checked after the refactor: a naive no_compile() body still fails
with 'compile was handed back mid-embed'.
2026-08-20 18:16:00 +00:00
Palash Debnath 3441201be0 fix(dictation): clear granted accessibility blocker (#1609)
* fix(dictation): refresh accessibility blocker

* docs(changelog): note accessibility refresh

* test(dictation): assert the native widget hide on accessibility grant

The recheck regression asserted only that the Accessibility pill text left
the DOM, so it still passed with hideWidgetWindow() removed and the native
capsule stranded on screen. Hold one stable getCurrentWindow().hide spy and
assert it after the poll (fails before the fix, passes after).
2026-08-20 17:10:56 +00:00
Palash Debnath de5d848189 Merge pull request #1598 from debpalash/integrate/open-repairs-20260820
merge: land reviewed repair train
2026-08-20 05:16:03 +00:00
debpalash aa7c2f5801 fix: fail open on watermark dispatch deadlines 2026-08-20 10:32:02 +05:30
debpalash b7f14ce4ad fix: bind deadlines to selected capability device 2026-08-20 10:17:31 +05:30
debpalash 7a928da1a0 fix: size remote deadlines for worker device 2026-08-20 10:14:50 +05:30
debpalash 0961a5e512 docs: explain deadline capability fallback 2026-08-20 10:06:40 +05:30
debpalash 0619df8dff fix(cloning): retain selected passage volume 2026-08-20 10:04:54 +05:30
debpalash a91b27b518 test: make cross-loop event delivery deterministic 2026-08-20 10:01:41 +05:30
debpalash 605236566c fix: align generation routing and CPU deadlines 2026-08-20 09:59:47 +05:30
debpalash 918c400f29 fix: preserve audio on watermark teardown cancellation 2026-08-20 09:48:43 +05:30
debpalash 76d16ac1bd fix: fail open on watermark shutdown submission race 2026-08-20 09:43:58 +05:30
debpalash f22606f3ad Merge PR #1600 follow-up: enforce reference bound without preprocessing 2026-08-20 09:36:57 +05:30
debpalash f8492dd676 fix(cloning): cover the fifteen-second boundary 2026-08-20 09:36:31 +05:30
debpalash f6afa43d07 Merge PR #1600: bound long cloning references
# Conflicts:
#	CHANGELOG.md
2026-08-20 09:31:02 +05:30
debpalash 835a889326 fix(cloning): bound exhaustive reference selection 2026-08-20 09:30:16 +05:30
debpalash 2f3888549b Merge PR #1606: isolate workspace DOM during rapid navigation
# Conflicts:
#	CHANGELOG.md
2026-08-20 09:18:01 +05:30
debpalash e9e4d95d06 perf(cloning): bound reference ASR candidates 2026-08-20 09:17:43 +05:30
debpalash 2048d2793a Merge PR #1604: keep healthy CPU synthesis past five minutes
# Conflicts:
#	CHANGELOG.md
2026-08-20 09:17:30 +05:30
debpalash 52ce462396 test: exercise delayed workspace cleanup 2026-08-20 09:14:09 +05:30
debpalash e300739d78 fix(cloning): select bounded speech with ASR 2026-08-20 09:13:42 +05:30
debpalash 7efae54cf8 fix: budget the selected engine device 2026-08-20 09:13:18 +05:30
debpalash 0257bfcfec fix: isolate workspace DOM lifecycles 2026-08-20 09:10:07 +05:30
debpalash 933c1a2cf1 Merge PR #1605: add resizable Dub editor columns 2026-08-20 09:09:59 +05:30