From f7e2fc66c87ba029608ed8e0c591fbe31c1b38c1 Mon Sep 17 00:00:00 2001 From: wuyang6 Date: Mon, 11 May 2026 21:46:09 +0800 Subject: [PATCH] Fix active run elapsed restore --- backend/api/server.py | 1 + .../components/assistant-ui/thread-list.tsx | 38 +++++++++++++++++-- .../app/components/assistant-ui/thread.tsx | 6 ++- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/backend/api/server.py b/backend/api/server.py index 8d51f52..76af425 100644 --- a/backend/api/server.py +++ b/backend/api/server.py @@ -1357,6 +1357,7 @@ def create_app(state: AgentState) -> FastAPI: started_event = { 'type': 'run_started', 'run_id': run_record.run_id, + 'started_at': run_record.started_at, 'session_id': requested_session_id or '', 'account_id': request.account_id or '', } diff --git a/frontend/app/components/assistant-ui/thread-list.tsx b/frontend/app/components/assistant-ui/thread-list.tsx index 80ce7b2..c04871f 100644 --- a/frontend/app/components/assistant-ui/thread-list.tsx +++ b/frontend/app/components/assistant-ui/thread-list.tsx @@ -78,6 +78,9 @@ type ClawActiveRunStatus = ClawRunStatus & { type ClawRunEvent = { type?: string; + started_at?: unknown; + recorded_at?: unknown; + elapsed_ms?: unknown; tool_name?: string; tool_call_id?: string; arguments?: unknown; @@ -500,6 +503,7 @@ export function toReplayRepository( } if (isActiveRunStatus(runStatus)) { const id = `${fallbackSessionId}-run-${runStatus.run_id ?? "active"}`; + const runStartedAtMs = resolveRunStartedAtMs(runStatus); const parts = buildActiveRunParts(runStatus) as UIMessage["parts"]; const uiMessage: UIMessage = { id, @@ -508,7 +512,7 @@ export function toReplayRepository( metadata: { sessionId: session.session_id ?? fallbackSessionId, runId: runStatus.run_id, - runStartedAtMs: normalizeRunTimestampMs(runStatus.started_at), + runStartedAtMs, }, } as UIMessage; const threadMessage = { @@ -525,7 +529,7 @@ export function toReplayRepository( custom: { sessionId: session.session_id ?? fallbackSessionId, runId: runStatus.run_id, - runStartedAtMs: normalizeRunTimestampMs(runStatus.started_at), + runStartedAtMs, }, }, } as ThreadMessage; @@ -552,7 +556,7 @@ function buildActiveRunParts(runStatus: ClawActiveRunStatus) { ? "当前会话已有任务在执行,本轮正在排队。" : runStatus.current_stage || "后端正在执行这个会话。"; const reasoningText = eventLines.length ? eventLines.join("\n") : fallback; - const runStartedAtMs = normalizeRunTimestampMs(runStatus.started_at); + const runStartedAtMs = resolveRunStartedAtMs(runStatus); return [ { type: "reasoning", text: reasoningText, runStartedAtMs }, ...buildRunToolParts(runStatus.events ?? []), @@ -723,6 +727,34 @@ function normalizeElapsedMs(value: unknown) { return Math.max(0, Math.round(value)); } +function resolveRunStartedAtMs(runStatus: ClawRunStatus) { + const direct = normalizeRunTimestampMs(runStatus.started_at); + if (direct !== undefined) return direct; + + for (const event of runStatus.events ?? []) { + if (event.type !== "run_started") continue; + const eventStarted = normalizeRunTimestampMs(event.started_at); + if (eventStarted !== undefined) return eventStarted; + const recordedAt = normalizeRunTimestampMs(event.recorded_at); + if (recordedAt !== undefined) return recordedAt; + } + + for (const event of runStatus.events ?? []) { + const recordedAt = normalizeRunTimestampMs(event.recorded_at); + const elapsedMs = normalizeElapsedMs(event.elapsed_ms); + if (recordedAt !== undefined && elapsedMs !== undefined) { + return Math.max(0, recordedAt - elapsedMs); + } + } + + for (const event of runStatus.events ?? []) { + const recordedAt = normalizeRunTimestampMs(event.recorded_at); + if (recordedAt !== undefined) return recordedAt; + } + + return undefined; +} + function resolveReplayElapsedMs( messages: readonly ClawStoredMessage[], index: number, diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx index 7f2e4c6..d7bb69a 100644 --- a/frontend/app/components/assistant-ui/thread.tsx +++ b/frontend/app/components/assistant-ui/thread.tsx @@ -1475,7 +1475,11 @@ const ActivityChainSummary: FC<{ return; } const startedAt = - metadataRunStartedAtMs ?? contentRunStartedAtMs ?? startedAtRef.current; + metadataRunStartedAtMs ?? + contentRunStartedAtMs ?? + (storedElapsedMs === null + ? startedAtRef.current + : Date.now() - storedElapsedMs); setElapsedMs(Math.max(0, Date.now() - startedAt)); const timer = window.setInterval(() => { setElapsedMs(Math.max(0, Date.now() - startedAt));