/** * Standalone agent harness. Local QVAC inference, sandbox tools, JSONL sessions. */ const path = require('path'); const { EventEmitter } = require('events'); const loop = require('./agent/loop.js'); const sessions = require('./agent/sessions.js'); const sandbox = require('./agent/sandbox.js'); const customTools = require('./agent/custom-tools.js'); const mcp = require('./agent/mcp.js'); const engine = require('./lib/qvac.js'); const catalog = require('./lib/catalog.js'); function emitBridge(emitter) { return function emit(_kind, payload) { const ev = payload || {}; emitter.emit('event', ev); if (ev.type) emitter.emit(ev.type, ev); }; } function wrapSession(summary, opts) { const emitter = new EventEmitter(); const emit = emitBridge(emitter); const sessionId = summary.id; function on(type, fn) { emitter.on(type, fn); return () => emitter.off(type, fn); } async function prompt(text, payload) { loop.markLive(sessionId); try { const session = sessions.load(sessionId); const jobId = 'job_' + Date.now().toString(36); const merged = Object.assign( { permissionMode: opts.permissionMode || 'ask', webFetch: opts.webFetch === true, browser: opts.browser, system: opts.system, maxTurns: opts.maxTurns, maxShellCalls: opts.maxShellCalls, maxToolRounds: opts.maxToolRounds, voice: opts.voice, }, payload || {} ); return await loop.runTurn({ session, userText: text, emit, jobId, payload: merged, }); } finally { loop.finishLive(sessionId); } } async function compact() { const session = sessions.load(sessionId); const ctxSize = (engine.getLoaded() && engine.getLoaded().ctxSize) || 8192; const toolDefs = []; const compactOpts = { budgetTokens: require('./agent/compaction.js').historyBudget(ctxSize, toolDefs, 0, 0, { voice: !!opts.voice }), tools: toolDefs, voice: !!opts.voice, }; session.history = opts.voice ? require('./agent/compaction.js').compact(session.history, compactOpts) : await require('./agent/compaction.js').compactWithLlm(session.history, Object.assign({}, compactOpts, { complete: (o) => engine.complete(Object.assign({}, o, { desktopVision: false })), })); sessions.replaceHistory(sessionId, session.history); return session.history; } return { id: sessionId, cwd: summary.cwd, model: summary.model, on, prompt, compact, permit(jobId, toolCallId, decision) { const d = decision === true || decision === 'allow' || decision === 'always' ? decision : 'deny'; loop.resolvePermission(jobId, toolCallId, d === true ? 'allow' : d); }, answer(toolCallId, choice) { loop.resolveAsk(null, toolCallId, choice); }, planDecision(decision) { loop.resolvePlanDecision(sessionId, decision); }, addTool(tool) { customTools.register(sessionId, tool); const session = sessions.load(sessionId); session.customTools = customTools.list(sessionId); sessions.saveSummary(session); return session.customTools; }, cancel() { loop.cancel(sessionId); }, async dispose() { loop.forget(sessionId); customTools.clear(sessionId); sessions.remove(sessionId); }, load() { return sessions.load(sessionId); }, }; } async function create(opts) { opts = opts || {}; const cwd = path.resolve(opts.cwd || process.cwd()); const extraRoots = Array.isArray(opts.roots) ? opts.roots.map((r) => path.resolve(r)) : []; sandbox.setGrants({ roots: [cwd].concat(extraRoots) }); const model = opts.model || 'qwen3.5-4b'; const summary = sessions.create({ origin: opts.origin || 'local', cwd, workspace: cwd, model, title: opts.title || path.basename(cwd), hostWorkspace: opts.hostWorkspace !== false, builtinTools: opts.builtinTools === false ? false : Array.isArray(opts.builtinTools) ? opts.builtinTools : undefined, goal: opts.goal || null, planMode: opts.planMode, sessionId: opts.sessionId, }); customTools.setSession(summary.id, { hostWorkspace: summary.hostWorkspace }); if (opts.tools) { customTools.register(summary.id, opts.tools); summary.customTools = customTools.list(summary.id); sessions.saveSummary(summary); } if (Array.isArray(opts.mcp)) { for (const spec of opts.mcp) await mcp.register(spec, opts); } return wrapSession(summary, opts); } function load(sessionId, opts) { const summary = sessions.load(sessionId); if (summary.cwd) sandbox.setGrants({ roots: [path.resolve(summary.cwd)].concat(opts && opts.roots ? opts.roots : []) }); if (summary.customTools) customTools.register(summary.id, summary.customTools); customTools.setSession(summary.id, { hostWorkspace: summary.hostWorkspace }); return wrapSession(summary, opts || {}); } module.exports = { create, load, list: () => sessions.list(), catalog: () => catalog.listCatalog(), suggest: (hw) => catalog.suggestProfile(hw), engine, mcp, sandbox, };