Files
codex-app-server/cloud/codex-ui-host/host/cdp-load.mjs
T
host 3e0ff898e0
ci / test (push) Successful in 45m25s
feat: wire native Codex UI as /console entry, keep classic console at /console-classic
- Vendor codex-ui-stack host/ as cloud/codex-ui-host/ (original/ not vendored, 650MB)
- Bridge emulates 7 methods missing in codex 0.164.0-alpha.9, exposes 29
  experimental-protocol methods (150 -> 179), Linux binary candidates,
  read-only POST /__codex_git
- GET /console 302s to the UI host (:8095) when reachable, else falls back
  to the classic React console; GET /console-classic always serves it
- Alignment tables: docs/codex-native-ui-alignment.md
- Tests: test_console 7, new test_codex_ui_host 4, handler method pin 247 -> 249
2026-11-06 19:03:35 +00:00

110 lines
3.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Load the local web host in the headless browser, capture console + exceptions,
* then dump the resulting page state and a screenshot.
*
* node host/cdp-load.mjs --port 9333 --url http://127.0.0.1:8095/ --out /tmp/shot.png
*/
import fs from "node:fs";
const argv = process.argv.slice(2);
const argOf = (name, fallback) => {
const i = argv.indexOf(name);
return i !== -1 && argv[i + 1] ? argv[i + 1] : fallback;
};
const PORT = argOf("--port", "9333");
const URL_ = argOf("--url", "http://127.0.0.1:8095/");
const OUT = argOf("--out", "/tmp/codexweb.png");
const SETTLE = Number(argOf("--settle", "6000"));
const version = await (await fetch(`http://127.0.0.1:${PORT}/json/version`)).json();
const ws = new WebSocket(version.webSocketDebuggerUrl);
let seq = 0;
const pending = new Map();
const events = [];
const send = (method, params = {}, sessionId) =>
new Promise((resolve, reject) => {
const id = ++seq;
pending.set(id, { resolve, reject });
const msg = { id, method, params };
if (sessionId) msg.sessionId = sessionId;
ws.send(JSON.stringify(msg));
});
ws.addEventListener("message", (ev) => {
const msg = JSON.parse(ev.data);
if (msg.method) {
if (msg.method === "Runtime.consoleAPICalled") {
const text = (msg.params.args || [])
.map((a) => a.value ?? a.description ?? a.unserializableValue ?? "")
.join(" ")
.slice(0, 600);
events.push(`[${msg.params.type}] ${text}`);
} else if (msg.method === "Runtime.exceptionThrown") {
const d = msg.params.exceptionDetails;
events.push(`[EXCEPTION] ${d.exception?.description ?? d.text}`.slice(0, 2200));
} else if (msg.method === "Log.entryAdded") {
const e = msg.params.entry;
events.push(`[log:${e.level}] ${e.text}`.slice(0, 500));
}
}
if (!msg.id || !pending.has(msg.id)) return;
const { resolve, reject } = pending.get(msg.id);
pending.delete(msg.id);
msg.error ? reject(new Error(JSON.stringify(msg.error).slice(0, 400))) : resolve(msg.result);
});
await new Promise((r) => ws.addEventListener("open", r, { once: true }));
const { targetInfos } = await send("Target.getTargets");
const pages = targetInfos.filter((t) => t.type === "page");
const main = pages.find((t) => t.url.includes("127.0.0.1")) ?? pages[0];
if (!main) {
console.error("no page target");
process.exit(1);
}
const { sessionId } = await send("Target.attachToTarget", { targetId: main.targetId, flatten: true });
await send("Page.enable", {}, sessionId);
await send("Runtime.enable", {}, sessionId);
await send("Log.enable", {}, sessionId);
await send("Page.navigate", { url: URL_ }, sessionId);
// Runtime.enable replays the previous page's console buffer; drop it so the
// report only covers this load.
events.length = 0;
await new Promise((r) => setTimeout(r, SETTLE));
const state = await send(
"Runtime.evaluate",
{
expression: `(() => {
const root = document.querySelector('#root');
const text = (root?.innerText || '').replace(/\\s+/g, ' ').trim();
return JSON.stringify({
url: location.href,
title: document.title,
rootChildren: root ? root.children.length : -1,
rootBytes: root ? root.innerHTML.length : 0,
textLength: text.length,
textSample: text.slice(0, 500),
bootstrapped: !!globalThis.__codexBootstrapped,
hasShim: typeof globalThis.__codexHost,
}, null, 1);
})()`,
returnByValue: true,
},
sessionId,
);
console.log("STATE:", state.result.value);
console.log(`\nEVENTS (${events.length}):`);
for (const e of events.slice(0, 60)) console.log(" " + e);
const shot = await send("Page.captureScreenshot", { format: "png" }, sessionId);
fs.writeFileSync(OUT, Buffer.from(shot.data, "base64"));
console.log(`\nscreenshot: ${OUT} (${fs.statSync(OUT).size} bytes)`);
ws.close();