* fix(browser): keep negative script columns Chrome reports Chrome reported a Debugger.scriptParsed event with startColumn -60 and endColumn -15 for an inline script in WordPress's helper frame. CDP declares these columns as plain integers, but REA's admission required min(0), so one script failed the whole observe-web-execution run with unreadable_output. CDP resource columns now share one representation, cdpResourceColumnSchema, for script start/end columns, initiator call frames, parser initiators and event listeners. Positions on a script's first line include its start column, so keeping the reported values keeps the documented script-relative conversion consistent. The domain contract and web runtime docs state that a negative column does not locate a column in the resource. Fixes #8674 Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(browser): apply the CDP column policy to passive capture inspect_web_page and discover_webmcp_tools still read CDP columns through cdpSourceCoordinates, whose own >= 0 check turned a negative column into null and reported invalid_protocol_value. WebMCP discovery also dropped the tool's whole registration_source. cdpResourceColumnSchema now lives with the domain's CDP observation schemas. Runtime admission, the passive coordinate helper and the runtime, console, initiator and WebMCP output schemas all use it. Lines keep their non-negative rule. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
156 lines
5.2 KiB
TypeScript
156 lines
5.2 KiB
TypeScript
import { z } from "zod";
|
|
import {
|
|
cdpStringValue,
|
|
numberValue,
|
|
recordValue,
|
|
} from "./CdpCaptureValues.js";
|
|
import { cdpResourceColumnSchema } from "../domain/cdpObservationSchemas.js";
|
|
|
|
const integer = z.int().min(0);
|
|
const contextAuxDataSchema = z
|
|
.object({
|
|
frameId: z.string().optional(),
|
|
isDefault: z.boolean().optional(),
|
|
type: z.string().optional(),
|
|
})
|
|
.passthrough();
|
|
const scriptDeclaration = z.object({
|
|
scriptId: z.string(),
|
|
url: z.string(),
|
|
executionContextId: z.number(),
|
|
});
|
|
const contextDeclaration = z.object({
|
|
id: z.number(),
|
|
origin: z.string().optional(),
|
|
name: z.string().optional(),
|
|
uniqueId: z.string().optional(),
|
|
auxData: contextAuxDataSchema.optional(),
|
|
});
|
|
|
|
/** Active runtime admission retains its required positions and exact auxiliary declarations. */
|
|
export const runtimeScriptParsedSchema = scriptDeclaration.extend({
|
|
scriptId: z.string().min(1),
|
|
executionContextId: integer,
|
|
startLine: integer,
|
|
startColumn: cdpResourceColumnSchema,
|
|
endLine: integer,
|
|
endColumn: cdpResourceColumnSchema,
|
|
hash: z.string().optional(),
|
|
sourceMapURL: z.string().optional(),
|
|
hasSourceURL: z.boolean().optional(),
|
|
scriptLanguage: z.string().optional(),
|
|
executionContextAuxData: contextAuxDataSchema.optional(),
|
|
});
|
|
export const runtimeContextSchema = z.object({
|
|
context: contextDeclaration.extend({ id: integer }).passthrough(),
|
|
});
|
|
|
|
/** Private storage seeding keeps its original numeric and text admission policy. */
|
|
export const storageScriptParsedSchema = scriptDeclaration;
|
|
export const storageContextSchema = z.object({
|
|
context: contextDeclaration
|
|
.pick({ id: true, origin: true, name: true, auxData: true })
|
|
.required({ origin: true, name: true })
|
|
.extend({
|
|
auxData: contextAuxDataSchema.pick({ frameId: true }).strip().optional(),
|
|
}),
|
|
});
|
|
export const storageContextDestroyedSchema = z.object({
|
|
executionContextId: z.number(),
|
|
});
|
|
|
|
/** Narrow frame claims may be retained without borrowing unbounded auxiliary JSON. */
|
|
export interface CdpContextFrameClaim {
|
|
readonly state: "missing" | "invalid" | "observed";
|
|
/** Supplied string spelling, including an invalid empty claim, remains available. */
|
|
readonly frameId: string | undefined;
|
|
}
|
|
export const cdpContextFrameClaim = (
|
|
auxiliary: unknown,
|
|
): CdpContextFrameClaim => {
|
|
if (auxiliary === undefined) return { state: "missing", frameId: undefined };
|
|
const value = recordValue(auxiliary);
|
|
if (value === undefined) return { state: "invalid", frameId: undefined };
|
|
if (value.frameId === undefined)
|
|
return { state: "missing", frameId: undefined };
|
|
const frameId = cdpStringValue(value.frameId);
|
|
return {
|
|
state: frameId === undefined || frameId === "" ? "invalid" : "observed",
|
|
frameId,
|
|
};
|
|
};
|
|
|
|
/** Numeric producer identity and the safe integer lookup spelling have distinct policies. */
|
|
const contextIdentity = (reported: unknown) => {
|
|
const id = numberValue(reported);
|
|
return {
|
|
id,
|
|
contextKey:
|
|
id !== undefined && Number.isSafeInteger(id) ? String(id) : null,
|
|
};
|
|
};
|
|
|
|
export const readCdpExecutionContextCreated = (params: unknown) => {
|
|
const context = recordValue(recordValue(params)?.context);
|
|
return {
|
|
...contextIdentity(context?.id),
|
|
name: cdpStringValue(context?.name),
|
|
origin: cdpStringValue(context?.origin),
|
|
frameClaim: cdpContextFrameClaim(context?.auxData),
|
|
};
|
|
};
|
|
export const readCdpExecutionContextDestroyed = (params: unknown) =>
|
|
contextIdentity(recordValue(params)?.executionContextId);
|
|
|
|
/** Partial passive observations do not impose the active runtime's required fields. */
|
|
export const readCdpScriptParsed = (params: unknown) => {
|
|
const value = recordValue(params);
|
|
const identity = contextIdentity(value?.executionContextId);
|
|
const length = numberValue(value?.length);
|
|
return {
|
|
scriptId: cdpStringValue(value?.scriptId),
|
|
url: cdpStringValue(value?.url),
|
|
contextId: identity.id,
|
|
contextKey: identity.contextKey,
|
|
metadata: {
|
|
hash: cdpStringValue(value?.hash) ?? null,
|
|
length:
|
|
length !== undefined && Number.isSafeInteger(length) && length >= 0
|
|
? length
|
|
: null,
|
|
isModule: typeof value?.isModule === "boolean" ? value.isModule : null,
|
|
language: cdpStringValue(value?.scriptLanguage) ?? null,
|
|
},
|
|
frameClaim: cdpContextFrameClaim(value?.executionContextAuxData),
|
|
hasSourceUrl:
|
|
typeof value?.hasSourceURL === "boolean" ? value.hasSourceURL : null,
|
|
invalidHasSourceUrl:
|
|
value?.hasSourceURL !== undefined &&
|
|
typeof value.hasSourceURL !== "boolean",
|
|
sourceMapUrl: value?.sourceMapURL,
|
|
};
|
|
};
|
|
/** Missing claims allow other producer evidence; malformed and conflicting claims never do. */
|
|
export const selectCdpScriptFrame = (
|
|
script: CdpContextFrameClaim,
|
|
context: CdpContextFrameClaim | undefined,
|
|
): {
|
|
readonly frameId: string | null;
|
|
readonly invalid: boolean;
|
|
readonly conflicting: boolean;
|
|
} => {
|
|
const invalid = script.state === "invalid" || context?.state === "invalid";
|
|
const conflicting =
|
|
script.state === "observed" &&
|
|
context?.state === "observed" &&
|
|
script.frameId !== context.frameId;
|
|
return {
|
|
frameId:
|
|
invalid || conflicting
|
|
? null
|
|
: (script.frameId ?? context?.frameId ?? null),
|
|
invalid,
|
|
conflicting,
|
|
};
|
|
};
|