From bd5acfae06ce5668ea1df6a2d28b7880cd9ef848 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Wed, 24 Jun 2026 04:09:58 +0800 Subject: [PATCH] fix(web-probe): recover compact analyze turn details (#776) * fix(web-probe): keep rounds in compact analyze output * fix(web-probe): recover compact analyze turn details --------- Co-authored-by: Codex --- scripts/src/hwlab-node-impl.ts | 44 +++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index 418a3e7f..fed82a1c 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -8831,7 +8831,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec "exit \"$analyzer_exit\"", ].join("\n"); const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds); - const analysis = parseJsonObject(result.stdout); + const analysis = recoverWebObserveAnalyzeTurnDetails(options, spec, parseJsonObject(result.stdout)); const analysisOk = result.exitCode === 0 && analysis?.ok === true; const analysisFailure = analysisOk ? null @@ -8864,6 +8864,48 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec }); } +function recoverWebObserveAnalyzeTurnDetails(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec, analysis: Record): Record { + const sampleMetrics = record(analysis.sampleMetrics); + const hasRounds = Array.isArray(sampleMetrics?.rounds) && sampleMetrics.rounds.length > 0; + const hasColumns = Array.isArray(sampleMetrics?.turnColumns) && sampleMetrics.turnColumns.length > 0; + if (hasRounds && hasColumns) return analysis; + const reportJsonPath = typeof analysis.reportJsonPath === "string" ? analysis.reportJsonPath : ""; + if (!reportJsonPath) return analysis; + const archiveMetrics = record(record(analysis.archiveSummary)?.sampleMetrics); + const archiveHasTurnEvidence = Number(archiveMetrics?.rounds ?? 0) > 0 || Number(archiveMetrics?.turnColumns ?? 0) > 0 || Number(archiveMetrics?.turnTimingRows ?? 0) > 0; + if (!archiveHasTurnEvidence) return analysis; + const recoverScript = [ + "set -eu", + `report_json=${shellQuote(reportJsonPath)}`, + "node - \"$report_json\" <<'UNIDESK_WEB_OBSERVE_RECOVER_TURN_DETAILS'", + "const fs = require('fs');", + "const reportJsonPath = process.argv[2];", + "const readJson = (path) => { try { return JSON.parse(fs.readFileSync(path, 'utf8')); } catch { return null; } };", + "const report = readJson(reportJsonPath) || {};", + "const stdoutJson = readJson(String(reportJsonPath).replace(/\\/analysis\\/report\\.json$/u, '/analysis/analyzer-stdout.json')) || {};", + "const objectOrNull = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;", + "const firstNonEmptyArray = (...values) => { for (const value of values) if (Array.isArray(value) && value.length > 0) return value; return []; };", + "const clip = (value, limit = 160) => value === null || value === undefined ? null : String(value).slice(0, limit);", + "const slimRound = (item) => { const v = objectOrNull(item) || {}; return { promptIndex: v.promptIndex ?? null, sampleCount: v.sampleCount ?? null, loadingSamples: v.loadingSamples ?? null, maxLoadingCount: v.maxLoadingCount ?? null, loadingOwnerCount: v.loadingOwnerCount ?? 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 rounds = firstNonEmptyArray(stdoutJson.sampleMetrics?.rounds, stdoutJson.sampleMetrics?.roundItems, report.windows?.recent?.sampleMetrics?.rounds, report.sampleMetrics?.rounds).slice(-8).map(slimRound);", + "const turnColumns = firstNonEmptyArray(stdoutJson.sampleMetrics?.turnColumns, report.windows?.recent?.sampleMetrics?.turnColumns, report.sampleMetrics?.turnColumns).slice(-12).map(slimTurnColumn);", + "console.log(JSON.stringify({ ok: true, rounds, turnColumns }));", + "UNIDESK_WEB_OBSERVE_RECOVER_TURN_DETAILS", + ].join("\n"); + const recovered = parseJsonObject(runTransWorkspaceStdinScript(options.node, spec.workspace, recoverScript, Math.min(options.commandTimeoutSeconds, 30)).stdout); + const recoveredRounds = Array.isArray(recovered.rounds) ? recovered.rounds : []; + const recoveredColumns = Array.isArray(recovered.turnColumns) ? recovered.turnColumns : []; + if (recoveredRounds.length === 0 && recoveredColumns.length === 0) return analysis; + const nextMetrics = { ...(sampleMetrics ?? {}) }; + if (!hasRounds && recoveredRounds.length > 0) { + nextMetrics.roundItems = recoveredRounds; + nextMetrics.rounds = recoveredRounds; + } + if (!hasColumns && recoveredColumns.length > 0) nextMetrics.turnColumns = recoveredColumns; + return { ...analysis, sampleMetrics: nextMetrics, recoveredTurnDetails: { rounds: recoveredRounds.length, turnColumns: recoveredColumns.length, source: "analysis-report", valuesRedacted: true } }; +} + function withWebObserveAnalyzeRendered(value: Record): RenderedCliResult { return { ok: value.ok !== false,