fix(review): reset mic restart budget on a productive dictation result (#5294)
_micRestartCount bounds the continuity auto-restart loop (_micMaxRestarts=20)
so a stolen audio session can't spin tight. But it only reset on
fresh-start / terminal-end / error -- never on a real onresult. So a long
mobile dictation with 20 natural pauses (each producing speech) hit the cap
mid-session and the mic silently stopped, undercutting the feature's whole
purpose ("keep dictation active through pauses").
Reset _micRestartCount=0 in sr.onresult: a real result proves the restarts
are PRODUCTIVE, not a failure spin. The cap still guards the failure case --
consecutive restarts that yield no speech (onend with no intervening
onresult) keep incrementing and trip the bound.
Regression test seeds the count AT the cap, fires a productive onresult, and
asserts the ensuing pause still restarts (count reset to 0 -> 1). Verified
fail-without / pass-with against boot.js.
This commit is contained in:
1 parent
18845fd000
commit
762fd01ae2
2 files changed
+31
-2
No files matched your search
@@ -919,6 +919,13 @@ function _micToastKeyForRecognitionError(error){
|
|||||||
sr.onstart=()=>{ _finalText=''; };
|
sr.onstart=()=>{ _finalText=''; };
|
||||||
|
|
||||||
sr.onresult=(event)=>{
|
sr.onresult=(event)=>{
|
||||||
|
// #5294: a real result means the continuity restarts are PRODUCTIVE, not a
|
||||||
|
// stolen-audio-session tight loop — reset the restart budget so a long
|
||||||
|
// dictation with many natural pauses isn't silently capped at
|
||||||
|
// _micMaxRestarts. The cap still guards the failure case: consecutive
|
||||||
|
// restarts that yield no speech (onend without an intervening onresult)
|
||||||
|
// keep incrementing and trip the bound.
|
||||||
|
_micRestartCount=0;
|
||||||
let interim='';
|
let interim='';
|
||||||
let final=_finalText;
|
let final=_finalText;
|
||||||
for(let i=event.resultIndex;i<event.results.length;i++){
|
for(let i=event.resultIndex;i<event.results.length;i++){
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ const continuousAfterEnsure = recognition.continuous;
|
|||||||
// Simulate _startMicCapture's speech branch entering an active session.
|
// Simulate _startMicCapture's speech branch entering an active session.
|
||||||
_activeCaptureMode = 'speech';
|
_activeCaptureMode = 'speech';
|
||||||
_speechStopRequested = !!input.stopRequested;
|
_speechStopRequested = !!input.stopRequested;
|
||||||
_micRestartCount = 0;
|
// #5294 regression: allow pre-seeding the restart budget so we can prove a real
|
||||||
|
// result resets it (a long productive dictation must not silently hit the cap).
|
||||||
|
_micRestartCount = (typeof input.preRestartCount === 'number') ? input.preRestartCount : 0;
|
||||||
window._micActive = true;
|
window._micActive = true;
|
||||||
recognition.start(); // initial start (mimics _startMicCapture)
|
recognition.start(); // initial start (mimics _startMicCapture)
|
||||||
const startCallsAfterStart = recognition.startCalls;
|
const startCallsAfterStart = recognition.startCalls;
|
||||||
@@ -131,6 +133,7 @@ const out = {
|
|||||||
continuousAfterEnsure: continuousAfterEnsure,
|
continuousAfterEnsure: continuousAfterEnsure,
|
||||||
restarted: recognition.startCalls > startCallsAfterStart,
|
restarted: recognition.startCalls > startCallsAfterStart,
|
||||||
micActiveAfterPause: !!window._micActive,
|
micActiveAfterPause: !!window._micActive,
|
||||||
|
restartCountAfter: _micRestartCount,
|
||||||
taValue: ta.value,
|
taValue: ta.value,
|
||||||
toasts: toasts,
|
toasts: toasts,
|
||||||
};
|
};
|
||||||
@@ -145,8 +148,10 @@ def driver_path(tmp_path_factory):
|
|||||||
return str(p)
|
return str(p)
|
||||||
|
|
||||||
|
|
||||||
def _run(driver_path, *, coarse=False, store=None, stop_requested=False):
|
def _run(driver_path, *, coarse=False, store=None, stop_requested=False, pre_restart_count=None):
|
||||||
payload = {"coarse": coarse, "store": store or {}, "stopRequested": stop_requested}
|
payload = {"coarse": coarse, "store": store or {}, "stopRequested": stop_requested}
|
||||||
|
if pre_restart_count is not None:
|
||||||
|
payload["preRestartCount"] = pre_restart_count
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[NODE, driver_path, str(BOOT_JS_PATH), json.dumps(payload)],
|
[NODE, driver_path, str(BOOT_JS_PATH), json.dumps(payload)],
|
||||||
capture_output=True, text=True, timeout=30,
|
capture_output=True, text=True, timeout=30,
|
||||||
@@ -201,3 +206,20 @@ class TestMobileKeepsSessionAlive:
|
|||||||
out = _run(driver_path, coarse=False, store={"hermes-mic-continuous": "true"})
|
out = _run(driver_path, coarse=False, store={"hermes-mic-continuous": "true"})
|
||||||
assert out["continuousAfterEnsure"] is True
|
assert out["continuousAfterEnsure"] is True
|
||||||
assert out["restarted"] is True
|
assert out["restarted"] is True
|
||||||
|
|
||||||
|
def test_productive_result_resets_restart_budget(self, driver_path):
|
||||||
|
# A real result means the continuity restarts are PRODUCTIVE, so the
|
||||||
|
# restart budget must reset — otherwise a long mobile dictation with many
|
||||||
|
# natural pauses silently hits _micMaxRestarts and dies mid-session. Seed
|
||||||
|
# the count AT the cap: onresult must reset it to 0 so the ensuing pause
|
||||||
|
# still restarts. Without the reset, count stays at the cap and the
|
||||||
|
# session would NOT restart.
|
||||||
|
out = _run(driver_path, coarse=True, pre_restart_count=20)
|
||||||
|
assert out["restartCountAfter"] == 1, (
|
||||||
|
"a productive onresult must reset _micRestartCount (to 0), leaving 1 "
|
||||||
|
"after the subsequent natural-pause restart — not stay pinned at the cap"
|
||||||
|
)
|
||||||
|
assert out["restarted"] is True, (
|
||||||
|
"mobile dictation must keep restarting through pauses once a real "
|
||||||
|
"result has proven the session productive, even past the raw cap"
|
||||||
|
)
|
||||||
Reference in new issue
Block a user