// SPEC: PJ2026-01040111 long-running Workbench observation. // Responsibility: Analyzer sample metrics, loading continuity, session rail title, and bounded report rows source fragment. export function nodeWebObserveAnalyzerSampleMetricsSource(): string { return String.raw`function isTerminalTraceText(text) { return /轮次完成|轮次失败|轮次取消|已记录|已完成第\d+轮|final response|sealed final response|turn completed|turn failed|turn canceled|terminal result|\bcompleted\b|\bfailed\b|\bcanceled\b|\bcancelled\b|\bterminal\b|\bdone\b/iu.test(String(text || "")); } function isFinalResultText(text) { return /已完成第\d+轮|已按第\d+轮完成|final response|sealed final response|最终结果|已完成[::]|smoke\s*测试结果|benchmark|PVC\/workspace|修改文件|Results:/iu.test(String(text || "")); } function buildSampleMetrics(samples, control) { const promptCommands = buildSendPromptCommandTimeline(control); const promptTimes = promptCommands.map((item) => item.tsMs); const timeline = samples.map((sample) => { const texts = sampleTexts(sample); const timingTexts = sampleTurnTimingTexts(sample); const tsMs = Date.parse(sample.ts); const promptIndex = Number.isFinite(tsMs) ? latestPromptIndex(promptTimes, tsMs) : 0; const totalElapsedValues = timingTexts.flatMap(parseTotalElapsedSeconds).filter(Number.isFinite); const recentUpdateValues = timingTexts.flatMap(parseRecentUpdateSeconds).filter(Number.isFinite); const diagnosticTexts = texts.filter(isDiagnosticText).slice(0, 5); const terminalTexts = texts.filter(isTerminalTraceText).slice(0, 5); const finalResultTexts = texts.filter(isFinalResultText).slice(0, 5); const loadings = Array.isArray(sample.loadings) ? sample.loadings : []; const loadingOwners = uniqueLoadingOwners(loadings); return { seq: sample.seq ?? null, ts: sample.ts ?? null, routeSessionId: sample.routeSessionId ?? null, activeSessionId: sample.activeSessionId ?? null, promptIndex, messageCount: Array.isArray(sample.messages) ? sample.messages.length : 0, traceRowCount: Array.isArray(sample.traceRows) ? sample.traceRows.length : 0, loadingCount: loadings.length, loadingOwnerCount: loadingOwners.length, loadingOwners: loadingOwners.map((item) => ({ ownerKey: item.ownerKey, ownerKind: item.ownerKind, ownerLabel: item.ownerLabel, count: item.count })).slice(0, 12), sessionRailVisibleCount: Number(sample?.sessionRail?.visibleCount ?? 0), sessionRailFallbackTitleCount: Number(sample?.sessionRail?.fallbackTitleCount ?? 0), sessionRailFallbackTitleRatio: Number(sample?.sessionRail?.fallbackTitleRatio ?? 0), totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null, recentUpdateSeconds: recentUpdateValues.length > 0 ? Math.max(...recentUpdateValues) : null, terminalSeen: terminalTexts.length > 0, finalResultTextSeen: finalResultTexts.length > 0, diagnosticSeen: diagnosticTexts.length > 0, diagnosticTextHashes: diagnosticTexts.map(sha256).slice(0, 5), textDigest: digestSample(sample) }; }); const turnTiming = buildTurnTimingTable(samples, timeline); const traceOrder = buildTraceOrderMetrics(samples, timeline); const codeAgentCardTiming = buildCodeAgentCardTimingMetrics(samples, timeline, turnTiming); const codeAgentCardDurationUnderreported = buildCodeAgentCardDurationUnderreportedMetrics(samples, timeline); const codeAgentCardDurationMismatches = buildCodeAgentCardDurationMismatchMetrics(samples, timeline); if (codeAgentCardTiming && codeAgentCardTiming.summary) { codeAgentCardTiming.summary.durationUnderreportedCount = codeAgentCardDurationUnderreported.length; codeAgentCardTiming.summary.durationMismatchCount = codeAgentCardDurationMismatches.length; codeAgentCardTiming.durationUnderreported = codeAgentCardDurationUnderreported; codeAgentCardTiming.durationMismatches = codeAgentCardDurationMismatches; } const turnCells = turnTiming.rows.flatMap((row) => Object.values(row.cells || {})); const turnTimingNonMonotonic = Array.isArray(turnTiming.nonMonotonic) ? turnTiming.nonMonotonic : []; const turnTimingElapsedZeroResets = Array.isArray(turnTiming.elapsedZeroResets) ? turnTiming.elapsedZeroResets : []; const turnTimingTotalElapsedForwardJumps = Array.isArray(turnTiming.totalElapsedForwardJumps) ? turnTiming.totalElapsedForwardJumps : []; const turnTimingRecentUpdateSawtoothJumps = turnTimingNonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump"); const turnTimingTerminalElapsedGrowth = Array.isArray(turnTiming.terminalElapsedGrowth) ? turnTiming.terminalElapsedGrowth : []; const turnTimingRecentUpdateResets = Array.isArray(turnTiming.recentUpdateResets) ? turnTiming.recentUpdateResets : []; const turnTimingRecentUpdateSteps = Array.isArray(turnTiming.recentUpdateSteps) ? turnTiming.recentUpdateSteps : []; const turnTimingTerminalElapsedGrowthDeltas = turnTimingTerminalElapsedGrowth .map((item) => Number(item.delta)) .filter((value) => Number.isFinite(value) && value > 0); const turnTimingRecentUpdateLargestSteps = turnTimingRecentUpdateSteps .filter((item) => Number.isFinite(Number(item.delta))) .slice() .sort((a, b) => Number(b.delta) - Number(a.delta)) .slice(0, 200); const turnTimingRecentUpdatePositiveSteps = turnTimingRecentUpdateSteps .map((item) => Number(item.delta)) .filter((value) => Number.isFinite(value) && value >= 0); const turnTimingRecentUpdateExcessSteps = turnTimingRecentUpdateSteps .map((item) => Number(item.excessiveIncreaseSeconds)) .filter((value) => Number.isFinite(value) && value > 0); const withTotal = timeline.filter((item) => item.totalElapsedSeconds !== null).length; const withRecent = timeline.filter((item) => item.recentUpdateSeconds !== null).length; const diagnostics = timeline.filter((item) => item.diagnosticSeen).length; const loading = buildLoadingMetrics(samples, timeline); const sessionRailTitles = buildSessionRailTitleMetrics(samples, timeline); const workbenchTurnStateTriad = buildWorkbenchTurnStateTriadMetrics(samples, timeline); const reportTurnTimingRows = boundedTurnTimingRowsForReport(turnTiming.rows); const reportTimeline = boundedRowsForReport(timeline); const rounds = buildRoundMetricSummaries(timeline, promptCommands, { columns: turnTiming.columns, rows: turnTiming.rows, nonMonotonic: turnTimingNonMonotonic, elapsedZeroResets: turnTimingElapsedZeroResets, totalElapsedForwardJumps: turnTimingTotalElapsedForwardJumps, terminalElapsedGrowth: turnTimingTerminalElapsedGrowth, recentUpdateResets: turnTimingRecentUpdateResets, recentUpdateSteps: turnTimingRecentUpdateSteps }); const recentUpdateJumpCount = turnTimingRecentUpdateSawtoothJumps.length; return { summary: { sampleCount: timeline.length, withTotalElapsed: withTotal, withRecentUpdate: withRecent, diagnostics, loadingSampleCount: loading.summary.loadingSampleCount, loadingMaxCount: loading.summary.maxSimultaneousCount, loadingMaxOwnerCount: loading.summary.maxSimultaneousOwnerCount, loadingOwnerCount: loading.summary.ownerCount, loadingConcurrentSampleCount: loading.summary.concurrentLoadingSampleCount, loadingLongestContinuousSeconds: loading.summary.longestContinuousSeconds, loadingCurrentContinuousSeconds: loading.summary.currentContinuousSeconds, loadingOverFiveSecondSegmentCount: loading.summary.overFiveSecondSegmentCount, loadingOverBudgetSegmentCount: loading.summary.overBudgetSegmentCount, sessionRailSampleCount: sessionRailTitles.summary.sampleCount, sessionRailVisibleSampleCount: sessionRailTitles.summary.visibleSampleCount, sessionRailFallbackMajoritySampleCount: sessionRailTitles.summary.majorityFallbackSampleCount, sessionRailFallbackMaxRatio: sessionRailTitles.summary.maxFallbackRatio, sessionRailFallbackMaxVisibleCount: sessionRailTitles.summary.maxVisibleCount, sessionRailFallbackMaxCount: sessionRailTitles.summary.maxFallbackTitleCount, workbenchTurnStateTriadRows: workbenchTurnStateTriad.summary.rowCount, workbenchTurnStateTriadInvalidRows: workbenchTurnStateTriad.summary.invalidRowCount, workbenchTurnStateTriadFullInvalidRows: workbenchTurnStateTriad.summary.invalidFullTriadCount, workbenchTurnStateCardFinalResponseMismatchRows: workbenchTurnStateTriad.summary.cardFinalResponseMismatchCount, workbenchTurnStateCollectorMissingRows: workbenchTurnStateTriad.summary.collectorMissingRowCount, promptSegments: Math.max(0, promptTimes.length), rounds: rounds.length, turnColumns: turnTiming.columns.length, turnTimingRows: turnTiming.rows.length, turnCellsWithTotalElapsed: turnCells.filter((item) => item.totalElapsedSeconds !== null).length, turnCellsWithRecentUpdate: turnCells.filter((item) => item.recentUpdateSeconds !== null).length, turnTimingNonMonotonicCount: turnTimingNonMonotonic.length, turnTimingTotalElapsedDecreaseCount: turnTimingNonMonotonic.filter((item) => item.metric === "totalElapsedSeconds").length, turnTimingTotalElapsedZeroResetCount: turnTimingElapsedZeroResets.length, turnTimingTotalElapsedForwardJumpCount: turnTimingTotalElapsedForwardJumps.length, turnTimingTotalElapsedForwardJumpMaxSeconds: maxPositiveDelta(turnTimingTotalElapsedForwardJumps), turnTimingTerminalElapsedGrowthCount: turnTimingTerminalElapsedGrowth.length, turnTimingTerminalElapsedGrowthMaxSeconds: turnTimingTerminalElapsedGrowthDeltas.length > 0 ? Math.max(...turnTimingTerminalElapsedGrowthDeltas) : 0, turnTimingRecentUpdateJumpCount: recentUpdateJumpCount, turnTimingRecentUpdateSawtoothJumpCount: recentUpdateJumpCount, turnTimingRecentUpdateStepCount: turnTimingRecentUpdateSteps.length, turnTimingRecentUpdateMaxIncreaseSeconds: turnTimingRecentUpdatePositiveSteps.length > 0 ? Math.max(...turnTimingRecentUpdatePositiveSteps) : null, turnTimingRecentUpdateMaxExcessSeconds: turnTimingRecentUpdateExcessSteps.length > 0 ? Math.max(...turnTimingRecentUpdateExcessSteps) : 0, turnTimingRecentUpdateResetCount: turnTimingRecentUpdateResets.length, turnTimingRecentUpdateDecreaseCount: turnTimingRecentUpdateResets.length, codeAgentCardSampleCount: codeAgentCardTiming.summary.cardSampleCount, codeAgentCardMissingElapsedCount: codeAgentCardTiming.summary.missingElapsedCount, codeAgentCardMissingRecentUpdateCount: codeAgentCardTiming.summary.missingRecentUpdateCount, roundCompletionEventCount: codeAgentCardTiming.summary.roundCompletionEventCount, roundCompletionElapsedMismatchCount: codeAgentCardTiming.summary.roundCompletionElapsedMismatchCount, roundCompletionFinalResponseMissingCount: codeAgentCardTiming.summary.roundCompletionFinalResponseMissingCount, roundCompletionPostTimingChangeCount: codeAgentCardTiming.summary.roundCompletionPostTimingChangeCount, codeAgentCardDurationUnderreportedCount: codeAgentCardTiming.summary.durationUnderreportedCount, codeAgentCardDurationMismatchCount: codeAgentCardTiming.summary.durationMismatchCount, traceRowCount: traceOrder.summary.traceRowCount, traceRowOrderAnomalyCount: traceOrder.summary.orderAnomalyCount, traceRowCompletionNotLastCount: traceOrder.summary.completionNotLastCount, roundsWithTurnTimingNonMonotonic: rounds.filter((item) => item.turnTimingNonMonotonicCount > 0).length, roundsWithTurnTimingTotalElapsedForwardJumps: rounds.filter((item) => item.turnTimingTotalElapsedForwardJumpCount > 0).length, roundsWithTerminalElapsedGrowth: rounds.filter((item) => item.turnTimingTerminalElapsedGrowthCount > 0).length, roundsWithRecentUpdateJumps: rounds.filter((item) => item.turnTimingRecentUpdateJumpCount > 0).length }, loading, sessionRailTitles, workbenchTurnStateTriad, codeAgentCardTiming, traceOrder, rounds, turnColumns: turnTiming.columns, turnTimingTable: reportTurnTimingRows.rows, turnTimingTableDisclosure: reportTurnTimingRows.disclosure, turnTimingNonMonotonic, turnTimingElapsedZeroResets, turnTimingTotalElapsedForwardJumps, turnTimingTerminalElapsedGrowth, turnTimingRecentUpdateSawtoothJumps, turnTimingRecentUpdateSteps, turnTimingRecentUpdateLargestSteps, turnTimingRecentUpdateResets, timeline: reportTimeline.rows, timelineDisclosure: reportTimeline.disclosure }; } function boundedRowsForReport(rows) { const sourceRows = Array.isArray(rows) ? rows : []; const maxRows = 1200; const headRows = 120; if (sourceRows.length <= maxRows) return { rows: sourceRows, disclosure: { truncated: false, totalRows: sourceRows.length, includedRows: sourceRows.length, omittedRows: 0, headRows: sourceRows.length, tailRows: 0 } }; const tailRows = Math.max(0, maxRows - headRows); return { rows: [...sourceRows.slice(0, headRows), ...sourceRows.slice(-tailRows)], disclosure: { truncated: true, totalRows: sourceRows.length, includedRows: maxRows, omittedRows: Math.max(0, sourceRows.length - maxRows), headRows, tailRows, policy: "report-bounded-head-tail; summary metrics are computed before truncation", valuesRedacted: true } }; } function buildSendPromptCommandTimeline(control) { const byCommand = new Map(); let ordinal = 0; for (const item of Array.isArray(control) ? control : []) { if (item?.type !== "sendPrompt") continue; const commandId = item.commandId ? String(item.commandId) : "sendPrompt-" + String(ordinal++); const existing = byCommand.get(commandId) || { commandId, startedAt: null, startedTsMs: null, completedAt: null, completedTsMs: null, failedAt: null, failedTsMs: null, textHash: null, textBytes: null, }; if (!existing.textHash && item.input?.textHash) existing.textHash = item.input.textHash; if (!existing.textBytes && item.input?.textBytes) existing.textBytes = item.input.textBytes; const tsMs = Date.parse(item.ts); if (item.phase === "started" && Number.isFinite(tsMs)) { existing.startedAt = item.ts ?? existing.startedAt; existing.startedTsMs = tsMs; } else if (item.phase === "completed" && Number.isFinite(tsMs)) { existing.completedAt = item.ts ?? existing.completedAt; existing.completedTsMs = tsMs; } else if (item.phase === "failed" && Number.isFinite(tsMs)) { existing.failedAt = item.ts ?? existing.failedAt; existing.failedTsMs = tsMs; } byCommand.set(commandId, existing); } return Array.from(byCommand.values()) .map((item) => { const tsMs = Number.isFinite(item.startedTsMs) ? item.startedTsMs : Number.isFinite(item.completedTsMs) ? item.completedTsMs : item.failedTsMs; const ts = Number.isFinite(item.startedTsMs) ? item.startedAt : Number.isFinite(item.completedTsMs) ? item.completedAt : item.failedAt; return { ...item, ts, tsMs }; }) .filter((item) => Number.isFinite(item.tsMs)) .sort((a, b) => a.tsMs - b.tsMs) .map((item, index) => ({ ...item, promptIndex: index + 1 })); } function buildSessionRailTitleMetrics(samples, timeline) { const rows = []; const examplesByHash = new Map(); for (let index = 0; index < (Array.isArray(samples) ? samples : []).length; index += 1) { const sample = samples[index]; const rail = sample?.sessionRail && typeof sample.sessionRail === "object" ? sample.sessionRail : null; if (!rail) continue; const visibleCount = Number(rail.visibleCount ?? 0); const fallbackTitleCount = Number(rail.fallbackTitleCount ?? 0); const safeVisibleCount = Number.isFinite(visibleCount) && visibleCount > 0 ? visibleCount : 0; const safeFallbackTitleCount = Number.isFinite(fallbackTitleCount) && fallbackTitleCount > 0 ? fallbackTitleCount : 0; const fallbackTitleRatio = safeVisibleCount > 0 ? Number((safeFallbackTitleCount / safeVisibleCount).toFixed(4)) : 0; const fallbackItems = Array.isArray(rail.fallbackItems) ? rail.fallbackItems : []; for (const item of fallbackItems) { const hash = String(item?.titleHash || item?.titlePreview || item?.sessionIdPrefix || "").trim(); if (!hash || examplesByHash.has(hash)) continue; examplesByHash.set(hash, { titleHash: item?.titleHash ?? null, titlePreview: limitText(String(item?.titlePreview || ""), 160), sessionIdPrefix: item?.sessionIdPrefix ?? null, active: item?.active === true, firstSeq: sample?.seq ?? null, firstAt: sample?.ts ?? null, pageRole: sample?.pageRole ?? null, }); } rows.push({ ...ref(sample), promptIndex: timeline[index]?.promptIndex ?? 0, visibleCount: safeVisibleCount, fallbackTitleCount: safeFallbackTitleCount, fallbackTitleRatio, majorityFallback: safeVisibleCount > 0 && safeFallbackTitleCount > safeVisibleCount / 2, overThreshold: safeVisibleCount > 0 && fallbackTitleRatio > alertThresholds.sessionRailFallbackRatio, examples: fallbackItems.slice(0, 5).map((item) => ({ titleHash: item?.titleHash ?? null, titlePreview: limitText(String(item?.titlePreview || ""), 160), sessionIdPrefix: item?.sessionIdPrefix ?? null, active: item?.active === true, })), }); } const visibleRows = rows.filter((item) => item.visibleCount > 0); const majorityRows = rows.filter((item) => item.majorityFallback); const overThresholdRows = rows.filter((item) => item.overThreshold); const fallbackRows = rows.filter((item) => item.fallbackTitleCount > 0); const maxFallbackRatio = rows.length > 0 ? Math.max(...rows.map((item) => Number(item.fallbackTitleRatio) || 0)) : 0; const maxVisibleCount = rows.length > 0 ? Math.max(...rows.map((item) => Number(item.visibleCount) || 0)) : 0; const maxFallbackTitleCount = rows.length > 0 ? Math.max(...rows.map((item) => Number(item.fallbackTitleCount) || 0)) : 0; return { summary: { sampleCount: rows.length, visibleSampleCount: visibleRows.length, fallbackSampleCount: fallbackRows.length, majorityFallbackSampleCount: majorityRows.length, overThresholdSampleCount: overThresholdRows.length, thresholdRatio: alertThresholds.sessionRailFallbackRatio, maxFallbackRatio, maxVisibleCount, maxFallbackTitleCount, }, samples: majorityRows.slice(0, 80), examples: Array.from(examplesByHash.values()).slice(0, 80), timeline: rows.slice(-200), valuesRedacted: true }; } function uniqueLoadingOwners(loadings) { const groups = new Map(); for (let index = 0; index < (Array.isArray(loadings) ? loadings : []).length; index += 1) { const item = loadings[index]; const ownerKey = loadingOwnerKey(item, index); const ownerIdentity = loadingOwnerIdentity(item); const existing = groups.get(ownerKey) || { ownerKey, ownerKind: item?.ownerKind ?? "unknown", ownerLabel: loadingOwnerLabel(item, ownerKey), ...ownerIdentity, count: 0, textHashes: [] }; existing.count += 1; if (item?.textHash && !existing.textHashes.includes(item.textHash)) existing.textHashes.push(item.textHash); for (const key of ["ownerSessionId", "ownerMessageId", "ownerTraceId"]) { if (!existing[key] && ownerIdentity[key]) existing[key] = ownerIdentity[key]; } groups.set(ownerKey, existing); } return Array.from(groups.values()).sort((a, b) => b.count - a.count || String(a.ownerLabel).localeCompare(String(b.ownerLabel))); } function loadingOwnerIdentity(item) { const owner = item?.owner && typeof item.owner === "object" ? item.owner : {}; return { ownerSessionId: owner.sessionId ?? null, ownerMessageId: owner.messageId ?? null, ownerTraceId: owner.traceId ?? null, }; } function loadingOwnerKey(item, index = 0) { const key = String(item?.ownerKey || "").trim(); if (key) return key.slice(0, 240); const owner = item?.owner && typeof item.owner === "object" ? item.owner : {}; return [ item?.ownerKind || "unknown", owner.testId || item?.testId || owner.id || owner.role || owner.className || item?.role || item?.tag || "node", owner.sessionId || owner.messageId || owner.traceId || item?.textHash || String(index) ].filter(Boolean).join(":").slice(0, 240); } function loadingOwnerLabel(item, fallback) { return limitText(String(item?.ownerLabel || item?.owner?.ariaLabel || item?.owner?.testId || item?.owner?.className || fallback || "unknown"), 160); } function buildLoadingMetrics(samples, timeline) { const events = samples.map((sample, index) => { const tsMs = Date.parse(sample?.ts); const loadings = Array.isArray(sample?.loadings) ? sample.loadings : []; const owners = uniqueLoadingOwners(loadings); return { seq: sample?.seq ?? null, ts: sample?.ts ?? null, tsMs, promptIndex: timeline[index]?.promptIndex ?? 0, routeSessionId: sample?.routeSessionId ?? null, activeSessionId: sample?.activeSessionId ?? null, loadingCount: loadings.length, ownerCount: owners.length, owners, ownerKeys: owners.map((item) => item.ownerKey), ownerLabels: owners.map((item) => item.ownerLabel).slice(0, 8) }; }).filter((item) => Number.isFinite(item.tsMs)); const continuityThresholdMs = loadingContinuityThresholdMs(events); const segments = buildLoadingSegments(events, continuityThresholdMs, (event) => event.loadingCount, (event) => event.owners) .sort((a, b) => Number(b.durationSeconds ?? 0) - Number(a.durationSeconds ?? 0) || Number(b.maxCount ?? 0) - Number(a.maxCount ?? 0)); const ownerMap = new Map(); for (const event of events) { for (const owner of event.owners) { const existing = ownerMap.get(owner.ownerKey) || { ownerKey: owner.ownerKey, ownerKind: owner.ownerKind, ownerLabel: owner.ownerLabel, ownerSessionId: owner.ownerSessionId ?? null, ownerMessageId: owner.ownerMessageId ?? null, ownerTraceId: owner.ownerTraceId ?? null, sampleCount: 0, occurrenceCount: 0, maxSimultaneousCount: 0, firstAt: event.ts, lastAt: event.ts, firstSeq: event.seq, lastSeq: event.seq, promptIndexes: new Set(), events: [] }; existing.sampleCount += 1; existing.occurrenceCount += owner.count; existing.maxSimultaneousCount = Math.max(existing.maxSimultaneousCount, owner.count); existing.lastAt = event.ts; existing.lastSeq = event.seq; for (const key of ["ownerSessionId", "ownerMessageId", "ownerTraceId"]) { if (!existing[key] && owner[key]) existing[key] = owner[key]; } if (Number.isFinite(Number(event.promptIndex))) existing.promptIndexes.add(Number(event.promptIndex)); existing.events.push({ ...event, loadingCount: owner.count, owners: [owner] }); ownerMap.set(owner.ownerKey, existing); } } const owners = Array.from(ownerMap.values()).map((owner) => { const ownerSegments = buildLoadingSegments(owner.events, continuityThresholdMs, (event) => event.loadingCount, (event) => event.owners); const longest = ownerSegments.reduce((max, item) => Math.max(max, Number(item.durationSeconds ?? 0)), 0); return { ownerKey: owner.ownerKey, ownerKind: owner.ownerKind, ownerLabel: owner.ownerLabel, ownerSessionId: owner.ownerSessionId ?? null, ownerMessageId: owner.ownerMessageId ?? null, ownerTraceId: owner.ownerTraceId ?? null, sampleCount: owner.sampleCount, occurrenceCount: owner.occurrenceCount, maxSimultaneousCount: owner.maxSimultaneousCount, longestContinuousSeconds: longest, firstAt: owner.firstAt, lastAt: owner.lastAt, firstSeq: owner.firstSeq, lastSeq: owner.lastSeq, promptIndexes: Array.from(owner.promptIndexes).sort((a, b) => a - b), segments: ownerSegments.sort((a, b) => Number(b.durationSeconds ?? 0) - Number(a.durationSeconds ?? 0)).slice(0, 8), valuesRedacted: true }; }).sort((a, b) => Number(b.longestContinuousSeconds ?? 0) - Number(a.longestContinuousSeconds ?? 0) || Number(b.occurrenceCount ?? 0) - Number(a.occurrenceCount ?? 0)); const latest = events[events.length - 1] || null; const currentSegment = latest && latest.loadingCount > 0 ? segments.find((segment) => segment.ongoing === true && segment.lastSeq === latest.seq) || null : null; const timelineRows = events .filter((event, index) => event.loadingCount > 0 || (index > 0 && events[index - 1]?.loadingCount > 0)) .slice(0, 500) .map((event) => ({ seq: event.seq, ts: event.ts, promptIndex: event.promptIndex, loadingCount: event.loadingCount, ownerCount: event.ownerCount, owners: event.owners.map((owner) => ({ ownerKind: owner.ownerKind, ownerLabel: owner.ownerLabel, ownerSessionId: owner.ownerSessionId ?? null, ownerMessageId: owner.ownerMessageId ?? null, ownerTraceId: owner.ownerTraceId ?? null, count: owner.count })).slice(0, 8) })); return { summary: { sampleCount: events.length, loadingSampleCount: events.filter((event) => event.loadingCount > 0).length, maxSimultaneousCount: events.reduce((max, event) => Math.max(max, event.loadingCount), 0), maxSimultaneousOwnerCount: events.reduce((max, event) => Math.max(max, event.ownerCount), 0), concurrentLoadingSampleCount: events.filter((event) => event.loadingCount > 1).length, ownerCount: owners.length, segmentCount: segments.length, overFiveSecondSegmentCount: segments.filter((segment) => Number(segment.durationSeconds ?? 0) > 5).length, overBudgetSegmentCount: segments.filter((segment) => Number(segment.durationSeconds ?? 0) > alertThresholds.visibleLoadingSlowMs / 1000).length, budgetSeconds: alertThresholds.visibleLoadingSlowMs / 1000, longestContinuousSeconds: segments.length > 0 ? Number(segments[0].durationSeconds ?? 0) : 0, currentContinuousSeconds: currentSegment ? Number(currentSegment.durationSeconds ?? 0) : 0, continuityThresholdMs, latestLoadingCount: latest?.loadingCount ?? 0, latestOwnerCount: latest?.ownerCount ?? 0, valuesRedacted: true }, segments: segments.slice(0, 80), owners: owners.slice(0, 80), timeline: timelineRows, valuesRedacted: true }; } function loadingContinuityThresholdMs(events) { const deltas = []; for (let index = 1; index < events.length; index += 1) { const delta = events[index].tsMs - events[index - 1].tsMs; if (Number.isFinite(delta) && delta > 0) deltas.push(delta); } if (deltas.length === 0) return 5000; const sorted = deltas.slice().sort((a, b) => a - b); const median = sorted[Math.floor(sorted.length / 2)]; return Math.min(15000, Math.max(1500, Math.round(median * 2.5))); } function buildLoadingSegments(events, continuityThresholdMs, countForEvent, ownersForEvent) { const segments = []; let segment = null; let previousTsMs = null; for (const event of events) { const count = Number(countForEvent(event) ?? 0); const gapOk = previousTsMs === null || !Number.isFinite(event.tsMs) || event.tsMs - previousTsMs <= continuityThresholdMs; if (count > 0) { if (!segment || !gapOk) { if (segment) segments.push(finalizeLoadingSegment(segment, null)); segment = { firstAt: event.ts, lastAt: event.ts, firstSeq: event.seq, lastSeq: event.seq, promptIndexes: new Set(), ownerKeys: new Set(), ownerLabels: new Map(), sampleCount: 0, maxCount: 0, ongoing: true }; } segment.lastAt = event.ts; segment.lastSeq = event.seq; segment.sampleCount += 1; segment.maxCount = Math.max(segment.maxCount, count); if (Number.isFinite(Number(event.promptIndex))) segment.promptIndexes.add(Number(event.promptIndex)); for (const owner of ownersForEvent(event) || []) { if (!owner?.ownerKey) continue; segment.ownerKeys.add(owner.ownerKey); if (!segment.ownerLabels.has(owner.ownerKey)) segment.ownerLabels.set(owner.ownerKey, { ownerKey: owner.ownerKey, ownerKind: owner.ownerKind, ownerLabel: owner.ownerLabel, count: 0 }); const label = segment.ownerLabels.get(owner.ownerKey); label.count += owner.count ?? 1; } } else if (segment) { segment.ongoing = false; segment.endedAt = event.ts; segment.endSeq = event.seq; segments.push(finalizeLoadingSegment(segment, event)); segment = null; } previousTsMs = event.tsMs; } if (segment) segments.push(finalizeLoadingSegment(segment, null)); return segments; } function finalizeLoadingSegment(segment, absentEvent) { const startMs = Date.parse(segment.firstAt || ""); const lastMs = Date.parse(segment.lastAt || ""); const absentMs = Date.parse(absentEvent?.ts || ""); const durationSeconds = Number.isFinite(startMs) && Number.isFinite(lastMs) && lastMs >= startMs ? Number(((lastMs - startMs) / 1000).toFixed(3)) : 0; const upperBoundSeconds = Number.isFinite(startMs) && Number.isFinite(absentMs) && absentMs >= startMs ? Number(((absentMs - startMs) / 1000).toFixed(3)) : durationSeconds; const endedGapSeconds = Number.isFinite(lastMs) && Number.isFinite(absentMs) && absentMs >= lastMs ? Number(((absentMs - lastMs) / 1000).toFixed(3)) : null; return { firstAt: segment.firstAt, lastAt: segment.lastAt, endedAt: absentEvent?.ts ?? null, firstSeq: segment.firstSeq, lastSeq: segment.lastSeq, endSeq: absentEvent?.seq ?? null, durationSeconds, upperBoundSeconds, endedGapSeconds, sampleCount: segment.sampleCount, maxCount: segment.maxCount, ownerCount: segment.ownerKeys.size, owners: Array.from(segment.ownerLabels.values()).sort((a, b) => b.count - a.count || String(a.ownerLabel).localeCompare(String(b.ownerLabel))).slice(0, 12), promptIndexes: Array.from(segment.promptIndexes).sort((a, b) => a - b), ongoing: absentEvent ? false : segment.ongoing === true, valuesRedacted: true }; } function boundedTurnTimingRowsForReport(rows) { const sourceRows = Array.isArray(rows) ? rows : []; const maxRows = 1200; const headRows = 120; if (sourceRows.length <= maxRows) return { rows: sourceRows, disclosure: { truncated: false, totalRows: sourceRows.length, includedRows: sourceRows.length, omittedRows: 0, headRows: sourceRows.length, tailRows: 0 } }; const tailRows = Math.max(0, maxRows - headRows); return { rows: [...sourceRows.slice(0, headRows), ...sourceRows.slice(-tailRows)], disclosure: { truncated: true, totalRows: sourceRows.length, includedRows: maxRows, omittedRows: Math.max(0, sourceRows.length - maxRows), headRows, tailRows, policy: "report-bounded-head-tail; full anomaly counters are computed before truncation", valuesRedacted: true } }; } `; }