open-standards/wai/quantum-studio/src/views/main.js
Transaction Science d4d5f4dd48 wai-quantum: the engine's answers as views inside agent hosts, the receipt checked in the view
Both MCP servers serve six ui:// views (a circuit, a result with its
verdict, a receipt, a critique, an estimate's frontier, a device target's
map) through the protocol's extension for interactive views, version
2026-01-26, to a host that says it draws them: each tool names its view in
_meta.ui.resourceUri, the view's data (the engine's layout and the circuit
in words, a target's map) rides in the result's _meta, and the service's
poll_quantum_job, which a view calls while a job runs, is kept from the
model. The service keeps no session, so the client's word travels in the
session id it is given at initialize. Every host gets each such answer in
plain words.

The views are one self-contained page (736 KB, held under 1 MiB) built from
the Studio's components, carrying the engine (wai-quantum-views) as
WebAssembly and translated to JavaScript. Under the extension's default
content-security policy the browser refuses WebAssembly, so the JavaScript
form checks the receipt there; a scripted host in a headless browser draws
every view from both servers under both policies and holds the check, a
changed result, the size and no outside request. The page rebuilds to the
same bytes in the CI image.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 08:42:54 -04:00

99 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// A view of WAI Quantum's engine inside an agent host: one page, the view it
// draws named by the server in <meta name="wai-view">, the tool's answer
// given by the host (src/views/host.js). The page loads nothing and connects
// nowhere; a job the service is still running is followed through the
// server's own tool for that, which the host keeps from the model.
import { el } from '../dom.js';
import { connect } from './host.js';
import { render, META } from './render.js';
const VIEW = document.querySelector('meta[name="wai-view"]')?.content || 'result';
const POLL = 'poll_quantum_job';
const root = document.getElementById('wq');
const body = document.body;
body.dataset.view = VIEW;
body.dataset.state = 'waiting';
const TITLES = { result: 'Result', receipt: 'Receipt', circuit: 'Circuit', critique: 'Critique', estimate: 'Resource estimate', target: 'Device target' };
let args = {};
function head(sub) {
return el('header', { class: 'wq-head' }, el('span', { class: 'wq-name', text: 'WAI Quantum' }), el('span', { class: 'wq-what', text: TITLES[VIEW] || VIEW }), sub ? el('span', { class: 'qs-note', text: sub }) : null);
}
function waiting(text) {
root.replaceChildren(head(), el('p', { class: 'qs-note', id: 'wq-waiting', text }));
}
const host = connect({
name: 'wai-quantum-views',
version: '1',
onInput: (a) => {
args = a || {};
waiting('The tool is running; its answer appears here.');
},
onResult: (res) => show(res),
onCancelled: (why) => {
body.dataset.state = 'cancelled';
waiting(`The call was cancelled${why ? `: ${why}` : ''}.`);
},
onContext: (ctx) => {
if (ctx?.theme === 'light' || ctx?.theme === 'dark') document.documentElement.dataset.theme = ctx.theme;
},
onTeardown: () => { stopped = true; },
});
let stopped = false;
let polling = null;
async function show(res) {
const data = res?._meta?.[META] || null;
const out = render(VIEW, { args, res: res || {}, data });
root.replaceChildren(head(), ...out.blocks);
body.dataset.state = out.state;
if (out.poll) follow(out.poll);
if (out.checked) {
const v = await out.checked;
if (!v.error) {
const what = VIEW === 'estimate' ? 'the estimate’s seal' : VIEW === 'circuit' ? 'the compile’s certificate' : 'the receipt';
host.tellModel(`The ${TITLES[VIEW].toLowerCase()} view checked ${what} on the person’s machine with the engine: ${v.valid ? 'it verifies' : 'it does NOT verify'}${v.signer ? `, signed by ${v.signer}` : ''}.`).catch(() => {});
}
}
}
// A job the service is still running: asked after, more slowly each time, until it ends.
function follow(id) {
if (polling) return;
let wait = 1000;
polling = (async () => {
while (!stopped) {
await new Promise((r) => setTimeout(r, wait));
wait = Math.min(5000, wait * 1.5);
let res;
try { res = await host.callTool(POLL, { id }); } catch (e) {
waiting(`This host would not let the view follow the job (${e.message}); get_quantum_job reads it.`);
break;
}
const s = res?.structuredContent?.status;
if (s !== 'queued' && s !== 'running') { polling = null; show(res); return; }
}
polling = null;
})();
}
// The host sizes the frame to the page.
let sized = 0;
new ResizeObserver(() => {
cancelAnimationFrame(sized);
sized = requestAnimationFrame(() => {
// The content's own size, not the frame's: the host fits the frame to it.
const r = root.getBoundingClientRect();
host.sizeChanged(Math.ceil(r.width), Math.ceil(r.bottom));
});
}).observe(root);
waiting('Waiting for the host.');
host.start().catch((e) => {
body.dataset.state = 'no-host';
waiting(`No host answered (${e.message}). This page is a view for an agent host; it draws what a tool answers.`);
});