From 6804decc569366feed228182dc48802aac7b99d1 Mon Sep 17 00:00:00 2001 From: lyon Date: Sun, 21 Jun 2026 15:45:40 +0800 Subject: [PATCH] fix: stop completed workbench diagnostics polling --- web/hwlab-cloud-web/src/stores/workbench.ts | 17 +++++++++++++++++ .../specs/projection-diagnostics.spec.ts | 10 ++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index 0983f25c..5a8fe8f6 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -421,6 +421,7 @@ export const useWorkbenchStore = defineStore("workbench", () => { const response = await api.workbench.sessionMessages(id, { limit: 100 }); if (!response.ok || !response.data) { if (shouldSuppressTransientWorkbenchReadFailure(response)) return; + if (traceHasCompletedFinalResponse(traceId, messages.value)) return; applyProjectionDiagnostic(traceId, projectionDiagnosticFromApiFailure(response, { code: "message_projection_refresh_failed", message: response.error ?? "消息投影刷新失败,主消息正文保持上一份 canonical projection。", health: response.status === 0 ? "unavailable" : "degraded" })); return; } @@ -498,6 +499,7 @@ export const useWorkbenchStore = defineStore("workbench", () => { return; } if (shouldSuppressTransientWorkbenchReadFailure(response)) return; + if (traceHasCompletedFinalResponse(id, messages.value)) return; applyProjectionDiagnostic(id, projectionDiagnosticFromApiFailure(response, { code: "turn_status_poll_failed", message: response.error ?? "状态更新失败,当前 turn 状态暂不可见。", health: response.status === 0 ? "unavailable" : "degraded" })); } @@ -663,6 +665,7 @@ export const useWorkbenchStore = defineStore("workbench", () => { const result = await fetchTraceHydrationPage(traceId, afterProjectedSeq); if (!result.ok || !result.data) { if (shouldSuppressTransientWorkbenchReadFailure(result)) return; + if (messageHasCompletedFinalResponse(message)) return; applyProjectionDiagnostic(traceId, projectionDiagnosticFromApiFailure(result, { code: "trace_hydration_failed", message: result.error ?? "Trace 更新超时,运行记录暂不可见。", health: result.status === 0 ? "unavailable" : "degraded" })); return; } @@ -1702,6 +1705,7 @@ function agentErrorFromProjection(projection: ProjectionDiagnostic): ChatMessage function messageNeedsTerminalDiagnostics(message: ChatMessage): boolean { if (message.role !== "agent") return false; if (!isTerminalMessageStatus(message.status)) return false; + if (messageHasCompletedFinalResponse(message)) return false; const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId); if (!traceId) return false; const agentRun = agentRunFromMessage(message); @@ -1711,6 +1715,7 @@ function messageNeedsTerminalDiagnostics(message: ChatMessage): boolean { function messageNeedsTraceHydration(message: ChatMessage): boolean { if (message.role !== "agent") return false; + if (messageHasCompletedFinalResponse(message)) return false; const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId); if (!traceId) return false; const trace = message.runnerTrace; @@ -1720,6 +1725,18 @@ function messageNeedsTraceHydration(message: ChatMessage): boolean { return events.length === 0 || trace?.eventsCompacted === true || trace?.fullTraceLoaded !== true; } +function traceHasCompletedFinalResponse(traceId: string | null | undefined, source: ChatMessage[]): boolean { + const id = firstNonEmptyString(traceId); + if (!id) return false; + return source.some((message) => firstNonEmptyString(message.traceId, message.runnerTrace?.traceId) === id && messageHasCompletedFinalResponse(message)); +} + +function messageHasCompletedFinalResponse(message: ChatMessage | null | undefined): boolean { + if (!message || message.role !== "agent") return false; + if (normalizedStatusText(message.status) !== "completed" && normalizedStatusText(message.runnerTrace?.status) !== "completed") return false; + return Boolean(firstNonEmptyString(message.text, finalResponseText((message as Record).finalResponse))); +} + function traceHasEvents(trace: ChatMessage["runnerTrace"]): boolean { return Array.isArray(trace?.events) && trace.events.length > 0; } diff --git a/web/hwlab-cloud-web/tests/workbench-e2e/specs/projection-diagnostics.spec.ts b/web/hwlab-cloud-web/tests/workbench-e2e/specs/projection-diagnostics.spec.ts index 31cf9122..0884e1b1 100644 --- a/web/hwlab-cloud-web/tests/workbench-e2e/specs/projection-diagnostics.spec.ts +++ b/web/hwlab-cloud-web/tests/workbench-e2e/specs/projection-diagnostics.spec.ts @@ -75,19 +75,13 @@ test.describe("REST gap hydration errors", () => { test.describe("sealed final response diagnostics separation", () => { test.use({ scenarioId: "sealed-final-response-diagnostics" }); - test("keeps completed final response visible while showing diagnostics separately", async ({ page }, testInfo) => { + test("keeps completed final response visible without surfacing transient hydration diagnostics", async ({ page }, testInfo) => { await gotoWorkbench(page, "/workbench/sessions/ses_sealed_final_diag"); const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last(); - const diagnostic = card.locator(".projection-diagnostic"); - await expectCompactDiagnostic(diagnostic, card, "55555555555555555555555555555555"); - await expect(diagnostic).not.toContainText("req_e2e_sealed_final_trace_timeout"); - await expandDiagnostic(diagnostic); - await expect(diagnostic).toContainText("Trace 更新超时"); - await expect(diagnostic).toContainText("req_e2e_sealed_final_trace_timeout"); - await expect(diagnostic).toContainText("/v1/workbench/traces/trc_sealed_final_diag/events"); const finalResponse = card.locator(".message-text").filter({ hasText: "已完成的 sealed final response 不应被诊断覆盖。" }); await expect(finalResponse).toBeVisible(); await expect(finalResponse).not.toContainText("Trace 更新超时"); + await expect(card.locator(".projection-diagnostic")).toHaveCount(0); await saveScreenshot(page, testInfo, "sealed-final-response-diagnostic-separate"); }); });