diff --git a/frontend/app/components/assistant-ui/activity-panel.tsx b/frontend/app/components/assistant-ui/activity-panel.tsx index e3a73ea..9ec9ae0 100644 --- a/frontend/app/components/assistant-ui/activity-panel.tsx +++ b/frontend/app/components/assistant-ui/activity-panel.tsx @@ -165,20 +165,29 @@ export function ActivityPanel({ asDrawer = false }: { asDrawer?: boolean } = {}) () => collectLiveRunItems(liveRunStatus), [liveRunStatus], ); - const selectedMessageId = selectedId ? activityMessageId(selectedId) : null; const liveRunActive = hasActiveRun(liveRunStatus) && liveItems.length > 0; + const mergedItems = useMemo( + () => + liveRunActive + ? mergeActivityItems(messageItems, liveItems) + : messageItems, + [liveRunActive, messageItems, liveItems], + ); + const selectedMessageId = selectedId ? activityMessageId(selectedId) : null; const selectedMessageItems = selectedMessageId - ? messageItems.filter( + ? mergedItems.filter( (item) => activityMessageId(item.id) === selectedMessageId, ) : []; const shouldUseLiveItems = Boolean(activeSessionId && liveRunActive); - const items = shouldUseLiveItems ? liveItems : messageItems; + const items = shouldUseLiveItems ? mergedItems : messageItems; const selectedItems = selectedMessageId ? items.filter((item) => activityMessageId(item.id) === selectedMessageId) : []; const visibleItems = shouldUseLiveItems - ? liveItems + ? selectedMessageId && selectedMessageItems.length > 0 + ? selectedMessageItems + : items : selectedMessageId && selectedMessageItems.length > 0 ? selectedMessageItems : selectedMessageId && selectedItems.length > 0 @@ -1013,6 +1022,37 @@ function collectLiveRunItems(runStatus: ClawRunStatus | null) { return items; } +function mergeActivityItems( + messageItems: readonly ActivityItem[], + liveItems: readonly ActivityItem[], +) { + if (!liveItems.length) return [...messageItems]; + if (!messageItems.length) return [...liveItems]; + const merged = [...messageItems]; + const seen = new Set( + messageItems.map((item) => + [ + item.kind, + item.title, + item.argsText ?? "", + item.summary, + ].join("\u0000"), + ), + ); + for (const item of liveItems) { + const key = [ + item.kind, + item.title, + item.argsText ?? "", + item.summary, + ].join("\u0000"); + if (seen.has(key)) continue; + seen.add(key); + merged.push(item); + } + return merged; +} + function summarizeLiveRunEvents( runStatus: ClawRunStatus, events: readonly LiveRunEvent[], diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx index 7972a4f..30601b3 100644 --- a/frontend/app/components/assistant-ui/thread.tsx +++ b/frontend/app/components/assistant-ui/thread.tsx @@ -2079,12 +2079,14 @@ const ActivityChainSummary: FC<{ setElapsedMs(storedElapsedMs); return; } - const explicitStartedAt = - metadataRunStartedAtMs ?? contentRunStartedAtMs ?? messageCreatedAtMs; - if (explicitStartedAt !== null) { - liveStartedAtRef.current = explicitStartedAt; + const authoritativeStartedAt = + metadataRunStartedAtMs ?? contentRunStartedAtMs; + if (authoritativeStartedAt !== null) { + liveStartedAtRef.current = authoritativeStartedAt; } else if (liveStartedAtRef.current === null && storedElapsedMs !== null) { liveStartedAtRef.current = Date.now() - storedElapsedMs; + } else if (liveStartedAtRef.current === null && messageCreatedAtMs !== null) { + liveStartedAtRef.current = messageCreatedAtMs; } const startedAt = liveStartedAtRef.current; if (startedAt === null) {