Updates
Rolling release / release (push) Successful in 8m31s

This commit is contained in:
2026-09-13 22:24:14 -04:00
parent 1ca4224377
commit a097adf4eb
62 changed files with 2707 additions and 957 deletions
+171 -71
View File
@@ -1,10 +1,36 @@
import { EventEmitter } from 'node:events';
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { BrowserClient, resolveNodeBinary } from '../browser-use/client.js';
import { createBrowserTools } from '../skills/browser-tools.js';
const require = createRequire(import.meta.url);
const web = require('../vendor/agent-harness/agent/web-search.js');
const reader = require('../vendor/agent-harness/agent/web-reader.js');
const response = (url, text, status = 200, headers = {}) => ({ url: String(url), status, text: async () => text, headers: { get: key => headers[key] || null } });
test('browser-use maps Node builtins so Bare can load the sidecar client', () => {
const pkg = JSON.parse(readFileSync(new URL('../browser-use/package.json', import.meta.url), 'utf8'));
assert.equal(pkg.imports['node:child_process'].bare, 'bare-subprocess');
assert.equal(pkg.imports['node:os'].bare, 'bare-os');
assert.equal(pkg.imports['node:path'].bare, 'bare-path');
});
function mockBackend(handler) {
return { call: (action, payload, timeoutMs) => handler(action, payload, timeoutMs) };
}
function helper() {
const child = new EventEmitter();
child.stdout = new EventEmitter();
child.stderr = new EventEmitter();
child.stdin = new EventEmitter();
child.stdin.writable = true;
child.stdin.write = (chunk) => { child.written = String(chunk); return true; };
child.kill = () => { child.killed = true; };
return child;
}
test('reader extracts structured content, relative links, entities, pagination and find', () => {
const html = `<html><head><title>A &amp; B</title><meta name='description' content='A guide'></head><body><nav>Ignore navigation</nav><main><h1>Guide &#x1f680;</h1><p>${'Useful text. '.repeat(40)}</p><a href='../next?utm_source=x&amp;q=one'>Next</a><script>malicious()</script></main></body></html>`;
@@ -16,86 +42,160 @@ test('reader extracts structured content, relative links, entities, pagination a
assert.equal(page.next_offset, 200);
assert.equal(page.matches.length, 20);
assert.doesNotMatch(page.text, /navigation|malicious/);
assert.equal(reader.extractPage(html, 'https://example.com', { offset: 200 }).text, reader.extractPage(html, 'https://example.com').text.slice(200));
});
test('DDG parses reordered, single-quoted attributes and snippets', () => {
const hits = web.parseDdgHtmlHits(`<a href='/l/?uddg=https%3A%2F%2Fexample.com%2Fa%253Fb' class='extra result__a'>Title &#8212; test</a><div class='result__snippet'>The snippet</div>`);
assert.equal(hits[0].url, 'https://example.com/a%3Fb');
assert.equal(hits[0].title, 'Title — test');
assert.equal(hits[0].snippet, 'The snippet');
assert.equal(web.parseDdgLiteHits(`<a href='https://example.com' class='result-link'>Lite</a>`)[0].title, 'Lite');
});
test('auto merges scraped engines and removes tracking duplicates without calling APIs', async () => {
const orig = globalThis.fetch; const calls = [];
globalThis.fetch = async url => {
calls.push(String(url));
if (String(url).includes('duckduckgo')) return response(url, '<a class="result__a" href="https://example.com/a?utm_source=ddg">Example</a>');
if (String(url).includes('bing')) return response(url, '<rss><item><title>Example</title><link>https://example.com/a</link></item><item><title>Other</title><link>https://other.example/b</link></item></rss>');
return response(url, '');
};
test('web search and fetch go through the browser helper instead of HTML scrapers', async () => {
const calls = [];
web.setBrowserBackend(mockBackend(async (action, payload) => {
calls.push([action, payload.engine || payload.url]);
if (action === 'search') return [{ url: 'https://example.com/hit', title: 'Example', snippet: 'Hello', source: payload.engine }];
return { url: payload.url, status: 200, html: '<html><body><p>Readable article</p></body></html>', text: 'Readable article', via: 'browser' };
}));
try {
const hits = await web.runWebSearch('example', { limit: 2, timeoutMs: 500 });
assert.equal(hits.length, 2);
assert.deepEqual(hits[0].sources, ['duckduckgo', 'bing_rss']);
assert.equal(hits[0].url, 'https://example.com/a');
assert.ok(calls.every(url => !/jina|api\.|\/api\//.test(url)));
assert.match((await web.runWebSearch('test', { engine: 'jina' })).error, /unknown engine/);
} finally { globalThis.fetch = orig; }
const hits = await web.runWebSearch('example', { timeoutMs: 200 });
assert.equal(hits[0].url, 'https://example.com/hit');
assert.equal(calls[0][0], 'search');
const page = await web.fetchPage('https://example.com/article', 200);
assert.equal(page.via, 'browser');
assert.match(page.text, /Readable article/);
const google = await web.googleSearchWithFallback('example', 200);
assert.equal(google[0].source, 'google');
} finally {
web.setBrowserBackend(null);
}
});
test('direct fetch checks redirects, rejects binary pages, and reports challenges', async () => {
const orig = globalThis.fetch; let calls = 0;
test('unknown engines and private URLs fail before Chromium starts', async () => {
web.setBrowserBackend(mockBackend(async () => assert.fail('backend should not run')));
try {
globalThis.fetch = async url => { calls++; return response(url, '', 302, { location: 'http://127.0.0.1/secret' }); };
assert.match((await web.fetchPage('https://example.com')).error, /blocked/);
assert.equal(calls, 1);
globalThis.fetch = async url => response(url, 'binary', 200, { 'content-type': 'application/pdf' });
assert.match((await web.fetchPage('https://example.com')).error, /unsupported content type/);
globalThis.fetch = async url => response(url, '<p>Verify you are human</p>');
assert.match((await web.fetchPage('https://challenge.example.com')).warning, /challenge/);
globalThis.fetch = async url => response(url, 'x'.repeat(2 * 1024 * 1024 + 1));
assert.match((await web.fetchPage('https://example.com')).error, /limit/);
} finally { globalThis.fetch = orig; }
const unknown = await web.runWebSearch('example', { engine: 'jina' });
assert.match(String(unknown.error), /unknown engine/);
const blocked = await web.fetchPage('http://127.0.0.1/secret', 200);
assert.match(String(blocked.error), /blocked/);
} finally {
web.setBrowserBackend(null);
}
});
test('stalled streaming bodies are cancelled at the deadline', async () => {
let cancelled = false;
const res = new Response(new ReadableStream({ cancel() { cancelled = true; } }));
await assert.rejects(web.readBodyWithTimeout(res, 30), /timed out/);
assert.equal(cancelled, true);
});
test('challenge responses fall back and cool down related search hosts', async () => {
const orig = globalThis.fetch; const calls = [];
globalThis.fetch = async url => {
calls.push(String(url));
if (String(url).includes('duckduckgo')) return response(url, '<form id="challenge-form">Verify you are human</form>');
if (String(url).includes('bing')) return response(url, '<rss><item><title>Available</title><link>https://available.example/page</link></item></rss>');
return response(url, '');
};
test('search and fetch stop at the overall budget instead of hanging', async () => {
web.setBrowserBackend(mockBackend(() => new Promise(() => {})));
try {
const hits = await web.runWebSearch('fallback', { limit: 1 });
assert.equal(hits[0].source, 'bing_rss');
const blocked = await web.runWebSearch('fallback', { engine: 'ddg_lite' });
assert.equal(blocked.code, 'bot_challenge');
assert.ok(blocked.retry_after_ms > 0);
assert.equal(calls.filter(url => url.includes('duckduckgo')).length, 1);
} finally { globalThis.fetch = orig; }
const started = Date.now();
const search = await web.runWebSearch('example', { timeoutMs: 80 });
assert.match(String(search.error), /timed out/i);
assert.ok(Date.now() - started < 500);
const page = await web.webFetch('https://example.com/ip', 40);
assert.match(String(page.error), /timed out/i);
assert.equal(page.url, 'https://example.com/ip');
} finally {
web.setBrowserBackend(null);
}
});
test('rate limits preserve retry guidance and never expose challenge content', async () => {
const orig = globalThis.fetch; let calls = 0;
globalThis.fetch = async url => { calls++; return response(url, 'blocked content', 429, { 'retry-after': '120' }); };
test('a remaining challenge tells the user to finish it in the Jarvis window', async () => {
web.setBrowserBackend(mockBackend(async () => ({
url: 'https://challenge.example/page',
status: 200,
html: '<html><title>Just a moment</title><body>Checking your browser</body></html>',
text: 'Checking your browser',
challenge: true,
next_action: 'Complete the prompt in the Jarvis browser window, then call the tool again.',
})));
try {
const result = await web.fetchPage('https://limited.example/page');
assert.equal(result.code, 'rate_limited');
assert.equal(result.retry_after_ms, 120000);
assert.equal(result.text, undefined);
assert.match(result.next_action, /browser/);
await web.fetchPage('https://limited.example/other');
assert.equal(calls, 1);
} finally { globalThis.fetch = orig; }
const page = await web.fetchPage('https://challenge.example/page', 200);
assert.equal(page.challenge, true);
assert.match(page.next_action, /Jarvis browser/);
} finally {
web.setBrowserBackend(null);
}
});
test('auto search falls back to Google when DuckDuckGo has no hits', async () => {
const engines = [];
web.setBrowserBackend(mockBackend(async (action, payload) => {
engines.push(payload.engine);
if (payload.engine === 'duckduckgo') return [];
return [{ url: 'https://example.com/g', title: 'From Google', source: 'google' }];
}));
try {
const hits = await web.runWebSearch('example', { timeoutMs: 200 });
assert.deepEqual(engines, ['duckduckgo', 'google']);
assert.equal(hits[0].source, 'google');
} finally {
web.setBrowserBackend(null);
}
});
test('code_search queries GitHub, npm, and MDN through the helper', async () => {
const engines = [];
web.setBrowserBackend(mockBackend(async (_action, payload) => {
engines.push(payload.engine);
return [{ url: 'https://example.com/' + payload.engine, title: payload.engine, source: payload.engine }];
}));
try {
const out = await web.codeSearch('playwright', 200);
assert.deepEqual(engines, ['github', 'npm', 'mdn']);
assert.equal(out.github[0].source, 'github');
assert.equal(out.npm[0].source, 'npm');
assert.equal(out.mdn[0].source, 'mdn');
} finally {
web.setBrowserBackend(null);
}
});
test('browser client maps spawn ENOENT to a Node install hint', async () => {
const child = helper();
const client = new BrowserClient({
lookupBin: false,
spawnImpl: () => {
setImmediate(() => child.emit('error', Object.assign(new Error('no such file or directory'), { code: 'ENOENT' })));
return child;
},
timeoutMs: 80,
});
const result = await client.call('navigate', { url: 'https://example.com' });
assert.match(String(result.error), /Node\.js is not available to jarvisd/);
assert.doesNotMatch(String(result.error), /^no such file or directory$/i);
});
test('browser client explains a missing helper script', async () => {
const node = resolveNodeBinary() || process.execPath;
const client = new BrowserClient({ node, command: '/no/such/jarvis-browser-helper.js' });
const result = await client.call('snapshot', {});
assert.match(String(result.error), /browser helper is missing/);
});
test('browser helper speaks JSON lines and the client times out hung actions', async () => {
const child = helper();
const client = new BrowserClient({ spawnImpl: () => child, timeoutMs: 80 });
const ready = client.ensure();
child.stdout.emit('data', '{"type":"ready","headless":true}\n');
await ready;
const pending = client.call('fetch', { url: 'https://example.com' }, 50);
await new Promise((resolve) => setImmediate(resolve));
assert.match(child.written, /"action":"fetch"/);
child.stdout.emit('data', '{"id":1,"ok":true,"result":{"url":"https://example.com","title":"Example","text":"ok"}}\n');
assert.equal((await pending).title, 'Example');
const hungStarted = Date.now();
const hung = await client.call('snapshot', {}, 40);
assert.match(String(hung.error), /timed out/i);
assert.ok(Date.now() - hungStarted >= 20);
client.close();
assert.equal(child.killed, true);
});
test('the browser gateway forwards snapshot click and type', async () => {
const calls = [];
const tools = createBrowserTools({
browser: { call: async (action, payload) => { calls.push([action, payload.ref || payload.url]); return { ok: true, action, refs: [{ ref: '1', name: 'Next' }] }; } },
});
assert.equal(tools[0].name, 'browser');
assert.equal(tools[0].permission, 'read');
assert.deepEqual(tools[0].parameters.properties.action.enum, ['navigate', 'snapshot', 'click', 'type', 'press', 'scroll', 'wait']);
assert.match(tools[0].description, /same Playwright session/);
assert.match(tools[0].description, /refs change/);
await tools[0].execute({ action: 'snapshot' });
await tools[0].execute({ action: 'click', ref: '1' });
await tools[0].execute({ action: 'navigate', url: 'https://example.com' });
assert.deepEqual(calls, [['snapshot', undefined], ['click', '1'], ['navigate', 'https://example.com']]);
});