From 9c2ac656358df63d2164d8d120364500ff155430 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Sat, 30 May 2026 13:00:42 -0400 Subject: [PATCH] feat: track processing start-times + global needs-input set for activity monitor --- src/hooks/useSessionProtection.ts | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/hooks/useSessionProtection.ts b/src/hooks/useSessionProtection.ts index cbdcdda..cb5f5ca 100644 --- a/src/hooks/useSessionProtection.ts +++ b/src/hooks/useSessionProtection.ts @@ -3,6 +3,10 @@ import { useCallback, useState } from 'react'; export function useSessionProtection() { const [activeSessions, setActiveSessions] = useState>(new Set()); const [processingSessions, setProcessingSessions] = useState>(new Set()); + // sessionId -> epoch ms when processing began (for the live elapsed timer) + const [processingStartTimes, setProcessingStartTimes] = useState>(new Map()); + // sessions waiting on the user (permission / AskUserQuestion pending) + const [needsInputSessions, setNeedsInputSessions] = useState>(new Set()); const markSessionAsActive = useCallback((sessionId?: string | null) => { if (!sessionId) { @@ -30,6 +34,16 @@ export function useSessionProtection() { } setProcessingSessions((prev) => new Set([...prev, sessionId])); + setProcessingStartTimes((prev) => { + // Preserve an existing start time so the elapsed timer doesn't reset on + // repeated marks for the same run. + if (prev.has(sessionId)) { + return prev; + } + const next = new Map(prev); + next.set(sessionId, Date.now()); + return next; + }); }, []); const markSessionAsNotProcessing = useCallback((sessionId?: string | null) => { @@ -42,14 +56,49 @@ export function useSessionProtection() { next.delete(sessionId); return next; }); + setProcessingStartTimes((prev) => { + if (!prev.has(sessionId)) { + return prev; + } + const next = new Map(prev); + next.delete(sessionId); + return next; + }); + }, []); + + const markSessionAsNeedsInput = useCallback((sessionId?: string | null) => { + if (!sessionId) { + return; + } + + setNeedsInputSessions((prev) => new Set([...prev, sessionId])); + }, []); + + const clearSessionNeedsInput = useCallback((sessionId?: string | null) => { + if (!sessionId) { + return; + } + + setNeedsInputSessions((prev) => { + if (!prev.has(sessionId)) { + return prev; + } + const next = new Set(prev); + next.delete(sessionId); + return next; + }); }, []); return { activeSessions, processingSessions, + processingStartTimes, + needsInputSessions, markSessionAsActive, markSessionAsInactive, markSessionAsProcessing, markSessionAsNotProcessing, + markSessionAsNeedsInput, + clearSessionNeedsInput, }; }