From dc50f0c7158625a3cf69a846ce09c5db7a952127 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 2 Jul 2026 17:39:41 +0000 Subject: [PATCH] fix(web-probe): restore analyzer runtime diagnostics visibility --- ...observe-analyzer-request-runtime-source.ts | 8 +-- scripts/src/hwlab-node-web-observe-render.ts | 13 +++++ scripts/src/hwlab-node/web-observe-render.ts | 17 ++++++ scripts/src/hwlab-node/web-observe-scripts.ts | 12 +++-- ...eb-observe-web-performance-payload.test.ts | 52 +++++++++++++++++++ scripts/src/hwlab-node/web-probe-observe.ts | 13 ++++- 6 files changed, 105 insertions(+), 10 deletions(-) diff --git a/scripts/src/hwlab-node-web-observe-analyzer-request-runtime-source.ts b/scripts/src/hwlab-node-web-observe-analyzer-request-runtime-source.ts index 6ec3e6de..62473448 100644 --- a/scripts/src/hwlab-node-web-observe-analyzer-request-runtime-source.ts +++ b/scripts/src/hwlab-node-web-observe-analyzer-request-runtime-source.ts @@ -807,9 +807,9 @@ function groupWebPerformanceRuntimeDiagnostics(events) { group.deliveredCount += Number(item.deliveredCount || 0); group.chunkCount += Number(item.chunkCount || 0); group.droppedCount += Number(item.droppedCount || 0); - group.maxFlushDurationMs = maxNumber(group.maxFlushDurationMs, item.flushDurationMs); - group.maxItemsPerChunk = maxNumber(group.maxItemsPerChunk, item.maxItemsPerChunk); - group.maxChunkMs = maxNumber(group.maxChunkMs, item.maxChunkMs); + group.maxFlushDurationMs = maxRuntimeNumber(group.maxFlushDurationMs, item.flushDurationMs); + group.maxItemsPerChunk = maxRuntimeNumber(group.maxItemsPerChunk, item.maxItemsPerChunk); + group.maxChunkMs = maxRuntimeNumber(group.maxChunkMs, item.maxChunkMs); if (Number.isFinite(Number(item.replacedByKey))) group.replacedByKeyCount += Number(item.replacedByKey); else if (item.replacedByKey === true || item.replacedByKeyHash) group.replacedByKeyCount += 1; if (group.examples.length < 6) group.examples.push({ @@ -851,7 +851,7 @@ function groupWebPerformanceRuntimeDiagnostics(events) { })).sort((left, right) => right.count - left.count || String(left.firstAt || "").localeCompare(String(right.firstAt || ""))); } -function maxNumber(current, candidate) { +function maxRuntimeNumber(current, candidate) { const value = Number(candidate); if (!Number.isFinite(value)) return current ?? null; const existing = Number(current); diff --git a/scripts/src/hwlab-node-web-observe-render.ts b/scripts/src/hwlab-node-web-observe-render.ts index 5329aa73..f9c55332 100644 --- a/scripts/src/hwlab-node-web-observe-render.ts +++ b/scripts/src/hwlab-node-web-observe-render.ts @@ -348,6 +348,19 @@ function renderWebObserveCollectTable(value: Record): string { } if (collect.mode === "file") { + const requestedRelative = file.requestedRelative ?? collect.requestedFile ?? value.requestedFile; + const resolvedRelative = file.resolvedRelative ?? collect.resolvedFile ?? file.relative; + if (webObserveText(requestedRelative) !== webObserveText(resolvedRelative) || file.fallbackReason || collect.fallbackReason) { + lines.push( + "File resolution:", + webObserveTable(["REQUESTED", "RESOLVED", "FALLBACK_REASON"], [[ + webObserveShort(webObserveText(requestedRelative), 64), + webObserveShort(webObserveText(resolvedRelative), 64), + webObserveShort(webObserveText(file.fallbackReason ?? collect.fallbackReason), 40), + ]]), + "", + ); + } lines.push( "File:", webObserveTable(["RELATIVE", "BYTES", "TRUNC", "SHA256"], [[ diff --git a/scripts/src/hwlab-node/web-observe-render.ts b/scripts/src/hwlab-node/web-observe-render.ts index 3433e85b..525e30da 100644 --- a/scripts/src/hwlab-node/web-observe-render.ts +++ b/scripts/src/hwlab-node/web-observe-render.ts @@ -114,6 +114,9 @@ export function renderWebObserveAnalyzeTable(value: Record): st const archiveMetrics = record(archiveSummary?.sampleMetrics); const archivePagePerformance = record(archiveSummary?.pagePerformance); const runtimeAlerts = record(analysis?.runtimeAlerts); + const webPerformanceRuntime = record(analysis?.webPerformanceRuntimeDiagnostics); + const webPerformanceRuntimeSummary = record(webPerformanceRuntime?.summary); + const webPerformanceRuntimeGroups = webObserveArray(webPerformanceRuntime?.groups).map(record).filter((item): item is Record => item !== null).slice(0, 4); const pagePerformance = record(analysis?.pagePerformance); const pagePerformanceSummary = record(pagePerformance?.summary); const nonEmptyRecord = (item: unknown): Record | null => { @@ -456,6 +459,20 @@ export function renderWebObserveAnalyzeTable(value: Record): st webObserveShort(webObserveText(item.sourceFunctionName ?? item.functionName ?? item.invoker), 42), webObserveShort(webObserveText(item.sourceURL ?? item.url), 64), ]) : [["-", "-", "-", "-"]]), + webObserveTable(["PAYLOADS", "PARSED", "PARSE_ISSUES", "RUNTIME_EVENTS", "GROUPS"], [[ + webObserveText(webPerformanceRuntimeSummary?.payloadRequestCount), + webObserveText(webPerformanceRuntimeSummary?.payloadParsedCount), + webObserveText(webPerformanceRuntimeSummary?.payloadParseIssueCount), + webObserveText(webPerformanceRuntimeSummary?.runtimeDiagnosticCount), + webObserveText(webPerformanceRuntimeSummary?.runtimeDiagnosticGroupCount), + ]]), + webObserveTable(["DIAGNOSTIC", "REASON", "MODULE", "EVENTS", "FLUSH_MAX"], webPerformanceRuntimeGroups.length > 0 ? webPerformanceRuntimeGroups.map((item) => [ + webObserveShort(webObserveText(item.diagnosticCode ?? item.eventType), 36), + webObserveShort(webObserveText(item.reason), 28), + webObserveShort(webObserveText(item.module), 32), + webObserveText(item.eventCount ?? item.count), + webObserveText(item.maxFlushDurationMs), + ]) : [["-", "-", "-", "-", "-"]]), "", "Next:", ` bun scripts/cli.ts web-probe observe collect ${id} --view turn-summary`, diff --git a/scripts/src/hwlab-node/web-observe-scripts.ts b/scripts/src/hwlab-node/web-observe-scripts.ts index f604e4e0..9400d99c 100644 --- a/scripts/src/hwlab-node/web-observe-scripts.ts +++ b/scripts/src/hwlab-node/web-observe-scripts.ts @@ -329,14 +329,18 @@ const slimJsonl=(value)=>{ }; }; if(selected){ + const requested=selected; if(!safeRel(selected)){console.log(JSON.stringify({ok:false,command:'web-probe-observe collect',stateDir:dir,mode:'file',reason:'unsafe-file',file:selected,valuesRedacted:true},null,2)); process.exit(2);} let file=path.join(dir,selected); let relative=path.relative(dir,file); if(relative.startsWith('..')||path.isAbsolute(relative)){console.log(JSON.stringify({ok:false,command:'web-probe-observe collect',stateDir:dir,mode:'file',reason:'file-outside-state-dir',file:selected,valuesRedacted:true},null,2)); process.exit(2);} + let fallbackReason=null; + let fallbackCandidates=[]; if(!fs.existsSync(file)||!fs.statSync(file).isFile()){ - const fallback=collectFallbackCandidates(selected).find((item)=>{const candidate=path.join(dir,item); return fs.existsSync(candidate)&&fs.statSync(candidate).isFile();}); - if(fallback){selected=fallback; file=path.join(dir,selected); relative=path.relative(dir,file);} + fallbackCandidates=collectFallbackCandidates(selected); + const fallback=fallbackCandidates.find((item)=>{const candidate=path.join(dir,item); return fs.existsSync(candidate)&&fs.statSync(candidate).isFile();}); + if(fallback){fallbackReason='requested-file-missing'; selected=fallback; file=path.join(dir,selected); relative=path.relative(dir,file);} } - if(!fs.existsSync(file)||!fs.statSync(file).isFile()){console.log(JSON.stringify({ok:false,command:'web-probe-observe collect',stateDir:dir,mode:'file',reason:'file-not-found',file:selected,fallbackCandidates:collectFallbackCandidates(selected).slice(0,8),valuesRedacted:true},null,2)); process.exit(1);} + if(!fs.existsSync(file)||!fs.statSync(file).isFile()){console.log(JSON.stringify({ok:false,command:'web-probe-observe collect',stateDir:dir,mode:'file',reason:'file-not-found',requestedFile:requested,resolvedFile:selected,file:selected,fallbackCandidates:(fallbackCandidates.length>0?fallbackCandidates:collectFallbackCandidates(selected)).slice(0,8),valuesRedacted:true},null,2)); process.exit(1);} const st=fs.statSync(file); const raw=fs.readFileSync(file); const truncated=raw.length>maxReadBytes; const isJsonl=selected.endsWith('.jsonl'); const isJson=selected.endsWith('.json'); const tailReadBytes=isJsonl?maxJsonlTailBytes:maxReadBytes; const text=(isJsonl?raw.slice(Math.max(0,raw.length-tailReadBytes)):raw.slice(0,Math.min(raw.length,maxReadBytes))).toString('utf8'); @@ -347,7 +351,7 @@ if(selected){ let jsonSummary=null; let jsonContent=null; if(isJson){try{const parsed=JSON.parse(raw.toString('utf8')); const inferredFindingFilter=findingFilter||(grepText&&Array.isArray(parsed&&parsed.findings)&&parsed.findings.some((item)=>item&&typeof item==='object'&&String(item.id??item.kind??item.code??'')===grepText)?grepText:null); jsonContent=raw.length<=4096?slimJsonValue(parsed):null; jsonSummary={topLevelKeys:parsed&&typeof parsed==='object'&&!Array.isArray(parsed)?Object.keys(parsed).slice(0,24):[],counts:slimObject(parsed&&parsed.counts,16),sampleMetrics:slimObject(parsed&&parsed.sampleMetrics,16),runtimeAlerts:slimObject(parsed&&parsed.runtimeAlerts,16),pagePerformance:slimObject(parsed&&parsed.pagePerformance,16),runnerErrors:Array.isArray(parsed&&parsed.runnerErrors)?parsed.runnerErrors.slice(-4).map(slimRunnerError).filter(Boolean):[],findings:Array.isArray(parsed&&parsed.findings)?parsed.findings.slice(0,6).map(slimFinding).filter(Boolean):[],findingDetail:slimFindingDetail(parsed,inferredFindingFilter)}}catch(error){jsonSummary={parseError:String(error&&error.message||error).slice(0,160)}}} const jsonlTail=isJsonl&&!grep?lines.slice(-8).map((line,index)=>{try{return slimJsonl(JSON.parse(line))}catch(error){return {parseError:true,index,lineTail:line.slice(-240),error:String(error&&error.message||error).slice(0,160)}}}):null; - console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',stateDir:dir,mode:'file',file:{path:file,relative,byteCount:st.size,sha256:shaFile(file),truncated,content:isJsonl||isJson||jsonSummary||grep?undefined:text.slice(0,maxTextPreviewBytes),contentTruncated:!isJsonl&&!isJson&&!jsonSummary&&!grep&&text.length>maxTextPreviewBytes,jsonlTail,jsonSummary,jsonContent,grep,lineCount:lines.length},valuesRedacted:true})); + console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',stateDir:dir,mode:'file',requestedFile:requested,resolvedFile:selected,fallbackReason,file:{path:file,relative,requestedRelative:requested,resolvedRelative:selected,fallbackReason,byteCount:st.size,sha256:shaFile(file),truncated,content:isJsonl||isJson||jsonSummary||grep?undefined:text.slice(0,maxTextPreviewBytes),contentTruncated:!isJsonl&&!isJson&&!jsonSummary&&!grep&&text.length>maxTextPreviewBytes,jsonlTail,jsonSummary,jsonContent,grep,lineCount:lines.length},valuesRedacted:true})); process.exit(0); } const out=[]; const walk=(p)=>{for(const ent of fs.readdirSync(p,{withFileTypes:true})){const full=path.join(p,ent.name); if(ent.isDirectory()) walk(full); else out.push(full); if(out.length>=maxFiles) return;}}; diff --git a/scripts/src/hwlab-node/web-observe-web-performance-payload.test.ts b/scripts/src/hwlab-node/web-observe-web-performance-payload.test.ts index acee5a16..9d273674 100644 --- a/scripts/src/hwlab-node/web-observe-web-performance-payload.test.ts +++ b/scripts/src/hwlab-node/web-observe-web-performance-payload.test.ts @@ -5,8 +5,10 @@ import { join } from "node:path"; import { spawnSync } from "node:child_process"; import { test } from "bun:test"; +import { withWebObserveCollectRendered } from "../hwlab-node-web-observe-render"; import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source"; import { nodeWebObserveCollectViewNodeScript } from "../hwlab-node-web-observe-collect"; +import { nodeWebObserveCollectNodeScript } from "./web-observe-scripts"; const alertThresholds = { sameOriginApiSlowMs: 60000, @@ -167,6 +169,56 @@ async function writeState(): Promise { return stateDir; } +test("generated observe analyzer source compiles as a single ESM bundle", async () => { + const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-analyzer-compile-")); + const analyzerPath = join(stateDir, "observer-analyzer.mjs"); + await writeFile(analyzerPath, nodeWebObserveAnalyzerSource(), { mode: 0o700 }); + const result = spawnSync("node", ["--check", analyzerPath], { encoding: "utf8" }); + assert.equal(result.status, 0, result.stderr || result.stdout); +}, 20_000); + +test("file collect exposes requested and resolved artifact when analysis report falls back", async () => { + const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-file-fallback-")); + await mkdir(join(stateDir, "analysis"), { recursive: true }); + await writeFile(join(stateDir, "analysis", "analyzer-stdout.json"), JSON.stringify({ + ok: false, + command: "web-probe-observe analyze", + error: "fixture analyzer failed before report", + valuesRedacted: true, + }) + "\n"); + + const script = nodeWebObserveCollectNodeScript(100, "analysis/report.json", null, null); + const result = spawnSync("bash", ["-lc", `state_dir=${shellQuote(stateDir)}\n${script}`], { + cwd: join(import.meta.dir, "../../.."), + encoding: "utf8", + }); + assert.equal(result.status, 0, result.stderr || result.stdout); + const output = JSON.parse(result.stdout) as Record; + const file = output.file as Record; + assert.equal(output.requestedFile, "analysis/report.json"); + assert.equal(output.resolvedFile, "analysis/analyzer-stdout.json"); + assert.equal(output.fallbackReason, "requested-file-missing"); + assert.equal(file.requestedRelative, "analysis/report.json"); + assert.equal(file.resolvedRelative, "analysis/analyzer-stdout.json"); + + const rendered = withWebObserveCollectRendered({ + ok: true, + status: "collected", + command: "web-probe observe collect", + id: "webobs-fallback-test", + node: "JD01", + lane: "v03", + view: "files", + requestedFile: "analysis/report.json", + collect: output, + valuesRedacted: true, + }); + assert.match(rendered.renderedText, /File resolution/u); + assert.match(rendered.renderedText, /analysis\/report\.json/u); + assert.match(rendered.renderedText, /analysis\/analyzer-stdout\.json/u); + assert.match(rendered.renderedText, /requested-file-missing/u); +}, 20_000); + test("analyzer and performance-summary include bounded web-performance runtime diagnostics", async () => { const stateDir = await writeState(); const report = await runAnalyzer(stateDir); diff --git a/scripts/src/hwlab-node/web-probe-observe.ts b/scripts/src/hwlab-node/web-probe-observe.ts index 1633b9e3..fd273a0d 100644 --- a/scripts/src/hwlab-node/web-probe-observe.ts +++ b/scripts/src/hwlab-node/web-probe-observe.ts @@ -2181,7 +2181,11 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption " promptSegments: selectedMetrics.promptSegments ?? selectedSummary?.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 runtimeAlertSummary = objectOrNull(srcRuntimeAlerts?.summary);", + "const runtimeAlerts = srcRuntimeAlerts ? { httpErrorCount: srcRuntimeAlerts.httpErrorCount ?? runtimeAlertSummary?.httpErrorCount ?? null, requestFailedCount: srcRuntimeAlerts.requestFailedCount ?? runtimeAlertSummary?.requestFailedCount ?? null, domDiagnosticSampleCount: srcRuntimeAlerts.domDiagnosticSampleCount ?? runtimeAlertSummary?.domDiagnosticSampleCount ?? null, consoleAlertCount: srcRuntimeAlerts.consoleAlertCount ?? runtimeAlertSummary?.consoleAlertCount ?? null, webPerformancePayloadRequestCount: srcRuntimeAlerts.webPerformancePayloadRequestCount ?? runtimeAlertSummary?.webPerformancePayloadRequestCount ?? null, webPerformancePayloadParsedCount: srcRuntimeAlerts.webPerformancePayloadParsedCount ?? runtimeAlertSummary?.webPerformancePayloadParsedCount ?? null, webPerformancePayloadParseIssueCount: srcRuntimeAlerts.webPerformancePayloadParseIssueCount ?? runtimeAlertSummary?.webPerformancePayloadParseIssueCount ?? null, webPerformanceRuntimeDiagnosticCount: srcRuntimeAlerts.webPerformanceRuntimeDiagnosticCount ?? runtimeAlertSummary?.webPerformanceRuntimeDiagnosticCount ?? null, webPerformanceRuntimeDiagnosticGroupCount: srcRuntimeAlerts.webPerformanceRuntimeDiagnosticGroupCount ?? runtimeAlertSummary?.webPerformanceRuntimeDiagnosticGroupCount ?? null } : null;", + "const slimWebPerformanceGroup = (item) => { const v = objectOrNull(item) || {}; return { diagnosticCode: clip(v.diagnosticCode, 80), reason: clip(v.reason, 60), module: clip(v.module, 80), eventType: clip(v.eventType, 40), count: v.count ?? null, eventCount: v.eventCount ?? null, deliveredCount: v.deliveredCount ?? null, chunkCount: v.chunkCount ?? null, droppedCount: v.droppedCount ?? null, maxFlushDurationMs: v.maxFlushDurationMs ?? null, maxItemsPerChunk: v.maxItemsPerChunk ?? null, maxChunkMs: v.maxChunkMs ?? null, replacedByKeyCount: v.replacedByKeyCount ?? null, firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, valuesRedacted: true }; };", + "const webPerformancePayloadStates = objectOrNull(srcRuntimeAlerts?.webPerformancePayloadStates) || objectOrNull(fullRecentWindow?.runtimeAlerts?.webPerformancePayloadStates) || objectOrNull(fullSource?.runtimeAlerts?.webPerformancePayloadStates);", + "const webPerformanceRuntimeDiagnostics = { summary: runtimeAlerts ? { payloadRequestCount: runtimeAlerts.webPerformancePayloadRequestCount ?? 0, payloadParsedCount: runtimeAlerts.webPerformancePayloadParsedCount ?? 0, payloadParseIssueCount: runtimeAlerts.webPerformancePayloadParseIssueCount ?? 0, runtimeDiagnosticCount: runtimeAlerts.webPerformanceRuntimeDiagnosticCount ?? 0, runtimeDiagnosticGroupCount: runtimeAlerts.webPerformanceRuntimeDiagnosticGroupCount ?? 0, capturedEventCount: webPerformancePayloadStates?.capturedEventCount ?? null, storedEventCount: webPerformancePayloadStates?.storedEventCount ?? null, valuesRedacted: true } : null, states: takeHead(firstNonEmptyArray(webPerformancePayloadStates?.states), 6), groups: takeHead(firstNonEmptyArray(srcRuntimeAlerts?.webPerformanceRuntimeDiagnosticsByCode, fullRecentWindow?.runtimeAlerts?.webPerformanceRuntimeDiagnosticsByCode, fullSource?.runtimeAlerts?.webPerformanceRuntimeDiagnosticsByCode), 6).map(slimWebPerformanceGroup), valuesRedacted: true };", "const srcPagePerformance = objectOrNull(source?.pagePerformance);", "const srcPagePerformanceSummary = objectOrNull(srcPagePerformance?.summary);", "const pagePerformance = srcPagePerformance ? { sameOriginApiPaths: srcPagePerformance.sameOriginApiPaths ?? srcPagePerformanceSummary?.sameOriginApiPathCount ?? null, longLivedStreamPathCount: srcPagePerformance.longLivedStreamPathCount ?? srcPagePerformanceSummary?.longLivedStreamPathCount ?? null, longLivedStreamOpenOverFiveSecondSampleCount: srcPagePerformance.longLivedStreamOpenOverFiveSecondSampleCount ?? srcPagePerformanceSummary?.longLivedStreamOpenOverFiveSecondSampleCount ?? null, slowPathCount: srcPagePerformance.slowPathCount ?? srcPagePerformanceSummary?.slowPathCount ?? null, slowSampleCount: srcPagePerformance.slowSampleCount ?? srcPagePerformanceSummary?.slowSampleCount ?? null, worstP95Ms: srcPagePerformance.worstP95Ms ?? srcPagePerformanceSummary?.worstP95Ms ?? null } : null;", @@ -2217,6 +2221,7 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption " archiveSummary: objectOrNull(source.archiveSummary),", " sampleMetrics: metrics,", " runtimeAlerts,", + " webPerformanceRuntimeDiagnostics,", " pagePerformance,", " projectManagement,", " promptNetwork,", @@ -2311,6 +2316,7 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption " turnColumns: minimalTurnColumns", " } : null,", " runtimeAlerts: compact.runtimeAlerts ?? null,", + " webPerformanceRuntimeDiagnostics: compact.webPerformanceRuntimeDiagnostics ?? null,", " pagePerformance: compact.pagePerformance ?? null,", " requestRate: compact.requestRate ?? null,", " requestRateCurve: compact.requestRateCurve ? { summary: compact.requestRateCurve.summary ?? null, buckets: Array.isArray(compact.requestRateCurve.buckets) ? compact.requestRateCurve.buckets.slice(-6) : [], pageCurves: Array.isArray(compact.requestRateCurve.pageCurves) ? compact.requestRateCurve.pageCurves.slice(0, 4).map((item) => ({ pageKey: item.pageKey ?? null, path: item.path ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-6) : [] })) : [], apiPathCurves: Array.isArray(compact.requestRateCurve.apiPathCurves) ? compact.requestRateCurve.apiPathCurves.slice(0, 6).map((item) => ({ apiKey: item.apiKey ?? null, path: item.path ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-6) : [] })) : [], valuesRedacted: true } : null,", @@ -2380,6 +2386,7 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption " turnTimingRecentUpdateJumpCount: compact.sampleMetrics.turnTimingRecentUpdateJumpCount ?? compact.sampleMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null", " } : null,", " runtimeAlerts: compact.runtimeAlerts ?? null,", + " webPerformanceRuntimeDiagnostics: compact.webPerformanceRuntimeDiagnostics ?? null,", " pagePerformance: compact.pagePerformance ?? null,", " requestRate: compact.requestRate ?? null,", " requestRateCurve: compact.requestRateCurve ? { summary: compact.requestRateCurve.summary ?? null, buckets: Array.isArray(compact.requestRateCurve.buckets) ? compact.requestRateCurve.buckets.slice(-6) : [], pageCurves: Array.isArray(compact.requestRateCurve.pageCurves) ? compact.requestRateCurve.pageCurves.slice(0, 3).map((item) => ({ pageKey: item.pageKey ?? null, path: item.path ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-6) : [] })) : [], apiPathCurves: Array.isArray(compact.requestRateCurve.apiPathCurves) ? compact.requestRateCurve.apiPathCurves.slice(0, 4).map((item) => ({ apiKey: item.apiKey ?? null, path: item.path ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-6) : [] })) : [], valuesRedacted: true } : null,", @@ -2424,6 +2431,7 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption " turnTimingRecentUpdateJumpCount: compact.sampleMetrics.turnTimingRecentUpdateJumpCount ?? compact.sampleMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null", " } : null,", " runtimeAlerts: compact.runtimeAlerts ?? null,", + " webPerformanceRuntimeDiagnostics: compact.webPerformanceRuntimeDiagnostics ?? null,", " pagePerformance: compact.pagePerformance ?? null,", " requestRate: compact.requestRate ?? null,", " requestRatePeaks: Array.isArray(compact.requestRatePeaks) ? compact.requestRatePeaks.slice(0, 3) : [],", @@ -2451,7 +2459,7 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption " output = compactOutput(ultratiny);", " }", " if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {", - " output = compactOutput({ ok: compact.ok, counts: compact.counts ?? null, jsonlScope: compact.jsonlScope ?? null, analysisWindow: compact.analysisWindow ?? null, archiveSummary: compact.archiveSummary ? { redFindingCount: compact.archiveSummary.redFindingCount ?? null, findingCount: compact.archiveSummary.findingCount ?? null, sampleCount: compact.archiveSummary.sampleMetrics?.sampleCount ?? null, slowPathCount: compact.archiveSummary.pagePerformance?.slowPathCount ?? null } : null, requestRate: compact.requestRate ?? null, requestRateCurve: compact.requestRateCurve ? { summary: compact.requestRateCurve.summary ?? null, buckets: Array.isArray(compact.requestRateCurve.buckets) ? compact.requestRateCurve.buckets.slice(-4) : [], pageCurves: Array.isArray(compact.requestRateCurve.pageCurves) ? compact.requestRateCurve.pageCurves.slice(0, 2).map((item) => ({ path: item.path ?? item.pageKey ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-4) : [] })) : [], apiPathCurves: Array.isArray(compact.requestRateCurve.apiPathCurves) ? compact.requestRateCurve.apiPathCurves.slice(0, 3).map((item) => ({ apiKey: item.apiKey ?? item.path ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-4) : [] })) : [], valuesRedacted: true } : null, requestRatePeaks: Array.isArray(compact.requestRatePeaks) ? compact.requestRatePeaks.slice(0, 3) : [], projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-2) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-2) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-2) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 2) : [], valuesRedacted: true } : null, toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [], commandState: compact.commandState ? { pendingCount: compact.commandState.pendingCount ?? null, processingCount: compact.commandState.processingCount ?? null, abandonedCount: compact.commandState.abandonedCount ?? null, failedCount: compact.commandState.failedCount ?? null } : null, findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, timingSourceOfTruth: item.timingSourceOfTruth ?? null, timingStatus: item.timingStatus ?? null, timingAlert: item.timingAlert === true, summary: clip(item.summary ?? item.message, 120) })) : [], archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, timingSourceOfTruth: item.timingSourceOfTruth ?? null, timingStatus: item.timingStatus ?? null, timingAlert: item.timingAlert === true, summary: clip(item.summary ?? item.message, 120) })) : [], commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [], archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [], reportJsonPath: compact.reportJsonPath ?? reportJsonPath, reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: compact.reportMdPath ?? reportMdPath, reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, hardFallback: true, compactStdoutLimitBytes }, valuesRedacted: true });", + " output = compactOutput({ ok: compact.ok, counts: compact.counts ?? null, jsonlScope: compact.jsonlScope ?? null, analysisWindow: compact.analysisWindow ?? null, archiveSummary: compact.archiveSummary ? { redFindingCount: compact.archiveSummary.redFindingCount ?? null, findingCount: compact.archiveSummary.findingCount ?? null, sampleCount: compact.archiveSummary.sampleMetrics?.sampleCount ?? null, slowPathCount: compact.archiveSummary.pagePerformance?.slowPathCount ?? null } : null, requestRate: compact.requestRate ?? null, requestRateCurve: compact.requestRateCurve ? { summary: compact.requestRateCurve.summary ?? null, buckets: Array.isArray(compact.requestRateCurve.buckets) ? compact.requestRateCurve.buckets.slice(-4) : [], pageCurves: Array.isArray(compact.requestRateCurve.pageCurves) ? compact.requestRateCurve.pageCurves.slice(0, 2).map((item) => ({ path: item.path ?? item.pageKey ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-4) : [] })) : [], apiPathCurves: Array.isArray(compact.requestRateCurve.apiPathCurves) ? compact.requestRateCurve.apiPathCurves.slice(0, 3).map((item) => ({ apiKey: item.apiKey ?? item.path ?? null, count: item.count ?? null, peakRequestPerMinute: item.peakRequestPerMinute ?? null, peakBucket: item.peakBucket ?? null, buckets: Array.isArray(item.buckets) ? item.buckets.slice(-4) : [] })) : [], valuesRedacted: true } : null, requestRatePeaks: Array.isArray(compact.requestRatePeaks) ? compact.requestRatePeaks.slice(0, 3) : [], webPerformanceRuntimeDiagnostics: compact.webPerformanceRuntimeDiagnostics ?? null, projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-2) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-2) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-2) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 2) : [], valuesRedacted: true } : null, toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [], commandState: compact.commandState ? { pendingCount: compact.commandState.pendingCount ?? null, processingCount: compact.commandState.processingCount ?? null, abandonedCount: compact.commandState.abandonedCount ?? null, failedCount: compact.commandState.failedCount ?? null } : null, findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, timingSourceOfTruth: item.timingSourceOfTruth ?? null, timingStatus: item.timingStatus ?? null, timingAlert: item.timingAlert === true, summary: clip(item.summary ?? item.message, 120) })) : [], archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, timingSourceOfTruth: item.timingSourceOfTruth ?? null, timingStatus: item.timingStatus ?? null, timingAlert: item.timingAlert === true, summary: clip(item.summary ?? item.message, 120) })) : [], commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [], archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [], reportJsonPath: compact.reportJsonPath ?? reportJsonPath, reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: compact.reportMdPath ?? reportMdPath, reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, hardFallback: true, compactStdoutLimitBytes }, valuesRedacted: true });", " }", " }", "}", @@ -2467,6 +2475,7 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption " sampleMetrics: compact.sampleMetrics ? { sampleCount: compact.sampleMetrics.sampleCount ?? null, roundCount: Array.isArray(compact.sampleMetrics.rounds) ? compact.sampleMetrics.rounds.length : null, turnColumnCount: Array.isArray(compact.sampleMetrics.turnColumns) ? compact.sampleMetrics.turnColumns.length : null, turnTimingRows: compact.sampleMetrics.turnTimingRows ?? null, turnTimingTotalElapsedForwardJumpCount: compact.sampleMetrics.turnTimingTotalElapsedForwardJumpCount ?? null, turnTimingRecentUpdateJumpCount: compact.sampleMetrics.turnTimingRecentUpdateJumpCount ?? compact.sampleMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null, loadingSampleCount: compact.sampleMetrics.loadingSampleCount ?? null, loadingMaxCount: compact.sampleMetrics.loadingMaxCount ?? null, loadingOverFiveSecondSegmentCount: compact.sampleMetrics.loadingOverFiveSecondSegmentCount ?? null } : null,", " requestRate: compact.requestRate ? { requestCount: compact.requestRate.requestCount ?? null, totalPeakPerMinute: compact.requestRate.totalPeakPerMinute ?? null, pagePeakPerMinute: compact.requestRate.pagePeakPerMinute ?? null, apiPathPeakPerMinute: compact.requestRate.apiPathPeakPerMinute ?? null, overThresholdPeakCount: compact.requestRate.overThresholdPeakCount ?? null } : null,", " frontendPerformance: frontend ? { eventCount: frontend.eventCount ?? null, longTaskCount: frontend.longTaskCount ?? null, longAnimationFrameCount: frontend.longAnimationFrameCount ?? null, eventLoopGapCount: frontend.eventLoopGapCount ?? null, captureCount: frontend.captureCount ?? null, maxLongTaskMs: frontend.maxLongTaskMs ?? null, maxLongAnimationFrameMs: frontend.maxLongAnimationFrameMs ?? null, maxEventLoopGapMs: frontend.maxEventLoopGapMs ?? null, profileHotspotCount: frontend.profileHotspotCount ?? null, scriptHotspotCount: frontend.scriptHotspotCount ?? null, profileFunctions: takeHead(frontendHotspots.profileFunctions, 3).map((item) => ({ functionName: clip(item?.functionName ?? item?.name, 64), url: clip(item?.url ?? item?.sourceURL, 96), selfTimeMs: item?.selfTimeMs ?? null, totalTimeMs: item?.totalTimeMs ?? null, hitCount: item?.hitCount ?? null })), profileStacks: takeHead(frontendHotspots.profileStacks, 2).map((item) => ({ functionName: clip(item?.functionName ?? item?.name, 64), selfTimeMs: item?.selfTimeMs ?? null, frameCount: item?.frameCount ?? (Array.isArray(item?.frames) ? item.frames.length : null) })), scripts: takeHead(frontendHotspots.scripts, 3).map((item) => ({ functionName: clip(item?.sourceFunctionName ?? item?.functionName ?? item?.invoker, 64), url: clip(item?.sourceURL ?? item?.url, 96), totalDurationMs: item?.totalDurationMs ?? null, count: item?.count ?? null })) } : null,", + " webPerformanceRuntimeDiagnostics: compact.webPerformanceRuntimeDiagnostics ?? null,", " commandState: compact.commandState ? { pendingCount: compact.commandState.pendingCount ?? null, processingCount: compact.commandState.processingCount ?? null, abandonedCount: compact.commandState.abandonedCount ?? null, failedCount: compact.commandState.failedCount ?? null } : null,", " findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 100) })) : [],", " archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 3).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 100) })) : [],",