From 46d1e52521a6efa731109a101ff9a428efc04b25 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 27 Jun 2026 08:23:38 +0000 Subject: [PATCH] fix: require persistent workbench trace hydration stalls --- .../hwlab-node-web-observe-analyzer-source.ts | 65 +++++++++++++++---- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/scripts/src/hwlab-node-web-observe-analyzer-source.ts b/scripts/src/hwlab-node-web-observe-analyzer-source.ts index 5d27c4a3..3a106ef3 100644 --- a/scripts/src/hwlab-node-web-observe-analyzer-source.ts +++ b/scripts/src/hwlab-node-web-observe-analyzer-source.ts @@ -2234,26 +2234,65 @@ function detectWorkbenchInPlaceProjectionLag(samples, network) { } function detectWorkbenchTerminalTraceMissing(samples) { + const budgetMs = Number.isFinite(Number(alertThresholds.sameOriginApiSlowMs)) ? Number(alertThresholds.sameOriginApiSlowMs) : 10_000; const rows = []; - for (const sample of Array.isArray(samples) ? samples : []) { + const open = new Map(); + const sortedSamples = (Array.isArray(samples) ? samples : []) + .filter(isWorkbenchPathSample) + .slice() + .sort((a, b) => Date.parse(a?.ts || "") - Date.parse(b?.ts || "")); + const closeSegment = (key, lastSample = null) => { + const segment = open.get(key); + if (!segment) return; + open.delete(key); + const firstMs = Date.parse(segment.first.ts || ""); + const lastMs = Date.parse((lastSample ?? segment.last).ts || ""); + const observedMs = Number.isFinite(firstMs) && Number.isFinite(lastMs) && lastMs >= firstMs ? Math.round(lastMs - firstMs) : 0; + if (observedMs <= budgetMs) return; + rows.push({ + ...ref(segment.first), + lastSeq: segment.last.seq ?? null, + lastAt: segment.last.ts ?? null, + traceId: segment.traceId, + messageCount: Array.isArray(segment.last.messages) ? segment.last.messages.length : 0, + turnCount: Array.isArray(segment.last.turns) ? segment.last.turns.length : 0, + traceRowCount: 0, + sampleCount: segment.sampleCount, + observedMs, + budgetMs, + finalMessageVisible: workbenchFinalMessageVisible(segment.last, segment.traceId), + terminalTurnVisible: workbenchTerminalTurnVisible(segment.last, segment.traceId), + detail: "terminal turn/message stayed visible for this trace while the same in-place Workbench page had zero trace rows beyond the configured budget", + valuesRedacted: true + }); + }; + for (const sample of sortedSamples) { if (!isWorkbenchPathSample(sample)) continue; + const tsMs = Date.parse(sample?.ts || ""); + if (!Number.isFinite(tsMs)) continue; const terminalTraceIds = workbenchTerminalTraceIdsFromDom(sample); + const presentKeys = new Set(); for (const traceId of terminalTraceIds) { + const key = [samplePageKey(sample), sample?.routeSessionId ?? "", sample?.activeSessionId ?? "", traceId].join("|"); + presentKeys.add(key); const traceRows = workbenchTraceRowsForTrace(sample, traceId); - if (traceRows.length > 0) continue; - rows.push({ - ...ref(sample), - traceId, - messageCount: Array.isArray(sample.messages) ? sample.messages.length : 0, - turnCount: Array.isArray(sample.turns) ? sample.turns.length : 0, - traceRowCount: 0, - finalMessageVisible: workbenchFinalMessageVisible(sample, traceId), - terminalTurnVisible: workbenchTerminalTurnVisible(sample, traceId), - detail: "terminal turn/message was visible for this trace, but the same in-place Workbench sample still had zero trace rows", - valuesRedacted: true - }); + if (traceRows.length > 0) { + closeSegment(key, sample); + continue; + } + const existing = open.get(key); + if (existing) { + existing.last = sample; + existing.sampleCount += 1; + } else { + open.set(key, { first: sample, last: sample, traceId, sampleCount: 1 }); + } + } + for (const [key, segment] of Array.from(open.entries())) { + if (samplePageKey(sample) === samplePageKey(segment.first) && !presentKeys.has(key)) closeSegment(key, sample); } } + for (const key of Array.from(open.keys())) closeSegment(key); return rows; }