Demos execute JS and need a browser; the app's own binary now provides one: main.cjs boots a hidden window + CDP port under --browser-service (before the single-instance lock - a helper is deliberately a second instance of this binary), lifetime tied to the server's stdin (EOF = quit, no orphans). headless.ts connects over CDP as PRIMARY (owner ruling 2026-10-10), system Chrome stays the fallback, graceful refusal when neither. Electron's CDP has no Target.createTarget, so the helper's pre-created window is THE page, reused and reset per job behind a serialized queue. PDFs and SVGs never reach a browser anymore (native since stages 1-2); stale Chrome wording in model-facing tool copy made engine-neutral. Live-proofed end-to-end from the dev tree (helper seen by process watch, js-ran 42 probe, no orphan). Gates 1523/1523.
43 lines
1.7 KiB
TypeScript
43 lines
1.7 KiB
TypeScript
/**
|
|
* Wave E stage 3 (owner 2026-10-10): demos run in the app's own Chromium
|
|
* via a --browser-service helper (main.cjs boots a hidden window with a CDP
|
|
* port), system Chrome as fallback. These pin the pure halves of that
|
|
* chain: the handshake line parser and the spawn-env sanitizer. The live
|
|
* chain (spawn → connect → pages() → run) is exercised on dev machines,
|
|
* not in the suite.
|
|
*/
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { browserServiceSpawnEnv, parseBrowserServiceLine } from "../src/agent/subagents/headless.ts";
|
|
|
|
describe("browser-service helper (pure halves)", () => {
|
|
it("parses the handshake line into a port", () => {
|
|
assert.equal(parseBrowserServiceLine("BROWSER_SERVICE_CDP 54321"), 54321);
|
|
});
|
|
|
|
it("rejects non-handshake lines", () => {
|
|
assert.equal(parseBrowserServiceLine("DevTools listening on ws://127.0.0.1:57983/devtools/browser/x"), null);
|
|
assert.equal(parseBrowserServiceLine(""), null);
|
|
assert.equal(parseBrowserServiceLine("BROWSER_SERVICE_CDP 0"), null);
|
|
assert.equal(parseBrowserServiceLine("BROWSER_SERVICE_CDP 99999"), null);
|
|
});
|
|
|
|
it("spawn env strips ELECTRON_RUN_AS_NODE — the helper must be a full app", () => {
|
|
const env = browserServiceSpawnEnv({
|
|
ELECTRON_RUN_AS_NODE: "1",
|
|
LH_DEV_URL: "http://localhost:3000",
|
|
PATH: "/usr/bin",
|
|
});
|
|
assert.equal("ELECTRON_RUN_AS_NODE" in env, false);
|
|
assert.equal("LH_DEV_URL" in env, false);
|
|
assert.equal(env.PATH, "/usr/bin");
|
|
});
|
|
|
|
it("spawn env never mutates the caller's env object", () => {
|
|
const original = { ELECTRON_RUN_AS_NODE: "1", KEEP: "yes" };
|
|
browserServiceSpawnEnv(original);
|
|
assert.equal(original.ELECTRON_RUN_AS_NODE, "1");
|
|
assert.equal(original.KEEP, "yes");
|
|
});
|
|
});
|