From 94587679429400eca6dc79aa422e71f9f3f8ac2f Mon Sep 17 00:00:00 2001 From: Forgotten Date: Mon, 23 Feb 2026 20:20:38 -0600 Subject: [PATCH] feat(ui): add live duration clock for running agent runs When an agent run is in "running" status, the Duration field now counts up every second from startedAt, matching the same format shown when a run completes (e.g. "Duration: 5m 32s"). Co-Authored-By: Claude Opus 4.6 --- ui/src/pages/AgentDetail.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index ab0e6338..ae001c7e 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -1553,12 +1553,29 @@ function RunDetail({ run, adapterType }: { run: HeartbeatRun; adapterType: strin }, }); + const isRunning = run.status === "running" && !!run.startedAt && !run.finishedAt; + const [elapsedSec, setElapsedSec] = useState(() => { + if (!run.startedAt) return 0; + return Math.max(0, Math.round((Date.now() - new Date(run.startedAt).getTime()) / 1000)); + }); + + useEffect(() => { + if (!isRunning || !run.startedAt) return; + const startMs = new Date(run.startedAt).getTime(); + setElapsedSec(Math.max(0, Math.round((Date.now() - startMs) / 1000))); + const id = setInterval(() => { + setElapsedSec(Math.max(0, Math.round((Date.now() - startMs) / 1000))); + }, 1000); + return () => clearInterval(id); + }, [isRunning, run.startedAt]); + const timeFormat: Intl.DateTimeFormatOptions = { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }; const startTime = run.startedAt ? new Date(run.startedAt).toLocaleTimeString("en-US", timeFormat) : null; const endTime = run.finishedAt ? new Date(run.finishedAt).toLocaleTimeString("en-US", timeFormat) : null; const durationSec = run.startedAt && run.finishedAt ? Math.round((new Date(run.finishedAt).getTime() - new Date(run.startedAt).getTime()) / 1000) : null; + const displayDurationSec = durationSec ?? (isRunning ? elapsedSec : null); const hasMetrics = metrics.input > 0 || metrics.output > 0 || metrics.cached > 0 || metrics.cost > 0; const hasSession = !!(run.sessionIdBefore || run.sessionIdAfter); const sessionChanged = run.sessionIdBefore && run.sessionIdAfter && run.sessionIdBefore !== run.sessionIdAfter; @@ -1597,9 +1614,9 @@ function RunDetail({ run, adapterType }: { run: HeartbeatRun; adapterType: strin {relativeTime(run.startedAt!)} {run.finishedAt && <> → {relativeTime(run.finishedAt)}} - {durationSec !== null && ( + {displayDurationSec !== null && (
- Duration: {durationSec >= 60 ? `${Math.floor(durationSec / 60)}m ${durationSec % 60}s` : `${durationSec}s`} + Duration: {displayDurationSec >= 60 ? `${Math.floor(displayDurationSec / 60)}m ${displayDurationSec % 60}s` : `${displayDurationSec}s`}
)}