fix(hwlab): extract web-probe diagnostic summaries (#545)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -568,6 +568,38 @@ async function samplePage(reason) {
|
||||
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
||||
};
|
||||
const textHashInput = (element) => trim(element.textContent || "", 800);
|
||||
const diagnosticSummaryText = (element) => {
|
||||
const summarySelectors = [
|
||||
".api-error-diagnostic-summary-text p",
|
||||
".api-error-diagnostic-summary-text",
|
||||
"[class*='diagnostic-summary-text' i] p",
|
||||
"[class*='diagnostic-summary-text' i]",
|
||||
"[data-testid*='diagnostic-summary' i]",
|
||||
"[data-testid*='error-summary' i]",
|
||||
"[role='alert'] p",
|
||||
"[role='alert']"
|
||||
];
|
||||
const parts = [];
|
||||
for (const selector of summarySelectors) {
|
||||
for (const candidate of Array.from(element.querySelectorAll(selector))) {
|
||||
if (!visible(candidate)) continue;
|
||||
const text = trim(candidate.textContent || "", 800);
|
||||
if (text && !parts.includes(text)) parts.push(text);
|
||||
}
|
||||
}
|
||||
const ownText = textHashInput(element);
|
||||
const text = parts.length > 0 ? parts.join(" ") : ownText;
|
||||
return text.replace(/\s+(?:!|i诊断|诊断详情)$/u, "").trim();
|
||||
};
|
||||
const diagnosticToggleOnly = (element, text) => {
|
||||
const compact = String(text || "").trim();
|
||||
if (!/^(?:!|i诊断|诊断|诊断详情)$/u.test(compact)) return false;
|
||||
const tag = element.tagName.toLowerCase();
|
||||
const role = element.getAttribute("role");
|
||||
const aria = element.getAttribute("aria-label") || "";
|
||||
const title = element.getAttribute("title") || "";
|
||||
return tag === "button" || role === "button" || /诊断/u.test(aria) || /诊断/u.test(title);
|
||||
};
|
||||
const summarize = (selector, limit) => Array.from(document.querySelectorAll(selector)).filter(visible).slice(-limit).map((element, index) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
@@ -591,10 +623,13 @@ async function samplePage(reason) {
|
||||
const traceRows = summarize(traceSelector, 30);
|
||||
const diagnostics = Array.from(document.querySelectorAll(diagnosticSelector)).filter(visible).slice(-40).map((element, index) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const text = textHashInput(element);
|
||||
const text = diagnosticSummaryText(element);
|
||||
if (!text || diagnosticToggleOnly(element, text)) return null;
|
||||
const traceMatch = text.match(/\b(?:trace_id=)?(trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/iu);
|
||||
const httpStatusMatch = text.match(/\bHTTP\s+([1-5][0-9]{2})\b/iu);
|
||||
const idleMatch = text.match(/\bidle\s+(\d+)s\b/iu);
|
||||
const waitingForMatch = text.match(/\bwaitingFor=([^\s;;,,)]+)/iu);
|
||||
const lastEventLabelMatch = text.match(/\blastEventLabel=([^\s;;,,)]+)/iu);
|
||||
const diagnosticCode = httpStatusMatch ? "http-" + httpStatusMatch[1] : /turn\s*超过|无新活动/iu.test(text) ? "turn-idle-no-activity" : /Failed to fetch/iu.test(text) ? "failed-to-fetch" : "diagnostic";
|
||||
return {
|
||||
index,
|
||||
@@ -609,10 +644,12 @@ async function samplePage(reason) {
|
||||
traceId: traceMatch?.[1] || null,
|
||||
httpStatus: httpStatusMatch ? Number(httpStatusMatch[1]) : null,
|
||||
idleSeconds: idleMatch ? Number(idleMatch[1]) : null,
|
||||
waitingFor: waitingForMatch?.[1] || null,
|
||||
lastEventLabel: lastEventLabelMatch?.[1] || null,
|
||||
text,
|
||||
rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
|
||||
};
|
||||
});
|
||||
}).filter(Boolean);
|
||||
const turns = Array.from(document.querySelectorAll('article.message-card[data-role="agent"], .message-card[data-role="agent"], article[data-role="agent"]')).filter(visible).map((element, index) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const text = textHashInput(element);
|
||||
@@ -1007,6 +1044,30 @@ function buildPromptNetworkReport(control, network) {
|
||||
};
|
||||
}
|
||||
|
||||
function parseDomDiagnosticSummary(text) {
|
||||
const value = String(text || "");
|
||||
const traceMatch = value.match(/\b(?:trace_id=)?(trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/iu);
|
||||
const httpStatusMatch = value.match(/\bHTTP\s+([1-5][0-9]{2})\b/iu);
|
||||
const idleMatch = value.match(/\bidle\s+(\d+)s\b/iu);
|
||||
const waitingForMatch = value.match(/\bwaitingFor=([^\s;;,,)]+)/iu);
|
||||
const lastEventLabelMatch = value.match(/\blastEventLabel=([^\s;;,,)]+)/iu);
|
||||
const diagnosticCode = httpStatusMatch
|
||||
? "http-" + httpStatusMatch[1]
|
||||
: /turn\s*超过|无新活动/iu.test(value)
|
||||
? "turn-idle-no-activity"
|
||||
: /Failed to fetch/iu.test(value)
|
||||
? "failed-to-fetch"
|
||||
: "diagnostic";
|
||||
return {
|
||||
diagnosticCode,
|
||||
traceId: traceMatch?.[1] || null,
|
||||
httpStatus: httpStatusMatch ? Number(httpStatusMatch[1]) : null,
|
||||
idleSeconds: idleMatch ? Number(idleMatch[1]) : null,
|
||||
waitingFor: waitingForMatch?.[1] || null,
|
||||
lastEventLabel: lastEventLabelMatch?.[1] || null
|
||||
};
|
||||
}
|
||||
|
||||
function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
const promptTimes = control
|
||||
.filter((item) => item.type === "sendPrompt" && item.phase === "completed")
|
||||
@@ -1032,16 +1093,19 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
for (const diagnostic of sample.diagnostics.slice(0, 12)) {
|
||||
const text = diagnostic?.textPreview || diagnostic?.text || "";
|
||||
if (!String(text).trim()) continue;
|
||||
const parsedDiagnostic = parseDomDiagnosticSummary(text);
|
||||
domDiagnostics.push({
|
||||
seq: sample.seq ?? null,
|
||||
ts: sample.ts ?? null,
|
||||
promptIndex,
|
||||
source: "diagnostic-node",
|
||||
className: diagnostic.className ?? null,
|
||||
diagnosticCode: diagnostic.diagnosticCode ?? null,
|
||||
traceId: diagnostic.traceId ?? null,
|
||||
httpStatus: diagnostic.httpStatus ?? null,
|
||||
idleSeconds: diagnostic.idleSeconds ?? null,
|
||||
diagnosticCode: diagnostic.diagnosticCode ?? parsedDiagnostic.diagnosticCode,
|
||||
traceId: diagnostic.traceId ?? parsedDiagnostic.traceId,
|
||||
httpStatus: diagnostic.httpStatus ?? parsedDiagnostic.httpStatus,
|
||||
idleSeconds: diagnostic.idleSeconds ?? parsedDiagnostic.idleSeconds,
|
||||
waitingFor: diagnostic.waitingFor ?? parsedDiagnostic.waitingFor,
|
||||
lastEventLabel: diagnostic.lastEventLabel ?? parsedDiagnostic.lastEventLabel,
|
||||
compact: diagnostic.compact ?? null,
|
||||
expanded: diagnostic.expanded ?? null,
|
||||
routeSessionId: sample.routeSessionId ?? null,
|
||||
@@ -1053,11 +1117,18 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
}
|
||||
const texts = sampleTexts(sample).filter(isDiagnosticText);
|
||||
for (const text of texts.slice(0, 4)) {
|
||||
const parsedDiagnostic = parseDomDiagnosticSummary(text);
|
||||
domDiagnostics.push({
|
||||
seq: sample.seq ?? null,
|
||||
ts: sample.ts ?? null,
|
||||
promptIndex,
|
||||
source: "sample-text",
|
||||
diagnosticCode: parsedDiagnostic.diagnosticCode,
|
||||
traceId: parsedDiagnostic.traceId,
|
||||
httpStatus: parsedDiagnostic.httpStatus,
|
||||
idleSeconds: parsedDiagnostic.idleSeconds,
|
||||
waitingFor: parsedDiagnostic.waitingFor,
|
||||
lastEventLabel: parsedDiagnostic.lastEventLabel,
|
||||
routeSessionId: sample.routeSessionId ?? null,
|
||||
activeSessionId: sample.activeSessionId ?? null,
|
||||
textHash: sha256(text),
|
||||
@@ -1941,7 +2012,7 @@ function renderMarkdown(report) {
|
||||
? report.runtimeAlerts.networkRequestFailedByPath.slice(0, 40).map((item) => "- requestfailed " + item.method + " " + item.urlPath + " count=" + item.count + " failure=" + (item.failureKinds?.slice(0, 4).join(",") || "-") + " prompts=" + (item.promptIndexes?.join(",") || "-") + " first=" + (item.firstAt || "-") + " last=" + (item.lastAt || "-")).join("\n")
|
||||
: "- 无 requestfailed。";
|
||||
const domDiagnosticLines = Array.isArray(report.runtimeAlerts?.domDiagnostics) && report.runtimeAlerts.domDiagnostics.length > 0
|
||||
? report.runtimeAlerts.domDiagnostics.slice(0, 40).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " source=" + (item.source || "-") + " code=" + (item.diagnosticCode || "-") + " traceId=" + (item.traceId || "-") + " http=" + (item.httpStatus ?? "-") + " idle=" + (item.idleSeconds ?? "-") + " textHash=" + (item.textHash || "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n")
|
||||
? report.runtimeAlerts.domDiagnostics.slice(0, 40).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " source=" + (item.source || "-") + " code=" + (item.diagnosticCode || "-") + " traceId=" + (item.traceId || "-") + " http=" + (item.httpStatus ?? "-") + " idle=" + (item.idleSeconds ?? "-") + " waitingFor=" + (item.waitingFor || "-") + " lastEventLabel=" + (item.lastEventLabel || "-") + " textHash=" + (item.textHash || "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n")
|
||||
: "- 无 DOM 诊断文本。";
|
||||
const consoleAlertLines = Array.isArray(report.runtimeAlerts?.consoleAlerts) && report.runtimeAlerts.consoleAlerts.length > 0
|
||||
? report.runtimeAlerts.consoleAlerts.slice(0, 40).map((item) => "- " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " type=" + (item.type || "-") + " status=" + (item.status ?? "-") + " path=" + (item.urlPath || "-") + " traceId=" + (item.traceId || "-") + " textHash=" + (item.textHash || "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n")
|
||||
|
||||
Reference in New Issue
Block a user