Files
reverse-engineering-anything/src/application/BrowserObservationService.ts
T
morluto f6905719c3 fix(browser): bound capture work and preserve observation gaps
Own service-worker CDP registrations and retry failed teardown. Admit redaction and network metadata against remaining capture capacity, retain explicit omission reasons, and preserve source URL, redirect ancestry, caller-selected content, and historical capture values. Forward CLI cancellation through provider cleanup.

Reviewed fixes: #2263, #2637, #2767, #2845, #2859, #2863, #2872, #2874, #2876, #2879, #2955, #2957, #2961, #3033.

Validation: 25 current event/network source tests passed; earlier focused browser lanes passed 201 source tests and 176 boundary tests with two skips, plus 35 Python tests. Real browser/Windows lanes were not rerun. Playwright allocation before admission remains unbounded by these retention limits.
2026-10-14 08:31:29 +08:00

214 lines
7.2 KiB
TypeScript

import type { Evidence } from "../domain/evidence.js";
import type {
InspectWebPageInput,
ListBrowserTargetsInput,
} from "../domain/browserObservation.js";
import type { AnalyzeWebBundleInput } from "../domain/webBundleAnalysis.js";
import type { ObserveWebSessionInput } from "../domain/browserSession.js";
import type { DiscoverWebMcpToolsInput } from "../domain/webMcpDiscovery.js";
import type { BrowserCaptureComparisonInput } from "../domain/browserCaptureComparison.js";
import type {
CaptureWebScreenshotInput,
CompareWebScreenshotsInput,
} from "../domain/webScreenshot.js";
import {
AnalysisCancelledError,
AnalysisCapabilityUnavailableError,
} from "../domain/analysisErrorCore.js";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import { err, ok, type Result } from "../domain/result.js";
import type { ExecutionOptions } from "./AnalysisProvider.js";
import type { BrowserObservationPort } from "./BrowserObservationPort.js";
import { createBrowserEvidence } from "./BrowserEvidence.js";
/** List browser targets within the endpoint and origin scope in the request. */
export const listBrowserTargets = async (
browser: BrowserObservationPort | undefined,
input: ListBrowserTargetsInput,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "list_browser_targets");
if (!ready.ok) return ready;
const result = await ready.value.listTargets(input, options);
if (result.ok && options.signal?.aborted === true)
return err(new AnalysisCancelledError("list_browser_targets"));
return result.ok
? ok(
createBrowserEvidence(
"list_browser_targets",
input,
result.value,
ready.value.identity(),
),
)
: result;
};
/** Inspect one page within the endpoint and origin scope in the request. */
export const inspectWebPage = async (
browser: BrowserObservationPort | undefined,
input: InspectWebPageInput,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "inspect_web_page");
if (!ready.ok) return ready;
const result = await ready.value.inspectPage(input, options);
if (result.ok && options.signal?.aborted === true)
return err(new AnalysisCancelledError("inspect_web_page"));
return result.ok
? ok(
createBrowserEvidence(
"inspect_web_page",
input,
result.value,
ready.value.identity(),
),
)
: result;
};
/** Capture requested sources and derive static web-bundle evidence. */
export const analyzeWebBundle = async (
browser: BrowserObservationPort | undefined,
input: AnalyzeWebBundleInput,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "analyze_web_bundle");
if (!ready.ok) return ready;
const analyzed = await ready.value.analyzeBundle(input, options);
if (analyzed.ok && options.signal?.aborted === true)
return err(new AnalysisCancelledError("analyze_web_bundle"));
if (!analyzed.ok) return analyzed;
return ok(
createBrowserEvidence(
"analyze_web_bundle",
input,
analyzed.value,
ready.value.identity(),
),
);
};
/** Observe navigation caused by external user actions during one armed window. */
export const observeWebSession = async (
browser: BrowserObservationPort | undefined,
input: ObserveWebSessionInput,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "observe_web_session");
if (!ready.ok) return ready;
const observed = await ready.value.observeSession(input, options);
if (observed.ok && options.signal?.aborted === true)
return err(new AnalysisCancelledError("observe_web_session"));
return observed.ok
? ok(
createBrowserEvidence(
"observe_web_session",
input,
observed.value,
ready.value.identity(),
),
)
: observed;
};
/** Discover WebMCP declarations without exposing an invocation surface. */
export const discoverWebMcpTools = async (
browser: BrowserObservationPort | undefined,
input: DiscoverWebMcpToolsInput,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "discover_webmcp_tools");
if (!ready.ok) return ready;
const discovered = await ready.value.discoverWebMcpTools(input, options);
if (discovered.ok && options.signal?.aborted === true)
return err(new AnalysisCancelledError("discover_webmcp_tools"));
return discovered.ok
? ok(
createBrowserEvidence(
"discover_webmcp_tools",
input,
discovered.value,
ready.value.identity(),
),
)
: discovered;
};
/** Compare two already-normalized web captures without external access. */
export const compareWebCaptureEvidence = async (
browser: BrowserObservationPort | undefined,
input: BrowserCaptureComparisonInput,
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "compare_web_captures");
if (!ready.ok) return ready;
const compared = await ready.value.compareCaptures(input);
return compared.ok
? ok(
createBrowserEvidence(
"compare_web_captures",
input,
compared.value,
ready.value.identity(),
),
)
: compared;
};
/** Capture one visible-viewport screenshot for the requested target. */
export const captureWebScreenshot = async (
browser: BrowserObservationPort | undefined,
input: CaptureWebScreenshotInput,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "capture_web_screenshot");
if (!ready.ok) return ready;
const captured = await ready.value.captureScreenshot(input, options);
if (captured.ok && options.signal?.aborted === true)
return err(new AnalysisCancelledError("capture_web_screenshot"));
return captured.ok
? ok(
createBrowserEvidence(
"capture_web_screenshot",
input,
captured.value,
ready.value.identity(),
),
)
: captured;
};
/** Compare two self-verifying PNG artifacts without external access. */
export const compareWebScreenshotEvidence = async (
browser: BrowserObservationPort | undefined,
input: CompareWebScreenshotsInput,
): Promise<Result<Evidence, AnalysisError>> => {
const ready = requireBrowser(browser, "compare_web_screenshots");
if (!ready.ok) return ready;
const compared = await ready.value.compareScreenshots(input);
return compared.ok
? ok(
createBrowserEvidence(
"compare_web_screenshots",
input,
compared.value,
ready.value.identity(),
),
)
: compared;
};
const requireBrowser = (
browser: BrowserObservationPort | undefined,
operation: string,
): Result<BrowserObservationPort, AnalysisError> =>
browser === undefined
? err(
new AnalysisCapabilityUnavailableError(
"rea-cdp-browser",
operation,
"browser observation provider is not configured",
),
)
: ok(browser);