import { html, useState, useEffect, useRef } from '../lib/preact.js'; /** * Shows live agent activity: elapsed time since user prompt, token usage. * Visible when session is active/starting, pauses during needs_attention, * shows final duration when done. * * @param {object} session - Session object with turn_started_at, turn_paused_at, turn_paused_ms, status */ export function AgentActivityIndicator({ session }) { const [elapsed, setElapsed] = useState(0); const intervalRef = useRef(null); // Safely extract session fields (handles null/undefined session) const status = session?.status; const turn_started_at = session?.turn_started_at; const turn_paused_at = session?.turn_paused_at; const turn_paused_ms = session?.turn_paused_ms ?? 0; const last_event_at = session?.last_event_at; const context_usage = session?.context_usage; const turn_start_tokens = session?.turn_start_tokens; // Only show for sessions with turn timing const hasTurnTiming = !!turn_started_at; const isActive = status === 'active' || status === 'starting'; const isPaused = status === 'needs_attention'; const isDone = status === 'done'; useEffect(() => { if (!hasTurnTiming) return; const calculate = () => { const startMs = new Date(turn_started_at).getTime(); const pausedMs = turn_paused_ms || 0; if (isActive) { // Running: current time - start - paused return Date.now() - startMs - pausedMs; } else if (isPaused && turn_paused_at) { // Paused: frozen at pause time const pausedAtMs = new Date(turn_paused_at).getTime(); return pausedAtMs - startMs - pausedMs; } else if (isDone && last_event_at) { // Done: final duration const endMs = new Date(last_event_at).getTime(); return endMs - startMs - pausedMs; } return 0; }; setElapsed(calculate()); // Only tick while active if (isActive) { intervalRef.current = setInterval(() => { setElapsed(calculate()); }, 1000); } return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; }, [hasTurnTiming, isActive, isPaused, isDone, turn_started_at, turn_paused_at, turn_paused_ms, last_event_at]); // Don't render if no turn timing or session is done with no activity if (!hasTurnTiming) return null; // Format elapsed time (clamp to 0 for safety) const formatElapsed = (ms) => { const totalSec = Math.max(0, Math.floor(ms / 1000)); if (totalSec < 60) return `${totalSec}s`; const min = Math.floor(totalSec / 60); const sec = totalSec % 60; return `${min}m ${sec}s`; }; // Format token count const formatTokens = (count) => { if (count == null) return null; if (count >= 1000) return `${(count / 1000).toFixed(1)}k`; return String(count); }; // Calculate turn tokens (current - baseline from turn start) const currentTokens = context_usage?.current_tokens; const turnTokens = (currentTokens != null && turn_start_tokens != null) ? Math.max(0, currentTokens - turn_start_tokens) : null; const tokenDisplay = formatTokens(turnTokens); return html`