feat(web-probe): detect trace order and card timing drift (#737)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-23 15:31:08 +08:00
committed by GitHub
parent f92dbce91f
commit 2b89905a73
2 changed files with 890 additions and 1 deletions
+1 -1
View File
@@ -2966,7 +2966,7 @@ function withNodeRuntimeTriggerRendered(result: Record<string, unknown>, scoped:
const warning = record(result.warning ?? pipelineWait.warning);
const postFlush = record(result.postFlush);
const gitMirror = record(result.gitMirror);
const gitMirrorSummary = record(gitMirror.statusSummary ?? gitMirror.afterSummary ?? gitMirror.beforeSummary ?? gitMirror.summary);
const gitMirrorSummary = record(postFlush.afterSummary ?? postFlush.beforeSummary ?? gitMirror.statusSummary ?? gitMirror.afterSummary ?? gitMirror.beforeSummary ?? gitMirror.summary);
const refresh = record(result.refresh);
const create = record(result.create);
const next = record(result.next);
@@ -1600,11 +1600,14 @@ async function sampleOnePage(targetPage, { reason, groupSeq, pageRole, targetPag
tag: element.tagName.toLowerCase(),
testId: element.getAttribute("data-testid"),
role: element.getAttribute("role"),
dataRole: element.getAttribute("data-role"),
status: element.getAttribute("data-status") || element.getAttribute("aria-busy") || null,
sessionId: element.getAttribute("data-session-id") || null,
messageId: element.getAttribute("data-message-id") || element.getAttribute("id") || null,
traceId: element.getAttribute("data-trace-id") || null,
turnId: element.getAttribute("data-turn-id") || null,
durationText: trim(element.querySelector(".message-duration-meta")?.textContent || "", 120),
activityText: trim(element.querySelector(".message-activity-meta")?.textContent || "", 120),
text: stableMessageText(element),
rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
};
@@ -2449,6 +2452,7 @@ function compactDomItem(item) {
tag: item.tag ?? null,
testId: item.testId ?? null,
role: item.role ?? null,
dataRole: item.dataRole ?? null,
status: item.status ?? null,
sessionId: item.sessionId ?? null,
messageId: item.messageId ?? null,
@@ -2580,6 +2584,44 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN
if (finalFlicker.length > 0) findings.push({ id: "final-response-flicker", severity: "red", summary: "message text digest disappeared or switched to diagnostic-like text after non-empty final text", count: finalFlicker.length, samples: finalFlicker.slice(0, 20) });
const terminalZeroElapsed = detectTerminalZeroElapsed(samples);
if (terminalZeroElapsed.length > 0) findings.push({ id: "turn-terminal-zero-elapsed", severity: "red", summary: "terminal Code Agent card displayed 耗时 0 秒; terminal duration must come from durable timing projection, not a missing/zero display fallback", count: terminalZeroElapsed.length, samples: terminalZeroElapsed.slice(0, 20) });
const cardTiming = sampleMetrics?.codeAgentCardTiming || {};
const cardTimingSummary = cardTiming.summary || {};
if (Number(cardTimingSummary.missingElapsedCount ?? 0) > 0) findings.push({ id: "code-agent-card-elapsed-missing", severity: "red", summary: "visible Code Agent card did not display total elapsed time; elapsed must be visible for running and terminal cards", count: cardTimingSummary.missingElapsedCount, samples: (cardTiming.missingElapsed || []).slice(0, 20) });
if (Number(cardTimingSummary.missingRecentUpdateCount ?? 0) > 0) findings.push({ id: "code-agent-card-running-recent-update-missing", severity: "red", summary: "non-terminal Code Agent card did not display 最近更新", count: cardTimingSummary.missingRecentUpdateCount, samples: (cardTiming.missingRecentUpdate || []).slice(0, 20) });
const roundCompletion = cardTiming.roundCompletion || {};
if (Number(cardTimingSummary.durationUnderreportedCount ?? 0) > 0) {
findings.push({
id: "code-agent-card-duration-underreported",
severity: "red",
summary: "completed Code Agent card total elapsed is shorter than trace/final-response duration evidence",
count: cardTimingSummary.durationUnderreportedCount,
samples: (cardTiming.durationUnderreported || []).slice(0, 20),
});
}
const traceOrder = sampleMetrics?.traceOrder || {};
const traceOrderSummary = traceOrder.summary || {};
if (Number(traceOrderSummary.orderAnomalyCount ?? 0) > 0) {
findings.push({
id: "trace-row-order-nonmonotonic",
severity: "red",
summary: "visible trace rows are not monotonic by total time, clock time, or projected sequence in DOM order",
count: traceOrderSummary.orderAnomalyCount,
samples: (traceOrder.orderAnomalies || []).slice(0, 20),
});
}
if (Number(traceOrderSummary.completionNotLastCount ?? 0) > 0) {
findings.push({
id: "trace-completion-row-not-last",
severity: "red",
summary: "visible trace shows a completion row before later trace rows for the same trace",
count: traceOrderSummary.completionNotLastCount,
samples: (traceOrder.completionNotLast || []).slice(0, 20),
});
}
if (Number(cardTimingSummary.roundCompletionElapsedMismatchCount ?? 0) > 0) findings.push({ id: "round-completion-elapsed-mismatch", severity: "red", summary: "Trace row 轮次完成(总耗时 ... does not match the visible Code Agent card total elapsed time within YAML timing slack", count: cardTimingSummary.roundCompletionElapsedMismatchCount, toleranceSeconds: cardTimingSummary.elapsedMismatchToleranceSeconds, samples: (roundCompletion.elapsedMismatches || []).slice(0, 20) });
if (Number(cardTimingSummary.roundCompletionFinalResponseMissingCount ?? 0) > 0) findings.push({ id: "round-completion-final-response-missing", severity: "red", summary: "Trace row showed 轮次完成, but no final response was visible in the Code Agent card afterward", count: cardTimingSummary.roundCompletionFinalResponseMissingCount, samples: (roundCompletion.finalResponseMissing || []).slice(0, 20) });
if (Number(cardTimingSummary.roundCompletionPostTimingChangeCount ?? 0) > 0) findings.push({ id: "round-completion-post-timing-change", severity: "red", summary: "After 轮次完成, card total elapsed or 最近更新 continued changing; terminal timing should be sealed", count: cardTimingSummary.roundCompletionPostTimingChangeCount, samples: (roundCompletion.postCompletionTimingChanges || []).slice(0, 20) });
if (Number(cardTimingSummary.roundCompletionPostRecentUpdateVisibleCount ?? 0) > 0) findings.push({ id: "round-completion-recent-update-still-visible", severity: "info", summary: "最近更新 was still visible after 轮次完成; inspect whether terminal cards should hide activity age or keep it sealed", count: cardTimingSummary.roundCompletionPostRecentUpdateVisibleCount, samples: (roundCompletion.postCompletionRecentUpdateVisible || []).slice(0, 20) });
const scrollJumps = [];
for (let i = 1; i < samples.length; i += 1) {
const prevY = Number(samples[i - 1]?.scroll?.y ?? 0);
@@ -3673,6 +3715,8 @@ function prioritizeFindings(findings) {
const id = String(item?.id ?? item?.kind ?? item?.code ?? "");
if (id === "page-performance-slow-same-origin-api") return 0;
if (id === "session-rail-title-fallback-majority") return 0.5;
if (id.startsWith("code-agent-card-")) return 0.8;
if (id.startsWith("round-completion-")) return 0.9;
if (id.startsWith("turn-timing-total-elapsed")) return 1;
if (id.startsWith("turn-timing-recent-update")) return 2;
if (id.includes("runtime-execution") || id.includes("prompt-chat-submit-failed")) return 3;
@@ -3736,6 +3780,13 @@ function buildSampleMetrics(samples, control) {
};
});
const turnTiming = buildTurnTimingTable(samples, timeline);
const traceOrder = buildTraceOrderMetrics(samples, timeline);
const codeAgentCardTiming = buildCodeAgentCardTimingMetrics(samples, timeline, turnTiming);
const codeAgentCardDurationUnderreported = buildCodeAgentCardDurationUnderreportedMetrics(samples, timeline);
if (codeAgentCardTiming && codeAgentCardTiming.summary) {
codeAgentCardTiming.summary.durationUnderreportedCount = codeAgentCardDurationUnderreported.length;
codeAgentCardTiming.durationUnderreported = codeAgentCardDurationUnderreported;
}
const turnCells = turnTiming.rows.flatMap((row) => Object.values(row.cells || {}));
const turnTimingNonMonotonic = Array.isArray(turnTiming.nonMonotonic) ? turnTiming.nonMonotonic : [];
const turnTimingElapsedZeroResets = Array.isArray(turnTiming.elapsedZeroResets) ? turnTiming.elapsedZeroResets : [];
@@ -3813,6 +3864,17 @@ function buildSampleMetrics(samples, control) {
turnTimingRecentUpdateMaxExcessSeconds: turnTimingRecentUpdateExcessSteps.length > 0 ? Math.max(...turnTimingRecentUpdateExcessSteps) : 0,
turnTimingRecentUpdateResetCount: turnTimingRecentUpdateResets.length,
turnTimingRecentUpdateDecreaseCount: turnTimingRecentUpdateResets.length,
codeAgentCardSampleCount: codeAgentCardTiming.summary.cardSampleCount,
codeAgentCardMissingElapsedCount: codeAgentCardTiming.summary.missingElapsedCount,
codeAgentCardMissingRecentUpdateCount: codeAgentCardTiming.summary.missingRecentUpdateCount,
roundCompletionEventCount: codeAgentCardTiming.summary.roundCompletionEventCount,
roundCompletionElapsedMismatchCount: codeAgentCardTiming.summary.roundCompletionElapsedMismatchCount,
roundCompletionFinalResponseMissingCount: codeAgentCardTiming.summary.roundCompletionFinalResponseMissingCount,
roundCompletionPostTimingChangeCount: codeAgentCardTiming.summary.roundCompletionPostTimingChangeCount,
codeAgentCardDurationUnderreportedCount: codeAgentCardTiming.summary.durationUnderreportedCount,
traceRowCount: traceOrder.summary.traceRowCount,
traceRowOrderAnomalyCount: traceOrder.summary.orderAnomalyCount,
traceRowCompletionNotLastCount: traceOrder.summary.completionNotLastCount,
roundsWithTurnTimingNonMonotonic: rounds.filter((item) => item.turnTimingNonMonotonicCount > 0).length,
roundsWithTurnTimingTotalElapsedForwardJumps: rounds.filter((item) => item.turnTimingTotalElapsedForwardJumpCount > 0).length,
roundsWithTerminalElapsedGrowth: rounds.filter((item) => item.turnTimingTerminalElapsedGrowthCount > 0).length,
@@ -3820,6 +3882,8 @@ function buildSampleMetrics(samples, control) {
},
loading,
sessionRailTitles,
codeAgentCardTiming,
traceOrder,
rounds,
turnColumns: turnTiming.columns,
turnTimingTable: turnTiming.rows,
@@ -4421,6 +4485,758 @@ function isTerminalTurnStatus(value) {
return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "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,
timestamp: 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 (reasons.length) {
orderAnomalies.push({
sampleIndex: row.sampleIndex,
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,
previousClockSeconds: previous.clockSeconds,
currentClockSeconds: row.clockSeconds,
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,
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,
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.length ? traceMatched : traceRows);
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 && 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,
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 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,
});
});
}
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);
return {
source,
rowIndex,
preview,
pageRole: meta.pageRole || null,
pageId: meta.pageId || null,
sessionId: meta.sessionId || null,
traceId: meta.traceId || parseTraceRowTraceId(preview),
clockSeconds: parseTraceRowClockSeconds(preview),
totalSeconds: parseTraceRowTotalSeconds(preview),
projectedSeq: parseTraceRowProjectedSeq(preview),
isCompletion: /轮次完成|turn\s+completed|completed\s+turn/i.test(preview),
};
}
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 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) {
return [row.pageRole || '', row.pageId || '', row.sessionId || '', row.traceId || 'unknown'].join('|');
}
function traceRowMatchesCard(row, card, terminalCardCount) {
if (!row) return false;
if (row.traceId && card.traceId) return row.traceId === card.traceId;
if (row.sessionId && card.sessionId) return 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);
evidences.push({ kind: 'trace-total', seconds: maxTotal, traceId: source?.traceId || null, preview: source?.preview || '' });
}
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' });
}
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 (const line of lines) {
if (!/耗时|用时|duration|elapsed/i.test(line)) continue;
const seconds = parseSelfReportedRoundDurationSeconds(line);
if (!Number.isFinite(seconds)) continue;
if (!best || seconds > best.seconds) {
best = { kind: 'final-response-duration', seconds, preview: compactOneLine(line).slice(0, 240) };
}
}
return best;
}
function parseSelfReportedRoundDurationSeconds(text) {
const value = String(text || '');
const clock = value.match(/(\d{1,2}:\d{2}:\d{2}|\d{1,2}:\d{2})/);
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(text).filter(Number.isFinite);
const recentUpdateValues = parseRecentUpdateSeconds(text).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]),
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) {
if (isTerminalTurnStatus(item?.status)) return true;
const text = codeAgentCardText(item);
return isTerminalTraceText(text) || /||||completed|failed|canceled|cancelled|blocked/iu.test(text);
}
function buildRoundCompletionMetrics(samples, timeline, turnTiming) {
const events = [];
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 elapsedMismatches = [];
const finalResponseMissing = [];
const postCompletionTimingChanges = [];
const postCompletionRecentUpdateVisible = [];
for (const event of events) {
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 > alertThresholds.turnTimingSampleSlackSeconds) {
elapsedMismatches.push({
...eventRef(event),
cardTotalElapsedSeconds: Number(bestCard.totalElapsedSeconds),
completionElapsedSeconds: Number(event.elapsedSeconds),
deltaSeconds: Number(delta.toFixed(3)),
toleranceSeconds: alertThresholds.turnTimingSampleSlackSeconds,
cardTraceId: bestCard.traceId ?? null,
cardMessageId: bestCard.messageId ?? null,
valuesRedacted: true
});
}
}
if (!hasFinalResponseAfterCompletion(samples, timeline, event)) {
finalResponseMissing.push({
...eventRef(event),
completionElapsedSeconds: event.elapsedSeconds,
valuesRedacted: true
});
}
const postTiming = detectPostCompletionTimingChanges(turnTiming, event);
postCompletionTimingChanges.push(...postTiming.timingChanges);
postCompletionRecentUpdateVisible.push(...postTiming.recentUpdateVisible);
}
return {
events: dedupeRoundCompletionRows(events).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 Array.isArray(sample?.traceRows) ? sample.traceRows : []) {
const text = String(item?.text || item?.textPreview || "");
if (!//iu.test(text)) continue;
const elapsedValues = parseTotalElapsedSeconds(text).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?.index ?? null,
elapsedSeconds: elapsedValues.length > 0 ? Math.max(...elapsedValues) : null,
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) {
if (!event.traceId && !event.messageId) return [];
return turnMetricItems(sample, timelineItem)
.filter((item) => item.promptIndex === event.promptIndex)
.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) 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.promptIndex && promptIndex !== event.promptIndex) continue;
if (sampleHasVisibleFinalResponse(sample, event)) return true;
}
return false;
}
function sampleHasVisibleFinalResponse(sample, event = {}) {
for (const group of [sample?.messages, sample?.turns]) {
if (!Array.isArray(group)) continue;
for (const item of group) {
if (event.traceId && item?.traceId && item.traceId !== event.traceId) continue;
if (event.messageId && item?.messageId && item.messageId !== event.messageId) continue;
const text = normalizedText([item?.text, item?.textPreview].filter(Boolean).join(" "));
if (text.length < 24) continue;
if (isDiagnosticText(text)) continue;
if (isFinalResultText(text)) return true;
if (//iu.test(text) && /(?:|||||||README||summary)/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 = 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 = 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 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,
};
}
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";
@@ -5067,6 +5883,11 @@ function renderMarkdown(report) {
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 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")
@@ -5089,6 +5910,33 @@ function renderMarkdown(report) {
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 ?? "-")).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 || "-") + " preview=" + escapeMarkdownCell(item.evidencePreview || item.cardPreview || "")).join("\n")
: "- 未观察到 Code Agent 卡片耗时低于 trace/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")
: "- 未观察到“加载中”可见区间。";
@@ -5161,7 +6009,48 @@ function renderMarkdown(report) {
+ "- 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"
+ "- 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"
+ "- policy: Code Agent 卡片无论终态/非终态都必须显示耗时;非终态必须显示最近更新。该 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"
+ "### 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: 轮次完成(总耗时 ...) 的耗时必须与卡片总耗时一致;完成后 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"