Row 95, round 1. The Board's first section lists each role a run holds
on the bus (from /api/bus/agents), with a Message button on a Console
started with --send. Machine sessions sit behind a "Show machine
sessions" checkbox, off by default, kept in localStorage; while off the
page never reads /api/board. Every Console fetch passes
redirect: 'manual'; the first opaqueredirect signs the page out: timers
stop, reads are dropped, one line says to reload, and the send form
keeps its draft.
Candidate manifest 246c0557 (agents/filbert/work/queue-95/), base
a3f6e9e6, no drift on the manifest paths. Builder Filbert. Approved by
Darkwing (comment 27953). Mutants 30/35 killed, 5 redundant guards.
Sage's gate 2026-10-12T21:52:13Z-21:54:35Z: webui 86/0, control-board
124/0, every scripts/test-*.sh green.
Live on landing: serve.mjs reads src/public on every request with
no-store (decision 123). Mos gave the go (sage-0115).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
83 lines
4.2 KiB
JavaScript
83 lines
4.2 KiB
JavaScript
// Small CDP client for the installed Chromium. No application dependencies.
|
|
import { spawn } from 'node:child_process';
|
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
// Row 95 (#1589): the Board's machine sessions are off by default. Most tests read the board scan,
|
|
// so by default every page this browser opens starts with Show machine sessions on, as if the
|
|
// person had turned it on before. Pass { machine: false } to see the page as a first visit does.
|
|
export async function browser({ machine = true } = {}) {
|
|
const profile = await mkdtemp(join(tmpdir(), 'dewey-brand-browser-'));
|
|
const child = spawn(process.env.CHROMIUM || '/usr/bin/chromium', [
|
|
'--headless', '--no-first-run', '--no-default-browser-check',
|
|
'--disable-dev-shm-usage', '--remote-debugging-pipe', `--user-data-dir=${profile}`,
|
|
], { stdio: ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'] });
|
|
let id = 0, buffer = '';
|
|
const pending = new Map();
|
|
child.stdio[4].on('data', chunk => {
|
|
buffer += chunk.toString();
|
|
let end;
|
|
while ((end = buffer.indexOf('\0')) !== -1) {
|
|
const message = JSON.parse(buffer.slice(0, end));
|
|
buffer = buffer.slice(end + 1);
|
|
if (pending.has(message.id)) {
|
|
const { resolve, reject, timeout } = pending.get(message.id);
|
|
clearTimeout(timeout);
|
|
pending.delete(message.id);
|
|
message.error ? reject(new Error(JSON.stringify(message.error))) : resolve(message.result);
|
|
}
|
|
}
|
|
});
|
|
function send(method, params = {}, sessionId) {
|
|
return new Promise((resolve, reject) => {
|
|
const key = ++id;
|
|
const timeout = setTimeout(() => { pending.delete(key); reject(new Error(`CDP timeout: ${method}`)); }, 25000);
|
|
pending.set(key, { resolve, reject, timeout });
|
|
child.stdio[3].write(JSON.stringify({ id: key, method, params, ...(sessionId ? {sessionId} : {}) }) + '\0');
|
|
});
|
|
}
|
|
const { targetId } = await send('Target.createTarget', { url: 'about:blank' });
|
|
const { sessionId } = await send('Target.attachToTarget', { targetId, flatten: true });
|
|
const call = (method, params) => send(method, params, sessionId);
|
|
await call('Page.enable');
|
|
await call('Runtime.enable');
|
|
if (machine) await call('Page.addScriptToEvaluateOnNewDocument', { source: `try { localStorage.setItem('mosaic-console-board', '{"machine":true}'); } catch {}` });
|
|
const evaluate = async expression => {
|
|
const r = await call('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true });
|
|
if (r.exceptionDetails) throw new Error(r.exceptionDetails.text + ': ' + r.result.description);
|
|
return r.result.value;
|
|
};
|
|
return {
|
|
call, evaluate,
|
|
async viewport(width, height = 1000) {
|
|
await call('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
|
|
},
|
|
async navigate(url) {
|
|
const result = await call('Page.navigate', { url });
|
|
if (result.errorText) throw new Error(result.errorText);
|
|
// Bounded page-load check, not an agent wake/watch loop.
|
|
for (let n = 0; n < 50; n++) {
|
|
if (await evaluate('document.readyState === "complete"')) break;
|
|
await new Promise(r => setTimeout(r, 100));
|
|
}
|
|
await evaluate('document.fonts.ready.then(() => true)');
|
|
return result;
|
|
},
|
|
async screenshot(path) {
|
|
const { data } = await call('Page.captureScreenshot', { format: 'png' });
|
|
await writeFile(path, Buffer.from(data, 'base64'));
|
|
},
|
|
async key(key, code = key, modifiers = 0) {
|
|
const virtual = { Tab: 9, Enter: 13, Escape: 27, ArrowDown: 40, ArrowUp: 38, ArrowRight: 39, ' ': 32, k: 75 }[key];
|
|
await call('Input.dispatchKeyEvent', { type: 'keyDown', key, code, modifiers, windowsVirtualKeyCode: virtual, text: modifiers ? '' : key === 'Enter' ? '\r' : key.length === 1 ? key : '' });
|
|
await call('Input.dispatchKeyEvent', { type: 'keyUp', key, code, modifiers, windowsVirtualKeyCode: virtual });
|
|
},
|
|
async close() {
|
|
await send('Browser.close').catch(() => {});
|
|
if (child.exitCode === null) await new Promise(resolve => child.once('exit', resolve));
|
|
await rm(profile, { recursive: true, force: true });
|
|
},
|
|
};
|
|
}
|