feat(browser): retain compact scenario summaries and selected views (#7120)

Retain complete browser scenario Evidence while delivering compact summaries and selected step, event, transaction and text views through shared CLI/MCP workflows. Preserve initial CLI failures across cleanup retries and validate with real Chromium/TodoMVC.

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-25 19:03:06 +08:00
1 parent bc143125ed
commit fc43e285ea
19 files changed
+2297 -90

No files matched your search

+1
View File
@@ -1290,6 +1290,7 @@ const registerObservationTools = ({
});
registerBrowserScenarioTool(server, {
...common,
evidenceById,
provider: options.browserScenarioCapture,
loadFailure: options.optionalProviderLoadFailures?.browserScenarioCapture,
});
+35 -50
View File
@@ -1,82 +1,67 @@
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
import {
optionalProviderUnavailable,
type OptionalProviderLoadFailure,
} from "../application/OptionalObservationProviders.js";
import { err } from "../domain/result.js";
import { browserScenarioSchema } from "../domain/browserScenario.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import type { BrowserScenarioCapturePort } from "../application/BrowserScenarioCapturePort.js";
import { captureBrowserScenario } from "../application/BrowserScenarioCaptureService.js";
import { toolContract } from "../contracts/toolContracts.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { browserScenarioSchema } from "../domain/browserScenario.js";
import type { Logger } from "pino";
import { logToolExecution } from "./toolLogging.js";
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
import { runAdmittedToolOperation } from "./admittedToolOperation.js";
import { deliverEvidenceResult } from "./contractToolHandler.js";
import { createRetainedSummaryToolHandler } from "./contractToolHandler.js";
interface BrowserScenarioToolRegistration {
readonly logger: Logger;
readonly loadFailure?: OptionalProviderLoadFailure | undefined;
readonly provider: BrowserScenarioCapturePort | undefined;
readonly recordEvidence: EvidenceWriter["recordEvidence"] | undefined;
readonly evidenceById?: EvidenceLookup | undefined;
readonly withAdmittedAnalysis?: WithAdmittedAnalysis;
}
/** Register the browser scenario tool with execution-time provider diagnostics. */
/** Validate one scenario, then share complete/retained delivery with other analyses. */
export const registerBrowserScenarioTool = (
server: EvidenceMcpServer,
options: BrowserScenarioToolRegistration,
): void => {
const contract = toolContract("capture_browser_scenario");
const handler = createRetainedSummaryToolHandler(
server,
contract,
async (input, execution) => {
// The SDK admits the advertised structure. The canonical semantic parser
// owns field-path diagnostics, shared with the CLI rather than flattened
// into the SDK's pre-handler validation message.
const scenario = browserScenarioSchema.safeParse(input);
if (!scenario.success)
return err(
analysisInputErrorFromIssues(
contract.name,
scenario.error.issues,
input,
{ cause: scenario.error },
),
);
return options.loadFailure === undefined
? captureBrowserScenario(options.provider, scenario.data, execution)
: err(optionalProviderUnavailable(options.loadFailure, contract.name));
},
options.logger,
{
recordEvidence: options.recordEvidence,
evidenceById: options.evidenceById,
},
options.withAdmittedAnalysis,
);
server.registerTool(
contract.name,
toolRegistrationOptions(contract),
async (input, context) => {
const parsedScenario = browserScenarioSchema.safeParse(input);
if (!parsedScenario.success)
return server.delivery.toCallToolResult(
err(
analysisInputErrorFromIssues(
contract.name,
parsedScenario.error.issues,
input,
{ cause: parsedScenario.error },
),
),
contract,
);
const scenario = parsedScenario.data;
if (options.loadFailure !== undefined)
return server.delivery.toCallToolResult(
err(optionalProviderUnavailable(options.loadFailure, contract.name)),
contract,
);
return runAdmittedToolOperation(
server,
options.withAdmittedAnalysis,
contract.name,
context.mcpReq.signal,
async () => {
const result = await logToolExecution(
options.logger,
contract.name,
() =>
captureBrowserScenario(options.provider, scenario, {
signal: context.mcpReq.signal,
}),
);
return deliverEvidenceResult(
server,
result,
contract,
options.recordEvidence,
);
},
);
},
handler,
);
};