From c8b2c084451982691cc6a4285da7a1b47503ac6c Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 2 Jul 2026 08:04:28 +0000 Subject: [PATCH] fix: bound web probe performance summary collect --- scripts/src/hwlab-node-web-observe-collect.ts | 46 +++- .../hwlab-node/web-observe-collect-compact.ts | 229 ++++++++++++++++++ scripts/src/hwlab-node/web-probe-observe.ts | 143 +---------- 3 files changed, 267 insertions(+), 151 deletions(-) create mode 100644 scripts/src/hwlab-node/web-observe-collect-compact.ts diff --git a/scripts/src/hwlab-node-web-observe-collect.ts b/scripts/src/hwlab-node-web-observe-collect.ts index 83ae5a0d..87772bb4 100644 --- a/scripts/src/hwlab-node-web-observe-collect.ts +++ b/scripts/src/hwlab-node-web-observe-collect.ts @@ -724,20 +724,42 @@ function targetNodeFromStateDir(){ const index=parts.lastIndexOf('web-observe'); return index>=0&&parts[index+1]?parts[index+1]:null; } +function perfFrameKey(frame){ + return {functionName:short(frame?.functionName||frame?.name||'(anonymous)',48),url:short(frame?.url||frame?.sourceURL||frame?.scriptUrl||'',72),scriptId:frame?.scriptId??null,lineNumber:frame?.lineNumber??frame?.line??null,columnNumber:frame?.columnNumber??frame?.column??null,valuesRedacted:true}; +} +function compactProfileHotspot(item){ + return {functionName:short(item?.functionName||item?.name||'(anonymous)',64),url:short(item?.url||item?.sourceURL||item?.scriptUrl||'',88),scriptId:item?.scriptId??null,lineNumber:item?.lineNumber??item?.line??null,columnNumber:item?.columnNumber??item?.column??null,selfTimeMs:item?.selfTimeMs??item?.selfMs??null,totalTimeMs:item?.totalTimeMs??item?.totalMs??null,hitCount:item?.hitCount??item?.sampleCount??null,captureCount:item?.captureCount??null,valuesRedacted:true}; +} +function compactProfileStack(item){ + const frames=Array.isArray(item?.frames)?item.frames.slice(0,3).map(perfFrameKey):[]; + return {functionName:short(item?.functionName||item?.name||'(anonymous)',64),url:short(item?.url||item?.sourceURL||item?.scriptUrl||'',88),scriptId:item?.scriptId??null,lineNumber:item?.lineNumber??item?.line??null,columnNumber:item?.columnNumber??item?.column??null,selfTimeMs:item?.selfTimeMs??item?.selfMs??null,totalTimeMs:item?.totalTimeMs??item?.totalMs??null,hitCount:item?.hitCount??item?.sampleCount??null,frameCount:Array.isArray(item?.frames)?item.frames.length:null,frames,framesOmitted:Math.max(0,(Array.isArray(item?.frames)?item.frames.length:0)-frames.length),valuesRedacted:true}; +} +function compactScriptHotspot(item){ + return {sourceFunctionName:short(item?.sourceFunctionName||item?.functionName||item?.invoker||'(anonymous)',64),sourceURL:short(item?.sourceURL||item?.url||'',88),lineNumber:item?.lineNumber??item?.line??null,columnNumber:item?.columnNumber??item?.column??null,totalDurationMs:item?.totalDurationMs??item?.durationMs??null,count:item?.count??item?.hitCount??null,invoker:short(item?.invoker||'',64),valuesRedacted:true}; +} +function compactPerfEvent(item){ + return {ts:item?.ts??null,kind:item?.kind??null,durationMs:item?.durationMs??null,pageRole:item?.pageRole??null,pageId:item?.pageId??null,sampleSeq:item?.sampleSeq??item?.seq??null,scriptCount:item?.scriptCount??(Array.isArray(item?.scripts)?item.scripts.length:null),url:short(item?.url||item?.sourceURL||'',88),valuesRedacted:true}; +} +function compactPerfCapture(item){ + return {ts:item?.ts??null,commandId:item?.commandId??null,status:item?.status??null,durationMs:item?.durationMs??item?.profileDurationMs??null,sampleCount:item?.sampleCount??item?.samples??null,nodeCount:item?.nodeCount??item?.nodes??null,file:short(item?.file||item?.path||item?.relativePath||'',88),valuesRedacted:true}; +} +function compactPerfFinding(item){ + return {severity:item?.severity??item?.level??null,id:short(item?.id||item?.kind||item?.code||'',56),count:item?.count??item?.sampleCount??null,rootCause:item?.rootCause??item?.rootCauseStatus??null,summary:short(item?.summary||item?.message||'',110),valuesRedacted:true}; +} function performanceSummaryFromReport(){ const perf=report.frontendPerformance&&typeof report.frontendPerformance==='object'?report.frontendPerformance:{}; const summary=perf.summary&&typeof perf.summary==='object'?perf.summary:{}; - const findings=Array.isArray(report.findings)?report.findings.filter((item)=>String(item?.id||item?.kind||'').match(/^frontend-(?:long|event-loop|cpu-profile|performance)/u)).slice(0,20):[]; + const findings=Array.isArray(report.findings)?report.findings.filter((item)=>String(item?.id||item?.kind||'').match(/^frontend-(?:long|event-loop|cpu-profile|performance)/u)).slice(0,6).map(compactPerfFinding):[]; const captureRows=Array.isArray(perf.captures)?perf.captures:[]; return { summary:{...summary, rawPerformanceRowCount:performanceRows.length, valuesRedacted:true}, - longTasks:Array.isArray(perf.longTasks)?perf.longTasks.slice(0,12):[], - longAnimationFrames:Array.isArray(perf.longAnimationFrames)?perf.longAnimationFrames.slice(0,12):[], - eventLoopGaps:Array.isArray(perf.eventLoopGaps)?perf.eventLoopGaps.slice(0,12):[], - scriptHotspots:Array.isArray(perf.scriptHotspots)?perf.scriptHotspots.slice(0,12):[], - profileHotspots:Array.isArray(perf.profileHotspots)?perf.profileHotspots.slice(0,12):[], - profileStacks:Array.isArray(perf.profileStacks)?perf.profileStacks.slice(0,8):[], - captures:captureRows.slice(-12), + longTasks:Array.isArray(perf.longTasks)?perf.longTasks.slice(0,3).map(compactPerfEvent):[], + longAnimationFrames:Array.isArray(perf.longAnimationFrames)?perf.longAnimationFrames.slice(0,3).map(compactPerfEvent):[], + eventLoopGaps:Array.isArray(perf.eventLoopGaps)?perf.eventLoopGaps.slice(0,3).map(compactPerfEvent):[], + scriptHotspots:Array.isArray(perf.scriptHotspots)?perf.scriptHotspots.slice(0,5).map(compactScriptHotspot):[], + profileHotspots:Array.isArray(perf.profileHotspots)?perf.profileHotspots.slice(0,5).map(compactProfileHotspot):[], + profileStacks:Array.isArray(perf.profileStacks)?perf.profileStacks.slice(0,3).map(compactProfileStack):[], + captures:captureRows.slice(-3).map(compactPerfCapture), findings, valuesRedacted:true }; @@ -750,6 +772,12 @@ function renderPerformanceSummary(perf){ lines.push('','CPU profile hotspots'); if(perf.profileHotspots.length===0) lines.push('-'); for(const item of perf.profileHotspots.slice(0,10)) lines.push(String(item.selfTimeMs??0)+'ms self '+short(item.functionName||'(anonymous)',44)+' '+short(item.url||item.scriptId||'-',92)+' line='+String(item.lineNumber??'-')+' captures='+String(item.captureCount??'-')); + lines.push('','CPU profile stacks'); + if(perf.profileStacks.length===0) lines.push('-'); + for(const item of perf.profileStacks.slice(0,5)){ + const frames=Array.isArray(item.frames)?item.frames.slice(0,5).map((frame)=>short(frame.functionName||'(anonymous)',36)+'@'+short(frame.url||frame.scriptId||'-',54)+':'+String(frame.lineNumber??'-')).join(' <- '):'-'; + lines.push(String(item.selfTimeMs??0)+'ms self '+short(item.functionName||'(anonymous)',44)+' line='+String(item.lineNumber??'-')+' frames='+frames+(item.framesOmitted?(' omitted='+String(item.framesOmitted)):'')); + } lines.push('','LoAF script hotspots'); if(perf.scriptHotspots.length===0) lines.push('-'); for(const item of perf.scriptHotspots.slice(0,10)) lines.push(String(item.totalDurationMs??0)+'ms total count='+String(item.count??0)+' '+short(item.sourceFunctionName||item.invoker||'(anonymous)',44)+' '+short(item.sourceURL||'-',92)+' line='+String(item.lineNumber??'-')); @@ -788,7 +816,7 @@ function renderProjectSummary(project){ const rows=turnSummaryRows(); if(view==='performance-summary'){ const perf=performanceSummaryFromReport(); - console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:perf.summary,profileHotspots:perf.profileHotspots,profileStacks:perf.profileStacks,scriptHotspots:perf.scriptHotspots,longTasks:perf.longTasks,longAnimationFrames:perf.longAnimationFrames,eventLoopGaps:perf.eventLoopGaps,captures:perf.captures,findings:perf.findings,artifactFileCount:files.length,skippedFileCount:skippedFiles.length,skippedFiles:skippedFiles.slice(0,20),renderedText:renderPerformanceSummary(perf),sourceFiles:['performance-events.jsonl','artifacts.jsonl','analysis/report.json'],valuesRedacted:true})); + console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:perf.summary,artifactFileCount:files.length,skippedFileCount:skippedFiles.length,renderedText:renderPerformanceSummary(perf),sourceFiles:['performance-events.jsonl','artifacts.jsonl','analysis/report.json'],drillDown:'bun scripts/cli.ts web-probe observe collect '+String(manifest.jobId||'')+' --view files --file analysis/report.json',valuesRedacted:true})); process.exit(0); } if(view==='project-summary'||view==='project-mdtodo-summary'){ diff --git a/scripts/src/hwlab-node/web-observe-collect-compact.ts b/scripts/src/hwlab-node/web-observe-collect-compact.ts new file mode 100644 index 00000000..fa61d9a6 --- /dev/null +++ b/scripts/src/hwlab-node/web-observe-collect-compact.ts @@ -0,0 +1,229 @@ +// SPEC: PJ2026-01040111 长程观测 draft-2026-06-20-p0-passive-web-probe-observer. +// Responsibility: bounded raw payloads for web-probe observe collect views. + +export function compactObserveCollectForRaw(collect: Record | null): Record | null { + if (collect === null) return null; + const rows = Array.isArray(collect.rows) ? collect.rows.map((item) => { + const row = observeRecord(item); + const finalResponse = observeRecord(row.finalResponse); + return { + round: row.round ?? null, + commandId: row.commandId ?? null, + userHash: row.userHash ?? null, + userBytes: row.userBytes ?? null, + traceId: row.traceId ?? null, + status: row.status ?? null, + elapsedSeconds: row.elapsedSeconds ?? null, + recentUpdateSeconds: row.recentUpdateSeconds ?? null, + marks: row.marks ?? null, + firstSeq: row.firstSeq ?? null, + lastSeq: row.lastSeq ?? null, + lastTs: row.lastTs ?? null, + finalResponse: { + preview: finalResponse.preview ?? null, + textHash: finalResponse.textHash ?? null, + textBytes: finalResponse.textBytes ?? null, + empty: finalResponse.empty === true, + }, + valuesRedacted: true, + }; + }) : undefined; + const timelineRows = Array.isArray(collect.timelineRows) ? collect.timelineRows.slice(0, 12).map((item) => { + const row = observeRecord(item); + return { + ts: row.ts ?? null, + seq: row.seq ?? null, + kind: row.kind ?? null, + phase: row.phase ?? null, + type: row.type ?? null, + commandId: row.commandId ?? null, + sessionId: row.sessionId ?? null, + traceId: row.traceId ?? null, + summary: row.summary ?? null, + valuesRedacted: true, + }; + }) : undefined; + const collectView = typeof collect.view === "string" ? collect.view : null; + const slimNetworkEvidence = (value: unknown): Record => { + const source = observeRecord(value); + const latestTurn = observeRecord(source.latestTurnResponse); + const latestTrace = observeRecord(source.latestTraceEventsResponse); + return { + requestCount: source.requestCount ?? null, + responseCount: source.responseCount ?? null, + failedCount: source.failedCount ?? null, + terminalResponseCount: source.terminalResponseCount ?? null, + turnResponseCount: source.turnResponseCount ?? null, + traceEventsResponseCount: source.traceEventsResponseCount ?? null, + latestTurnResponse: { + ts: latestTurn.ts ?? null, + sampleSeq: latestTurn.sampleSeq ?? null, + status: latestTurn.status ?? null, + bodySummary: observeRecord(latestTurn.bodySummary), + valuesRedacted: true, + }, + latestTraceEventsResponse: { + ts: latestTrace.ts ?? null, + sampleSeq: latestTrace.sampleSeq ?? null, + status: latestTrace.status ?? null, + bodySummary: observeRecord(latestTrace.bodySummary), + valuesRedacted: true, + }, + valuesRedacted: true, + }; + }; + const slimFreeze = (value: unknown): Record => { + const source = observeRecord(value); + const blocker = observeRecord(source.latestBlocker); + return { + blockerCount: source.blockerCount ?? null, + responsivenessEventCount: source.responsivenessEventCount ?? null, + latestBlocker: { + ts: blocker.ts ?? null, + sampleSeq: blocker.sampleSeq ?? null, + rootCause: blocker.rootCause ?? null, + rootCauseStatus: blocker.rootCauseStatus ?? null, + fallbackAllowed: blocker.fallbackAllowed === true, + observed: observeRecord(blocker.observed), + page: observeRecord(blocker.page), + valuesRedacted: true, + }, + valuesRedacted: true, + }; + }; + const slimFindings = Array.isArray(collect.findings) ? collect.findings.slice(0, 6).map((item) => { + const row = observeRecord(item); + return { + id: row.id ?? null, + severity: row.severity ?? null, + count: row.count ?? null, + rootCause: row.rootCause ?? row.rootCauseStatus ?? null, + summary: typeof row.summary === "string" ? row.summary.slice(0, 140) : row.summary ?? null, + valuesRedacted: true, + }; + }) : undefined; + const slimPerfFrame = (value: unknown): Record => { + const row = observeRecord(value); + return { + functionName: row.functionName ?? null, + url: row.url ?? null, + scriptId: row.scriptId ?? null, + lineNumber: row.lineNumber ?? null, + columnNumber: row.columnNumber ?? null, + valuesRedacted: true, + }; + }; + const slimPerfStack = (value: unknown): Record => { + const row = observeRecord(value); + return { + functionName: row.functionName ?? null, + url: row.url ?? null, + scriptId: row.scriptId ?? null, + lineNumber: row.lineNumber ?? null, + columnNumber: row.columnNumber ?? null, + selfTimeMs: row.selfTimeMs ?? null, + totalTimeMs: row.totalTimeMs ?? null, + hitCount: row.hitCount ?? null, + frameCount: row.frameCount ?? null, + frames: Array.isArray(row.frames) ? row.frames.slice(0, 3).map(slimPerfFrame) : [], + framesOmitted: row.framesOmitted ?? null, + valuesRedacted: true, + }; + }; + const slimPerfHotspot = (value: unknown): Record => { + const row = observeRecord(value); + return { + functionName: row.functionName ?? row.sourceFunctionName ?? null, + url: row.url ?? row.sourceURL ?? null, + scriptId: row.scriptId ?? null, + lineNumber: row.lineNumber ?? null, + columnNumber: row.columnNumber ?? null, + selfTimeMs: row.selfTimeMs ?? null, + totalTimeMs: row.totalTimeMs ?? null, + totalDurationMs: row.totalDurationMs ?? null, + count: row.count ?? null, + hitCount: row.hitCount ?? null, + captureCount: row.captureCount ?? null, + invoker: row.invoker ?? null, + valuesRedacted: true, + }; + }; + const slimPerfEvent = (value: unknown): Record => { + const row = observeRecord(value); + return { + ts: row.ts ?? null, + kind: row.kind ?? null, + durationMs: row.durationMs ?? null, + pageRole: row.pageRole ?? null, + pageId: row.pageId ?? null, + sampleSeq: row.sampleSeq ?? null, + scriptCount: row.scriptCount ?? null, + url: row.url ?? null, + valuesRedacted: true, + }; + }; + const slimPerfCapture = (value: unknown): Record => { + const row = observeRecord(value); + return { + ts: row.ts ?? null, + commandId: row.commandId ?? null, + status: row.status ?? null, + durationMs: row.durationMs ?? null, + sampleCount: row.sampleCount ?? null, + nodeCount: row.nodeCount ?? null, + file: row.file ?? null, + valuesRedacted: true, + }; + }; + const slimPerfFindings = Array.isArray(collect.findings) ? collect.findings.slice(0, 5).map((item) => { + const row = observeRecord(item); + return { + id: row.id ?? null, + severity: row.severity ?? null, + count: row.count ?? null, + rootCause: row.rootCause ?? row.rootCauseStatus ?? null, + summary: typeof row.summary === "string" ? row.summary.slice(0, 110) : row.summary ?? null, + valuesRedacted: true, + }; + }) : undefined; + const isPerformanceSummary = collectView === "performance-summary"; + return { + ok: collect.ok !== false, + command: collect.command, + view: collect.view, + stateDir: collect.stateDir, + turnCount: collect.turnCount, + artifactFileCount: collect.artifactFileCount ?? collect.fileCount, + skippedFileCount: collect.skippedFileCount, + skippedFiles: Array.isArray(collect.skippedFiles) ? collect.skippedFiles.slice(0, 8) : undefined, + anchor: observeRecord(collect.anchor), + window: observeRecord(collect.window), + counts: observeRecord(collect.counts), + summary: observeRecord(collect.summary), + ...(rows === undefined ? {} : { rows }), + ...(timelineRows === undefined ? {} : { timelineRows }), + renderedText: collectView === "timeline" || collectView === "workbench-triad" ? undefined : typeof collect.renderedText === "string" ? collect.renderedText : undefined, + sourceFiles: Array.isArray(collect.sourceFiles) ? collect.sourceFiles : undefined, + blocker: collect.blocker, + sampleSeq: collect.sampleSeq, + traceId: collect.traceId, + finalResponse: collect.finalResponse, + statusGap: observeRecord(collect.statusGap), + dom: observeRecord(collect.dom), + networkEvidence: collectView === "workbench-triad" ? slimNetworkEvidence(collect.networkEvidence) : observeRecord(collect.networkEvidence), + freeze: collectView === "workbench-triad" ? slimFreeze(collect.freeze) : observeRecord(collect.freeze), + profileHotspots: Array.isArray(collect.profileHotspots) ? (isPerformanceSummary ? collect.profileHotspots.slice(0, 5).map(slimPerfHotspot) : collect.profileHotspots.slice(0, 8)) : undefined, + profileStacks: Array.isArray(collect.profileStacks) ? (isPerformanceSummary ? collect.profileStacks.slice(0, 3).map(slimPerfStack) : collect.profileStacks.slice(0, 5)) : undefined, + scriptHotspots: Array.isArray(collect.scriptHotspots) ? (isPerformanceSummary ? collect.scriptHotspots.slice(0, 5).map(slimPerfHotspot) : collect.scriptHotspots.slice(0, 8)) : undefined, + longTasks: Array.isArray(collect.longTasks) ? (isPerformanceSummary ? collect.longTasks.slice(0, 3).map(slimPerfEvent) : collect.longTasks.slice(0, 8)) : undefined, + longAnimationFrames: Array.isArray(collect.longAnimationFrames) ? (isPerformanceSummary ? collect.longAnimationFrames.slice(0, 3).map(slimPerfEvent) : collect.longAnimationFrames.slice(0, 8)) : undefined, + eventLoopGaps: Array.isArray(collect.eventLoopGaps) ? (isPerformanceSummary ? collect.eventLoopGaps.slice(0, 3).map(slimPerfEvent) : collect.eventLoopGaps.slice(0, 8)) : undefined, + captures: Array.isArray(collect.captures) ? (isPerformanceSummary ? collect.captures.slice(0, 3).map(slimPerfCapture) : collect.captures.slice(0, 8)) : undefined, + findings: collectView === "workbench-triad" ? slimFindings : isPerformanceSummary ? slimPerfFindings : Array.isArray(collect.findings) ? collect.findings.slice(0, 8) : undefined, + valuesRedacted: true, + }; +} + +function observeRecord(value: unknown): Record { + return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record : {}; +} diff --git a/scripts/src/hwlab-node/web-probe-observe.ts b/scripts/src/hwlab-node/web-probe-observe.ts index 3d32c7db..8852dd45 100644 --- a/scripts/src/hwlab-node/web-probe-observe.ts +++ b/scripts/src/hwlab-node/web-probe-observe.ts @@ -36,6 +36,7 @@ import { transPath } from "./runtime-common"; import { assertLane, assertNodeId, compactCommandResult, compactCommandResultRedacted, compactCommandResultWithStdoutTail, nullableRecord, optionValue, parseJsonObject, positiveIntegerOption, record, requiredOption, shellQuote } from "./utils"; import { nodeWebObserveResolveStateDirShell, recoverWebObserveAnalyzeTurnDetails, renderWebObserveStartResult, renderWebProbeRunResult, upsertWebObserveIndexEntry, webObserveCommandLabel, webObserveIdFromOptions, webObserveIdFromStatus, webObserveIndexEntryFromOptions, webObserveNextCommands, withWebObserveAnalyzeRendered, withWebObserveShortcuts } from "./web-observe-render"; import { commandSummaryForOutput, isSafeWebObserveArchivePrefix, isSafeWebObserveCollectFile, isSafeWebObserveFindingId, isSafeWebObserveJobId, isSafeWebObserveStateDir, isSafeWebObserveTraceId, nodeWebObserveCollectNodeScript, nodeWebObserveForceStopNodeScript, nodeWebObserveStatusNodeScript, nodeWebObserveWaitCommandShell, runNodeWebProbeScript, safeWebObserveSegment, safeWebObserveTargetSegment } from "./web-observe-scripts"; +import { compactObserveCollectForRaw } from "./web-observe-collect-compact"; import { displayRepoPath, readBootstrapAdminPasswordMaterial, sleepSync } from "./web-probe"; export function parseNodeWebProbeSentinelOptions(args: string[]): NodeWebProbeSentinelOptions { @@ -2063,148 +2064,6 @@ export function runNodeWebProbeObserveCollect(options: NodeWebProbeObserveOption return options.raw ? payload : withWebObserveCollectRendered(payload); } -function compactObserveCollectForRaw(collect: Record | null): Record | null { - if (collect === null) return null; - const rows = Array.isArray(collect.rows) ? collect.rows.map((item) => { - const row = observeRecord(item); - const finalResponse = observeRecord(row.finalResponse); - return { - round: row.round ?? null, - commandId: row.commandId ?? null, - userHash: row.userHash ?? null, - userBytes: row.userBytes ?? null, - traceId: row.traceId ?? null, - status: row.status ?? null, - elapsedSeconds: row.elapsedSeconds ?? null, - recentUpdateSeconds: row.recentUpdateSeconds ?? null, - marks: row.marks ?? null, - firstSeq: row.firstSeq ?? null, - lastSeq: row.lastSeq ?? null, - lastTs: row.lastTs ?? null, - finalResponse: { - preview: finalResponse.preview ?? null, - textHash: finalResponse.textHash ?? null, - textBytes: finalResponse.textBytes ?? null, - empty: finalResponse.empty === true, - }, - valuesRedacted: true, - }; - }) : undefined; - const timelineRows = Array.isArray(collect.timelineRows) ? collect.timelineRows.slice(0, 12).map((item) => { - const row = observeRecord(item); - return { - ts: row.ts ?? null, - seq: row.seq ?? null, - kind: row.kind ?? null, - phase: row.phase ?? null, - type: row.type ?? null, - commandId: row.commandId ?? null, - sessionId: row.sessionId ?? null, - traceId: row.traceId ?? null, - summary: row.summary ?? null, - valuesRedacted: true, - }; - }) : undefined; - const collectView = typeof collect.view === "string" ? collect.view : null; - const slimNetworkEvidence = (value: unknown): Record => { - const source = observeRecord(value); - const latestTurn = observeRecord(source.latestTurnResponse); - const latestTrace = observeRecord(source.latestTraceEventsResponse); - return { - requestCount: source.requestCount ?? null, - responseCount: source.responseCount ?? null, - failedCount: source.failedCount ?? null, - terminalResponseCount: source.terminalResponseCount ?? null, - turnResponseCount: source.turnResponseCount ?? null, - traceEventsResponseCount: source.traceEventsResponseCount ?? null, - latestTurnResponse: { - ts: latestTurn.ts ?? null, - sampleSeq: latestTurn.sampleSeq ?? null, - status: latestTurn.status ?? null, - bodySummary: observeRecord(latestTurn.bodySummary), - valuesRedacted: true, - }, - latestTraceEventsResponse: { - ts: latestTrace.ts ?? null, - sampleSeq: latestTrace.sampleSeq ?? null, - status: latestTrace.status ?? null, - bodySummary: observeRecord(latestTrace.bodySummary), - valuesRedacted: true, - }, - valuesRedacted: true, - }; - }; - const slimFreeze = (value: unknown): Record => { - const source = observeRecord(value); - const blocker = observeRecord(source.latestBlocker); - return { - blockerCount: source.blockerCount ?? null, - responsivenessEventCount: source.responsivenessEventCount ?? null, - latestBlocker: { - ts: blocker.ts ?? null, - sampleSeq: blocker.sampleSeq ?? null, - rootCause: blocker.rootCause ?? null, - rootCauseStatus: blocker.rootCauseStatus ?? null, - fallbackAllowed: blocker.fallbackAllowed === true, - observed: observeRecord(blocker.observed), - page: observeRecord(blocker.page), - valuesRedacted: true, - }, - valuesRedacted: true, - }; - }; - const slimFindings = Array.isArray(collect.findings) ? collect.findings.slice(0, 6).map((item) => { - const row = observeRecord(item); - return { - id: row.id ?? null, - severity: row.severity ?? null, - count: row.count ?? null, - rootCause: row.rootCause ?? row.rootCauseStatus ?? null, - summary: typeof row.summary === "string" ? row.summary.slice(0, 140) : row.summary ?? null, - valuesRedacted: true, - }; - }) : undefined; - return { - ok: collect.ok !== false, - command: collect.command, - view: collect.view, - stateDir: collect.stateDir, - turnCount: collect.turnCount, - artifactFileCount: collect.artifactFileCount ?? collect.fileCount, - skippedFileCount: collect.skippedFileCount, - skippedFiles: Array.isArray(collect.skippedFiles) ? collect.skippedFiles.slice(0, 8) : undefined, - anchor: observeRecord(collect.anchor), - window: observeRecord(collect.window), - counts: observeRecord(collect.counts), - summary: observeRecord(collect.summary), - ...(rows === undefined ? {} : { rows }), - ...(timelineRows === undefined ? {} : { timelineRows }), - renderedText: collectView === "timeline" || collectView === "workbench-triad" ? undefined : typeof collect.renderedText === "string" ? collect.renderedText : undefined, - sourceFiles: Array.isArray(collect.sourceFiles) ? collect.sourceFiles : undefined, - blocker: collect.blocker, - sampleSeq: collect.sampleSeq, - traceId: collect.traceId, - finalResponse: collect.finalResponse, - statusGap: observeRecord(collect.statusGap), - dom: observeRecord(collect.dom), - networkEvidence: collectView === "workbench-triad" ? slimNetworkEvidence(collect.networkEvidence) : observeRecord(collect.networkEvidence), - freeze: collectView === "workbench-triad" ? slimFreeze(collect.freeze) : observeRecord(collect.freeze), - profileHotspots: Array.isArray(collect.profileHotspots) ? collect.profileHotspots.slice(0, 8) : undefined, - profileStacks: Array.isArray(collect.profileStacks) ? collect.profileStacks.slice(0, 5) : undefined, - scriptHotspots: Array.isArray(collect.scriptHotspots) ? collect.scriptHotspots.slice(0, 8) : undefined, - longTasks: Array.isArray(collect.longTasks) ? collect.longTasks.slice(0, 8) : undefined, - longAnimationFrames: Array.isArray(collect.longAnimationFrames) ? collect.longAnimationFrames.slice(0, 8) : undefined, - eventLoopGaps: Array.isArray(collect.eventLoopGaps) ? collect.eventLoopGaps.slice(0, 8) : undefined, - captures: Array.isArray(collect.captures) ? collect.captures.slice(0, 8) : undefined, - findings: collectView === "workbench-triad" ? slimFindings : Array.isArray(collect.findings) ? collect.findings.slice(0, 8) : undefined, - valuesRedacted: true, - }; -} - -function observeRecord(value: unknown): Record { - return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record : {}; -} - export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec): Record | RenderedCliResult { const analyzerB64 = Buffer.from(nodeWebObserveAnalyzerSource(), "utf8").toString("base64"); const alertThresholds = nodeWebProbeAlertThresholds(spec);