fix: expose full-window web probe timing anomalies (#651)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-22 13:59:55 +08:00
committed by GitHub
parent 40c593c966
commit 9924212bd9
2 changed files with 200 additions and 14 deletions
+91 -8
View File
@@ -7602,17 +7602,21 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
"const takeHead = (value, limit) => Array.isArray(value) ? value.slice(0, limit) : [];",
"const takeTail = (value, limit) => Array.isArray(value) ? value.slice(-limit) : [];",
"const firstArray = (...values) => { for (const value of values) if (Array.isArray(value)) return value; return []; };",
"const mergeArrays = (...values) => { const out = []; const seen = new Set(); for (const value of values) { if (!Array.isArray(value)) continue; for (const item of value) { const key = JSON.stringify([item?.id ?? item?.kind ?? item?.code ?? item?.columnLabel ?? item?.traceId ?? null, item?.summary ?? item?.message ?? item?.fromSeq ?? null, item?.toSeq ?? null]); if (seen.has(key)) continue; seen.add(key); out.push(item); } } return out; };",
"const clip = (value, limit = 160) => value === null || value === undefined ? null : String(value).slice(0, limit);",
"const findingRank = (item) => { const id = String(item?.id ?? item?.kind ?? item?.code ?? ''); if (id === 'page-performance-slow-same-origin-api') return 0; 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; return 10; };",
"const severityRank = (item) => { const severity = String(item?.severity ?? item?.level ?? '').toLowerCase(); if (severity === 'red') return 0; if (severity === 'amber' || severity === 'warning') return 1; if (severity === 'info') return 3; return 2; };",
"const sortFindings = (items) => (Array.isArray(items) ? items : []).slice().sort((a, b) => findingRank(a) - findingRank(b) || severityRank(a) - severityRank(b));",
"const stdoutJson = readJson(stdoutPath);",
"const reportJson = readJson(reportJsonPath);",
"const source = (stdoutJson && stdoutJson.ok !== false ? stdoutJson : null) || reportJson || stdoutJson || null;",
"const fullSource = reportJson || source;",
"const objectOrNull = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;",
"const slimRound = (item) => { const v = objectOrNull(item) || {}; return { promptIndex: v.promptIndex ?? null, sampleCount: v.sampleCount ?? null, lastTotalElapsedSeconds: v.lastTotalElapsedSeconds ?? null, lastRecentUpdateSeconds: v.lastRecentUpdateSeconds ?? null, diagnosticSamples: v.diagnosticSamples ?? null, terminalSamples: v.terminalSamples ?? null, promptTextHash: clip(v.promptTextHash, 80) }; };",
"const slimRound = (item) => { const v = objectOrNull(item) || {}; return { promptIndex: v.promptIndex ?? null, sampleCount: v.sampleCount ?? null, lastTotalElapsedSeconds: v.lastTotalElapsedSeconds ?? null, lastRecentUpdateSeconds: v.lastRecentUpdateSeconds ?? null, diagnosticSamples: v.diagnosticSamples ?? null, terminalSamples: v.terminalSamples ?? null, turnTimingTotalElapsedForwardJumpCount: v.turnTimingTotalElapsedForwardJumpCount ?? null, turnTimingTotalElapsedForwardJumpMaxSeconds: v.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null, promptTextHash: clip(v.promptTextHash, 80) }; };",
"const slimTurnColumn = (item) => { const v = objectOrNull(item) || {}; return { label: clip(v.label, 24), promptIndex: v.promptIndex ?? null, lastPromptIndex: v.lastPromptIndex ?? null, traceId: clip(v.traceId, 48), firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, source: clip(v.source, 48) }; };",
"const slimSlowSample = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, seq: v.seq ?? null, path: clip(v.path ?? v.rawPath, 96), initiatorType: clip(v.initiatorType, 24), durationMs: v.durationMs ?? null, requestToResponseStartMs: v.requestToResponseStartMs ?? v.streamOpenMs ?? null, responseTransferMs: v.responseTransferMs ?? null, nextHopProtocol: clip(v.nextHopProtocol, 24) }; };",
"const slimSlowApi = (item) => { const v = objectOrNull(item) || {}; return { path: clip(v.path ?? v.route, 96), route: clip(v.route ?? v.path, 96), sampleCount: v.sampleCount ?? null, p95Ms: v.p95Ms ?? v.p95 ?? null, maxMs: v.maxMs ?? v.max ?? null, overFiveSecondCount: v.overFiveSecondCount ?? null, slowSamples: Array.isArray(v.slowSamples) ? v.slowSamples.slice(0, 3).map(slimSlowSample) : [] }; };",
"const slimFinding = (item) => { const v = objectOrNull(item) || {}; return { kind: clip(v.kind ?? v.code, 48), code: clip(v.code ?? v.kind, 48), severity: clip(v.severity ?? v.level, 24), level: clip(v.level ?? v.severity, 24), count: v.count ?? v.sampleCount ?? null, sampleCount: v.sampleCount ?? v.count ?? null, summary: clip(v.summary ?? v.message, 180), message: clip(v.message ?? v.summary, 180) }; };",
"const slimFinding = (item) => { const v = objectOrNull(item) || {}; return { kind: clip(v.kind ?? v.id ?? v.code, 48), code: clip(v.code ?? v.id ?? v.kind, 48), severity: clip(v.severity ?? v.level, 24), level: clip(v.level ?? v.severity, 24), count: v.count ?? v.sampleCount ?? null, sampleCount: v.sampleCount ?? v.count ?? null, summary: clip(v.summary ?? v.message, 180), message: clip(v.message ?? v.summary, 180) }; };",
"const slimDomGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, text: clip(v.text ?? v.preview, 180) }; };",
"const slimNetworkGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, method: clip(v.method, 12), status: v.status ?? null, path: clip(v.path, 96), firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, promptIndexes: Array.isArray(v.promptIndexes) ? v.promptIndexes.slice(0, 6) : [], failureKinds: Array.isArray(v.failureKinds) ? v.failureKinds.slice(0, 4).map((x) => clip(x, 48)) : [] }; };",
"const slimDomSample = (item) => { const v = objectOrNull(item) || {}; return { seq: v.seq ?? null, ts: v.ts ?? null, source: clip(v.source, 32), diagnosticCode: clip(v.diagnosticCode, 48), traceId: clip(v.traceId, 64), httpStatus: v.httpStatus ?? null, idleSeconds: v.idleSeconds ?? null, waitingFor: clip(v.waitingFor, 48), lastEventLabel: clip(v.lastEventLabel, 80), text: clip(v.text ?? v.preview, 180) }; };",
@@ -7628,6 +7632,8 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" turnTimingRows: Array.isArray(srcMetrics.turnTimingRows) ? srcMetrics.turnTimingRows.length : (srcMetrics.turnTimingRows ?? null),",
" turnTimingNonMonotonicCount: srcMetrics.turnTimingNonMonotonicCount ?? null,",
" turnTimingTotalElapsedDecreaseCount: srcMetrics.turnTimingTotalElapsedDecreaseCount ?? null,",
" turnTimingTotalElapsedForwardJumpCount: srcMetrics.turnTimingTotalElapsedForwardJumpCount ?? null,",
" turnTimingTotalElapsedForwardJumpMaxSeconds: srcMetrics.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null,",
" turnTimingTerminalElapsedGrowthCount: srcMetrics.turnTimingTerminalElapsedGrowthCount ?? null,",
" turnTimingRecentUpdateJumpCount: srcMetrics.turnTimingRecentUpdateJumpCount ?? null,",
" turnTimingRecentUpdateSawtoothJumpCount: srcMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null,",
@@ -7642,6 +7648,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
"const fullRecentWindow = objectOrNull(fullSource?.windows?.recent);",
"const fullPagePerformance = objectOrNull(fullRecentWindow?.pagePerformance) || objectOrNull(fullSource?.pagePerformance);",
"const sourceSlowApi = Array.isArray(source?.pagePerformanceSlowApi) && source.pagePerformanceSlowApi.some((item) => Array.isArray(item?.slowSamples) && item.slowSamples.length > 0) ? source.pagePerformanceSlowApi : (Array.isArray(fullPagePerformance?.sameOriginApiByPath) ? fullPagePerformance.sameOriginApiByPath.filter((item) => Number(item?.overFiveSecondCount ?? 0) > 0) : []);",
"const combinedFindings = sortFindings(mergeArrays(source?.findings, fullSource?.findings));",
"const srcPromptNetwork = objectOrNull(source?.promptNetwork);",
"const promptNetwork = srcPromptNetwork ? { promptSegments: srcPromptNetwork.promptSegments ?? null } : null;",
"const compact = source ? {",
@@ -7654,7 +7661,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" pagePerformance,",
" promptNetwork,",
" pagePerformanceSlowApi: takeHead(sourceSlowApi, 4).map(slimSlowApi),",
" findings: takeHead(firstArray(source.findings, fullSource?.findings), 6).map(slimFinding),",
" findings: takeHead(combinedFindings, 8).map(slimFinding),",
" httpErrorGroups: takeHead(firstArray(source.httpErrorGroups, fullSource?.httpErrorGroups), 4).map(slimNetworkGroup),",
" requestFailedGroups: takeHead(firstArray(source.requestFailedGroups, fullSource?.requestFailedGroups), 5).map(slimNetworkGroup),",
" domDiagnosticGroups: takeHead(firstArray(source.domDiagnosticGroups, fullSource?.domDiagnosticGroups), 3).map(slimDomGroup),",
@@ -7662,8 +7669,9 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" consoleAlertGroups: takeHead(firstArray(source.consoleAlertGroups, fullSource?.consoleAlertGroups), 5).map(slimConsoleGroup),",
" consoleAlertSamples: takeHead(firstArray(source.consoleAlertSamples, fullSource?.consoleAlertSamples), 5).map(slimConsoleSample),",
" runnerErrors: takeTail(firstArray(source.runnerErrors, fullSource?.runnerErrors), 8).map(slimRunnerError),",
" turnTimingRecentUpdateJumps: takeHead(firstArray(source.turnTimingRecentUpdateJumps, srcMetrics?.turnTimingRecentUpdateJumps, fullSource?.turnTimingRecentUpdateJumps), 5).map(slimJump),",
" turnTimingElapsedZeroResets: takeHead(firstArray(source.turnTimingElapsedZeroResets, srcMetrics?.turnTimingElapsedZeroResets, fullSource?.turnTimingElapsedZeroResets), 5).map(slimJump),",
" turnTimingRecentUpdateJumps: takeHead(mergeArrays(source.turnTimingRecentUpdateJumps, srcMetrics?.turnTimingRecentUpdateJumps, fullSource?.sampleMetrics?.turnTimingRecentUpdateSawtoothJumps, fullSource?.turnTimingRecentUpdateJumps), 5).map(slimJump),",
" turnTimingElapsedZeroResets: takeHead(mergeArrays(source.turnTimingElapsedZeroResets, srcMetrics?.turnTimingElapsedZeroResets, fullSource?.sampleMetrics?.turnTimingElapsedZeroResets, fullSource?.turnTimingElapsedZeroResets), 5).map(slimJump),",
" turnTimingTotalElapsedForwardJumps: takeHead(mergeArrays(source.turnTimingTotalElapsedForwardJumps, srcMetrics?.turnTimingTotalElapsedForwardJumps, fullSource?.sampleMetrics?.turnTimingTotalElapsedForwardJumps, fullSource?.turnTimingTotalElapsedForwardJumps), 5).map(slimJump),",
" reportJsonPath: source.reportJsonPath || reportJsonPath,",
" reportJsonSha256: source.reportJsonSha256 || sha256(reportJsonPath),",
" reportMdPath: source.reportMdPath || reportMdPath,",
@@ -7688,7 +7696,47 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec
" stderrTail: tail(readText(stderrPath))",
" }",
"};",
"console.log(JSON.stringify(compact));",
"const compactStdoutLimitBytes = 8000;",
"const compactOutput = (value) => JSON.stringify(value);",
"let output = compactOutput(compact);",
"if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {",
" const minimal = {",
" ok: compact.ok,",
" counts: compact.counts ?? null,",
" analysisWindow: compact.analysisWindow ?? null,",
" archiveSummary: compact.archiveSummary ?? null,",
" sampleMetrics: compact.sampleMetrics ? {",
" rounds: Array.isArray(compact.sampleMetrics.rounds) ? compact.sampleMetrics.rounds : compact.sampleMetrics.rounds ?? null,",
" turnTimingRows: compact.sampleMetrics.turnTimingRows ?? null,",
" turnTimingNonMonotonicCount: compact.sampleMetrics.turnTimingNonMonotonicCount ?? null,",
" turnTimingTotalElapsedDecreaseCount: compact.sampleMetrics.turnTimingTotalElapsedDecreaseCount ?? null,",
" turnTimingTotalElapsedForwardJumpCount: compact.sampleMetrics.turnTimingTotalElapsedForwardJumpCount ?? null,",
" turnTimingTotalElapsedForwardJumpMaxSeconds: compact.sampleMetrics.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null,",
" turnTimingTerminalElapsedGrowthCount: compact.sampleMetrics.turnTimingTerminalElapsedGrowthCount ?? null,",
" turnTimingRecentUpdateJumpCount: compact.sampleMetrics.turnTimingRecentUpdateJumpCount ?? null,",
" turnTimingRecentUpdateSawtoothJumpCount: compact.sampleMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null,",
" turnTimingRecentUpdateMaxIncreaseSeconds: compact.sampleMetrics.turnTimingRecentUpdateMaxIncreaseSeconds ?? null,",
" turnColumns: Array.isArray(compact.sampleMetrics.turnColumns) ? compact.sampleMetrics.turnColumns.slice(-6) : []",
" } : null,",
" runtimeAlerts: compact.runtimeAlerts ?? null,",
" pagePerformance: compact.pagePerformance ?? null,",
" promptNetwork: compact.promptNetwork ?? null,",
" findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4) : [],",
" pagePerformanceSlowApi: Array.isArray(compact.pagePerformanceSlowApi) ? compact.pagePerformanceSlowApi.slice(0, 3) : [],",
" runnerErrors: Array.isArray(compact.runnerErrors) ? compact.runnerErrors.slice(-4) : [],",
" turnTimingRecentUpdateJumps: Array.isArray(compact.turnTimingRecentUpdateJumps) ? compact.turnTimingRecentUpdateJumps.slice(0, 4) : [],",
" turnTimingElapsedZeroResets: Array.isArray(compact.turnTimingElapsedZeroResets) ? compact.turnTimingElapsedZeroResets.slice(0, 4) : [],",
" turnTimingTotalElapsedForwardJumps: Array.isArray(compact.turnTimingTotalElapsedForwardJumps) ? compact.turnTimingTotalElapsedForwardJumps.slice(0, 4) : [],",
" reportJsonPath: compact.reportJsonPath ?? reportJsonPath,",
" reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath),",
" reportMdPath: compact.reportMdPath ?? reportMdPath,",
" reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath),",
" analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, compactStdoutLimitBytes, originalCompactBytes: Buffer.byteLength(output, 'utf8') },",
" valuesRedacted: true",
" };",
" output = compactOutput(minimal);",
"}",
"console.log(output);",
"UNIDESK_WEB_OBSERVE_ANALYZE_COMPACT",
"exit \"$analyzer_exit\"",
].join("\n");
@@ -7725,6 +7773,7 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
const analysisWindow = record(analysis?.analysisWindow);
const archiveSummary = record(analysis?.archiveSummary);
const sampleMetrics = record(analysis?.sampleMetrics);
const archiveMetrics = record(archiveSummary?.sampleMetrics);
const runtimeAlerts = record(analysis?.runtimeAlerts);
const pagePerformance = record(analysis?.pagePerformance);
const promptNetwork = record(analysis?.promptNetwork);
@@ -7743,6 +7792,7 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
const runnerErrors = Array.isArray(analysis?.runnerErrors) ? analysis.runnerErrors.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(-8) : [];
const recentUpdateJumps = Array.isArray(analysis?.turnTimingRecentUpdateJumps) ? analysis.turnTimingRecentUpdateJumps.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
const elapsedZeroResets = Array.isArray(analysis?.turnTimingElapsedZeroResets) ? analysis.turnTimingElapsedZeroResets.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
const elapsedForwardJumps = Array.isArray(analysis?.turnTimingTotalElapsedForwardJumps) ? analysis.turnTimingTotalElapsedForwardJumps.map(record).filter((item): item is Record<string, unknown> => item !== null).slice(0, 8) : [];
const domDiagnosticRows = domDiagnostics.length > 0
? domDiagnostics.map((item) => [
webObserveText(item.count),
@@ -7801,6 +7851,17 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
webObserveText(item.delta),
webObserveShort(webObserveText(item.traceId), 24),
]);
const elapsedForwardJumpRows = elapsedForwardJumps.map((item) => [
webObserveShort(webObserveText(item.columnLabel), 14),
webObserveText(item.promptIndex),
webObserveText(item.fromSeq),
webObserveText(item.toSeq),
`${webObserveText(item.fromValue)}->${webObserveText(item.toValue)}`,
webObserveText(item.delta),
webObserveText(item.sampleDeltaSeconds),
webObserveText(item.allowedIncreaseSeconds),
webObserveShort(webObserveText(item.traceId), 24),
]);
const httpErrorRows = httpErrorGroups.map((item) => [
webObserveText(item.count),
webObserveText(item.method),
@@ -7866,18 +7927,34 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
]),
"",
"Turn timing:",
webObserveTable(["ROUNDS", "TURNS", "ROWS", "NON_MONO", "ELAPSED_DEC", "ZERO_RESET", "TERMINAL_GROWTH", "RECENT_JUMP", "MAX_RECENT_STEP"], [[
webObserveTable(["ROUNDS", "TURNS", "ROWS", "NON_MONO", "ELAPSED_DEC", "ZERO_RESET", "ELAPSED_JUMP", "TERMINAL_GROWTH", "RECENT_JUMP", "MAX_RECENT_STEP"], [[
webObserveText(roundCount),
webObserveText(turnColumnCount),
webObserveText(sampleMetrics?.turnTimingRows),
webObserveText(sampleMetrics?.turnTimingNonMonotonicCount),
webObserveText(sampleMetrics?.turnTimingTotalElapsedDecreaseCount),
webObserveText(sampleMetrics?.turnTimingTotalElapsedZeroResetCount ?? elapsedZeroResets.length),
webObserveText(sampleMetrics?.turnTimingTotalElapsedForwardJumpCount ?? elapsedForwardJumps.length),
webObserveText(sampleMetrics?.turnTimingTerminalElapsedGrowthCount),
webObserveText(sampleMetrics?.turnTimingRecentUpdateJumpCount ?? sampleMetrics?.turnTimingRecentUpdateSawtoothJumpCount),
webObserveText(sampleMetrics?.turnTimingRecentUpdateMaxIncreaseSeconds),
]]),
"",
"Archive turn timing:",
webObserveTable(["SAMPLES", "ROUNDS", "TURNS", "ROWS", "NON_MONO", "ELAPSED_DEC", "ZERO_RESET", "ELAPSED_JUMP", "TERMINAL_GROWTH", "RECENT_JUMP", "MAX_RECENT_STEP"], [[
webObserveText(archiveMetrics?.sampleCount),
webObserveText(archiveMetrics?.rounds),
webObserveText(archiveMetrics?.turnColumns),
webObserveText(archiveMetrics?.turnTimingRows),
webObserveText(archiveMetrics?.turnTimingNonMonotonicCount),
webObserveText(archiveMetrics?.turnTimingTotalElapsedDecreaseCount),
webObserveText(archiveMetrics?.turnTimingTotalElapsedZeroResetCount),
webObserveText(archiveMetrics?.turnTimingTotalElapsedForwardJumpCount),
webObserveText(archiveMetrics?.turnTimingTerminalElapsedGrowthCount),
webObserveText(archiveMetrics?.turnTimingRecentUpdateJumpCount ?? archiveMetrics?.turnTimingRecentUpdateSawtoothJumpCount),
webObserveText(archiveMetrics?.turnTimingRecentUpdateMaxIncreaseSeconds),
]]),
"",
"Rounds:",
webObserveTable(["ROUND", "SAMPLES", "TOTAL_LAST", "RECENT_LAST", "DIAG", "TERMINAL", "PROMPT_HASH"], rounds.length > 0 ? rounds.map((item) => [
webObserveText(item.promptIndex),
@@ -7905,6 +7982,9 @@ function renderWebObserveAnalyzeTable(value: Record<string, unknown>): string {
"Total elapsed zero resets:",
webObserveTable(["TURN", "PROMPT", "FROM_SEQ", "TO_SEQ", "VALUE", "DELTA", "TRACE"], elapsedZeroResetRows.length > 0 ? elapsedZeroResetRows : [["-", "-", "-", "-", "-", "-", "-"]]),
"",
"Total elapsed forward jumps:",
webObserveTable(["TURN", "PROMPT", "FROM_SEQ", "TO_SEQ", "VALUE", "DELTA", "SAMPLE_S", "ALLOWED", "TRACE"], elapsedForwardJumpRows.length > 0 ? elapsedForwardJumpRows : [["-", "-", "-", "-", "-", "-", "-", "-", "-"]]),
"",
"Runtime alerts:",
webObserveTable(["HTTP_ERR", "REQUEST_FAILED", "DOM_DIAG", "CONSOLE_ALERT", "PROMPT_SEGMENTS", "PROMPT_NETWORK"], [[
webObserveText(runtimeAlerts?.httpErrorCount),
@@ -8372,6 +8452,8 @@ function renderWebObserveAnalyzeResult(result: Record<string, unknown>): Record<
[
["nonMonotonic", sampleMetrics.turnTimingNonMonotonicCount],
["elapsedDecrease", sampleMetrics.turnTimingTotalElapsedDecreaseCount],
["elapsedForwardJump", sampleMetrics.turnTimingTotalElapsedForwardJumpCount],
["elapsedForwardJumpMaxSec", sampleMetrics.turnTimingTotalElapsedForwardJumpMaxSeconds],
["recentJump", sampleMetrics.turnTimingRecentUpdateJumpCount],
["maxRecentStepSec", sampleMetrics.turnTimingRecentUpdateMaxIncreaseSeconds],
],
@@ -8380,7 +8462,7 @@ function renderWebObserveAnalyzeResult(result: Record<string, unknown>): Record<
rounds.length === 0
? "ROUNDS\n-"
: webObserveTable(
["ROUND", "COMMAND", "SAMPLES", "TOTAL_MAX", "TOTAL_LAST", "RECENT_MAX", "RECENT_LAST", "DIAG", "TERM", "NONMONO", "JUMP"],
["ROUND", "COMMAND", "SAMPLES", "TOTAL_MAX", "TOTAL_LAST", "RECENT_MAX", "RECENT_LAST", "DIAG", "TERM", "NONMONO", "ELAPSED_JUMP", "JUMP"],
rounds.map((item) => [
item.promptIndex,
item.promptCommandId,
@@ -8392,6 +8474,7 @@ function renderWebObserveAnalyzeResult(result: Record<string, unknown>): Record<
item.diagnosticSamples,
item.terminalSamples,
item.turnTimingNonMonotonicCount,
item.turnTimingTotalElapsedForwardJumpCount,
item.turnTimingRecentUpdateJumpCount,
]),
),
@@ -1308,7 +1308,7 @@ console.log(JSON.stringify({
readIssues: jsonlReadIssues.slice(0, 3).map((item) => ({ file: item.file, line: item.line ?? null, error: String(item.error ?? "").slice(0, 160) })),
sampleMetrics: {
...recentWindow.sampleMetrics.summary,
rounds: recentWindow.sampleMetrics.rounds.slice(-8).map((item) => ({ promptIndex: item.promptIndex, promptTextHash: item.promptTextHash, sampleCount: item.sampleCount, firstSeq: item.firstSeq, lastSeq: item.lastSeq, lastTotalElapsedSeconds: item.lastTotalElapsedSeconds, lastRecentUpdateSeconds: item.lastRecentUpdateSeconds, diagnosticSamples: item.diagnosticSamples, terminalSamples: item.terminalSamples, finalTextSamples: item.finalTextSamples, turnTimingTotalElapsedZeroResetCount: item.turnTimingTotalElapsedZeroResetCount, turnTimingRecentUpdateJumpCount: item.turnTimingRecentUpdateJumpCount, turnTimingRecentUpdateMaxIncreaseSeconds: item.turnTimingRecentUpdateMaxIncreaseSeconds })),
rounds: recentWindow.sampleMetrics.rounds.slice(-8).map((item) => ({ promptIndex: item.promptIndex, promptTextHash: item.promptTextHash, sampleCount: item.sampleCount, firstSeq: item.firstSeq, lastSeq: item.lastSeq, lastTotalElapsedSeconds: item.lastTotalElapsedSeconds, lastRecentUpdateSeconds: item.lastRecentUpdateSeconds, diagnosticSamples: item.diagnosticSamples, terminalSamples: item.terminalSamples, finalTextSamples: item.finalTextSamples, turnTimingTotalElapsedZeroResetCount: item.turnTimingTotalElapsedZeroResetCount, turnTimingTotalElapsedForwardJumpCount: item.turnTimingTotalElapsedForwardJumpCount, turnTimingTotalElapsedForwardJumpMaxSeconds: item.turnTimingTotalElapsedForwardJumpMaxSeconds, turnTimingRecentUpdateJumpCount: item.turnTimingRecentUpdateJumpCount, turnTimingRecentUpdateMaxIncreaseSeconds: item.turnTimingRecentUpdateMaxIncreaseSeconds })),
turnColumns: recentWindow.sampleMetrics.turnColumns.slice(-12).map((item) => ({ label: item.label, source: item.source, promptIndex: item.promptIndex, lastPromptIndex: item.lastPromptIndex, firstSeq: item.firstSeq, lastSeq: item.lastSeq, traceId: item.traceId, messageId: item.messageId })),
},
pageProvenance: recentWindow.pageProvenance.summary,
@@ -1405,8 +1405,24 @@ console.log(JSON.stringify({
delta: item.delta ?? null,
anomaly: item.anomaly ?? null,
})),
pagePerformanceSlowApi: recentWindow.pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, p95Ms: item.p95Ms, maxMs: item.maxMs, overFiveSecondCount: item.overFiveSecondCount })),
findings: recentWindow.findings.slice(0, 8).map((item) => ({ kind: item.id ?? item.kind ?? item.code, severity: item.severity, count: item.count ?? item.sampleCount ?? null, summary: String(item.summary ?? item.message ?? "").slice(0, 180) })),
turnTimingTotalElapsedForwardJumps: recentWindow.sampleMetrics.turnTimingTotalElapsedForwardJumps.slice(0, 8).map((item) => ({
columnLabel: item.columnLabel ?? item.columnId ?? null,
promptIndex: item.promptIndex ?? null,
traceId: item.traceId ?? null,
fromSeq: item.fromSeq ?? null,
toSeq: item.toSeq ?? null,
fromTs: item.fromTs ?? null,
toTs: item.toTs ?? null,
fromValue: item.fromValue ?? null,
toValue: item.toValue ?? null,
delta: item.delta ?? null,
sampleDeltaSeconds: item.sampleDeltaSeconds ?? null,
allowedIncreaseSeconds: item.allowedIncreaseSeconds ?? null,
excessiveIncreaseSeconds: item.excessiveIncreaseSeconds ?? null,
anomaly: item.anomaly ?? null,
})),
pagePerformanceSlowApi: recentWindow.pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0).slice(0, 5).map((item) => ({ path: item.path, route: item.route, sampleCount: item.sampleCount, p95Ms: item.p95Ms, maxMs: item.maxMs, overFiveSecondCount: item.overFiveSecondCount, slowSamples: item.slowSamples })),
findings: prioritizeFindings(recentWindow.findings).slice(0, 8).map((item) => ({ kind: item.id ?? item.kind ?? item.code, severity: item.severity, count: item.count ?? item.sampleCount ?? null, summary: String(item.summary ?? item.message ?? "").slice(0, 180) })),
valuesRedacted: true,
}));
@@ -1575,6 +1591,8 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN
if (promptFailures.length > 0) findings.push({ id: "prompt-chat-submit-failed", severity: "red", summary: "sendPrompt command had no successful /v1/agent/chat POST response in the sampling window", count: promptFailures.length, rounds: promptFailures.slice(0, 10) });
const elapsedZeroResets = Array.isArray(sampleMetrics?.turnTimingElapsedZeroResets) ? sampleMetrics.turnTimingElapsedZeroResets : [];
if (elapsedZeroResets.length > 0) findings.push({ id: "turn-timing-total-elapsed-zero-reset", severity: "red", summary: "Code Agent total elapsed jumped from a non-zero value back to 0 seconds", count: elapsedZeroResets.length, samples: elapsedZeroResets.slice(0, 20) });
const elapsedForwardJumps = Array.isArray(sampleMetrics?.turnTimingTotalElapsedForwardJumps) ? sampleMetrics.turnTimingTotalElapsedForwardJumps : [];
if (elapsedForwardJumps.length > 0) findings.push({ id: "turn-timing-total-elapsed-forward-jump", severity: "red", summary: "Code Agent total elapsed jumped forward faster than browser sample interval", count: elapsedForwardJumps.length, samples: elapsedForwardJumps.slice(0, 20) });
const recentUpdateSawtoothJumps = Array.isArray(sampleMetrics?.turnTimingRecentUpdateSawtoothJumps)
? sampleMetrics.turnTimingRecentUpdateSawtoothJumps
: Array.isArray(sampleMetrics?.turnTimingNonMonotonic)
@@ -2496,6 +2514,30 @@ function isDiagnosticText(text) {
return /Failed to (?:fetch|load resource)|request failed|net::ERR_[A-Z0-9_:-]+|server responded with a status of [45][0-9]{2}|HTTP\s+[45][0-9]{2}\b|trace_id=|workbench turn\s*超过|turn\s*超过|无新活动|idle\s+\d+s|waitingFor=|lastEventLabel=|无法连接上游|代理暂时无法连接上游|provider-unavailable|agentrun:error|AgentRun error|projection-resume|sync-failed|durable projection store|realtime-gap|Trace 更新超时|加载失败|请求失败|请求已失败/iu.test(value);
}
function prioritizeFindings(findings) {
const items = Array.isArray(findings) ? findings : [];
const severityRank = (severity) => {
const value = String(severity || "").toLowerCase();
if (value === "red") return 0;
if (value === "amber" || value === "warning") return 1;
if (value === "info") return 3;
return 2;
};
const kindRank = (item) => {
const id = String(item?.id ?? item?.kind ?? item?.code ?? "");
if (id === "page-performance-slow-same-origin-api") return 0;
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;
return 10;
};
return items.slice().sort((left, right) => {
const kindDelta = kindRank(left) - kindRank(right);
if (kindDelta !== 0) return kindDelta;
return severityRank(left?.severity ?? left?.level) - severityRank(right?.severity ?? right?.level);
});
}
function isTerminalTraceText(text) {
return /轮次完成|轮次失败|轮次取消|已记录|已完成第\d+轮|final response|sealed final response|turn completed|turn failed|turn canceled|terminal result|\bcompleted\b|\bfailed\b|\bcanceled\b|\bcancelled\b|\bterminal\b|\bdone\b/iu.test(String(text || ""));
}
@@ -2541,6 +2583,7 @@ function buildSampleMetrics(samples, control) {
const turnCells = turnTiming.rows.flatMap((row) => Object.values(row.cells || {}));
const turnTimingNonMonotonic = Array.isArray(turnTiming.nonMonotonic) ? turnTiming.nonMonotonic : [];
const turnTimingElapsedZeroResets = Array.isArray(turnTiming.elapsedZeroResets) ? turnTiming.elapsedZeroResets : [];
const turnTimingTotalElapsedForwardJumps = Array.isArray(turnTiming.totalElapsedForwardJumps) ? turnTiming.totalElapsedForwardJumps : [];
const turnTimingRecentUpdateSawtoothJumps = turnTimingNonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump");
const turnTimingTerminalElapsedGrowth = Array.isArray(turnTiming.terminalElapsedGrowth) ? turnTiming.terminalElapsedGrowth : [];
const turnTimingRecentUpdateResets = Array.isArray(turnTiming.recentUpdateResets) ? turnTiming.recentUpdateResets : [];
@@ -2565,6 +2608,7 @@ function buildSampleMetrics(samples, control) {
const rounds = buildRoundMetricSummaries(timeline, promptCommands, {
nonMonotonic: turnTimingNonMonotonic,
elapsedZeroResets: turnTimingElapsedZeroResets,
totalElapsedForwardJumps: turnTimingTotalElapsedForwardJumps,
terminalElapsedGrowth: turnTimingTerminalElapsedGrowth,
recentUpdateResets: turnTimingRecentUpdateResets,
recentUpdateSteps: turnTimingRecentUpdateSteps
@@ -2585,6 +2629,8 @@ function buildSampleMetrics(samples, control) {
turnTimingNonMonotonicCount: turnTimingNonMonotonic.length,
turnTimingTotalElapsedDecreaseCount: turnTimingNonMonotonic.filter((item) => item.metric === "totalElapsedSeconds").length,
turnTimingTotalElapsedZeroResetCount: turnTimingElapsedZeroResets.length,
turnTimingTotalElapsedForwardJumpCount: turnTimingTotalElapsedForwardJumps.length,
turnTimingTotalElapsedForwardJumpMaxSeconds: maxPositiveDelta(turnTimingTotalElapsedForwardJumps),
turnTimingTerminalElapsedGrowthCount: turnTimingTerminalElapsedGrowth.length,
turnTimingTerminalElapsedGrowthMaxSeconds: turnTimingTerminalElapsedGrowthDeltas.length > 0 ? Math.max(...turnTimingTerminalElapsedGrowthDeltas) : 0,
turnTimingRecentUpdateJumpCount: recentUpdateJumpCount,
@@ -2595,6 +2641,7 @@ function buildSampleMetrics(samples, control) {
turnTimingRecentUpdateResetCount: turnTimingRecentUpdateResets.length,
turnTimingRecentUpdateDecreaseCount: turnTimingRecentUpdateResets.length,
roundsWithTurnTimingNonMonotonic: rounds.filter((item) => item.turnTimingNonMonotonicCount > 0).length,
roundsWithTurnTimingTotalElapsedForwardJumps: rounds.filter((item) => item.turnTimingTotalElapsedForwardJumpCount > 0).length,
roundsWithTerminalElapsedGrowth: rounds.filter((item) => item.turnTimingTerminalElapsedGrowthCount > 0).length,
roundsWithRecentUpdateJumps: rounds.filter((item) => item.turnTimingRecentUpdateJumpCount > 0).length
},
@@ -2603,6 +2650,7 @@ function buildSampleMetrics(samples, control) {
turnTimingTable: turnTiming.rows,
turnTimingNonMonotonic,
turnTimingElapsedZeroResets,
turnTimingTotalElapsedForwardJumps,
turnTimingTerminalElapsedGrowth,
turnTimingRecentUpdateSawtoothJumps,
turnTimingRecentUpdateSteps,
@@ -2673,6 +2721,7 @@ function buildTurnTimingTable(samples, timeline) {
rows,
nonMonotonic: timingEvents.anomalies,
elapsedZeroResets: timingEvents.elapsedZeroResets,
totalElapsedForwardJumps: timingEvents.totalElapsedForwardJumps,
terminalElapsedGrowth: timingEvents.terminalElapsedGrowth,
recentUpdateResets: timingEvents.recentUpdateResets,
recentUpdateSteps: timingEvents.recentUpdateSteps
@@ -2682,6 +2731,7 @@ function buildTurnTimingTable(samples, timeline) {
function detectTurnTimingNonMonotonic(columns, rows) {
const anomalies = [];
const elapsedZeroResets = [];
const totalElapsedForwardJumps = [];
const terminalElapsedGrowth = [];
const recentUpdateResets = [];
const recentUpdateSteps = [];
@@ -2720,6 +2770,35 @@ function detectTurnTimingNonMonotonic(columns, rows) {
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 + 3;
if (delta > allowedIncreaseSeconds) {
totalElapsedForwardJumps.push({
columnId: column.id,
columnLabel: column.label,
metric,
anomaly: "forward-jump",
expectedPattern: "total-elapsed-increase-matches-browser-sample-interval",
fromSeq: previous.seq,
fromTs: previous.ts,
fromValue: previous.value,
toSeq: row.seq ?? null,
toTs: row.ts ?? null,
toValue: current,
delta,
sampleDeltaSeconds,
allowedIncreaseSeconds,
excessiveIncreaseSeconds: Number((delta - allowedIncreaseSeconds).toFixed(3)),
traceId: cell.traceId ?? column.traceId ?? null,
messageId: cell.messageId ?? column.messageId ?? null,
promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
source: cell.source ?? column.source ?? null,
valuesRedacted: true
});
}
}
if (metric === "totalElapsedSeconds" && isTerminalTurnStatus(cell.status)) {
if (previousTerminalTotal && current > previousTerminalTotal.value) {
terminalElapsedGrowth.push({
@@ -2824,7 +2903,21 @@ function detectTurnTimingNonMonotonic(columns, rows) {
}
}
}
return { anomalies, elapsedZeroResets, terminalElapsedGrowth, recentUpdateResets, recentUpdateSteps };
return { anomalies, elapsedZeroResets, totalElapsedForwardJumps, terminalElapsedGrowth, 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) {
@@ -2931,6 +3024,7 @@ function firstTraceId(texts) {
function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) {
const rounds = [];
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 : [];
@@ -2941,6 +3035,7 @@ function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) {
const totalElapsed = items.map((item) => item.totalElapsedSeconds).filter((value) => value !== null);
const recentUpdate = items.map((item) => item.recentUpdateSeconds).filter((value) => value !== null);
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);
@@ -2971,6 +3066,8 @@ function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) {
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,
@@ -3151,6 +3248,10 @@ function renderTurnTimingTable(sampleMetrics) {
const elapsedZeroResetLines = elapsedZeroResets.length > 0
? elapsedZeroResets.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " totalElapsed zero-reset " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " 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) => "- " + (item.columnLabel || item.columnId || "-") + " totalElapsed forward-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 sawtoothJumps = Array.isArray(sampleMetrics?.turnTimingRecentUpdateSawtoothJumps)
? sampleMetrics.turnTimingRecentUpdateSawtoothJumps
: nonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump");
@@ -3169,7 +3270,7 @@ function renderTurnTimingTable(sampleMetrics) {
const resetLines = recentUpdateResets.length > 0
? recentUpdateResets.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " 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")
: "- 未观察到最近更新归零/回落。";
return lines.join("\n") + "\n\n列说明:\n" + columnLines + "\n\n异常事件(仅报表暴露,不做下游 repair;最近更新按三角波模型检测异常跳增):\n" + nonMonotonicLines + "\n\nTerminal 后总耗时增长事件(终态 turn 的总耗时应 sealed,不应继续增长):\n" + terminalGrowthLines + "\n\n总耗时归零跳变事件(例如已显示真实耗时后又变成 0 秒):\n" + elapsedZeroResetLines + "\n\n最近更新 sawtooth jump 事件(预期每秒增长约 1,遇到新活动归零;例如 1 秒 -> 1 分 4 秒应被列入这里):\n" + sawtoothJumpLines + "\n\n最近更新相邻采样 step(按 delta 降序;用于人工识别一秒跳几十秒/一分钟的瞬态):\n" + stepLines + "\n\n最近更新 reset 事件(预期三角波归零,不计为异常):\n" + resetLines;
return lines.join("\n") + "\n\n列说明:\n" + columnLines + "\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 formatMetricCell(value) {
@@ -3206,7 +3307,7 @@ function renderMarkdown(report) {
? report.promptNetwork.rounds.map((item) => "- round " + item.promptIndex + " promptHash=" + (item.promptTextHash || "-") + " chatPostOk=" + String(item.chatPostOk) + " 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 + " totalMax=" + (item.maxTotalElapsedSeconds ?? "-") + " totalLast=" + (item.lastTotalElapsedSeconds ?? "-") + " recentMax=" + (item.maxRecentUpdateSeconds ?? "-") + " recentLast=" + (item.lastRecentUpdateSeconds ?? "-") + " totalDecrease=" + (item.turnTimingTotalElapsedDecreaseCount ?? 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")
? report.sampleMetrics.rounds.map((item) => "- round " + item.promptIndex + " promptHash=" + (item.promptTextHash || "-") + " samples=" + item.sampleCount + " 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 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")
@@ -3237,6 +3338,8 @@ function renderMarkdown(report) {
+ "- 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"