+51
-7
@@ -24,6 +24,12 @@ import { StateRecovery } from './recovery.js';
|
||||
import { spokenReply, voiceSystemPrompt } from '../skills/voice-prompt.js';
|
||||
import { ensureAgentWorkspace, applyAssistantName, applyAssistantPrompt, normalizeAssistantName } from './agent-workspace.js';
|
||||
|
||||
function chunkText(ev) {
|
||||
if (ev == null) return '';
|
||||
if (typeof ev === 'string' || typeof ev === 'number' || typeof ev === 'boolean') return String(ev);
|
||||
return String(ev.text || ev.delta || ev.content || ev.chunk || '');
|
||||
}
|
||||
|
||||
export class JarvisDaemon extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -46,6 +52,8 @@ export class JarvisDaemon extends EventEmitter {
|
||||
this.harness = new HarnessBridge({ cwd: this.workspace, computer: this.computer, observer: this.observer, actuator: this.actuator, fsAccess: this.settings.fsAccess });
|
||||
this.log = new PrivacyLog();
|
||||
this.locked = false;
|
||||
this.muted = false;
|
||||
this.listenEnabled = true;
|
||||
this.lastReply = '';
|
||||
this.voiceLoop = null;
|
||||
this._activeAsk = null;
|
||||
@@ -54,8 +62,11 @@ export class JarvisDaemon extends EventEmitter {
|
||||
this._idleTimer.unref?.();
|
||||
this.telemetry = new RuntimeTelemetry();
|
||||
this._telemetryTimer = setInterval(() => this.telemetry.sample(), 60_000); this._telemetryTimer.unref?.();
|
||||
this.harness.on('agent_message_chunk', (ev) => this.emit('Token', ev?.text || ev?.delta || ''));
|
||||
this.harness.on('agent_thought_chunk', (ev) => this.emit('Thinking', String(ev?.text || ev?.delta || '')));
|
||||
this.harness.on('agent_message_chunk', (ev) => this.emit('Token', chunkText(ev)));
|
||||
this.harness.on('agent_thought_chunk', (ev) => {
|
||||
const text = chunkText(ev);
|
||||
if (text) this.emit('Thinking', text);
|
||||
});
|
||||
this.harness.on('tool_call', (ev) => this.emit('ToolCall', JSON.stringify({ name: ev?.call?.name || 'tool', arguments: ev?.call?.arguments || {} })));
|
||||
this.harness.on('tool_result', (ev) => this.emit('ToolResult', JSON.stringify({ name: ev?.name || 'tool', result: ev?.result || '', toolCallId: ev?.toolCallId || '' })));
|
||||
this.harness.on('permission', (ev) => this.emit('ConfirmationRequired', ev));
|
||||
@@ -64,7 +75,8 @@ 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;
|
||||
if (this.locked || this.muted) return;
|
||||
this.listenEnabled = true;
|
||||
await resumeQvac().catch(() => {});
|
||||
await this.ensureAsr();
|
||||
this.voice.wake();
|
||||
@@ -143,7 +155,7 @@ export class JarvisDaemon extends EventEmitter {
|
||||
}
|
||||
_finishSpeech() {
|
||||
try { this.voice.finishSpeaking(); } catch {}
|
||||
if (this.settings.listeningMode !== 'conversation') { this.voice.cancel(); this.setState('ARMED'); }
|
||||
if (this.muted || !this.listenEnabled || this.settings.listeningMode !== 'conversation') { this.voice.cancel(); this.setState('ARMED'); }
|
||||
else this.setState('LISTENING');
|
||||
}
|
||||
_speakReply(spoken) {
|
||||
@@ -212,7 +224,7 @@ export class JarvisDaemon extends EventEmitter {
|
||||
});
|
||||
loop.on('error', (error) => { console.error(`jarvisd: voice: ${error.message}`); this.emit('Error', 'VOICE', error.message); });
|
||||
this.voiceLoop = loop;
|
||||
try { await loop.start(); this.emit('StateChanged', this.state); } catch (error) {
|
||||
try { await loop.start(); if (this.muted) loop.setMuted(true); 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.wake) console.log('jarvisd: voice: wake detector ready');
|
||||
@@ -261,8 +273,40 @@ export class JarvisDaemon extends EventEmitter {
|
||||
try { await this.voiceLoop.speak(preview); } finally { if (generation === this._askGeneration) this._finishSpeech(); }
|
||||
}
|
||||
stopSpeech() { this.voiceLoop?.interrupt?.(); this._finishSpeech(); }
|
||||
setPushToTalk(pressed) { this.voiceLoop?.setPushToTalk(pressed); this.emit('PushToTalk', Boolean(pressed)); }
|
||||
runtimeStatus() { return JSON.stringify({ local: true, qvac: qvacStatus(), scheduler: this.scheduler.status(), scheduler_metrics: this.scheduler.metrics(), telemetry: this.telemetry.snapshot(), settings: this.settings, computer: this.computer.status(), voice: this.voiceLoop ? { ...this.voiceLoop.metrics.snapshot(), ...this.voiceLoop.status } : null, p2p: { enabled: process.env.JARVIS_P2P_ENABLE === '1', inference: false, memorySync: false } }); }
|
||||
setPushToTalk(pressed) {
|
||||
if (this.muted && pressed) return;
|
||||
this.voiceLoop?.setPushToTalk(pressed);
|
||||
this.emit('PushToTalk', Boolean(pressed));
|
||||
}
|
||||
setMuted(muted) {
|
||||
const next = Boolean(muted);
|
||||
this.muted = next;
|
||||
if (next) this.listenEnabled = false;
|
||||
this.voiceLoop?.setMuted?.(next);
|
||||
if (next) {
|
||||
this.voiceLoop?.setPushToTalk?.(false);
|
||||
if (this.state === 'LISTENING') {
|
||||
this.voice.cancel();
|
||||
this.setState('ARMED');
|
||||
} else {
|
||||
this.emit('StateChanged', this.state);
|
||||
}
|
||||
} else {
|
||||
this.emit('StateChanged', this.state);
|
||||
}
|
||||
}
|
||||
setListening(on) {
|
||||
if (this.muted) return;
|
||||
if (on) return this.arm();
|
||||
this.listenEnabled = false;
|
||||
this.voiceLoop?.setPushToTalk?.(false);
|
||||
try { this.voiceLoop?.vad?.reset?.(); } catch {}
|
||||
if (this.state === 'LISTENING') {
|
||||
this.voice.cancel();
|
||||
this.setState('ARMED');
|
||||
}
|
||||
}
|
||||
runtimeStatus() { return JSON.stringify({ local: true, qvac: qvacStatus(), scheduler: this.scheduler.status(), scheduler_metrics: this.scheduler.metrics(), telemetry: this.telemetry.snapshot(), settings: this.settings, computer: this.computer.status(), voice: this.voiceLoop ? { ...this.voiceLoop.metrics.snapshot(), ...this.voiceLoop.status, muted: this.muted } : { muted: this.muted }, muted: this.muted, p2p: { enabled: process.env.JARVIS_P2P_ENABLE === '1', inference: false, memorySync: false } }); }
|
||||
async assessModelFit(model) { return JSON.stringify(await callQvac('assessModelFit', { modelSrc: String(model) })); }
|
||||
async downloadModel(model) { return JSON.stringify(await callQvac('downloadAsset', { modelSrc: String(model) })); }
|
||||
async cancelModel(model) { return JSON.stringify(await cancelQvacRequest({ modelId: String(model) })); }
|
||||
|
||||
Reference in New Issue
Block a user