Updates
Rolling release / release (push) Successful in 8m31s

This commit is contained in:
2026-09-13 15:08:05 -04:00
parent b56171da9b
commit c5ccaa490b
29 changed files with 933 additions and 146 deletions
+51 -7
View File
@@ -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) })); }