feat(monitor): surface workbench sentinel root causes
This commit is contained in:
@@ -1084,6 +1084,24 @@ select {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.finding-root-cause {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
padding: 7px 8px;
|
||||
border-left: 3px solid #2563eb;
|
||||
border-radius: 4px;
|
||||
background: #f5f9ff;
|
||||
color: #1f2937;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.finding-root-cause strong {
|
||||
color: #1d4ed8;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.finding-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -523,6 +523,9 @@ function renderFindingItemCollapsed(item) {
|
||||
const latestRunId = item.latestRunId || "-";
|
||||
const hasLatestRun = latestRunId !== "-";
|
||||
const codeLabel = displayFindingCode(code);
|
||||
const rootCause = findingRootCauseText(item);
|
||||
const evidence = findingEvidenceText(item);
|
||||
const nextAction = item.nextAction || findingNextAction(code);
|
||||
return `<article class="finding-item is-collapsed" data-finding-run-id="${escapeAttr(latestRunId)}">
|
||||
<div class="finding-row" data-finding-toggle>
|
||||
<span class="finding-title">${escapeHtml(codeLabel)}${codeLabel === code ? "" : ` <small class="mono">${escapeHtml(code)}</small>`}<small> · ${formatNumber(item.count ?? 0)} 次</small></span>
|
||||
@@ -536,7 +539,9 @@ function renderFindingItemCollapsed(item) {
|
||||
</div>
|
||||
<div class="finding-meta">次数=${escapeHtml(String(item.count ?? 0))} · 运行=${escapeHtml(String(item.runCount ?? 0))} · 最近=${escapeHtml(item.latestAt ? formatRelative(item.latestAt) : "-")}</div>
|
||||
<div class="finding-meta mono">run=${escapeHtml(latestRunId)} report=${escapeHtml(item.latestReportJsonSha256 || "-")}</div>
|
||||
<div class="finding-meta">${escapeHtml(findingNextAction(code))}</div>
|
||||
${rootCause ? `<div class="finding-root-cause"><strong>根因</strong><span>${escapeHtml(rootCause)}</span></div>` : ""}
|
||||
${evidence ? `<div class="finding-meta mono">evidence=${escapeHtml(evidence)}</div>` : ""}
|
||||
<div class="finding-meta">${escapeHtml(nextAction)}</div>
|
||||
<button type="button" class="link-button" data-open-finding-run="${escapeAttr(latestRunId)}"${hasLatestRun ? "" : " disabled"}>打开最近运行</button>
|
||||
</div>
|
||||
</article>`;
|
||||
@@ -732,12 +737,13 @@ function detailFindings(findings) {
|
||||
}
|
||||
return `<article class="detail-block"><strong>运行发现项 · ${formatNumber(findings.length)} 条</strong>
|
||||
<div class="detail-table-frame"><table class="detail-table"><thead><tr>
|
||||
<th>严重级别</th><th>代码</th><th>次数</th><th>摘要</th><th>报告</th>
|
||||
<th>严重级别</th><th>代码</th><th>次数</th><th>摘要</th><th>根因</th><th>报告</th>
|
||||
</tr></thead><tbody>${findings.map((item) => `<tr>
|
||||
<td><span class="severity-pill ${severityClass(item.severity)}">${escapeHtml(displaySeverity(item.severity))}</span></td>
|
||||
<td class="mono">${escapeHtml(item.finding_id || item.findingId || "-")}</td>
|
||||
<td>${escapeHtml(String(item.count ?? 0))}</td>
|
||||
<td>${escapeHtml(shortText(displayFindingSummary(item.finding_id || item.findingId || "", item.summary || ""), 220))}</td>
|
||||
<td>${escapeHtml(shortText(findingRootCauseText(item) || item.nextAction || "-", 240))}</td>
|
||||
<td class="mono">${escapeHtml(shortText(item.report_json_sha256 || item.reportJsonSha256 || "-", 24))}</td>
|
||||
</tr>`).join("")}</tbody></table></div>
|
||||
</article>`;
|
||||
@@ -1252,6 +1258,10 @@ function displayFindingCode(code) {
|
||||
const normalized = String(code || "").toLowerCase();
|
||||
const labels = {
|
||||
"quick-verify-no-business-turn": "quick verify 未触达业务 turn",
|
||||
"session-rail-title-fallback-root-cause": "INV-02 会话标题 fallback 根因",
|
||||
"trace-events-page-read-404-root-cause": "INV-07 trace events 404 根因",
|
||||
"trace-events-page-read-http-error-root-cause": "trace events HTTP 错误根因",
|
||||
"trace-events-page-read-requestfailed-root-cause": "trace events 网络失败根因",
|
||||
"observer-command-failed": "观察器控制命令失败",
|
||||
"runtime-requestfailed": "运行时请求失败",
|
||||
"runtime-console-alerts": "运行时控制台告警",
|
||||
@@ -1267,6 +1277,10 @@ function displayFindingSummary(code, summary) {
|
||||
const normalized = String(code || "").toLowerCase();
|
||||
const summaries = {
|
||||
"quick-verify-no-business-turn": "quick verify 没有形成 sendPrompt、session、trace rows 或 Final Response;不能把公开 dashboard 200 当作 HWLAB 恢复证据。",
|
||||
"session-rail-title-fallback-root-cause": "会话列表可见 Session ses_* fallback 标题;根因定位到 session list projection/read model 或 rail 数据绑定缺稳定 title/preview。",
|
||||
"trace-events-page-read-404-root-cause": "trace events page read 返回 404;根因定位到 /v1/workbench/traces/:traceId/events API 分页/read-model 合约,早于 DOM 渲染。",
|
||||
"trace-events-page-read-http-error-root-cause": "trace events page read 返回 HTTP 错误;根因定位到 trace-events API 路径,不是通用前端渲染失败。",
|
||||
"trace-events-page-read-requestfailed-root-cause": "trace events page read 在浏览器网络层失败;monitor 已定位路径,但需要 OTel/API 字段确认后端根因。",
|
||||
"observer-command-failed": "observe control command 失败,需要查看 observer timeline 和 failed command 文件确认是 readiness、session API、超时还是 runner shutdown。",
|
||||
"runtime-requestfailed": "页面运行时存在请求失败,需要按路径聚合确认是 asset/provenance 噪声、public origin、auth/session 还是 Workbench API。",
|
||||
"runtime-console-alerts": "页面控制台出现告警,需要结合 run detail 的 network/console 证据判断是否影响业务 turn。",
|
||||
@@ -1284,6 +1298,10 @@ function findingNextAction(code) {
|
||||
const normalized = String(code || "").toLowerCase();
|
||||
const actions = {
|
||||
"quick-verify-no-business-turn": "下一步: 打开该 run 的 turn-summary/trace-frame,并用 CLI 对照命令确认没有业务 turn。",
|
||||
"session-rail-title-fallback-root-cause": "下一步: 查同 run 的 OTel session_list_read fallbackTitleCount/fallbackTitleRatio,修 session list projection/read model title/preview。",
|
||||
"trace-events-page-read-404-root-cause": "下一步: 查同 trace 的 OTel trace_events_read sinceSeq/range/hasMore/fullTraceLoaded,修 trace-events 分页合约或 instrumentation。",
|
||||
"trace-events-page-read-http-error-root-cause": "下一步: 按 traceId/afterProjectedSeq 对齐 OTel trace_events_read 和 HTTP route span。",
|
||||
"trace-events-page-read-requestfailed-root-cause": "下一步: 先排除 observer refresh/navigation;若非刷新噪声,补 HTTP route span 的 traceId/status/afterProjectedSeq 字段。",
|
||||
"observer-command-failed": "下一步: 查看 observe collect timeline 和 failed command 文件,定位失败阶段。",
|
||||
"runtime-requestfailed": "下一步: 按请求路径聚合失败,区分网络、auth/session、API 或静态资源问题。",
|
||||
"runtime-console-alerts": "下一步: 结合 console 样本与业务 trace 判断是否为阻塞级。",
|
||||
@@ -1294,6 +1312,23 @@ function findingNextAction(code) {
|
||||
return actions[normalized] || "下一步: 打开最近运行详情,并用 CLI 对照命令复核同一 run/observer/report。";
|
||||
}
|
||||
|
||||
function findingRootCauseText(item) {
|
||||
const rootCause = item.rootCause || "";
|
||||
const status = item.rootCauseStatus || "";
|
||||
const confidence = item.rootCauseConfidence || "";
|
||||
if (!rootCause && !status && !confidence) return "";
|
||||
return [
|
||||
rootCause ? `rootCause=${rootCause}` : "",
|
||||
status ? `status=${status}` : "",
|
||||
confidence ? `confidence=${confidence}` : "",
|
||||
].filter(Boolean).join(" · ");
|
||||
}
|
||||
|
||||
function findingEvidenceText(item) {
|
||||
const text = item.evidenceSummary || "";
|
||||
return text ? shortText(String(text), 220) : "";
|
||||
}
|
||||
|
||||
function formatSeveritySummary(counts) {
|
||||
const entries = Object.entries(counts || {}).filter(([, value]) => Number(value || 0) > 0);
|
||||
if (entries.length === 0) return "无";
|
||||
|
||||
Reference in New Issue
Block a user