ci / test (push) Successful in 45m25s
- 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
161 lines
5.7 KiB
JavaScript
Executable File
161 lines
5.7 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Self-check for the standalone original-UI web host. Run while ./start.sh is up.
|
|
* node verify.mjs served assets + HTTP surface
|
|
* node verify.mjs --deep also drive a real app-server RPC through the UI host
|
|
*
|
|
* The orchestrator/API layer is intentionally not part of this stack; nothing
|
|
* here probes port 8899. Everything the UI needs is served by host/web-server.mjs
|
|
* on UI_PORT, including the local JSON-RPC passthrough and the mock backends.
|
|
*/
|
|
import http from "node:http";
|
|
|
|
const uiPort = Number(process.env.UI_PORT || 8095);
|
|
const deep = process.argv.includes("--deep");
|
|
|
|
function request(path, { method = "GET", headers = {}, body = null, timeout = 8000 } = {}) {
|
|
return new Promise((resolve) => {
|
|
const req = http.request({ host: "127.0.0.1", port: uiPort, path, method, headers }, (res) => {
|
|
const chunks = [];
|
|
res.on("data", (c) => chunks.push(c));
|
|
res.on("end", () => resolve({
|
|
status: res.statusCode,
|
|
body: Buffer.concat(chunks).toString("utf8"),
|
|
}));
|
|
});
|
|
req.setTimeout(timeout, () => req.destroy(new Error("timeout")));
|
|
req.on("error", (e) => resolve({ status: 0, body: String(e.message) }));
|
|
if (body) req.write(body);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
function json(r) {
|
|
try { return JSON.parse(r.body); } catch { return null; }
|
|
}
|
|
|
|
const results = [];
|
|
async function check(name, fn) {
|
|
let out;
|
|
try {
|
|
out = await fn();
|
|
} catch (e) {
|
|
out = { ok: false, detail: String(e.message) };
|
|
}
|
|
results.push({ name, ...out });
|
|
console.log(`${out.ok ? "PASS" : "FAIL"} ${name} ${out.detail ?? ""}`);
|
|
}
|
|
|
|
const post = (path, payload, timeout = 8000) => request(path, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
timeout,
|
|
});
|
|
|
|
await check("UI GET / 注入两个宿主脚本", async () => {
|
|
const r = await request("/");
|
|
const ok = r.status === 200
|
|
&& r.body.includes("/__codex_local_host.js")
|
|
&& r.body.includes("/__codex_shim.js");
|
|
return { ok, detail: `HTTP ${r.status}, ${r.body.length}B` };
|
|
});
|
|
|
|
await check("UI GET /__codex_local_host.js", async () => {
|
|
const r = await request("/__codex_local_host.js");
|
|
return {
|
|
ok: r.status === 200 && r.body.includes("__codexLocalHost"),
|
|
detail: `HTTP ${r.status}, ${r.body.length}B`,
|
|
};
|
|
});
|
|
|
|
await check("UI GET /__codex_shim.js", async () => {
|
|
const r = await request("/__codex_shim.js");
|
|
return { ok: r.status === 200 && r.body.length > 1000, detail: `HTTP ${r.status}, ${r.body.length}B` };
|
|
});
|
|
|
|
await check("UI 响应层补丁命中 connect-app-host", async () => {
|
|
const r = await request("/assets/connect-app-host-90a50ca9e4b4.js");
|
|
return {
|
|
ok: r.status === 200 && r.body.includes("__codexLocalHost"),
|
|
detail: `HTTP ${r.status}, ${r.body.length}B`,
|
|
};
|
|
});
|
|
|
|
await check("UI 原版入口 chunk 可加载", async () => {
|
|
const r = await request("/assets/index-fcd3fcef6840.js");
|
|
return { ok: r.status === 200 && r.body.length > 100, detail: `HTTP ${r.status}, ${r.body.length}B` };
|
|
});
|
|
|
|
await check("UI GET /ces/v1/rgstr 遥测丢弃", async () => {
|
|
const r = await request("/ces/v1/rgstr");
|
|
return { ok: r.status === 202, detail: `HTTP ${r.status}` };
|
|
});
|
|
|
|
await check("UI GET /__codex_rpc 拒绝非 POST", async () => {
|
|
const r = await request("/__codex_rpc");
|
|
return { ok: r.status === 405, detail: `HTTP ${r.status}` };
|
|
});
|
|
|
|
await check("UI POST /__codex_projectless 工作区根", async () => {
|
|
const r = await post("/__codex_projectless", { op: "readWorkspaceRoot" });
|
|
const j = json(r);
|
|
return {
|
|
ok: r.status === 200 && typeof j?.result?.workspaceRoot === "string",
|
|
detail: `HTTP ${r.status}, ${j?.result?.workspaceRoot ?? r.body.slice(0, 80)}`,
|
|
};
|
|
});
|
|
|
|
await check("UI POST /__codex_statsig_bootstrap 空配置", async () => {
|
|
const r = await post("/__codex_statsig_bootstrap", { stable_id: "verify" });
|
|
const j = json(r);
|
|
const gates = json({ body: j?.statsigPayload ?? "" })?.feature_gates;
|
|
return {
|
|
ok: r.status === 200 && gates && Object.keys(gates).length === 0,
|
|
detail: `HTTP ${r.status}, ${r.body.length}B`,
|
|
};
|
|
});
|
|
|
|
await check("UI GET /mock/wham/tasks/list 云端替身", async () => {
|
|
const r = await request("/mock/wham/tasks/list");
|
|
const j = json(r);
|
|
return {
|
|
ok: r.status === 200 && Array.isArray(j?.items) && j.items.length === 0,
|
|
detail: `HTTP ${r.status}, ${r.body.trim()}`,
|
|
};
|
|
});
|
|
|
|
await check("UI GET /__codex_mocklog 诊断日志", async () => {
|
|
const r = await request("/__codex_mocklog");
|
|
const j = json(r);
|
|
return {
|
|
ok: r.status === 200 && Array.isArray(j?.requests),
|
|
detail: `HTTP ${r.status}, ${j?.requests?.length ?? 0} 条`,
|
|
};
|
|
});
|
|
|
|
if (deep) {
|
|
await check("UI POST /__codex_rpc -> 真实 app-server thread/list", async () => {
|
|
const r = await post("/__codex_rpc", { method: "thread/list", params: { limit: 1 } }, 45000);
|
|
const j = json(r);
|
|
const ok = r.status === 200 && j && !j.error && j.result != null;
|
|
return { ok: !!ok, detail: `HTTP ${r.status}, ${r.body.slice(0, 140)}` };
|
|
});
|
|
|
|
await check("UI POST /__codex_rpc -> config/read 含 relay 覆盖", async () => {
|
|
const r = await post("/__codex_rpc", { method: "config/read", params: {} }, 45000);
|
|
const j = json(r);
|
|
const model = j?.result?.config?.model;
|
|
return {
|
|
ok: r.status === 200 && typeof model === "string" && model.length > 0,
|
|
detail: `HTTP ${r.status}, model=${model ?? "n/a"}`,
|
|
};
|
|
});
|
|
}
|
|
|
|
const failed = results.filter((r) => !r.ok);
|
|
console.log(`\n${results.length - failed.length}/${results.length} passed`);
|
|
console.log("orchestrator 已移除;本栈只有 :8095 一个服务,无 :8899 API。");
|
|
if (!deep) console.log("shallow run: pass --deep to exercise the real app-server RPC");
|
|
process.exit(failed.length ? 1 : 0);
|