From 6e3d6f8aa66c2affa27da8591b1829d1fee27524 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Wed, 24 Jun 2026 09:04:12 +0800 Subject: [PATCH] fix(workbench): seal terminal timing without projection (#2032) --- web/hwlab-cloud-web/src/stores/workbench.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index c5e9e1f5..a1b75cbd 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -1838,18 +1838,19 @@ function messageTimingPatchForMerge(message: ChatMessage, value: unknown): Parti // projection when trace rows arrive out of order. const current = messageTimingPatch(message); const currentTiming = current.timing ?? null; - const incoming = normalizeTimingProjection(value); - if (!incoming || isTerminalMessageStatus(message.status) || currentTiming?.finishedAt || currentTiming?.durationMs != null) return current; const record = recordValue(value); const incomingStatus = normalizedStatusText(firstNonEmptyString(record?.status)); const incomingTerminal = isTerminalMessageStatus(incomingStatus) || traceResultHasTerminalEvidence(value); - if (!incomingTerminal) return current; - const startedAt = firstNonEmptyString(currentTiming?.startedAt, incoming.startedAt) ?? null; - const durationMs = firstFiniteNumber(incoming.durationMs, record?.durationMs) ?? inferredDurationMs(startedAt, incoming.finishedAt) ?? currentTiming?.durationMs ?? null; + if (!incomingTerminal || isTerminalMessageStatus(message.status) || currentTiming?.finishedAt || currentTiming?.durationMs != null) return current; + const incoming = normalizeTimingProjection(value); + const terminalTiming = incoming ?? ({} as Partial); + const startedAt = firstNonEmptyString(currentTiming?.startedAt, terminalTiming.startedAt) ?? null; + const terminalFinishedAt = firstNonEmptyString(terminalTiming.finishedAt, record?.finishedAt, record?.completedAt, record?.updatedAt, terminalTiming.lastEventAt, currentTiming?.finishedAt, currentTiming?.lastEventAt) ?? null; + const durationMs = firstFiniteNumber(terminalTiming.durationMs, record?.durationMs) ?? inferredDurationMs(startedAt, terminalFinishedAt) ?? currentTiming?.durationMs ?? null; const inferredFinishedAt = inferFinishedAtFromDuration(startedAt, durationMs); - const finishedAt = firstNonEmptyString(incoming.finishedAt, inferredFinishedAt, incoming.lastEventAt, currentTiming?.finishedAt) ?? null; - const lastEventAt = firstNonEmptyString(incoming.lastEventAt, finishedAt, currentTiming?.lastEventAt, startedAt) ?? null; - const timing = { ...currentTiming, ...incoming, startedAt, lastEventAt, finishedAt, durationMs, valuesRedacted: incoming.valuesRedacted !== false && currentTiming?.valuesRedacted !== false } as WorkbenchTurnTimingProjection; + const finishedAt = firstNonEmptyString(terminalTiming.finishedAt, terminalFinishedAt, inferredFinishedAt, terminalTiming.lastEventAt, currentTiming?.finishedAt) ?? null; + const lastEventAt = firstNonEmptyString(terminalTiming.lastEventAt, finishedAt, currentTiming?.lastEventAt, startedAt) ?? null; + const timing = { ...currentTiming, ...terminalTiming, startedAt, lastEventAt, finishedAt, durationMs, valuesRedacted: terminalTiming.valuesRedacted !== false && currentTiming?.valuesRedacted !== false } as WorkbenchTurnTimingProjection; return { ...current, timing, startedAt, lastEventAt, finishedAt, durationMs }; }