From 2a313e1ab88b0d8dc4f69b164488240b781c8d6d Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:43:06 +0800 Subject: [PATCH] fix(workbench): preserve projected trace order and duration (#1974) --- .../src/composables/useTraceSubscription.ts | 14 ++++++++++---- web/hwlab-cloud-web/src/stores/workbench.ts | 13 +++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts b/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts index 79af5121..657e6c8f 100644 --- a/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts +++ b/web/hwlab-cloud-web/src/composables/useTraceSubscription.ts @@ -146,7 +146,7 @@ function mergeTraceTimingProjection(previous: unknown, next: unknown): TraceSnap const terminalTiming = traceTimingIsTerminal(next) || finishedAt !== null; const previousTerminalDurationMs = previousTiming.finishedAt ? previousTiming.durationMs : null; const durationMs = terminalTiming - ? firstTraceDuration(nextTiming.durationMs, elapsedDurationMs, previousTerminalDurationMs) + ? maxTraceDuration(previousTiming.durationMs, nextTiming.durationMs, elapsedDurationMs, previousTerminalDurationMs) : maxTraceDuration(previousTiming.durationMs, nextTiming.durationMs, elapsedDurationMs); return { ...previousTiming, @@ -266,17 +266,23 @@ function mergeTraceEvents(previousEvents: TraceEvent[], nextEvents: TraceEvent[] } function traceEventSortSeq(event: TraceEvent): number { - const seq = Number(event.seq); + const seq = traceEventProjectedSeq(event); return Number.isFinite(seq) ? seq : Number.MAX_SAFE_INTEGER; } function traceEventIdentity(event: TraceEvent): string | null { const label = typeof event.label === "string" ? event.label : typeof event.type === "string" ? event.type : ""; - const seq = typeof event.seq === "number" || typeof event.seq === "string" ? String(event.seq) : ""; + const seqValue = event.projectedSeq ?? event.seq; + const seq = typeof seqValue === "number" || typeof seqValue === "string" ? String(seqValue) : ""; if (seq) return `projected:${seq}:${label || "event"}`; return null; } +function traceEventProjectedSeq(event: TraceEvent): number { + const seq = Number(event.projectedSeq ?? event.seq); + return Number.isFinite(seq) ? Math.trunc(seq) : Number.NaN; +} + export function mergeTraceResults(terminal: AgentChatResultResponse, trace: TraceSnapshot | null): AgentChatResultResponse { if (!trace) return terminal; const mergedTrace: TraceSnapshot = { @@ -486,7 +492,7 @@ function traceSnapshotNextProjectedSeq(snapshot: TraceSnapshot, fallback: number if (Number.isFinite(direct) && direct >= 0) return Math.trunc(direct); const events = Array.isArray(snapshot.events) ? snapshot.events : []; return events.reduce((max, event) => { - const seq = Number(event.seq); + const seq = traceEventProjectedSeq(event); return Number.isFinite(seq) && seq > max ? Math.trunc(seq) : max; }, fallback); } diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index fe4d4867..0d6482ba 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -1810,8 +1810,8 @@ function messageTimingPatchForMerge(message: ChatMessage, value: unknown): Parti function terminalTimingDurationMsForMerge(message: ChatMessage, timing: WorkbenchTurnTimingProjection): number | null { const elapsed = elapsedTimingDurationMs(timing); - const positive = firstPositiveFiniteNumber(timing.durationMs, elapsed, message.durationMs, message.timing?.durationMs, message.runnerTrace?.durationMs, message.runnerTrace?.timing?.durationMs); - if (positive !== null) return positive; + const maximum = maxFiniteNumber(timing.durationMs, elapsed, message.durationMs, message.timing?.durationMs, message.runnerTrace?.durationMs, message.runnerTrace?.timing?.durationMs); + if (maximum !== null) return maximum; return firstFiniteNumber(timing.durationMs, elapsed, message.durationMs, message.timing?.durationMs, message.runnerTrace?.durationMs, message.runnerTrace?.timing?.durationMs); } @@ -2180,7 +2180,7 @@ function traceHydrationProjectedSeq(trace: ChatMessage["runnerTrace"]): number { if (Number.isFinite(rangeNext) && rangeNext > 0 && trace?.hasMore === true) return Math.trunc(rangeNext); const events = Array.isArray(trace?.events) ? trace.events : []; return events.reduce((max, event) => { - const seq = Number(event.seq); + const seq = traceEventProjectedSeq(event); return Number.isFinite(seq) && seq > max ? Math.trunc(seq) : max; }, 0); } @@ -2190,11 +2190,16 @@ function traceNextProjectedSeq(result: AgentChatResultResponse, fallback: number if (Number.isFinite(direct) && direct >= 0) return Math.trunc(direct); const events = Array.isArray(result.events) ? result.events : Array.isArray(result.traceEvents) ? result.traceEvents : []; return events.reduce((max, event) => { - const seq = Number(event.seq); + const seq = traceEventProjectedSeq(event); return Number.isFinite(seq) && seq > max ? Math.trunc(seq) : max; }, fallback); } +function traceEventProjectedSeq(event: TraceEvent): number { + const seq = Number(event.projectedSeq ?? event.seq); + return Number.isFinite(seq) ? Math.trunc(seq) : Number.NaN; +} + function makeMessage(role: ChatMessage["role"], text: string, status: ChatMessage["status"], extra: Partial = {}): ChatMessage { return { id: nextProtocolId("msg"), role, title: extra.title ?? (role === "user" ? "用户" : "Code Agent"), text, status, createdAt: new Date().toISOString(), ...extra }; }