fix(web-probe): report agentrun execution errors
This commit is contained in:
@@ -815,6 +815,7 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN
|
||||
if ((runtimeAlerts?.summary?.httpErrorCount ?? 0) > 0) findings.push({ id: "runtime-http-errors", severity: "amber", summary: "natural page requests returned HTTP error status during observation", count: runtimeAlerts.summary.httpErrorCount, groups: runtimeAlerts.networkHttpErrorsByPath.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.requestFailedCount ?? 0) > 0) findings.push({ id: "runtime-requestfailed", severity: "amber", summary: "browser requestfailed events were captured during observation", count: runtimeAlerts.summary.requestFailedCount, groups: runtimeAlerts.networkRequestFailedByPath.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.domDiagnosticSampleCount ?? 0) > 0) findings.push({ id: "runtime-dom-diagnostics", severity: "amber", summary: "diagnostic/error/warning-like text was visible in sampled DOM", count: runtimeAlerts.summary.domDiagnosticSampleCount, samples: runtimeAlerts.domDiagnostics.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.executionErrorCount ?? 0) > 0) findings.push({ id: "runtime-execution-errors", severity: "red", summary: "Workbench rendered execution failure/error rows during observation", count: runtimeAlerts.summary.executionErrorCount, groups: runtimeAlerts.runtimeExecutionErrorsByCode.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.consoleAlertCount ?? 0) > 0) findings.push({ id: "runtime-console-alerts", severity: "amber", summary: "browser console warning/error entries were captured during observation", count: runtimeAlerts.summary.consoleAlertCount, groups: runtimeAlerts.consoleAlertsByPath.slice(0, 12) });
|
||||
const naturalApi = network.filter((item) => item.observerInitiated === false && item.type === "response" && /\/v1\/|\/auth\//u.test(String(item.url || "")));
|
||||
findings.push({ id: "natural-api-dom-lag-baseline", severity: "info", summary: "natural API responses and DOM samples are available for manual lag correlation", naturalApiResponses: naturalApi.length, sampleCount: samples.length });
|
||||
@@ -925,6 +926,7 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
.filter((item) => item?.type === "requestfailed")
|
||||
.map((item) => networkAlertEvent(item, promptTimes));
|
||||
const domDiagnostics = [];
|
||||
const executionErrors = [];
|
||||
for (const sample of samples) {
|
||||
const tsMs = Date.parse(sample?.ts);
|
||||
const promptIndex = Number.isFinite(tsMs) ? latestPromptIndex(promptTimes, tsMs) : 0;
|
||||
@@ -964,6 +966,43 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
preview: limitText(text, 220)
|
||||
});
|
||||
}
|
||||
const seenExecutionErrors = new Set();
|
||||
for (const candidate of sampleExecutionErrorCandidates(sample)) {
|
||||
const parsed = parseExecutionErrorText(candidate.text);
|
||||
if (!parsed) continue;
|
||||
const dedupeKey = [candidate.source, candidate.traceId || "-", parsed.backend || "-", parsed.code || "-", parsed.status || "-", sha256(candidate.text)].join("|");
|
||||
if (seenExecutionErrors.has(dedupeKey)) continue;
|
||||
seenExecutionErrors.add(dedupeKey);
|
||||
executionErrors.push({
|
||||
seq: sample.seq ?? null,
|
||||
ts: sample.ts ?? null,
|
||||
promptIndex,
|
||||
source: candidate.source,
|
||||
backend: parsed.backend,
|
||||
status: parsed.status,
|
||||
code: parsed.code,
|
||||
rawCode: parsed.rawCode,
|
||||
totalSeconds: parsed.totalSeconds,
|
||||
traceId: candidate.traceId || parsed.traceId || null,
|
||||
messageId: candidate.messageId || null,
|
||||
routeSessionId: sample.routeSessionId ?? null,
|
||||
activeSessionId: sample.activeSessionId ?? null,
|
||||
textHash: sha256(candidate.text),
|
||||
preview: limitText(candidate.text, 260)
|
||||
});
|
||||
domDiagnostics.push({
|
||||
seq: sample.seq ?? null,
|
||||
ts: sample.ts ?? null,
|
||||
promptIndex,
|
||||
source: "execution-row",
|
||||
diagnosticCode: parsed.rawCode || parsed.code || "execution-error",
|
||||
traceId: candidate.traceId || parsed.traceId || null,
|
||||
routeSessionId: sample.routeSessionId ?? null,
|
||||
activeSessionId: sample.activeSessionId ?? null,
|
||||
textHash: sha256(candidate.text),
|
||||
preview: limitText(candidate.text, 220)
|
||||
});
|
||||
}
|
||||
}
|
||||
const consoleAlerts = consoleEvents
|
||||
.filter((item) => /error|warning|warn|assert/iu.test(String(item?.type || "")) || isDiagnosticText(item?.text))
|
||||
@@ -981,21 +1020,110 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
httpErrorCount: httpErrors.length,
|
||||
requestFailedCount: requestFailed.length,
|
||||
domDiagnosticSampleCount: domDiagnostics.length,
|
||||
executionErrorCount: executionErrors.length,
|
||||
consoleAlertCount: consoleAlerts.length,
|
||||
pageErrorCount: pageErrors.length,
|
||||
networkErrorGroupCount: groupNetworkAlerts(httpErrors).length,
|
||||
requestFailedGroupCount: groupNetworkAlerts(requestFailed).length,
|
||||
executionErrorGroupCount: groupExecutionErrors(executionErrors).length,
|
||||
consoleAlertGroupCount: groupConsoleAlerts(consoleAlerts).length
|
||||
},
|
||||
networkHttpErrorsByPath: groupNetworkAlerts(httpErrors),
|
||||
networkRequestFailedByPath: groupNetworkAlerts(requestFailed),
|
||||
domDiagnostics: domDiagnostics.slice(0, 80),
|
||||
runtimeExecutionErrors: executionErrors.slice(0, 120),
|
||||
runtimeExecutionErrorsByCode: groupExecutionErrors(executionErrors),
|
||||
consoleAlerts: consoleAlerts.slice(0, 80),
|
||||
consoleAlertsByPath: groupConsoleAlerts(consoleAlerts),
|
||||
pageErrors: pageErrors.slice(0, 40)
|
||||
};
|
||||
}
|
||||
|
||||
function sampleExecutionErrorCandidates(sample) {
|
||||
const candidates = [];
|
||||
const add = (source, items) => {
|
||||
if (!Array.isArray(items)) return;
|
||||
for (const item of items) {
|
||||
const text = String(item?.textPreview || item?.text || item?.preview || "").trim();
|
||||
if (!text) continue;
|
||||
if (!parseExecutionErrorText(text)) continue;
|
||||
candidates.push({
|
||||
source,
|
||||
text,
|
||||
traceId: item?.traceId ?? null,
|
||||
messageId: item?.messageId ?? null,
|
||||
status: item?.status ?? null
|
||||
});
|
||||
}
|
||||
};
|
||||
add("diagnostic-node", sample?.diagnostics);
|
||||
add("message", sample?.messages);
|
||||
add("trace-row", sample?.traceRows);
|
||||
add("turn", sample?.turns);
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function parseExecutionErrorText(text) {
|
||||
const value = String(text || "");
|
||||
const agentRunCodeMatch = value.match(/\bagentrun:error:([a-z0-9_.:-]+)/iu);
|
||||
const agentRunText = /\bAgentRun\s+error\b|\bagentrun:error:/iu.test(value);
|
||||
const providerUnavailable = /\bprovider[-_\s]*unavailable\b/iu.test(value);
|
||||
if (!agentRunCodeMatch && !agentRunText && !providerUnavailable) return null;
|
||||
const statusMatch = value.match(/\b(fail(?:ed)?|error|blocked|cancel(?:ed)?)\b/iu);
|
||||
const traceMatch = value.match(/\btrc_[A-Za-z0-9_-]+\b/u);
|
||||
const totalMatch = value.match(/\btotal\s*=\s*([0-9]{1,2}:[0-9]{2}(?::[0-9]{2})?)\b/iu)
|
||||
|| value.match(/总耗时\s*[::]?\s*([0-9]{1,2}:[0-9]{2}(?::[0-9]{2})?)/iu);
|
||||
const rawCode = agentRunCodeMatch ? "agentrun:error:" + agentRunCodeMatch[1] : providerUnavailable ? "provider-unavailable" : "agentrun:error";
|
||||
return {
|
||||
backend: agentRunText || agentRunCodeMatch ? "agentrun" : "unknown",
|
||||
status: normalizeExecutionStatus(statusMatch?.[1] || "error"),
|
||||
code: agentRunCodeMatch?.[1] || (providerUnavailable ? "provider-unavailable" : "error"),
|
||||
rawCode,
|
||||
totalSeconds: totalMatch ? parseClockDurationSeconds(totalMatch[1]) : null,
|
||||
traceId: traceMatch?.[0] || null
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeExecutionStatus(status) {
|
||||
const value = String(status || "").toLowerCase();
|
||||
if (value === "failed") return "fail";
|
||||
if (value === "cancelled" || value === "canceled") return "cancel";
|
||||
return value || "error";
|
||||
}
|
||||
|
||||
function parseClockDurationSeconds(value) {
|
||||
const parts = String(value || "").split(":").map((part) => Number(part));
|
||||
if (parts.length === 2 && parts.every(Number.isFinite)) return parts[0] * 60 + parts[1];
|
||||
if (parts.length === 3 && parts.every(Number.isFinite)) return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
||||
return null;
|
||||
}
|
||||
|
||||
function groupExecutionErrors(events) {
|
||||
const groups = new Map();
|
||||
for (const event of events) {
|
||||
const key = [event.backend || "-", event.status || "-", event.code || "-"].join(" ");
|
||||
const group = groups.get(key) || {
|
||||
backend: event.backend ?? null,
|
||||
status: event.status ?? null,
|
||||
code: event.code ?? null,
|
||||
rawCode: event.rawCode ?? null,
|
||||
count: 0,
|
||||
firstAt: event.ts,
|
||||
lastAt: event.ts,
|
||||
promptIndexes: [],
|
||||
traceIds: [],
|
||||
sources: []
|
||||
};
|
||||
group.count += 1;
|
||||
group.lastAt = event.ts;
|
||||
if (event.promptIndex && !group.promptIndexes.includes(event.promptIndex)) group.promptIndexes.push(event.promptIndex);
|
||||
if (event.traceId && !group.traceIds.includes(event.traceId)) group.traceIds.push(event.traceId);
|
||||
if (event.source && !group.sources.includes(event.source)) group.sources.push(event.source);
|
||||
groups.set(key, group);
|
||||
}
|
||||
return Array.from(groups.values()).sort((a, b) => b.count - a.count || String(a.code).localeCompare(String(b.code)));
|
||||
}
|
||||
|
||||
function consoleAlertEvent(item, promptTimes) {
|
||||
const text = String(item?.text || "");
|
||||
const statusMatch = text.match(/\bstatus\s+of\s+([1-5][0-9]{2})\b/iu) || text.match(/\bHTTP\s+([1-5][0-9]{2})\b/iu);
|
||||
@@ -1074,7 +1202,7 @@ function groupNetworkAlerts(events) {
|
||||
}
|
||||
|
||||
function isDiagnosticText(text) {
|
||||
return /Failed to fetch|request failed|realtime-gap|durable projection store|turn 超过|无法连接|暂时|报警|告警|警告|失败|错误|异常|超时|timeout|error|failed|warning|502|503|504|provider-unavailable|projection-resume/iu.test(String(text || ""));
|
||||
return /Failed to fetch|request failed|realtime-gap|durable projection store|turn 超过|无法连接|暂时|报警|告警|警告|失败|错误|异常|超时|timeout|error|failed|warning|502|503|504|provider-unavailable|agentrun:error|AgentRun error|projection-resume/iu.test(String(text || ""));
|
||||
}
|
||||
|
||||
function buildSampleMetrics(samples, control) {
|
||||
|
||||
Reference in New Issue
Block a user