// SPEC: PJ2026-01040111 long-running Workbench observation. // Responsibility: Timing, trace-order, Code Agent card, and report-rendering source for the offline web-probe observe analyzer. export function nodeWebObserveAnalyzerTimingSource(): string { return String.raw`function buildTurnTimingTable(samples, timeline) { const columns = []; const registry = new Map(); const promptAssignmentByKey = new Map(); const rows = []; for (let index = 0; index < samples.length; index += 1) { const sample = samples[index]; const timelineItem = timeline[index] || {}; const cells = {}; const rawMetrics = turnMetricItems(sample, timelineItem); const domIndexes = rawMetrics.map((item) => Number(item.domIndex)).filter(Number.isFinite); const maxDomIndex = domIndexes.length > 0 ? Math.max(...domIndexes) : null; for (const rawMetric of rawMetrics) { const scopedKey = turnTimingScopedMetricKey(rawMetric, sample); const samplePromptIndex = Number(timelineItem.promptIndex ?? 0); const evidencePromptIndex = inferTurnMetricPromptIndex(rawMetric, samplePromptIndex, maxDomIndex); if (evidencePromptIndex !== null) { const existingPromptIndex = promptAssignmentByKey.get(scopedKey); if (existingPromptIndex === undefined || evidencePromptIndex > existingPromptIndex) promptAssignmentByKey.set(scopedKey, evidencePromptIndex); } const assignedPromptIndex = promptAssignmentByKey.get(scopedKey) ?? null; const metric = { ...rawMetric, key: scopedKey, baseKey: rawMetric.key, promptIndex: assignedPromptIndex, samplePromptIndex, pageEpoch: Number(sample?.pageEpoch ?? rawMetric.pageEpoch ?? 0) || 0 }; let column = registry.get(metric.key); if (!column) { column = { id: "T" + String(columns.length + 1), label: "T" + String(columns.length + 1), keyHash: sha256(metric.key), source: metric.source, firstSeq: sample.seq ?? null, firstTs: sample.ts ?? null, lastSeq: sample.seq ?? null, lastTs: sample.ts ?? null, promptIndex: metric.promptIndex ?? null, lastPromptIndex: metric.promptIndex ?? null, traceId: metric.traceId ?? null, messageId: metric.messageId ?? null, domIndex: metric.domIndex ?? null, pageRole: metric.pageRole ?? sample.pageRole ?? null, pageId: metric.pageId ?? sample.pageId ?? null, pageEpoch: metric.pageEpoch ?? null }; registry.set(metric.key, column); columns.push(column); } else { column.lastSeq = sample.seq ?? null; column.lastTs = sample.ts ?? null; if (column.source !== "turn" && metric.source === "turn") column.source = "turn"; if (metric.promptIndex && column.promptIndex !== metric.promptIndex) column.promptIndex = metric.promptIndex; column.lastPromptIndex = metric.promptIndex ?? column.lastPromptIndex ?? null; if (!column.traceId && metric.traceId) column.traceId = metric.traceId; if (!column.messageId && metric.messageId) column.messageId = metric.messageId; } cells[column.id] = { totalElapsedSeconds: metric.totalElapsedSeconds, recentUpdateSeconds: metric.recentUpdateSeconds, status: metric.status ?? null, promptIndex: metric.promptIndex ?? null, source: metric.source, pageRole: metric.pageRole ?? sample.pageRole ?? null, pageId: metric.pageId ?? sample.pageId ?? null, pageEpoch: metric.pageEpoch ?? null, sampleGroupSeq: sample.sampleGroupSeq ?? null, traceId: metric.traceId ?? null, messageId: metric.messageId ?? null, textHash: metric.textHash ?? null, samplePromptIndex: metric.samplePromptIndex ?? null }; } rows.push({ ts: sample.ts ?? null, seq: sample.seq ?? null, sampleGroupSeq: sample.sampleGroupSeq ?? null, pageRole: sample.pageRole ?? null, pageId: sample.pageId ?? null, pageEpoch: Number(sample?.pageEpoch ?? 0) || 0, promptIndex: timelineItem.promptIndex ?? 0, routeSessionId: sample.routeSessionId ?? null, activeSessionId: sample.activeSessionId ?? null, cells }); } const timingEvents = detectTurnTimingNonMonotonic(columns, rows); return { columns, rows, nonMonotonic: timingEvents.anomalies, elapsedZeroResets: timingEvents.elapsedZeroResets, totalElapsedForwardJumps: timingEvents.totalElapsedForwardJumps, terminalElapsedGrowth: timingEvents.terminalElapsedGrowth, terminalElapsedCorrections: timingEvents.terminalElapsedCorrections, recentUpdateResets: timingEvents.recentUpdateResets, recentUpdateSteps: timingEvents.recentUpdateSteps }; } function turnTimingScopedMetricKey(metric, sample) { return [ metric?.key ?? "unknown", metric?.pageRole ?? sample?.pageRole ?? "unknown-role", metric?.pageId ?? sample?.pageId ?? "unknown-page", Number(sample?.pageEpoch ?? metric?.pageEpoch ?? 0) || 0 ].join("|page:"); } function inferTurnMetricPromptIndex(metric, samplePromptIndex, maxDomIndex) { const promptIndex = Number(samplePromptIndex); if (!Number.isFinite(promptIndex) || promptIndex <= 0) return null; if (metric?.source === "aggregate") return promptIndex; if (isActiveTurnStatus(metric?.status)) return promptIndex; const domIndex = Number(metric?.domIndex); if (!isTerminalTurnStatus(metric?.status) && Number.isFinite(domIndex) && Number.isFinite(maxDomIndex) && domIndex === maxDomIndex && metric?.recentUpdateSeconds !== null && metric?.recentUpdateSeconds !== undefined) return promptIndex; return null; } function detectTurnTimingNonMonotonic(columns, rows) { const anomalies = []; const elapsedZeroResets = []; const totalElapsedForwardJumps = []; const terminalElapsedGrowth = []; const terminalElapsedCorrections = []; const recentUpdateResets = []; const recentUpdateSteps = []; for (const column of columns) { const previousByMetric = new Map(); let previousTerminalTotal = null; for (const row of rows) { const cell = row.cells?.[column.id]; if (!cell) continue; for (const metric of ["totalElapsedSeconds", "recentUpdateSeconds"]) { const value = cell[metric]; if (value === null || value === undefined || !Number.isFinite(Number(value))) continue; const current = Number(value); const previous = previousByMetric.get(metric); if (previous && metric === "totalElapsedSeconds" && current < previous.value) { const anomaly = previous.value > 0 && current === 0 ? "zero-reset" : "decrease"; const terminalTransition = !isTerminalTurnStatus(previous.status) && isTerminalTurnStatus(cell.status); const dropSeconds = previous.value - current; const allowedDropSeconds = Math.max(1, Number(alertThresholds?.turnTimingSampleSlackSeconds || 0)); const event = { columnId: column.id, columnLabel: column.label, ...timingFindingMeta("dom-card-total-elapsed-sequence", timingStatusFromTurnStatus(cell.status ?? previous.status), { cardElapsedSource: cell.source ?? column.source ?? "dom-card", }), metric, anomaly, expectedPattern: anomaly === "zero-reset" ? "total-elapsed-should-not-return-to-zero" : "total-elapsed-monotonic", fromSeq: previous.seq, fromTs: previous.ts, fromValue: previous.value, fromStatus: previous.status ?? null, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: current, toStatus: cell.status ?? null, delta: current - previous.value, traceId: cell.traceId ?? column.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, promptIndex: cell.promptIndex ?? null, samplePromptIndex: row.promptIndex ?? null, source: cell.source ?? column.source ?? null, pageRole: cell.pageRole ?? column.pageRole ?? null, pageId: cell.pageId ?? column.pageId ?? null, pageEpoch: cell.pageEpoch ?? row.pageEpoch ?? column.pageEpoch ?? null, valuesRedacted: true }; if (terminalTransition && anomaly === "decrease" && dropSeconds <= allowedDropSeconds) { terminalElapsedCorrections.push({ ...event, anomaly: "terminal-boundary-correction", expectedPattern: "terminal-sealed-duration-may-correct-running-elapsed-within-slack", allowedDropSeconds, }); } else { anomalies.push(event); if (anomaly === "zero-reset") elapsedZeroResets.push(event); } } if (previous && metric === "totalElapsedSeconds" && current > previous.value) { const sampleDeltaSeconds = elapsedSecondsBetween(previous.ts, row.ts); const delta = current - previous.value; const allowedIncreaseSeconds = sampleDeltaSeconds + alertThresholds.turnTimingSampleSlackSeconds; const terminalTransition = !isTerminalTurnStatus(previous.status) && isTerminalTurnStatus(cell.status); if (delta > allowedIncreaseSeconds && !terminalTransition) { totalElapsedForwardJumps.push({ columnId: column.id, columnLabel: column.label, ...timingFindingMeta("dom-card-total-elapsed-sequence", timingStatusFromTurnStatus(cell.status ?? previous.status), { cardElapsedSource: cell.source ?? column.source ?? "dom-card", }), metric, anomaly: "forward-jump", expectedPattern: "total-elapsed-increase-should-match-browser-sample-interval", fromSeq: previous.seq, fromTs: previous.ts, fromValue: previous.value, fromStatus: previous.status ?? null, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: current, toStatus: cell.status ?? null, delta, sampleDeltaSeconds, allowedIncreaseSeconds, excessiveIncreaseSeconds: Number((delta - allowedIncreaseSeconds).toFixed(3)), traceId: cell.traceId ?? column.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, promptIndex: cell.promptIndex ?? null, samplePromptIndex: row.promptIndex ?? null, source: cell.source ?? column.source ?? null, pageRole: cell.pageRole ?? column.pageRole ?? null, pageId: cell.pageId ?? column.pageId ?? null, pageEpoch: cell.pageEpoch ?? row.pageEpoch ?? column.pageEpoch ?? null, valuesRedacted: true }); } } if (metric === "totalElapsedSeconds" && isTerminalTurnStatus(cell.status)) { if (previousTerminalTotal && current > previousTerminalTotal.value) { terminalElapsedGrowth.push({ columnId: column.id, columnLabel: column.label, ...timingFindingMeta("terminal-card-total-elapsed-seal", timingStatusFromTurnStatus(cell.status, "business-turn-completed"), { cardElapsedSource: cell.source ?? column.source ?? "dom-card", }), metric, anomaly: "terminal-growth", expectedPattern: "terminal-total-elapsed-sealed", fromSeq: previousTerminalTotal.seq, fromTs: previousTerminalTotal.ts, fromValue: previousTerminalTotal.value, fromStatus: previousTerminalTotal.status, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: current, toStatus: cell.status ?? null, delta: current - previousTerminalTotal.value, traceId: cell.traceId ?? column.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, promptIndex: cell.promptIndex ?? null, samplePromptIndex: row.promptIndex ?? null, source: cell.source ?? column.source ?? null, pageRole: cell.pageRole ?? column.pageRole ?? null, pageId: cell.pageId ?? column.pageId ?? null, pageEpoch: cell.pageEpoch ?? row.pageEpoch ?? column.pageEpoch ?? null, valuesRedacted: true }); } previousTerminalTotal = { value: current, seq: row.seq ?? null, ts: row.ts ?? null, status: cell.status ?? null }; } if (previous && metric === "recentUpdateSeconds") { const elapsedMs = Date.parse(String(row.ts ?? "")) - Date.parse(String(previous.ts ?? "")); const elapsedSeconds = Number.isFinite(elapsedMs) && elapsedMs >= 0 ? elapsedMs / 1000 : null; const increase = current - previous.value; const allowedIncrease = elapsedSeconds === null ? alertThresholds.turnTimingSampleSlackSeconds : Math.max(alertThresholds.turnTimingSampleSlackSeconds, elapsedSeconds + alertThresholds.turnTimingSampleSlackSeconds); const excessiveIncrease = increase > allowedIncrease ? increase - allowedIncrease : 0; recentUpdateSteps.push({ columnId: column.id, columnLabel: column.label, metric: "recentUpdateSeconds", event: increase < 0 ? "reset" : excessiveIncrease > 0 ? "jump" : "increase", expectedPattern: "sawtooth-increase-or-reset", fromSeq: previous.seq, fromTs: previous.ts, fromValue: previous.value, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: current, delta: increase, sampleDeltaSeconds: elapsedSeconds, allowedIncreaseSeconds: allowedIncrease, excessiveIncreaseSeconds: excessiveIncrease, traceId: cell.traceId ?? column.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, promptIndex: cell.promptIndex ?? null, samplePromptIndex: row.promptIndex ?? null, source: cell.source ?? column.source ?? null, pageRole: cell.pageRole ?? column.pageRole ?? null, pageId: cell.pageId ?? column.pageId ?? null, pageEpoch: cell.pageEpoch ?? row.pageEpoch ?? column.pageEpoch ?? null, valuesRedacted: true }); if (increase < 0) { recentUpdateResets.push({ columnId: column.id, columnLabel: column.label, metric: "recentUpdateSeconds", event: "reset", fromSeq: previous.seq, fromTs: previous.ts, fromValue: previous.value, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: current, delta: increase, sampleDeltaSeconds: elapsedSeconds, traceId: cell.traceId ?? column.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, promptIndex: cell.promptIndex ?? null, samplePromptIndex: row.promptIndex ?? null, source: cell.source ?? column.source ?? null, pageRole: cell.pageRole ?? column.pageRole ?? null, pageId: cell.pageId ?? column.pageId ?? null, pageEpoch: cell.pageEpoch ?? row.pageEpoch ?? column.pageEpoch ?? null, valuesRedacted: true }); } if (excessiveIncrease > 0) { anomalies.push({ columnId: column.id, columnLabel: column.label, metric: "recentUpdateSeconds", anomaly: "jump", expectedPattern: "sawtooth-increase-or-reset", fromSeq: previous.seq, fromTs: previous.ts, fromValue: previous.value, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: current, delta: increase, sampleDeltaSeconds: elapsedSeconds, allowedIncreaseSeconds: allowedIncrease, excessiveIncreaseSeconds: excessiveIncrease, traceId: cell.traceId ?? column.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, promptIndex: cell.promptIndex ?? null, samplePromptIndex: row.promptIndex ?? null, source: cell.source ?? column.source ?? null, pageRole: cell.pageRole ?? column.pageRole ?? null, pageId: cell.pageId ?? column.pageId ?? null, pageEpoch: cell.pageEpoch ?? row.pageEpoch ?? column.pageEpoch ?? null, valuesRedacted: true }); } } previousByMetric.set(metric, { value: current, seq: row.seq ?? null, ts: row.ts ?? null, status: cell.status ?? null }); } } } return { anomalies, elapsedZeroResets, totalElapsedForwardJumps, terminalElapsedGrowth, terminalElapsedCorrections, recentUpdateResets, recentUpdateSteps }; } function elapsedSecondsBetween(fromTs, toTs) { const from = Date.parse(fromTs); const to = Date.parse(toTs); if (!Number.isFinite(from) || !Number.isFinite(to) || to < from) return 0; return Number(((to - from) / 1000).toFixed(3)); } function maxPositiveDelta(items) { const values = (Array.isArray(items) ? items : []) .map((item) => Number(item.delta)) .filter((value) => Number.isFinite(value) && value > 0); return values.length > 0 ? Math.max(...values) : 0; } function isTerminalTurnStatus(value) { const status = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"); return ["completed", "succeeded", "success", "failed", "error", "blocked", "timeout", "canceled", "cancelled", "stale", "done", "terminal", "thread-resume-failed"].includes(status); } function buildTraceOrderMetrics(samples, timeline) { const rows = []; const orderAnomalies = []; const completionNotLast = []; const groups = new Map(); for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex += 1) { const sample = samples[sampleIndex] || {}; const sampleRows = traceTimingRowsForSample(sample, timeline[sampleIndex] || {}); for (const row of sampleRows) { const normalized = { ...row, sampleIndex, sampleSeq: sample.seq ?? null, timestamp: sample.ts || sample.timestamp || sample.collectedAt || sample.time || null, pageRole: row.pageRole || sample.pageRole || sample.role || sample.contextRole || null, pageId: row.pageId || sample.pageId || sample.contextId || null, sessionId: row.sessionId || sample.sessionId || sample.workbenchSessionId || null, }; rows.push(normalized); const key = traceRowGroupKey(normalized); if (!groups.has(key)) groups.set(key, []); groups.get(key).push(normalized); } } const slackSeconds = Math.max(1, Number(alertThresholds?.turnTimingSampleSlackSeconds || 0)); for (const groupRows of groups.values()) { const sorted = groupRows.slice().sort((a, b) => { if (a.sampleIndex !== b.sampleIndex) return a.sampleIndex - b.sampleIndex; return (a.rowIndex ?? 0) - (b.rowIndex ?? 0); }); let previous = null; for (const row of sorted) { if (previous) { const reasons = []; if (Number.isFinite(previous.totalSeconds) && Number.isFinite(row.totalSeconds) && row.totalSeconds + slackSeconds < previous.totalSeconds) { reasons.push('total-decreased'); } if (Number.isFinite(previous.projectedSeq) && Number.isFinite(row.projectedSeq) && row.projectedSeq < previous.projectedSeq) { reasons.push('projected-seq-decreased'); } if (Number.isFinite(previous.clockSeconds) && Number.isFinite(row.clockSeconds)) { const diff = previous.clockSeconds - row.clockSeconds; if (diff > slackSeconds && diff < 43200) reasons.push('clock-decreased'); } if (Number.isFinite(previous.timestampMs) && Number.isFinite(row.timestampMs) && row.timestampMs + slackSeconds * 1000 < previous.timestampMs) { reasons.push('timestamp-decreased'); } if (reasons.length) { orderAnomalies.push({ sampleIndex: row.sampleIndex, sampleSeq: row.sampleSeq, timestamp: row.timestamp, pageRole: row.pageRole, pageId: row.pageId, sessionId: row.sessionId, traceId: row.traceId || previous.traceId || null, previousRowIndex: previous.rowIndex, currentRowIndex: row.rowIndex, reasons, previousTotalSeconds: previous.totalSeconds, currentTotalSeconds: row.totalSeconds, previousProjectedSeq: previous.projectedSeq, currentProjectedSeq: row.projectedSeq, previousSourceSeq: previous.sourceSeq, currentSourceSeq: row.sourceSeq, previousEventSeq: previous.eventSeq, currentEventSeq: row.eventSeq, previousClockSeconds: previous.clockSeconds, currentClockSeconds: row.clockSeconds, previousTimestampMs: previous.timestampMs, currentTimestampMs: row.timestampMs, previousPreview: previous.preview, currentPreview: row.preview, }); } } previous = row; } for (let index = 0; index < sorted.length; index += 1) { const row = sorted[index]; if (!row.isCompletion) continue; const later = sorted.slice(index + 1).find((candidate) => { if (candidate.isCompletion && candidate.preview === row.preview) return false; return Number.isFinite(candidate.totalSeconds) || Number.isFinite(candidate.clockSeconds) || Number.isFinite(candidate.projectedSeq); }); if (later) { completionNotLast.push({ sampleIndex: row.sampleIndex, sampleSeq: row.sampleSeq, timestamp: row.timestamp, pageRole: row.pageRole, pageId: row.pageId, sessionId: row.sessionId, traceId: row.traceId || later.traceId || null, completionRowIndex: row.rowIndex, laterRowIndex: later.rowIndex, completionTotalSeconds: row.totalSeconds, laterTotalSeconds: later.totalSeconds, completionProjectedSeq: row.projectedSeq, laterProjectedSeq: later.projectedSeq, completionSourceSeq: row.sourceSeq, laterSourceSeq: later.sourceSeq, completionEventSeq: row.eventSeq, laterEventSeq: later.eventSeq, completionPreview: row.preview, laterPreview: later.preview, }); } } } return { summary: { sampleCount: samples.length, traceRowCount: rows.length, orderAnomalyCount: orderAnomalies.length, completionNotLastCount: completionNotLast.length, }, rows: rows.slice(0, 200), orderAnomalies: orderAnomalies.slice(0, 100), completionNotLast: completionNotLast.slice(0, 100), }; } function buildCodeAgentCardDurationUnderreportedMetrics(samples, timeline) { const findings = []; const slackSeconds = Math.max(5, Number(alertThresholds?.turnTimingSampleSlackSeconds || 0)); for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex += 1) { const sample = samples[sampleIndex] || {}; const cards = codeAgentCardsForSample(sample); if (!cards.length) continue; const traceRows = traceTimingRowsForSample(sample, timeline[sampleIndex] || {}); const terminalCards = cards.filter((card) => isCodeAgentCardTerminal(card)); const sampleText = sampleVisibleText(sample, timeline[sampleIndex] || {}); for (const card of terminalCards) { const cardText = codeAgentCardText(card); const parsedCardSeconds = parseTotalElapsedSeconds(cardText).filter(Number.isFinite); const cardSeconds = Number.isFinite(Number(card.totalElapsedSeconds)) ? Number(card.totalElapsedSeconds) : parsedCardSeconds.length > 0 ? Math.max(...parsedCardSeconds) : NaN; if (!Number.isFinite(cardSeconds)) continue; const traceMatched = traceRows.filter((row) => traceRowMatchesCard(row, card, terminalCards.length)); const traceEvidence = maxTraceDurationEvidence(traceMatched); const textEvidence = maxSelfReportedDurationEvidence([card.text, card.preview, card.finalResponseText, card.runningRecordText, terminalCards.length === 1 ? sampleText : ''].filter(Boolean).join('\n')); const evidences = [traceEvidence, textEvidence].filter((item) => item && item.exact === true && Number.isFinite(item.seconds)); if (!evidences.length) continue; const strongest = evidences.sort((a, b) => b.seconds - a.seconds)[0]; const tolerance = Math.max(slackSeconds, Math.ceil(strongest.seconds * 0.05)); if (strongest.seconds > cardSeconds + tolerance) { findings.push({ sampleIndex, ...timingFindingMeta(strongest.kind, timingStatusFromTurnStatus(card.status || card.state || card.phase, "business-turn-completed"), { cardElapsedSource: "dom-card-total-elapsed", expectedElapsedSource: strongest.kind, }), timestamp: sample.timestamp || sample.collectedAt || sample.time || null, pageRole: card.pageRole || sample.pageRole || sample.role || sample.contextRole || null, pageId: card.pageId || sample.pageId || sample.contextId || null, sessionId: card.sessionId || sample.sessionId || sample.workbenchSessionId || null, traceId: card.traceId || strongest.traceId || null, status: card.status || card.state || card.phase || null, cardTotalElapsedSeconds: cardSeconds, expectedElapsedSeconds: strongest.seconds, deltaSeconds: strongest.seconds - cardSeconds, toleranceSeconds: tolerance, evidenceKind: strongest.kind, evidencePreview: strongest.preview, cardPreview: card.preview || compactOneLine(cardText || ''), }); } } } return findings.slice(0, 100); } function buildCodeAgentCardDurationMismatchMetrics(samples, timeline) { const findings = []; const slackSeconds = Math.max(5, Number(alertThresholds?.turnTimingSampleSlackSeconds || 0)); for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex += 1) { const sample = samples[sampleIndex] || {}; const cards = codeAgentCardsForSample(sample); if (!cards.length) continue; const traceRows = traceTimingRowsForSample(sample, timeline[sampleIndex] || {}); const terminalCards = cards.filter((card) => isCodeAgentCardTerminal(card)); const sampleText = sampleVisibleText(sample, timeline[sampleIndex] || {}); for (const card of terminalCards) { const cardText = codeAgentCardText(card); const parsedCardSeconds = parseTotalElapsedSeconds(cardText).filter(Number.isFinite); const cardSeconds = Number.isFinite(Number(card.totalElapsedSeconds)) ? Number(card.totalElapsedSeconds) : parsedCardSeconds.length > 0 ? Math.max(...parsedCardSeconds) : NaN; if (!Number.isFinite(cardSeconds)) continue; const traceMatched = traceRows.filter((row) => traceRowMatchesCard(row, card, terminalCards.length)); const traceEvidence = maxTraceDurationEvidence(traceMatched); const textEvidence = maxSelfReportedDurationEvidence([card.text, card.preview, card.finalResponseText, card.runningRecordText, terminalCards.length === 1 ? sampleText : ''].filter(Boolean).join('\n')); const evidences = [traceEvidence, textEvidence].filter((item) => item && item.exact === true && Number.isFinite(item.seconds)); if (!evidences.length) continue; const strongest = evidences.sort((a, b) => b.seconds - a.seconds)[0]; const exactEvidence = strongest.exact === true || strongest.kind === 'trace-completion-total' || strongest.kind === 'final-response-duration'; const tolerance = Math.max(slackSeconds, Math.ceil(strongest.seconds * 0.05)); const signedDelta = Number((cardSeconds - strongest.seconds).toFixed(3)); const absoluteDelta = Math.abs(signedDelta); const underreported = strongest.seconds > cardSeconds + tolerance; const overreported = exactEvidence && cardSeconds > strongest.seconds + tolerance; if (!underreported && !overreported) continue; findings.push({ sampleIndex, ...timingFindingMeta(strongest.kind, timingStatusFromTurnStatus(card.status || card.state || card.phase, "business-turn-completed"), { cardElapsedSource: "dom-card-total-elapsed", expectedElapsedSource: strongest.kind, }), timestamp: sample.timestamp || sample.collectedAt || sample.time || sample.ts || null, pageRole: card.pageRole || sample.pageRole || sample.role || sample.contextRole || null, pageId: card.pageId || sample.pageId || sample.contextId || null, sessionId: card.sessionId || sample.sessionId || sample.workbenchSessionId || null, traceId: card.traceId || strongest.traceId || null, status: card.status || card.state || card.phase || null, direction: underreported ? 'underreported' : 'overreported', cardTotalElapsedSeconds: cardSeconds, expectedElapsedSeconds: strongest.seconds, signedDeltaSeconds: signedDelta, deltaSeconds: Number(absoluteDelta.toFixed(3)), toleranceSeconds: tolerance, evidenceKind: strongest.kind, exactEvidence, evidencePreview: strongest.preview, cardPreview: card.preview || compactOneLine(cardText || ''), }); } } return findings.slice(0, 100); } function traceTimingRowsForSample(sample, timelineItem) { const rows = []; const seen = new Set(); const appendRowsFromText = (text, source, baseIndex, meta = {}) => { for (const extracted of extractTraceRowsFromText(text, source, baseIndex, meta)) { const key = [extracted.pageRole || '', extracted.pageId || '', extracted.rowIndex, extracted.preview].join('|'); if (seen.has(key)) continue; seen.add(key); rows.push(extracted); } }; for (const candidate of traceRowCandidateArrays(sample, timelineItem)) { const array = Array.isArray(candidate.rows) ? candidate.rows : []; array.forEach((item, index) => { if (typeof item === 'string') { appendRowsFromText(item, candidate.source, index, candidate.meta || {}); return; } if (!item || typeof item !== 'object') return; const text = objectText(item); if (!text) return; appendRowsFromText(text, candidate.source, Number.isFinite(Number(item.index)) ? Number(item.index) : index, { ...(candidate.meta || {}), pageRole: item.pageRole || item.role || candidate.meta?.pageRole || null, pageId: item.pageId || item.contextId || candidate.meta?.pageId || null, sessionId: item.sessionId || item.workbenchSessionId || candidate.meta?.sessionId || null, traceId: item.traceId || item.trace_id || candidate.meta?.traceId || null, projectedSeq: item.projectedSeq ?? item.projected_seq ?? item.projectedSequence ?? null, sourceSeq: item.sourceSeq ?? item.source_seq ?? item.sourceSequence ?? null, eventSeq: item.eventSeq ?? item.event_seq ?? item.sequence ?? null, eventTimestamp: item.eventTimestamp ?? item.event_ts ?? item.timestamp ?? item.ts ?? null, eventTimeText: item.eventTimeText ?? item.timeText ?? null, eventKind: item.eventKind ?? item.kind ?? item.status ?? null, }); }); } if (!rows.length) { appendRowsFromText(sampleVisibleText(sample, timelineItem), 'visible-text', 0, { pageRole: sample.pageRole || sample.role || sample.contextRole || null, pageId: sample.pageId || sample.contextId || null, sessionId: sample.sessionId || sample.workbenchSessionId || null, }); } rows.sort((a, b) => (a.rowIndex ?? 0) - (b.rowIndex ?? 0)); return rows; } function extractTraceRowsFromText(text, source, baseIndex, meta = {}) { const result = []; const normalized = String(text || '').replace(/\r/g, '\n'); if (!normalized.trim()) return result; const lines = normalized.split('\n').map((line) => line.trim()).filter(Boolean); for (let index = 0; index < lines.length; index += 1) { const line = lines[index]; if (!traceLineLooksRelevant(line)) continue; const nextLine = index + 1 < lines.length && !/^\d{1,2}:\d{2}:\d{2}\b/.test(lines[index + 1]) ? lines[index + 1] : ''; const preview = compactOneLine(nextLine ? line + ' ' + nextLine : line); result.push(normalizeTraceTimingRow(preview, source, Number(baseIndex || 0) * 1000 + index, meta)); } return result; } function normalizeTraceTimingRow(text, source, rowIndex, meta = {}) { const preview = compactOneLine(text).slice(0, 240); const projectedSeq = firstFiniteNumber(meta.projectedSeq, parseTraceRowProjectedSeq(preview)); const sourceSeq = firstFiniteNumber(meta.sourceSeq); const eventSeq = firstFiniteNumber(meta.eventSeq); const timestampMs = parseTraceRowTimestampMs(meta.eventTimestamp || meta.eventTimeText || preview); return { source, rowIndex, preview, pageRole: meta.pageRole || null, pageId: meta.pageId || null, sessionId: meta.sessionId || null, traceId: meta.traceId || parseTraceRowTraceId(preview), clockSeconds: parseTraceRowClockSeconds(preview) ?? parseTraceRowClockSeconds(meta.eventTimeText || ""), timestampMs, totalSeconds: parseTraceRowTotalSeconds(preview), projectedSeq, sourceSeq, eventSeq, eventTimestamp: meta.eventTimestamp || null, eventTimeText: meta.eventTimeText || null, eventKind: meta.eventKind || null, isCompletion: traceRowIsTerminalCompletionText(preview, meta.eventKind || ""), }; } function traceRowIsTerminalCompletionText(preview, eventKind = "") { const value = [preview, eventKind].map((item) => String(item || "")).join(" "); if (/\bnon[-_ ]?terminal\b/i.test(value)) return false; if (/轮次完成|turn\s+completed|completed\s+turn|backend[_: -]?turn[_: -]?finished/i.test(value)) return true; return /\bterminal(?:[_: -]?status|Status)?\s*[=: -]\s*(?:completed|failed|cancelled|canceled|timeout)\b/i.test(value); } function traceRowIsRoundCompletionText(preview, eventKind = "") { const value = [preview, eventKind].map((item) => String(item || "")).join(" "); if (/\bnon[-_ ]?terminal\b/i.test(value)) return false; return /轮次完成|turn\s+completed|completed\s+turn|backend[_: -]?turn[_: -]?finished/i.test(value); } function traceRowCandidateArrays(sample, timelineItem) { const candidates = []; const pushArray = (rows, source, meta = {}) => { if (Array.isArray(rows) && rows.length) candidates.push({ rows, source, meta }); }; const directSources = [ [sample?.traceRows, 'sample.traceRows'], [sample?.eventRows, 'sample.eventRows'], [sample?.activityRows, 'sample.activityRows'], [sample?.timelineRows, 'sample.timelineRows'], [sample?.dom?.traceRows, 'sample.dom.traceRows'], [sample?.dom?.eventRows, 'sample.dom.eventRows'], [sample?.dom?.activityRows, 'sample.dom.activityRows'], [sample?.dom?.timelineRows, 'sample.dom.timelineRows'], [timelineItem?.traceRows, 'timeline.traceRows'], [timelineItem?.eventRows, 'timeline.eventRows'], [timelineItem?.activityRows, 'timeline.activityRows'], [timelineItem?.rows, 'timeline.rows'], [timelineItem?.events, 'timeline.events'], ]; for (const [rows, source] of directSources) pushArray(rows, source, {}); if (!candidates.length) collectNamedTraceArrays(sample, candidates, 'sample', 0); if (!candidates.length) collectNamedTraceArrays(timelineItem, candidates, 'timeline', 0); return candidates; } function collectNamedTraceArrays(value, candidates, path, depth) { if (!value || depth > 5) return; if (Array.isArray(value)) { const pathLooksTrace = /trace|timeline|activity|event|log|record/i.test(path); const valueLooksTrace = value.slice(0, 5).some((item) => traceLineLooksRelevant(typeof item === 'string' ? item : objectText(item))); if (pathLooksTrace || valueLooksTrace) candidates.push({ rows: value, source: path, meta: {} }); return; } if (typeof value !== 'object') return; for (const [key, child] of Object.entries(value)) { if (!child) continue; if (Array.isArray(child)) { const childPath = path + '.' + key; const pathLooksTrace = /trace|timeline|activity|event|log|record/i.test(key); const valueLooksTrace = child.slice(0, 5).some((item) => traceLineLooksRelevant(typeof item === 'string' ? item : objectText(item))); if (pathLooksTrace || valueLooksTrace) candidates.push({ rows: child, source: childPath, meta: {} }); continue; } if (typeof child === 'object' && /dom|trace|timeline|activity|event|log|record|page|card|message|panel|diagnostic/i.test(key)) { collectNamedTraceArrays(child, candidates, path + '.' + key, depth + 1); } } } function traceLineLooksRelevant(text) { const value = String(text || '').trim(); if (!value) return false; if (/^\d{1,2}:\d{2}:\d{2}\b/.test(value)) return true; if (/\btotal=\d/.test(value)) return true; if (/轮次完成(总耗时/.test(value)) return true; if (/\bseq(?:uence)?[=:]\s*\d+/i.test(value)) return true; return false; } function parseTraceRowClockSeconds(text) { const match = String(text || '').match(/^\s*(\d{1,2}):(\d{2}):(\d{2})\b/); if (!match) return null; return Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]); } function parseTraceRowTotalSeconds(text) { const value = String(text || ''); const totalMatch = value.match(/\btotal=([0-9:.]+)/i); if (totalMatch) return parseTraceDurationSeconds(totalMatch[1]); const completionMatch = value.match(/总耗时\s*([0-9:.]+)/); if (completionMatch) return parseTraceDurationSeconds(completionMatch[1]); return null; } function parseTraceDurationSeconds(value) { const text = String(value || '').trim(); if (!text) return null; const parts = text.split(':').map((part) => Number(part)); if (parts.some((part) => !Number.isFinite(part))) return null; if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]; if (parts.length === 2) return parts[0] * 60 + parts[1]; if (parts.length === 1) return parts[0]; return null; } function parseTraceRowProjectedSeq(text) { const value = String(text || ''); const match = value.match(/\b(?:projected[_-]?seq|seq(?:uence)?|event[_-]?seq)\s*[=:]\s*(\d+)/i); return match ? Number(match[1]) : null; } function parseTraceRowTimestampMs(value) { const text = String(value || '').trim(); if (!text) return null; const parsed = Date.parse(text); return Number.isFinite(parsed) ? parsed : null; } function firstFiniteNumber(...values) { for (const value of values) { const numeric = Number(value); if (Number.isFinite(numeric)) return numeric; } return null; } function parseTraceRowTraceId(text) { const match = String(text || '').match(/\b(?:trace_id=|traceId[:=]?\s*)(trc_[a-z0-9_-]+|[a-f0-9]{16,})\b/i); return match ? match[1] : null; } function traceRowGroupKey(row) { const identity = row.traceId ? 'trace:' + row.traceId : 'sample:' + (row.sampleIndex ?? '-') + ':' + (row.source || 'unknown'); return [row.pageRole || '', row.pageId || '', row.sessionId || '', row.source || '', row.sampleIndex ?? '', identity].join('|'); } function traceRowMatchesCard(row, card, terminalCardCount) { if (!row) return false; if (card.traceId) return row.traceId === card.traceId; if (row.traceId) return false; if (row.sessionId && card.sessionId) return terminalCardCount === 1 && row.sessionId === card.sessionId; if (terminalCardCount === 1) return true; return false; } function maxTraceDurationEvidence(rows) { const finiteTotals = rows.map((row) => Number(row.totalSeconds)).filter(Number.isFinite); const finiteClocks = rows.map((row) => Number(row.clockSeconds)).filter(Number.isFinite); const evidences = []; if (finiteTotals.length) { const maxTotal = Math.max(...finiteTotals); const source = rows.find((row) => Number(row.totalSeconds) === maxTotal); const exact = source?.isCompletion === true; evidences.push({ kind: exact ? 'trace-completion-total' : 'trace-total', seconds: maxTotal, traceId: source?.traceId || null, preview: source?.preview || '', exact }); } if (finiteClocks.length >= 2) { const minClock = Math.min(...finiteClocks); const maxClock = Math.max(...finiteClocks); const span = maxClock - minClock; if (span >= 0 && span < 43200) evidences.push({ kind: 'trace-clock-span', seconds: span, traceId: null, preview: 'visible trace row clock span', exact: false }); } if (!evidences.length) return null; evidences.sort((a, b) => b.seconds - a.seconds); return evidences[0]; } function maxSelfReportedDurationEvidence(text) { const value = String(text || ''); const lines = value.split(/\n+/).map((line) => line.trim()).filter(Boolean); let best = null; for (let index = 0; index < lines.length; index += 1) { const line = lines[index]; const previous = index > 0 ? lines[index - 1] : ''; const next = index + 1 < lines.length ? lines[index + 1] : ''; const candidateText = selfReportedDurationCandidateText(previous, line, next); if (!candidateText) continue; const seconds = parseSelfReportedRoundDurationSeconds(candidateText); if (!Number.isFinite(seconds)) continue; if (!best || seconds > best.seconds) { best = { kind: 'final-response-duration', seconds, preview: compactOneLine(candidateText).slice(0, 240), exact: true }; } } return best; } function selfReportedDurationCandidateText(previous, line, next) { const current = String(line || ''); const before = String(previous || ''); const after = String(next || ''); const windowText = [before, current, after].filter(Boolean).join(' '); const hasDurationKeyword = /耗时|用时|duration|elapsed/i.test(current); const hasNearbyDurationHeading = /(?:本轮|整轮|全程|任务|round|turn)?\s*(?:耗时|用时|duration|elapsed)/i.test(before) || /(?:本轮|整轮|全程|任务|round|turn)?\s*(?:耗时|用时|duration|elapsed)/i.test(after); const hasDurationValue = /(?:约|大约|around|about)?\s*\d+(?:\.\d+)?\s*(?:小时|分钟|分|秒|hour|hours|hr|hrs|min|mins|minute|minutes|sec|secs|second|seconds)/i.test(windowText); const hasRoundContext = /本轮|整轮|全程|从.+到|全部通过|smoke|benchmark|round|turn|completed|passed/i.test(windowText); if (hasDurationKeyword && hasDurationValue) return current; if (hasNearbyDurationHeading && hasDurationValue) return windowText; if (hasRoundContext && hasDurationValue && /(?:约|大约|around|about)\s*\d|\d+(?:\.\d+)?\s*(?:分钟|小时|minute|hour)/i.test(current)) return windowText; return ''; } function parseSelfReportedRoundDurationSeconds(text) { const value = String(text || ''); const clock = value.match(/(?:耗时|用时|duration|elapsed)[^0-9]{0,24}(\d{1,2}:\d{2}:\d{2}|\d{1,2}:\d{2})/i); if (clock) return parseTraceDurationSeconds(clock[1]); const hour = value.match(/(?:约|大约|around|about)?\s*(\d+(?:\.\d+)?)\s*(?:小时|hour|hours|hr|hrs)/i); const minute = value.match(/(?:约|大约|around|about)?\s*(\d+(?:\.\d+)?)\s*(?:分钟|分|min|mins|minute|minutes)/i); const second = value.match(/(?:约|大约|around|about)?\s*(\d+(?:\.\d+)?)\s*(?:秒|sec|secs|second|seconds)/i); let total = 0; if (hour) total += Number(hour[1]) * 3600; if (minute) total += Number(minute[1]) * 60; if (second) total += Number(second[1]); return total > 0 ? total : null; } function sampleVisibleText(sample, timelineItem) { const chunks = []; for (const source of [sample?.visibleText, sample?.text, sample?.innerText, sample?.dom?.visibleText, sample?.dom?.text, timelineItem?.visibleText, timelineItem?.text, timelineItem?.message, timelineItem?.summary]) { if (typeof source === 'string' && source.trim()) chunks.push(source); } return chunks.join('\n'); } function objectText(value) { if (!value || typeof value !== 'object') return typeof value === 'string' ? value : ''; const keys = ['text', 'innerText', 'visibleText', 'label', 'title', 'summary', 'message', 'content', 'body', 'preview', 'description']; const chunks = []; for (const key of keys) { const part = value[key]; if (typeof part === 'string' && part.trim()) chunks.push(part); } return chunks.join('\n'); } function compactOneLine(value) { return String(value || '').replace(/\s+/g, ' ').trim(); } function buildCodeAgentCardTimingMetrics(samples, timeline, turnTiming) { const missingElapsed = []; const missingRecentUpdate = []; const cardRows = []; for (let index = 0; index < (Array.isArray(samples) ? samples : []).length; index += 1) { const sample = samples[index]; const timelineItem = timeline[index] || {}; for (const card of codeAgentCardsForSample(sample)) { const text = codeAgentCardText(card); const totalElapsedValues = parseTotalElapsedSeconds(card?.durationText).filter(Number.isFinite); const recentUpdateValues = parseRecentUpdateSeconds(card?.activityText).filter(Number.isFinite); const terminal = isCodeAgentCardTerminal(card); const row = { ...ref(sample), promptIndex: timelineItem.promptIndex ?? 0, source: card.source ?? "turn", status: card.status ?? null, messageId: card.messageId ?? null, traceId: card.traceId ?? firstTraceId([text]), sessionId: card.sessionId ?? sample.sessionId ?? sample.workbenchSessionId ?? sample.routeSessionId ?? sample.activeSessionId ?? null, durationText: limitText(card.durationText, 120), activityText: limitText(card.activityText, 120), totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null, recentUpdateSeconds: recentUpdateValues.length > 0 ? Math.max(...recentUpdateValues) : null, terminal, textHash: card.textHash ?? sha256(text), textPreview: limitText(text, 180), valuesRedacted: true }; cardRows.push(row); if (row.totalElapsedSeconds === null) missingElapsed.push(row); if (!terminal && row.recentUpdateSeconds === null) missingRecentUpdate.push(row); } } const roundCompletion = buildRoundCompletionMetrics(samples, timeline, turnTiming); return { summary: { cardSampleCount: cardRows.length, terminalCardSampleCount: cardRows.filter((item) => item.terminal).length, runningCardSampleCount: cardRows.filter((item) => !item.terminal).length, missingElapsedCount: missingElapsed.length, missingRecentUpdateCount: missingRecentUpdate.length, roundCompletionEventCount: roundCompletion.events.length, roundCompletionElapsedMismatchCount: roundCompletion.elapsedMismatches.length, roundCompletionFinalResponseMissingCount: roundCompletion.finalResponseMissing.length, roundCompletionPostTimingChangeCount: roundCompletion.postCompletionTimingChanges.length, roundCompletionPostRecentUpdateVisibleCount: roundCompletion.postCompletionRecentUpdateVisible.length, elapsedMismatchToleranceSeconds: alertThresholds.turnTimingSampleSlackSeconds, }, cardSamples: cardRows.slice(0, 200), missingElapsed: missingElapsed.slice(0, 200), missingRecentUpdate: missingRecentUpdate.slice(0, 200), roundCompletion, valuesRedacted: true }; } function codeAgentCardsForSample(sample) { const turnCards = (Array.isArray(sample?.turns) ? sample.turns : []) .map((item) => ({ ...item, source: item?.source || "turn" })) .filter(isCodeAgentCardLike); if (turnCards.length > 0) return turnCards; return (Array.isArray(sample?.messages) ? sample.messages : []) .map((item) => ({ ...item, source: item?.source || "message" })) .filter(isCodeAgentCardLike); } function isCodeAgentCardLike(item) { const text = codeAgentCardText(item); const role = String(item?.dataRole || item?.role || "").toLowerCase(); if (/agent|assistant/u.test(role)) return true; return /Code Agent|运行记录|耗时|最近\s*(?:\d+|一|两|三)|轮次完成|trace_id=trc_/iu.test(text); } function codeAgentCardText(item) { return [ item?.durationText, item?.activityText, item?.text, item?.textPreview, item?.status ].map((value) => String(value || "")).filter((value) => value.trim().length > 0).join("\n"); } function isCodeAgentCardTerminal(item) { const status = String(item?.status ?? item?.state ?? item?.phase ?? "").trim().toLowerCase().replace(/_/gu, "-"); if (isActiveTurnStatus(status)) return false; if (isTerminalTurnStatus(status)) return true; if (item?.terminal === true || item?.sealed === true) return true; const text = codeAgentCardText(item); return isTerminalTraceText(text) || /轮次完成|轮次失败|轮次取消|已记录|completed|failed|canceled|cancelled|blocked/iu.test(text); } function timingFindingMeta(sourceOfTruth, status, extra = {}) { return { timingSourceOfTruth: sourceOfTruth, timingStatus: status || "non-blocking-timing-alert", timingAlert: true, blocking: false, ...extra, }; } function timingStatusFromTurnStatus(status, fallback = "observer-timeout") { const normalized = String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"); if (isSuccessfulTurnStatus(normalized)) return "business-turn-completed"; if (isTerminalTurnStatus(normalized)) return "scenario-incomplete"; if (isActiveTurnStatus(normalized)) return "observer-timeout"; return fallback; } function timingStatusFromRows(rows, fallback = "observer-timeout") { const list = Array.isArray(rows) ? rows : []; if (list.some((item) => isSuccessfulTurnStatus(item?.status ?? item?.toStatus ?? item?.fromStatus))) return "business-turn-completed"; if (list.some((item) => Number(item?.finalTextSamples ?? 0) > 0)) return "business-turn-completed"; if (list.some((item) => isTerminalTurnStatus(item?.status ?? item?.toStatus ?? item?.fromStatus))) return "scenario-incomplete"; if (list.some((item) => isActiveTurnStatus(item?.status ?? item?.toStatus ?? item?.fromStatus))) return "observer-timeout"; return fallback; } function isSuccessfulTurnStatus(status) { const normalized = String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"); return ["completed", "complete", "succeeded", "success", "done", "ok", "passed"].includes(normalized); } function isActiveTurnStatus(value) { const status = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"); return ["pending", "running", "queued", "admitted", "dispatching", "in-progress", "inprogress", "executing", "progress", "thinking", "working", "active", "streaming", "created", "started"].includes(status); } function buildRoundCompletionMetrics(samples, timeline, turnTiming) { const events = []; const elapsedMismatchToleranceSeconds = Math.max(5, Number(alertThresholds.turnTimingSampleSlackSeconds || 0)); for (let index = 0; index < (Array.isArray(samples) ? samples : []).length; index += 1) { const sample = samples[index]; const timelineItem = timeline[index] || {}; for (const event of roundCompletionEventsForSample(sample, timelineItem)) events.push(event); } const completionEvents = dedupeRoundCompletionEvents(events); const elapsedMismatches = []; const finalResponseMissing = []; const postCompletionTimingChanges = []; const postCompletionRecentUpdateVisible = []; for (const event of completionEvents) { const sampleIndex = samples.findIndex((sample) => sample?.seq === event.seq && sample?.pageRole === event.pageRole && sample?.pageId === event.pageId); const sameSample = sampleIndex >= 0 ? samples[sampleIndex] : null; const sameTimelineItem = sampleIndex >= 0 ? timeline[sampleIndex] || {} : {}; const cards = sameSample ? cardMetricItemsForCompletion(sameSample, sameTimelineItem, event) : []; const bestCard = cards.filter((card) => Number.isFinite(Number(card.totalElapsedSeconds)))[0] || null; if (Number.isFinite(Number(event.elapsedSeconds)) && bestCard) { const delta = Math.abs(Number(bestCard.totalElapsedSeconds) - Number(event.elapsedSeconds)); if (delta > elapsedMismatchToleranceSeconds) { elapsedMismatches.push({ ...eventRef(event), ...timingFindingMeta("trace-round-completion-total", timingStatusFromTurnStatus(bestCard.status, "business-turn-completed"), { cardElapsedSource: bestCard.source ?? "dom-card", completionElapsedSource: "trace-round-completion", }), cardTotalElapsedSeconds: Number(bestCard.totalElapsedSeconds), completionElapsedSeconds: Number(event.elapsedSeconds), deltaSeconds: Number(delta.toFixed(3)), toleranceSeconds: elapsedMismatchToleranceSeconds, cardTraceId: bestCard.traceId ?? null, cardMessageId: bestCard.messageId ?? null, valuesRedacted: true }); } } if (!(sameSample && sampleHasTerminalAgentResultCard(sameSample, event)) && !(sameSample && sampleHasVisibleFinalResponse(sameSample, event)) && !hasFinalResponseAfterCompletion(samples, timeline, event)) { finalResponseMissing.push({ ...eventRef(event), completionElapsedSeconds: event.elapsedSeconds, finalResponseProbe: sameSample ? terminalAgentResultProbe(sameSample, event) : { ok: false, reason: "sample-not-found" }, valuesRedacted: true }); } const postTiming = detectPostCompletionTimingChanges(turnTiming, event); postCompletionTimingChanges.push(...postTiming.timingChanges); postCompletionRecentUpdateVisible.push(...postTiming.recentUpdateVisible); } return { events: completionEvents.slice(0, 200), elapsedMismatches: dedupeRoundCompletionRows(elapsedMismatches).slice(0, 200), finalResponseMissing: dedupeRoundCompletionRows(finalResponseMissing).slice(0, 200), postCompletionTimingChanges: dedupeRoundCompletionRows(postCompletionTimingChanges).slice(0, 200), postCompletionRecentUpdateVisible: dedupeRoundCompletionRows(postCompletionRecentUpdateVisible).slice(0, 200), valuesRedacted: true }; } function roundCompletionEventsForSample(sample, timelineItem) { const rows = []; for (const item of traceTimingRowsForSample(sample, timelineItem)) { const text = String(item?.preview || item?.text || item?.textPreview || ""); if (!traceRowIsRoundCompletionText(text, item?.eventKind || "")) continue; const elapsedValues = [ Number(item?.totalSeconds), ...parseTotalElapsedSeconds(text).filter(Number.isFinite) ].filter(Number.isFinite); const attributed = inferRoundCompletionCard(sample, text); rows.push({ ...ref(sample), promptIndex: timelineItem.promptIndex ?? 0, traceId: item?.traceId ?? firstTraceId([text]) ?? attributed?.traceId ?? null, messageId: item?.messageId ?? attributed?.messageId ?? null, attributed: Boolean(item?.traceId || item?.messageId || attributed?.traceId || attributed?.messageId), traceRowIndex: item?.rowIndex ?? item?.index ?? null, elapsedSeconds: elapsedValues.length > 0 ? Math.max(...elapsedValues) : null, timingSourceOfTruth: "trace-round-completion-total", timingStatus: "business-turn-completed", textHash: item?.textHash ?? sha256(text), preview: limitText(text, 180), valuesRedacted: true }); } return rows; } function inferRoundCompletionCard(sample, text) { const cards = codeAgentCardsForSample(sample) .filter((card) => isCodeAgentCardTerminal(card)) .filter((card) => card?.traceId || card?.messageId); const textHash = sha256(String(text || "")); const direct = cards.filter((card) => { const cardText = codeAgentCardText(card); return card?.textHash === textHash || cardText.includes(String(text || "").slice(0, 80)) || /轮次完成/iu.test(cardText); }); const candidates = direct.length > 0 ? direct : cards; if (candidates.length !== 1) return null; const card = candidates[0]; return { traceId: card.traceId ?? null, messageId: card.messageId ?? card.id ?? null }; } function cardMetricItemsForCompletion(sample, timelineItem, event) { const metrics = turnMetricItems(sample, timelineItem) .filter((item) => item.promptIndex === event.promptIndex) .filter((item) => item.pageRole === event.pageRole || !event.pageRole) .filter((item) => item.pageId === event.pageId || !event.pageId); if (!event.traceId && !event.messageId) { const withElapsed = metrics.filter((item) => Number.isFinite(Number(item.totalElapsedSeconds))); return withElapsed.length === 1 ? withElapsed : []; } return metrics .filter((item) => !event.traceId || !item.traceId || item.traceId === event.traceId) .filter((item) => !event.messageId || !item.messageId || item.messageId === event.messageId) .sort((left, right) => { const leftTraceMatch = event.traceId && left.traceId === event.traceId ? 0 : 1; const rightTraceMatch = event.traceId && right.traceId === event.traceId ? 0 : 1; const leftMessageMatch = event.messageId && left.messageId === event.messageId ? 0 : 1; const rightMessageMatch = event.messageId && right.messageId === event.messageId ? 0 : 1; return leftTraceMatch - rightTraceMatch || leftMessageMatch - rightMessageMatch || String(left.source || "").localeCompare(String(right.source || "")); }); } function hasFinalResponseAfterCompletion(samples, timeline, event) { if (!event.traceId && !event.messageId && !event.promptIndex) return true; const eventTsMs = Date.parse(String(event.ts || "")); for (let index = 0; index < (Array.isArray(samples) ? samples : []).length; index += 1) { const sample = samples[index]; const tsMs = Date.parse(String(sample?.ts || "")); if (Number.isFinite(eventTsMs) && Number.isFinite(tsMs) && tsMs < eventTsMs) continue; if (sample?.pageRole !== event.pageRole) continue; const sampleSession = sample?.routeSessionId || sample?.activeSessionId || null; const eventSession = event.routeSessionId || event.activeSessionId || null; if (sampleSession && eventSession && sampleSession !== eventSession) continue; const promptIndex = timeline[index]?.promptIndex ?? 0; if (!event.traceId && !event.messageId && event.promptIndex && promptIndex !== event.promptIndex) continue; if (sampleHasTerminalAgentResultCard(sample, event)) return true; if (sampleHasVisibleFinalResponse(sample, event)) return true; } return false; } function sampleHasTerminalAgentResultCard(sample, event = {}) { return terminalAgentResultProbe(sample, event).ok === true; } function terminalAgentResultProbe(sample, event = {}) { const decisions = []; for (const item of codeAgentCardsForSample(sample)) { const text = normalizedText(codeAgentCardText(item)); const traceMatched = Boolean(event.traceId && item?.traceId && item.traceId === event.traceId); const decision = { source: item?.source || null, role: item?.role ?? null, dataRole: item?.dataRole ?? null, status: item?.status ?? null, traceId: item?.traceId ?? null, messageId: item?.messageId ?? null, textBytes: Buffer.byteLength(text), valuesRedacted: true }; if (event.traceId && item?.traceId && item.traceId !== event.traceId) { decisions.push({ ...decision, decision: "skip-trace" }); continue; } if (!traceMatched && event.messageId && item?.messageId && item.messageId !== event.messageId) { decisions.push({ ...decision, decision: "skip-message" }); continue; } if (!isAssistantFinalResponseCandidate(item, item?.source || "turn")) { decisions.push({ ...decision, decision: "skip-role" }); continue; } if (!isCodeAgentCardTerminal(item)) { decisions.push({ ...decision, decision: "skip-non-terminal" }); continue; } if (text.length < 24) { decisions.push({ ...decision, decision: "skip-short" }); continue; } decisions.push({ ...decision, decision: "accept-terminal-agent-card" }); return { ok: true, decisions: decisions.slice(-8), valuesRedacted: true }; } return { ok: false, decisions: decisions.slice(-8), valuesRedacted: true }; } function sampleHasVisibleFinalResponse(sample, event = {}) { for (const [groupName, group] of [["messages", sample?.messages], ["turns", sample?.turns]]) { if (!Array.isArray(group)) continue; for (const item of group) { const traceMatched = Boolean(event.traceId && item?.traceId && item.traceId === event.traceId); if (event.traceId && item?.traceId && item.traceId !== event.traceId) continue; if (!traceMatched && event.messageId && item?.messageId && item.messageId !== event.messageId) continue; if (!isAssistantFinalResponseCandidate(item, groupName)) continue; const text = normalizedText([item?.text, item?.textPreview].filter(Boolean).join(" ")); if (text.length < 24) continue; if (groupName === "messages" && isTerminalTurnStatus(item?.status)) return true; if (isDiagnosticText(text)) continue; if (isFinalResultText(text)) return true; if (/运行记录/iu.test(text) && /(?:已完成|完成|新增|实现|验证|测试|结果|README|文件|summary)/iu.test(text)) return true; } } return false; } function normalizedDomRole(item) { return String(item?.dataRole ?? item?.role ?? item?.ariaRole ?? "") .trim() .toLowerCase() .replace(/[_\s]+/gu, "-"); } function isAssistantFinalResponseCandidate(item, groupName) { const role = normalizedDomRole(item); if (/agent|assistant|code-agent|bot/iu.test(role)) return true; if (/user|human|client|prompt/iu.test(role)) return false; if (groupName === "turns") return isCodeAgentCardLike(item); const text = codeAgentCardText(item); if (isTerminalTurnStatus(item?.status) && /Code Agent|运行记录|assistant|agent/iu.test(text)) return true; return false; } function detectPostCompletionTimingChanges(turnTiming, event) { const timingChanges = []; const recentUpdateVisible = []; if (!event.traceId && !event.messageId) return { timingChanges, recentUpdateVisible }; const rows = Array.isArray(turnTiming?.rows) ? turnTiming.rows : []; const columns = Array.isArray(turnTiming?.columns) ? turnTiming.columns : []; const eventTsMs = Date.parse(String(event.ts || "")); for (const column of columns) { if (column.pageRole && event.pageRole && column.pageRole !== event.pageRole) continue; if (event.traceId && column.traceId && column.traceId !== event.traceId) continue; if (event.messageId && column.messageId && column.messageId !== event.messageId) continue; if (event.promptIndex && column.promptIndex && column.promptIndex !== event.promptIndex && column.lastPromptIndex !== event.promptIndex) continue; let previousTotal = null; let previousRecent = null; for (const row of rows) { const rowTsMs = Date.parse(String(row.ts || "")); if (Number.isFinite(eventTsMs) && Number.isFinite(rowTsMs) && rowTsMs < eventTsMs) continue; if (row.pageRole && event.pageRole && row.pageRole !== event.pageRole) continue; const cell = row.cells?.[column.id]; if (!cell) continue; if (event.traceId && cell.traceId && cell.traceId !== event.traceId) continue; if (event.messageId && cell.messageId && cell.messageId !== event.messageId) continue; const total = cell.totalElapsedSeconds === null || cell.totalElapsedSeconds === undefined ? NaN : Number(cell.totalElapsedSeconds); if (Number.isFinite(total)) { if (previousTotal && Math.abs(total - previousTotal.value) > alertThresholds.turnTimingSampleSlackSeconds) { timingChanges.push({ ...eventRef(event), columnId: column.id, columnLabel: column.label, metric: "totalElapsedSeconds", fromSeq: previousTotal.seq, fromTs: previousTotal.ts, fromValue: previousTotal.value, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: total, delta: Number((total - previousTotal.value).toFixed(3)), toleranceSeconds: alertThresholds.turnTimingSampleSlackSeconds, traceId: cell.traceId ?? column.traceId ?? event.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, valuesRedacted: true }); } previousTotal = { value: total, seq: row.seq ?? null, ts: row.ts ?? null }; } const recent = cell.recentUpdateSeconds === null || cell.recentUpdateSeconds === undefined ? NaN : Number(cell.recentUpdateSeconds); if (Number.isFinite(recent)) { recentUpdateVisible.push({ ...eventRef(event), columnId: column.id, columnLabel: column.label, metric: "recentUpdateSeconds", seq: row.seq ?? null, ts: row.ts ?? null, value: recent, traceId: cell.traceId ?? column.traceId ?? event.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, valuesRedacted: true }); if (previousRecent && recent !== previousRecent.value) { timingChanges.push({ ...eventRef(event), columnId: column.id, columnLabel: column.label, metric: "recentUpdateSeconds", fromSeq: previousRecent.seq, fromTs: previousRecent.ts, fromValue: previousRecent.value, toSeq: row.seq ?? null, toTs: row.ts ?? null, toValue: recent, delta: Number((recent - previousRecent.value).toFixed(3)), traceId: cell.traceId ?? column.traceId ?? event.traceId ?? null, messageId: cell.messageId ?? column.messageId ?? null, valuesRedacted: true }); } previousRecent = { value: recent, seq: row.seq ?? null, ts: row.ts ?? null }; } } } return { timingChanges, recentUpdateVisible }; } function dedupeRoundCompletionEvents(rows) { const result = []; const seen = new Set(); for (const row of Array.isArray(rows) ? rows : []) { const key = [ row?.pageRole ?? "", row?.pageId ?? "", row?.promptIndex ?? "", row?.traceId ?? "", row?.messageId ?? "", row?.textHash ?? "", row?.elapsedSeconds ?? "" ].join("|"); if (seen.has(key)) continue; seen.add(key); result.push(row); } return result; } function eventRef(event) { return { seq: event?.seq ?? null, sampleGroupSeq: event?.sampleGroupSeq ?? null, ts: event?.ts ?? null, pageRole: event?.pageRole ?? null, pageId: event?.pageId ?? null, routeSessionId: event?.routeSessionId ?? null, activeSessionId: event?.activeSessionId ?? null, promptIndex: event?.promptIndex ?? null, traceId: event?.traceId ?? null, messageId: event?.messageId ?? null, }; } function dedupeRoundCompletionRows(rows) { const result = []; const seen = new Set(); for (const row of Array.isArray(rows) ? rows : []) { const key = [ row?.seq ?? row?.fromSeq ?? "", row?.toSeq ?? "", row?.pageRole ?? "", row?.promptIndex ?? "", row?.traceId ?? "", row?.metric ?? "", row?.textHash ?? "", row?.columnId ?? "" ].join("|"); if (seen.has(key)) continue; seen.add(key); result.push(row); } return result; } function turnMetricItems(sample, timelineItem) { const promptIndex = timelineItem.promptIndex ?? 0; const pageRole = sample?.pageRole || "control"; const pageId = sample?.pageId || "unknown-page"; const sessionKey = pageRole + ":" + pageId + ":" + (sample?.routeSessionId || sample?.activeSessionId || "unknown-session"); const roundKey = String(promptIndex); const items = []; if (Array.isArray(sample?.turns) && sample.turns.length > 0) { for (const turn of sample.turns) { const texts = turnTexts(turn); const totalElapsedValues = texts.flatMap(parseTotalElapsedSeconds).filter(Number.isFinite); 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 = stableId ? "timing:" + sessionKey + ":id-" + stableId : "turn:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex); items.push({ key, source: "turn", pageRole, pageId, promptIndex, traceId, messageId, turnId, domIndex, status: turn.status ?? null, totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null, recentUpdateSeconds: recentUpdateValues.length > 0 ? Math.max(...recentUpdateValues) : null, textHash: turn.textHash || sha256(texts.join("\n")) }); } return items; } if (Array.isArray(sample?.messages) && sample.messages.length > 0) { for (const message of sample.messages) { const text = [message?.durationText, message?.activityText].map((value) => String(value || "")).filter((value) => value.trim().length > 0).join("\n"); const totalElapsedValues = parseTotalElapsedSeconds(text).filter(Number.isFinite); 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: stableId ? "timing:" + sessionKey + ":id-" + stableId : "message:" + sessionKey + ":round-" + roundKey + ":dom-index-" + String(domIndex), source: "message", pageRole, pageId, promptIndex, traceId, messageId, turnId: message.turnId || traceId || null, domIndex, status: message.status ?? null, totalElapsedSeconds: totalElapsedValues.length > 0 ? Math.max(...totalElapsedValues) : null, recentUpdateSeconds: recentUpdateValues.length > 0 ? Math.max(...recentUpdateValues) : null, textHash: message.textHash || sha256(text) }); } if (items.length > 0) return items; } if (timelineItem.totalElapsedSeconds !== null || timelineItem.recentUpdateSeconds !== null) { return [{ key: "aggregate:" + sessionKey + ":round-" + roundKey, source: "aggregate", pageRole, pageId, promptIndex, traceId: null, messageId: null, domIndex: null, status: null, totalElapsedSeconds: timelineItem.totalElapsedSeconds ?? null, recentUpdateSeconds: timelineItem.recentUpdateSeconds ?? null, textHash: timelineItem.textDigest ?? null }]; } return []; } function turnTexts(turn) { return [ turn?.durationText, turn?.activityText ].map((value) => String(value || "")).filter((value) => value.trim().length > 0); } function firstTraceId(texts) { for (const text of texts) { const match = String(text || "").match(/\btrc_[A-Za-z0-9_-]+\b/u); if (match) return match[0]; } return null; } function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) { const rounds = []; const timingColumns = Array.isArray(timing.columns) ? timing.columns : []; const timingRows = Array.isArray(timing.rows) ? timing.rows : []; const nonMonotonic = Array.isArray(timing.nonMonotonic) ? timing.nonMonotonic : []; const totalElapsedForwardJumps = Array.isArray(timing.totalElapsedForwardJumps) ? timing.totalElapsedForwardJumps : []; const elapsedZeroResets = Array.isArray(timing.elapsedZeroResets) ? timing.elapsedZeroResets : []; const terminalElapsedGrowth = Array.isArray(timing.terminalElapsedGrowth) ? timing.terminalElapsedGrowth : []; const recentUpdateResets = Array.isArray(timing.recentUpdateResets) ? timing.recentUpdateResets : []; const recentUpdateSteps = Array.isArray(timing.recentUpdateSteps) ? timing.recentUpdateSteps : []; for (let index = 0; index < promptCommands.length; index += 1) { const promptIndex = index + 1; const items = timeline.filter((item) => item.promptIndex === promptIndex); const aggregateTotalElapsed = items.map((item) => item.totalElapsedSeconds).filter((value) => value !== null); const aggregateRecentUpdate = items.map((item) => item.recentUpdateSeconds).filter((value) => value !== null); const promptTurnTiming = roundPromptTurnTimingValues(timingRows, timingColumns, promptIndex, { firstSeq: items[0]?.seq ?? null, firstSampleAt: items[0]?.ts ?? null }); const totalElapsed = promptTurnTiming.totalElapsed.length > 0 ? promptTurnTiming.totalElapsed : aggregateTotalElapsed; const recentUpdate = promptTurnTiming.recentUpdate.length > 0 ? promptTurnTiming.recentUpdate : aggregateRecentUpdate; const loadingCounts = items.map((item) => Number(item.loadingCount ?? 0)).filter(Number.isFinite); const loadingOwners = new Set(); for (const item of items) { for (const owner of Array.isArray(item.loadingOwners) ? item.loadingOwners : []) { if (owner?.ownerKey) loadingOwners.add(owner.ownerKey); } } const timingAnomalies = nonMonotonic.filter((item) => item.promptIndex === promptIndex); const timingForwardJumps = totalElapsedForwardJumps.filter((item) => item.promptIndex === promptIndex); const timingZeroResets = elapsedZeroResets.filter((item) => item.promptIndex === promptIndex); const timingTerminalGrowth = terminalElapsedGrowth.filter((item) => item.promptIndex === promptIndex); const timingResets = recentUpdateResets.filter((item) => item.promptIndex === promptIndex); const timingSteps = recentUpdateSteps.filter((item) => item.promptIndex === promptIndex); const terminalGrowthDeltas = timingTerminalGrowth.map((item) => Number(item.delta)).filter((value) => Number.isFinite(value) && value > 0); const timingStepDeltas = timingSteps.map((item) => Number(item.delta)).filter((value) => Number.isFinite(value) && value >= 0); const timingStepExcess = timingSteps.map((item) => Number(item.excessiveIncreaseSeconds)).filter((value) => Number.isFinite(value) && value > 0); rounds.push({ promptIndex, promptCommandId: promptCommands[index].commandId, promptTextHash: promptCommands[index].textHash, promptTextBytes: promptCommands[index].textBytes, promptCompletedAt: promptCommands[index].ts, sampleCount: items.length, firstSeq: items[0]?.seq ?? null, lastSeq: items[items.length - 1]?.seq ?? null, firstSampleAt: items[0]?.ts ?? null, lastSampleAt: items[items.length - 1]?.ts ?? null, withTotalElapsed: totalElapsed.length, withRecentUpdate: recentUpdate.length, loadingSamples: loadingCounts.filter((value) => value > 0).length, maxLoadingCount: loadingCounts.length > 0 ? Math.max(...loadingCounts) : 0, loadingOwnerCount: loadingOwners.size, maxTotalElapsedSeconds: totalElapsed.length > 0 ? Math.max(...totalElapsed) : null, lastTotalElapsedSeconds: lastNonNull(totalElapsed), maxRecentUpdateSeconds: recentUpdate.length > 0 ? Math.max(...recentUpdate) : null, lastRecentUpdateSeconds: lastNonNull(recentUpdate), diagnosticSamples: items.filter((item) => item.diagnosticSeen).length, terminalSamples: items.filter((item) => item.terminalSeen).length, finalTextSamples: items.filter((item) => item.finalResultTextSeen).length, turnTimingNonMonotonicCount: timingAnomalies.length, turnTimingTotalElapsedDecreaseCount: timingAnomalies.filter((item) => item.metric === "totalElapsedSeconds").length, turnTimingTotalElapsedZeroResetCount: timingZeroResets.length, turnTimingTotalElapsedForwardJumpCount: timingForwardJumps.length, turnTimingTotalElapsedForwardJumpMaxSeconds: maxPositiveDelta(timingForwardJumps), turnTimingTerminalElapsedGrowthCount: timingTerminalGrowth.length, turnTimingTerminalElapsedGrowthMaxSeconds: terminalGrowthDeltas.length > 0 ? Math.max(...terminalGrowthDeltas) : 0, turnTimingRecentUpdateJumpCount: timingAnomalies.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump").length, turnTimingRecentUpdateSawtoothJumpCount: timingAnomalies.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump").length, turnTimingRecentUpdateStepCount: timingSteps.length, turnTimingRecentUpdateMaxIncreaseSeconds: timingStepDeltas.length > 0 ? Math.max(...timingStepDeltas) : null, turnTimingRecentUpdateMaxExcessSeconds: timingStepExcess.length > 0 ? Math.max(...timingStepExcess) : 0, turnTimingRecentUpdateResetCount: timingResets.length, turnTimingRecentUpdateDecreaseCount: timingResets.length }); } return rounds; } function roundPromptTurnTimingValues(rows, columns, promptIndex, round = {}) { const roundFirstSeq = Number(round.firstSeq); const roundFirstMs = Date.parse(String(round.firstSampleAt ?? "")); const seqSlack = 3; const timeSlackMs = 3000; const promptColumnIds = new Set(columns .filter((column) => { if (Number(column?.promptIndex) === Number(promptIndex)) return true; const firstSeq = Number(column?.firstSeq); if (Number.isFinite(roundFirstSeq) && Number.isFinite(firstSeq) && firstSeq >= roundFirstSeq - seqSlack) return true; const firstMs = Date.parse(String(column?.firstTs ?? "")); return Number.isFinite(roundFirstMs) && Number.isFinite(firstMs) && firstMs >= roundFirstMs - timeSlackMs; }) .map((column) => String(column?.id || "")) .filter(Boolean)); if (promptColumnIds.size === 0) return { totalElapsed: [], recentUpdate: [] }; const totalElapsed = []; const recentUpdate = []; for (const row of rows) { if (Number(row?.promptIndex ?? 0) !== Number(promptIndex)) continue; const cells = row?.cells && typeof row.cells === "object" ? row.cells : {}; for (const [columnId, cell] of Object.entries(cells)) { if (!promptColumnIds.has(columnId)) continue; const total = Number(cell?.totalElapsedSeconds); if (Number.isFinite(total)) totalElapsed.push(total); const recent = Number(cell?.recentUpdateSeconds); if (Number.isFinite(recent)) recentUpdate.push(recent); } } return { totalElapsed, recentUpdate }; } function sampleTexts(sample) { const rows = []; for (const group of [sample?.messages, sample?.traceRows, sample?.diagnostics]) { if (!Array.isArray(group)) continue; for (const item of group) { const text = String(item?.textPreview || ""); if (text.trim()) rows.push(text); } } return rows; } function sampleTurnTimingTexts(sample) { const rows = []; for (const group of [sample?.turns, sample?.messages]) { if (!Array.isArray(group)) continue; for (const item of group) { for (const value of [item?.durationText, item?.activityText]) { const text = String(value || ""); if (text.trim()) rows.push(text); } } } return rows; } function parseTotalElapsedSeconds(text) { const values = []; for (const match of String(text || "").matchAll(/(?:总耗时|耗时)\s*[=::]?\s*(\d{1,2}):(\d{2}):(\d{2})/giu)) { values.push(Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3])); } for (const match of String(text || "").matchAll(/(?:总耗时|耗时)\s*[=::]?\s*(\d{1,2}):(\d{2})(?!:)/giu)) { values.push(Number(match[1]) * 60 + Number(match[2])); } for (const match of String(text || "").matchAll(/(?:总耗时|耗时)\s*[=::]?\s*(?:(\d+)\s*天)?\s*(?:(\d+)\s*小时)?\s*(?:(\d+)\s*(?:分钟|分))?\s*(?:(\d+)\s*秒)?/giu)) { const days = Number(match[1] || 0); const hours = Number(match[2] || 0); const minutes = Number(match[3] || 0); const seconds = Number(match[4] || 0); if (days || hours || minutes || seconds || /(?:天|小时|分钟|分|秒)/u.test(match[0] || "")) values.push(days * 86400 + hours * 3600 + minutes * 60 + seconds); } return values; } function lastNonNull(values) { for (let index = values.length - 1; index >= 0; index -= 1) if (values[index] !== null && values[index] !== undefined) return values[index]; return null; } function parseRecentUpdateSeconds(text) { const values = []; for (const match of String(text || "").matchAll(/最近\s*(?:(\d+)\s*天)?\s*(?:(\d+)\s*小时)?\s*(?:(\d+)\s*(?:分钟|分))?\s*(?:(\d+)\s*秒)?\s*前/giu)) { const days = Number(match[1] || 0); const hours = Number(match[2] || 0); const minutes = Number(match[3] || 0); const seconds = Number(match[4] || 0); values.push(days * 86400 + hours * 3600 + minutes * 60 + seconds); } return values; } function latestPromptIndex(promptTimes, tsMs) { let index = 0; for (let i = 0; i < promptTimes.length; i += 1) { if (promptTimes[i] <= tsMs) index = i + 1; else break; } return index; } function promptIndexForTs(promptTimes, ts) { const tsMs = Date.parse(ts); return Number.isFinite(tsMs) ? latestPromptIndex(promptTimes, tsMs) : 0; } function buildTransitions(samples) { const rows = []; let last = null; for (const sample of samples) { const digest = digestSample(sample); if (digest !== last) { rows.push({ seq: sample.seq, ts: sample.ts, url: sample.url, routeSessionId: sample.routeSessionId || null, activeSessionId: sample.activeSessionId || null, messageCount: Array.isArray(sample.messages) ? sample.messages.length : 0, traceRowCount: Array.isArray(sample.traceRows) ? sample.traceRows.length : 0, digest }); last = digest; } } return rows.slice(0, 200); } function detectFinalFlicker(samples) { const flickers = []; const lastNonEmptyByScope = new Map(); for (const sample of samples) { const scope = finalFlickerScope(sample); if (!scope) continue; const messageText = Array.isArray(sample.messages) ? sample.messages.map((item) => item.textPreview || "").join("\n") : ""; const nonEmpty = messageText.trim().length > 0; const finalLike = /轮次完成|已记录|已完成第\d+轮|final response|terminal result/iu.test(messageText); const diagnosticLike = /temporarily|timeout|无法连接|暂时|error|failed|超时/iu.test(messageText); const lastNonEmpty = lastNonEmptyByScope.get(scope); if (nonEmpty && finalLike && !diagnosticLike) lastNonEmptyByScope.set(scope, { sample, messageText }); if (lastNonEmpty && nonEmpty && diagnosticLike) flickers.push({ scope, from: ref(lastNonEmpty.sample), to: ref(sample) }); if (lastNonEmpty && !nonEmpty) flickers.push({ scope, from: ref(lastNonEmpty.sample), to: ref(sample), reason: "text-disappeared" }); } return flickers; } function finalFlickerScope(sample) { const pathname = samplePathname(sample); const sessionId = sample?.routeSessionId || sample?.activeSessionId || workbenchSessionIdFromPath(pathname); if (!sessionId) return null; if (!pathname.startsWith("/workbench/sessions/" + sessionId)) return null; return (sample?.pageRole || "control") + ":" + sessionId; } function samplePathname(sample) { const raw = String(sample?.path || sample?.url || "").trim(); if (!raw) return ""; try { return new URL(raw, "http://hwlab.local").pathname || raw; } catch { return raw.split(/[?#]/u, 1)[0] || raw; } } function workbenchSessionIdFromPath(pathname) { const match = String(pathname || "").match(/^\/workbench\/sessions\/([^/?#]+)/u); return match ? match[1] : null; } function detectTerminalZeroElapsed(samples) { const rows = []; for (const sample of samples) { const turns = Array.isArray(sample?.turns) ? sample.turns : []; const messages = Array.isArray(sample?.messages) ? sample.messages : []; for (const item of [...turns, ...messages]) { const text = [item?.durationText, item?.activityText, item?.status, item?.textPreview, item?.text].filter(Boolean).join("\n"); if (!/(?:Code Agent|运行记录|completed|failed|canceled|blocked)/iu.test(text)) continue; if (!/(?:completed|failed|canceled|blocked|已完成|失败|取消)/iu.test(text)) continue; const timingText = [item?.durationText, item?.activityText].filter(Boolean).join("\n"); const elapsedValues = parseTotalElapsedSeconds(timingText); if (!elapsedValues.includes(0)) continue; rows.push({ ...ref(sample), status: item?.status ?? null, messageId: item?.messageId ?? null, traceId: item?.traceId ?? null, durationText: item?.durationText ?? null, activityText: item?.activityText ?? null, textPreview: limitText(item?.textPreview || item?.text || "", 180) }); } } return rows; } function detectCrossPageProjectionDiffs(samples) { const groups = new Map(); for (const sample of samples) { const key = sample?.sampleGroupSeq ?? sample?.seq; if (key === null || key === undefined) continue; const group = groups.get(key) || {}; if (sample?.pageRole === "control") group.control = sample; else if (sample?.pageRole === "observer") group.observer = sample; groups.set(key, group); } const rows = []; for (const [sampleGroupSeq, group] of groups.entries()) { const control = group.control; const observer = group.observer; if (!control || !observer) continue; const controlTraceIds = visibleTraceIds(control); const observerTraceIds = visibleTraceIds(observer); const missingInObserver = [...controlTraceIds].filter((item) => !observerTraceIds.has(item)); const controlMessages = Array.isArray(control.messages) ? control.messages.length : 0; const observerMessages = Array.isArray(observer.messages) ? observer.messages.length : 0; const controlTraceRows = Array.isArray(control.traceRows) ? control.traceRows.length : 0; const observerTraceRows = Array.isArray(observer.traceRows) ? observer.traceRows.length : 0; const controlZero = detectTerminalZeroElapsed([control]).length > 0; const observerZero = detectTerminalZeroElapsed([observer]).length > 0; const sameSession = (control.routeSessionId || control.activeSessionId || null) && (control.routeSessionId || control.activeSessionId || null) === (observer.routeSessionId || observer.activeSessionId || null); const controlMessageDigest = digestMessageTexts(control); const observerMessageDigest = digestMessageTexts(observer); const messageTextDigestDiff = controlMessageDigest !== observerMessageDigest; const projectionDivergent = sameSession && (Math.abs(controlMessages - observerMessages) > 0 || controlZero !== observerZero); const traceVisibilityDivergent = sameSession && !projectionDivergent && (missingInObserver.length > 0 || Math.abs(controlTraceRows - observerTraceRows) > 0); if (!projectionDivergent && !traceVisibilityDivergent) continue; rows.push({ sampleGroupSeq, diffKind: traceVisibilityDivergent ? "trace-visibility" : "projection", control: ref(control), observer: ref(observer), controlTraceIds: [...controlTraceIds].slice(0, 8), observerTraceIds: [...observerTraceIds].slice(0, 8), missingTraceIdsInObserver: missingInObserver.slice(0, 8), controlMessageCount: controlMessages, observerMessageCount: observerMessages, controlTraceRowCount: controlTraceRows, observerTraceRowCount: observerTraceRows, terminalZeroElapsedDiff: controlZero !== observerZero, messageTextDigestDiff, controlMessageDigest, observerMessageDigest }); } return rows; } function mergeCrossPageDiffRows(...groups) { const rows = []; const seen = new Set(); for (const group of groups) { if (!Array.isArray(group)) continue; for (const row of group) { const key = [ row?.diffKind || "projection", row?.control?.seq ?? null, row?.observer?.seq ?? null, row?.controlMessageCount ?? null, row?.observerMessageCount ?? null, row?.controlTraceRowCount ?? null, row?.observerTraceRowCount ?? null ].join(":"); if (seen.has(key)) continue; seen.add(key); rows.push(row); } } return rows; } function annotateCrossPageDiffTiming(rows) { const groups = new Map(); for (const row of Array.isArray(rows) ? rows : []) { const controlAt = Date.parse(String(row?.control?.ts || "")); const observerAt = Date.parse(String(row?.observer?.ts || "")); const timestamps = [controlAt, observerAt].filter(Number.isFinite); const startMs = timestamps.length > 0 ? Math.min(...timestamps) : null; const endMs = timestamps.length > 0 ? Math.max(...timestamps) : null; const sessionId = row?.control?.routeSessionId || row?.control?.activeSessionId || row?.observer?.routeSessionId || row?.observer?.activeSessionId || "unknown-session"; const key = [row?.diffKind || "projection", sessionId].join(":"); const group = groups.get(key) || { rows: [], firstMs: null, lastMs: null }; const annotated = { ...row, sampleStartAt: startMs === null ? null : new Date(startMs).toISOString(), sampleEndAt: endMs === null ? null : new Date(endMs).toISOString(), pairSkewMs: startMs === null || endMs === null ? null : endMs - startMs, }; group.rows.push(annotated); if (startMs !== null && (group.firstMs === null || startMs < group.firstMs)) group.firstMs = startMs; if (endMs !== null && (group.lastMs === null || endMs > group.lastMs)) group.lastMs = endMs; groups.set(key, group); } const result = []; for (const group of groups.values()) { const sortedRows = group.rows.slice().sort((a, b) => Number(Date.parse(String(a.sampleStartAt || ""))) - Number(Date.parse(String(b.sampleStartAt || "")))); const segments = []; const splitGapMs = Math.max(1000, Number(alertThresholds.crossPageProjectionDivergenceRedMs || alertThresholds.visibleLoadingSlowMs || 10_000)); for (const row of sortedRows) { const startMs = Date.parse(String(row.sampleStartAt || "")); const endMs = Date.parse(String(row.sampleEndAt || row.sampleStartAt || "")); const last = segments.at(-1); const lastEndMs = last && last.lastMs !== null ? last.lastMs : null; if (!last || (Number.isFinite(startMs) && lastEndMs !== null && startMs - lastEndMs > splitGapMs)) { segments.push({ rows: [row], firstMs: Number.isFinite(startMs) ? startMs : null, lastMs: Number.isFinite(endMs) ? endMs : Number.isFinite(startMs) ? startMs : null }); continue; } last.rows.push(row); if (Number.isFinite(startMs) && (last.firstMs === null || startMs < last.firstMs)) last.firstMs = startMs; const effectiveEndMs = Number.isFinite(endMs) ? endMs : Number.isFinite(startMs) ? startMs : null; if (effectiveEndMs !== null && (last.lastMs === null || effectiveEndMs > last.lastMs)) last.lastMs = effectiveEndMs; } for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex += 1) { const segment = segments[segmentIndex]; const observedSpanMs = segment.firstMs === null || segment.lastMs === null ? null : segment.lastMs - segment.firstMs; for (const row of segment.rows) { result.push({ ...row, segmentIndex, observedFirstAt: segment.firstMs === null ? null : new Date(segment.firstMs).toISOString(), observedLastAt: segment.lastMs === null ? null : new Date(segment.lastMs).toISOString(), observedSpanMs, }); } } } return result; } function detectAdjacentCrossPageProjectionDiffs(samples) { const rows = []; const ordered = (Array.isArray(samples) ? samples : []).slice().sort((a, b) => Number(a?.seq ?? 0) - Number(b?.seq ?? 0)); for (let i = 1; i < ordered.length; i += 1) { const a = ordered[i - 1]; const b = ordered[i]; const roles = new Set([a?.pageRole, b?.pageRole]); if (!roles.has("control") || !roles.has("observer")) continue; const control = a?.pageRole === "control" ? a : b; const observer = a?.pageRole === "observer" ? a : b; const controlSession = control.routeSessionId || control.activeSessionId || null; const observerSession = observer.routeSessionId || observer.activeSessionId || null; if (!controlSession || controlSession !== observerSession) continue; const controlAt = Date.parse(String(control.ts || "")); const observerAt = Date.parse(String(observer.ts || "")); if (Number.isFinite(controlAt) && Number.isFinite(observerAt) && Math.abs(controlAt - observerAt) > 1500) continue; const controlMessages = Array.isArray(control.messages) ? control.messages.length : 0; const observerMessages = Array.isArray(observer.messages) ? observer.messages.length : 0; const controlTraceRows = Array.isArray(control.traceRows) ? control.traceRows.length : 0; const observerTraceRows = Array.isArray(observer.traceRows) ? observer.traceRows.length : 0; const controlZero = detectTerminalZeroElapsed([control]).length > 0; const observerZero = detectTerminalZeroElapsed([observer]).length > 0; const missingInObserver = [...visibleTraceIds(control)].filter((item) => !visibleTraceIds(observer).has(item)); const controlTraceIds = visibleTraceIds(control); const observerTraceIds = visibleTraceIds(observer); const controlMessageDigest = digestMessageTexts(control); const observerMessageDigest = digestMessageTexts(observer); const messageTextDigestDiff = controlMessageDigest !== observerMessageDigest; const projectionDivergent = controlMessages !== observerMessages || controlZero !== observerZero; const traceVisibilityDivergent = !projectionDivergent && (missingInObserver.length > 0 || controlTraceRows !== observerTraceRows); if (!projectionDivergent && !traceVisibilityDivergent) continue; rows.push({ sampleGroupSeq: control.sampleGroupSeq ?? observer.sampleGroupSeq ?? null, adjacentPair: true, diffKind: traceVisibilityDivergent ? "trace-visibility" : "projection", control: ref(control), observer: ref(observer), controlTraceIds: [...controlTraceIds].slice(0, 8), observerTraceIds: [...observerTraceIds].slice(0, 8), missingTraceIdsInObserver: missingInObserver.slice(0, 8), controlMessageCount: controlMessages, observerMessageCount: observerMessages, controlTraceRowCount: controlTraceRows, observerTraceRowCount: observerTraceRows, terminalZeroElapsedDiff: controlZero !== observerZero, messageTextDigestDiff, controlMessageDigest, observerMessageDigest }); } return rows; } function detectTraceMessageDuplication(samples) { const rows = []; for (const sample of samples) { const traceRows = Array.isArray(sample?.traceRows) ? sample.traceRows : []; const groups = new Map(); for (let fallbackIndex = 0; fallbackIndex < traceRows.length; fallbackIndex += 1) { const row = traceRows[fallbackIndex]; const rowTextRaw = String(row?.textPreview || row?.text || ""); if (!/(?:助手消息|assistant\s+message|assistant)/iu.test(rowTextRaw)) continue; const rowText = normalizedText(rowTextRaw); if (rowText.length < 24) continue; const traceId = row?.traceId === undefined || row?.traceId === null ? "" : String(row.traceId); const key = traceId + "\u0000" + rowText; const group = groups.get(key) ?? { traceId: traceId || null, rowText, rowTextRaw, rows: [] }; group.rows.push({ row, fallbackIndex }); groups.set(key, group); } for (const group of groups.values()) { if (group.rows.length < 2) continue; rows.push({ ...ref(sample), traceId: group.traceId, visibleAssistantFinalRowCount: group.rows.length, rowIndexes: group.rows.map((item) => item.row?.index ?? item.fallbackIndex).slice(0, 12), rowTextHash: sha256(group.rowText), rowTextPreview: limitText(group.rowTextRaw, 180), finalResponseSummaryBlockCounted: false, traceFrameSource: "traceRows-only" }); } } return rows; } function detectTurnTraceIdMissing(samples) { const rows = []; for (const sample of samples) { for (const item of Array.isArray(sample?.turns) ? sample.turns : []) { const text = [item?.status, item?.durationText, item?.activityText, item?.textPreview, item?.text].filter(Boolean).join("\n"); if (!/(?:Code Agent|运行记录|耗时|最近)/iu.test(text)) continue; if (item?.traceId) continue; rows.push({ ...ref(sample), status: item?.status ?? null, messageId: item?.messageId ?? null, textPreview: limitText(item?.textPreview || item?.text || "", 180) }); } } return rows; } function visibleTraceIds(sample) { const ids = new Set(); for (const group of [sample?.turns, sample?.messages, sample?.traceRows]) { if (!Array.isArray(group)) continue; for (const item of group) if (item?.traceId) ids.add(String(item.traceId)); } return ids; } function digestMessageTexts(sample) { return sha256((Array.isArray(sample?.messages) ? sample.messages : []).map((item) => item?.textHash || normalizedText(item?.textPreview || item?.text || "")).join("|")); } function normalizedText(value) { return String(value || "").replace(/\s+/gu, " ").trim(); } function longestSharedSubstringLength(a, b) { if (!a || !b) return 0; const left = a.length <= b.length ? a : b; const right = a.length <= b.length ? b : a; const max = Math.min(left.length, 280); let best = 0; for (let start = 0; start < max; start += 1) { for (let end = Math.min(max, start + 160); end > start + best; end -= 1) { if (right.includes(left.slice(start, end))) { best = end - start; break; } } } return best; } function digestSample(sample) { const messages = Array.isArray(sample.messages) ? sample.messages.map((item) => stableDigestItem(item, ["dataRole", "role", "status", "sessionId", "messageId", "traceId", "turnId"])).join("|") : ""; const trace = Array.isArray(sample.traceRows) ? sample.traceRows.map((item) => stableDigestItem(item, ["status", "sessionId", "messageId", "traceId", "turnId", "projectedSeq", "sourceSeq", "eventSeq", "eventKind"])).join("|") : ""; const diagnostics = Array.isArray(sample.diagnostics) ? sample.diagnostics.map((item) => stableDigestItem(item, ["className", "status", "sessionId", "messageId", "traceId", "turnId", "diagnosticCode"])).join("|") : ""; return sha256((sample.routeSessionId || "") + "|" + (sample.activeSessionId || "") + "|" + messages + "|" + trace + "|" + diagnostics); } function samplePageKey(sample) { return String(sample?.pageRole || "control") + ":" + String(sample?.pageId || "default"); } function stableDigestItem(item, fields) { if (!item || typeof item !== "object") return ""; const identity = fields.map((field) => String(item?.[field] ?? "")).join(":"); return identity + ":" + stableVisibleDigestText(item?.textPreview || item?.text || item?.textHash || ""); } function stableVisibleDigestText(value) { return normalizedText(value) .replace(/\btotal=\d{1,2}:\d{2}:\d{2}\b/giu, "total=") .replace(/(?:总耗时|耗时)\s*[=::]?\s*(?:(?:\d+\s*天)?\s*(?:\d+\s*小时)?\s*(?:\d+\s*(?:分钟|分))?\s*(?:\d+\s*秒)?|\d{1,2}:\d{2}(?::\d{2})?)/giu, "耗时 ") .replace(/最近\s*(?:(?:\d+\s*天)?\s*(?:\d+\s*小时)?\s*(?:\d+\s*(?:分钟|分))?\s*(?:\d+\s*秒)?)\s*前/giu, "最近 前"); } function nearCommand(sample, commandTimes, windowMs) { const ts = Date.parse(sample.ts); return Number.isFinite(ts) && commandTimes.some((item) => Math.abs(ts - item) <= windowMs); } function sampleRefs(samples, pick) { const seen = new Set(); const refs = []; for (const sample of samples) { const value = pick(sample); if (!value || seen.has(value)) continue; seen.add(value); refs.push({ ...ref(sample), value }); } return refs.slice(0, 20); } function ref(sample) { if (!sample) return null; return { seq: sample.seq ?? null, sampleGroupSeq: sample.sampleGroupSeq ?? null, ts: sample.ts ?? null, pageRole: sample.pageRole ?? null, pageId: sample.pageId ?? null, url: sample.url ?? null, routeSessionId: sample.routeSessionId ?? null, activeSessionId: sample.activeSessionId ?? null }; } async function artifactSummary(artifacts) { const items = artifacts.slice(-30).map((item) => ({ kind: item.kind, reason: item.reason, sampleSeq: item.sampleSeq, path: item.path, sha256: item.sha256, byteCount: item.byteCount })); return { count: artifacts.length, latest: items }; } function compactManifest(value) { if (!value) return null; return { jobId: value.jobId, stateDir: value.stateDir, baseUrl: value.baseUrl, targetPath: value.targetPath, startedAt: value.startedAt, status: value.status, pageAuthority: value.pageAuthority ?? null, navigation: value.navigation ?? null, sampling: value.sampling, pageProvenance: value.pageProvenance ?? null, safety: value.safety }; } function compactHeartbeat(value) { if (!value) return null; return { jobId: value.jobId, pid: value.pid, status: value.status, sampleSeq: value.sampleSeq, commandSeq: value.commandSeq, pageId: value.pageId ?? null, observerPageId: value.observerPageId ?? null, currentUrl: value.currentUrl, observerUrl: value.observerUrl ?? null, observerRefreshIntervalMs: value.observerRefreshIntervalMs ?? null, lastObserverRefreshAt: value.lastObserverRefreshAt ?? null, pageProvenance: value.pageProvenance ?? null, updatedAt: value.updatedAt, uptimeMs: value.uptimeMs }; } function renderTurnTimingTable(sampleMetrics) { const columns = Array.isArray(sampleMetrics?.turnColumns) ? sampleMetrics.turnColumns : []; const rows = Array.isArray(sampleMetrics?.turnTimingTable) ? sampleMetrics.turnTimingTable : []; const disclosure = sampleMetrics?.turnTimingTableDisclosure || null; if (columns.length === 0 || rows.length === 0) return "- 无 turn 时间表。"; const header = ["时间戳"]; for (const column of columns) { const columnLabel = formatTurnColumnDisplayLabel(column); header.push(columnLabel + " 总耗时(s)"); header.push(columnLabel + " 最近更新(s)"); } const lines = []; lines.push("| " + header.map(escapeMarkdownCell).join(" | ") + " |"); lines.push("| " + header.map(() => "---").join(" | ") + " |"); for (const row of rows) { const cells = [row.ts || "-"]; for (const column of columns) { const cell = row.cells?.[column.id] || {}; cells.push(formatMetricCell(cell.totalElapsedSeconds)); cells.push(formatMetricCell(cell.recentUpdateSeconds)); } lines.push("| " + cells.map(escapeMarkdownCell).join(" | ") + " |"); } const columnLines = columns.map((column) => "- " + formatTurnColumnDisplayLabel(column) + ": pageRole=" + (column.pageRole || "-") + " pageId=" + (column.pageId || "-") + " source=" + (column.source || "-") + " prompt=" + (column.promptIndex ?? "-") + " lastPrompt=" + (column.lastPromptIndex ?? "-") + " firstSeq=" + (column.firstSeq ?? "-") + " lastSeq=" + (column.lastSeq ?? "-") + " traceId=" + (column.traceId || "-") + " messageId=" + (column.messageId || "-")).join("\n"); const nonMonotonic = Array.isArray(sampleMetrics?.turnTimingNonMonotonic) ? sampleMetrics.turnTimingNonMonotonic : []; const nonMonotonicLines = nonMonotonic.length > 0 ? nonMonotonic.slice(0, 80).map((item) => "- " + formatTurnEventDisplayLabel(item) + " " + item.metric + (item.anomaly ? " " + item.anomaly : "") + " " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + formatTimingMeta(item) + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到总耗时下降或最近更新异常跳增。"; const terminalGrowth = Array.isArray(sampleMetrics?.turnTimingTerminalElapsedGrowth) ? sampleMetrics.turnTimingTerminalElapsedGrowth : []; const terminalGrowthLines = terminalGrowth.length > 0 ? terminalGrowth.slice(0, 80).map((item) => "- " + formatTurnEventDisplayLabel(item) + " terminal totalElapsed growth " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " status " + (item.fromStatus || "-") + " -> " + (item.toStatus || "-") + formatTimingMeta(item) + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到 terminal 后总耗时增长。"; const elapsedZeroResets = Array.isArray(sampleMetrics?.turnTimingElapsedZeroResets) ? sampleMetrics.turnTimingElapsedZeroResets : []; const elapsedZeroResetLines = elapsedZeroResets.length > 0 ? elapsedZeroResets.slice(0, 80).map((item) => "- " + formatTurnEventDisplayLabel(item) + " totalElapsed zero-reset " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + formatTimingMeta(item) + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到总耗时从非零跳回 0 秒。"; const totalElapsedForwardJumps = Array.isArray(sampleMetrics?.turnTimingTotalElapsedForwardJumps) ? sampleMetrics.turnTimingTotalElapsedForwardJumps : []; const totalElapsedForwardJumpLines = totalElapsedForwardJumps.length > 0 ? totalElapsedForwardJumps.slice(0, 80).map((item) => "- " + formatTurnEventDisplayLabel(item) + " totalElapsed forward-jump " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + formatTimingMeta(item) + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到总耗时超出采样间隔的异常前跳。"; const sawtoothJumps = Array.isArray(sampleMetrics?.turnTimingRecentUpdateSawtoothJumps) ? sampleMetrics.turnTimingRecentUpdateSawtoothJumps : nonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump"); const sawtoothJumpLines = sawtoothJumps.length > 0 ? sawtoothJumps.slice(0, 80).map((item) => "- " + formatTurnEventDisplayLabel(item) + " recentUpdate sawtooth-jump " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到最近更新三角波异常跳增。"; const recentUpdateSteps = Array.isArray(sampleMetrics?.turnTimingRecentUpdateLargestSteps) ? sampleMetrics.turnTimingRecentUpdateLargestSteps : Array.isArray(sampleMetrics?.turnTimingRecentUpdateSteps) ? sampleMetrics.turnTimingRecentUpdateSteps.filter((item) => Number.isFinite(Number(item.delta))).slice().sort((a, b) => Number(b.delta) - Number(a.delta)).slice(0, 200) : []; const stepLines = recentUpdateSteps.length > 0 ? recentUpdateSteps.slice(0, 80).map((item) => "- " + formatTurnEventDisplayLabel(item) + " recentUpdate step " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + " excess=" + (item.excessiveIncreaseSeconds ?? 0) + " event=" + (item.event || "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到最近更新相邻采样 step。"; const recentUpdateResets = Array.isArray(sampleMetrics?.turnTimingRecentUpdateResets) ? sampleMetrics.turnTimingRecentUpdateResets : []; const resetLines = recentUpdateResets.length > 0 ? recentUpdateResets.slice(0, 80).map((item) => "- " + formatTurnEventDisplayLabel(item) + " reset " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到最近更新归零/回落。"; const disclosureLine = disclosure?.truncated ? "表格披露:已按 head/tail 有界输出,totalRows=" + disclosure.totalRows + " includedRows=" + disclosure.includedRows + " omittedRows=" + disclosure.omittedRows + ";异常计数在截断前基于全量采样计算。" : "表格披露:完整输出当前分析窗口的采样点。"; return disclosureLine + "\n\n" + lines.join("\n") + "\n\n列说明:\n" + columnLines + "\n\n时间源说明:totalElapsed 异常以 DOM Code Agent card 采样序列为观察源;completion/final-response 可用时作为 sealed source-of-truth;status=business-turn-completed 表示 timing 仅为非阻塞告警,status=observer-timeout 表示观察等待超预算,status=scenario-incomplete 表示业务回合失败或未完成。\n\n异常事件(仅报表暴露,不做下游 repair;最近更新按三角波模型检测异常跳增):\n" + nonMonotonicLines + "\n\nTerminal 后总耗时增长事件(终态 turn 的总耗时应 sealed,不应继续增长):\n" + terminalGrowthLines + "\n\n总耗时归零跳变事件(例如已显示真实耗时后又变成 0 秒):\n" + elapsedZeroResetLines + "\n\n总耗时异常前跳事件(预期按采样间隔近似递增;例如 14 秒 -> 1137 秒应被列入这里):\n" + totalElapsedForwardJumpLines + "\n\n最近更新 sawtooth jump 事件(预期每秒增长约 1,遇到新活动归零;例如 1 秒 -> 1 分 4 秒应被列入这里):\n" + sawtoothJumpLines + "\n\n最近更新相邻采样 step(按 delta 降序;用于人工识别一秒跳几十秒/一分钟的瞬态):\n" + stepLines + "\n\n最近更新 reset 事件(预期三角波归零,不计为异常):\n" + resetLines; } function formatTurnColumnDisplayLabel(column) { const base = String(column?.label || column?.id || "-"); const role = String(column?.pageRole || "unknown"); const pageId = compactPageId(column?.pageId); return pageId ? base + "@" + role + "/" + pageId : base + "@" + role; } function formatTurnEventDisplayLabel(item) { const base = String(item?.columnLabel || item?.columnId || "-"); const role = String(item?.pageRole || "unknown"); const pageId = compactPageId(item?.pageId); return pageId ? base + "@" + role + "/" + pageId : base + "@" + role; } function compactPageId(value) { if (value === null || value === undefined || value === "") return ""; const text = String(value); if (text.length <= 18) return text; return text.slice(0, 12) + ".." + text.slice(-4); } function formatMetricCell(value) { if (value === null || value === undefined || !Number.isFinite(Number(value))) return "-"; return String(Number(value)); } function escapeMarkdownCell(value) { return String(value ?? "-").replace(/\|/gu, "\\|"); } function formatTimingMeta(item) { const source = item?.timingSourceOfTruth || item?.expectedElapsedSource || item?.evidenceKind || "-"; const status = item?.timingStatus || "-"; return " source=" + source + " status=" + status; } function renderMarkdown(report) { const findingLines = report.findings.length === 0 ? "- 无红灯项。" : report.findings.map((item) => "- " + item.severity + ": " + item.id + " - " + item.summary).join("\n"); const commandLines = report.commandTimeline.length === 0 ? "- 无控制命令。" : report.commandTimeline.map((item) => "- " + item.ts + " " + item.phase + " " + item.type + " " + item.commandId + " " + (item.afterUrl || "")).join("\n"); const commandFailureLines = Array.isArray(report.commandFailures) && report.commandFailures.length > 0 ? report.commandFailures.slice(0, 80).map((item) => "- " + (item.ts || "-") + " type=" + (item.type || "-") + " commandId=" + (item.commandId || "-") + " durationMs=" + (item.durationMs ?? "-") + " sampleSeq=" + (item.sampleSeq ?? "-") + " path=" + (item.beforePath || "-") + "->" + (item.afterPath || "-") + " message=" + escapeMarkdownCell(item.message || item.failureKind || item.name || "-")).join("\n") : "- 无失败控制命令。"; const transitionLines = report.transitions.length === 0 ? "- 无状态变化。" : report.transitions.slice(0, 80).map((item) => "- #" + item.seq + " " + item.ts + " messages=" + item.messageCount + " traceRows=" + item.traceRowCount + " route=" + (item.routeSessionId || "-") + " active=" + (item.activeSessionId || "-")).join("\n"); const metricSummary = report.sampleMetrics?.summary || {}; const loading = report.sampleMetrics?.loading || {}; const loadingSummary = loading.summary || {}; const sessionRailTitles = report.sampleMetrics?.sessionRailTitles || {}; const sessionRailTitleSummary = sessionRailTitles.summary || {}; const codeAgentCardTiming = report.sampleMetrics?.codeAgentCardTiming || {}; const codeAgentCardTimingSummary = codeAgentCardTiming.summary || {}; const roundCompletion = codeAgentCardTiming.roundCompletion || {}; const traceOrder = report.sampleMetrics?.traceOrder || {}; const traceOrderSummary = traceOrder.summary || {}; const alertSummary = report.runtimeAlerts?.summary || {}; const apiDomLag = report.apiDomLag || {}; const apiDomLagSummary = apiDomLag.summary || {}; const projectManagement = report.projectManagement || {}; const projectSummary = projectManagement.summary || {}; const projectCommandLines = Array.isArray(projectManagement.commands) && projectManagement.commands.length > 0 ? projectManagement.commands.slice(0, 40).map((item) => "- " + (item.ts || "-") + " " + (item.phase || "-") + " " + (item.type || "-") + " command=" + (item.commandId || "-") + " status=" + (item.launchStatus ?? "-") + " session=" + (item.sessionId || "-") + " otel=" + (item.otelTraceId || "-") + " taskHash=" + (item.selectedTaskRefHash || "-")).join("\n") : "- 无项目管理控制命令。"; const projectApiLines = Array.isArray(projectManagement.projectApiByPath) && projectManagement.projectApiByPath.length > 0 ? projectManagement.projectApiByPath.slice(0, 40).map((item) => "- " + (item.method || "-") + " " + (item.path || "-") + " type=" + (item.type || "-") + " status=" + (item.status ?? "-") + " count=" + (item.count ?? 0) + " first=" + (item.firstAt || "-") + " last=" + (item.lastAt || "-")).join("\n") : "- 无项目管理自然 API 记录。"; const projectSampleLines = Array.isArray(projectManagement.samples) && projectManagement.samples.length > 0 ? projectManagement.samples.slice(-40).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " role=" + (item.pageRole || "-") + " kind=" + (item.pageKind || "-") + " src=" + (item.sourceCount ?? "-") + " files=" + (item.fileCount ?? "-") + " tasks=" + (item.taskCount ?? "-") + " selectedTask=" + (item.selectedTaskRefHash || "-") + " launchEnabled=" + String(item.launchButtonEnabled === true) + " links=" + (item.workbenchLinkCount ?? 0)).join("\n") : "- 无项目管理 DOM 采样。"; const httpAlertLines = Array.isArray(report.runtimeAlerts?.networkHttpErrorsByPath) && report.runtimeAlerts.networkHttpErrorsByPath.length > 0 ? report.runtimeAlerts.networkHttpErrorsByPath.slice(0, 40).map((item) => "- HTTP " + (item.status ?? "-") + " " + item.method + " " + item.urlPath + " count=" + item.count + " prompts=" + (item.promptIndexes?.join(",") || "-") + " first=" + (item.firstAt || "-") + " last=" + (item.lastAt || "-")).join("\n") : "- 无 HTTP 错误。"; const requestFailedLines = Array.isArray(report.runtimeAlerts?.networkRequestFailedByPath) && report.runtimeAlerts.networkRequestFailedByPath.length > 0 ? report.runtimeAlerts.networkRequestFailedByPath.slice(0, 40).map((item) => "- requestfailed " + item.method + " " + item.urlPath + " count=" + item.count + " failure=" + (item.failureKinds?.slice(0, 4).join(",") || "-") + " prompts=" + (item.promptIndexes?.join(",") || "-") + " first=" + (item.firstAt || "-") + " last=" + (item.lastAt || "-")).join("\n") : "- 无 requestfailed。"; const domDiagnosticLines = Array.isArray(report.runtimeAlerts?.domDiagnostics) && report.runtimeAlerts.domDiagnostics.length > 0 ? report.runtimeAlerts.domDiagnostics.slice(0, 40).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " source=" + (item.source || "-") + " code=" + (item.diagnosticCode || "-") + " traceId=" + (item.traceId || "-") + " http=" + (item.httpStatus ?? "-") + " idle=" + (item.idleSeconds ?? "-") + " waitingFor=" + (item.waitingFor || "-") + " lastEventLabel=" + (item.lastEventLabel || "-") + " textHash=" + (item.textHash || "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n") : "- 无 DOM 诊断文本。"; const consoleAlertLines = Array.isArray(report.runtimeAlerts?.consoleAlerts) && report.runtimeAlerts.consoleAlerts.length > 0 ? report.runtimeAlerts.consoleAlerts.slice(0, 40).map((item) => "- " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " type=" + (item.type || "-") + " status=" + (item.status ?? "-") + " path=" + (item.urlPath || "-") + " traceId=" + (item.traceId || "-") + " textHash=" + (item.textHash || "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n") : "- 无 console warning/error。"; const consoleAlertGroupLines = Array.isArray(report.runtimeAlerts?.consoleAlertsByPath) && report.runtimeAlerts.consoleAlertsByPath.length > 0 ? report.runtimeAlerts.consoleAlertsByPath.slice(0, 40).map((item) => "- console " + (item.type || "-") + " status=" + (item.status ?? "-") + " path=" + (item.urlPath || "-") + " count=" + item.count + " prompts=" + (item.promptIndexes?.join(",") || "-") + " traces=" + (item.traceIds?.slice(0, 6).join(",") || "-")).join("\n") : "- 无 console 分组。"; const promptNetworkLines = Array.isArray(report.promptNetwork?.rounds) && report.promptNetwork.rounds.length > 0 ? report.promptNetwork.rounds.map((item) => "- round " + item.promptIndex + " promptHash=" + (item.promptTextHash || "-") + " chatPostOk=" + String(item.chatPostOk) + " modes=" + (Array.isArray(item.submitModes) && item.submitModes.length > 0 ? item.submitModes.join(",") : "-") + " failure=" + (item.failureKind || "-") + " statuses=" + (Array.isArray(item.responseStatuses) && item.responseStatuses.length > 0 ? item.responseStatuses.join(",") : "-") + " firstChat=" + (item.firstChatEventAt || "-") + " lastChat=" + (item.lastChatEventAt || "-")).join("\n") : "- 无 prompt 网络记录。"; const roundLines = Array.isArray(report.sampleMetrics?.rounds) && report.sampleMetrics.rounds.length > 0 ? report.sampleMetrics.rounds.map((item) => "- round " + item.promptIndex + " promptHash=" + (item.promptTextHash || "-") + " samples=" + item.sampleCount + " loadingSamples=" + (item.loadingSamples ?? 0) + " maxLoading=" + (item.maxLoadingCount ?? 0) + " loadingOwners=" + (item.loadingOwnerCount ?? 0) + " totalMax=" + (item.maxTotalElapsedSeconds ?? "-") + " totalLast=" + (item.lastTotalElapsedSeconds ?? "-") + " recentMax=" + (item.maxRecentUpdateSeconds ?? "-") + " recentLast=" + (item.lastRecentUpdateSeconds ?? "-") + " totalDecrease=" + (item.turnTimingTotalElapsedDecreaseCount ?? 0) + " totalForwardJump=" + (item.turnTimingTotalElapsedForwardJumpCount ?? 0) + " totalForwardJumpMax=" + (item.turnTimingTotalElapsedForwardJumpMaxSeconds ?? 0) + " terminalGrowth=" + (item.turnTimingTerminalElapsedGrowthCount ?? 0) + " terminalGrowthMax=" + (item.turnTimingTerminalElapsedGrowthMaxSeconds ?? 0) + " recentJump=" + (item.turnTimingRecentUpdateJumpCount ?? 0) + " recentSawtoothJump=" + (item.turnTimingRecentUpdateSawtoothJumpCount ?? item.turnTimingRecentUpdateJumpCount ?? 0) + " recentStep=" + (item.turnTimingRecentUpdateStepCount ?? 0) + " recentMaxIncrease=" + (item.turnTimingRecentUpdateMaxIncreaseSeconds ?? "-") + " recentMaxExcess=" + (item.turnTimingRecentUpdateMaxExcessSeconds ?? 0) + " recentReset=" + (item.turnTimingRecentUpdateResetCount ?? 0) + " diagnostics=" + item.diagnosticSamples + " terminal=" + item.terminalSamples + " finalText=" + item.finalTextSamples).join("\n") : "- 无轮次指标。"; const cardMissingElapsedLines = Array.isArray(codeAgentCardTiming.missingElapsed) && codeAgentCardTiming.missingElapsed.length > 0 ? codeAgentCardTiming.missingElapsed.slice(0, 80).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " role=" + (item.pageRole || "-") + " status=" + (item.status || "-") + " traceId=" + (item.traceId || "-") + " messageId=" + (item.messageId || "-") + " preview=" + escapeMarkdownCell(item.textPreview || "")).join("\n") : "- 未观察到 Code Agent 卡片缺少耗时。"; const cardMissingRecentLines = Array.isArray(codeAgentCardTiming.missingRecentUpdate) && codeAgentCardTiming.missingRecentUpdate.length > 0 ? codeAgentCardTiming.missingRecentUpdate.slice(0, 80).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " role=" + (item.pageRole || "-") + " status=" + (item.status || "-") + " traceId=" + (item.traceId || "-") + " messageId=" + (item.messageId || "-") + " total=" + (item.totalElapsedSeconds ?? "-") + " preview=" + escapeMarkdownCell(item.textPreview || "")).join("\n") : "- 未观察到未终态 Code Agent 卡片缺少最近更新。"; const roundCompletionLines = Array.isArray(roundCompletion.events) && roundCompletion.events.length > 0 ? roundCompletion.events.slice(0, 80).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " role=" + (item.pageRole || "-") + " traceId=" + (item.traceId || "-") + " completionElapsed=" + (item.elapsedSeconds ?? "-") + " preview=" + escapeMarkdownCell(item.preview || "")).join("\n") : "- 未观察到“轮次完成(总耗时 ...)”trace 行。"; const roundCompletionMismatchLines = Array.isArray(roundCompletion.elapsedMismatches) && roundCompletion.elapsedMismatches.length > 0 ? roundCompletion.elapsedMismatches.slice(0, 80).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " traceId=" + (item.traceId || "-") + " completion=" + (item.completionElapsedSeconds ?? "-") + " card=" + (item.cardTotalElapsedSeconds ?? "-") + " delta=" + (item.deltaSeconds ?? "-") + " tolerance=" + (item.toleranceSeconds ?? "-") + formatTimingMeta(item)).join("\n") : "- 未观察到轮次完成耗时与卡片耗时不一致。"; const roundCompletionFinalMissingLines = Array.isArray(roundCompletion.finalResponseMissing) && roundCompletion.finalResponseMissing.length > 0 ? roundCompletion.finalResponseMissing.slice(0, 80).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " role=" + (item.pageRole || "-") + " traceId=" + (item.traceId || "-") + " completionElapsed=" + (item.completionElapsedSeconds ?? "-")).join("\n") : "- 未观察到轮次完成后 final response 缺失。"; const roundCompletionPostTimingLines = Array.isArray(roundCompletion.postCompletionTimingChanges) && roundCompletion.postCompletionTimingChanges.length > 0 ? roundCompletion.postCompletionTimingChanges.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " " + (item.metric || "-") + " " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " completionSeq=" + (item.seq ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " traceId=" + (item.traceId || "-")).join("\n") : "- 未观察到轮次完成后耗时/最近更新继续变化。"; const durationUnderreportedLines = Array.isArray(codeAgentCardTiming.durationUnderreported) && codeAgentCardTiming.durationUnderreported.length > 0 ? codeAgentCardTiming.durationUnderreported.slice(0, 80).map((item) => "- sample=" + (item.sampleIndex ?? "-") + " " + (item.timestamp || "-") + " role=" + (item.pageRole || "-") + " status=" + (item.status || "-") + " traceId=" + (item.traceId || "-") + " card=" + (item.cardTotalElapsedSeconds ?? "-") + "s expected=" + (item.expectedElapsedSeconds ?? "-") + "s delta=" + (item.deltaSeconds ?? "-") + "s evidence=" + (item.evidenceKind || "-") + formatTimingMeta(item) + " preview=" + escapeMarkdownCell(item.evidencePreview || item.cardPreview || "")).join("\n") : "- 未观察到 Code Agent 卡片耗时低于 trace/final-response 证据。"; const durationMismatchLines = Array.isArray(codeAgentCardTiming.durationMismatches) && codeAgentCardTiming.durationMismatches.length > 0 ? codeAgentCardTiming.durationMismatches.slice(0, 80).map((item) => "- sample=" + (item.sampleIndex ?? "-") + " " + (item.timestamp || "-") + " role=" + (item.pageRole || "-") + " status=" + (item.status || "-") + " traceId=" + (item.traceId || "-") + " direction=" + (item.direction || "-") + " card=" + (item.cardTotalElapsedSeconds ?? "-") + "s expected=" + (item.expectedElapsedSeconds ?? "-") + "s signedDelta=" + (item.signedDeltaSeconds ?? "-") + "s delta=" + (item.deltaSeconds ?? "-") + "s tolerance=" + (item.toleranceSeconds ?? "-") + "s evidence=" + (item.evidenceKind || "-") + " exact=" + String(item.exactEvidence === true) + formatTimingMeta(item) + " preview=" + escapeMarkdownCell(item.evidencePreview || item.cardPreview || "")).join("\n") : "- 未观察到 Code Agent 卡片耗时与 completion/final-response 封口证据不一致。"; const traceOrderAnomalyLines = Array.isArray(traceOrder.orderAnomalies) && traceOrder.orderAnomalies.length > 0 ? traceOrder.orderAnomalies.slice(0, 80).map((item) => "- sample=" + (item.sampleIndex ?? "-") + " " + (item.timestamp || "-") + " role=" + (item.pageRole || "-") + " traceId=" + (item.traceId || "-") + " rows=" + (item.previousRowIndex ?? "-") + "->" + (item.currentRowIndex ?? "-") + " reasons=" + (Array.isArray(item.reasons) ? item.reasons.join(",") : "-") + " total=" + (item.previousTotalSeconds ?? "-") + "->" + (item.currentTotalSeconds ?? "-") + " clock=" + (item.previousClockSeconds ?? "-") + "->" + (item.currentClockSeconds ?? "-") + " preview=" + escapeMarkdownCell((item.previousPreview || "") + " / " + (item.currentPreview || ""))).join("\n") : "- 未观察到可见 trace 行顺序非单调。"; const traceCompletionNotLastLines = Array.isArray(traceOrder.completionNotLast) && traceOrder.completionNotLast.length > 0 ? traceOrder.completionNotLast.slice(0, 80).map((item) => "- sample=" + (item.sampleIndex ?? "-") + " " + (item.timestamp || "-") + " role=" + (item.pageRole || "-") + " traceId=" + (item.traceId || "-") + " rows=" + (item.completionRowIndex ?? "-") + "->" + (item.laterRowIndex ?? "-") + " total=" + (item.completionTotalSeconds ?? "-") + "->" + (item.laterTotalSeconds ?? "-") + " completion=" + escapeMarkdownCell(item.completionPreview || "") + " later=" + escapeMarkdownCell(item.laterPreview || "")).join("\n") : "- 未观察到 completion 行后还有同 trace 后续行。"; const loadingSegmentLines = Array.isArray(loading.segments) && loading.segments.length > 0 ? loading.segments.slice(0, 80).map((item) => "- observedDuration=" + (item.durationSeconds ?? 0) + "s upperBound=" + (item.upperBoundSeconds ?? item.durationSeconds ?? 0) + "s endedGap=" + (item.endedGapSeconds ?? "-") + "s samples=" + (item.sampleCount ?? 0) + " countMax=" + (item.maxCount ?? 0) + " owners=" + (item.ownerCount ?? 0) + " seq=" + (item.firstSeq ?? "-") + ".." + (item.lastSeq ?? "-") + " ts=" + (item.firstAt || "-") + ".." + (item.lastAt || "-") + " endedAt=" + (item.endedAt || (item.ongoing ? "ongoing" : "-")) + " ownerLabels=" + ((Array.isArray(item.owners) ? item.owners : []).slice(0, 6).map((owner) => (owner.ownerKind || "-") + ":" + (owner.ownerLabel || "-") + "x" + (owner.count ?? 0)).join(",") || "-")).join("\n") : "- 未观察到“加载中”可见区间。"; const loadingOwnerLines = Array.isArray(loading.owners) && loading.owners.length > 0 ? loading.owners.slice(0, 80).map((item) => "- " + (item.ownerKind || "-") + " " + escapeMarkdownCell(item.ownerLabel || item.ownerKey || "-") + " traceId=" + (item.ownerTraceId || "-") + " messageId=" + (item.ownerMessageId || "-") + " sessionId=" + (item.ownerSessionId || "-") + " samples=" + (item.sampleCount ?? 0) + " occurrences=" + (item.occurrenceCount ?? 0) + " maxCount=" + (item.maxSimultaneousCount ?? 0) + " longest=" + (item.longestContinuousSeconds ?? 0) + "s seq=" + (item.firstSeq ?? "-") + ".." + (item.lastSeq ?? "-") + " prompts=" + (Array.isArray(item.promptIndexes) ? item.promptIndexes.join(",") : "-")).join("\n") : "- 未观察到“加载中”归属。"; const loadingTimelineLines = Array.isArray(loading.timeline) && loading.timeline.length > 0 ? loading.timeline.slice(0, 160).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " prompt=" + (item.promptIndex ?? "-") + " loadingCount=" + (item.loadingCount ?? 0) + " ownerCount=" + (item.ownerCount ?? 0) + " owners=" + ((Array.isArray(item.owners) ? item.owners : []).slice(0, 6).map((owner) => (owner.ownerKind || "-") + ":" + (owner.ownerLabel || "-") + " trace=" + (owner.ownerTraceId || "-") + "x" + (owner.count ?? 0)).join(",") || "-")).join("\n") : "- 未观察到“加载中”采样点。"; const sessionRailTitleSampleLines = Array.isArray(sessionRailTitles.samples) && sessionRailTitles.samples.length > 0 ? sessionRailTitles.samples.slice(0, 80).map((item) => "- #" + (item.seq ?? "-") + " " + (item.ts || "-") + " role=" + (item.pageRole || "-") + " visible=" + (item.visibleCount ?? 0) + " fallback=" + (item.fallbackTitleCount ?? 0) + " ratio=" + (item.fallbackTitleRatio ?? 0) + " examples=" + ((Array.isArray(item.examples) ? item.examples : []).slice(0, 4).map((example) => escapeMarkdownCell(example.titlePreview || example.titleHash || "-")).join(",") || "-")).join("\n") : "- 未观察到超过一半 fallback 的 session 列表采样点。"; const sessionRailTitleExampleLines = Array.isArray(sessionRailTitles.examples) && sessionRailTitles.examples.length > 0 ? sessionRailTitles.examples.slice(0, 80).map((item) => "- firstSeq=" + (item.firstSeq ?? "-") + " role=" + (item.pageRole || "-") + " active=" + String(item.active === true) + " sessionPrefix=" + (item.sessionIdPrefix || "-") + " titleHash=" + (item.titleHash || "-") + " preview=" + escapeMarkdownCell(item.titlePreview || "")).join("\n") : "- 无 fallback 标题示例。"; const provenanceLines = Array.isArray(report.pageProvenance?.segments) && report.pageProvenance.segments.length > 0 ? report.pageProvenance.segments.slice(0, 40).map((item) => "- fingerprint=" + (item.assetFingerprint || "-") + " samples=" + item.sampleCount + " seq=" + (item.firstSeq ?? "-") + ".." + (item.lastSeq ?? "-") + " ts=" + (item.firstAt || "-") + ".." + (item.lastAt || "-") + " scripts=" + (item.scriptCount ?? "-") + " styles=" + (item.stylesheetCount ?? "-") + " urlPaths=" + (Array.isArray(item.urlPaths) ? item.urlPaths.slice(0, 4).join(",") : "-")).join("\n") : "- 无页面 provenance segment。"; const ordinaryPerformanceItems = Array.isArray(report.pagePerformance?.sameOriginApiByPath) ? report.pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream !== true) : []; const streamPerformanceItems = Array.isArray(report.pagePerformance?.sameOriginApiByPath) ? report.pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream === true) : []; const sameOriginApiBudgetMs = Number(report.alertThresholds?.sameOriginApiSlowMs ?? report.pagePerformance?.summary?.budgetMs); const streamOpenBudgetMs = Number(report.alertThresholds?.longLivedStreamOpenSlowMs); const performanceLines = ordinaryPerformanceItems.length > 0 ? ordinaryPerformanceItems.slice(0, 80).map((item) => "- " + item.path + " kind=" + (item.routeKind || "same-origin-api") + " budgetMetric=" + (item.budgetMetric || "durationMs") + " samples=" + item.sampleCount + " p50=" + (item.p50Ms ?? "-") + "ms p75=" + (item.p75Ms ?? "-") + "ms p95=" + (item.p95Ms ?? "-") + "ms max=" + (item.maxMs ?? "-") + "ms >budget=" + (item.overBudgetCount ?? item.overFiveSecondCount ?? 0) + " budgetMs=" + (item.budgetMs ?? sameOriginApiBudgetMs) + " legacy>5s=" + (item.overFiveSecondCount ?? 0) + " window=" + (item.firstAt || "-") + ".." + (item.lastAt || "-")).join("\n") : "- 无同源 API Resource Timing 样本。"; const streamPerformanceLines = streamPerformanceItems.length > 0 ? streamPerformanceItems.slice(0, 80).map((item) => "- " + item.path + " kind=" + (item.routeKind || "same-origin-api-stream") + " samples=" + item.sampleCount + " streamOpenP50=" + (item.streamOpenP50Ms ?? "-") + "ms streamOpenP75=" + (item.streamOpenP75Ms ?? "-") + "ms streamOpenP95=" + (item.streamOpenP95Ms ?? "-") + "ms streamOpenMax=" + (item.streamOpenMaxMs ?? "-") + "ms streamOpen>budget=" + (item.streamOpenOverBudgetCount ?? item.streamOpenOverFiveSecondCount ?? 0) + " streamOpenBudgetMs=" + (item.streamOpenBudgetMs ?? streamOpenBudgetMs) + " streamOpenLegacy>5s=" + (item.streamOpenOverFiveSecondCount ?? 0) + " streamLifetime>5s=" + (item.streamLifetimeOverFiveSecondCount ?? 0) + " lifetimeMax=" + (item.maxMs ?? "-") + "ms window=" + (item.firstAt || "-") + ".." + (item.lastAt || "-")).join("\n") : "- 无同源长连接 Resource Timing 样本。"; const apiDomLagGroupLines = Array.isArray(apiDomLag.groups) && apiDomLag.groups.length > 0 ? apiDomLag.groups.slice(0, 80).map((item) => "- " + (item.method || "-") + " " + (item.path || "-") + " status=" + (item.status ?? "-") + " kind=" + (item.routeKind || "-") + " confidence=" + (item.confidence || "-") + " count=" + (item.count ?? 0) + " changed=" + (item.domChangedCount ?? 0) + " noChange=" + (item.noDomChangeWithinWindowCount ?? 0) + " p50=" + (item.p50DomChangeDeltaMs ?? "-") + "ms p95=" + (item.p95DomChangeDeltaMs ?? "-") + "ms max=" + (item.maxDomChangeDeltaMs ?? "-") + "ms overBudget=" + (item.overBudgetCount ?? 0) + " window=" + (item.firstAt || "-") + ".." + (item.lastAt || "-")).join("\n") : "- 无 API-DOM 候选分组。"; const apiDomLagWorstLines = Array.isArray(apiDomLag.worstCandidates) && apiDomLag.worstCandidates.length > 0 ? apiDomLag.worstCandidates.slice(0, 40).map((item) => "- " + (item.ts || "-") + " " + (item.method || "-") + " " + (item.path || "-") + " status=" + (item.status ?? "-") + " confidence=" + (item.confidence || "-") + " domChange=" + (item.domChangeDeltaMs ?? "-") + "ms firstSample=" + (item.firstSampleDeltaMs ?? "-") + "ms session=" + (item.sessionId || "-") + " traceId=" + (item.traceId || "-") + " beforeSeq=" + (item.beforeSample?.seq ?? "-") + " changeSeq=" + (item.changeSample?.seq ?? "-")).join("\n") : "- 无 API-DOM digest 变化候选。"; const metricLines = Array.isArray(report.sampleMetrics?.timeline) && report.sampleMetrics.timeline.length > 0 ? report.sampleMetrics.timeline.slice(0, 120).map((item) => "- #" + item.seq + " " + item.ts + " prompt=" + item.promptIndex + " loadingCount=" + (item.loadingCount ?? 0) + " loadingOwners=" + (item.loadingOwnerCount ?? 0) + " totalElapsedSeconds=" + (item.totalElapsedSeconds ?? "-") + " recentUpdateSeconds=" + (item.recentUpdateSeconds ?? "-") + " terminal=" + item.terminalSeen + " finalText=" + item.finalResultTextSeen + " diagnostic=" + item.diagnosticSeen).join("\n") : "- 无采样指标。"; const turnTimingTable = renderTurnTimingTable(report.sampleMetrics); return "# web-probe observe analysis\n\n" + "- stateDir: " + report.stateDir + "\n" + "- generatedAt: " + report.generatedAt + "\n" + "- samples: " + report.counts.samples + "\n" + "- control: " + report.counts.control + "\n" + "- network: " + report.counts.network + "\n" + "- console: " + (report.counts.console ?? 0) + "\n" + "- errors: " + report.counts.errors + "\n\n" + "## Findings\n\n" + findingLines + "\n\n" + "## Project management\n\n" + "- enabled: " + String(projectSummary.enabled === true) + "\n" + "- projectSampleCount: " + (projectSummary.projectSampleCount ?? 0) + "\n" + "- mdtodoSampleCount: " + (projectSummary.mdtodoSampleCount ?? 0) + "\n" + "- latestPageKind: " + (projectSummary.latestPageKind || "-") + "\n" + "- latestPath: " + (projectSummary.latestPath || "-") + "\n" + "- latestCounts: source=" + (projectSummary.latestSourceCount ?? "-") + " file=" + (projectSummary.latestFileCount ?? "-") + " task=" + (projectSummary.latestTaskCount ?? "-") + "\n" + "- latestSelectedTaskRefHash: " + (projectSummary.latestSelectedTaskRefHash || "-") + "\n" + "- launch: commands=" + (projectSummary.launchCommandCount ?? 0) + " success=" + (projectSummary.launchSuccessCount ?? 0) + " failure=" + (projectSummary.launchFailureCount ?? 0) + " otelTraceHeader=" + (projectSummary.launchWithOtelTraceHeaderCount ?? 0) + "\n" + "- projectApi: responses=" + (projectSummary.projectApiResponseCount ?? 0) + " failures=" + (projectSummary.projectApiFailureCount ?? 0) + " requestfailed=" + (projectSummary.projectApiRequestFailedCount ?? 0) + " slowPaths=" + (projectSummary.projectApiSlowPathCount ?? 0) + "\n\n" + "### Project samples\n\n" + projectSampleLines + "\n\n" + "### Project commands\n\n" + projectCommandLines + "\n\n" + "### Project API\n\n" + projectApiLines + "\n\n" + "## Command failures\n\n" + commandFailureLines + "\n\n" + "## Sample metrics\n\n" + "- sampleCount: " + (metricSummary.sampleCount ?? 0) + "\n" + "- withTotalElapsed: " + (metricSummary.withTotalElapsed ?? 0) + "\n" + "- withRecentUpdate: " + (metricSummary.withRecentUpdate ?? 0) + "\n" + "- diagnostics: " + (metricSummary.diagnostics ?? 0) + "\n" + "- loadingSamples: " + (metricSummary.loadingSampleCount ?? 0) + "\n" + "- loadingMaxCount: " + (metricSummary.loadingMaxCount ?? 0) + "\n" + "- loadingMaxOwnerCount: " + (metricSummary.loadingMaxOwnerCount ?? 0) + "\n" + "- loadingOwnerCount: " + (metricSummary.loadingOwnerCount ?? 0) + "\n" + "- loadingLongestContinuousSeconds: " + (metricSummary.loadingLongestContinuousSeconds ?? 0) + "\n" + "- loadingCurrentContinuousSeconds: " + (metricSummary.loadingCurrentContinuousSeconds ?? 0) + "\n" + "- loadingOverFiveSecondSegmentCount: " + (metricSummary.loadingOverFiveSecondSegmentCount ?? 0) + "\n" + "- sessionRailFallbackMajoritySampleCount: " + (metricSummary.sessionRailFallbackMajoritySampleCount ?? 0) + "\n" + "- sessionRailFallbackMaxRatio: " + (metricSummary.sessionRailFallbackMaxRatio ?? 0) + "\n" + "- sessionRailFallbackMaxCount: " + (metricSummary.sessionRailFallbackMaxCount ?? 0) + "\n" + "- promptSegments: " + (metricSummary.promptSegments ?? 0) + "\n\n" + "- turnColumns: " + (metricSummary.turnColumns ?? 0) + "\n" + "- turnTimingRows: " + (metricSummary.turnTimingRows ?? 0) + "\n" + "- turnTimingNonMonotonicCount: " + (metricSummary.turnTimingNonMonotonicCount ?? 0) + "\n" + "- turnTimingTotalElapsedDecreaseCount: " + (metricSummary.turnTimingTotalElapsedDecreaseCount ?? 0) + "\n" + "- turnTimingTotalElapsedForwardJumpCount: " + (metricSummary.turnTimingTotalElapsedForwardJumpCount ?? 0) + "\n" + "- turnTimingTotalElapsedForwardJumpMaxSeconds: " + (metricSummary.turnTimingTotalElapsedForwardJumpMaxSeconds ?? 0) + "\n" + "- turnTimingTerminalElapsedGrowthCount: " + (metricSummary.turnTimingTerminalElapsedGrowthCount ?? 0) + "\n" + "- turnTimingTerminalElapsedGrowthMaxSeconds: " + (metricSummary.turnTimingTerminalElapsedGrowthMaxSeconds ?? 0) + "\n" + "- turnTimingRecentUpdateJumpCount: " + (metricSummary.turnTimingRecentUpdateJumpCount ?? 0) + "\n" + "- turnTimingRecentUpdateSawtoothJumpCount: " + (metricSummary.turnTimingRecentUpdateSawtoothJumpCount ?? metricSummary.turnTimingRecentUpdateJumpCount ?? 0) + "\n" + "- turnTimingRecentUpdateStepCount: " + (metricSummary.turnTimingRecentUpdateStepCount ?? 0) + "\n" + "- turnTimingRecentUpdateMaxIncreaseSeconds: " + (metricSummary.turnTimingRecentUpdateMaxIncreaseSeconds ?? "-") + "\n" + "- turnTimingRecentUpdateMaxExcessSeconds: " + (metricSummary.turnTimingRecentUpdateMaxExcessSeconds ?? 0) + "\n" + "- turnTimingRecentUpdateResetCount: " + (metricSummary.turnTimingRecentUpdateResetCount ?? 0) + "\n\n" + "- codeAgentCardSampleCount: " + (metricSummary.codeAgentCardSampleCount ?? 0) + "\n" + "- codeAgentCardMissingElapsedCount: " + (metricSummary.codeAgentCardMissingElapsedCount ?? 0) + "\n" + "- codeAgentCardMissingRecentUpdateCount: " + (metricSummary.codeAgentCardMissingRecentUpdateCount ?? 0) + "\n" + "- codeAgentCardDurationUnderreportedCount: " + (metricSummary.codeAgentCardDurationUnderreportedCount ?? 0) + "\n" + "- codeAgentCardDurationMismatchCount: " + (metricSummary.codeAgentCardDurationMismatchCount ?? 0) + "\n" + "- traceRowCount: " + (metricSummary.traceRowCount ?? 0) + "\n" + "- traceRowOrderAnomalyCount: " + (metricSummary.traceRowOrderAnomalyCount ?? 0) + "\n" + "- traceRowCompletionNotLastCount: " + (metricSummary.traceRowCompletionNotLastCount ?? 0) + "\n" + "- roundCompletionEventCount: " + (metricSummary.roundCompletionEventCount ?? 0) + "\n" + "- roundCompletionElapsedMismatchCount: " + (metricSummary.roundCompletionElapsedMismatchCount ?? 0) + "\n" + "- roundCompletionFinalResponseMissingCount: " + (metricSummary.roundCompletionFinalResponseMissingCount ?? 0) + "\n" + "- roundCompletionPostTimingChangeCount: " + (metricSummary.roundCompletionPostTimingChangeCount ?? 0) + "\n\n" + "### Rounds\n\n" + roundLines + "\n\n" + "### Code Agent card timing display\n\n" + "- cardSampleCount: " + (codeAgentCardTimingSummary.cardSampleCount ?? 0) + "\n" + "- runningCardSampleCount: " + (codeAgentCardTimingSummary.runningCardSampleCount ?? 0) + "\n" + "- terminalCardSampleCount: " + (codeAgentCardTimingSummary.terminalCardSampleCount ?? 0) + "\n" + "- missingElapsedCount: " + (codeAgentCardTimingSummary.missingElapsedCount ?? 0) + "\n" + "- missingRecentUpdateCount: " + (codeAgentCardTimingSummary.missingRecentUpdateCount ?? 0) + "\n" + "- durationUnderreportedCount: " + (codeAgentCardTimingSummary.durationUnderreportedCount ?? 0) + "\n" + "- durationMismatchCount: " + (codeAgentCardTimingSummary.durationMismatchCount ?? 0) + "\n" + "- policy: Code Agent 卡片无论终态/非终态都必须显示耗时;非终态必须显示最近更新。completion/final-response 可用时是 sealed source-of-truth;DOM card elapsed 是显示源。该 analyzer 只报告采样到的页面表现,不做下游 repair。\n\n" + "#### Missing elapsed samples\n\n" + cardMissingElapsedLines + "\n\n" + "#### Missing recent update samples\n\n" + cardMissingRecentLines + "\n\n" + "#### Duration underreported samples\n\n" + durationUnderreportedLines + "\n\n" + "#### Duration mismatch samples\n\n" + durationMismatchLines + "\n\n" + "### Trace row visual order\n\n" + "- traceRowCount: " + (traceOrderSummary.traceRowCount ?? 0) + "\n" + "- orderAnomalyCount: " + (traceOrderSummary.orderAnomalyCount ?? 0) + "\n" + "- completionNotLastCount: " + (traceOrderSummary.completionNotLastCount ?? 0) + "\n" + "- policy: 可见 trace 行在同一 trace 内必须按 total/时钟/projected seq 单调展示;completion 行不得出现在同 trace 后续行之前。\n\n" + "#### Trace order anomalies\n\n" + traceOrderAnomalyLines + "\n\n" + "#### Completion row not last samples\n\n" + traceCompletionNotLastLines + "\n\n" + "### Round completion consistency\n\n" + "- completionEventCount: " + (codeAgentCardTimingSummary.roundCompletionEventCount ?? 0) + "\n" + "- elapsedMismatchCount: " + (codeAgentCardTimingSummary.roundCompletionElapsedMismatchCount ?? 0) + "\n" + "- finalResponseMissingCount: " + (codeAgentCardTimingSummary.roundCompletionFinalResponseMissingCount ?? 0) + "\n" + "- postTimingChangeCount: " + (codeAgentCardTimingSummary.roundCompletionPostTimingChangeCount ?? 0) + "\n" + "- postRecentUpdateVisibleCount: " + (codeAgentCardTimingSummary.roundCompletionPostRecentUpdateVisibleCount ?? 0) + "\n" + "- elapsedMismatchToleranceSeconds: " + (codeAgentCardTimingSummary.elapsedMismatchToleranceSeconds ?? "-") + "\n" + "- policy: 轮次完成(总耗时 ...) 是 trace sealed source-of-truth;卡片总耗时必须与它一致。完成后 final response 必须可见,耗时/最近更新不得继续跳变。\n\n" + "#### Round completion events\n\n" + roundCompletionLines + "\n\n" + "#### Completion elapsed mismatches\n\n" + roundCompletionMismatchLines + "\n\n" + "#### Final response missing after completion\n\n" + roundCompletionFinalMissingLines + "\n\n" + "#### Post-completion timing changes\n\n" + roundCompletionPostTimingLines + "\n\n" + "### Loading visibility: visible 加载中\n\n" + "- sampleCount: " + (loadingSummary.sampleCount ?? 0) + "\n" + "- loadingSampleCount: " + (loadingSummary.loadingSampleCount ?? 0) + "\n" + "- maxSimultaneousCount: " + (loadingSummary.maxSimultaneousCount ?? 0) + "\n" + "- maxSimultaneousOwnerCount: " + (loadingSummary.maxSimultaneousOwnerCount ?? 0) + "\n" + "- concurrentLoadingSampleCount: " + (loadingSummary.concurrentLoadingSampleCount ?? 0) + "\n" + "- ownerCount: " + (loadingSummary.ownerCount ?? 0) + "\n" + "- segmentCount: " + (loadingSummary.segmentCount ?? 0) + "\n" + "- overFiveSecondSegmentCount: " + (loadingSummary.overFiveSecondSegmentCount ?? 0) + "\n" + "- longestContinuousSeconds: " + (loadingSummary.longestContinuousSeconds ?? 0) + "\n" + "- currentContinuousSeconds: " + (loadingSummary.currentContinuousSeconds ?? 0) + "\n" + "- budgetSeconds: " + (loadingSummary.budgetSeconds ?? (Number.isFinite(Number(report.alertThresholds?.visibleLoadingSlowMs)) ? Number(report.alertThresholds.visibleLoadingSlowMs) / 1000 : "unconfigured")) + "\n" + "- policy: 该指标只能证明用户真实看到“加载中”的持续时间;修复必须降低真实请求/投影/渲染耗时,禁止提前展示未加载完内容来压低该指标。\n\n" + "#### Loading segments\n\n" + loadingSegmentLines + "\n\n" + "#### Loading owners\n\n" + loadingOwnerLines + "\n\n" + "#### Loading sample timeline\n\n" + loadingTimelineLines + "\n\n" + "### Session rail titles\n\n" + "- sampleCount: " + (sessionRailTitleSummary.sampleCount ?? 0) + "\n" + "- visibleSampleCount: " + (sessionRailTitleSummary.visibleSampleCount ?? 0) + "\n" + "- fallbackSampleCount: " + (sessionRailTitleSummary.fallbackSampleCount ?? 0) + "\n" + "- majorityFallbackSampleCount: " + (sessionRailTitleSummary.majorityFallbackSampleCount ?? 0) + "\n" + "- maxFallbackRatio: " + (sessionRailTitleSummary.maxFallbackRatio ?? 0) + "\n" + "- maxVisibleCount: " + (sessionRailTitleSummary.maxVisibleCount ?? 0) + "\n" + "- maxFallbackTitleCount: " + (sessionRailTitleSummary.maxFallbackTitleCount ?? 0) + "\n" + "- policy: 可见 session 列表中 'Session ses_...' fallback 标题超过一半必须报警;修复应让上游 session list projection 直接携带名称,不能靠点击详情后下游修补。\n\n" + "#### Session rail fallback samples\n\n" + sessionRailTitleSampleLines + "\n\n" + "#### Session rail fallback examples\n\n" + sessionRailTitleExampleLines + "\n\n" + "### Page provenance\n\n" + "- segmentCount: " + (report.pageProvenance?.summary?.segmentCount ?? 0) + "\n" + "- controlSegmentCount: " + (report.pageProvenance?.summary?.controlSegmentCount ?? 0) + "\n\n" + provenanceLines + "\n\n" + "### Page performance: same-origin API Resource Timing\n\n" + "- budgetMs: " + (report.pagePerformance?.summary?.budgetMs ?? sameOriginApiBudgetMs) + "\n" + "- sameOriginApiPathCount: " + (report.pagePerformance?.summary?.sameOriginApiPathCount ?? 0) + "\n" + "- sameOriginApiSampleCount: " + (report.pagePerformance?.summary?.sameOriginApiSampleCount ?? 0) + "\n" + "- longLivedStreamPathCount: " + (report.pagePerformance?.summary?.longLivedStreamPathCount ?? 0) + "\n" + "- longLivedStreamSampleCount: " + (report.pagePerformance?.summary?.longLivedStreamSampleCount ?? 0) + "\n" + "- longLivedStreamOpenOverFiveSecondPathCount: " + (report.pagePerformance?.summary?.longLivedStreamOpenOverFiveSecondPathCount ?? 0) + "\n" + "- longLivedStreamOpenOverFiveSecondSampleCount: " + (report.pagePerformance?.summary?.longLivedStreamOpenOverFiveSecondSampleCount ?? 0) + "\n" + "- longLivedStreamLifetimeOverFiveSecondSampleCount: " + (report.pagePerformance?.summary?.longLivedStreamLifetimeOverFiveSecondSampleCount ?? 0) + "\n" + "- slowPathCount: " + (report.pagePerformance?.summary?.slowPathCount ?? 0) + "\n" + "- slowSampleCount: " + (report.pagePerformance?.summary?.slowSampleCount ?? 0) + "\n" + "- worstP95Ms: " + (report.pagePerformance?.summary?.worstP95Ms ?? "-") + "\n\n" + performanceLines + "\n\n" + "### Page performance: long-lived streams\n\n" + "- policy: SSE/long-lived stream lifetime is not ordinary API load latency; only stream open latency is compared with the YAML usability budget, while disconnects remain runtime alerts.\n\n" + streamPerformanceLines + "\n\n" + "### Natural API to DOM lag candidates\n\n" + "- naturalApiResponseCount: " + (apiDomLagSummary.naturalApiResponseCount ?? 0) + "\n" + "- stateRelevantResponseCount: " + (apiDomLagSummary.stateRelevantResponseCount ?? 0) + "\n" + "- candidateCount: " + (apiDomLagSummary.candidateCount ?? 0) + "\n" + "- domChangedCount: " + (apiDomLagSummary.domChangedCount ?? 0) + "\n" + "- noDomChangeWithinWindowCount: " + (apiDomLagSummary.noDomChangeWithinWindowCount ?? 0) + "\n" + "- budgetMs: " + (apiDomLagSummary.budgetMs ?? "-") + "\n" + "- p95DomChangeDeltaMs: " + (apiDomLagSummary.p95DomChangeDeltaMs ?? "-") + "\n" + "- maxDomChangeDeltaMs: " + (apiDomLagSummary.maxDomChangeDeltaMs ?? "-") + "\n" + "- overBudgetCount: " + (apiDomLagSummary.overBudgetCount ?? 0) + "\n" + "- lowConfidenceStreamOpenCount: " + (apiDomLagSummary.lowConfidenceStreamOpenCount ?? 0) + "\n" + "- policy: 该指标是调查证据,不作为 Code Agent 阻塞项;/v1/workbench/events 只能代表 SSE stream-open 到后续 DOM 变化的低置信度候选。\n\n" + "#### API-DOM groups\n\n" + apiDomLagGroupLines + "\n\n" + "#### Worst API-DOM candidates\n\n" + apiDomLagWorstLines + "\n\n" + "### Prompt network\n\n" + promptNetworkLines + "\n\n" + "### Runtime alerts\n\n" + "- httpErrorCount: " + (alertSummary.httpErrorCount ?? 0) + "\n" + "- requestFailedCount: " + (alertSummary.requestFailedCount ?? 0) + "\n" + "- domDiagnosticSampleCount: " + (alertSummary.domDiagnosticSampleCount ?? 0) + "\n" + "- consoleAlertCount: " + (alertSummary.consoleAlertCount ?? 0) + "\n" + "- pageErrorCount: " + (alertSummary.pageErrorCount ?? 0) + "\n\n" + "#### HTTP errors\n\n" + httpAlertLines + "\n\n" + "#### Request failed\n\n" + requestFailedLines + "\n\n" + "#### DOM diagnostics\n\n" + domDiagnosticLines + "\n\n" + "#### Console alerts\n\n" + consoleAlertLines + "\n\n" + "#### Console alert groups\n\n" + consoleAlertGroupLines + "\n\n" + "### Turn timing table\n\n" + turnTimingTable + "\n\n" + "### Aggregate timeline\n\n" + metricLines + "\n\n" + "## Command timeline\n\n" + commandLines + "\n\n" + "## State transitions\n\n" + transitionLines + "\n"; } async function fileMeta(file) { const [buffer, stats] = await Promise.all([readFile(file), stat(file)]); return { byteCount: stats.size, sha256: "sha256:" + createHash("sha256").update(buffer).digest("hex") }; } function sha256(value) { return "sha256:" + createHash("sha256").update(String(value)).digest("hex"); } function urlPath(value) { try { const url = new URL(String(value || "http://invalid.local/")); return url.pathname; } catch { return "-"; } } function compactLocation(value) { if (!value || typeof value !== "object") return null; return { urlPath: urlPath(value.url), lineNumber: value.lineNumber ?? null, columnNumber: value.columnNumber ?? null }; } function limitText(value, limit) { const text = String(value ?? ""); if (text.length <= limit) return text; return text.slice(0, Math.max(0, limit - 1)) + "…"; } `; }