Updates
Rolling release / release (push) Successful in 6m40s

This commit is contained in:
2026-09-12 07:22:47 -04:00
parent e4546f8e95
commit e9040d110a
30 changed files with 1688 additions and 444 deletions
+22 -1
View File
@@ -11,7 +11,28 @@ import { createPhase9GatewayTool } from '../skills/phase9-tools.js';
export class HarnessBridge extends EventEmitter {
constructor({ cwd = process.cwd(), model = 'qwen3.5-4b', tools = [], computer, observer, actuator, permissionMode = 'ask' } = {}) {
super();
this.options = { cwd, model, tools: [...createRuntimeTools({ computer }), ...createPhase2Tools({ cwd, computer }), ...createComputerObserveTools({ computer, observer }), ...createComputerActTools({ actuator }), ...createQvacTools(), ...createPhase9GatewayTool(), ...tools], permissionMode, origin: 'jarvis-qvac', system: VOICE_SYSTEM_PROMPT };
this.options = {
cwd,
model,
tools: [
...createRuntimeTools({ computer }),
...createPhase2Tools({ cwd, computer }),
...createComputerObserveTools({ computer, observer }),
...createComputerActTools({ actuator }),
...createQvacTools(),
...createPhase9GatewayTool(),
...tools,
],
builtinTools: ['read_file', 'list_dir', 'grep', 'run_terminal_cmd', 'web_fetch', 'web_search'],
webFetch: true,
permissionMode,
origin: 'jarvis-qvac',
system: VOICE_SYSTEM_PROMPT,
voice: true,
maxTurns: 6,
maxShellCalls: 1,
maxToolRounds: 4,
};
this.session = null;
}
+11 -8
View File
@@ -102,21 +102,24 @@ export class JarvisDaemon extends EventEmitter {
if (this.state === 'SPEAKING') this.setState('LISTENING');
}
_speakReply(spoken) {
if (!this.voiceLoop) {
this._finishSpeech();
return;
}
const play = async () => {
await this.ensureTts();
const playing = this.voiceLoop?.speak?.(spoken);
if (!playing) {
if (!this.voiceLoop?.status?.tts) {
const reason = this.voiceLoop?.status?.errors?.tts;
if (reason && reason !== 'Spoken replies are disabled') this.emit('Error', 'TTS', reason);
this._finishSpeech();
return;
}
await playing;
await this.voiceLoop.speak(spoken);
};
Promise.resolve(play()).catch((error) => {
this.emit('Error', 'TTS', error.message);
if (this.state === 'SPEAKING') this._finishSpeech();
});
Promise.resolve(play())
.catch((error) => {
this.emit('Error', 'TTS', error.message);
})
.finally(() => this._finishSpeech());
}
async ensureTts() {
if (!this.voiceLoop) return;
+57
View File
@@ -1,5 +1,62 @@
export const MIN_UTTERANCE_CHARS = 3;
const SPOKEN_ACRONYMS = {
qvac: 'Quantum Verse Automatic Computer',
cpu: 'C P U',
gpu: 'G P U',
ram: 'R A M',
ssd: 'S S D',
hdd: 'H D D',
usb: 'U S B',
dns: 'D N S',
isp: 'I S P',
vpn: 'V P N',
ssh: 'S S H',
api: 'A P I',
url: 'U R L',
uri: 'U R I',
http: 'H T T P',
https: 'H T T P S',
html: 'H T M L',
json: 'J S O N',
xml: 'X M L',
os: 'O S',
ip: 'I P',
tts: 'T T S',
asr: 'A S R',
hud: 'heads up display',
llm: 'L L M',
cli: 'C L I',
gui: 'G U I',
ptt: 'P T T',
vad: 'V A D',
};
function spellAcronyms(text) {
return String(text || '').replace(/\b([A-Za-z]{2,6})\b/g, (word) => {
const spoken = SPOKEN_ACRONYMS[word.toLowerCase()];
return spoken || word;
});
}
function speakableAddresses(text) {
return String(text || '')
.replace(/\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/g, '$1 $2 $3 $4')
.replace(/https?:\/\//gi, '')
.replace(/\b([A-Za-z0-9-]+)\.(com|org|net|io|dev|local|lan)\b/gi, (_, host, tld) => `${host} dot ${tld}`)
.replace(/\//g, ' slash ')
.replace(/@/g, ' at ')
.replace(/_/g, ' ');
}
export function speakableForTts(text) {
return spellAcronyms(speakableAddresses(String(text || '')))
.replace(/[`]/g, "'")
.replace(/[<>]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
export function isSpeakable(text) {
const value = String(text || '').trim();
if (!value || value === '[object Object]') return false;
+1 -1
View File
@@ -49,7 +49,7 @@ export class QvacVoiceAdapter extends EventEmitter {
if (!this.ttsId) throw new Error('Speech output is unavailable');
const sdk = await qvacSdk();
const samples = await withQvacMaster(async () => {
const result = await sdk.textToSpeech({ modelId: this.ttsId, text: String(text), inputType: 'text', stream: false });
const result = await sdk.textToSpeech({ modelId: this.ttsId, text: String(text).replace(/[`]/g, "'"), inputType: 'text', stream: false });
if (result?.buffer != null) return await result.buffer;
if (result?.bufferStream) {
const chunks = [];
+32 -13
View File
@@ -3,7 +3,7 @@ import { PipeWireCapture } from './audio-pipewire.js';
import { PipeWirePlayback } from './audio-playback.js';
import { WakeEngine } from './wake-engine.js';
import { VadSegmenter } from './vad.js';
import { isMeaningfulTranscript, isSpeakable, SentenceBuffer } from './transcript.js';
import { isMeaningfulTranscript, isSpeakable, speakableForTts, SentenceBuffer } from './transcript.js';
import { VoiceMetrics } from './voice-metrics.js';
export const POST_PLAYBACK_COOLDOWN_MS = 400;
@@ -91,29 +91,48 @@ export class VoiceLoop extends EventEmitter {
_releaseSpeaking() {
this.isSpeaking = false;
this.wake.resume();
if (this.daemon?.state === 'SPEAKING') { this.daemon.voice?.finishSpeaking?.(); this.daemon.setState?.('LISTENING'); }
if (this.daemon?.state === 'SPEAKING') {
try { this.daemon.voice?.finishSpeaking?.(); } catch {}
this.daemon.setState?.('LISTENING');
}
}
async speak(text) {
const generation = this._generation || 0;
if (!isSpeakable(text) || !this.tts?.speak || !this.status.tts) { this._releaseSpeaking(); return; }
if (!isSpeakable(text) || !this.tts?.speak || !this.status.tts) {
this._releaseSpeaking();
return;
}
this.metrics.reply();
const sentences = String(text).match(/[^.!?]+[.!?]+|[^.!?]+$/g)?.map((part) => part.trim()).filter(isSpeakable) || [];
if (!sentences.length) { this._releaseSpeaking(); return; }
if (!sentences.length) {
this._releaseSpeaking();
return;
}
this._speechQueue = this._speechQueue.catch(() => {}).then(async () => {
if (generation !== this._generation) return;
for (const sentence of sentences) {
try {
if (generation !== this._generation) return;
await this.speakSentence(sentence);
for (const sentence of sentences) {
if (generation !== this._generation) return;
await this.speakSentence(sentence, generation);
}
} finally {
if (generation === this._generation) this._releaseSpeaking();
}
});
return this._speechQueue;
}
async speakSentence(text) {
this.isSpeaking = true; this.wake.pause(); this.daemon?.emit('StateChanged', 'SPEAKING');
try { const audio = await this.tts.speak(text); await this.playback.play(audio.samples); try { this.daemon?.emit('SpeakingLevel', 0); } catch {} }
finally {
this.isSpeaking = false; this.cooldownUntil = this.now() + this.cooldownMs; this.wake.resume();
if (this.daemon?.state === 'SPEAKING') { this.daemon.voice?.finishSpeaking?.(); this.daemon.setState?.('LISTENING'); }
async speakSentence(text, generation) {
this.isSpeaking = true;
this.wake.pause();
try {
const audio = await this.tts.speak(speakableForTts(text));
if (generation !== this._generation) return;
await this.playback.play(audio.samples);
try { this.daemon?.emit('SpeakingLevel', 0); } catch {}
} finally {
this.isSpeaking = false;
this.cooldownUntil = this.now() + this.cooldownMs;
this.wake.resume();
}
}
}