diff --git a/daemon/index.js b/daemon/index.js index 266f8ff..6affb96 100644 --- a/daemon/index.js +++ b/daemon/index.js @@ -47,7 +47,7 @@ export class JarvisDaemon extends EventEmitter { async sleep() { this.voice.sleep(); this.setState('SLEEPING'); await suspendQvac().catch((error) => this.emit('Error', 'QVAC_SUSPEND', error.message)); } say(text) { this.lastReply = String(text); this.emit('Reply', this.lastReply); } async ask(text) { - this.voice.utterance(); this.setState('THINKING'); + this.voice.typedUtterance(); this.setState('THINKING'); try { const startedAt = Date.now(); const reply = await this.scheduler.run(() => this.harness.ask(text), { lane: 'voice' }); this.telemetry.record('llm', startedAt, { success: true }); this.voice.speak(); this.setState('SPEAKING'); this.lastReply = String(reply || ''); this.emit('Reply', this.lastReply); this.voiceLoop?.speak(this.lastReply).catch((error) => this.emit('Error', 'TTS', error.message)); return reply; diff --git a/daemon/voice-state.js b/daemon/voice-state.js index d97481b..31e134f 100644 --- a/daemon/voice-state.js +++ b/daemon/voice-state.js @@ -31,6 +31,10 @@ export class VoiceStateMachine { wake() { return this.transition('LISTENING', 'wake'); } utterance() { return this.transition('THINKING', 'utterance'); } + typedUtterance() { + if (this.state === 'ARMED' || this.state === 'SLEEPING') this.wake(); + return this.utterance(); + } speak() { return this.transition('SPEAKING', 'reply'); } finishSpeaking() { return this.transition('LISTENING', 'playback-finished'); } cancel() { return this.transition('ARMED', 'cancel'); } diff --git a/test/daemon.test.js b/test/daemon.test.js index a2bc85f..a80e93d 100644 --- a/test/daemon.test.js +++ b/test/daemon.test.js @@ -14,6 +14,12 @@ test('voice state machine handles wake, reply, cancel, and idle sleep', () => { assert.equal(voice.state, 'SLEEPING'); }); +test('typed questions wake an armed voice session before thinking', () => { + const voice = new VoiceStateMachine(); + voice.typedUtterance(); + assert.equal(voice.state, 'THINKING'); +}); + test('QVAC scheduler prioritizes voice and keeps one active job', async () => { const scheduler = new QvacScheduler(); const order = [];