mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(runtime): preserve exact selected target identities
This commit is contained in:
1 parent
d9e12c957b
commit
44b716f2f2
13 files changed
+146
-45
No files matched your search
@@ -207,7 +207,7 @@ export const listBrowserTargetsInputSchema = z.strictObject({
|
||||
|
||||
const inspectWebPageInputFacts = {
|
||||
...browserInput,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
command_timeout_ms: webRuntimeCommandTimeoutSchema,
|
||||
observation_ms: z.int().min(0).default(500),
|
||||
include_accessibility_text: z.boolean().default(false),
|
||||
|
||||
@@ -182,7 +182,7 @@ export const browserScenarioBrowserSchema = z.discriminatedUnion("mode", [
|
||||
z.strictObject({
|
||||
mode: z.literal("connect"),
|
||||
cdp_endpoint: browserEndpointSchema,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
}),
|
||||
]);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { browserVersionSchema } from "./browserObservationSchemas.js";
|
||||
export const observeWebSessionInputSchema = z.strictObject({
|
||||
cdp_endpoint: browserObservationEndpointSchema,
|
||||
allowed_origins: browserAllowedOriginsSchema,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
observation_ms: z.int().min(1).default(10_000),
|
||||
command_timeout_ms: webRuntimeCommandTimeoutSchema,
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ export type ListElectronTargetsInput = z.infer<
|
||||
/** Input for passive structural inspection of one Electron file page. */
|
||||
const inspectElectronPageFacts = {
|
||||
...approvedElectronInput,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
observation_ms: z.int().min(0).default(100),
|
||||
command_timeout_ms: webRuntimeCommandTimeoutSchema,
|
||||
} as const;
|
||||
|
||||
@@ -30,7 +30,7 @@ const observedJavaScriptRuntimeKindSchema = javascriptRuntimeKindSchema.or(
|
||||
/** Input for one passive attach-only V8 Inspector observation. */
|
||||
export const observeJavaScriptRuntimeInputSchema = z.strictObject({
|
||||
...runtimeEndpoint,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
runtime_kind: javascriptRuntimeKindSchema.optional(),
|
||||
observation_ms: z.int().min(0).default(100),
|
||||
});
|
||||
|
||||
@@ -293,7 +293,7 @@ const assertV8RuntimeParameters = (
|
||||
z
|
||||
.object({
|
||||
inspector_endpoint: browserEndpointSchema,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
runtime_kind: javascriptRuntimeKindSchema.optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
@@ -456,7 +456,7 @@ const assertRuntimeParameters = (
|
||||
path: readonly (string | number)[],
|
||||
): void => {
|
||||
const common = {
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
include_script_sources: z.boolean(),
|
||||
};
|
||||
if (expected.kind === "browser") {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { digestSchema, prefixedDigestSchema } from "./../domain/digests.js";
|
||||
export const discoverWebMcpToolsInputSchema = z.strictObject({
|
||||
cdp_endpoint: browserObservationEndpointSchema,
|
||||
allowed_origins: browserAllowedOriginsSchema,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
observation_ms: z.int().min(0).default(100),
|
||||
command_timeout_ms: webRuntimeCommandTimeoutSchema,
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ export type WebScreenshotArtifact = z.infer<typeof webScreenshotArtifactSchema>;
|
||||
export const captureWebScreenshotInputSchema = z.strictObject({
|
||||
cdp_endpoint: browserObservationEndpointSchema,
|
||||
allowed_origins: browserAllowedOriginsSchema,
|
||||
target_id: z.string().trim().min(1),
|
||||
target_id: z.string().min(1),
|
||||
command_timeout_ms: webRuntimeCommandTimeoutSchema,
|
||||
});
|
||||
export type CaptureWebScreenshotInput = z.infer<
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { browserScenarioBrowserSchema } from "../../../src/domain/browserScenarioValues.js";
|
||||
|
||||
import { BROWSER_TOOL_CONTRACTS } from "../../../src/contracts/browserToolContracts.js";
|
||||
import { comparePngScreenshots } from "../../../src/browser/PngVisualDiff.js";
|
||||
import { compareWebScreenshotsInputSchema } from "../../../src/domain/webScreenshot.js";
|
||||
@@ -17,3 +19,26 @@ it("executes the advertised screenshot comparison example through PNG checksum v
|
||||
changed_pixels: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([" target ", "\t", "target"])(
|
||||
"preserves the connect scenario target identity %j",
|
||||
(targetId) => {
|
||||
expect(
|
||||
browserScenarioBrowserSchema.parse({
|
||||
mode: "connect",
|
||||
cdp_endpoint: "http://127.0.0.1:9222",
|
||||
target_id: targetId,
|
||||
}),
|
||||
).toMatchObject({ target_id: targetId });
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects an empty connect scenario target identity", () => {
|
||||
expect(
|
||||
browserScenarioBrowserSchema.safeParse({
|
||||
mode: "connect",
|
||||
cdp_endpoint: "http://127.0.0.1:9222",
|
||||
target_id: "",
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
+45
-36
@@ -42,43 +42,52 @@ const readInput = (input: unknown) => {
|
||||
const reconcileInput = (input: unknown) =>
|
||||
reconcileJavaScriptRuntime(readInput(input));
|
||||
|
||||
it("preserves source-capture input paths for runtime Evidence", async () => {
|
||||
const root = await applicationFixture();
|
||||
const application = await analyzeFixture(root);
|
||||
const runtime = electronRuntimeEvidence(root, SOURCE);
|
||||
const invalidRuntime = createEvidence(undefined, runtime.provider, {
|
||||
predicateType: runtime.predicate_type,
|
||||
operation: runtime.operation,
|
||||
parameters: { ...runtime.parameters, include_script_sources: false },
|
||||
result: runtime.normalized_result,
|
||||
confidence: runtime.confidence,
|
||||
authority: runtime.authority,
|
||||
});
|
||||
it.each([
|
||||
{
|
||||
parameter: "include_script_sources",
|
||||
value: false,
|
||||
message:
|
||||
"Runtime Evidence contains source without source-capture selection",
|
||||
},
|
||||
{
|
||||
parameter: "target_id",
|
||||
value: " target-main ",
|
||||
message: "Runtime Evidence target disagrees with its captured result",
|
||||
},
|
||||
])(
|
||||
"preserves $parameter input paths for runtime Evidence",
|
||||
async ({ parameter, value, message }) => {
|
||||
const root = await applicationFixture();
|
||||
const application = await analyzeFixture(root);
|
||||
const runtime = electronRuntimeEvidence(root, SOURCE);
|
||||
const invalidRuntime = createEvidence(undefined, runtime.provider, {
|
||||
predicateType: runtime.predicate_type,
|
||||
operation: runtime.operation,
|
||||
parameters: { ...runtime.parameters, [parameter]: value },
|
||||
result: runtime.normalized_result,
|
||||
confidence: runtime.confidence,
|
||||
authority: runtime.authority,
|
||||
});
|
||||
|
||||
const result = reconcileJavaScriptRuntimeEvidence({
|
||||
static_layers: [{ role: "application", analysis: application }],
|
||||
runtime_observations: [invalidRuntime],
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisInputError",
|
||||
issues: [
|
||||
{
|
||||
path: [
|
||||
"runtime_observations",
|
||||
0,
|
||||
"parameters",
|
||||
"include_script_sources",
|
||||
],
|
||||
reason: "invalid_value",
|
||||
message:
|
||||
"Runtime Evidence contains source without source-capture selection",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
const result = reconcileJavaScriptRuntimeEvidence({
|
||||
static_layers: [{ role: "application", analysis: application }],
|
||||
runtime_observations: [invalidRuntime],
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisInputError",
|
||||
issues: [
|
||||
{
|
||||
path: ["runtime_observations", 0, "parameters", parameter],
|
||||
reason: "invalid_value",
|
||||
message,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("reconciles runtime scripts inside dot-prefixed child directories", async () => {
|
||||
const root = await applicationFixture();
|
||||
|
||||
@@ -746,6 +746,40 @@ it.each(["full", "compact"] as const)(
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["inspect_web_page", 0],
|
||||
["analyze_web_bundle", 0],
|
||||
["observe_web_session", 1],
|
||||
["discover_webmcp_tools", 0],
|
||||
["capture_web_screenshot", undefined],
|
||||
] as const)(
|
||||
"selects the exact opaque target ID through %s",
|
||||
async (name, observationMs) => {
|
||||
const browser = await startFakeCdpBrowser();
|
||||
browsers.push(browser);
|
||||
const { client } = await connectBrowser();
|
||||
const args = {
|
||||
cdp_endpoint: browser.endpoint,
|
||||
allowed_origins: [browser.allowedOrigin],
|
||||
...(observationMs === undefined ? {} : { observation_ms: observationMs }),
|
||||
};
|
||||
const rejected = await client.callTool({
|
||||
name,
|
||||
arguments: { ...args, target_id: " allowed-page " },
|
||||
});
|
||||
expect(parseMcpToolError(rejected)).toMatchObject({
|
||||
error: { details: { reason: "target_not_found" } },
|
||||
});
|
||||
expect(browser.commands).toHaveLength(0);
|
||||
const accepted = await client.callTool({
|
||||
name,
|
||||
arguments: { ...args, target_id: "allowed-page" },
|
||||
});
|
||||
expect(accepted.isError).not.toBe(true);
|
||||
expect(browser.commands.length).toBeGreaterThan(0);
|
||||
},
|
||||
);
|
||||
|
||||
it("does not attach to a target outside the request's allowed origin scope", async () => {
|
||||
const browser = await startFakeCdpBrowser();
|
||||
browsers.push(browser);
|
||||
|
||||
@@ -344,6 +344,7 @@ it("exposes endpoint-scoped Electron discovery and inspection as Evidence", asyn
|
||||
targets: [{ target_id: "electron-page" }],
|
||||
},
|
||||
});
|
||||
await expectPaddedElectronTargetRejected(client, browser);
|
||||
const inspected = await client.callTool({
|
||||
name: "inspect_electron_page",
|
||||
arguments: {
|
||||
@@ -703,3 +704,22 @@ it("reports every unresolved Electron path without starting a provider", async (
|
||||
expect(capture).not.toHaveBeenCalled();
|
||||
await client.ping();
|
||||
});
|
||||
|
||||
const expectPaddedElectronTargetRejected = async (
|
||||
client: Client,
|
||||
browser: FakeCdpBrowser,
|
||||
): Promise<void> => {
|
||||
const commandsBeforeRejectedInspection = browser.commands.length;
|
||||
const rejectedInspection = await client.callTool({
|
||||
name: "inspect_electron_page",
|
||||
arguments: {
|
||||
cdp_endpoint: browser.endpoint,
|
||||
target_id: " electron-page ",
|
||||
observation_ms: 0,
|
||||
},
|
||||
});
|
||||
expect(parseMcpToolError(rejectedInspection)).toMatchObject({
|
||||
error: { details: { reason: "target_not_found" } },
|
||||
});
|
||||
expect(browser.commands).toHaveLength(commandsBeforeRejectedInspection);
|
||||
};
|
||||
@@ -92,6 +92,7 @@ function registerRuntimeParameterContract(
|
||||
for (const [target_id, runtime_kind, expectedPath] of [
|
||||
["example-v8-target", "node", "runtime_kind"],
|
||||
["different-target", "electron-main", "target_id"],
|
||||
[" example-v8-target ", "electron-main", "target_id"],
|
||||
["example-v8-target", "electron-main", null],
|
||||
] as const) {
|
||||
const evidence = createJavaScriptRuntimeObservationEvidence(
|
||||
@@ -226,6 +227,18 @@ function registerRetainedRuntimeEvidence(
|
||||
targets: [{ target_id: inspector.targetId }],
|
||||
},
|
||||
});
|
||||
const paddedTarget = await client.callTool({
|
||||
name: "observe_javascript_runtime",
|
||||
arguments: {
|
||||
inspector_endpoint: inspector.endpoint,
|
||||
target_id: ` ${inspector.targetId} `,
|
||||
observation_ms: 0,
|
||||
},
|
||||
});
|
||||
expect(parseMcpToolError(paddedTarget).error).toMatchObject({
|
||||
code: "target_unavailable",
|
||||
});
|
||||
expect(inspector.commands).toEqual([]);
|
||||
const observedRuntime = await client.callTool({
|
||||
name: "observe_javascript_runtime",
|
||||
arguments: {
|
||||
|
||||
Reference in new issue
Block a user