Files
gnome-jarvis/test/browser-continuation.test.js
T
snxraven 3c9257ce88
Rolling release / release (push) Successful in 11m4s
Updates
2026-09-14 12:24:05 -04:00

71 lines
4.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { createBrowserTools } from '../skills/browser-tools.js';
const require = createRequire(import.meta.url);
const engine = require('../vendor/agent-harness/lib/qvac.js');
const sessions = require('../vendor/agent-harness/agent/sessions.js');
const custom = require('../vendor/agent-harness/agent/custom-tools.js');
const loop = require('../vendor/agent-harness/agent/loop.js');
for (const remote of [false, true]) {
for (const outcome of ['answer', 'empty', 'error', 'cancel']) {
test(`browser reading reaches continuation and recovers (${remote ? 'remote' : 'local'}, ${outcome})`, async (t) => {
const root = mkdtempSync(path.join(tmpdir(), 'jarvis-browser-turn-'));
const previousRoot = process.env.AGENT_HARNESS_HOME;
process.env.AGENT_HARNESS_HOME = root;
const original = Object.fromEntries(['complete', 'getLoaded', 'localLoaded', 'remoteActive', 'remoteVision', 'cancel'].map(k => [k, engine[k]]));
t.after(() => {
Object.assign(engine, original);
if (previousRoot === undefined) delete process.env.AGENT_HARNESS_HOME;
else process.env.AGENT_HARNESS_HOME = previousRoot;
rmSync(root, { recursive: true, force: true });
});
const loaded = () => ({ modelId: 'test-model', friendlyId: 'test-model', ctxSize: 32768 });
engine.getLoaded = engine.localLoaded = loaded;
engine.remoteActive = () => remote;
engine.remoteVision = () => false;
engine.cancel = async () => {};
const meta = sessions.create({ origin: 'jarvis-qvac', cwd: root, hostWorkspace: false, builtinTools: false, model: 'test-model' });
custom.register(meta.id, createBrowserTools({ browser: { call: async () => ({
url: 'https://example.com/article', text: 'The observatory opens at nine. Tickets cost twelve dollars.',
images: [{ path: '/tmp/not-needed.jpg' }],
}) } }));
t.after(() => loop.finishLive(meta.id));
loop.markLive(meta.id);
let completions = 0;
engine.complete = async (opts) => {
completions++;
if (completions === 1) return { text: '', toolCalls: [{ id: 'browse1', name: 'browser', arguments: { action: 'navigate', url: 'https://example.com/article' } }] };
assert.equal(opts.webProcess, remote, 'internal answer-only reminder must not change provider');
assert.equal(opts.timeoutMs, 90000);
assert.equal(opts.idleMs, 45000);
assert.ok(opts.history.some(m => m.role === 'tool' && m.tool_call_id === 'browse1' && m.content.includes('page_text:\nThe observatory opens at nine. Tickets cost twelve dollars.')));
assert.deepEqual(opts.tools, [], 'answer-only budget remains respected');
if (outcome === 'error') throw new Error('inference unavailable');
if (outcome === 'cancel') {
loop.cancel(meta.id);
return { text: 'Do not deliver cancelled answer.', toolCalls: [] };
}
if (completions === 2 || outcome === 'empty') return { text: ' ', thinking: 'Considering the page.', toolCalls: [] };
return { text: 'The observatory opens at nine; tickets are twelve dollars.', toolCalls: [] };
};
const run = loop.runTurn({ session: sessions.load(meta.id), userText: 'Find visiting hours and prices.', jobId: 'browser-test', payload: { voice: true, maxTurns: 4, maxToolRounds: 1 }, emit() {} });
if (outcome === 'empty' || outcome === 'error') {
await assert.rejects(run, outcome === 'empty' ? /could not finish processing/ : /inference unavailable/);
} else {
const result = await run;
if (outcome === 'cancel') { assert.equal(result.reason, 'cancelled'); assert.equal(result.text, ''); }
else {
assert.equal(result.text, 'The observatory opens at nine; tickets are twelve dollars.');
assert.equal(sessions.load(meta.id).history.at(-1).content, result.text);
}
}
assert.equal(completions, ['error', 'cancel'].includes(outcome) ? 2 : 3);
});
}
}