From 8c82ed8f11280a5945289df3f818c90ffc065d1c Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 3 Jul 2026 08:57:44 -0400 Subject: [PATCH] fix(#5435): read voice silence timeout at use time --- static/boot.js | 14 +++++----- tests/test_issue4761_voice_mode_config.py | 32 ++++++++++++----------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/static/boot.js b/static/boot.js index 19f44b4f..529b12d6 100644 --- a/static/boot.js +++ b/static/boot.js @@ -1221,12 +1221,12 @@ window._hermesTtsSynth=function(id, text, opts){ let _browserTtsWatchdog=null; let _browserTtsSuppressNextErrorRearm=false; // Configurable via localStorage keys (set from dev console or a future settings panel). - // hermes-voice-silence-ms — pause duration before auto-send (ms, default 1800) - // hermes-voice-continuous — keep mic open across natural pauses ("true"/"false", default false) - const _silenceMsRaw=parseInt(localStorage.getItem('hermes-voice-silence-ms'),10); - // Fall back to 1800 for missing/NaN/non-positive values, and floor at 200ms so a - // mistyped tiny/negative value can't make the recognizer auto-send instantly. - const SILENCE_MS=(Number.isFinite(_silenceMsRaw)&&_silenceMsRaw>0)?Math.max(200,_silenceMsRaw):1800; + // hermes-voice-silence-ms, pause duration before auto-send (ms, default 1800) + // hermes-voice-continuous, keep mic open across natural pauses ("true"/"false", default false) + function _voiceSilenceMs(){ + const _silenceMsRaw=parseInt(localStorage.getItem('hermes-voice-silence-ms'),10); + return (Number.isFinite(_silenceMsRaw)&&_silenceMsRaw>0)?Math.max(200,_silenceMsRaw):1800; + } function _clearBrowserTtsRecovery(){ if(_browserTtsKeepAlive){ @@ -1311,7 +1311,7 @@ window._hermesTtsSynth=function(id, text, opts){ if(_finalText){ _silenceTimer=setTimeout(()=>{ _voiceModeSend(); - },SILENCE_MS); + },_voiceSilenceMs()); } }; diff --git a/tests/test_issue4761_voice_mode_config.py b/tests/test_issue4761_voice_mode_config.py index 6e20ddaa..c78cdf2a 100644 --- a/tests/test_issue4761_voice_mode_config.py +++ b/tests/test_issue4761_voice_mode_config.py @@ -1,7 +1,7 @@ -"""Tests for #4761 — configurable voice-mode silence timeout and continuous recognition. +"""Tests for #4761, configurable voice-mode silence timeout and continuous recognition. -The voice-mode loop currently hardcodes: - - SILENCE_MS = 1800 (1.8s pause before auto-send) +The voice-mode loop used to hardcode: + - a startup silence timeout constant (1.8s pause before auto-send) - _recognition.continuous = false (mic closes after each utterance) This module pins the fix: both values are now configurable via localStorage keys @@ -19,7 +19,7 @@ def _boot_src() -> str: class TestVoiceModeSilenceMsConfig: - """SILENCE_MS must read from localStorage with 1800 fallback.""" + """The silence timeout must read from localStorage with 1800 fallback.""" def test_silence_ms_reads_local_storage_with_fallback(self): src = _boot_src() @@ -29,23 +29,25 @@ class TestVoiceModeSilenceMsConfig: assert re.search( r"parseInt\s*\(\s*localStorage\.getItem\s*\(\s*'hermes-voice-silence-ms'\s*\)", src, - ), "SILENCE_MS must read the 'hermes-voice-silence-ms' localStorage key via parseInt." + ), "Voice mode must read the 'hermes-voice-silence-ms' localStorage key via parseInt." # The 1800 default must remain the fallback for missing/invalid values. - assert re.search(r"SILENCE_MS\s*=.*\b1800\b", src), ( - "SILENCE_MS must keep 1800 as the default fallback so behavior is " + assert re.search(r"return\s*\(.*\)\?Math\.max\(200,_silenceMsRaw\):1800", src), ( + "Voice mode must keep 1800 as the default fallback so behavior is " "unchanged when the key is unset or invalid." ) - # A non-positive / mistyped value must not be honored verbatim (no instant - # auto-send): the value is guarded by a positivity check and/or a min floor. + # A non-positive or mistyped value must not be honored verbatim. assert "_silenceMsRaw>0" in src or "Math.max(" in src or "> 0" in src, ( - "SILENCE_MS must guard against non-positive values (positivity check " + "Voice mode must guard against non-positive values (positivity check " "or a Math.max floor) so a mistyped tiny/negative value can't make the " "recognizer auto-send instantly." ) - def test_silence_ms_used_in_timeout(self): + def test_silence_ms_read_at_timeout_use_time(self): src = _boot_src() - assert "SILENCE_MS" in src, "SILENCE_MS must still be referenced in the timeout call." + assert "_voiceSilenceMs()" in src, "The silence timeout must be read when scheduling auto-send." + assert re.search(r"setTimeout\s*\(\s*\(\)\s*=>\s*\{\s*_voiceModeSend\(\);\s*\}\s*,\s*_voiceSilenceMs\(\)\s*\)", src, re.S), ( + "Voice mode must call _voiceSilenceMs() inside the setTimeout path so mirrored server settings apply without reload." + ) class TestVoiceModeContinuousConfig: @@ -73,11 +75,11 @@ class TestVoiceModeContinuousConfig: class TestBootJsVoiceSectionIntegrity: - """Smoke checks — the surrounding voice-mode infrastructure is intact.""" + """Smoke checks, the surrounding voice-mode infrastructure is intact.""" - def test_voice_mode_declares_silence_ms(self): + def test_voice_mode_declares_silence_helper(self): src = _boot_src() - assert "SILENCE_MS" in src, "SILENCE_MS constant must exist in boot.js" + assert "function _voiceSilenceMs()" in src, "The voice silence timeout helper must exist in boot.js" def test_voice_mode_declares_recognition(self): src = _boot_src()