From 37d63ca7bf52b6baa2d3505b08baa07f3116eef4 Mon Sep 17 00:00:00 2001 From: MyButtermilk Date: Mon, 28 Sep 2026 11:10:00 +0200 Subject: [PATCH] Initialize meeting enhancement before connecting capture producers --- AGENTS.md | 2 ++ Frontend/package-lock.json | 4 ++-- Frontend/package.json | 2 +- Frontend/src-tauri/src/audio_sidecar.rs | 29 +++++++++++++++-------- docs/ARCHITECTURE.md | 2 ++ native/scriber-audio-sidecar/Cargo.lock | 2 +- native/scriber-audio-sidecar/Cargo.toml | 2 +- src/version.py | 2 +- tests/test_audio_worker_build_contract.py | 5 +++- 9 files changed, 33 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8c174095..b797540c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -388,6 +388,8 @@ Frontend and shell: Preserve the 16 kHz 256-sample hop adapter, one-hop delay compensation, bounded shared-timeline queue, and Stop/Pause/EOF tail flush. Model construction must emit no stdout because that channel carries the sidecar JSON protocol. + Initialize the model and connect output consumers before opening upstream + capture pipes; opening those pipes starts bounded producer writes. The token-protected Meeting device test must reuse this path, remain explicit and local-only, return only bounded level/activity statistics, and always stop its ephemeral sidecar capture without persisting or uploading PCM. Product diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 65afbcab..09c898f0 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "scriber", - "version": "0.5.126", + "version": "0.5.127", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scriber", - "version": "0.5.126", + "version": "0.5.127", "license": "MIT", "dependencies": { "@carrot-kpi/switzer-font": "^0.1.0", diff --git a/Frontend/package.json b/Frontend/package.json index 83acd026..c11fe79f 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -1,6 +1,6 @@ { "name": "scriber", - "version": "0.5.126", + "version": "0.5.127", "type": "module", "license": "MIT", "engines": { diff --git a/Frontend/src-tauri/src/audio_sidecar.rs b/Frontend/src-tauri/src/audio_sidecar.rs index 551d6214..46e9b3b5 100644 --- a/Frontend/src-tauri/src/audio_sidecar.rs +++ b/Frontend/src-tauri/src/audio_sidecar.rs @@ -3754,6 +3754,20 @@ fn run_meeting_aec_relay( .map(|handle| *handle as HANDLE) .collect(); let result = (|| -> Result<(), String> { + // Connecting upstream pipes starts bounded capture writes. Prepare the + // model and all consumers first so a cold model cannot fill those pipes. + let mut aec = if aec_enabled { + // Force the real pipe regression beyond its buffer/write deadline + // in tests; production performs no artificial wait. + #[cfg(test)] + thread::sleep(Duration::from_millis(1500)); + Some(MeetingEnhancer::new()?) + } else { + None + }; + for handle in &outputs { + wait_for_meeting_output_pipe_client(*handle, &stop_rx)?; + } let microphone = open_frame_pipe_reader(µphone_pipe)?; let system = match open_frame_pipe_reader(&system_pipe) { Ok(handle) => handle, @@ -3765,14 +3779,6 @@ fn run_meeting_aec_relay( } }; let processing = (|| -> Result<(), String> { - for handle in &outputs { - wait_for_meeting_output_pipe_client(*handle, &stop_rx)?; - } - let mut aec = if aec_enabled { - Some(MeetingEnhancer::new()?) - } else { - None - }; let mut relay_sequence = 0u64; let mut microphone_frame = Some(read_meeting_frame(microphone, &stop_rx)?); let mut system_frame = Some(read_meeting_frame(system, &stop_rx)?); @@ -6732,10 +6738,13 @@ mod tests { }) }) .collect(); + let capture_id = response["payload"]["meetingCaptureId"].as_str().unwrap(); for _ in 0..3 { - ready_rx.recv_timeout(Duration::from_secs(10)).unwrap(); + if let Err(error) = ready_rx.recv_timeout(Duration::from_secs(10)) { + let stopped = state.stop_meeting_capture(capture_id, "testFailureCleanup"); + panic!("Meeting frames not ready: {error}; stopped={stopped}"); + } } - let capture_id = response["payload"]["meetingCaptureId"].as_str().unwrap(); let stopped = state.stop_meeting_capture(capture_id, "meetingCaptureStop"); assert_eq!(stopped["stopped"], true); assert_eq!(stopped["relay"]["relayError"], Value::Null, "{stopped}"); diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e856f88d..76a75725 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -218,6 +218,8 @@ Meetings: loopback sources at 48 kHz in shared mode; it never opens a camera/video device. The microphone and loopback frames are downsampled to 16 kHz before the single pinned LocalVQE v1.3 model reduces echo, noise, and reverberation. + The relay initializes the model and connects consumers before opening its + upstream capture pipes, so cold model startup cannot overflow producer buffers. Its 256-sample inference hops are adapted to the 160-sample transport frames; the relay holds at most four frames, removes the native one-hop pre-roll, and flushes the final partial hop before EOF. All three tracks retain the diff --git a/native/scriber-audio-sidecar/Cargo.lock b/native/scriber-audio-sidecar/Cargo.lock index 5eb18ced..4fa85177 100644 --- a/native/scriber-audio-sidecar/Cargo.lock +++ b/native/scriber-audio-sidecar/Cargo.lock @@ -299,7 +299,7 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scriber-audio-sidecar" -version = "0.2.0" +version = "0.2.1" dependencies = [ "scriber-localvqe", "serde_json", diff --git a/native/scriber-audio-sidecar/Cargo.toml b/native/scriber-audio-sidecar/Cargo.toml index 22d03583..d9a925bc 100644 --- a/native/scriber-audio-sidecar/Cargo.toml +++ b/native/scriber-audio-sidecar/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scriber-audio-sidecar" -version = "0.2.0" +version = "0.2.1" description = "Scriber audio capture worker" authors = ["Scriber"] edition = "2021" diff --git a/src/version.py b/src/version.py index e20f87ff..f8629340 100644 --- a/src/version.py +++ b/src/version.py @@ -3,7 +3,7 @@ import os import re -__version__ = "0.5.126" +__version__ = "0.5.127" _SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$") diff --git a/tests/test_audio_worker_build_contract.py b/tests/test_audio_worker_build_contract.py index 99a3f509..73d353bc 100644 --- a/tests/test_audio_worker_build_contract.py +++ b/tests/test_audio_worker_build_contract.py @@ -93,5 +93,8 @@ def test_audio_cache_only_validation_does_not_require_a_local_rust_compiler(tmp_ check=False, ) assert result.returncode != 0 - assert "cache is missing, stale, or does not match worker version 0.2.0" in result.stderr + worker_version = tomllib.loads((ROOT / "native/scriber-audio-sidecar/Cargo.toml").read_text(encoding="utf-8"))[ + "package" + ]["version"] + assert f"cache is missing, stale, or does not match worker version {worker_version}" in result.stderr assert "rustc" not in result.stderr