Merge pull request #639 from pikasTech/fix/web-probe-observe-recent-window-20260622
fix(web-probe): observe analyze 默认展示最近窗口
This commit is contained in:
@@ -7475,6 +7475,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
|
||||
"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 slimNetworkGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, method: clip(v.method, 12), status: v.status ?? null, path: clip(v.path, 96), firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, promptIndexes: Array.isArray(v.promptIndexes) ? v.promptIndexes.slice(0, 6) : [], failureKinds: Array.isArray(v.failureKinds) ? v.failureKinds.slice(0, 4).map((x) => clip(x, 48)) : [] }; };",
|
||||
"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) }; };",
|
||||
@@ -7502,12 +7503,16 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
|
||||
"const compact = source ? {",
|
||||
" ok: analyzerExit === 0 && source.ok !== false,",
|
||||
" counts: source.counts ?? null,",
|
||||
" analysisWindow: objectOrNull(source.analysisWindow),",
|
||||
" archiveSummary: objectOrNull(source.archiveSummary),",
|
||||
" sampleMetrics: metrics,",
|
||||
" runtimeAlerts,",
|
||||
" pagePerformance,",
|
||||
" promptNetwork,",
|
||||
" pagePerformanceSlowApi: takeHead(source.pagePerformanceSlowApi, 5).map(slimSlowApi),",
|
||||
" findings: takeHead(source.findings, 8).map(slimFinding),",
|
||||
" httpErrorGroups: takeHead(source.httpErrorGroups, 8).map(slimNetworkGroup),",
|
||||
" requestFailedGroups: takeHead(source.requestFailedGroups, 8).map(slimNetworkGroup),",
|
||||
" domDiagnosticGroups: takeHead(source.domDiagnosticGroups, 5).map(slimDomGroup),",
|
||||
" domDiagnosticSamples: takeHead(source.domDiagnosticSamples, 5).map(slimDomSample),",
|
||||
" consoleAlertGroups: takeHead(source.consoleAlertGroups, 5).map(slimConsoleGroup),",
|
||||
@@ -7571,6 +7576,8 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
const analyzer = record(analysis?.analyzer);
|
||||
const result = record(value.result);
|
||||
const counts = record(analysis?.counts);
|
||||
const analysisWindow = record(analysis?.analysisWindow);
|
||||
const archiveSummary = record(analysis?.archiveSummary);
|
||||
const sampleMetrics = record(analysis?.sampleMetrics);
|
||||
const runtimeAlerts = record(analysis?.runtimeAlerts);
|
||||
const pagePerformance = record(analysis?.pagePerformance);
|
||||
@@ -7581,6 +7588,8 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
const turnColumnCount = Array.isArray(sampleMetrics?.turnColumns) ? sampleMetrics.turnColumns.length : sampleMetrics?.turnColumns;
|
||||
const slowApis = Array.isArray(analysis?.pagePerformanceSlowApi) ? analysis.pagePerformanceSlowApi.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
|
||||
const findings = Array.isArray(analysis?.findings) ? analysis.findings.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
|
||||
const httpErrorGroups = Array.isArray(analysis?.httpErrorGroups) ? analysis.httpErrorGroups.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
|
||||
const requestFailedGroups = Array.isArray(analysis?.requestFailedGroups) ? analysis.requestFailedGroups.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
|
||||
const domDiagnostics = Array.isArray(analysis?.domDiagnosticGroups) ? analysis.domDiagnosticGroups.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 5) : [];
|
||||
const domDiagnosticSamples = Array.isArray(analysis?.domDiagnosticSamples) ? analysis.domDiagnosticSamples.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
|
||||
const consoleAlertGroups = Array.isArray(analysis?.consoleAlertGroups) ? analysis.consoleAlertGroups.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
|
||||
@@ -7635,6 +7644,22 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
webObserveText(item.allowedIncreaseSeconds),
|
||||
webObserveShort(webObserveText(item.traceId), 24),
|
||||
]);
|
||||
const httpErrorRows = httpErrorGroups.map((item) => [
|
||||
webObserveText(item.count),
|
||||
webObserveText(item.method),
|
||||
webObserveText(item.status),
|
||||
webObserveShort(webObserveText(item.path), 52),
|
||||
webObserveShort(webObserveText(item.lastAt ?? item.firstAt), 24),
|
||||
webObserveShort(webObserveArray(item.failureKinds).join(",") || "-", 40),
|
||||
]);
|
||||
const requestFailedRows = requestFailedGroups.map((item) => [
|
||||
webObserveText(item.count),
|
||||
webObserveText(item.method),
|
||||
webObserveText(item.status),
|
||||
webObserveShort(webObserveText(item.path), 52),
|
||||
webObserveShort(webObserveText(item.lastAt ?? item.firstAt), 24),
|
||||
webObserveShort(webObserveArray(item.failureKinds).join(",") || "-", 40),
|
||||
]);
|
||||
const lines = [
|
||||
`hwlab nodes web-probe observe analyze (${webObserveText(value.status)})`,
|
||||
"",
|
||||
@@ -7665,6 +7690,18 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
webObserveText(counts?.artifacts),
|
||||
]]),
|
||||
"",
|
||||
"Analysis window:",
|
||||
webObserveTable(["WINDOW", "FROM", "TO", "SAMPLES", "NETWORK", "CONSOLE", "ARCHIVE_SAMPLES", "ARCHIVE_RED"], [[
|
||||
webObserveText(analysisWindow?.name ?? "recent-5m"),
|
||||
webObserveShort(webObserveText(analysisWindow?.fromAt), 24),
|
||||
webObserveShort(webObserveText(analysisWindow?.toAt), 24),
|
||||
webObserveText(analysisWindow?.samples),
|
||||
webObserveText(analysisWindow?.network),
|
||||
webObserveText(analysisWindow?.console),
|
||||
webObserveText(record(archiveSummary?.sampleMetrics)?.sampleCount ?? counts?.samples),
|
||||
webObserveText(archiveSummary?.redFindingCount),
|
||||
]]),
|
||||
"",
|
||||
"Reports:",
|
||||
webObserveTable(["KIND", "PATH", "SHA256"], [
|
||||
["json", webObserveShort(webObserveText(analysis?.reportJsonPath), 96), webObserveShort(webObserveText(analysis?.reportJsonSha256), 24)],
|
||||
@@ -7717,6 +7754,12 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
|
||||
webObserveText(promptNetwork?.promptSegments),
|
||||
]]),
|
||||
"",
|
||||
"HTTP error groups:",
|
||||
webObserveTable(["COUNT", "METHOD", "STATUS", "PATH", "LAST", "FAILURE"], httpErrorRows.length > 0 ? httpErrorRows : [["-", "-", "-", "-", "-", "-"]]),
|
||||
"",
|
||||
"Request failed groups:",
|
||||
webObserveTable(["COUNT", "METHOD", "STATUS", "PATH", "LAST", "FAILURE"], requestFailedRows.length > 0 ? requestFailedRows : [["-", "-", "-", "-", "-", "-"]]),
|
||||
"",
|
||||
"Slow API (>5s):",
|
||||
webObserveTable(["PATH", "SAMPLES", "P95", "MAX", "OVER5S"], slowApis.length > 0 ? slowApis.map((item) => [
|
||||
webObserveShort(webObserveText(item.path ?? item.route), 52),
|
||||
|
||||
@@ -1114,6 +1114,7 @@ const promptNetwork = buildPromptNetworkReport(control, network);
|
||||
const runtimeAlerts = buildRuntimeAlerts(samples, control, network, consoleEvents, errors);
|
||||
const findings = buildFindings(samples, control, network, errors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance);
|
||||
if (jsonlReadIssues.length > 0) findings.unshift({ id: "jsonl-read-issues", severity: "red", summary: "observer analyzer hit JSONL read/parse issues", count: jsonlReadIssues.length, issues: jsonlReadIssues.slice(0, 20) });
|
||||
const recentWindow = buildRecentAnalysisWindow({ samples, control, network, consoleEvents, errors, manifest });
|
||||
const commandTimeline = control.filter((item) => item.phase === "completed" || item.phase === "failed").map((item) => ({ ts: item.ts, phase: item.phase, commandId: item.commandId, type: item.type, input: item.input, afterUrl: item.afterUrl }));
|
||||
const report = {
|
||||
ok: findings.filter((item) => item.severity === "red").length === 0,
|
||||
@@ -1131,6 +1132,7 @@ const report = {
|
||||
promptNetwork,
|
||||
runtimeAlerts,
|
||||
findings,
|
||||
windows: { recent: recentWindow },
|
||||
readIssues: jsonlReadIssues,
|
||||
artifactSummary: await artifactSummary(artifacts),
|
||||
safety: { offlineOnly: true, browserDriven: false, apiFetch: false, valuesRedacted: true },
|
||||
@@ -1147,18 +1149,46 @@ console.log(JSON.stringify({
|
||||
reportMdPath,
|
||||
reportMdSha256: mdMeta.sha256,
|
||||
counts: report.counts,
|
||||
archiveSummary: {
|
||||
sampleMetrics: sampleMetrics.summary,
|
||||
pagePerformance: pagePerformance.summary,
|
||||
runtimeAlerts: runtimeAlerts.summary,
|
||||
findingCount: findings.length,
|
||||
redFindingCount: findings.filter((item) => item.severity === "red").length,
|
||||
},
|
||||
analysisWindow: recentWindow.summary,
|
||||
jsonlReadIssues: jsonlReadIssues.slice(0, 3).map((item) => ({ file: item.file, line: item.line ?? null, error: String(item.error ?? "").slice(0, 160) })),
|
||||
readIssues: jsonlReadIssues.slice(0, 3).map((item) => ({ file: item.file, line: item.line ?? null, error: String(item.error ?? "").slice(0, 160) })),
|
||||
sampleMetrics: {
|
||||
...sampleMetrics.summary,
|
||||
rounds: sampleMetrics.rounds.slice(-8).map((item) => ({ promptIndex: item.promptIndex, promptTextHash: item.promptTextHash, sampleCount: item.sampleCount, firstSeq: item.firstSeq, lastSeq: item.lastSeq, lastTotalElapsedSeconds: item.lastTotalElapsedSeconds, lastRecentUpdateSeconds: item.lastRecentUpdateSeconds, diagnosticSamples: item.diagnosticSamples, terminalSamples: item.terminalSamples, finalTextSamples: item.finalTextSamples, turnTimingRecentUpdateJumpCount: item.turnTimingRecentUpdateJumpCount, turnTimingRecentUpdateMaxIncreaseSeconds: item.turnTimingRecentUpdateMaxIncreaseSeconds })),
|
||||
turnColumns: sampleMetrics.turnColumns.slice(-12).map((item) => ({ label: item.label, source: item.source, promptIndex: item.promptIndex, lastPromptIndex: item.lastPromptIndex, firstSeq: item.firstSeq, lastSeq: item.lastSeq, traceId: item.traceId, messageId: item.messageId })),
|
||||
...recentWindow.sampleMetrics.summary,
|
||||
rounds: recentWindow.sampleMetrics.rounds.slice(-8).map((item) => ({ promptIndex: item.promptIndex, promptTextHash: item.promptTextHash, sampleCount: item.sampleCount, firstSeq: item.firstSeq, lastSeq: item.lastSeq, lastTotalElapsedSeconds: item.lastTotalElapsedSeconds, lastRecentUpdateSeconds: item.lastRecentUpdateSeconds, diagnosticSamples: item.diagnosticSamples, terminalSamples: item.terminalSamples, finalTextSamples: item.finalTextSamples, turnTimingRecentUpdateJumpCount: item.turnTimingRecentUpdateJumpCount, turnTimingRecentUpdateMaxIncreaseSeconds: item.turnTimingRecentUpdateMaxIncreaseSeconds })),
|
||||
turnColumns: recentWindow.sampleMetrics.turnColumns.slice(-12).map((item) => ({ label: item.label, source: item.source, promptIndex: item.promptIndex, lastPromptIndex: item.lastPromptIndex, firstSeq: item.firstSeq, lastSeq: item.lastSeq, traceId: item.traceId, messageId: item.messageId })),
|
||||
},
|
||||
pageProvenance: pageProvenance.summary,
|
||||
pagePerformance: pagePerformance.summary,
|
||||
promptNetwork: promptNetwork.summary,
|
||||
runtimeAlerts: runtimeAlerts.summary,
|
||||
domDiagnosticGroups: runtimeAlerts.domDiagnosticsByFingerprint.slice(0, 5).map((item) => ({
|
||||
pageProvenance: recentWindow.pageProvenance.summary,
|
||||
pagePerformance: recentWindow.pagePerformance.summary,
|
||||
promptNetwork: recentWindow.promptNetwork.summary,
|
||||
runtimeAlerts: recentWindow.runtimeAlerts.summary,
|
||||
httpErrorGroups: recentWindow.runtimeAlerts.networkHttpErrorsByPath.slice(0, 8).map((item) => ({
|
||||
method: item.method ?? null,
|
||||
status: item.status ?? null,
|
||||
path: item.urlPath ?? null,
|
||||
count: item.count,
|
||||
firstAt: item.firstAt ?? null,
|
||||
lastAt: item.lastAt ?? null,
|
||||
promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
|
||||
failureKinds: Array.isArray(item.failureKinds) ? item.failureKinds.slice(0, 4) : [],
|
||||
})),
|
||||
requestFailedGroups: recentWindow.runtimeAlerts.networkRequestFailedByPath.slice(0, 8).map((item) => ({
|
||||
method: item.method ?? null,
|
||||
status: item.status ?? null,
|
||||
path: item.urlPath ?? null,
|
||||
count: item.count,
|
||||
firstAt: item.firstAt ?? null,
|
||||
lastAt: item.lastAt ?? null,
|
||||
promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
|
||||
failureKinds: Array.isArray(item.failureKinds) ? item.failureKinds.slice(0, 4) : [],
|
||||
})),
|
||||
domDiagnosticGroups: recentWindow.runtimeAlerts.domDiagnosticsByFingerprint.slice(0, 5).map((item) => ({
|
||||
diagnosticCode: item.diagnosticCode ?? null,
|
||||
count: item.count,
|
||||
firstAt: item.firstAt,
|
||||
@@ -1167,7 +1197,7 @@ console.log(JSON.stringify({
|
||||
traceIds: Array.isArray(item.traceIds) ? item.traceIds.slice(0, 4) : [],
|
||||
text: String(item.preview ?? item.normalizedPreview ?? item.text ?? "").slice(0, 160),
|
||||
})),
|
||||
domDiagnosticSamples: runtimeAlerts.domDiagnostics.slice(-8).map((item) => ({
|
||||
domDiagnosticSamples: recentWindow.runtimeAlerts.domDiagnostics.slice(-8).map((item) => ({
|
||||
seq: item.seq ?? null,
|
||||
ts: item.ts ?? null,
|
||||
promptIndex: item.promptIndex ?? null,
|
||||
@@ -1180,7 +1210,7 @@ console.log(JSON.stringify({
|
||||
lastEventLabel: item.lastEventLabel ?? null,
|
||||
text: String(item.preview ?? item.text ?? "").slice(0, 180),
|
||||
})),
|
||||
consoleAlertGroups: runtimeAlerts.consoleAlertsByPath.slice(0, 8).map((item) => ({
|
||||
consoleAlertGroups: recentWindow.runtimeAlerts.consoleAlertsByPath.slice(0, 8).map((item) => ({
|
||||
type: item.type ?? null,
|
||||
status: item.status ?? null,
|
||||
path: item.urlPath ?? null,
|
||||
@@ -1190,7 +1220,7 @@ console.log(JSON.stringify({
|
||||
promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
|
||||
traceIds: Array.isArray(item.traceIds) ? item.traceIds.slice(0, 4) : [],
|
||||
})),
|
||||
consoleAlertSamples: runtimeAlerts.consoleAlerts.slice(-8).map((item) => ({
|
||||
consoleAlertSamples: recentWindow.runtimeAlerts.consoleAlerts.slice(-8).map((item) => ({
|
||||
ts: item.ts ?? null,
|
||||
promptIndex: item.promptIndex ?? null,
|
||||
type: item.type ?? null,
|
||||
@@ -1199,7 +1229,7 @@ console.log(JSON.stringify({
|
||||
traceId: item.traceId ?? null,
|
||||
text: String(item.preview ?? item.text ?? "").slice(0, 180),
|
||||
})),
|
||||
turnTimingRecentUpdateJumps: sampleMetrics.turnTimingRecentUpdateSawtoothJumps.slice(0, 8).map((item) => ({
|
||||
turnTimingRecentUpdateJumps: recentWindow.sampleMetrics.turnTimingRecentUpdateSawtoothJumps.slice(0, 8).map((item) => ({
|
||||
columnLabel: item.columnLabel ?? item.columnId ?? null,
|
||||
promptIndex: item.promptIndex ?? null,
|
||||
traceId: item.traceId ?? null,
|
||||
@@ -1214,8 +1244,8 @@ console.log(JSON.stringify({
|
||||
allowedIncreaseSeconds: item.allowedIncreaseSeconds ?? null,
|
||||
excessiveIncreaseSeconds: item.excessiveIncreaseSeconds ?? null,
|
||||
})),
|
||||
pagePerformanceSlowApi: pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, p95Ms: item.p95Ms, maxMs: item.maxMs, overFiveSecondCount: item.overFiveSecondCount })),
|
||||
findings: findings.slice(0, 8).map((item) => ({ kind: item.id ?? item.kind ?? item.code, severity: item.severity, count: item.count ?? item.sampleCount ?? null, summary: String(item.summary ?? item.message ?? "").slice(0, 180) })),
|
||||
pagePerformanceSlowApi: recentWindow.pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, p95Ms: item.p95Ms, maxMs: item.maxMs, overFiveSecondCount: item.overFiveSecondCount })),
|
||||
findings: recentWindow.findings.slice(0, 8).map((item) => ({ kind: item.id ?? item.kind ?? item.code, severity: item.severity, count: item.count ?? item.sampleCount ?? null, summary: String(item.summary ?? item.message ?? "").slice(0, 180) })),
|
||||
valuesRedacted: true,
|
||||
}));
|
||||
|
||||
@@ -1389,6 +1419,58 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN
|
||||
return findings;
|
||||
}
|
||||
|
||||
function buildRecentAnalysisWindow({ samples, control, network, consoleEvents, errors, manifest }) {
|
||||
const latestSampleMs = latestTimestampMs(samples);
|
||||
const windowMs = 5 * 60 * 1000;
|
||||
const fromMs = Number.isFinite(latestSampleMs) ? latestSampleMs - windowMs : Number.NEGATIVE_INFINITY;
|
||||
const toMs = Number.isFinite(latestSampleMs) ? latestSampleMs : Number.POSITIVE_INFINITY;
|
||||
const inWindow = (item) => {
|
||||
const tsMs = Date.parse(item?.ts);
|
||||
return Number.isFinite(tsMs) && tsMs >= fromMs && tsMs <= toMs;
|
||||
};
|
||||
const windowSamples = samples.filter(inWindow);
|
||||
const windowControl = control.filter(inWindow);
|
||||
const windowNetwork = network.filter(inWindow);
|
||||
const windowConsole = consoleEvents.filter(inWindow);
|
||||
const windowErrors = errors.filter(inWindow);
|
||||
const sampleMetrics = buildSampleMetrics(windowSamples, control);
|
||||
const pageProvenance = buildPageProvenanceReport(windowSamples, windowControl, manifest);
|
||||
const pagePerformance = buildPagePerformanceReport(windowSamples, manifest);
|
||||
const promptNetwork = buildPromptNetworkReport(control, windowNetwork);
|
||||
const runtimeAlerts = buildRuntimeAlerts(windowSamples, control, windowNetwork, windowConsole, windowErrors);
|
||||
const findings = buildFindings(windowSamples, control, windowNetwork, windowErrors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance);
|
||||
return {
|
||||
summary: {
|
||||
name: "recent-5m",
|
||||
windowMs,
|
||||
fromAt: Number.isFinite(fromMs) ? new Date(fromMs).toISOString() : null,
|
||||
toAt: Number.isFinite(toMs) ? new Date(toMs).toISOString() : null,
|
||||
samples: windowSamples.length,
|
||||
control: windowControl.length,
|
||||
network: windowNetwork.length,
|
||||
console: windowConsole.length,
|
||||
errors: windowErrors.length,
|
||||
valuesRedacted: true
|
||||
},
|
||||
sampleMetrics,
|
||||
pageProvenance,
|
||||
pagePerformance,
|
||||
promptNetwork,
|
||||
runtimeAlerts,
|
||||
findings,
|
||||
valuesRedacted: true
|
||||
};
|
||||
}
|
||||
|
||||
function latestTimestampMs(items) {
|
||||
let latest = Number.NEGATIVE_INFINITY;
|
||||
for (const item of items || []) {
|
||||
const tsMs = Date.parse(item?.ts);
|
||||
if (Number.isFinite(tsMs) && tsMs > latest) latest = tsMs;
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
function buildPageProvenanceReport(samples, control, manifest) {
|
||||
const groups = new Map();
|
||||
for (const sample of samples) {
|
||||
|
||||
Reference in New Issue
Block a user