diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index b2bec40f..f15321a9 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -1951,6 +1951,7 @@ function messageTimingPatchForMerge(message: ChatMessage, value: unknown): Parti function messageTimingSealPatchForProjectionMerge(message: ChatMessage, previous: ChatMessage | null): Partial { if (previous && isProjectionRunningAgentMessageForTiming(previous) && isProjectionRunningAgentMessageForTiming(message)) return messageRunningSealPatchForProjectionMerge(message, previous); + if (previous && isProjectionRunningAgentMessageForTiming(previous) && isTerminalMessageStatus(message.status)) return messageTerminalFromRunningSealPatchForProjectionMerge(message, previous); return messageTerminalSealPatchForProjectionMerge(message, previous); } @@ -1987,6 +1988,26 @@ function isProjectionRunningAgentMessageForTiming(message: ChatMessage): boolean return status === null || isTraceActiveStatus(status) || message.traceAutoLifecycle === "running"; } +function messageTerminalFromRunningSealPatchForProjectionMerge(message: ChatMessage, previous: ChatMessage): Partial { + const incomingPatch = terminalMessageTimingPatchForNormalize(message); + const incomingTiming = normalizeTimingProjection(incomingPatch) ?? normalizeTimingProjection(message); + const previousTiming = normalizeTimingProjection(previous); + const runningDuration = runningDurationFromTiming(previousTiming); + if (runningDuration === null) return incomingPatch; + const incomingDuration = firstPositiveFiniteNumber(incomingTiming?.durationMs) ?? positiveDurationBetween(incomingTiming?.startedAt, incomingTiming?.finishedAt) ?? positiveDurationBetween(incomingTiming?.startedAt, incomingTiming?.lastEventAt); + const durationMs = Math.max(incomingDuration ?? 0, runningDuration); + const finishedAt = incomingTiming?.finishedAt ?? incomingTiming?.lastEventAt ?? new Date().toISOString(); + const timing = { + ...(incomingTiming ?? {}), + startedAt: previousTiming?.startedAt ?? incomingTiming?.startedAt ?? null, + lastEventAt: incomingTiming?.lastEventAt ?? finishedAt, + finishedAt, + durationMs, + valuesRedacted: incomingTiming?.valuesRedacted !== false && previousTiming?.valuesRedacted !== false, + } as WorkbenchTurnTimingProjection; + return messageTimingPatchFromProjection(timing); +} + function messageTerminalSealPatchForProjectionMerge(message: ChatMessage, previous: ChatMessage | null): Partial { const incomingTimingPatch = messageTimingPatch(message); if (!previous || !isTerminalMessageStatus(previous.status)) return incomingTimingPatch;