import { EventEmitter } from 'node:events'; import { acquireQvac, closeQvac, releaseQvac, Agent } from './qvac-master.js'; import { createRuntimeTools } from '../skills/runtime-tools.js'; import { createPhase2Tools } from '../skills/phase2-tools.js'; import { createQvacTools } from '../skills/qvac-tools.js'; import { VOICE_SYSTEM_PROMPT, parseHudSidecar } from '../skills/voice-prompt.js'; import { createComputerObserveTools } from '../skills/computer-observe.js'; import { createComputerActTools } from '../skills/computer-act.js'; import { createPhase9Tools } 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(), ...createPhase9Tools(), ...tools], permissionMode, origin: 'jarvis-qvac', system: VOICE_SYSTEM_PROMPT }; this.session = null; } async start() { if (this.session) return this.session; if (process.env.JARVIS_QVAC_MODEL && this.options.model !== process.env.JARVIS_QVAC_MODEL) { throw new Error(`Jarvis uses one QVAC master model (${process.env.JARVIS_QVAC_MODEL}); requested ${this.options.model}`); } await acquireQvac(); try { this.session = await Agent.create(this.options); } catch (error) { releaseQvac(); await closeQvac(); throw error; } for (const event of ['agent_message_chunk', 'tool_call', 'permission', 'ask_user', 'cap-chunk', 'error']) { this.session.on(event, (payload) => { if (event === 'agent_message_chunk' && payload?.text) { const parsed = parseHudSidecar(payload.text); if (parsed.hud) this.emit('hud_sidecar', parsed.hud); } this.emit(event, payload); }); } return this.session; } async ask(text) { if (!this.session) await this.start(); return this.session.prompt(text); } cancel() { this.session?.cancel(); } async close() { await this.session?.dispose(); releaseQvac(); await closeQvac(); } }