diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index ecebc644..dd3df423 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -428,13 +428,21 @@ async function createSessionFromUi() { return Boolean(activeTab && sessionId && sessionId !== initial.sessionId && input && !input.disabled && !warning); }, { sessionId: before?.activeSessionId || "" }, { timeout: 30000 }).catch(() => null); const after = await workbenchSessionSnapshot(); + const beforeSessionId = before?.activeSessionId || before?.routeSessionId || ""; + const afterSessionId = after?.activeSessionId || after?.routeSessionId || ""; + const ok = Boolean(afterSessionId && afterSessionId !== beforeSessionId && after?.composerReady); + if (!ok) { + const error = new Error("newSession did not create or select a different workbench session"); + error.details = { beforeUrl, afterUrl: currentPageUrl(), before, after, pageId, valuesRedacted: true }; + throw error; + } return { beforeUrl, afterUrl: currentPageUrl(), - ok: Boolean(after?.activeSessionId && after.activeSessionId !== before?.activeSessionId), + ok, before, after, - sessionId: after?.activeSessionId || null, + sessionId: afterSessionId || null, pageId }; } @@ -702,6 +710,10 @@ async function samplePage(reason) { testId: element.getAttribute("data-testid"), role: element.getAttribute("role"), status: element.getAttribute("data-status") || element.getAttribute("aria-busy") || null, + sessionId: element.getAttribute("data-session-id") || null, + messageId: element.getAttribute("data-message-id") || element.getAttribute("id") || null, + traceId: element.getAttribute("data-trace-id") || null, + turnId: element.getAttribute("data-turn-id") || null, text: textHashInput(element), rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) }, }; @@ -747,16 +759,19 @@ async function samplePage(reason) { const turns = Array.from(document.querySelectorAll('article.message-card[data-role="agent"], .message-card[data-role="agent"], article[data-role="agent"]')).filter(visible).map((element, index) => { const rect = element.getBoundingClientRect(); const text = textHashInput(element); - const traceElement = element.querySelector("[data-trace-id]"); + const traceElement = element.matches("[data-trace-id]") ? element : element.querySelector("[data-trace-id]"); const traceMatch = text.match(/\btrc_[A-Za-z0-9_-]+\b/u); const durationText = trim(element.querySelector(".message-duration-meta")?.textContent || "", 120); const activityText = trim(element.querySelector(".message-activity-meta")?.textContent || "", 120); + const directTraceId = element.getAttribute("data-trace-id") || traceElement?.getAttribute("data-trace-id") || traceMatch?.[0] || null; return { index, role: element.getAttribute("data-role") || "agent", status: element.getAttribute("data-status") || null, + sessionId: element.getAttribute("data-session-id") || null, messageId: element.getAttribute("data-message-id") || element.getAttribute("id") || null, - traceId: traceElement?.getAttribute("data-trace-id") || traceMatch?.[0] || null, + traceId: directTraceId, + turnId: element.getAttribute("data-turn-id") || directTraceId, durationText, activityText, text, @@ -2059,14 +2074,19 @@ function turnMetricItems(sample, timelineItem) { const recentUpdateValues = texts.flatMap(parseRecentUpdateSeconds).filter(Number.isFinite); const traceId = turn.traceId || firstTraceId(texts); const messageId = turn.messageId || null; + const turnId = turn.turnId || traceId || null; + const stableId = traceId || messageId || turnId || null; const domIndex = Number.isFinite(Number(turn.index)) ? Number(turn.index) : items.length; - const key = "turn:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex); + const key = stableId + ? "turn:" + sessionKey + ":id-" + stableId + : "turn:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex); items.push({ key, source: "turn", promptIndex, traceId, messageId, + turnId, domIndex, status: turn.status ?? null, totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null, @@ -2083,12 +2103,18 @@ function turnMetricItems(sample, timelineItem) { const recentUpdateValues = parseRecentUpdateSeconds(text).filter(Number.isFinite); if (totalElapsedValues.length === 0 && recentUpdateValues.length === 0) continue; const domIndex = Number.isFinite(Number(message.index)) ? Number(message.index) : items.length; + const traceId = message.traceId || firstTraceId([text]); + const messageId = message.messageId || null; + const stableId = traceId || messageId || message.turnId || null; items.push({ - key: "message:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex), + key: stableId + ? "message:" + sessionKey + ":id-" + stableId + : "message:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex), source: "message", promptIndex, - traceId: firstTraceId([text]), - messageId: null, + traceId, + messageId, + turnId: message.turnId || traceId || null, domIndex, status: message.status ?? null, totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null,