Files
rea/src/application/BrowserScenarioCaptureService.ts
T
morluto 3460ceac3f fix(browser): retain exact scenario cleanup ownership
Transfer canonical leases through opening, session and storage boundaries. Keep response deadlines separate from actual completion, drain independent owners, and join late opening failure cleanup before confirming recovery. Remove error copies in favor of canonical primary/cleanup composition.

Preserve exact caller reasons, completed observations and independent physical failures through final-close cancellation and retry. Closes #4869.

Validation:155 tests pass across15 focused source, service, CLI subprocess and MCP files; static/docs/discovery1551 pass. Two original-revision controls fail as expected. Bounded authored-session public CLI comparison preserves equivalent Evidence and1282 reply token estimates; no real Chromium qualification or forced-termination recovery claim (#4659).
2026-10-18 03:22:29 +08:00

83 lines
2.9 KiB
TypeScript

import type { ExecutionOptions } from "./AnalysisProvider.js";
import type { BrowserScenarioCapturePort } from "./BrowserScenarioCapturePort.js";
import { createBrowserScenarioEvidence } from "./BrowserScenarioEvidence.js";
import {
AnalysisCancelledError,
AnalysisCapabilityUnavailableError,
AnalysisOutputError,
} from "../domain/analysisErrorCore.js";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import type { Evidence } from "../domain/evidence.js";
import type { BrowserScenario } from "../domain/browserScenario.js";
import { err, type Result } from "../domain/result.js";
import { browserScenarioCaptureSchema } from "../domain/browserScenarioCapture.js";
import { completeProviderEvidence } from "./ProviderOperationExecution.js";
const OPERATION = "capture_browser_scenario" as const;
const PROVIDER_ID = "rea-playwright-browser-scenario";
/** Execute one controlled browser scenario using its explicit request scope. */
export const captureBrowserScenario = async (
provider: BrowserScenarioCapturePort | undefined,
scenario: BrowserScenario,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
if (provider === undefined)
return err(
new AnalysisCapabilityUnavailableError(
PROVIDER_ID,
OPERATION,
"browser scenario provider is not configured",
),
);
if (options.signal?.aborted)
return err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
);
const captured = await provider.captureScenario(scenario, options);
if (!captured.ok) {
const partial = captured.error.partialObservation;
if (
partial !== undefined &&
"kind" in partial &&
partial.kind === "browser-scenario-observation"
) {
const report = browserScenarioCaptureSchema.safeParse(partial.capture);
if (
report.success &&
report.data.steps.every(({ status }) => status === "completed")
) {
captured.error.partialObservation = createBrowserScenarioEvidence(
scenario,
report.data,
provider.identity(),
);
} else if (!report.success) {
if (captured.error instanceof AnalysisCancelledError)
return err(
new AnalysisOutputError(
OPERATION,
"Browser scenario provider retained a malformed completed capture.",
{ cause: report.error },
),
);
return captured;
}
}
return captured;
}
const report = browserScenarioCaptureSchema.safeParse(captured.value);
if (!report.success)
return err(
new AnalysisOutputError(
OPERATION,
"Browser scenario provider returned a malformed completed capture.",
{ cause: report.error },
),
);
return completeProviderEvidence(
createBrowserScenarioEvidence(scenario, report.data, provider.identity()),
options.signal,
);
};