494 lines
30 KiB
TypeScript
494 lines
30 KiB
TypeScript
// SPEC: PJ2026-01040111 long-running Workbench observation.
|
|
// Responsibility: Source string for the offline web-probe observe analyzer.
|
|
import { nodeWebObserveAnalyzerTimingSource } from "./hwlab-node-web-observe-analyzer-timing-source";
|
|
import { nodeWebObserveAnalyzerPerformanceSource } from "./hwlab-node-web-observe-analyzer-performance-source";
|
|
import { nodeWebObserveAnalyzerIoSource } from "./hwlab-node-web-observe-analyzer-io-source";
|
|
import { nodeWebObserveAnalyzerProjectSource } from "./hwlab-node-web-observe-analyzer-project-source";
|
|
import { nodeWebObserveAnalyzerSessionSource } from "./hwlab-node-web-observe-analyzer-session-source";
|
|
import { nodeWebObserveAnalyzerApiDomLagSource } from "./hwlab-node-web-observe-analyzer-api-dom-lag-source";
|
|
import { nodeWebObserveAnalyzerWorkbenchTriadSource } from "./hwlab-node-web-observe-analyzer-workbench-triad-source";
|
|
import { nodeWebObserveAnalyzerFindingsSource } from "./hwlab-node-web-observe-analyzer-findings-source";
|
|
import { nodeWebObserveAnalyzerBrowserProcessSource } from "./hwlab-node-web-observe-analyzer-browser-process-source";
|
|
import { nodeWebObserveAnalyzerWindowPageSource } from "./hwlab-node-web-observe-analyzer-window-page-source";
|
|
import { nodeWebObserveAnalyzerRequestRuntimeSource } from "./hwlab-node-web-observe-analyzer-request-runtime-source";
|
|
import { nodeWebObserveAnalyzerSampleMetricsSource } from "./hwlab-node-web-observe-analyzer-sample-metrics-source";
|
|
|
|
export function nodeWebObserveAnalyzerSource(): string {
|
|
return String.raw`#!/usr/bin/env node
|
|
import { createHash } from "node:crypto";
|
|
import { createReadStream } from "node:fs";
|
|
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { createInterface } from "node:readline";
|
|
|
|
const stateDir = path.resolve(process.env.UNIDESK_WEB_OBSERVE_STATE_DIR || process.argv[2] || ".state/web-observe/manual");
|
|
const archivePrefix = safeArchivePrefix(process.env.UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX || "");
|
|
const analyzeTailSamples = (() => {
|
|
const raw = process.env.UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES;
|
|
if (raw === undefined || raw === null || String(raw).trim() === "") return 360;
|
|
const parsed = Number(raw);
|
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 360;
|
|
})();
|
|
const alertThresholds = parseAlertThresholds(process.env.UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON);
|
|
const browserFreezePolicy = parseBrowserFreezePolicy(process.env.UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON);
|
|
const projectManagementConfig = parseProjectManagementConfig(process.env.UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON);
|
|
const dataDir = archivePrefix ? path.join(stateDir, "archive") : stateDir;
|
|
const dataFile = (name) => path.join(dataDir, archivePrefix ? archivePrefix + "-" + name : name);
|
|
const analysisDir = path.join(stateDir, "analysis");
|
|
const reportJsonPath = path.join(analysisDir, "report.json");
|
|
const reportMdPath = path.join(analysisDir, "report.md");
|
|
const jsonlReadIssues = [];
|
|
const sourceSamples = await readJsonl(dataFile("samples.jsonl"), { compact: compactSampleForAnalysis, tail: analyzeTailSamples });
|
|
const relatedJsonlTailLimit = analyzeTailSamples > 0 ? Math.max(1200, analyzeTailSamples * 50) : 0;
|
|
const smallJsonlTailLimit = analyzeTailSamples > 0 ? Math.max(500, analyzeTailSamples * 8) : 0;
|
|
const sourceSampleWindow = sampleTimeWindow(sourceSamples, 60_000);
|
|
const sourceControlAll = await readJsonl(dataFile("control.jsonl"), { tail: relatedJsonlTailLimit });
|
|
const analysisFocus = analysisFocusFromControl(sourceControlAll);
|
|
const sourceControl = filterRowsByTimeWindow(sourceControlAll, sourceSampleWindow);
|
|
const samples = applyAnalysisFocus(sourceSamples, analysisFocus);
|
|
const sampleWindow = sampleTimeWindow(samples, 60_000);
|
|
const controlWindow = analysisControlWindow(sampleWindow, analysisFocus, 1000);
|
|
const control = applyAnalysisFocus(filterRowsByTimeWindow(sourceControlAll, controlWindow), analysisFocus, 1000);
|
|
const sourceNetworkAll = await readJsonl(dataFile("network.jsonl"), { tail: relatedJsonlTailLimit });
|
|
const network = applyAnalysisFocus(filterRowsByTimeWindow(sourceNetworkAll, sampleWindow), analysisFocus);
|
|
const promptNetworkRows = applyAnalysisFocus(filterRowsByTimeWindow(sourceNetworkAll, controlWindow), analysisFocus);
|
|
const consoleEvents = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("console.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow), analysisFocus);
|
|
const errors = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("errors.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow), analysisFocus);
|
|
const artifacts = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("artifacts.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow), analysisFocus);
|
|
const browserProcessRows = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("browser-process.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow), analysisFocus);
|
|
const performanceRows = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("performance-events.jsonl"), { tail: relatedJsonlTailLimit }), sampleWindow), analysisFocus);
|
|
const manifest = await readJson(path.join(stateDir, "manifest.json"));
|
|
const heartbeat = await readJson(path.join(stateDir, "heartbeat.json"));
|
|
const commandState = await readCommandState(stateDir);
|
|
|
|
await mkdir(analysisDir, { recursive: true, mode: 0o700 });
|
|
const transitions = buildTransitions(samples);
|
|
const sampleMetrics = buildSampleMetrics(samples, control);
|
|
const pageProvenance = buildPageProvenanceReport(samples, control, manifest);
|
|
const pagePerformance = buildPagePerformanceReport(samples, manifest);
|
|
const requestRate = buildRequestRateReport(network);
|
|
const promptNetwork = buildPromptNetworkReport(control, promptNetworkRows);
|
|
const runtimeAlerts = buildRuntimeAlerts(samples, control, network, consoleEvents, errors);
|
|
const apiDomLag = buildApiDomLagReport(samples, network);
|
|
const browserProcess = buildBrowserProcessReport(browserProcessRows);
|
|
const frontendPerformance = await buildFrontendPerformanceReport(performanceRows, artifacts, samples, network);
|
|
const projectManagement = buildProjectManagementReport(samples, control, network, pagePerformance, projectManagementConfig);
|
|
const runnerErrors = errors.slice(-8).map((item) => {
|
|
const details = item.error?.details && typeof item.error.details === "object" ? item.error.details : {};
|
|
const attempts = Array.isArray(item.error?.attempts) ? item.error.attempts : Array.isArray(details.attempts) ? details.attempts : [];
|
|
const lastAttempt = attempts.length > 0 ? attempts[attempts.length - 1] : null;
|
|
const readiness = lastAttempt?.readiness || lastAttempt?.readinessBeforeClick || details.readinessBeforeClick || details.readinessAfterWait || item.error?.navigationReadiness || null;
|
|
const readinessSnapshot = readiness?.snapshot || readiness;
|
|
return {
|
|
ts: item.ts ?? null,
|
|
type: item.type ?? null,
|
|
commandId: item.commandId ?? null,
|
|
sampleSeq: item.sampleSeq ?? null,
|
|
message: limitText(item.error?.message ?? item.message ?? "", 240),
|
|
retry: item.error?.auth?.lastRetryLabel ?? null,
|
|
retryExhausted: item.error?.auth?.retryExhausted === true,
|
|
lastError: limitText(item.error?.auth?.lastError ?? "", 160),
|
|
attemptCount: attempts.length,
|
|
lastFailureKind: lastAttempt?.failureKind ?? null,
|
|
lastReadinessReason: readiness?.reason ?? null,
|
|
lastReadiness: readinessSnapshot ? {
|
|
reason: readiness?.reason ?? readinessSnapshot.reason ?? null,
|
|
path: readinessSnapshot.path ?? null,
|
|
readyState: readinessSnapshot.readyState ?? null,
|
|
workbenchShellVisible: readinessSnapshot.workbenchShellVisible === true,
|
|
sessionCreatePresent: readinessSnapshot.sessionCreatePresent === true,
|
|
sessionCreateVisible: readinessSnapshot.sessionCreateVisible === true,
|
|
sessionRailPresent: readinessSnapshot.sessionRailPresent === true,
|
|
sessionRailCollapsed: readinessSnapshot.sessionRailCollapsed ?? null,
|
|
sessionCollapseTogglePresent: readinessSnapshot.sessionCollapseTogglePresent === true,
|
|
sessionCollapseToggleVisible: readinessSnapshot.sessionCollapseToggleVisible === true,
|
|
sessionCollapseToggleExpanded: readinessSnapshot.sessionCollapseToggleExpanded ?? null,
|
|
commandInputPresent: readinessSnapshot.commandInputPresent === true,
|
|
activeTabPresent: readinessSnapshot.activeTabPresent === true,
|
|
warningPresent: readinessSnapshot.warningPresent === true,
|
|
loginVisible: readinessSnapshot.loginVisible === true,
|
|
bodyTextHash: readinessSnapshot.bodyTextHash ?? null,
|
|
valuesRedacted: true
|
|
} : null,
|
|
navigationAttempts: attempts.slice(-5).map((attempt) => ({
|
|
attempt: attempt?.attempt ?? null,
|
|
ok: attempt?.ok === true,
|
|
failureKind: attempt?.failureKind ?? null,
|
|
message: limitText(attempt?.message ?? "", 160),
|
|
readinessReason: attempt?.readiness?.reason ?? null,
|
|
valuesRedacted: true
|
|
})),
|
|
};
|
|
});
|
|
const commandFailures = summarizeCommandFailures(control);
|
|
const toolFindings = buildToolFindings({ manifest, heartbeat, commandState });
|
|
const findings = [...toolFindings, ...buildProjectManagementFindings(projectManagement), ...buildFindings(samples, control, network, errors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, requestRate, pageProvenance, commandFailures, manifest, apiDomLag, browserProcess, frontendPerformance)];
|
|
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 = await buildRecentAnalysisWindow({ samples, control, network, consoleEvents, errors, artifacts, browserProcessRows, performanceRows, 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,
|
|
command: "web-probe-observe analyze",
|
|
generatedAt: new Date().toISOString(),
|
|
stateDir,
|
|
jsonlScope: { mode: archivePrefix ? "archive" : "current", archivePrefix: archivePrefix || null, dataDir, analyzeTailSamples, sourceSampleCount: sourceSamples.length, effectiveSampleCount: samples.length, sourceControlCount: sourceControlAll.length, sampleWindow, focus: analysisFocus, valuesRedacted: true },
|
|
alertThresholds,
|
|
browserFreezePolicy,
|
|
manifest: compactManifest(manifest),
|
|
heartbeat: compactHeartbeat(heartbeat),
|
|
counts: { samples: samples.length, control: control.length, network: network.length, console: consoleEvents.length, errors: errors.length, artifacts: artifacts.length, browserProcess: browserProcessRows.length, performance: performanceRows.length },
|
|
commandTimeline,
|
|
transitions,
|
|
sampleMetrics,
|
|
pageProvenance,
|
|
pagePerformance,
|
|
requestRate,
|
|
projectManagement,
|
|
promptNetwork,
|
|
runtimeAlerts,
|
|
apiDomLag,
|
|
browserProcess,
|
|
frontendPerformance,
|
|
runnerErrors,
|
|
commandFailures,
|
|
commandState,
|
|
toolFindings,
|
|
findings,
|
|
windows: { recent: recentWindow },
|
|
readIssues: jsonlReadIssues,
|
|
artifactSummary: await artifactSummary(artifacts),
|
|
safety: { offlineOnly: true, browserDriven: false, apiFetch: false, valuesRedacted: true },
|
|
};
|
|
await writeFile(reportJsonPath, JSON.stringify(report, null, 2) + "\n", { mode: 0o600 });
|
|
await writeFile(reportMdPath, renderMarkdown(report), { mode: 0o600 });
|
|
const [jsonMeta, mdMeta] = await Promise.all([fileMeta(reportJsonPath), fileMeta(reportMdPath)]);
|
|
console.log(JSON.stringify({
|
|
ok: true,
|
|
command: "web-probe-observe analyze",
|
|
stateDir,
|
|
jsonlScope: report.jsonlScope,
|
|
reportJsonPath,
|
|
reportJsonSha256: jsonMeta.sha256,
|
|
reportMdPath,
|
|
reportMdSha256: mdMeta.sha256,
|
|
counts: report.counts,
|
|
archiveSummary: {
|
|
sampleMetrics: sampleMetrics.summary,
|
|
pagePerformance: pagePerformance.summary,
|
|
requestRate: requestRate.summary,
|
|
projectManagement: projectManagement.summary,
|
|
runtimeAlerts: runtimeAlerts.summary,
|
|
apiDomLag: apiDomLag.summary,
|
|
browserProcess: browserProcess.summary,
|
|
frontendPerformance: frontendPerformance.summary,
|
|
findingCount: findings.length,
|
|
redFindingCount: findings.filter((item) => item.severity === "red").length,
|
|
redFindings: prioritizeFindings(findings.filter((item) => item.severity === "red")).slice(0, 12).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) })),
|
|
},
|
|
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: {
|
|
...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, loadingSamples: item.loadingSamples, maxLoadingCount: item.maxLoadingCount, loadingOwnerCount: item.loadingOwnerCount, diagnosticSamples: item.diagnosticSamples, terminalSamples: item.terminalSamples, finalTextSamples: item.finalTextSamples, turnTimingTotalElapsedZeroResetCount: item.turnTimingTotalElapsedZeroResetCount, turnTimingTotalElapsedForwardJumpCount: item.turnTimingTotalElapsedForwardJumpCount, turnTimingTotalElapsedForwardJumpMaxSeconds: item.turnTimingTotalElapsedForwardJumpMaxSeconds, turnTimingRecentUpdateJumpCount: item.turnTimingRecentUpdateJumpCount, turnTimingRecentUpdateMaxIncreaseSeconds: item.turnTimingRecentUpdateMaxIncreaseSeconds })),
|
|
turnColumns: recentWindow.sampleMetrics.turnColumns.slice(-12).map((item) => ({ label: item.label, source: item.source, pageRole: item.pageRole ?? null, pageId: item.pageId ?? null, pageEpoch: item.pageEpoch ?? null, promptIndex: item.promptIndex, lastPromptIndex: item.lastPromptIndex, firstSeq: item.firstSeq, lastSeq: item.lastSeq, traceId: item.traceId, messageId: item.messageId })),
|
|
loading: compactLoadingMetricsForOutput(recentWindow.sampleMetrics.loading),
|
|
sessionRailTitles: compactSessionRailTitleMetricsForOutput(recentWindow.sampleMetrics.sessionRailTitles),
|
|
workbenchTurnStateTriad: compactWorkbenchTurnStateTriadForOutput(recentWindow.sampleMetrics.workbenchTurnStateTriad),
|
|
},
|
|
pageProvenance: recentWindow.pageProvenance.summary,
|
|
pagePerformance: recentWindow.pagePerformance.summary,
|
|
requestRate: recentWindow.requestRate.summary,
|
|
projectManagement: compactProjectManagementForOutput(projectManagement),
|
|
promptNetwork: recentWindow.promptNetwork.summary,
|
|
runtimeAlerts: recentWindow.runtimeAlerts.summary,
|
|
apiDomLag: compactApiDomLagForOutput(apiDomLag),
|
|
browserProcess: recentWindow.browserProcess.summary,
|
|
frontendPerformance: recentWindow.frontendPerformance.summary,
|
|
frontendPerformanceHotspots: {
|
|
scripts: recentWindow.frontendPerformance.scriptHotspots.slice(0, 8),
|
|
profileFunctions: recentWindow.frontendPerformance.profileHotspots.slice(0, 8),
|
|
profileStacks: recentWindow.frontendPerformance.profileStacks.slice(0, 5),
|
|
valuesRedacted: true,
|
|
},
|
|
runnerErrors,
|
|
commandFailures: commandFailures.slice(-8),
|
|
commandState,
|
|
toolFindings: toolFindings.slice(0, 8).map((item) => ({ kind: item.id, severity: item.severity, count: item.count ?? null, summary: String(item.summary ?? "").slice(0, 180) })),
|
|
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,
|
|
lastAt: item.lastAt,
|
|
promptIndexes: Array.isArray(item.promptIndexes) ? item.promptIndexes.slice(0, 6) : [],
|
|
traceIds: Array.isArray(item.traceIds) ? item.traceIds.slice(0, 4) : [],
|
|
text: String(item.preview ?? item.normalizedPreview ?? item.text ?? "").slice(0, 160),
|
|
})),
|
|
domDiagnosticSamples: recentWindow.runtimeAlerts.domDiagnostics.slice(-8).map((item) => ({
|
|
seq: item.seq ?? null,
|
|
ts: item.ts ?? null,
|
|
promptIndex: item.promptIndex ?? null,
|
|
source: item.source ?? null,
|
|
diagnosticCode: item.diagnosticCode ?? null,
|
|
traceId: item.traceId ?? null,
|
|
httpStatus: item.httpStatus ?? null,
|
|
idleSeconds: item.idleSeconds ?? null,
|
|
waitingFor: item.waitingFor ?? null,
|
|
lastEventLabel: item.lastEventLabel ?? null,
|
|
text: String(item.preview ?? item.text ?? "").slice(0, 180),
|
|
})),
|
|
consoleAlertGroups: recentWindow.runtimeAlerts.consoleAlertsByPath.slice(0, 8).map((item) => ({
|
|
type: item.type ?? 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) : [],
|
|
traceIds: Array.isArray(item.traceIds) ? item.traceIds.slice(0, 4) : [],
|
|
})),
|
|
consoleAlertSamples: recentWindow.runtimeAlerts.consoleAlerts.slice(-8).map((item) => ({
|
|
ts: item.ts ?? null,
|
|
promptIndex: item.promptIndex ?? null,
|
|
type: item.type ?? null,
|
|
status: item.status ?? null,
|
|
path: item.urlPath ?? null,
|
|
traceId: item.traceId ?? null,
|
|
text: String(item.preview ?? item.text ?? "").slice(0, 180),
|
|
})),
|
|
webPerformanceRuntimeDiagnostics: {
|
|
summary: {
|
|
payloadRequestCount: recentWindow.runtimeAlerts.summary.webPerformancePayloadRequestCount ?? 0,
|
|
payloadParsedCount: recentWindow.runtimeAlerts.summary.webPerformancePayloadParsedCount ?? 0,
|
|
payloadParseIssueCount: recentWindow.runtimeAlerts.summary.webPerformancePayloadParseIssueCount ?? 0,
|
|
runtimeDiagnosticCount: recentWindow.runtimeAlerts.summary.webPerformanceRuntimeDiagnosticCount ?? 0,
|
|
runtimeDiagnosticGroupCount: recentWindow.runtimeAlerts.summary.webPerformanceRuntimeDiagnosticGroupCount ?? 0,
|
|
valuesRedacted: true,
|
|
},
|
|
groups: Array.isArray(recentWindow.runtimeAlerts.webPerformanceRuntimeDiagnosticsByCode) ? recentWindow.runtimeAlerts.webPerformanceRuntimeDiagnosticsByCode.slice(0, 8) : [],
|
|
valuesRedacted: true,
|
|
},
|
|
turnTimingRecentUpdateJumps: recentWindow.sampleMetrics.turnTimingRecentUpdateSawtoothJumps.slice(0, 8).map((item) => ({
|
|
columnLabel: item.columnLabel ?? item.columnId ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
pageId: item.pageId ?? null,
|
|
pageEpoch: item.pageEpoch ?? null,
|
|
promptIndex: item.promptIndex ?? null,
|
|
traceId: item.traceId ?? null,
|
|
fromSeq: item.fromSeq ?? null,
|
|
toSeq: item.toSeq ?? null,
|
|
fromTs: item.fromTs ?? null,
|
|
toTs: item.toTs ?? null,
|
|
fromValue: item.fromValue ?? null,
|
|
toValue: item.toValue ?? null,
|
|
delta: item.delta ?? null,
|
|
sampleDeltaSeconds: item.sampleDeltaSeconds ?? null,
|
|
allowedIncreaseSeconds: item.allowedIncreaseSeconds ?? null,
|
|
excessiveIncreaseSeconds: item.excessiveIncreaseSeconds ?? null,
|
|
timingSourceOfTruth: item.timingSourceOfTruth ?? null,
|
|
timingStatus: item.timingStatus ?? null,
|
|
})),
|
|
turnTimingElapsedZeroResets: recentWindow.sampleMetrics.turnTimingElapsedZeroResets.slice(0, 8).map((item) => ({
|
|
columnLabel: item.columnLabel ?? item.columnId ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
pageId: item.pageId ?? null,
|
|
pageEpoch: item.pageEpoch ?? null,
|
|
promptIndex: item.promptIndex ?? null,
|
|
traceId: item.traceId ?? null,
|
|
fromSeq: item.fromSeq ?? null,
|
|
toSeq: item.toSeq ?? null,
|
|
fromTs: item.fromTs ?? null,
|
|
toTs: item.toTs ?? null,
|
|
fromValue: item.fromValue ?? null,
|
|
toValue: item.toValue ?? null,
|
|
delta: item.delta ?? null,
|
|
anomaly: item.anomaly ?? null,
|
|
timingSourceOfTruth: item.timingSourceOfTruth ?? null,
|
|
timingStatus: item.timingStatus ?? null,
|
|
})),
|
|
turnTimingTotalElapsedForwardJumps: recentWindow.sampleMetrics.turnTimingTotalElapsedForwardJumps.slice(0, 8).map((item) => ({
|
|
columnLabel: item.columnLabel ?? item.columnId ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
pageId: item.pageId ?? null,
|
|
pageEpoch: item.pageEpoch ?? null,
|
|
promptIndex: item.promptIndex ?? null,
|
|
traceId: item.traceId ?? null,
|
|
fromSeq: item.fromSeq ?? null,
|
|
toSeq: item.toSeq ?? null,
|
|
fromTs: item.fromTs ?? null,
|
|
toTs: item.toTs ?? null,
|
|
fromValue: item.fromValue ?? null,
|
|
toValue: item.toValue ?? null,
|
|
delta: item.delta ?? null,
|
|
sampleDeltaSeconds: item.sampleDeltaSeconds ?? null,
|
|
allowedIncreaseSeconds: item.allowedIncreaseSeconds ?? null,
|
|
excessiveIncreaseSeconds: item.excessiveIncreaseSeconds ?? null,
|
|
anomaly: item.anomaly ?? null,
|
|
timingSourceOfTruth: item.timingSourceOfTruth ?? null,
|
|
timingStatus: item.timingStatus ?? null,
|
|
})),
|
|
requestRateCurve: compactRequestRateForOutput(recentWindow.requestRate),
|
|
requestRatePeaks: recentWindow.requestRate.peaks.slice(0, 8),
|
|
pagePerformanceSlowApi: recentWindow.pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream !== true && Number(item.overBudgetCount ?? item.overFiveSecondCount ?? 0) > 0).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, p95Ms: item.p95Ms, maxMs: item.maxMs, overBudgetCount: item.overBudgetCount, budgetMs: item.budgetMs, overFiveSecondCount: item.overFiveSecondCount, slowSamples: item.slowSamples })),
|
|
archivePagePerformanceSlowApi: pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream !== true && Number(item.overBudgetCount ?? item.overFiveSecondCount ?? 0) > 0).slice(0, 8).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, p95Ms: item.p95Ms, maxMs: item.maxMs, overBudgetCount: item.overBudgetCount, budgetMs: item.budgetMs, overFiveSecondCount: item.overFiveSecondCount, slowSamples: item.slowSamples })),
|
|
pagePerformancePartialApi: recentWindow.pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream !== true && Number(item.partialOverBudgetCount ?? item.partialOverFiveSecondCount ?? 0) > 0).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, completeTimingSampleCount: item.completeTimingSampleCount, partialTimingSampleCount: item.partialTimingSampleCount, partialOverBudgetCount: item.partialOverBudgetCount, budgetMs: item.partialBudgetMs, partialOverFiveSecondCount: item.partialOverFiveSecondCount, partialSamples: item.partialSamples })),
|
|
pagePerformanceSseStreams: recentWindow.pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream === true).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, streamOpenSampleCount: item.streamOpenSampleCount, streamOpenP95Ms: item.streamOpenP95Ms, streamOpenMaxMs: item.streamOpenMaxMs, streamOpenOverBudgetCount: item.streamOpenOverBudgetCount, streamOpenBudgetMs: item.streamOpenBudgetMs, streamOpenOverFiveSecondCount: item.streamOpenOverFiveSecondCount, streamLifetimeOverFiveSecondCount: item.streamLifetimeOverFiveSecondCount, slowSamples: item.slowSamples })),
|
|
findings: prioritizeFindings(recentWindow.findings).slice(0, 8).map((item) => ({ kind: item.id ?? item.kind ?? item.code, severity: item.severity, count: item.count ?? item.sampleCount ?? null, timingSourceOfTruth: item.timingSourceOfTruth ?? null, timingStatus: item.timingStatus ?? null, summary: String(item.summary ?? item.message ?? "").slice(0, 180) })),
|
|
traceOrderAnomalies: (recentWindow.sampleMetrics?.traceOrder?.orderAnomalies || []).slice(0, 8).map((item) => ({
|
|
sampleSeq: item.sampleSeq ?? null,
|
|
sampleIndex: item.sampleIndex ?? null,
|
|
timestamp: item.timestamp ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
traceId: item.traceId ?? null,
|
|
reasons: item.reasons ?? [],
|
|
previousRowIndex: item.previousRowIndex ?? null,
|
|
currentRowIndex: item.currentRowIndex ?? null,
|
|
previousTotalSeconds: item.previousTotalSeconds ?? null,
|
|
currentTotalSeconds: item.currentTotalSeconds ?? null,
|
|
previousProjectedSeq: item.previousProjectedSeq ?? null,
|
|
currentProjectedSeq: item.currentProjectedSeq ?? null,
|
|
previousSourceSeq: item.previousSourceSeq ?? null,
|
|
currentSourceSeq: item.currentSourceSeq ?? null,
|
|
previousEventSeq: item.previousEventSeq ?? null,
|
|
currentEventSeq: item.currentEventSeq ?? null,
|
|
previousPreview: String(item.previousPreview || "").slice(0, 120),
|
|
currentPreview: String(item.currentPreview || "").slice(0, 120),
|
|
})),
|
|
traceCompletionNotLast: (recentWindow.sampleMetrics?.traceOrder?.completionNotLast || []).slice(0, 8).map((item) => ({
|
|
sampleSeq: item.sampleSeq ?? null,
|
|
sampleIndex: item.sampleIndex ?? null,
|
|
timestamp: item.timestamp ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
traceId: item.traceId ?? null,
|
|
completionRowIndex: item.completionRowIndex ?? null,
|
|
laterRowIndex: item.laterRowIndex ?? null,
|
|
completionTotalSeconds: item.completionTotalSeconds ?? null,
|
|
laterTotalSeconds: item.laterTotalSeconds ?? null,
|
|
completionProjectedSeq: item.completionProjectedSeq ?? null,
|
|
laterProjectedSeq: item.laterProjectedSeq ?? null,
|
|
completionPreview: String(item.completionPreview || "").slice(0, 120),
|
|
laterPreview: String(item.laterPreview || "").slice(0, 120),
|
|
})),
|
|
roundCompletionElapsedMismatches: (recentWindow.sampleMetrics?.codeAgentCardTiming?.roundCompletion?.elapsedMismatches || []).slice(0, 8).map((item) => ({
|
|
seq: item.seq ?? null,
|
|
ts: item.ts ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
promptIndex: item.promptIndex ?? null,
|
|
traceId: item.traceId ?? null,
|
|
completionElapsedSeconds: item.completionElapsedSeconds ?? null,
|
|
cardTotalElapsedSeconds: item.cardTotalElapsedSeconds ?? null,
|
|
deltaSeconds: item.deltaSeconds ?? null,
|
|
toleranceSeconds: item.toleranceSeconds ?? null,
|
|
timingSourceOfTruth: item.timingSourceOfTruth ?? null,
|
|
timingStatus: item.timingStatus ?? null,
|
|
})),
|
|
codeAgentCardDurationUnderreported: (recentWindow.sampleMetrics?.codeAgentCardTiming?.durationUnderreported || []).slice(0, 8).map((item) => ({
|
|
sampleIndex: item.sampleIndex ?? null,
|
|
timestamp: item.timestamp ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
traceId: item.traceId ?? null,
|
|
cardTotalElapsedSeconds: item.cardTotalElapsedSeconds ?? null,
|
|
expectedElapsedSeconds: item.expectedElapsedSeconds ?? null,
|
|
deltaSeconds: item.deltaSeconds ?? null,
|
|
evidenceKind: item.evidenceKind ?? null,
|
|
timingSourceOfTruth: item.timingSourceOfTruth ?? null,
|
|
timingStatus: item.timingStatus ?? null,
|
|
evidencePreview: String(item.evidencePreview || "").slice(0, 120),
|
|
})),
|
|
codeAgentCardDurationMismatches: (recentWindow.sampleMetrics?.codeAgentCardTiming?.durationMismatches || []).slice(0, 8).map((item) => ({
|
|
sampleIndex: item.sampleIndex ?? null,
|
|
timestamp: item.timestamp ?? null,
|
|
pageRole: item.pageRole ?? null,
|
|
traceId: item.traceId ?? null,
|
|
direction: item.direction ?? null,
|
|
cardTotalElapsedSeconds: item.cardTotalElapsedSeconds ?? null,
|
|
expectedElapsedSeconds: item.expectedElapsedSeconds ?? null,
|
|
signedDeltaSeconds: item.signedDeltaSeconds ?? null,
|
|
deltaSeconds: item.deltaSeconds ?? null,
|
|
evidenceKind: item.evidenceKind ?? null,
|
|
exactEvidence: item.exactEvidence === true,
|
|
timingSourceOfTruth: item.timingSourceOfTruth ?? null,
|
|
timingStatus: item.timingStatus ?? null,
|
|
evidencePreview: String(item.evidencePreview || "").slice(0, 120),
|
|
})),
|
|
valuesRedacted: true,
|
|
}));
|
|
|
|
${nodeWebObserveAnalyzerIoSource()}
|
|
|
|
${nodeWebObserveAnalyzerProjectSource()}
|
|
|
|
${nodeWebObserveAnalyzerSessionSource()}
|
|
|
|
${nodeWebObserveAnalyzerApiDomLagSource()}
|
|
|
|
${nodeWebObserveAnalyzerWorkbenchTriadSource()}
|
|
|
|
${nodeWebObserveAnalyzerFindingsSource()}
|
|
|
|
${nodeWebObserveAnalyzerBrowserProcessSource()}
|
|
|
|
${nodeWebObserveAnalyzerWindowPageSource()}
|
|
|
|
${nodeWebObserveAnalyzerRequestRuntimeSource()}
|
|
|
|
${nodeWebObserveAnalyzerPerformanceSource()}
|
|
|
|
function prioritizeFindings(findings) {
|
|
const items = Array.isArray(findings) ? findings : [];
|
|
const severityRank = (severity) => {
|
|
const value = String(severity || "").toLowerCase();
|
|
if (value === "red") return 0;
|
|
if (value === "amber" || value === "warning") return 1;
|
|
if (value === "info") return 3;
|
|
return 2;
|
|
};
|
|
const kindRank = (item) => {
|
|
const id = String(item?.id ?? item?.kind ?? item?.code ?? "");
|
|
if (id.startsWith("project-management-") || id.startsWith("mdtodo-") || id === "workbench-launch-button-unavailable") return 0;
|
|
if (id.startsWith("frontend-long") || id.startsWith("frontend-event-loop-gap") || id === "frontend-cpu-profile-hotspots") return 0;
|
|
if (id === "page-performance-slow-same-origin-api") return 0;
|
|
if (id === "session-rail-title-fallback-majority") return 0.5;
|
|
if (id === "workbench-turn-state-triad-inconsistent") return 0.55;
|
|
if (id.startsWith("workbench-terminal-")) return 0.6;
|
|
if (id.startsWith("code-agent-card-")) return 0.8;
|
|
if (id.startsWith("round-completion-")) return 0.9;
|
|
if (id.startsWith("turn-timing-total-elapsed")) return 1;
|
|
if (id.startsWith("turn-timing-terminal-elapsed")) return 1.1;
|
|
if (id.startsWith("turn-timing-recent-update")) return 2;
|
|
if (id.includes("runtime-execution") || id.includes("prompt-chat-submit-failed")) return 3;
|
|
return 10;
|
|
};
|
|
return items.slice().sort((left, right) => {
|
|
const kindDelta = kindRank(left) - kindRank(right);
|
|
if (kindDelta !== 0) return kindDelta;
|
|
return severityRank(left?.severity ?? left?.level) - severityRank(right?.severity ?? right?.level);
|
|
});
|
|
}
|
|
|
|
${nodeWebObserveAnalyzerSampleMetricsSource()}
|
|
|
|
|
|
` + nodeWebObserveAnalyzerTimingSource();
|
|
}
|