fix: tighten web-probe observe diagnostics
This commit is contained in:
@@ -7444,7 +7444,102 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
|
||||
"node -e \"const fs=require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(fs.readFileSync(process.argv[2], 'utf8').replace(/\\s+/g, ''), 'base64'))\" \"$analyzer\" \"$analyzer_b64\"",
|
||||
"rm -f \"$analyzer_b64\"",
|
||||
"chmod 700 \"$analyzer\"",
|
||||
"UNIDESK_WEB_OBSERVE_STATE_DIR=\"$state_dir\" node \"$analyzer\"",
|
||||
"mkdir -p \"$state_dir/analysis\"",
|
||||
"analysis_stdout=\"$state_dir/analysis/analyzer-stdout.json\"",
|
||||
"analysis_stderr=\"$state_dir/analysis/analyzer-stderr.log\"",
|
||||
"set +e",
|
||||
"UNIDESK_WEB_OBSERVE_STATE_DIR=\"$state_dir\" node \"$analyzer\" >\"$analysis_stdout\" 2>\"$analysis_stderr\"",
|
||||
"analyzer_exit=$?",
|
||||
"set -e",
|
||||
"report_json=\"$state_dir/analysis/report.json\"",
|
||||
"report_md=\"$state_dir/analysis/report.md\"",
|
||||
"node - \"$analysis_stdout\" \"$analysis_stderr\" \"$report_json\" \"$report_md\" \"$analyzer_exit\" <<'UNIDESK_WEB_OBSERVE_ANALYZE_COMPACT'",
|
||||
"const fs = require('fs');",
|
||||
"const crypto = require('crypto');",
|
||||
"const [stdoutPath, stderrPath, reportJsonPath, reportMdPath, analyzerExitRaw] = process.argv.slice(2);",
|
||||
"const analyzerExit = Number(analyzerExitRaw);",
|
||||
"const readText = (path) => { try { return fs.readFileSync(path, 'utf8'); } catch { return ''; } };",
|
||||
"const readJson = (path) => { const text = readText(path); if (!text.trim()) return null; try { return JSON.parse(text); } catch { return null; } };",
|
||||
"const sha256 = (path) => { const text = readText(path); return text ? 'sha256:' + crypto.createHash('sha256').update(text).digest('hex') : null; };",
|
||||
"const statSize = (path) => { try { return fs.statSync(path).size; } catch { return 0; } };",
|
||||
"const tail = (text, limit = 1200) => String(text || '').slice(-limit);",
|
||||
"const takeHead = (value, limit) => Array.isArray(value) ? value.slice(0, limit) : [];",
|
||||
"const takeTail = (value, limit) => Array.isArray(value) ? value.slice(-limit) : [];",
|
||||
"const clip = (value, limit = 160) => value === null || value === undefined ? null : String(value).slice(0, limit);",
|
||||
"const stdoutJson = readJson(stdoutPath);",
|
||||
"const reportJson = readJson(reportJsonPath);",
|
||||
"const source = (stdoutJson && stdoutJson.ok !== false ? stdoutJson : null) || reportJson || stdoutJson || null;",
|
||||
"const objectOrNull = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;",
|
||||
"const slimRound = (item) => { const v = objectOrNull(item) || {}; return { promptIndex: v.promptIndex ?? null, sampleCount: v.sampleCount ?? null, lastTotalElapsedSeconds: v.lastTotalElapsedSeconds ?? null, lastRecentUpdateSeconds: v.lastRecentUpdateSeconds ?? null, diagnosticSamples: v.diagnosticSamples ?? null, terminalSamples: v.terminalSamples ?? null, promptTextHash: clip(v.promptTextHash, 80) }; };",
|
||||
"const slimTurnColumn = (item) => { const v = objectOrNull(item) || {}; return { label: clip(v.label, 24), promptIndex: v.promptIndex ?? null, lastPromptIndex: v.lastPromptIndex ?? null, traceId: clip(v.traceId, 48), firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, source: clip(v.source, 48) }; };",
|
||||
"const slimSlowApi = (item) => { const v = objectOrNull(item) || {}; return { path: clip(v.path ?? v.route, 96), route: clip(v.route ?? v.path, 96), sampleCount: v.sampleCount ?? null, p95Ms: v.p95Ms ?? v.p95 ?? null, maxMs: v.maxMs ?? v.max ?? null, overFiveSecondCount: v.overFiveSecondCount ?? null }; };",
|
||||
"const slimFinding = (item) => { const v = objectOrNull(item) || {}; return { kind: clip(v.kind ?? v.code, 48), code: clip(v.code ?? v.kind, 48), severity: clip(v.severity ?? v.level, 24), level: clip(v.level ?? v.severity, 24), count: v.count ?? v.sampleCount ?? null, sampleCount: v.sampleCount ?? v.count ?? null, summary: clip(v.summary ?? v.message, 180), message: clip(v.message ?? v.summary, 180) }; };",
|
||||
"const slimDomGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, text: clip(v.text ?? v.preview, 180) }; };",
|
||||
"const slimDomSample = (item) => { const v = objectOrNull(item) || {}; return { seq: v.seq ?? null, ts: v.ts ?? null, source: clip(v.source, 32), diagnosticCode: clip(v.diagnosticCode, 48), traceId: clip(v.traceId, 64), httpStatus: v.httpStatus ?? null, idleSeconds: v.idleSeconds ?? null, waitingFor: clip(v.waitingFor, 48), lastEventLabel: clip(v.lastEventLabel, 80), text: clip(v.text ?? v.preview, 180) }; };",
|
||||
"const slimConsoleGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, type: clip(v.type, 24), status: v.status ?? null, path: clip(v.path, 96), lastAt: v.lastAt ?? v.firstAt ?? null, firstAt: v.firstAt ?? null, traceIds: Array.isArray(v.traceIds) ? v.traceIds.slice(0, 3).map((x) => clip(x, 64)) : [] }; };",
|
||||
"const slimConsoleSample = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, type: clip(v.type, 24), status: v.status ?? null, path: clip(v.path, 96), traceId: clip(v.traceId, 64), text: clip(v.text, 180) }; };",
|
||||
"const slimJump = (item) => { const v = objectOrNull(item) || {}; return { columnLabel: v.columnLabel ?? null, promptIndex: v.promptIndex ?? null, fromSeq: v.fromSeq ?? null, toSeq: v.toSeq ?? null, fromValue: v.fromValue ?? null, toValue: v.toValue ?? null, delta: v.delta ?? null, sampleDeltaSeconds: v.sampleDeltaSeconds ?? null, allowedIncreaseSeconds: v.allowedIncreaseSeconds ?? null, traceId: v.traceId ?? null }; };",
|
||||
"const srcMetrics = objectOrNull(source?.sampleMetrics);",
|
||||
"const metrics = srcMetrics ? {",
|
||||
" roundItems: takeTail(srcMetrics.roundItems, 8).map(slimRound),",
|
||||
" rounds: Array.isArray(srcMetrics.rounds) ? takeTail(srcMetrics.rounds, 8).map(slimRound) : srcMetrics.rounds,",
|
||||
" turnColumns: takeTail(srcMetrics.turnColumns, 8).map(slimTurnColumn),",
|
||||
" turnTimingRows: srcMetrics.turnTimingRows ?? null,",
|
||||
" turnTimingNonMonotonicCount: srcMetrics.turnTimingNonMonotonicCount ?? null,",
|
||||
" turnTimingTotalElapsedDecreaseCount: srcMetrics.turnTimingTotalElapsedDecreaseCount ?? null,",
|
||||
" turnTimingTerminalElapsedGrowthCount: srcMetrics.turnTimingTerminalElapsedGrowthCount ?? null,",
|
||||
" turnTimingRecentUpdateJumpCount: srcMetrics.turnTimingRecentUpdateJumpCount ?? null,",
|
||||
" turnTimingRecentUpdateSawtoothJumpCount: srcMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null,",
|
||||
" turnTimingRecentUpdateMaxIncreaseSeconds: srcMetrics.turnTimingRecentUpdateMaxIncreaseSeconds ?? null,",
|
||||
" promptSegments: srcMetrics.promptSegments ?? null",
|
||||
"} : null;",
|
||||
"const srcRuntimeAlerts = objectOrNull(source?.runtimeAlerts);",
|
||||
"const runtimeAlerts = srcRuntimeAlerts ? { httpErrorCount: srcRuntimeAlerts.httpErrorCount ?? null, requestFailedCount: srcRuntimeAlerts.requestFailedCount ?? null, domDiagnosticSampleCount: srcRuntimeAlerts.domDiagnosticSampleCount ?? null, consoleAlertCount: srcRuntimeAlerts.consoleAlertCount ?? null } : null;",
|
||||
"const srcPagePerformance = objectOrNull(source?.pagePerformance);",
|
||||
"const pagePerformance = srcPagePerformance ? { sameOriginApiPaths: srcPagePerformance.sameOriginApiPaths ?? null, slowPathCount: srcPagePerformance.slowPathCount ?? null, slowSampleCount: srcPagePerformance.slowSampleCount ?? null, worstP95Ms: srcPagePerformance.worstP95Ms ?? null } : null;",
|
||||
"const srcPromptNetwork = objectOrNull(source?.promptNetwork);",
|
||||
"const promptNetwork = srcPromptNetwork ? { promptSegments: srcPromptNetwork.promptSegments ?? null } : null;",
|
||||
"const compact = source ? {",
|
||||
" ok: analyzerExit === 0 && source.ok !== false,",
|
||||
" counts: source.counts ?? null,",
|
||||
" sampleMetrics: metrics,",
|
||||
" runtimeAlerts,",
|
||||
" pagePerformance,",
|
||||
" promptNetwork,",
|
||||
" pagePerformanceSlowApi: takeHead(source.pagePerformanceSlowApi, 5).map(slimSlowApi),",
|
||||
" findings: takeHead(source.findings, 8).map(slimFinding),",
|
||||
" domDiagnosticGroups: takeHead(source.domDiagnosticGroups, 5).map(slimDomGroup),",
|
||||
" domDiagnosticSamples: takeHead(source.domDiagnosticSamples, 5).map(slimDomSample),",
|
||||
" consoleAlertGroups: takeHead(source.consoleAlertGroups, 5).map(slimConsoleGroup),",
|
||||
" consoleAlertSamples: takeHead(source.consoleAlertSamples, 5).map(slimConsoleSample),",
|
||||
" turnTimingRecentUpdateJumps: takeHead(source.turnTimingRecentUpdateJumps, 5).map(slimJump),",
|
||||
" reportJsonPath: source.reportJsonPath || reportJsonPath,",
|
||||
" reportJsonSha256: source.reportJsonSha256 || sha256(reportJsonPath),",
|
||||
" reportMdPath: source.reportMdPath || reportMdPath,",
|
||||
" reportMdSha256: source.reportMdSha256 || sha256(reportMdPath),",
|
||||
" analyzer: {",
|
||||
" exitCode: Number.isFinite(analyzerExit) ? analyzerExit : null,",
|
||||
" recoveredFrom: stdoutJson && stdoutJson.ok !== false ? 'stdout' : reportJson ? 'report-file' : stdoutJson ? 'stdout-error' : 'missing-output',",
|
||||
" stdoutBytes: statSize(stdoutPath),",
|
||||
" stderrBytes: statSize(stderrPath),",
|
||||
" stderrTail: tail(readText(stderrPath))",
|
||||
" }",
|
||||
"} : {",
|
||||
" ok: false,",
|
||||
" error: 'web-probe-analyzer-output-missing',",
|
||||
" reportJsonPath,",
|
||||
" reportMdPath,",
|
||||
" analyzer: {",
|
||||
" exitCode: Number.isFinite(analyzerExit) ? analyzerExit : null,",
|
||||
" recoveredFrom: 'missing-output',",
|
||||
" stdoutBytes: statSize(stdoutPath),",
|
||||
" stderrBytes: statSize(stderrPath),",
|
||||
" stderrTail: tail(readText(stderrPath))",
|
||||
" }",
|
||||
"};",
|
||||
"console.log(JSON.stringify(compact));",
|
||||
"UNIDESK_WEB_OBSERVE_ANALYZE_COMPACT",
|
||||
"exit \"$analyzer_exit\"",
|
||||
].join("\n");
|
||||
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
|
||||
const analysis = parseJsonObject(result.stdout);
|
||||
@@ -7457,7 +7552,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
|
||||
lane: options.lane,
|
||||
workspace: spec.workspace,
|
||||
analysis,
|
||||
result: compactCommandResult(result),
|
||||
result: analysis === null ? compactCommandResultWithStdoutTail(result) : compactCommandResult(result),
|
||||
valuesRedacted: true,
|
||||
});
|
||||
}
|
||||
@@ -7473,6 +7568,7 @@ function withWebObserveAnalyzeRendered(value: Record<string, unknown>): Rendered
|
||||
|
||||
function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
const analysis = record(value.analysis);
|
||||
const analyzer = record(analysis?.analyzer);
|
||||
const result = record(value.result);
|
||||
const counts = record(analysis?.counts);
|
||||
const sampleMetrics = record(analysis?.sampleMetrics);
|
||||
@@ -7544,11 +7640,17 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
"",
|
||||
...(value.ok === false ? [
|
||||
"Blocked detail:",
|
||||
webObserveTable(["EXIT", "TIMEOUT", "STDOUT", "STDERR"], [[
|
||||
webObserveTable(["EXIT", "TIMEOUT", "ANALYZER", "STDOUT", "STDERR"], [[
|
||||
webObserveText(result.exitCode),
|
||||
webObserveText(result.timedOut),
|
||||
webObserveShort([
|
||||
analyzer?.recoveredFrom ? `source=${webObserveText(analyzer.recoveredFrom)}` : "",
|
||||
analyzer?.exitCode !== undefined && analyzer?.exitCode !== null ? `exit=${webObserveText(analyzer.exitCode)}` : "",
|
||||
analyzer?.stdoutBytes !== undefined && analyzer?.stdoutBytes !== null ? `stdout=${webObserveText(analyzer.stdoutBytes)}B` : "",
|
||||
analyzer?.stderrBytes !== undefined && analyzer?.stderrBytes !== null ? `stderr=${webObserveText(analyzer.stderrBytes)}B` : "",
|
||||
].filter((part) => part !== "").join(" "), 120),
|
||||
webObserveShort(webObserveText(result.stdoutTail), 120),
|
||||
webObserveShort(webObserveText(result.stderr), 120),
|
||||
webObserveShort(webObserveText(analyzer?.stderrTail ?? result.stderr), 500),
|
||||
]]),
|
||||
"",
|
||||
] : []),
|
||||
|
||||
@@ -813,7 +813,7 @@ async function samplePage(reason) {
|
||||
const activeSessionId = activeSession ? activeSession.getAttribute("data-session-id") : null;
|
||||
const messageSelector = '[data-testid*="message" i], [class*="message" i], article, [role="article"]';
|
||||
const traceSelector = '[data-testid*="trace" i], [class*="trace" i], [data-trace-id], [data-testid*="event" i]';
|
||||
const diagnosticSelector = '.api-error-diagnostic, .api-error-diagnostic.message-diagnostic, .api-error-diagnostic.projection-diagnostic, [data-testid="api-error-diagnostic" i], [data-testid="error-diagnostic" i], [role="alert"], .alert, .warning, .error';
|
||||
const diagnosticSelector = '.api-error-diagnostic, [class*="api-error-diagnostic" i], [class*="message-diagnostic" i], [class*="projection-diagnostic" i], [data-testid="api-error-diagnostic" i], [data-testid="error-diagnostic" i], [data-testid*="diagnostic" i], [role="alert"], [aria-live="assertive"]';
|
||||
const messages = summarize(messageSelector, 20);
|
||||
const traceRows = summarize(traceSelector, 30);
|
||||
const diagnostics = Array.from(document.querySelectorAll(diagnosticSelector)).filter(visible).slice(-40).map((element, index) => {
|
||||
@@ -1720,6 +1720,28 @@ function parseDomDiagnosticSummary(text) {
|
||||
};
|
||||
}
|
||||
|
||||
function isDomDiagnosticSampleText(text) {
|
||||
const value = String(text || "").replace(/\s+/g, " ").trim();
|
||||
if (!value) return false;
|
||||
const strongDiagnostic = [
|
||||
/\bHTTP\s+[45][0-9]{2}\b(?:[\s\S]{0,120}\btrace_id=|\b)/iu,
|
||||
/\btrace_id=(?:trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/iu,
|
||||
/workbench\s+turn\s*超过\s*\d+ms\s*无新活动/iu,
|
||||
/\bturn\s*超过\b[\s\S]{0,120}\b无新活动\b/iu,
|
||||
/\bprojection-resume:sync-failed\b/iu,
|
||||
/\bAgentRun\s+GET\b[\s\S]*\/result\b[\s\S]*timed out after\s+\d+ms\b/iu,
|
||||
/\bFailed to fetch\b/iu,
|
||||
/\bFailed to load resource\b[\s\S]{0,180}\bstatus of\s+[45][0-9]{2}\b/iu,
|
||||
/\bserver responded with a status of\s+[45][0-9]{2}\b/iu,
|
||||
/Code Agent\b[\s\S]{0,120}(?:无法连接上游|请求已结束)/iu
|
||||
].some((pattern) => pattern.test(value));
|
||||
if (!strongDiagnostic) return false;
|
||||
const looksLikeToolStdout = /\b(?:stdout|stderr):/iu.test(value)
|
||||
&& /(?:\becho\s+["']?===|\bnode\s+|\.tspy\b|tspy\/|===\s*[A-Za-z0-9_.-]+\s*===)/iu.test(value);
|
||||
if (!looksLikeToolStdout) return true;
|
||||
return /\b(?:trace_id=|HTTP\s+[45][0-9]{2}|workbench\s+turn\s*超过|projection-resume:sync-failed|Failed to fetch|Failed to load resource|server responded with a status of\s+[45][0-9]{2}|Code Agent\b[\s\S]{0,120}(?:无法连接上游|请求已结束))\b/iu.test(value);
|
||||
}
|
||||
|
||||
function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
const promptTimes = control
|
||||
.filter((item) => item.type === "sendPrompt" && item.phase === "completed")
|
||||
@@ -1767,7 +1789,7 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
});
|
||||
}
|
||||
}
|
||||
const texts = sampleTexts(sample).filter(isDiagnosticText);
|
||||
const texts = sampleTexts(sample).filter(isDomDiagnosticSampleText);
|
||||
for (const text of texts.slice(0, 4)) {
|
||||
const parsedDiagnostic = parseDomDiagnosticSummary(text);
|
||||
domDiagnostics.push({
|
||||
|
||||
Reference in New Issue
Block a user