Files
pikasTech-unidesk/scripts/src/hwlab-node-web-probe-summary.ts
T

690 lines
34 KiB
TypeScript

// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-26-p8-web-probe-sentinel-recovery.
// Responsibility: Redacted web-probe summaries and issue-ready compaction helpers.
function record(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
}
function nullableRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}
export function compactWebProbeScriptResult(report: Record<string, unknown> | null): Record<string, unknown> | null {
if (report === null) return null;
const browserRuntimeBlocker = classifyWebProbeBrowserRuntimeAuthFailure(report);
const summary = withBrowserRuntimeBlockerEvidence(compactIssueSummary(record(report.summary)), browserRuntimeBlocker);
const scriptResult = record(record(report.script).result);
const scriptReport = record(scriptResult.report);
const scriptScreenshot = nullableRecord(scriptResult.screenshot);
const issueEvidence = withBrowserRuntimeBlockerEvidence(
compactIssueEvidence(report.issueEvidence ?? scriptResult.issueEvidence ?? summary.issueEvidence ?? fallbackIssueEvidence(report, summary)),
browserRuntimeBlocker,
);
const reportPath = typeof report.reportPath === "string"
? report.reportPath
: typeof issueEvidence?.reportPath === "string"
? issueEvidence.reportPath
: typeof scriptReport.path === "string" ? scriptReport.path : null;
const reportSha256 = typeof report.reportSha256 === "string"
? report.reportSha256
: typeof issueEvidence?.reportSha256 === "string"
? issueEvidence.reportSha256
: typeof scriptReport.sha256 === "string" ? scriptReport.sha256 : null;
const failureEvidence = compactProductSmokeFailureEvidence(report, reportPath, reportSha256);
return {
ok: report.ok === true,
status: typeof report.status === "string" ? report.status : null,
summary,
issueEvidence,
failureEvidence,
baseUrl: typeof report.baseUrl === "string" ? report.baseUrl : null,
finalUrl: typeof report.finalUrl === "string" ? report.finalUrl : null,
lastUrl: typeof report.lastUrl === "string" ? report.lastUrl : null,
scriptSha256: typeof report.scriptSha256 === "string" ? report.scriptSha256 : null,
runDir: typeof report.runDir === "string" ? report.runDir : null,
reportPath,
reportSha256,
auth: record(report.auth),
script: compactWebProbeScriptBlock(report.script),
steps: compactWebProbeSteps(report.steps),
failureKind: browserRuntimeBlocker?.failureKind ?? (typeof report.failureKind === "string" ? report.failureKind : null),
guidance: typeof report.guidance === "string" ? report.guidance : null,
lastScreenshot: nullableRecord(report.lastScreenshot) ?? scriptScreenshot,
readiness: record(report.readiness),
artifacts: record(report.artifacts),
error: browserRuntimeBlocker?.code ?? (typeof report.error === "string" ? report.error : null),
errorMessage: typeof report.errorMessage === "string" ? report.errorMessage : null,
safety: record(report.safety),
};
}
export function compactProductSmokeFailureEvidence(
report: Record<string, unknown>,
reportPath: string | null = null,
reportSha256: string | null = null,
): Record<string, unknown> | null {
if (report.ok !== false) return null;
const pdf = findPdfFailureEvidence(report);
if (pdf === null) return null;
const artifacts = record(report.artifacts);
const artifactItems = Array.isArray(artifacts.items) ? artifacts.items.map(nullableRecord).filter((item): item is Record<string, unknown> => item !== null) : [];
const screenshot = artifactItems.find((item) => item.kind === "screenshot")
?? nullableRecord(report.lastScreenshot)
?? nullableRecord(record(record(report.script).result).screenshot);
return {
kind: "pdf-verification",
status: pdf.status,
mediaType: pdf.mediaType,
popupCreated: pdf.popupCreated,
popupProtocol: pdf.popupProtocol,
popupClosedBeforeBlob: pdf.popupClosedBeforeBlob,
documentType: typeof pdf.documentType === "string" ? pdf.documentType.slice(0, 80) : null,
path: typeof pdf.path === "string" ? pdf.path.slice(0, 240) : null,
reportPath,
reportSha256,
screenshotPath: typeof screenshot?.path === "string" ? screenshot.path : null,
screenshotSha256: typeof screenshot?.sha256 === "string" ? screenshot.sha256 : null,
valuesRedacted: true,
};
}
function findPdfFailureEvidence(value: unknown, depth = 0): Record<string, unknown> | null {
if (value === null || value === undefined || depth > 8) return null;
if (typeof value === "string") {
const first = value.indexOf("{");
const last = value.lastIndexOf("}");
if (first >= 0 && last > first) {
try {
return findPdfFailureEvidence(JSON.parse(value.slice(first, last + 1)), depth + 1);
} catch {
return null;
}
}
return null;
}
if (Array.isArray(value)) {
for (const item of value.slice(-16).reverse()) {
const found = findPdfFailureEvidence(item, depth + 1);
if (found !== null) return found;
}
return null;
}
const candidate = nullableRecord(value);
if (candidate === null) return null;
if (
candidate.ok === false
&& typeof candidate.status === "number"
&& typeof candidate.mediaType === "string"
&& typeof candidate.popupCreated === "boolean"
&& typeof candidate.popupProtocol === "string"
&& typeof candidate.popupClosedBeforeBlob === "boolean"
) return candidate;
for (const nested of Object.values(candidate).slice(0, 32).reverse()) {
const found = findPdfFailureEvidence(nested, depth + 1);
if (found !== null) return found;
}
return null;
}
export function classifyWebProbeBrowserRuntimeAuthFailure(report: Record<string, unknown>): Record<string, unknown> | null {
const auth = nullableRecord(report.auth);
if (auth === null || auth.method !== "ui-form-memory") return null;
const attempts = Array.isArray(auth.attempts) ? auth.attempts.map(nullableRecord).filter((item): item is Record<string, unknown> => item !== null) : [];
const firstAttempt = attempts[0] ?? null;
const script = record(report.script);
const stepCount = Array.isArray(script.steps)
? script.steps.length
: Array.isArray(report.steps) ? report.steps.length : null;
const authError = nullableRecord(auth.errorSummary);
const message = typeof authError?.message === "string"
? authError.message
: typeof report.errorMessage === "string" ? report.errorMessage : "";
const credentialResolved = auth.credentialResolved === true || auth.usernamePresent === true;
const explicitRequestReached = auth.requestReached === true || firstAttempt?.requestReached === true;
const response = firstAttempt?.response ?? null;
const apiResponseReached = auth.apiResponseReached === true
|| firstAttempt?.apiResponseReached === true
|| response !== null
|| (typeof auth.status === "number" && auth.status > 0);
const firstNavigationClosed = /Target page, context or browser has been closed/iu.test(message);
const browserResourceExhausted = /net::ERR_INSUFFICIENT_RESOURCES|net::ERR_OUT_OF_MEMORY/iu.test(message);
if (!credentialResolved || (!firstNavigationClosed && !browserResourceExhausted) || auth.status !== 0 || response !== null || stepCount !== 0 || explicitRequestReached || apiResponseReached) {
return null;
}
const code = browserResourceExhausted ? "browser-runtime-resource-exhausted" : "browser-runtime-closed";
return {
code,
failureKind: "browser-runtime-blocker",
credentialResolved: true,
requestReached: false,
apiResponseReached: false,
errorSummary: {
message: message.replace(/\s+/gu, " ").trim().slice(0, 600),
status: 0,
response: null,
stepCount: 0,
valuesRedacted: true,
},
valuesRedacted: true,
};
}
function withBrowserRuntimeBlockerEvidence(
evidence: Record<string, unknown> | null,
blocker: Record<string, unknown> | null,
): Record<string, unknown> | null {
if (blocker === null) return evidence;
const source = evidence ?? {};
const {
ok: _ok,
status: _status,
credentialResolved: _credentialResolved,
requestReached: _requestReached,
apiResponseReached: _apiResponseReached,
errorSummary: _errorSummary,
degradedReason: _degradedReason,
failureKind: _failureKind,
failedCondition: _failedCondition,
valuesRedacted: _valuesRedacted,
...rest
} = source;
return {
ok: false,
status: typeof source.status === "string" ? source.status : "blocked",
credentialResolved: blocker.credentialResolved,
requestReached: blocker.requestReached,
apiResponseReached: blocker.apiResponseReached,
errorSummary: blocker.errorSummary,
...rest,
degradedReason: blocker.code,
failureKind: blocker.failureKind,
failedCondition: record(blocker.errorSummary).message,
valuesRedacted: true,
};
}
function compactWebProbeScriptBlock(value: unknown): Record<string, unknown> {
const script = record(value);
return {
ok: script.ok === true,
result: compactJsonForEvidence(script.result),
stepCount: Array.isArray(script.steps) ? script.steps.length : null,
};
}
function compactWebProbeSteps(value: unknown): Record<string, unknown>[] {
if (!Array.isArray(value)) return [];
return value.slice(-5).map((item) => {
const step = record(item);
return {
index: typeof step.index === "number" ? step.index : null,
name: typeof step.name === "string" ? step.name : null,
ok: typeof step.ok === "boolean" ? step.ok : null,
atMs: typeof step.atMs === "number" ? step.atMs : null,
data: compactStepDataForIssue(step.data),
};
});
}
function compactIssueSummary(value: Record<string, unknown>): Record<string, unknown> {
if (Object.keys(value).length === 0) return {};
return {
ok: value.ok === true,
status: typeof value.status === "string" ? value.status : null,
credentialResolved: typeof value.credentialResolved === "boolean" ? value.credentialResolved : null,
requestReached: typeof value.requestReached === "boolean" ? value.requestReached : null,
apiResponseReached: typeof value.apiResponseReached === "boolean" ? value.apiResponseReached : null,
errorSummary: compactJsonForIssue(value.errorSummary),
degradedReason: typeof value.degradedReason === "string" ? value.degradedReason : null,
failureKind: typeof value.failureKind === "string" ? value.failureKind : null,
failedCondition: typeof value.failedCondition === "string" ? value.failedCondition : null,
nextAction: typeof value.nextAction === "string" ? value.nextAction : null,
finalUrl: typeof value.finalUrl === "string" ? value.finalUrl : null,
lastUrl: typeof value.lastUrl === "string" ? value.lastUrl : null,
scriptSha256: typeof value.scriptSha256 === "string" ? value.scriptSha256 : null,
runDir: typeof value.runDir === "string" ? value.runDir : null,
reportPath: typeof value.reportPath === "string" ? value.reportPath : null,
reportSha256: typeof value.reportSha256 === "string" ? value.reportSha256 : null,
lastScreenshot: nullableRecord(value.lastScreenshot),
screenshots: Array.isArray(value.screenshots) ? value.screenshots.slice(-5).map(nullableRecord).filter((item): item is Record<string, unknown> => item !== null) : [],
apiMatrix: compactApiMatrixSummary(value.apiMatrix),
stepCount: typeof value.stepCount === "number" ? value.stepCount : null,
lastStep: compactStepForIssue(value.lastStep),
issueEvidence: compactIssueEvidence(value.issueEvidence),
valuesRedacted: value.valuesRedacted === true,
};
}
function compactJsonForIssue(value: unknown, depth = 0): unknown {
if (value === null || value === undefined) return value ?? null;
if (typeof value === "string") return value.replace(/\s+/gu, " ").trim().slice(0, 240);
if (typeof value === "number" || typeof value === "boolean") return value;
if (depth >= 4) return "[max-depth]";
if (Array.isArray(value)) return value.slice(0, 8).map((item) => compactJsonForIssue(item, depth + 1));
if (typeof value === "object") {
const out: Record<string, unknown> = {};
for (const [key, nested] of Object.entries(value as Record<string, unknown>).slice(0, 16)) {
out[key] = compactJsonForIssue(nested, depth + 1);
}
return out;
}
return String(value).slice(0, 240);
}
function fallbackIssueEvidence(report: Record<string, unknown>, summary: Record<string, unknown>): Record<string, unknown> {
const script = record(report.script);
return {
ok: report.ok === true,
status: typeof report.status === "string" ? report.status : summary.status ?? null,
degradedReason: summary.degradedReason ?? null,
failureKind: summary.failureKind ?? (typeof report.failureKind === "string" ? report.failureKind : null),
failedCondition: summary.failedCondition ?? (typeof report.errorMessage === "string" ? report.errorMessage : null),
nextAction: summary.nextAction ?? null,
baseUrl: typeof report.baseUrl === "string" ? report.baseUrl : null,
finalUrl: typeof report.finalUrl === "string" ? report.finalUrl : summary.finalUrl ?? null,
lastUrl: typeof report.lastUrl === "string" ? report.lastUrl : summary.lastUrl ?? null,
scriptSha256: typeof report.scriptSha256 === "string" ? report.scriptSha256 : summary.scriptSha256 ?? null,
runDir: typeof report.runDir === "string" ? report.runDir : summary.runDir ?? null,
reportPath: typeof report.reportPath === "string" ? report.reportPath : summary.reportPath ?? null,
reportSha256: typeof report.reportSha256 === "string" ? report.reportSha256 : summary.reportSha256 ?? null,
result: script.result,
apiMatrix: summary.apiMatrix ?? null,
lastStep: summary.lastStep ?? null,
steps: Array.isArray(report.steps) ? report.steps.slice(-3) : [],
lastScreenshot: report.lastScreenshot ?? summary.lastScreenshot ?? null,
screenshots: summary.screenshots ?? [],
valuesRedacted: true,
};
}
function compactIssueEvidence(value: unknown): Record<string, unknown> | null {
const evidence = nullableRecord(value);
if (evidence === null) return null;
return {
ok: evidence.ok === true,
status: typeof evidence.status === "string" ? evidence.status : null,
credentialResolved: typeof evidence.credentialResolved === "boolean" ? evidence.credentialResolved : null,
requestReached: typeof evidence.requestReached === "boolean" ? evidence.requestReached : null,
apiResponseReached: typeof evidence.apiResponseReached === "boolean" ? evidence.apiResponseReached : null,
errorSummary: compactJsonForEvidence(evidence.errorSummary),
degradedReason: typeof evidence.degradedReason === "string" ? evidence.degradedReason : null,
failureKind: typeof evidence.failureKind === "string" ? evidence.failureKind : null,
failedCondition: typeof evidence.failedCondition === "string" ? evidence.failedCondition : null,
nextAction: typeof evidence.nextAction === "string" ? evidence.nextAction : null,
baseUrl: typeof evidence.baseUrl === "string" ? evidence.baseUrl : null,
finalUrl: typeof evidence.finalUrl === "string" ? evidence.finalUrl : null,
lastUrl: typeof evidence.lastUrl === "string" ? evidence.lastUrl : null,
scriptSha256: typeof evidence.scriptSha256 === "string" ? evidence.scriptSha256 : null,
runDir: typeof evidence.runDir === "string" ? evidence.runDir : null,
reportPath: typeof evidence.reportPath === "string" ? evidence.reportPath : null,
reportSha256: typeof evidence.reportSha256 === "string" ? evidence.reportSha256 : null,
result: compactJsonForEvidence(evidence.result),
apiMatrix: compactApiMatrixSummary(evidence.apiMatrix),
lastStep: compactStepForEvidence(evidence.lastStep),
steps: Array.isArray(evidence.steps) ? evidence.steps.slice(-3).map(compactStepForEvidence).filter((item): item is Record<string, unknown> => item !== null) : [],
lastScreenshot: nullableRecord(evidence.lastScreenshot),
screenshots: Array.isArray(evidence.screenshots) ? evidence.screenshots.slice(-5).map(nullableRecord).filter((item): item is Record<string, unknown> => item !== null) : [],
valuesRedacted: evidence.valuesRedacted === true,
};
}
function compactStepForEvidence(value: unknown): Record<string, unknown> | null {
const step = nullableRecord(value);
if (step === null) return null;
return {
index: typeof step.index === "number" ? step.index : null,
name: typeof step.name === "string" ? step.name : null,
ok: typeof step.ok === "boolean" ? step.ok : null,
atMs: typeof step.atMs === "number" ? step.atMs : null,
data: compactJsonForEvidence(step.data),
};
}
function compactJsonForEvidence(value: unknown, depth = 0): unknown {
if (value === null || value === undefined) return value ?? null;
if (typeof value === "string") return value.replace(/\s+/gu, " ").trim().slice(0, 600);
if (typeof value === "number" || typeof value === "boolean") return value;
if (depth >= 8) return "[max-depth]";
if (Array.isArray(value)) return value.slice(0, 16).map((item) => compactJsonForEvidence(item, depth + 1));
if (typeof value === "object") {
const out: Record<string, unknown> = {};
for (const [key, nested] of Object.entries(value as Record<string, unknown>).slice(0, 32)) {
out[key] = compactJsonForEvidence(nested, depth + 1);
}
return out;
}
return String(value).slice(0, 600);
}
function compactStepForIssue(value: unknown): Record<string, unknown> | null {
const step = nullableRecord(value);
if (step === null) return null;
return {
index: typeof step.index === "number" ? step.index : null,
name: typeof step.name === "string" ? step.name : null,
ok: typeof step.ok === "boolean" ? step.ok : null,
atMs: typeof step.atMs === "number" ? step.atMs : null,
data: compactStepDataForIssue(step.data),
};
}
function compactStepDataForIssue(value: unknown): unknown {
const data = nullableRecord(value);
if (data === null) return compactJsonForIssue(value);
if (Array.isArray(data.items) && typeof data.failedCount === "number") return compactApiMatrixSummary(data);
return compactJsonForIssue(data);
}
function compactApiMatrixSummary(value: unknown): Record<string, unknown> | null {
const matrix = nullableRecord(value);
if (matrix === null) return null;
return {
ok: matrix.ok === true,
count: typeof matrix.count === "number" ? matrix.count : null,
okCount: typeof matrix.okCount === "number" ? matrix.okCount : null,
failedCount: typeof matrix.failedCount === "number" ? matrix.failedCount : null,
items: Array.isArray(matrix.items)
? matrix.items.slice(0, 12).map((item) => {
const row = record(item);
return {
name: typeof row.name === "string" ? row.name : null,
path: typeof row.path === "string" ? row.path : null,
method: typeof row.method === "string" ? row.method : null,
ok: row.ok === true,
status: typeof row.status === "number" ? row.status : null,
error: typeof row.error === "string" ? row.error : null,
failureKind: typeof row.failureKind === "string" ? row.failureKind : null,
bodyKeys: Array.isArray(row.bodyKeys) ? row.bodyKeys.filter((key) => typeof key === "string").slice(0, 12) : [],
};
})
: [],
};
}
export function compactWebProbeResult(report: Record<string, unknown> | null): Record<string, unknown> | null {
if (report === null) return null;
const dom = record(report.dom);
const performance = compactWebProbePerformance(report.performance);
const trace = compactWebProbeTrace(report.trace);
const session = compactWebProbeSession(report.session);
const artifacts = record(report.artifacts);
const promptValidation = compactPromptValidation(report.promptValidation);
const degradedReason = typeof report.degradedReason === "string" ? report.degradedReason : null;
const summary = webProbeRunIssueSummary({ report, session, trace, promptValidation, artifacts, degradedReason });
return {
ok: report.ok === true,
status: typeof report.status === "string" ? report.status : null,
summary,
finalUrl: typeof report.finalUrl === "string" ? report.finalUrl : null,
error: typeof report.error === "string" ? report.error : null,
degradedReason,
failureKind: typeof summary.failureKind === "string" ? summary.failureKind : null,
nextAction: typeof summary.nextAction === "string" ? summary.nextAction : null,
actions: compactWebProbeActions(report.actions),
session,
trace,
promptValidation,
performance,
traceSamples: compactWebProbeTraceSamples(report.traceSamples),
dom: {
authState: typeof dom.authState === "string" ? dom.authState : null,
requiredSelectors: record(dom.requiredSelectors),
messageCount: typeof dom.messageCount === "number" ? dom.messageCount : null,
sessionRail: record(dom.sessionRail),
},
failureDom: record(report.failureDom),
artifacts,
safety: record(report.safety),
};
}
function compactWebProbeSession(value: unknown): Record<string, unknown> {
const session = record(value);
const freshSession = record(session.freshSession);
return {
freshSessionRequested: session.freshSessionRequested === true,
freshSessionAligned: typeof session.freshSessionAligned === "boolean" ? session.freshSessionAligned : null,
conversationId: session.conversationId ?? null,
routeConversationId: session.routeConversationId ?? null,
selectedConversationId: session.selectedConversationId ?? null,
sessionId: session.sessionId ?? null,
revision: session.revision ?? null,
messageCount: session.messageCount ?? null,
degradedReason: session.degradedReason ?? null,
freshSession: Object.keys(freshSession).length === 0 ? null : {
settled: freshSession.settled === true,
aligned: freshSession.aligned === true,
reason: freshSession.reason ?? null,
},
};
}
function compactWebProbeTrace(value: unknown): Record<string, unknown> {
const trace = record(value);
const latestFetch = record(trace.latestFetch);
return {
requested: trace.requested === true,
sampleCount: trace.sampleCount ?? null,
conversationId: trace.conversationId ?? null,
sessionId: trace.sessionId ?? null,
traceId: trace.traceId ?? null,
finalAgentStatus: trace.finalAgentStatus ?? null,
finalTraceStatus: trace.finalTraceStatus ?? null,
finalDomRowCount: trace.finalDomRowCount ?? null,
firstDomRowVisibleAt: trace.firstDomRowVisibleAt ?? null,
firstDomRowPreview: typeof trace.firstDomRowPreview === "string" ? trace.firstDomRowPreview.slice(0, 240) : null,
firstRestEventVisibleAt: trace.firstRestEventVisibleAt ?? null,
firstRestEventCount: trace.firstRestEventCount ?? null,
restTraceOk: trace.restTraceOk === true,
failedFetchCount: trace.failedFetchCount ?? null,
latestFetch: Object.keys(latestFetch).length === 0 ? null : {
ok: latestFetch.ok === true,
httpStatus: latestFetch.httpStatus ?? null,
eventCount: latestFetch.eventCount ?? null,
traceStatus: latestFetch.traceStatus ?? null,
degradedReason: latestFetch.degradedReason ?? null,
attempts: Array.isArray(latestFetch.attempts) ? latestFetch.attempts.length : null,
latestEventPreview: typeof latestFetch.latestEventPreview === "string" ? latestFetch.latestEventPreview.slice(0, 240) : null,
},
degradedReason: trace.degradedReason ?? null,
};
}
function compactWebProbePerformance(value: unknown): Record<string, unknown> {
const performance = record(value);
return {
startedAt: performance.startedAt ?? null,
submittedAt: performance.submittedAt ?? null,
firstDomTraceRowAt: performance.firstDomTraceRowAt ?? null,
firstRestTraceEventAt: performance.firstRestTraceEventAt ?? null,
submitToFirstDomTraceRowMs: performance.submitToFirstDomTraceRowMs ?? null,
submitToFirstRestTraceEventMs: performance.submitToFirstRestTraceEventMs ?? null,
totalSampleWindowMs: performance.totalSampleWindowMs ?? null,
};
}
function compactPromptValidation(value: unknown): Record<string, unknown> {
const prompt = record(value);
const finalResponse = record(prompt.finalResponse);
return {
ok: prompt.ok === true,
applicable: prompt.applicable === true,
reason: typeof prompt.reason === "string" ? prompt.reason : null,
failures: Array.isArray(prompt.failures) ? prompt.failures.filter((item) => typeof item === "string").slice(0, 20) : [],
finalResponse: Object.keys(finalResponse).length === 0 ? null : {
textChars: typeof finalResponse.textChars === "number" ? finalResponse.textChars : null,
textPreview: typeof finalResponse.textPreview === "string" ? finalResponse.textPreview.slice(0, 500) : null,
markdown: nullableRecord(finalResponse.markdown),
},
};
}
function webProbeRunIssueSummary(input: {
report: Record<string, unknown>;
session: Record<string, unknown>;
trace: Record<string, unknown>;
promptValidation: Record<string, unknown>;
artifacts: Record<string, unknown>;
degradedReason: string | null;
}): Record<string, unknown> {
const failureKind = webProbeRunFailureKind(input.degradedReason, input.promptValidation);
const failedCondition = webProbeRunFailedCondition(input.degradedReason, input.promptValidation);
const promptSubmitted = record(input.report.safety).promptSubmitted === true;
const traceRequested = input.trace.requested === true || record(input.report.safety).traceSamplingRequested === true;
return {
ok: input.report.status === "pass" && !input.degradedReason,
status: typeof input.report.status === "string" ? input.report.status : null,
degradedReason: input.degradedReason,
failureKind,
failedCondition,
nextAction: webProbeRunNextAction(failureKind, input.degradedReason, input.promptValidation, promptSubmitted, traceRequested),
finalUrl: typeof input.report.finalUrl === "string" ? input.report.finalUrl : null,
conversationId: input.session.conversationId ?? input.trace.conversationId ?? null,
sessionId: input.session.sessionId ?? input.trace.sessionId ?? null,
traceId: input.trace.traceId ?? null,
traceRequested,
traceStatus: input.trace.finalTraceStatus ?? null,
agentStatus: input.trace.finalAgentStatus ?? null,
messageCount: input.session.messageCount ?? record(input.report.dom).messageCount ?? null,
promptValidation: input.promptValidation,
reportPath: typeof input.artifacts.reportPath === "string" ? input.artifacts.reportPath : null,
reportSha256: typeof input.artifacts.reportSha256 === "string" ? input.artifacts.reportSha256 : null,
screenshotPath: typeof input.artifacts.screenshotPath === "string" ? input.artifacts.screenshotPath : null,
screenshotSha256: typeof input.artifacts.screenshotSha256 === "string" ? input.artifacts.screenshotSha256 : null,
traceRecommendation: promptSubmitted && !traceRequested
? "rerun with --trace-sample-count 2 --trace-sample-interval-ms 1500, or rely on the new default for prompt probes"
: null,
valuesRedacted: true,
};
}
function webProbeRunFailureKind(degradedReason: string | null, promptValidation: Record<string, unknown>): string | null {
if (degradedReason === null) return null;
if (degradedReason === "prompt-validation-failed") return "unmet-expectation";
if (degradedReason === "agent-terminal-timeout") return "agent-runtime-timeout";
if (/trace-fetch|api|fetch|http|network/iu.test(degradedReason)) return "network-or-api-fetch-bug";
if (/auth|login|credential/iu.test(degradedReason)) return "target-auth-bug";
if (/browser-timeout|selector-timeout|readiness|session|composer|navigation|load-jitter|timeout/iu.test(degradedReason)) return "target-page-readiness-timeout";
if (/browser|playwright|chromium/iu.test(degradedReason)) return "browser-environment-bug";
const failures = Array.isArray(promptValidation.failures) ? promptValidation.failures.join(" ") : "";
if (/final-response|agent-message|markdown|completed/iu.test(failures)) return "unmet-expectation";
return "user-facing-web-bug";
}
function webProbeRunFailedCondition(degradedReason: string | null, promptValidation: Record<string, unknown>): string | null {
if (degradedReason === "prompt-validation-failed") {
const failures = Array.isArray(promptValidation.failures) ? promptValidation.failures.filter((item) => typeof item === "string") : [];
return failures.length > 0 ? failures.join(",") : "prompt final response validation failed";
}
return degradedReason;
}
function webProbeRunNextAction(
failureKind: string | null,
degradedReason: string | null,
promptValidation: Record<string, unknown>,
promptSubmitted: boolean,
traceRequested: boolean,
): string | null {
if (failureKind === null) return null;
if (failureKind === "unmet-expectation") {
const failures = Array.isArray(promptValidation.failures) ? promptValidation.failures.join(",") : "";
if (/agent-not-completed/iu.test(failures)) return "Increase --wait-agent-terminal-ms or inspect the Code Agent terminal state for the conversation/trace ids in summary.";
if (/final-response/iu.test(failures)) return "Inspect Workbench message projection and conversation detail; terminal messages must expose a final response.";
if (/markdown/iu.test(failures)) return "Check whether the prompt expectation still requires markdown structure; stale assertions should be removed instead of preserved.";
return "Inspect promptValidation.failures, screenshots, and reportPath to decide whether the Web behavior or the assertion is stale.";
}
if (failureKind === "network-or-api-fetch-bug") return "Inspect trace/session API fetch fields in reportPath and retry after checking target API availability.";
if (failureKind === "agent-runtime-timeout") return "Inspect summary.traceId/sessionId and the corresponding Code Agent/AgentRun runtime; the browser probe submitted the prompt and observed trace events, but no terminal agent response arrived before the wait boundary.";
if (failureKind === "target-auth-bug") return "Inspect credential sourceRef/fingerprint and /auth/login status; do not print secrets.";
if (failureKind === "target-page-readiness-timeout") return "Inspect finalUrl, readiness/session-create state, Workbench loading indicators, network request failures, and session API evidence in reportPath before changing browser launcher settings.";
if (failureKind === "browser-environment-bug") return "Inspect browser launcher and Playwright availability on the target workspace.";
if (promptSubmitted && !traceRequested) return "Rerun with trace sampling if trace evidence is required.";
return degradedReason === null ? null : "Inspect reportPath for full redacted details, then rerun the same node/lane entry.";
}
function compactWebProbeTraceSamples(value: unknown): Record<string, unknown>[] {
if (!Array.isArray(value)) return [];
return value.map(record).map((sample) => {
const traceFetch = record(sample.traceFetch);
const attempts = Array.isArray(traceFetch.attempts) ? traceFetch.attempts : [];
return {
index: sample.index ?? null,
sampledAt: sample.sampledAt ?? null,
messageCount: sample.messageCount ?? null,
agentStatus: sample.agentStatus ?? null,
tracePresent: sample.tracePresent === true,
traceStatus: sample.traceStatus ?? null,
rowCount: sample.rowCount ?? null,
emptyLabel: sample.emptyLabel ?? null,
latestRowPreview: sample.latestRowPreview ?? null,
userVisibleRowCount: sample.userVisibleRowCount ?? null,
latestUserVisibleRowPreview: sample.latestUserVisibleRowPreview ?? null,
conversationId: sample.conversationId ?? null,
sessionId: sample.sessionId ?? null,
traceId: sample.traceId ?? null,
traceFetch: {
ok: traceFetch.ok === true,
httpStatus: traceFetch.httpStatus ?? null,
eventCount: traceFetch.eventCount ?? null,
degradedReason: traceFetch.degradedReason ?? null,
attempts: attempts.length,
latestEventPreview: traceFetch.latestEventPreview ?? null,
},
};
});
}
function compactWebProbeActions(value: unknown): Record<string, unknown>[] {
if (!Array.isArray(value)) return [];
return value.map(record).map((action) => {
const name = typeof action.action === "string" ? action.action : "unknown";
if (name === "fresh-session") {
const alignment = record(action.alignment);
const check = record(alignment.check);
const repair = record(alignment.repair);
return {
action: name,
settled: action.settled === true,
aligned: action.aligned === true,
reason: alignment.reason ?? check.reason ?? null,
conversationId: check.conversationId ?? null,
routeConversationId: check.routeConversationId ?? null,
selectedConversationId: check.selectedConversationId ?? null,
repaired: Object.keys(repair).length > 0,
repairConversationId: repair.conversationId ?? null,
attempts: Array.isArray(alignment.attempts) ? alignment.attempts.length : null,
};
}
if (name === "trace-interval-samples") {
return {
action: name,
count: action.count ?? null,
intervalMs: action.intervalMs ?? null,
};
}
if (name === "submit-prompt") {
return {
action: name,
chars: action.chars ?? null,
submittedAt: action.submittedAt ?? null,
};
}
if (name === "wait-agent-terminal") {
return {
action: name,
terminal: action.terminal === true,
timeoutMs: action.timeoutMs ?? null,
};
}
return {
action: name,
skipped: action.skipped ?? null,
ready: action.ready ?? null,
found: action.found ?? null,
timeoutMs: action.timeoutMs ?? null,
};
});
}