+44
-15
@@ -8,7 +8,7 @@ import { HarnessBridge } from './harness-bridge.js';
|
||||
import { ComputerUseSession } from '../computer-use/session.js';
|
||||
import { VoiceStateMachine } from './voice-state.js';
|
||||
import { QvacScheduler } from './qvac-scheduler.js';
|
||||
import { cancelQvac, resumeQvac, suspendQvac, callQvac, cancelQvacRequest, qvacStatus } from './qvac-master.js';
|
||||
import { cancelQvac, closeQvac, resumeQvac, suspendQvac, callQvac, cancelQvacRequest, qvacStatus } from './qvac-master.js';
|
||||
import { PrivacyLog } from './privacy-log.js';
|
||||
import { VoiceLoop } from './voice-loop.js';
|
||||
import { QvacVoiceAdapter } from './voice-adapters.js';
|
||||
@@ -37,7 +37,7 @@ export class JarvisDaemon extends EventEmitter {
|
||||
this.perception = new QvacPerception();
|
||||
this.observer = new DesktopObserver({ normalizer: new FrameNormalizer({ maxLongEdge: this.settings.screenshotMaxEdge, quality: this.settings.screenshotQuality }), ocr: (image) => this.perception.ocr(image) });
|
||||
this.actuator = new ComputerActuator({ session: this.computer, input: this.input, find: ({ ref }) => this.observer.lastTree.filter((node) => node.ref === ref), atspiAction: (target, action) => this.observer.atspi.action(target, action), highlight: async (target, action) => this.emit('ComputerHighlight', JSON.stringify({ rect: target?.rect || null, label: `${action} ${target?.name || ''}` })) , audit: this.audit });
|
||||
this.harness = new HarnessBridge({ cwd: process.cwd(), computer: this.computer, observer: this.observer, actuator: this.actuator });
|
||||
this.harness = new HarnessBridge({ cwd: process.cwd(), computer: this.computer, observer: this.observer, actuator: this.actuator, fsAccess: this.settings.fsAccess });
|
||||
this.log = new PrivacyLog();
|
||||
this.locked = false;
|
||||
this.lastReply = '';
|
||||
@@ -57,8 +57,32 @@ export class JarvisDaemon extends EventEmitter {
|
||||
}
|
||||
|
||||
setState(state) { this.state = state; try { this.recovery.save({ state, mode: this.mode }); } catch (error) { this.emit('Error', 'RECOVERY_WRITE', error.message); } this.emit('StateChanged', state); this.log.record('state', { state }).catch(() => {}); }
|
||||
async arm() { if (this.locked) return; await resumeQvac().catch(() => {}); this.voice.wake(); this.setState('LISTENING'); }
|
||||
async sleep() { this.cancel(); this.voice.sleep(); this.setState('SLEEPING'); await suspendQvac().catch((error) => this.emit('Error', 'QVAC_SUSPEND', error.message)); }
|
||||
async arm() {
|
||||
if (this.locked) return;
|
||||
await resumeQvac().catch(() => {});
|
||||
await this.ensureAsr();
|
||||
this.voice.wake();
|
||||
this.setState('LISTENING');
|
||||
}
|
||||
async sleep() {
|
||||
if (this._activeAsk) {
|
||||
await suspendQvac().catch((error) => this.emit('Error', 'QVAC_SUSPEND', error.message));
|
||||
return;
|
||||
}
|
||||
this.cancel();
|
||||
this.voice.sleep();
|
||||
this.setState('SLEEPING');
|
||||
if (this.settings.freeVramOnIdle !== false) await this.parkModels();
|
||||
else await suspendQvac().catch((error) => this.emit('Error', 'QVAC_SUSPEND', error.message));
|
||||
}
|
||||
async parkModels() {
|
||||
try { await this.voiceLoop?.parkModels?.(); }
|
||||
catch (error) { this.emit('Error', 'VOICE_UNLOAD', error.message); }
|
||||
try { await this.harness.resetContext(); }
|
||||
catch (error) { this.emit('Error', 'QVAC_UNLOAD', error.message); }
|
||||
try { await closeQvac(); }
|
||||
catch (error) { this.emit('Error', 'QVAC_CLOSE', error.message); }
|
||||
}
|
||||
say(text) {
|
||||
const spoken = spokenReply(text) || spokenReply(this.lastReply) || 'There is nothing to repeat.';
|
||||
this.lastReply = spoken;
|
||||
@@ -145,18 +169,21 @@ export class JarvisDaemon extends EventEmitter {
|
||||
if (!settings.ttsEnabled) { this.voiceLoop.status.tts = false; return; }
|
||||
if (this.voiceLoop.status.tts) return;
|
||||
if (!this.voiceLoop.tts) this.voiceLoop.tts = new QvacVoiceAdapter({ role: 'tts', settings });
|
||||
try {
|
||||
await this.voiceLoop.tts.start();
|
||||
this.voiceLoop.status.tts = true;
|
||||
delete this.voiceLoop.status.errors.tts;
|
||||
console.log('jarvisd: voice: TTS ready');
|
||||
} catch (error) {
|
||||
const message = String(error?.message || error);
|
||||
this.voiceLoop.status.errors.tts = message;
|
||||
const ready = await this.voiceLoop.ensureTts();
|
||||
if (ready) console.log('jarvisd: voice: TTS ready');
|
||||
else if (this.voiceLoop.status.errors.tts) {
|
||||
const message = this.voiceLoop.status.errors.tts;
|
||||
console.error(`jarvisd: voice: TTS: ${message}`);
|
||||
this.emit('Error', 'TTS', message);
|
||||
}
|
||||
}
|
||||
async ensureAsr() {
|
||||
await this._voiceStarting;
|
||||
if (!this.voiceLoop || !this.settings.microphoneEnabled) return;
|
||||
if (this.voiceLoop.status?.asr) return;
|
||||
if (!this.voiceLoop.asr) this.voiceLoop.asr = new QvacVoiceAdapter({ role: 'asr', settings: this.settings });
|
||||
await this.voiceLoop.ensureAsr?.();
|
||||
}
|
||||
cancel() { this._askGeneration += 1; this.voiceLoop?.interrupt?.(); this.harness.cancel(); this.scheduler.cancelQueued((job) => job.lane === 'voice'); this.computerRevoke(); this.voice.cancel(); this.setState('ARMED'); cancelQvac().catch((error) => this.emit('Error', 'QVAC_CANCEL', error.message)); }
|
||||
computerGrant(persist = false) { const result = this.computer.grant({ persist }); this.emit('ComputerStep', JSON.stringify({ action: 'grant', ...result })); if (this.settings.computerMode === 'observe') return result; this.input.grant({ persist }).then((backend) => { this.computer.setBackend(backend.backend); this.emit('ComputerStep', JSON.stringify({ action: 'backend', ...backend })); }).catch((error) => this.emit('Error', 'CU_GRANT', error.message)); return result; }
|
||||
computerRevoke() { this.input.revoke(); this.computer.revoke(); this.emit('ComputerStep', JSON.stringify({ action: 'revoke' })); }
|
||||
@@ -182,8 +209,10 @@ export class JarvisDaemon extends EventEmitter {
|
||||
try { await loop.start(); this.emit('StateChanged', this.state); } catch (error) {
|
||||
this.voiceLoop = null; await loop.stop?.().catch(() => {}); this.emit('Error', 'VOICE_UNAVAILABLE', error.message); throw error;
|
||||
}
|
||||
if (loop.status.tts) console.log('jarvisd: voice: TTS ready');
|
||||
else if (loop.status.errors.tts) console.error(`jarvisd: voice: TTS: ${loop.status.errors.tts}`);
|
||||
if (loop.status.wake) console.log('jarvisd: voice: wake detector ready');
|
||||
else if (loop.status.errors.wake) console.error(`jarvisd: voice: wake: ${loop.status.errors.wake}`);
|
||||
if (loop.status.capture) console.log('jarvisd: voice: microphone capture ready');
|
||||
else if (loop.status.errors.capture) console.error(`jarvisd: voice: capture: ${loop.status.errors.capture}`);
|
||||
}
|
||||
async reloadSettings() {
|
||||
if (this._settingsReload) return this._settingsReload;
|
||||
@@ -206,7 +235,7 @@ export class JarvisDaemon extends EventEmitter {
|
||||
Object.assign(this.observer.normalizer, { maxLongEdge: next.screenshotMaxEdge, quality: next.screenshotQuality });
|
||||
await this.startVoice();
|
||||
this.voice.cancel(); this.setState('ARMED');
|
||||
return JSON.stringify({ applied: true, voice: this.voiceLoop.status, environmentOverrides: ['JARVIS_TTS_MODEL', 'JARVIS_ASR_MODEL', 'JARVIS_WAKE_COMMAND', 'JARVIS_QVAC_MODEL'].filter(key => process.env[key]), restartRequired: ['chatModel', 'modelProfile', 'maxTurns', 'maxShellCalls', 'maxToolRounds'].filter(key => next[key] !== this.startupSettings[key]) });
|
||||
return JSON.stringify({ applied: true, voice: this.voiceLoop.status, environmentOverrides: ['JARVIS_TTS_MODEL', 'JARVIS_ASR_MODEL', 'JARVIS_WAKE_COMMAND', 'JARVIS_QVAC_MODEL'].filter(key => process.env[key]), restartRequired: ['chatModel', 'modelProfile', 'maxTurns', 'maxShellCalls', 'maxToolRounds', 'fsAccess'].filter(key => next[key] !== this.startupSettings[key]) });
|
||||
}
|
||||
async previewVoice(text) {
|
||||
if (this.locked) throw new Error('Unlock the desktop to preview a voice');
|
||||
|
||||
Reference in New Issue
Block a user