Files
rea/src/application/BrowserObservationService.ts
T
morluto b5e98915c6 fix(tools)!: simplify direct invocation and harden boundary contracts (#555)
Remove REA permission grants, scope ceilings, approval confirmations, and inert settings. Preserve selected local evidence, accept caller-chosen commands and paths, and report actual runtime failures.

Harden provider results, schema serialization, snapshot evidence binding, archive paths, source maps, native screenshots, managed EH parsing, and cancellation cleanup. Regenerate tool metadata and document direct invocation.

BREAKING CHANGE: Removed permission configuration and policy commands, approval fields, filesystem scope inputs, and fixed confirmation options. Process capture inherits the host environment and accepts command names; callers must refresh tool schemas and migrate removed fields.
2026-10-05 20:16:44 +08:00

201 lines
6.4 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 {
AnalysisCapabilityUnavailableError,
type AnalysisError,
} from "../domain/errors.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);
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);
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) 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);
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);
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);
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);