From 4290a280d9aaf9c31cb59ca89f46449821589b2b Mon Sep 17 00:00:00 2001 From: lyon Date: Sat, 20 Jun 2026 10:09:47 +0800 Subject: [PATCH] fix: separate workbench message and trace authorities --- .../scripts/workbench-e2e-server.ts | 45 ++++ .../workbench/ConversationPanel.vue | 9 +- .../src/composables/useTraceSubscription.ts | 19 +- .../src/stores/workbench-server-state.ts | 19 +- web/hwlab-cloud-web/src/stores/workbench.ts | 211 +++++++++++------- .../specs/projection-diagnostics.spec.ts | 19 ++ 6 files changed, 218 insertions(+), 104 deletions(-) diff --git a/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts b/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts index 08016391..4ae09a80 100644 --- a/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts +++ b/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts @@ -96,6 +96,8 @@ const terminalAssistantFinalText = [ "| Python | CPython | pass |", "| Rust | native | pass |" ].join("\n"); +const canonicalTraceFinalText = "messageProjection 的 sealed final response 才是主消息正文。"; +const traceDetailConflictText = "TRACE_DETAIL_CONFLICT_SHOULD_NOT_REPLACE_MESSAGE"; let state = createScenarioState("baseline"); const sseClients = new Set(); @@ -630,6 +632,10 @@ function createScenarioState(scenarioId: string): ScenarioState { sessions.unshift(sealedFinalResponseDiagnosticSession()); traces.trc_sealed_final_diag = sealedFinalResponseDiagnosticTrace(); } + if (id === "trace-final-response-not-message-authority") { + sessions.unshift(traceFinalResponseConflictSession()); + traces.trc_trace_final_conflict = traceFinalResponseConflictTrace(); + } if (id === "scroll-follow-long-trace") { const session = scrollFollowSession(); sessions.unshift(session); @@ -673,6 +679,8 @@ function createScenarioState(scenarioId: string): ScenarioState { ? "ses_trace_hydration_timeout" : id === "sealed-final-response-diagnostics" ? "ses_sealed_final_diag" + : id === "trace-final-response-not-message-authority" + ? "ses_trace_final_conflict" : id === "create-while-route-loading" ? "ses_running" : base.selectedSessionId; @@ -993,6 +1001,43 @@ function sealedFinalResponseDiagnosticSession(): SessionRecord { }; } +function traceFinalResponseConflictTrace(): JsonRecord { + const now = new Date().toISOString(); + const event = { seq: 1, sourceSeq: 91, createdAt: now, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", terminal: true, final: true, replyAuthority: true, message: traceDetailConflictText }; + return { + traceId: "trc_trace_final_conflict", + status: "completed", + sessionId: "ses_trace_final_conflict", + threadId: "thr_trace_final_conflict", + turnId: "turn_trace_final_conflict", + events: [event], + eventCount: 1, + fullTraceLoaded: true, + hasMore: false, + finalResponse: { text: traceDetailConflictText, status: "completed" }, + terminalEvidence: { terminal: true, status: "completed" }, + sealedAt: now + }; +} + +function traceFinalResponseConflictSession(): SessionRecord { + const now = new Date().toISOString(); + return { + sessionId: "ses_trace_final_conflict", + threadId: "thr_trace_final_conflict", + status: "completed", + lastTraceId: "trc_trace_final_conflict", + updatedAt: now, + messageCount: 2, + firstUserMessagePreview: "trace final response authority separation", + turnSummary: { traceId: "trc_trace_final_conflict", status: "completed", running: false, terminal: true, sealedAt: now }, + messages: [ + { id: "msg_trace_final_conflict_user", messageId: "msg_trace_final_conflict_user", role: "user", title: "用户", text: "trace final response authority separation", status: "sent", createdAt: now, sessionId: "ses_trace_final_conflict", threadId: "thr_trace_final_conflict", turnId: "turn_trace_final_conflict" }, + { id: "msg_trace_final_conflict_agent", messageId: "msg_trace_final_conflict_agent", role: "agent", title: "Code Agent", text: canonicalTraceFinalText, status: "completed", createdAt: now, updatedAt: now, sessionId: "ses_trace_final_conflict", threadId: "thr_trace_final_conflict", traceId: "trc_trace_final_conflict", turnId: "turn_trace_final_conflict", sealedAt: now, finalResponse: { text: canonicalTraceFinalText, status: "completed" }, runnerTrace: { traceId: "trc_trace_final_conflict", status: "completed", sessionId: "ses_trace_final_conflict", threadId: "thr_trace_final_conflict", events: [], eventCount: 1, fullTraceLoaded: false, hasMore: true } } + ] + }; +} + function noFinalRunningTrace(): JsonRecord { const createdAt = new Date().toISOString(); return { diff --git a/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue b/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue index ded92454..18e06880 100644 --- a/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue +++ b/web/hwlab-cloud-web/src/components/workbench/ConversationPanel.vue @@ -116,6 +116,13 @@ function traceStorageKey(message: ChatMessage): string { function traceAutoExpanded(message: ChatMessage): boolean | null { return traceLifecycleExpanded(message, traceTimelinePolicy.value); } + +function traceForDisplay(message: ChatMessage): ChatMessage["runnerTrace"] { + if (!message.runnerTrace) return message.runnerTrace; + const status = firstNonEmptyString(message.status, message.runnerTrace.status); + if (!status || status === message.runnerTrace.status) return message.runnerTrace; + return { ...message.runnerTrace, status }; +}