2026-08-21 11:52:49 +00:00
// Deterministic browser stub that drives dockerfiles/hermes-webui-atlas-voice.js
// through complete hands-free turns with no microphone, audio device or GPU.
//
// The script under test is an IIFE with no exported seams, so the only honest
// way to assert what reaches POST /api/tts is to run it against a fake DOM and
// fake clock and record the requests it actually makes. Usage:
//
// node atlas_voice_language_probe.js <path-to-atlas-voice.js>
//
// It prints one JSON object describing every scenario to stdout.
'use strict' ;
const fs = require ( 'fs' ) ;
const vm = require ( 'vm' ) ;
const SCRIPT _PATH = process . argv [ 2 ] ;
if ( ! SCRIPT _PATH ) {
throw new Error ( 'usage: atlas_voice_language_probe.js <atlas-voice.js>' ) ;
}
const SOURCE = fs . readFileSync ( SCRIPT _PATH , 'utf8' ) ;
function flush ( ) {
// Four macrotask hops drain the promise chains the script builds around
// fetch()/json()/blob()/play() without ever waiting on wall-clock time.
return new Promise ( ( resolve ) => {
let hops = 0 ;
( function hop ( ) {
hops += 1 ;
if ( hops > 12 ) {
resolve ( ) ;
return ;
}
setImmediate ( hop ) ;
} ) ( ) ;
} ) ;
}
function makeElement ( id ) {
return {
id ,
style : {
values : new Map ( ) ,
setProperty ( name , value ) { this . values . set ( name , String ( value ) ) ; } ,
removeProperty ( name ) { this . values . delete ( name ) ; } ,
getPropertyValue ( name ) { return this . values . get ( name ) || '' ; } ,
} ,
dataset : { } ,
attributes : { } ,
value : '' ,
textContent : '' ,
className : '' ,
classList : {
entries : new Set ( ) ,
add ( name ) { this . entries . add ( name ) ; } ,
remove ( name ) { this . entries . delete ( name ) ; } ,
contains ( name ) { return this . entries . has ( name ) ; } ,
} ,
listeners : [ ] ,
setAttribute ( name , value ) { this . attributes [ name ] = String ( value ) ; } ,
getAttribute ( name ) {
return Object . prototype . hasOwnProperty . call ( this . attributes , name )
? this . attributes [ name ] : null ;
} ,
addEventListener ( type , handler ) { this . listeners . push ( { type , handler } ) ; } ,
removeEventListener ( type , handler ) {
this . listeners = this . listeners . filter ( ( entry ) => entry . handler !== handler ) ;
} ,
click ( ) {
const event = { preventDefault ( ) { } , stopImmediatePropagation ( ) { } } ;
this . listeners
. filter ( ( entry ) => entry . type === 'click' )
. forEach ( ( entry ) => entry . handler ( event ) ) ;
} ,
querySelector ( ) { return null ; } ,
insertBefore ( ) { } ,
appendChild ( ) { } ,
} ;
}
2026-08-23 18:29:02 -03:00
function makeHarness ( options = { } ) {
2026-08-21 11:52:49 +00:00
const clock = { now : 1000000 } ;
const timeouts = [ ] ;
const intervals = new Map ( ) ;
let timerId = 1 ;
const ttsRequests = [ ] ;
2026-08-23 18:29:02 -03:00
const ttsStreamRequests = [ ] ;
2026-08-21 11:52:49 +00:00
const transcribeCalls = [ ] ;
const toasts = [ ] ;
const sends = [ ] ;
let capability = { ok : true , available : true , provider : 'local_command' } ;
let transcribeResponse = { ok : true , transcript : 'hello' , language : 'en' } ;
let transcribeStatus = 200 ;
let assistantRows = [ ] ;
let loud = false ;
let recorder = null ;
2026-08-23 18:29:02 -03:00
let lastAudio = null ;
2026-08-21 11:52:49 +00:00
const storage = new Map ( ) ;
const elements = { } ;
[ 'btnVoiceMode' , 'voiceModeBar' , 'voiceModeIndicator' , 'voiceModeLabel' , 'msg' ]
. forEach ( ( id ) => { elements [ id ] = makeElement ( id ) ; } ) ;
function MediaRecorder ( ) {
this . state = 'recording' ;
this . ondataavailable = null ;
this . onstop = null ;
recorder = this ;
}
MediaRecorder . prototype . start = function start ( ) { this . state = 'recording' ; } ;
MediaRecorder . prototype . stop = function stop ( ) {
if ( this . state === 'inactive' ) return ;
this . state = 'inactive' ;
if ( this . onstop ) this . onstop ( ) ;
} ;
MediaRecorder . isTypeSupported = function isTypeSupported ( ) { return true ; } ;
function AudioContext ( ) {
this . createAnalyser = ( ) => ( {
fftSize : 2048 ,
getByteTimeDomainData ( samples ) {
for ( let i = 0 ; i < samples . length ; i += 1 ) {
samples [ i ] = loud ? ( i % 2 ? 200 : 56 ) : 128 ;
}
} ,
} ) ;
this . createBiquadFilter = ( ) => ( {
type : '' , frequency : { value : 0 } , Q : { value : 0 } , connect ( ) { } ,
} ) ;
this . createMediaStreamSource = ( ) => ( { connect ( ) { } } ) ;
this . close = ( ) => { } ;
}
function AudioElement ( ) {
this . currentTime = 0 ;
this . onended = null ;
this . onerror = null ;
2026-08-23 18:29:02 -03:00
this . paused = false ;
this . pause = ( ) => { this . paused = true ; } ;
2026-08-21 11:52:49 +00:00
this . play = ( ) => {
2026-08-23 18:29:02 -03:00
this . paused = false ;
if ( ! options . manualAudio ) setImmediate ( ( ) => { if ( this . onended ) this . onended ( ) ; } ) ;
2026-08-21 11:52:49 +00:00
return Promise . resolve ( ) ;
} ;
2026-08-23 18:29:02 -03:00
this . finish = ( ) => { if ( this . onended ) this . onended ( ) ; } ;
lastAudio = this ;
2026-08-21 11:52:49 +00:00
}
async function fetchStub ( url , init ) {
if ( url === '/api/transcribe/capability' ) {
return { ok : true , status : 200 , json : async ( ) => capability } ;
}
2026-08-23 18:29:02 -03:00
if ( url === '/api/voice/streaming/capability' ) {
return {
ok : ! ! options . streamingCapability ,
status : options . streamingCapability ? 200 : 404 ,
json : async ( ) => options . streamingCapability || { } ,
} ;
}
2026-08-21 11:52:49 +00:00
if ( url === '/api/transcribe' ) {
transcribeCalls . push ( { body : init && init . body } ) ;
return {
ok : transcribeStatus < 400 ,
status : transcribeStatus ,
json : async ( ) => transcribeResponse ,
} ;
}
if ( url === '/api/tts' ) {
ttsRequests . push ( JSON . parse ( init . body ) ) ;
return {
ok : true ,
status : 200 ,
blob : async ( ) => ( { synthetic : true } ) ,
json : async ( ) => ( { } ) ,
} ;
}
2026-08-23 18:29:02 -03:00
if ( url === '/api/tts/stream' ) {
ttsStreamRequests . push ( JSON . parse ( init . body ) ) ;
return {
ok : ! options . ttsStreamFails ,
status : options . ttsStreamFails ? 503 : 200 ,
body : null ,
headers : { get ( ) { return null ; } } ,
} ;
}
2026-08-21 11:52:49 +00:00
throw new Error ( ` unexpected fetch: ${ url } ` ) ;
}
const context = {
2026-08-23 18:29:02 -03:00
AbortController ,
2026-08-21 11:52:49 +00:00
console ,
Uint8Array ,
Promise ,
Math ,
JSON ,
String ,
Number ,
Error ,
parseInt ,
isNaN ,
Set ,
Map ,
Array ,
Object ,
Date : { now : ( ) => clock . now } ,
Blob : function Blob ( parts , options ) { this . parts = parts ; this . type = ( options || { } ) . type || '' ; } ,
File : function File ( parts , name , options ) {
this . parts = parts ; this . name = name ; this . type = ( options || { } ) . type || '' ;
} ,
FormData : function FormData ( ) { this . entries = [ ] ; this . append = ( k , v ) => this . entries . push ( [ k , v ] ) ; } ,
URL : { createObjectURL : ( ) => 'blob:atlas-test' , revokeObjectURL ( ) { } } ,
Audio : AudioElement ,
2026-08-23 18:29:02 -03:00
AudioWorkletNode : options . enableAudioWorklet ? function AudioWorkletNode ( ) { } : undefined ,
2026-08-21 11:52:49 +00:00
MediaRecorder ,
AudioContext ,
2026-08-23 18:29:02 -03:00
ReadableStream : options . enableAudioWorklet ? function ReadableStream ( ) { } : undefined ,
2026-08-21 11:52:49 +00:00
fetch : fetchStub ,
localStorage : {
getItem : ( key ) => ( storage . has ( key ) ? storage . get ( key ) : null ) ,
setItem : ( key , value ) => { storage . set ( key , String ( value ) ) ; } ,
removeItem : ( key ) => { storage . delete ( key ) ; } ,
} ,
navigator : {
mediaDevices : {
getUserMedia : async ( ) => ( { getTracks : ( ) => [ { stop ( ) { } } ] } ) ,
getSupportedConstraints : ( ) => ( { } ) ,
} ,
} ,
document : {
getElementById : ( id ) => elements [ id ] || null ,
querySelectorAll : ( ) => assistantRows ,
createElement : ( ) => ( { value : '' , textContent : '' } ) ,
} ,
S : { session : { session _id : 'session-1' } , busy : false } ,
setTimeout : ( fn , delay ) => {
const id = timerId ; timerId += 1 ;
timeouts . push ( { id , fn , at : clock . now + ( delay || 0 ) } ) ;
return id ;
} ,
clearTimeout : ( id ) => {
const index = timeouts . findIndex ( ( entry ) => entry . id === id ) ;
if ( index >= 0 ) timeouts . splice ( index , 1 ) ;
} ,
setInterval : ( fn , delay ) => {
const id = timerId ; timerId += 1 ;
intervals . set ( id , { fn , delay : delay || 0 } ) ;
return id ;
} ,
clearInterval : ( id ) => { intervals . delete ( id ) ; } ,
} ;
context . window = context ;
context . showToast = ( message ) => { toasts . push ( message ) ; } ;
context . send = ( ) => { sends . push ( elements . msg . value ) ; } ;
context . autoResize = ( ) => { } ;
vm . createContext ( context ) ;
vm . runInContext ( SOURCE , context , { filename : 'atlas-voice.js' } ) ;
function runDueTimeouts ( ) {
const due = timeouts . filter ( ( entry ) => entry . at <= clock . now ) ;
due . forEach ( ( entry ) => {
const index = timeouts . indexOf ( entry ) ;
if ( index >= 0 ) timeouts . splice ( index , 1 ) ;
entry . fn ( ) ;
} ) ;
}
function tick ( ms ) {
clock . now += ms ;
Array . from ( intervals . values ( ) ) . forEach ( ( entry ) => entry . fn ( ) ) ;
runDueTimeouts ( ) ;
}
return {
context ,
elements ,
ttsRequests ,
2026-08-23 18:29:02 -03:00
ttsStreamRequests ,
2026-08-21 11:52:49 +00:00
transcribeCalls ,
toasts ,
sends ,
clock ,
recorder : ( ) => recorder ,
2026-08-23 18:29:02 -03:00
get lastAudio ( ) { return lastAudio ; } ,
2026-08-21 11:52:49 +00:00
setLoud : ( value ) => { loud = value ; } ,
setCapability : ( value ) => { capability = value ; } ,
setTranscribeResponse : ( value , status ) => {
transcribeResponse = value ;
transcribeStatus = status === undefined ? 200 : status ;
} ,
setAssistantReply : ( text ) => { assistantRows = [ { dataset : { rawText : text } } ] ; } ,
setSession : ( id ) => { context . S . session = { session _id : id } ; } ,
advance : ( ms ) => { clock . now += ms ; } ,
tick ,
runDueTimeouts ,
flush ,
} ;
}
// Walk one capture window: pre-roll audio, three loud frames so the VAD latches
// speech, then silence past the hangover so MediaRecorder.stop() fires.
async function captureSpeech ( harness ) {
const active = harness . recorder ( ) ;
if ( ! active ) throw new Error ( 'voice mode never created a recorder' ) ;
active . ondataavailable ( { data : { size : 512 } } ) ;
harness . setLoud ( true ) ;
for ( let i = 0 ; i < 4 ; i += 1 ) harness . tick ( 100 ) ;
active . ondataavailable ( { data : { size : 512 } } ) ;
harness . setLoud ( false ) ;
harness . advance ( 2500 ) ;
harness . tick ( 100 ) ;
await harness . flush ( ) ;
}
async function startVoiceMode ( harness ) {
await harness . flush ( ) ;
harness . elements . btnVoiceMode . click ( ) ;
await harness . flush ( ) ;
}
// One complete turn: speak, transcribe, let the app "answer", read it back.
async function runTurn ( harness , { transcript , language , reply } ) {
const payload = { ok : true , transcript } ;
if ( language !== undefined ) payload . language = language ;
harness . setTranscribeResponse ( payload ) ;
await captureSpeech ( harness ) ;
harness . setAssistantReply ( reply || 'An answer.' ) ;
harness . context . autoReadLastAssistant ( ) ;
await harness . flush ( ) ;
}
async function restartListening ( harness ) {
harness . advance ( 1000 ) ;
harness . runDueTimeouts ( ) ;
await harness . flush ( ) ;
}
const scenarios = { } ;
scenarios . english _turn _speaks _english = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : 'What is the weather?' , language : 'en' } ) ;
return { tts : harness . ttsRequests } ;
} ;
scenarios . russian _turn _speaks _russian = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : 'Как дела?' , language : 'ru' , reply : 'Всё хорошо.' } ) ;
return { tts : harness . ttsRequests } ;
} ;
scenarios . spanish _turn _speaks _spanish = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : '¿Qué tal?' , language : 'es' , reply : 'Muy bien.' } ) ;
return { tts : harness . ttsRequests } ;
} ;
scenarios . missing _language _falls _back = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : 'Hello there.' } ) ;
return { tts : harness . ttsRequests } ;
} ;
scenarios . unsupported _language _falls _back = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : 'Bonjour tout le monde.' , language : 'fr' } ) ;
return { tts : harness . ttsRequests } ;
} ;
scenarios . hostile _language _values _are _dropped = async ( ) => {
const results = [ ] ;
const hostile = [
'ru; rm -rf /' ,
'../../ru_RU-irina-medium' ,
'ru\\u0000' ,
'RUSSIAN' ,
{ language : 'ru' } ,
[ 'ru' ] ,
42 ,
null ,
'r' ,
'ru ru' ,
'x' . repeat ( 4096 ) ,
] ;
for ( const language of hostile ) {
const harness = makeHarness ( ) ;
// eslint-disable-next-line no-await-in-loop
await startVoiceMode ( harness ) ;
// eslint-disable-next-line no-await-in-loop
await runTurn ( harness , { transcript : 'Say something.' , language } ) ;
results . push ( { sent : String ( language ) , tts : harness . ttsRequests } ) ;
}
return { results } ;
} ;
scenarios . voice _field _is _never _sent = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : 'Как дела?' , language : 'ru' } ) ;
return { tts : harness . ttsRequests } ;
} ;
scenarios . language _does _not _leak _into _later _turn = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : 'Как дела?' , language : 'ru' , reply : 'Всё хорошо.' } ) ;
await restartListening ( harness ) ;
await runTurn ( harness , { transcript : 'And in English?' , language : undefined } ) ;
await restartListening ( harness ) ;
await runTurn ( harness , { transcript : '¿Y ahora?' , language : 'es' } ) ;
return { tts : harness . ttsRequests } ;
} ;
scenarios . empty _transcript _does _not _arm _a _language = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
harness . setTranscribeResponse ( { ok : true , transcript : ' ' , language : 'ru' } ) ;
await captureSpeech ( harness ) ;
const sendsAfterBlank = harness . sends . slice ( ) ;
// A reply landing while the blank turn winds down must not inherit a
// language that transcript never earned.
harness . setAssistantReply ( 'A stray answer.' ) ;
harness . context . autoReadLastAssistant ( ) ;
await harness . flush ( ) ;
await restartListening ( harness ) ;
await runTurn ( harness , { transcript : 'Hello.' , language : undefined } ) ;
return { sendsAfterBlank , tts : harness . ttsRequests , sends : harness . sends } ;
} ;
scenarios . session _change _discards _language = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
harness . setTranscribeResponse ( { ok : true , transcript : 'Как дела?' , language : 'ru' } ) ;
await captureSpeech ( harness ) ;
harness . setSession ( 'session-2' ) ;
harness . setAssistantReply ( 'Reply that belongs to another chat.' ) ;
harness . context . autoReadLastAssistant ( ) ;
await harness . flush ( ) ;
const afterSwitch = harness . ttsRequests . slice ( ) ;
await restartListening ( harness ) ;
await runTurn ( harness , { transcript : 'Hello again.' , language : undefined } ) ;
return { afterSwitch , tts : harness . ttsRequests } ;
} ;
scenarios . deactivation _discards _language = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
harness . setTranscribeResponse ( { ok : true , transcript : 'Как дела?' , language : 'ru' } ) ;
await captureSpeech ( harness ) ;
harness . elements . btnVoiceMode . click ( ) ;
await harness . flush ( ) ;
harness . setAssistantReply ( 'Late reply after the user left voice mode.' ) ;
harness . context . autoReadLastAssistant ( ) ;
await harness . flush ( ) ;
const afterDeactivate = harness . ttsRequests . slice ( ) ;
harness . elements . btnVoiceMode . click ( ) ;
await harness . flush ( ) ;
await runTurn ( harness , { transcript : 'Fresh start.' , language : undefined } ) ;
return { afterDeactivate , tts : harness . ttsRequests } ;
} ;
scenarios . transcribe _error _speaks _nothing = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
harness . setTranscribeResponse ( { error : 'Whisper is down' } , 503 ) ;
await captureSpeech ( harness ) ;
harness . setAssistantReply ( 'Some earlier answer.' ) ;
harness . context . autoReadLastAssistant ( ) ;
await harness . flush ( ) ;
return { tts : harness . ttsRequests , toasts : harness . toasts } ;
} ;
2026-08-23 18:29:02 -03:00
scenarios . adaptive _chunks _are _sentence _gated = async ( ) => {
const harness = makeHarness ( ) ;
await harness . flush ( ) ;
const partial = harness . context . _atlasAdaptiveChunks (
'Dr. Rivera is still explaining this opening thought without a safe sentence boundary yet' ,
false ,
) ;
const complete = harness . context . _atlasAdaptiveChunks (
'This opening clause gives Hermes a clean and quick place to begin speaking, while the rest of the first sentence remains coherent. '
+ 'The following explanation is deliberately long enough to demonstrate that later punctuation aligned chunks use a much larger target and retain natural prosody for listeners. '
+ 'A compact final tail remains.' ,
true ,
) ;
return { partial , complete } ;
} ;
scenarios . first _sentence _speaks _before _stream _completion = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
harness . setTranscribeResponse ( { ok : true , transcript : 'Tell me something useful.' , language : 'en' } ) ;
await captureSpeech ( harness ) ;
harness . setAssistantReply ( 'This answer is still streaming without a complete sentence' ) ;
harness . tick ( 100 ) ;
await harness . flush ( ) ;
const beforeBoundary = harness . ttsRequests . length ;
harness . setAssistantReply ( 'This answer now has its first complete sentence. The remainder is still' ) ;
harness . tick ( 100 ) ;
await harness . flush ( ) ;
const afterBoundary = harness . ttsRequests . length ;
harness . setAssistantReply ( 'This answer now has its first complete sentence. The remainder is still being generated and is now complete.' ) ;
harness . context . autoReadLastAssistant ( ) ;
await harness . flush ( ) ;
return { beforeBoundary , afterBoundary , tts : harness . ttsRequests } ;
} ;
scenarios . final _renderer _revision _closes _speech _queue = async ( ) => {
const harness = makeHarness ( ) ;
await startVoiceMode ( harness ) ;
harness . setTranscribeResponse ( { ok : true , transcript : 'Explain this.' , language : 'en' } ) ;
await captureSpeech ( harness ) ;
harness . setAssistantReply ( 'Hermes starts with a complete sentence. The draft tail is still' ) ;
harness . tick ( 100 ) ;
await harness . flush ( ) ;
const beforeRevision = harness . ttsRequests . length ;
harness . setAssistantReply ( 'The renderer revised the complete sentence. The final tail is now safe and complete.' ) ;
harness . context . autoReadLastAssistant ( ) ;
await harness . flush ( ) ;
return { beforeRevision , afterRevision : harness . ttsRequests . length } ;
} ;
scenarios . streaming _tts _failure _falls _back _to _wav = async ( ) => {
const harness = makeHarness ( {
enableAudioWorklet : true ,
ttsStreamFails : true ,
streamingCapability : {
tts : { available : true , transport : 'http' , format : 'pcm_s16le' , sample _rate : 22050 } ,
stt : { available : false } ,
} ,
} ) ;
await startVoiceMode ( harness ) ;
await runTurn ( harness , { transcript : 'Fallback please.' , language : 'en' , reply : 'A complete answer.' } ) ;
return { stream : harness . ttsStreamRequests , wav : harness . ttsRequests } ;
} ;
scenarios . one _ahead _is _bounded _and _turn _cancel _stops _audio = async ( ) => {
const harness = makeHarness ( { manualAudio : true } ) ;
await startVoiceMode ( harness ) ;
const reply = 'This opening sentence is sufficiently complete for a short first speech chunk. '
+ 'This second section contains enough carefully chosen words to become a larger punctuation aligned chunk without losing its natural rhythm or clarity for the listener. '
+ 'This third section is also deliberately long enough to require another queued synthesis request after playback advances.' ;
await runTurn ( harness , { transcript : 'Read it.' , language : 'en' , reply } ) ;
const beforeFirstEnds = harness . ttsRequests . length ;
const firstAudio = harness . lastAudio ;
harness . elements . btnVoiceMode . click ( ) ;
await harness . flush ( ) ;
return {
beforeFirstEnds ,
afterCancel : harness . ttsRequests . length ,
active : harness . context . _voiceModeActive ( ) ,
firstAudioPaused : ! ! ( firstAudio && firstAudio . paused ) ,
} ;
} ;
2026-08-21 11:52:49 +00:00
( async ( ) => {
const output = { } ;
const names = Object . keys ( scenarios ) ;
for ( const name of names ) {
// eslint-disable-next-line no-await-in-loop
output [ name ] = await scenarios [ name ] ( ) ;
}
process . stdout . write ( JSON . stringify ( output , null , 1 ) ) ;
} ) ( ) . catch ( ( error ) => {
process . stderr . write ( String ( ( error && error . stack ) || error ) ) ;
process . exit ( 1 ) ;
} ) ;