254 lines
12 KiB
JavaScript
254 lines
12 KiB
JavaScript
import { createRequire } from 'node:module';
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createRuntimeTools } from '../skills/runtime-tools.js';
|
|
import { assertSdkVersion } from '../daemon/qvac-master.js';
|
|
import { parseHudSidecar, VOICE_SYSTEM_PROMPT, voiceSystemPrompt } from '../skills/voice-prompt.js';
|
|
import { createPhase2Tools } from '../skills/phase2-tools.js';
|
|
import { createQvacTools } from '../skills/qvac-tools.js';
|
|
import { profile } from '../daemon/model-profiles.js';
|
|
|
|
test('runtime tools expose local QVAC and computer-use status', () => {
|
|
const computer = { status: () => ({ active: true, steps_used: 2, backend: 'portal-ei' }) };
|
|
const tools = createRuntimeTools({ computer });
|
|
const status = tools.find((tool) => tool.name === 'jarvis_status').execute({});
|
|
assert.equal(status.local, true);
|
|
assert.equal(status.computer_use.active, true);
|
|
assert.equal(tools.find((tool) => tool.name === 'cu_status').execute({}).backend, 'portal-ei');
|
|
});
|
|
|
|
test('the copied harness uses the pinned QVAC 0.19.x SDK', () => {
|
|
assert.match(assertSdkVersion(), /^0\.19\./);
|
|
});
|
|
|
|
test('voice sidecars are removed from speech and retained for the HUD', () => {
|
|
const parsed = parseHudSidecar('Done. <jarvis_hud>{"title":"Done","chips":[]}</jarvis_hud>');
|
|
assert.equal(parsed.spoken, 'Done.');
|
|
assert.equal(parsed.hud.title, 'Done');
|
|
});
|
|
|
|
test('web_search and fetch_page stop at the overall budget instead of hanging', async () => {
|
|
const require = createRequire(import.meta.url);
|
|
const tools = require('../vendor/agent-harness/agent/tools.js');
|
|
const orig = globalThis.fetch;
|
|
globalThis.fetch = () => new Promise(() => {});
|
|
try {
|
|
const started = Date.now();
|
|
const search = await tools.runWebSearch('example', { timeoutMs: 80 });
|
|
const searchMs = Date.now() - started;
|
|
assert.match(String(search.error), /timed out/i);
|
|
assert.ok(searchMs >= 40, 'search returned too fast: ' + searchMs + 'ms');
|
|
assert.ok(searchMs < 500, 'search hung for ' + searchMs + 'ms');
|
|
assert.ok(Array.isArray(search.tried) && search.tried.length >= 1);
|
|
|
|
const pageStarted = Date.now();
|
|
const page = await tools.fetchPage('https://example.com/article', 80);
|
|
const pageMs = Date.now() - pageStarted;
|
|
assert.match(String(page.error), /timed out/i);
|
|
assert.ok(pageMs >= 40, 'fetch_page returned too fast: ' + pageMs + 'ms');
|
|
assert.ok(pageMs < 500, 'fetch_page hung for ' + pageMs + 'ms');
|
|
} finally {
|
|
globalThis.fetch = orig;
|
|
}
|
|
});
|
|
|
|
test('web_fetch times out instead of hanging the turn', async () => {
|
|
const require = createRequire(import.meta.url);
|
|
const tools = require('../vendor/agent-harness/agent/tools.js');
|
|
const orig = globalThis.fetch;
|
|
globalThis.fetch = () => new Promise(() => {});
|
|
try {
|
|
const hung = await tools.webFetch('https://example.com/ip', 40);
|
|
assert.match(String(hung.error), /timed out/i);
|
|
assert.equal(hung.url, 'https://example.com/ip');
|
|
} finally {
|
|
globalThis.fetch = orig;
|
|
}
|
|
globalThis.fetch = async (url) => ({
|
|
status: 200,
|
|
url: String(url),
|
|
text: async () => '203.0.113.8',
|
|
});
|
|
try {
|
|
const ok = await tools.webFetch('https://ifconfig.me/ip', 200);
|
|
assert.equal(ok.status, 200);
|
|
assert.equal(ok.url, 'https://ifconfig.me/ip');
|
|
assert.equal(ok.text, '203.0.113.8');
|
|
} finally {
|
|
globalThis.fetch = orig;
|
|
}
|
|
});
|
|
|
|
test('google_search parses lite HTML and falls back to DuckDuckGo then Bing', async () => {
|
|
const require = createRequire(import.meta.url);
|
|
const tools = require('../vendor/agent-harness/agent/tools.js');
|
|
const parsed = tools.parseGoogleHits(
|
|
'<a href="/url?q=https://example.com/page&sa=U"><div class="BNeawe vvjwJb AP7Wnd">Example Domain</div></a>'
|
|
);
|
|
assert.equal(parsed[0].url, 'https://example.com/page');
|
|
assert.equal(parsed[0].title, 'Example Domain');
|
|
assert.equal(tools.parseGoogleHits('<title>Google Search</title>').length, 0);
|
|
|
|
const bingHref =
|
|
'https://www.bing.com/ck/a?!&&p=ae&u=a1aHR0cDovL3d3dy5leGFtcGxlLmNvbS8&ntb=1';
|
|
const bingHits = tools.parseBingHits(
|
|
'<li class="b_algo"><h2><a href="' + bingHref + '"><strong>Example Domain</strong></a></h2></li>'
|
|
);
|
|
assert.equal(bingHits[0].url, 'http://www.example.com/');
|
|
assert.equal(bingHits[0].title, 'Example Domain');
|
|
|
|
const orig = globalThis.fetch;
|
|
globalThis.fetch = async (url) => {
|
|
const href = String(url);
|
|
if (href.includes('google.com')) {
|
|
return { status: 200, url: href, text: async () => '<title>Google Search</title>' };
|
|
}
|
|
if (href.includes('duckduckgo.com')) {
|
|
return {
|
|
status: 202,
|
|
url: href,
|
|
text: async () => '<div class="anomaly-modal__title">Unfortunately, bots use DuckDuckGo too.</div>',
|
|
};
|
|
}
|
|
return {
|
|
status: 200,
|
|
url: href,
|
|
text: async () =>
|
|
'<li class="b_algo"><h2><a href="' + bingHref + '"><strong>Example Domain</strong></a></h2></li>',
|
|
};
|
|
};
|
|
try {
|
|
const hits = await tools.googleSearchWithFallback('example', 200);
|
|
assert.equal(hits[0].source, 'bing');
|
|
assert.equal(hits[0].url, 'http://www.example.com/');
|
|
assert.equal(hits[0].title, 'Example Domain');
|
|
} finally {
|
|
globalThis.fetch = orig;
|
|
}
|
|
});
|
|
|
|
test('web_search can pin an engine and fetch_page prefers Jina', async () => {
|
|
const require = createRequire(import.meta.url);
|
|
const tools = require('../vendor/agent-harness/agent/tools.js');
|
|
const webSearch = require('../vendor/agent-harness/agent/web-search.js');
|
|
const unknown = await tools.runWebSearch('example', { engine: 'nope' });
|
|
assert.match(String(unknown.error), /unknown engine/);
|
|
assert.ok(Array.isArray(unknown.engines));
|
|
|
|
const rss = webSearch.parseRssItems(
|
|
'<rss><item><title>Example</title><link>https://example.com/rss</link><description>Hello</description></item></rss>'
|
|
);
|
|
assert.equal(rss[0].url, 'https://example.com/rss');
|
|
assert.equal(rss[0].title, 'Example');
|
|
|
|
const orig = globalThis.fetch;
|
|
globalThis.fetch = async (url) => {
|
|
const href = String(url);
|
|
if (href.includes('wikipedia.org')) {
|
|
return {
|
|
status: 200,
|
|
url: href,
|
|
text: async () => JSON.stringify({
|
|
query: { search: [{ title: 'Example.com', snippet: 'an <span>example</span> domain' }] },
|
|
}),
|
|
};
|
|
}
|
|
if (href.includes('r.jina.ai')) {
|
|
return { status: 200, url: href, text: async () => '# Example\nReadable article from Jina reader' };
|
|
}
|
|
throw new Error('unexpected fetch ' + href);
|
|
};
|
|
try {
|
|
const wiki = await tools.runWebSearch('example.com', { engine: 'wikipedia', timeoutMs: 200 });
|
|
assert.equal(wiki[0].source, 'wikipedia');
|
|
assert.equal(wiki[0].title, 'Example.com');
|
|
assert.match(wiki[0].url, /wikipedia\.org\/wiki\/Example\.com/);
|
|
const page = await tools.fetchPage('https://example.com/article', 200);
|
|
assert.equal(page.via, 'jina');
|
|
assert.match(page.text, /Readable article from Jina reader/);
|
|
} finally {
|
|
globalThis.fetch = orig;
|
|
}
|
|
});
|
|
|
|
test('public web search and fetch do not require confirmation', () => {
|
|
const require = createRequire(import.meta.url);
|
|
const policy = require('../vendor/agent-harness/agent/policy.js');
|
|
assert.equal(policy.needsPermission('web_fetch', 'ask'), false);
|
|
assert.equal(policy.needsPermission('fetch_page', 'ask'), false);
|
|
assert.equal(policy.needsPermission('google_search', 'ask'), false);
|
|
assert.equal(policy.needsPermission('web_search', 'ask'), false);
|
|
assert.equal(policy.needsPermission('wiki_search', 'ask'), false);
|
|
assert.equal(policy.needsPermission('hn_search', 'ask'), false);
|
|
assert.equal(policy.needsPermission('code_search', 'ask'), false);
|
|
assert.equal(policy.needsPermission('run_terminal_cmd', 'ask'), true);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Follow SOUL\.md, IDENTITY\.md, AGENTS\.md/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /PERSONA\.md/);
|
|
assert.doesNotMatch(VOICE_SYSTEM_PROMPT, /User personality notes/);
|
|
assert.doesNotMatch(VOICE_SYSTEM_PROMPT, /\n## Acting\n/);
|
|
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /Your name is Ada/);
|
|
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /\n## Acting\nBe dry\. Skip filler\./);
|
|
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /override SOUL\.md/);
|
|
assert.doesNotMatch(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /User personality notes/);
|
|
assert.doesNotMatch(voiceSystemPrompt('Tony', 'Jarvis, you are acting as Tony Hinchcliffe.'), /You are Tony, a local Ubuntu GNOME voice assistant/);
|
|
const roast = voiceSystemPrompt('Tony', 'Jarvis, you are acting as Tony Hinchcliffe, roast comedian.\nYou must never call yourself Jarvis.');
|
|
assert.match(roast, /Your name is Tony/);
|
|
assert.match(roast, /## Acting/);
|
|
assert.match(roast, /never call yourself Jarvis/);
|
|
assert.match(roast, /still speak as Tony/);
|
|
assert.equal(policy.needsPermission('write_file', 'ask'), true);
|
|
assert.equal(policy.isIdentityPath('USER.md'), true);
|
|
assert.equal(policy.isIdentityPath('PERSONA.md'), true);
|
|
assert.equal(policy.isIdentityPath('memory/2026-09-12.md'), true);
|
|
assert.equal(policy.isIdentityPath('/tmp/secret.txt'), false);
|
|
});
|
|
|
|
test('voice prompt tells the model not to chain extra terminal commands', () => {
|
|
assert.match(VOICE_SYSTEM_PROMPT, /call run_terminal_cmd once/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Do not chain extra commands/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Keep a space between every word/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Never wrap words in asterisks/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Quantum Verse Automatic Computer/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /spell it as Q V A C/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Internet protocol addresses have no dots/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Spell them as separate letters/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /web_fetch/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /web_search, google_search, fetch_page, web_fetch, wiki_search, hn_search, and code_search are unrestricted/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Use web_search to find pages/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /call web_search or fetch_page again/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /times out or errors/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Do not keep searching the same query/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /call todo_write/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Do not repeat a sentence/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Do not stop in thoughts/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /ifconfig\.me\/ip/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /This computer can reach the internet/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /HTTP access is not allowed/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Tool names, tool arguments/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /File tools may read any path they accept/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /Allow now/);
|
|
assert.match(VOICE_SYSTEM_PROMPT, /call cu_observe to read the live PipeWire frame buffer/);
|
|
assert.doesNotMatch(VOICE_SYSTEM_PROMPT, /Never claim cloud access/);
|
|
});
|
|
|
|
test('phase 2 registers safe local tools with permission metadata', async () => {
|
|
const tools = createPhase2Tools({ cwd: process.cwd() });
|
|
assert.deepEqual(tools.map((tool) => tool.name), ['app_list', 'fs_search', 'fs_read', 'fs_write', 'memory_recall', 'memory_remember', 'rag_workspaces', 'capability_status']);
|
|
assert.equal(tools.find((tool) => tool.name === 'fs_search').permission, 'read');
|
|
assert.ok((await tools.find((tool) => tool.name === 'fs_search').execute({ query: 'ROADMAP' })).some((x) => x.endsWith('ROADMAP.md')));
|
|
assert.deepEqual(await tools.find((tool) => tool.name === 'fs_write').execute({ file: 'nope.txt', contents: 'x', confirmed: false }), { confirmation_required: true, action: 'write', file: 'nope.txt' });
|
|
});
|
|
|
|
test('QVAC utility tools are exposed only through the master adapter', () => {
|
|
assert.deepEqual(createQvacTools().map((tool) => tool.name), ['qvac_runtime_state', 'qvac_system_resources', 'qvac_assess_model_fit']);
|
|
});
|
|
|
|
test('model profiles select one master model without creating another runtime', () => {
|
|
assert.equal(profile('laptop-4gb-mm').model, 'qwen3.5-0.8b');
|
|
assert.equal(profile('laptop-4gb-mm').vision, true);
|
|
assert.equal(profile('laptop-8gb-mm').model, 'qwen3.5-2b');
|
|
assert.equal(profile('laptop-8gb-mm').vision, true);
|
|
assert.equal(profile('desktop-gpu').model, 'qwen3.5-9b');
|
|
assert.throws(() => profile('missing'), /unknown Jarvis model profile/);
|
|
});
|