mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 05:16:16 +00:00
* fix(browser): preserve capture evidence fidelity * fix(browser): align frame identity comparisons * fix(browser): retain historical frame evidence * fix(electron): retain same-document script owners * fix(electron): bind frame proof to tree reads * fix(browser): retain malformed frame scope * fix(browser): retain WebMCP event scope * fix(browser): retain malformed timeline identity * fix(browser): unify frame identity and scope * fix(electron): prove enclosing frame ancestry * fix(electron): separate ancestry from file policy * fix(browser): preserve transient fragment semantics * fix(browser): retry uncertain initial frame scope * fix(browser): preserve runtime frame identity * refactor(browser): separate independent capture fixes * fix(electron): detect tree-first document replacement * fix(electron): retain same-document tree authority --------- Co-authored-by: eco-null <eco-null@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
205 lines
6.6 KiB
TypeScript
205 lines
6.6 KiB
TypeScript
import { expect, it } from "vitest";
|
|
import type { CdpEvent } from "../../../src/browser/CdpConnection.js";
|
|
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
|
|
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
|
|
import { discoverWebMcpToolsInputSchema } from "../../../src/domain/webMcpDiscovery.js";
|
|
import { compareWebCaptures } from "../../../src/domain/webCaptureDiff.js";
|
|
import { compareWebCapturesInputSchema } from "../../../src/domain/webCaptureDiffSchemas.js";
|
|
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
|
|
import { trackBrowser } from "./cdpBrowserProvider.support.js";
|
|
|
|
it.each(
|
|
["about:blank", "about:srcdoc"].flatMap((base) =>
|
|
["inline", "split"].flatMap((encoding) =>
|
|
["initial", "event"].flatMap((phase) =>
|
|
[false, true].map((recover) => ({ base, encoding, phase, recover })),
|
|
),
|
|
),
|
|
),
|
|
)(
|
|
"keeps temporary document policy separate from fragment identity: $base/$encoding/$phase/$recover",
|
|
async ({ base, encoding, phase, recover }) => {
|
|
let document = phase === "initial" ? "transient" : "old";
|
|
const blank =
|
|
encoding === "split"
|
|
? { url: base, urlFragment: "#details" }
|
|
: { url: base + "#details" };
|
|
const frame = (origin: string) => ({
|
|
id: "child",
|
|
parentId: "frame-main",
|
|
loaderId: document,
|
|
...(document === "transient"
|
|
? blank
|
|
: {
|
|
url: origin + "/child",
|
|
urlFragment: document === "fresh" ? "#current" : "#old",
|
|
}),
|
|
});
|
|
const tool = (name: string) => ({
|
|
name,
|
|
frameId: "child",
|
|
inputSchema: {},
|
|
annotations: {
|
|
readOnly: true,
|
|
untrustedContent: true,
|
|
consequential: false,
|
|
autosubmit: false,
|
|
},
|
|
});
|
|
const browser = await startFakeCdpBrowser({
|
|
webMcpTools: true,
|
|
eventsBeforeReply: ["WebMCP.enable"],
|
|
commandResult: ({ method }, origin) =>
|
|
method === "Page.getFrameTree"
|
|
? {
|
|
frameTree: {
|
|
frame: { id: "frame-main", url: origin + "/app" },
|
|
childFrames: [{ frame: frame(origin) }],
|
|
},
|
|
}
|
|
: undefined,
|
|
commandEvents: ({ method, sessionId }, origin) => {
|
|
if (method !== "WebMCP.enable") return undefined;
|
|
const scope = sessionId === undefined ? {} : { sessionId };
|
|
const events: CdpEvent[] = [
|
|
{
|
|
method: "WebMCP.toolsAdded",
|
|
...scope,
|
|
params: { tools: [tool("stale")] },
|
|
},
|
|
];
|
|
document = "transient";
|
|
if (phase === "event")
|
|
events.push({
|
|
method: "Page.frameNavigated",
|
|
...scope,
|
|
params: { frame: frame(origin) },
|
|
});
|
|
if (recover) {
|
|
document = "fresh";
|
|
events.push({
|
|
method: "Page.frameNavigated",
|
|
...scope,
|
|
params: { frame: frame(origin) },
|
|
});
|
|
events.push({
|
|
method: "WebMCP.toolsAdded",
|
|
...scope,
|
|
params: { tools: [tool("current")] },
|
|
});
|
|
}
|
|
return events;
|
|
},
|
|
});
|
|
trackBrowser(browser);
|
|
const provider = new CdpBrowserProvider();
|
|
const input = {
|
|
cdp_endpoint: browser.endpoint,
|
|
allowed_origins: [browser.allowedOrigin],
|
|
target_id: "allowed-page",
|
|
observation_ms: 0,
|
|
};
|
|
const result = await provider.discoverWebMcpTools(
|
|
discoverWebMcpToolsInputSchema.parse(input),
|
|
);
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.tools.items.map(({ name }) => name)).toEqual(
|
|
recover ? ["current"] : [],
|
|
);
|
|
expect(result.value.completeness.policy_filtered_sections).not.toContain(
|
|
"webmcp_tools",
|
|
);
|
|
expect(
|
|
result.value.completeness.attach_limited_sections.includes(
|
|
"webmcp_tools",
|
|
),
|
|
).toBe(!recover);
|
|
if (recover)
|
|
expect(result.value.tools.items[0]?.frame_url).toBe(
|
|
browser.allowedOrigin + "/child#current",
|
|
);
|
|
const inspection = await provider.inspectPage(
|
|
inspectWebPageInputSchema.parse(input),
|
|
);
|
|
if (!inspection.ok) throw inspection.error;
|
|
const snapshot = { inspection: inspection.value, webmcp: result.value };
|
|
const compared = compareWebCaptures(
|
|
compareWebCapturesInputSchema.parse({
|
|
before: snapshot,
|
|
after: snapshot,
|
|
}),
|
|
);
|
|
expect(compared.dimensions.webmcp.status).toBe(
|
|
recover ? "unchanged" : "unknown",
|
|
);
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
"about:blanker#details",
|
|
"about:srcdoc-other#details",
|
|
"about:blank?policy=1#details",
|
|
"https://foreign.invalid/child#about:blank",
|
|
"allowed",
|
|
])("does not broaden temporary document policy: %s", async (rawUrl) => {
|
|
const browser = await startFakeCdpBrowser({
|
|
webMcpTools: true,
|
|
eventsBeforeReply: ["WebMCP.enable"],
|
|
commandResult: ({ method }, origin) =>
|
|
method === "Page.getFrameTree"
|
|
? {
|
|
frameTree: {
|
|
frame: { id: "frame-main", url: origin + "/app" },
|
|
childFrames: [
|
|
{
|
|
frame: {
|
|
id: "child",
|
|
parentId: "frame-main",
|
|
url:
|
|
rawUrl === "allowed"
|
|
? origin + "/child#about:blank"
|
|
: rawUrl,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
}
|
|
: undefined,
|
|
commandEvents: ({ method, sessionId }) =>
|
|
method === "WebMCP.enable"
|
|
? [
|
|
{
|
|
method: "WebMCP.toolsAdded",
|
|
...(sessionId === undefined ? {} : { sessionId }),
|
|
params: {
|
|
tools: [{ name: "current", frameId: "child", inputSchema: {} }],
|
|
},
|
|
},
|
|
]
|
|
: undefined,
|
|
});
|
|
trackBrowser(browser);
|
|
const result = await new CdpBrowserProvider().discoverWebMcpTools(
|
|
discoverWebMcpToolsInputSchema.parse({
|
|
cdp_endpoint: browser.endpoint,
|
|
allowed_origins: [browser.allowedOrigin],
|
|
target_id: "allowed-page",
|
|
observation_ms: 0,
|
|
}),
|
|
);
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.tools.items.map(({ name }) => name)).toEqual(
|
|
rawUrl === "allowed" ? ["current"] : [],
|
|
);
|
|
expect(
|
|
result.value.completeness.policy_filtered_sections.includes("webmcp_tools"),
|
|
).toBe(rawUrl !== "allowed");
|
|
expect(result.value.completeness.attach_limited_sections).not.toContain(
|
|
"webmcp_tools",
|
|
);
|
|
if (rawUrl === "allowed")
|
|
expect(result.value.tools.items[0]?.frame_url).toBe(
|
|
browser.allowedOrigin + "/child#about:blank",
|
|
);
|
|
});
|