PPalash DebnathDocument the integrated Electron transition and CI repairs
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat: add live bootstrap progress bars and log inspection to splash screen | 4 个月前 | |
docs(readme): lead with download + first clone; seed benchmarks page (#1555) * docs(readme): lead with download + first clone; seed benchmarks page Quickstart (installers, install guides, a three-step first-clone walkthrough) moves above What's-new/Features in both READMEs — visitors get the action before the pitch. New docs/benchmarks.md anchors measured per-engine/device numbers on the bench_pipeline.py harness, community-contributed, no estimates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): entry for the README conversion restructure (#1555) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): emit RTF + CUDA peak VRAM; guard NaN RAM; define the benchmarks schema Bot harvest on #1555: the tts stage now prints RTF per warm measurement and CUDA peak VRAM (None elsewhere — no made-up zeros), the stage floor refuses unmeasurable RAM instead of sailing past a NaN comparison (FLOOR_GB=0 overrides), docs/benchmarks.md columns map 1:1 to what the harness prints, and the download badges say they open the release page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(readme): link palash.dev from the maker section Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): name the resolved engine, track VRAM from resolution, comment the guards Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(readme): the quick-switch gif is the hero image The hero shows motion now; the Launchpad screenshot moves into the 0.5.0 What's-new slot so nothing appears twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): peak VRAM is reserved memory; adapter engines name their model Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): subprocess-isolated engines report VRAM n/a, not a parent-side zero Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): out-of-process detection is declarative; sherpa rows name their model 'runs_out_of_process' is now a TTSBackend attribute set by SubprocessBackend AND omnivoice-gguf (which inherits TTSBackend directly but spawns a binary per generate — the isinstance check missed it). Duck-typed for the same module-purge reason as _is_subprocess_isolated. Sherpa-onnx identity comes from _model_dir's basename when _model_id is absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bench): backends self-report model identity via TTSBackend.model_identity() Greptile enumerated the adapter engines one at a time (mlx _model_id, sherpa _model_dir, cosyvoice env-only) — the attribute sniffing rots per engine. The hook fixes the class: each multi-model backend reports its own identity, the profiler just asks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 1 个月前 | |
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. | 26 天前 | |
feat(onboarding): refresh the bundled demo voice | 1 个月前 | |
feat(demos): ship the demo audio and video the app already advertises (#1517) * feat(demos): ship the demo audio and video the app already advertises Every demo asset in the app was a dead link on anything but a Mac. `personalities.py` has carried a `preview_url` for each of the seven voice-design presets since they were added; DictationDemo.jsx posts three bundled WAVs to /transcribe so the feature can be shown without microphone permission; the Dub workspace reads a manifest and plays a source video plus four dubbed languages. None of those files were committed, because the tooling that renders them (scripts/build_demos.sh, scripts/build_dub_demo.sh) hard- requires macOS `say` — it even carries a `TODO: add espeak-ng path for Linux contributors`. So the presets returned 404, the replay buttons did nothing, and the dubbing demo never loaded. Rendered with VoiceStudio's own engine, which runs wherever the app does: - 7 voice-design previews (2.2 MB) - 3 dictation replay clips (1.1 MB) — verified by transcribing them back: the conversational and French clips round-trip exactly - dubbing demo: source + 4 dubbed videos with subtitles and manifest (9.6 MB) Tooling fixes this turned up: - build_dub_demo.sh wrote to backend/assets/demo/dubbing, but main.py mounts backend/assets/samples at /demo_audio — so the frontend's /demo_audio/demo/dubbing/manifest.json could never have resolved even after a successful Mac build. Output moved under the mount. - `say` is now the fallback rather than the requirement: the new scripts/render_dub_demo_audio.py renders the five tracks with the engine and the shell script picks them up. - The five demo paragraphs lived in two files. They are now one JSON both read — two copies is one edit away from a video whose subtitles disagree with it. - render_demos_omnivoice.py peak-normalized, which a single-sample transient defeats: the Helpdesk preset landed at -30 dB RMS against -17 dB for its neighbours, so the preview row played at wildly different volumes. Now EBU R128 at -18 LUFS with a -1.5 dBTP ceiling. - …and pinning the output rate, because loudnorm resamples to 192 kHz internally and writes there unless told otherwise, which turned 2.1 MB of previews into 17.5 MB of identical-sounding audio. - update_manifest() looked for a manifest at a path nothing writes, so it always printed "not found" and did nothing. - Dictation is rendered here now too. It was excluded on the grounds that `say` was good enough and engine TTS was overkill — true only on macOS. tests/test_demo_assets_exist.py resolves every advertised URL against the directory main.py actually mounts, and checks each dubbing subtitle matches the script its manifest entry claims. A missing static file is not an import error and not a failing request; nothing would have caught this otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the demo-asset entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): watermark rendered demo audio, and harden the render scripts Review findings on #1517: - Greptile P1: the renderers wrote engine output straight to disk, so a re-render shipped demo audio with no provenance mark. These clips play back to users as VoiceStudio output — they are synthetic audio leaving the app like any other, and now go through mark_synthetic (#1169), the one chokepoint every producing route uses. It runs on the file AFTER loudnorm, since loudnorm re-encodes what it is handed, and says so loudly when marking is unavailable rather than committing an unmarked asset. The dubbing renderer shares the same helper. - CodeRabbit: build_dub_demo.sh checked only source.src.wav before deciding it could run without macOS `say`, so a Linux or Windows run with four of five tracks present reached a missing one, called `say`, and left a half-built bundle. It now requires all five. - CodeRabbit: shutil.move over an existing path delegates to os.rename, which raises FileExistsError on Windows — os.replace overwrites atomically everywhere. - CodeRabbit: the preview test discovered presets in a parametrize argument, importing app code at collection time and leaving core.personalities in sys.modules for later tests. Discovery moved into the test body. CI: the rendered dub bundle's zh/ja subtitles, its manifest and the script source are dubbing CONTENT, not UI strings — allowlisted in test_no_hardcoded_cjk.py with that justification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): a render that cannot be watermarked fails instead of warning CodeRabbit and Greptile, #1517: mark_synthetic degrades rather than raising — correct for generation, wrong for a render script, whose whole job is to produce files a human then commits. A printed warning on a scrolling console is not a gate, so both scripts exited 0 with unmarked assets sitting on disk ready to commit. They now raise, with the reason and the fix; OMNIVOICE_DEMO_ALLOW_UNMARKED=1 stays for a local listen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: stop a flaky dependency fetch from failing green runs en-core-web-sm resolves to a direct GitHub release URL, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own three retries all land within the same few seconds and fail together, so the whole job dies on a dependency that has nothing to do with the change under test — it cost #1518 and #1517 an otherwise-green run tonight. Two changes: back off between whole `uv sync` attempts, which is what actually clears it, and pass --no-sync to the pytest steps. `uv run` re-resolves the environment before running, so every test step was a fresh chance to hit the same fetch even though the install step had already synced — that is exactly how #1518 failed, in the isolated backend/tests step, with all 5467 tests already passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: one retry seam for every uv sync, not just the job that failed last en-core-web-sm resolves to a direct GitHub *release* URL rather than a package index, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own retries all land inside the same ~10 seconds and fail together, so a job dies on a dependency unrelated to the change under test. Tonight that cost four otherwise-green runs across #1515, #1517 and #1518 — and the first fix only covered the Tests job, so the next failure simply moved to Smoke (Linux), which syncs separately. The fetch is per-job, so the fix has to be per-job: scripts/uv-sync-retry.sh backs off between whole attempts (15s, 45s, 90s) and every workflow that syncs now goes through it — ci.yml (tests + the platform matrix), release.yml, security.yml, evals.yml. It still fails loudly after four attempts, so a genuinely broken lockfile is not disguised as a flake. The Tests job also lacked the UV_HTTP_TIMEOUT / UV_HTTP_RETRIES the smoke matrix has always set, which is part of why it was the one that kept dying; it has them now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ci): pin the Intel-Mac contract by intent, not by command spelling test_ci_verifies_intel_mac_as_the_documented_remote_only_host asserted the literal line `run: uv sync --extra pockettts`, so routing every sync through scripts/uv-sync-retry.sh read as a broken Intel-Mac contract. The contract it exists to protect is that the pockettts extra installs ONLY on backend_supported legs — which the regex now pins, while leaving how the sync is invoked free to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: keep every uv run out of the resolver, and bound the retry budget CodeRabbit, #1517: - `uv run` re-resolves before running, so the smoke suite, the worker-artifact tests, the release test run and the eval run were each a fresh chance to hit the flaky direct-URL fetch outside the retry loop. All of them pass --no-sync now; the environment is already synced by the step that owns the retries. security.yml's `uv run --with pip-audit` is deliberately left alone — it layers an ephemeral package rather than running the project's own tests. - The retry count multiplied uv's own budget (UV_HTTP_RETRIES=5 with a 120 s timeout on the smoke matrix). Three attempts and 60 s of total backoff outlast the refusals actually observed while staying well inside the jobs' timeout-minutes. - The Intel-Mac contract test pinned the smoke command literally too, so --no-sync tripped it exactly like the sync line did. Same fix: assert the contract (smoke runs only on backend_supported legs), not its spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
Ship a supported per-user Windows installer (#1730) Closes #1713 Adds a separately identified per-user MSI and updater channel, non-administrator install/uninstall verification, and fail-closed WebView2 handling for current-user installs. | 16 天前 | |
feat: implement frontend UI components and expand research documentation for voice processing and translation workflows. | 4 个月前 | |
Polish Electron navigation and theme, refresh README and agent skills | 2 天前 | |
feat: setup wizard, donate page, CI fixes, performance optimizations, and style extraction - Implement donate page and migrate API fetching to react-query hooks - Add setup wizard for batch job management and voice clip editing - Refactor setup router into package (wizard, models, download sub-modules) - Fix 9 CI test failures from setup router refactor - Fix cross-device link error in prefs.py atomic writes - Fix event loop mismatch in export test fixtures - Modernize README with architecture diagram and 13 app screenshots - Defer per-segment disk writes in dub_generate for ~6s faster dubs - Extract 45 inline styles from Launchpad, KeyboardCheatsheet, DubSegmentRow - Add playwright dev dep and screenshot capture script | 4 个月前 | |
Document the integrated Electron transition and CI repairs | 1 天前 | |
feat(electron): add full VoiceStudio desktop app | 3 天前 | |
fix(dev): reclaim the port from a backend the app itself left running Closes #1974. The dev launcher only treated a port holder as ours when it ran out of the git checkout. A backend the Tauri shell spawned lives under a per-app directory named after the bundle id instead, so the launcher saw its OWN orphaned backend as a stranger, refused to free port 3900, and aborted the run with "Refusing to stop unrelated process" and no way forward but Task Manager. Ownership now also accepts the app's reverse-DNS identifier in the executable path or the command line. A bundle id is specific enough to be safe: nothing else on the machine carries it, which is the point of the namespace. The guard itself is unchanged in spirit — a foreign listener on the port is still refused, and a test pins that widening ownership did not widen it to everything, including a process from some other vendor's bundle. Known limit, since I hit it in this repo: on Windows the check is given the command line and executable path but not the working directory, so a backend started by hand from an arbitrary interpreter — a bare `uvicorn` whose only link to the checkout is a relative --app-dir — is still not recognised. That is a different shape from the reported one and needs the cwd, which this code path does not currently have. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S | 8 天前 | |
fix(release): clear target asset collisions before retry uploads | 11 天前 | |
feat(triage): crash-class recurrence report — the reliability metric (#1549) * feat(triage): crash-class recurrence report — the reliability metric scripts/crash_class_report.py measures the "backend died / never came up" class (the project's #1 lifetime failure, ~1 in 5 of all issues) filtered to reports from the current version — the definition of done for the reliability cycle. Buckets by the bug reporter's Build-status stamp (#1547): current / outdated / unknown (pre-deflection builds), so deflection-miss noise never pollutes the number the work is judged on. tests/scripts/test_crash_class_report.py pins the title→sub-class mapping against the real historical title shapes and locks the stamp literals to frontend/src/utils/bugReport.js so a reworded marker fails in CI instead of silently zeroing the metric. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(triage): --version is authoritative; loud fetch-cap warning Bot-review harvest on #1549: with --version, the Environment Version line now decides the bucket (extracted to pure classify_build + tests) — a report stamped "current at filing time" during another version's window no longer counts toward this version's recurrence. Hitting the 500-issue fetch cap now warns loudly instead of silently understating. The stamp lockstep test asserts the full Build-status prefix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 1 个月前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
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. | 21 天前 | |
feat(dictation): model picker in the engine quick-switch, and a recoverable Windows dev stack Adds the sherpa-onnx dictation model picker under the Transcription engine row so the model the hotkey loads is switchable without opening Settings, routes the Sherpa transcription path through that same preference, and makes the Windows desktop dev stack recover instead of demanding Task Manager. Refreshes the Tauri and npm dependency pins that went with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S | 8 天前 | |
feat(dictation): model picker in the engine quick-switch, and a recoverable Windows dev stack Adds the sherpa-onnx dictation model picker under the Transcription engine row so the model the hotkey loads is switchable without opening Settings, routes the Sherpa transcription path through that same preference, and makes the Windows desktop dev stack recover instead of demanding Task Manager. Refreshes the Tauri and npm dependency pins that went with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S | 8 天前 | |
feat(dictation): model picker in the engine quick-switch, and a recoverable Windows dev stack Adds the sherpa-onnx dictation model picker under the Transcription engine row so the model the hotkey loads is switchable without opening Settings, routes the Sherpa transcription path through that same preference, and makes the Windows desktop dev stack recover instead of demanding Task Manager. Refreshes the Tauri and npm dependency pins that went with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S | 8 天前 | |
fix: stop extracted AppImage before prod reset | 1 个月前 | |
fix(desktop): preflight Linux runtime dependencies (#1681) Fail before backend/window startup when Linux source hosts lack Enigo’s libxdo linker input or WebKit’s GStreamer audio sink. Print an exact distro package command, sync source-build docs, and lock the probes with deterministic tests. Closes #1680 Closes #1682 | 21 天前 | |
feat: add desktop toolchain PATH healing script and unit tests | 5 天前 | |
fix: close AppImage termination races | 1 个月前 | |
fix(dev): mata a arvore de processos do backend no Windows O supervisor faz `spawn("uv", ...)` e o uv sobe o uvicorn como filho dele. Windows não tem sinais: `child.kill()` vira TerminateProcess só no filho DIRETO, então matar o `uv` deixava o uvicorn neto vivo segurando a porta 3900. O spawn seguinte falhava com `[Errno 10048]`, o supervisor contava como crash, e três desses derrubavam a stack inteira de dev — inclusive o Vite, via `--kill-others-on-fail`. `killProcessTree` usa `taskkill /T` no win32 e mantém o envio de sinal no POSIX. Como o kill forçado devolve exit não-zero e sinal nulo, o reload que nós mesmos pedimos passaria por crash; isso é tratado olhando se o tree-kill de fato aconteceu, e não a plataforma — um crash de verdade durante um reload continua indo para a recuperação de crash (coberto por teste que já existia). | 9 天前 | |
test: run generated asset hook inside MSI fixture | 11 天前 | |
feat(demos): ship the demo audio and video the app already advertises (#1517) * feat(demos): ship the demo audio and video the app already advertises Every demo asset in the app was a dead link on anything but a Mac. `personalities.py` has carried a `preview_url` for each of the seven voice-design presets since they were added; DictationDemo.jsx posts three bundled WAVs to /transcribe so the feature can be shown without microphone permission; the Dub workspace reads a manifest and plays a source video plus four dubbed languages. None of those files were committed, because the tooling that renders them (scripts/build_demos.sh, scripts/build_dub_demo.sh) hard- requires macOS `say` — it even carries a `TODO: add espeak-ng path for Linux contributors`. So the presets returned 404, the replay buttons did nothing, and the dubbing demo never loaded. Rendered with VoiceStudio's own engine, which runs wherever the app does: - 7 voice-design previews (2.2 MB) - 3 dictation replay clips (1.1 MB) — verified by transcribing them back: the conversational and French clips round-trip exactly - dubbing demo: source + 4 dubbed videos with subtitles and manifest (9.6 MB) Tooling fixes this turned up: - build_dub_demo.sh wrote to backend/assets/demo/dubbing, but main.py mounts backend/assets/samples at /demo_audio — so the frontend's /demo_audio/demo/dubbing/manifest.json could never have resolved even after a successful Mac build. Output moved under the mount. - `say` is now the fallback rather than the requirement: the new scripts/render_dub_demo_audio.py renders the five tracks with the engine and the shell script picks them up. - The five demo paragraphs lived in two files. They are now one JSON both read — two copies is one edit away from a video whose subtitles disagree with it. - render_demos_omnivoice.py peak-normalized, which a single-sample transient defeats: the Helpdesk preset landed at -30 dB RMS against -17 dB for its neighbours, so the preview row played at wildly different volumes. Now EBU R128 at -18 LUFS with a -1.5 dBTP ceiling. - …and pinning the output rate, because loudnorm resamples to 192 kHz internally and writes there unless told otherwise, which turned 2.1 MB of previews into 17.5 MB of identical-sounding audio. - update_manifest() looked for a manifest at a path nothing writes, so it always printed "not found" and did nothing. - Dictation is rendered here now too. It was excluded on the grounds that `say` was good enough and engine TTS was overkill — true only on macOS. tests/test_demo_assets_exist.py resolves every advertised URL against the directory main.py actually mounts, and checks each dubbing subtitle matches the script its manifest entry claims. A missing static file is not an import error and not a failing request; nothing would have caught this otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the demo-asset entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): watermark rendered demo audio, and harden the render scripts Review findings on #1517: - Greptile P1: the renderers wrote engine output straight to disk, so a re-render shipped demo audio with no provenance mark. These clips play back to users as VoiceStudio output — they are synthetic audio leaving the app like any other, and now go through mark_synthetic (#1169), the one chokepoint every producing route uses. It runs on the file AFTER loudnorm, since loudnorm re-encodes what it is handed, and says so loudly when marking is unavailable rather than committing an unmarked asset. The dubbing renderer shares the same helper. - CodeRabbit: build_dub_demo.sh checked only source.src.wav before deciding it could run without macOS `say`, so a Linux or Windows run with four of five tracks present reached a missing one, called `say`, and left a half-built bundle. It now requires all five. - CodeRabbit: shutil.move over an existing path delegates to os.rename, which raises FileExistsError on Windows — os.replace overwrites atomically everywhere. - CodeRabbit: the preview test discovered presets in a parametrize argument, importing app code at collection time and leaving core.personalities in sys.modules for later tests. Discovery moved into the test body. CI: the rendered dub bundle's zh/ja subtitles, its manifest and the script source are dubbing CONTENT, not UI strings — allowlisted in test_no_hardcoded_cjk.py with that justification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): a render that cannot be watermarked fails instead of warning CodeRabbit and Greptile, #1517: mark_synthetic degrades rather than raising — correct for generation, wrong for a render script, whose whole job is to produce files a human then commits. A printed warning on a scrolling console is not a gate, so both scripts exited 0 with unmarked assets sitting on disk ready to commit. They now raise, with the reason and the fix; OMNIVOICE_DEMO_ALLOW_UNMARKED=1 stays for a local listen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: stop a flaky dependency fetch from failing green runs en-core-web-sm resolves to a direct GitHub release URL, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own three retries all land within the same few seconds and fail together, so the whole job dies on a dependency that has nothing to do with the change under test — it cost #1518 and #1517 an otherwise-green run tonight. Two changes: back off between whole `uv sync` attempts, which is what actually clears it, and pass --no-sync to the pytest steps. `uv run` re-resolves the environment before running, so every test step was a fresh chance to hit the same fetch even though the install step had already synced — that is exactly how #1518 failed, in the isolated backend/tests step, with all 5467 tests already passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: one retry seam for every uv sync, not just the job that failed last en-core-web-sm resolves to a direct GitHub *release* URL rather than a package index, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own retries all land inside the same ~10 seconds and fail together, so a job dies on a dependency unrelated to the change under test. Tonight that cost four otherwise-green runs across #1515, #1517 and #1518 — and the first fix only covered the Tests job, so the next failure simply moved to Smoke (Linux), which syncs separately. The fetch is per-job, so the fix has to be per-job: scripts/uv-sync-retry.sh backs off between whole attempts (15s, 45s, 90s) and every workflow that syncs now goes through it — ci.yml (tests + the platform matrix), release.yml, security.yml, evals.yml. It still fails loudly after four attempts, so a genuinely broken lockfile is not disguised as a flake. The Tests job also lacked the UV_HTTP_TIMEOUT / UV_HTTP_RETRIES the smoke matrix has always set, which is part of why it was the one that kept dying; it has them now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ci): pin the Intel-Mac contract by intent, not by command spelling test_ci_verifies_intel_mac_as_the_documented_remote_only_host asserted the literal line `run: uv sync --extra pockettts`, so routing every sync through scripts/uv-sync-retry.sh read as a broken Intel-Mac contract. The contract it exists to protect is that the pockettts extra installs ONLY on backend_supported legs — which the regex now pins, while leaving how the sync is invoked free to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: keep every uv run out of the resolver, and bound the retry budget CodeRabbit, #1517: - `uv run` re-resolves before running, so the smoke suite, the worker-artifact tests, the release test run and the eval run were each a fresh chance to hit the flaky direct-URL fetch outside the retry loop. All of them pass --no-sync now; the environment is already synced by the step that owns the retries. security.yml's `uv run --with pip-audit` is deliberately left alone — it layers an ephemeral package rather than running the project's own tests. - The retry count multiplied uv's own budget (UV_HTTP_RETRIES=5 with a 120 s timeout on the smoke matrix). Three attempts and 60 s of total backoff outlast the refusals actually observed while staying well inside the jobs' timeout-minutes. - The Intel-Mac contract test pinned the smoke command literally too, so --no-sync tripped it exactly like the sync line did. Same fix: assert the contract (smoke runs only on backend_supported legs), not its spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
feat(startup): bind the socket in ~1s and narrate startup step by step (#1550) * feat(startup): bind the socket in ~1s and narrate startup step by step The structural fix for the "can't reach the local backend" class (~1 in 5 of every issue ever filed): uvicorn served nothing until torch import (10-20s cold), the 30-router fan-out, an import-time DB migration, the cuDNN preload, and alembic all finished — every slow or fragile step rendered as an unexplained dead backend. main.py now keeps module scope fast and defers the heavy work: - _phase_a_build (executor thread): prefs/env restore + #963 migration, yt-dlp overlay, cuDNN preload, torchaudio, model_manager, router imports — order preserved, literal imports so PyInstaller still traces. - _phase_a_finalize (event loop, no awaits → atomic wrt requests): include_router, mounts, MCP, SPA, openapi bust. - _phase_b: the old lifespan startup body; handles on app.state so shutdown survives a startup that never finished. - Eager mode (pytest / OMNIVOICE_EAGER_INIT=1) runs everything at import — byte-equivalent behavior for the ~100 lifespan-less TestClient sites and for embedders (dump_api_routes, probe boot runner opt in). While starting: /health answers 503 with the current step, new /startup/progress serves the full ledger (always 200), and StartupGateMiddleware 503s everything else with the [starting] marker (same skip-the-Report-button convention as [shutting_down]). A deferred failure keeps import-crash semantics: traceback to stderr → shell crash forensics, run sentinel stays uncleared, exit 1 names the failed step. Shell: startup_progress() probe (marker-header-gated so a foreign responder can't narrate the splash) feeds per-step log lines into the launch poll and the supervisor's reconnect wait. --health-check absorbs the deferred init (60→180s); --diagnose runs Phase A up front so it still sees restored prefs. Docker HEALTHCHECK semantics unchanged (curl -f fails on 503 exactly as it did on connection-refused). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(startup): join the Phase A thread on shutdown; async fail-path sleep Bot-review harvest on #1550: cancelling the deferred-startup task cannot stop the executor thread inside Phase A's blocking imports — shutdown now waits (bounded, only when a build started and hasn't finished) on a thread-completion event so interpreter teardown can't race a mid-import (#1000 class). The failure path's last-poll beat is now awaited, not time.sleep — a blocking sleep froze the very loop that beat exists to let serve. Also: dump_api_routes forces eager (assignment, not setdefault), and the integration test's child gets DEVNULL instead of an undrained pipe that could wedge a cold boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(startup): close the Phase A submission race; CodeQL nits Review finds on #1550: shutdown could sample _phase_a_started unset while the executor callable was queued-but-not-running, skipping the thread join. started is now set BEFORE submission, the submission is shielded so a cancel can't strand a queued callable that would never set _phase_a_finished, and the wrapper sets finished on every exit including the already-built early return. Contract pinned by test_phase_a_thread_join_contract. Plus explanatory comments on the new bare excepts and a consistent return in the gate's websocket branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 1 个月前 | |
feat(workers): remote GPU workers over a versioned gRPC protocol Send individual jobs to GPUs on your other machines while everything else stays local. Opt-in, off by default: with the toggle off there is no listening socket, no certificate and no background loop. Design follows remote/goal_v2.md, the council-revised goal doc. The decisions that shaped the code, and why: * A disconnect is an unknown outcome, not a failure. The original design reassigned on disconnect while also describing the case where the worker had already finished — following both guarantees duplicate execution. An attempt now holds a grace window; a worker returning inside it commits its result and no second attempt is ever made. * At-least-once execution, exactly-once result commit. The result is persisted BEFORE it is acknowledged, so a crash between the two cannot silently lose a finished render. * Deadlines are phased (accept -> model load -> execute -> deliver) and liveness is a progress lease. The old fixed 30s execution budget was two orders of magnitude below what this product actually does; silence is the failure signal, not slowness. * Capacity is derived from free VRAM, never configured: a static value corrupts output under torch.compile thread affinity (#315) and aborts the process on small cards (#567). * A circuit breaker replaces the reliability-score/quarantine machinery, which had no recovery path (no probation workload exists in a TTS product) and penalised consumer networks for existing. * Identity is a keypair the worker generates and never sends. A server-assigned id is a name, not an authenticator, so revocation of one would be theatre. Enrollment tokens are single-use and carry the control plane's certificate fingerprint for pin-on-first-use. Adds the domain core, scheduler, durable task store, gRPC transport, worker agent, management API, Settings panel, and docs. Protobuf reserves the tenant/trace/usage fields a hosted control plane would need, since adding them later means upgrading a whole fleet. Includes tests for the failure paths that matter: duplicate delivery, stale-session fencing, reconnect reconciliation, grace expiry, breaker attribution, and a real end-to-end TLS round trip. | 1 个月前 | |
fix(appimage): ship the compatibility launcher | 1 个月前 | |
test(appimage): verify packaged WebKit marker | 1 个月前 | |
chore(release): prepare v0.5.2 (#1761) Synchronize VoiceStudio release metadata, lockfiles, installers, container references, documentation, and the dated v0.5.2 changelog after all planned fixes landed. | 16 天前 | |
chore(release): prepare v0.5.2 (#1761) Synchronize VoiceStudio release metadata, lockfiles, installers, container references, documentation, and the dated v0.5.2 changelog after all planned fixes landed. | 16 天前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
feat: implement streaming TTS, A/B voice comparison, and background task processing with SSE updates | 5 个月前 | |
chore: update project assets, documentation, and backend services across voice-pro and voicebox repositories | 4 个月前 | |
Prepare Electron desktop releases and final Tauri sunset workflow | 1 天前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
fix(windows): preserve registry separators during template expansion | 11 天前 | |
feat(demos): ship the demo audio and video the app already advertises (#1517) * feat(demos): ship the demo audio and video the app already advertises Every demo asset in the app was a dead link on anything but a Mac. `personalities.py` has carried a `preview_url` for each of the seven voice-design presets since they were added; DictationDemo.jsx posts three bundled WAVs to /transcribe so the feature can be shown without microphone permission; the Dub workspace reads a manifest and plays a source video plus four dubbed languages. None of those files were committed, because the tooling that renders them (scripts/build_demos.sh, scripts/build_dub_demo.sh) hard- requires macOS `say` — it even carries a `TODO: add espeak-ng path for Linux contributors`. So the presets returned 404, the replay buttons did nothing, and the dubbing demo never loaded. Rendered with VoiceStudio's own engine, which runs wherever the app does: - 7 voice-design previews (2.2 MB) - 3 dictation replay clips (1.1 MB) — verified by transcribing them back: the conversational and French clips round-trip exactly - dubbing demo: source + 4 dubbed videos with subtitles and manifest (9.6 MB) Tooling fixes this turned up: - build_dub_demo.sh wrote to backend/assets/demo/dubbing, but main.py mounts backend/assets/samples at /demo_audio — so the frontend's /demo_audio/demo/dubbing/manifest.json could never have resolved even after a successful Mac build. Output moved under the mount. - `say` is now the fallback rather than the requirement: the new scripts/render_dub_demo_audio.py renders the five tracks with the engine and the shell script picks them up. - The five demo paragraphs lived in two files. They are now one JSON both read — two copies is one edit away from a video whose subtitles disagree with it. - render_demos_omnivoice.py peak-normalized, which a single-sample transient defeats: the Helpdesk preset landed at -30 dB RMS against -17 dB for its neighbours, so the preview row played at wildly different volumes. Now EBU R128 at -18 LUFS with a -1.5 dBTP ceiling. - …and pinning the output rate, because loudnorm resamples to 192 kHz internally and writes there unless told otherwise, which turned 2.1 MB of previews into 17.5 MB of identical-sounding audio. - update_manifest() looked for a manifest at a path nothing writes, so it always printed "not found" and did nothing. - Dictation is rendered here now too. It was excluded on the grounds that `say` was good enough and engine TTS was overkill — true only on macOS. tests/test_demo_assets_exist.py resolves every advertised URL against the directory main.py actually mounts, and checks each dubbing subtitle matches the script its manifest entry claims. A missing static file is not an import error and not a failing request; nothing would have caught this otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the demo-asset entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): watermark rendered demo audio, and harden the render scripts Review findings on #1517: - Greptile P1: the renderers wrote engine output straight to disk, so a re-render shipped demo audio with no provenance mark. These clips play back to users as VoiceStudio output — they are synthetic audio leaving the app like any other, and now go through mark_synthetic (#1169), the one chokepoint every producing route uses. It runs on the file AFTER loudnorm, since loudnorm re-encodes what it is handed, and says so loudly when marking is unavailable rather than committing an unmarked asset. The dubbing renderer shares the same helper. - CodeRabbit: build_dub_demo.sh checked only source.src.wav before deciding it could run without macOS `say`, so a Linux or Windows run with four of five tracks present reached a missing one, called `say`, and left a half-built bundle. It now requires all five. - CodeRabbit: shutil.move over an existing path delegates to os.rename, which raises FileExistsError on Windows — os.replace overwrites atomically everywhere. - CodeRabbit: the preview test discovered presets in a parametrize argument, importing app code at collection time and leaving core.personalities in sys.modules for later tests. Discovery moved into the test body. CI: the rendered dub bundle's zh/ja subtitles, its manifest and the script source are dubbing CONTENT, not UI strings — allowlisted in test_no_hardcoded_cjk.py with that justification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): a render that cannot be watermarked fails instead of warning CodeRabbit and Greptile, #1517: mark_synthetic degrades rather than raising — correct for generation, wrong for a render script, whose whole job is to produce files a human then commits. A printed warning on a scrolling console is not a gate, so both scripts exited 0 with unmarked assets sitting on disk ready to commit. They now raise, with the reason and the fix; OMNIVOICE_DEMO_ALLOW_UNMARKED=1 stays for a local listen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: stop a flaky dependency fetch from failing green runs en-core-web-sm resolves to a direct GitHub release URL, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own three retries all land within the same few seconds and fail together, so the whole job dies on a dependency that has nothing to do with the change under test — it cost #1518 and #1517 an otherwise-green run tonight. Two changes: back off between whole `uv sync` attempts, which is what actually clears it, and pass --no-sync to the pytest steps. `uv run` re-resolves the environment before running, so every test step was a fresh chance to hit the same fetch even though the install step had already synced — that is exactly how #1518 failed, in the isolated backend/tests step, with all 5467 tests already passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: one retry seam for every uv sync, not just the job that failed last en-core-web-sm resolves to a direct GitHub *release* URL rather than a package index, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own retries all land inside the same ~10 seconds and fail together, so a job dies on a dependency unrelated to the change under test. Tonight that cost four otherwise-green runs across #1515, #1517 and #1518 — and the first fix only covered the Tests job, so the next failure simply moved to Smoke (Linux), which syncs separately. The fetch is per-job, so the fix has to be per-job: scripts/uv-sync-retry.sh backs off between whole attempts (15s, 45s, 90s) and every workflow that syncs now goes through it — ci.yml (tests + the platform matrix), release.yml, security.yml, evals.yml. It still fails loudly after four attempts, so a genuinely broken lockfile is not disguised as a flake. The Tests job also lacked the UV_HTTP_TIMEOUT / UV_HTTP_RETRIES the smoke matrix has always set, which is part of why it was the one that kept dying; it has them now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ci): pin the Intel-Mac contract by intent, not by command spelling test_ci_verifies_intel_mac_as_the_documented_remote_only_host asserted the literal line `run: uv sync --extra pockettts`, so routing every sync through scripts/uv-sync-retry.sh read as a broken Intel-Mac contract. The contract it exists to protect is that the pockettts extra installs ONLY on backend_supported legs — which the regex now pins, while leaving how the sync is invoked free to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: keep every uv run out of the resolver, and bound the retry budget CodeRabbit, #1517: - `uv run` re-resolves before running, so the smoke suite, the worker-artifact tests, the release test run and the eval run were each a fresh chance to hit the flaky direct-URL fetch outside the retry loop. All of them pass --no-sync now; the environment is already synced by the step that owns the retries. security.yml's `uv run --with pip-audit` is deliberately left alone — it layers an ephemeral package rather than running the project's own tests. - The retry count multiplied uv's own budget (UV_HTTP_RETRIES=5 with a 120 s timeout on the smoke matrix). Three attempts and 60 s of total backoff outlast the refusals actually observed while staying well inside the jobs' timeout-minutes. - The Intel-Mac contract test pinned the smoke command literally too, so --no-sync tripped it exactly like the sync line did. Same fix: assert the contract (smoke runs only on backend_supported legs), not its spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
feat(demos): ship the demo audio and video the app already advertises (#1517) * feat(demos): ship the demo audio and video the app already advertises Every demo asset in the app was a dead link on anything but a Mac. `personalities.py` has carried a `preview_url` for each of the seven voice-design presets since they were added; DictationDemo.jsx posts three bundled WAVs to /transcribe so the feature can be shown without microphone permission; the Dub workspace reads a manifest and plays a source video plus four dubbed languages. None of those files were committed, because the tooling that renders them (scripts/build_demos.sh, scripts/build_dub_demo.sh) hard- requires macOS `say` — it even carries a `TODO: add espeak-ng path for Linux contributors`. So the presets returned 404, the replay buttons did nothing, and the dubbing demo never loaded. Rendered with VoiceStudio's own engine, which runs wherever the app does: - 7 voice-design previews (2.2 MB) - 3 dictation replay clips (1.1 MB) — verified by transcribing them back: the conversational and French clips round-trip exactly - dubbing demo: source + 4 dubbed videos with subtitles and manifest (9.6 MB) Tooling fixes this turned up: - build_dub_demo.sh wrote to backend/assets/demo/dubbing, but main.py mounts backend/assets/samples at /demo_audio — so the frontend's /demo_audio/demo/dubbing/manifest.json could never have resolved even after a successful Mac build. Output moved under the mount. - `say` is now the fallback rather than the requirement: the new scripts/render_dub_demo_audio.py renders the five tracks with the engine and the shell script picks them up. - The five demo paragraphs lived in two files. They are now one JSON both read — two copies is one edit away from a video whose subtitles disagree with it. - render_demos_omnivoice.py peak-normalized, which a single-sample transient defeats: the Helpdesk preset landed at -30 dB RMS against -17 dB for its neighbours, so the preview row played at wildly different volumes. Now EBU R128 at -18 LUFS with a -1.5 dBTP ceiling. - …and pinning the output rate, because loudnorm resamples to 192 kHz internally and writes there unless told otherwise, which turned 2.1 MB of previews into 17.5 MB of identical-sounding audio. - update_manifest() looked for a manifest at a path nothing writes, so it always printed "not found" and did nothing. - Dictation is rendered here now too. It was excluded on the grounds that `say` was good enough and engine TTS was overkill — true only on macOS. tests/test_demo_assets_exist.py resolves every advertised URL against the directory main.py actually mounts, and checks each dubbing subtitle matches the script its manifest entry claims. A missing static file is not an import error and not a failing request; nothing would have caught this otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the demo-asset entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): watermark rendered demo audio, and harden the render scripts Review findings on #1517: - Greptile P1: the renderers wrote engine output straight to disk, so a re-render shipped demo audio with no provenance mark. These clips play back to users as VoiceStudio output — they are synthetic audio leaving the app like any other, and now go through mark_synthetic (#1169), the one chokepoint every producing route uses. It runs on the file AFTER loudnorm, since loudnorm re-encodes what it is handed, and says so loudly when marking is unavailable rather than committing an unmarked asset. The dubbing renderer shares the same helper. - CodeRabbit: build_dub_demo.sh checked only source.src.wav before deciding it could run without macOS `say`, so a Linux or Windows run with four of five tracks present reached a missing one, called `say`, and left a half-built bundle. It now requires all five. - CodeRabbit: shutil.move over an existing path delegates to os.rename, which raises FileExistsError on Windows — os.replace overwrites atomically everywhere. - CodeRabbit: the preview test discovered presets in a parametrize argument, importing app code at collection time and leaving core.personalities in sys.modules for later tests. Discovery moved into the test body. CI: the rendered dub bundle's zh/ja subtitles, its manifest and the script source are dubbing CONTENT, not UI strings — allowlisted in test_no_hardcoded_cjk.py with that justification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): a render that cannot be watermarked fails instead of warning CodeRabbit and Greptile, #1517: mark_synthetic degrades rather than raising — correct for generation, wrong for a render script, whose whole job is to produce files a human then commits. A printed warning on a scrolling console is not a gate, so both scripts exited 0 with unmarked assets sitting on disk ready to commit. They now raise, with the reason and the fix; OMNIVOICE_DEMO_ALLOW_UNMARKED=1 stays for a local listen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: stop a flaky dependency fetch from failing green runs en-core-web-sm resolves to a direct GitHub release URL, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own three retries all land within the same few seconds and fail together, so the whole job dies on a dependency that has nothing to do with the change under test — it cost #1518 and #1517 an otherwise-green run tonight. Two changes: back off between whole `uv sync` attempts, which is what actually clears it, and pass --no-sync to the pytest steps. `uv run` re-resolves the environment before running, so every test step was a fresh chance to hit the same fetch even though the install step had already synced — that is exactly how #1518 failed, in the isolated backend/tests step, with all 5467 tests already passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: one retry seam for every uv sync, not just the job that failed last en-core-web-sm resolves to a direct GitHub *release* URL rather than a package index, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own retries all land inside the same ~10 seconds and fail together, so a job dies on a dependency unrelated to the change under test. Tonight that cost four otherwise-green runs across #1515, #1517 and #1518 — and the first fix only covered the Tests job, so the next failure simply moved to Smoke (Linux), which syncs separately. The fetch is per-job, so the fix has to be per-job: scripts/uv-sync-retry.sh backs off between whole attempts (15s, 45s, 90s) and every workflow that syncs now goes through it — ci.yml (tests + the platform matrix), release.yml, security.yml, evals.yml. It still fails loudly after four attempts, so a genuinely broken lockfile is not disguised as a flake. The Tests job also lacked the UV_HTTP_TIMEOUT / UV_HTTP_RETRIES the smoke matrix has always set, which is part of why it was the one that kept dying; it has them now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ci): pin the Intel-Mac contract by intent, not by command spelling test_ci_verifies_intel_mac_as_the_documented_remote_only_host asserted the literal line `run: uv sync --extra pockettts`, so routing every sync through scripts/uv-sync-retry.sh read as a broken Intel-Mac contract. The contract it exists to protect is that the pockettts extra installs ONLY on backend_supported legs — which the regex now pins, while leaving how the sync is invoked free to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: keep every uv run out of the resolver, and bound the retry budget CodeRabbit, #1517: - `uv run` re-resolves before running, so the smoke suite, the worker-artifact tests, the release test run and the eval run were each a fresh chance to hit the flaky direct-URL fetch outside the retry loop. All of them pass --no-sync now; the environment is already synced by the step that owns the retries. security.yml's `uv run --with pip-audit` is deliberately left alone — it layers an ephemeral package rather than running the project's own tests. - The retry count multiplied uv's own budget (UV_HTTP_RETRIES=5 with a 120 s timeout on the smoke matrix). Three attempts and 60 s of total backoff outlast the refusals actually observed while staying well inside the jobs' timeout-minutes. - The Intel-Mac contract test pinned the smoke command literally too, so --no-sync tripped it exactly like the sync line did. Same fix: assert the contract (smoke runs only on backend_supported legs), not its spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
fix(worker): address legacy transport review findings | 1 个月前 | |
Phase 3 Plan 03-01: Supertonic-3 engine on SubprocessBackend (#101) * Phase 3 Plan 03-01: Supertonic-3 engine on SubprocessBackend Adds Supertonic-3 as a 7th opt-in TTS engine on the Phase 2 SubprocessBackend primitive. Closes TTS-01..06 (REQUIREMENTS.md): * TTS-01 — _REGISTRY["supertonic3"] resolves to Supertonic3Backend, a SubprocessBackend subclass. * TTS-02 — `supertonic==1.3.1` lives under [project.optional-dependencies]; default `uv sync --no-dev` does NOT install it. Exactly one `onnxruntime` row in `uv pip list` after `--extra supertonic`. * TTS-03 — Model revision pinned by 40-char commit SHA (724fb5abbf5502583fb520898d45929e62f02c0b — the "Initial Supertonic 3 release" SHA, same as the SDK's own pin). Resolver script for intentional bumps: scripts/resolve_supertonic3_sha.py. * TTS-04 — Honest CPU-only reporting. `is_available()` message says "ready (CPU-only via onnxruntime)" and never mentions "cuda" or "mps". `gpu_compat = ("cpu",)`. * TTS-05 — License gate via settings_store helpers (get/set_license_accepted) + Loopback-only /api/settings/license endpoint + SupertonicLicenseDialog frontend modal showing MIT (code) and OpenRAIL-M (model). Wired into EngineCompatibilityMatrix as an "Accept license" button on rows whose `reason` mentions "license not accepted". * TTS-06 — 3 langs (en/ja/ru) × 3 sec smoke test in tests/test_supertonic3.py::test_smoke_3langs_3sec (OMNIVOICE_SMOKE-gated; asserts no onnxruntime-gpu row post-synthesize). Package legitimacy gate (Task 1 in plan): supertonic on PyPI verified to be published by Supertone Inc. (ato@supertone.ai), repo github.com/supertone-inc/supertonic, wheel is pure-Python with no postinstall scripts. Same publisher ships supertonic-js on npm under the same maintainer email. Test results: * tests/test_supertonic3.py — 10 passed, 3 skipped (network-gated). * tests/smoke/ — 4 passed. * tests/ (full, --ignore=tests/manual) — 412 passed, 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(tests): uv sync --all-extras so optional-engine tests can import their package Phase 3 added `supertonic` as an optional dependency. The CI Tests job runs `uv sync` (no extras), so `test_cpu_only_honest` and `test_license_gate` in tests/test_supertonic3.py hit the "supertonic package not installed" fallback instead of the real import path, and fail. Bare `uv sync` is the right default for users (engines are opt-in), but the test environment should exercise the full surface. `--all-extras` keeps the smoke job lean (still bare `uv sync`) while letting Tests verify the integrated behavior of every optional engine. Future-proofs against the same failure mode in Phase 4 (GGUF) and any later optional engines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | 3 个月前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
chore: retire finished planning archives from the repo root (#1095) Removes ~110 files of process noise (all preserved in git history): .planning/ (GSD-era phases/quick-plans/issue-clusters; workflow retired 2026-07-08), specs/ (spec-kit specs for shipped features 001-007), design/ (pre-React ASCII mockups), research/ (legacy Gradio archive), and .agents/ (rules for a third-party agent tool no longer in use). The four load-bearing decision docs move to docs/adr/ with an archival note; every live pointer follows (gguf engine module docs + quant_map, inject-apprun.sh, pyproject/test comments, fixture README + its seed script — kept byte-identical). The CJK allowlist drops the deleted legacy_gradio entries; STRUCTURE.md and ROADMAP.md document the removal instead of linking into it. Backend suite: 2891 passed. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
docs: refresh STRUCTURE.md to match the tree as it is today STRUCTURE.md still described the April layout: it was missing backend/engines, worker, mcp_shim, speech_client, migrations, plugins, hooks and config; the frontend e2e suites, i18n and src-tauri packaging inputs; and the bin, skills, .agents/skills, notebooks, omnivoice-gallery and .github/workflows top-level entries. Stale docs are bugs. Three corrections beyond the missing entries: - "all tests live here, no exceptions" was wrong. There are three homes (tests/, backend/tests/, co-located vitest) and the split is deliberate: pyproject testpaths, a separate ci.yml job, and the sys.modules-stub hazard documented in backend/tests/conftest.py. Replaced the claim with a table that records why each home exists. - .env.example does not exist and the app never reads a repo-local .env; the durable user env file is ~/.config/omnivoice/env (backend/core/user_env.py), written by the Settings panel. - .agents/ was listed as deleted, but it is back with a different job: the canonical skill copies pinned by skills-lock.json. Also fixes the dead blob/main/STRUCTURE.md URL in the backlink script -- the file has lived in docs/ since the cleanup pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LDyC6prbjFydox9XQGhyny | 8 天前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
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. | 21 天前 | |
Phase 4 Plan 04-01: SPIKE-01 GGUF — GO + integration (#100) * Phase 4 Plan 04-01: SPIKE-01 GGUF — GO + Wave 1 integration Integrates Serveurperso/OmniVoice-GGUF as a hardware-adaptive default voice-cloning engine, with overridable fallback to the in-process OmniVoiceBackend. Spike confirmed GO: the model is a clean quantization of k2-fsa/OmniVoice (Apache-2.0 + MIT runtime, `omnivoice-lm` custom architecture so it does NOT load in vanilla llama.cpp). Pinned SHAs: * Serveurperso/OmniVoice-GGUF revision: 361609388ae572a820d085185bbbe2a2aac4b30e * ServeurpersoCom/omnivoice.cpp master: 886fc079838ca7400cb2b42b36e2a65aa1daabe8 Implements GGUF-01 (hardware probe) through GGUF-05 (default-engine resolver with graceful fallback). The four `bin/omnivoice-tts-*` artifacts are committed as zero-byte placeholders; the new CI matrix job builds the real binaries per platform from the pinned commit SHA and appends a SHA-256 manifest used by `is_available()` for tampering detection (T-04-01). The macos-14 (Apple Silicon) slot is marked `continue-on-error: true` because omnivoice.cpp publishes no `buildmetal.sh` (Pitfall 1 / Assumption A1) — failure feeds into Task 3's GO/NO-GO call. Quant override is allow-listed against quant_map.json entries only (T-04-05). Argv is composed from typed Path objects rooted in HF_HUB_CACHE; never uses `shell=True`. HF token redaction applies to captured stderr before logging (AUTH-05 / T-04-04). Tests: 36 new (8 hardware-probe + 13 GGUF engine + 6 settings_store quant override + grep gate); 428 passed in full suite vs 402+ baseline. ADR Status stays "Proposed (research-supported)" — Task 3 (human checkpoint) flips to Accepted after CI produces real binaries and a reviewer signs off on the GGUF-06 cross-hardware smoke. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: install libopenblas-dev on linux-x86_64 omnivoice-tts build The pinned omnivoice.cpp commit (886fc079...) ships a `buildcpu.sh` that passes `-DGGML_BLAS=ON`. ubuntu-latest has no BLAS implementation preinstalled, so the cmake configure step fails with `Could NOT find BLAS (missing: BLAS_LIBRARIES)` and the job exits in 13 s before producing the linux-x86_64 binary. macOS (Accelerate, built in) and Windows (BLAS off by default in the ggml CMakeLists for non-APPLE platforms — the build script doesn't invoke buildcpu.sh on those slots) are unaffected and stay green. Adds a Linux-gated apt step to install libopenblas-dev + pkg-config before the build, restoring cross-platform parity per the CLAUDE.md "default features must work on every platform" rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(gguf): constrain ref_audio to project roots — block /etc/shadow on Linux The GGUF engine's `_build_argv` previously validated ref_audio only via `ref_path.is_file()` — i.e. "does this path exist?" That check is platform-dependent: `/etc/shadow` doesn't exist on macOS (rejected naturally), but it IS a real system file on Linux, so the validation silently accepted it. CI's ubuntu-22.04 runner exposed the gap via `test_generate_blocks_freeform_ref_audio`, which exists precisely to guard the "freeform ref_audio path" attack surface. Fix: confine ref_audio to one of three allowed roots before existence checks: - VOICES_DIR (user-saved voice profiles) - DUB_DIR (per-job auto-clones extracted from source video) - tempfile.gettempdir() (browser-upload temp files; existing `cleanup_ref` flow in generation.py) Anything outside those roots → FileNotFoundError, matching the existing failure-mode contract callers handle. Existence check still runs after, so the test's mocked subprocess.run is never reached and the test passes deterministically on all three platforms. Cross-platform parity (per CLAUDE.md 2026-05-20 rule): identical behaviour on macOS / Windows / Linux — the allow-list is computed from core.config which uses platform-specific path resolution but yields the same logical "project tree" on every OS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(gguf): mark darwin-x86_64 binary build as experimental GitHub's macos-13 (Intel) runner pool is heavily contended — PR #100 queued for 30+ minutes waiting on darwin-x86_64 while every other platform finished in ~1m. Intel Macs are also fading hardware (Apple's platform momentum is entirely on Apple Silicon), and the GGUF engine's runtime already handles a missing binary gracefully (`is_available()` returns False on Intel Mac with a "binary not bundled for this platform" message, same path used for first-launch before any binaries build). `experimental: true` mirrors what darwin-arm64 (Metal) already has — slot still runs and uploads its binary when successful, but a failure or runner backlog no longer blocks merges. Keeps the GGUF engine shippable across the dominant arm64 / Linux / Windows surface without holding the inbox on a slow-runner queue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | 3 个月前 | |
fix(ci): prepare hosted Windows policy for non-admin MSI smoke | 10 天前 | |
fix(dictation): stop the widget stranding an empty square, repair the swept data dirs (#1398) The dictation hotkey could leave a blank dark square stuck on the desktop with no way to dismiss it. Three defects compounded: the tray listener's effect depended on [state], so it detached across an await on every state change and a press landing in that gap was lost; an idle pill renders null, so the window Rust had already shown was empty; and the opaque chrome background made that empty window a hard-edged square. Nothing could hide it — dismiss() is only reachable from the X button, Esc, or a post-session timer, none of which exist for a session that never started. Fixed at the invariant rather than the call sites: the listener subscribes once for the component's lifetime, the widget window's chrome background is transparent, and an idle-but-visible window reconciles itself to hidden. The reconcile is polled (a dropped press changes no React state, so there is nothing to key an effect off) and aborts if its effect is torn down mid-check, so it can never hide a dictation that has just started. Also in scope: - The rename sweep had repointed three data-dir literals at a brand-named directory that does not exist, so smoke-test.sh verified a directory the backend never writes and desktop-prod.sh silently stopped clearing backend state on Windows. Both invisible on macOS, where they are usually run. A guard test now pins the assignments specifically. - The dictation model picker's download sizes were wrong for all seven models, in both directions — Parakeet TDT v3 (the recommended default) understated 180 MB against an actual 670 MB, while the low-RAM fallbacks were overstated threefold, discouraging exactly the choice that would have helped. Measured from the published repos and pinned by a test. - The 0.6B Parakeet models now decode on more threads, capped by host cores and still overridable. - uninstall.ps1 gained a UTF-8 BOM (Windows PowerShell 5.1 mis-decodes its non-ASCII output without one), and sponsor.yml lost its last OmniVoice references. | 1 个月前 | |
feat(electron): add full VoiceStudio desktop app | 3 天前 | |
feat(electron): add full VoiceStudio desktop app | 3 天前 | |
fix(release): keep Preview ahead of Stable (#1763) Closes #1762. | 16 天前 | |
feat: implement structured progress tracking for model downloads and add local environment variable loading support. | 4 个月前 | |
fix(ci): prepare hosted Windows policy for non-admin MSI smoke | 10 天前 | |
i18n: backfill update-channel + auto-update keys across 18 languages (#202) PR #200 added 18 new locale files, but they predated #199 (auto-update badge + Stable/Preview channel toggle), so they were missing the `update.*` namespace (6 keys) and `about.channel_*` (5 keys) — those strings fell back to English in ar/de/es/fr/hi/id/it/ja/ko/nl/pl/pt/ru/sv/th/tr/uk/vi/zh-TW. Backfill all 11 keys in every one of those languages so the updater UI is fully localized. en.json / zh-CN.json already had them and are untouched. Placeholders ({{version}}, {{pct}}, {{channel}}) preserved verbatim; files re-emitted in the exact format scripts/translate_all.py writes (ensure_ascii=False, indent=2) so the diff is additions only (+13 lines/file, 0 deletions). Also fix scripts/translate_all.py: LOCALES_DIR was hardcoded to a contributor's absolute path (/Users/.../orca/...) — make it repo-relative so the generator actually runs for anyone. Verified: all 21 locales valid JSON + key-complete, placeholders intact; tsc clean; vitest 162/162; build OK; CJK guard passes (locales are the allowlisted translation layer). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> | 3 个月前 | |
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 | 27 天前 | |
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 | 27 天前 | |
feat(demos): ship the demo audio and video the app already advertises (#1517) * feat(demos): ship the demo audio and video the app already advertises Every demo asset in the app was a dead link on anything but a Mac. `personalities.py` has carried a `preview_url` for each of the seven voice-design presets since they were added; DictationDemo.jsx posts three bundled WAVs to /transcribe so the feature can be shown without microphone permission; the Dub workspace reads a manifest and plays a source video plus four dubbed languages. None of those files were committed, because the tooling that renders them (scripts/build_demos.sh, scripts/build_dub_demo.sh) hard- requires macOS `say` — it even carries a `TODO: add espeak-ng path for Linux contributors`. So the presets returned 404, the replay buttons did nothing, and the dubbing demo never loaded. Rendered with VoiceStudio's own engine, which runs wherever the app does: - 7 voice-design previews (2.2 MB) - 3 dictation replay clips (1.1 MB) — verified by transcribing them back: the conversational and French clips round-trip exactly - dubbing demo: source + 4 dubbed videos with subtitles and manifest (9.6 MB) Tooling fixes this turned up: - build_dub_demo.sh wrote to backend/assets/demo/dubbing, but main.py mounts backend/assets/samples at /demo_audio — so the frontend's /demo_audio/demo/dubbing/manifest.json could never have resolved even after a successful Mac build. Output moved under the mount. - `say` is now the fallback rather than the requirement: the new scripts/render_dub_demo_audio.py renders the five tracks with the engine and the shell script picks them up. - The five demo paragraphs lived in two files. They are now one JSON both read — two copies is one edit away from a video whose subtitles disagree with it. - render_demos_omnivoice.py peak-normalized, which a single-sample transient defeats: the Helpdesk preset landed at -30 dB RMS against -17 dB for its neighbours, so the preview row played at wildly different volumes. Now EBU R128 at -18 LUFS with a -1.5 dBTP ceiling. - …and pinning the output rate, because loudnorm resamples to 192 kHz internally and writes there unless told otherwise, which turned 2.1 MB of previews into 17.5 MB of identical-sounding audio. - update_manifest() looked for a manifest at a path nothing writes, so it always printed "not found" and did nothing. - Dictation is rendered here now too. It was excluded on the grounds that `say` was good enough and engine TTS was overkill — true only on macOS. tests/test_demo_assets_exist.py resolves every advertised URL against the directory main.py actually mounts, and checks each dubbing subtitle matches the script its manifest entry claims. A missing static file is not an import error and not a failing request; nothing would have caught this otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the demo-asset entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): watermark rendered demo audio, and harden the render scripts Review findings on #1517: - Greptile P1: the renderers wrote engine output straight to disk, so a re-render shipped demo audio with no provenance mark. These clips play back to users as VoiceStudio output — they are synthetic audio leaving the app like any other, and now go through mark_synthetic (#1169), the one chokepoint every producing route uses. It runs on the file AFTER loudnorm, since loudnorm re-encodes what it is handed, and says so loudly when marking is unavailable rather than committing an unmarked asset. The dubbing renderer shares the same helper. - CodeRabbit: build_dub_demo.sh checked only source.src.wav before deciding it could run without macOS `say`, so a Linux or Windows run with four of five tracks present reached a missing one, called `say`, and left a half-built bundle. It now requires all five. - CodeRabbit: shutil.move over an existing path delegates to os.rename, which raises FileExistsError on Windows — os.replace overwrites atomically everywhere. - CodeRabbit: the preview test discovered presets in a parametrize argument, importing app code at collection time and leaving core.personalities in sys.modules for later tests. Discovery moved into the test body. CI: the rendered dub bundle's zh/ja subtitles, its manifest and the script source are dubbing CONTENT, not UI strings — allowlisted in test_no_hardcoded_cjk.py with that justification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(demos): a render that cannot be watermarked fails instead of warning CodeRabbit and Greptile, #1517: mark_synthetic degrades rather than raising — correct for generation, wrong for a render script, whose whole job is to produce files a human then commits. A printed warning on a scrolling console is not a gate, so both scripts exited 0 with unmarked assets sitting on disk ready to commit. They now raise, with the reason and the fix; OMNIVOICE_DEMO_ALLOW_UNMARKED=1 stays for a local listen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: stop a flaky dependency fetch from failing green runs en-core-web-sm resolves to a direct GitHub release URL, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own three retries all land within the same few seconds and fail together, so the whole job dies on a dependency that has nothing to do with the change under test — it cost #1518 and #1517 an otherwise-green run tonight. Two changes: back off between whole `uv sync` attempts, which is what actually clears it, and pass --no-sync to the pytest steps. `uv run` re-resolves the environment before running, so every test step was a fresh chance to hit the same fetch even though the install step had already synced — that is exactly how #1518 failed, in the isolated backend/tests step, with all 5467 tests already passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: one retry seam for every uv sync, not just the job that failed last en-core-web-sm resolves to a direct GitHub *release* URL rather than a package index, and github.com intermittently answers `http2 error: refused stream before processing any application logic`. uv's own retries all land inside the same ~10 seconds and fail together, so a job dies on a dependency unrelated to the change under test. Tonight that cost four otherwise-green runs across #1515, #1517 and #1518 — and the first fix only covered the Tests job, so the next failure simply moved to Smoke (Linux), which syncs separately. The fetch is per-job, so the fix has to be per-job: scripts/uv-sync-retry.sh backs off between whole attempts (15s, 45s, 90s) and every workflow that syncs now goes through it — ci.yml (tests + the platform matrix), release.yml, security.yml, evals.yml. It still fails loudly after four attempts, so a genuinely broken lockfile is not disguised as a flake. The Tests job also lacked the UV_HTTP_TIMEOUT / UV_HTTP_RETRIES the smoke matrix has always set, which is part of why it was the one that kept dying; it has them now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ci): pin the Intel-Mac contract by intent, not by command spelling test_ci_verifies_intel_mac_as_the_documented_remote_only_host asserted the literal line `run: uv sync --extra pockettts`, so routing every sync through scripts/uv-sync-retry.sh read as a broken Intel-Mac contract. The contract it exists to protect is that the pockettts extra installs ONLY on backend_supported legs — which the regex now pins, while leaving how the sync is invoked free to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: keep every uv run out of the resolver, and bound the retry budget CodeRabbit, #1517: - `uv run` re-resolves before running, so the smoke suite, the worker-artifact tests, the release test run and the eval run were each a fresh chance to hit the flaky direct-URL fetch outside the retry loop. All of them pass --no-sync now; the environment is already synced by the step that owns the retries. security.yml's `uv run --with pip-audit` is deliberately left alone — it layers an ephemeral package rather than running the project's own tests. - The retry count multiplied uv's own budget (UV_HTTP_RETRIES=5 with a 120 s timeout on the smoke matrix). Three attempts and 60 s of total backoff outlast the refusals actually observed while staying well inside the jobs' timeout-minutes. - The Intel-Mac contract test pinned the smoke command literally too, so --no-sync tripped it exactly like the sync line did. Same fix: assert the contract (smoke runs only on backend_supported legs), not its spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
fix(ci): keep install-docs help cp1252-safe | 13 天前 | |
fix(appimage): stop shipping a dangling .DirIcon, and prove it in CI (#1518) * fix(appimage): stop shipping a dangling .DirIcon, and prove it in CI The Linux icon is blank because the AppImage's .DirIcon is an absolute symlink into the machine that built it. From the published v0.4.2: .DirIcon -> /home/runner/work/OmniVoice-Studio/OmniVoice-Studio/frontend/ src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/ appimage/OmniVoice Studio.AppDir/OmniVoice Studio.png That path exists on nobody's computer. The link dangles the moment the AppImage leaves CI, so file managers have no icon for the file, and the integration tools that read .DirIcon install nothing. A dangling symlink is not a build error — the bundle packs, runs, and passes every check we had — which is how it shipped for a whole release without anyone noticing. Locally built AppDirs are worse: both .DirIcon AND the root .desktop symlink come out absolute, so a from-source bundle has no readable desktop entry either, which is why the icon is missing in the menu and the dock too. - `.DirIcon` is now a real file, copied in through `appimage.files` — the same seam that already places the WebKitGTK marker. - `bundle.category` is set, so the generated desktop entry stops emitting an empty `Categories=`. That is not the same as omitting the key: desktop-file-validate rejects the entry and menu builders skip it. - verify-apprun-bundle.sh — already run against the extracted AppImage in the release job — now fails when .DirIcon is missing or resolves outside the bundle, when the .desktop entry does not resolve inside it, when Icon= names a file that is not at the AppImage root, or when Categories= is present but empty. Its unit test covers each of those, including the exact shape v0.4.2 shipped. The `.DirIcon` copy cannot be verified without a full release build, so the guard is the load-bearing part: the next release either passes it or fails loudly. It can no longer ship blank in silence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the AppImage icon entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): fold the AppImage icon fix into the existing Fixed section CodeRabbit (#1518): the Unreleased block must carry one `### Fixed` section of one-line entries. Merge the two entries in and drop the narrative and the version reference. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
fix(appimage): stop shipping a dangling .DirIcon, and prove it in CI (#1518) * fix(appimage): stop shipping a dangling .DirIcon, and prove it in CI The Linux icon is blank because the AppImage's .DirIcon is an absolute symlink into the machine that built it. From the published v0.4.2: .DirIcon -> /home/runner/work/OmniVoice-Studio/OmniVoice-Studio/frontend/ src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/ appimage/OmniVoice Studio.AppDir/OmniVoice Studio.png That path exists on nobody's computer. The link dangles the moment the AppImage leaves CI, so file managers have no icon for the file, and the integration tools that read .DirIcon install nothing. A dangling symlink is not a build error — the bundle packs, runs, and passes every check we had — which is how it shipped for a whole release without anyone noticing. Locally built AppDirs are worse: both .DirIcon AND the root .desktop symlink come out absolute, so a from-source bundle has no readable desktop entry either, which is why the icon is missing in the menu and the dock too. - `.DirIcon` is now a real file, copied in through `appimage.files` — the same seam that already places the WebKitGTK marker. - `bundle.category` is set, so the generated desktop entry stops emitting an empty `Categories=`. That is not the same as omitting the key: desktop-file-validate rejects the entry and menu builders skip it. - verify-apprun-bundle.sh — already run against the extracted AppImage in the release job — now fails when .DirIcon is missing or resolves outside the bundle, when the .desktop entry does not resolve inside it, when Icon= names a file that is not at the AppImage root, or when Categories= is present but empty. Its unit test covers each of those, including the exact shape v0.4.2 shipped. The `.DirIcon` copy cannot be verified without a full release build, so the guard is the load-bearing part: the next release either passes it or fails loudly. It can no longer ship blank in silence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): stamp the AppImage icon entries with their PR ref Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(changelog): fold the AppImage icon fix into the existing Fixed section CodeRabbit (#1518): the Unreleased block must carry one `### Fixed` section of one-line entries. Merge the two entries in and drop the narrative and the version reference. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio) Renames what users see. The app, the installers, the window title, the docs and all 21 locales now say VoiceStudio, with "(previously OmniVoice-Studio)" noted near the title of each doc surface so people recognise it. Deliberately NOT renamed, because renaming any of them silently breaks an existing install — there is no legacy-path fallback anywhere in this codebase: - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode, macOS TCC grants, managed venv, WebView localStorage, the single-instance lock) - data directories OmniVoice / .omnivoice and omnivoice.db - the ~150 OMNIVOICE_* environment variables - the X-OmniVoice-* HTTP headers (a wire protocol) - the published Docker image paths - the OmniVoice ENGINE, which is a model name and not this product tests/test_identity_paths_survive_the_rename.py pins every one of those so a future well-meaning sweep cannot orphan a user's library. Linux .deb users install a new package name and should apt remove omnivoice-studio; that note is in the changelog. | 1 个月前 | |
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. | 25 天前 | |
fix(installer): make WebView2 download opt-in | 18 天前 |