()
| 32 | return Object.values(tasks).filter((t): t is LocalAgentTaskState => isPanelAgentTask(t) && t.evictAfter !== 0).sort((a, b) => a.startTime - b.startTime); |
| 33 | } |
| 34 | export function CoordinatorTaskPanel(): React.ReactNode { |
| 35 | const tasks = useAppState(s => s.tasks); |
| 36 | const viewingAgentTaskId = useAppState(s_0 => s_0.viewingAgentTaskId); |
| 37 | const agentNameRegistry = useAppState(s_1 => s_1.agentNameRegistry); |
| 38 | const coordinatorTaskIndex = useAppState(s_2 => s_2.coordinatorTaskIndex); |
| 39 | const tasksSelected = useAppState(s_3 => s_3.footerSelection === 'tasks'); |
| 40 | const selectedIndex = tasksSelected ? coordinatorTaskIndex : undefined; |
| 41 | const setAppState = useSetAppState(); |
| 42 | const visibleTasks = getVisibleAgentTasks(tasks); |
| 43 | const hasTasks = Object.values(tasks).some(isPanelAgentTask); |
| 44 | |
| 45 | // 1s tick: re-render for elapsed time + evict tasks past their deadline. |
| 46 | // The eviction deletes from prev.tasks, which makes useCoordinatorTaskCount |
| 47 | // (and other consumers) see the updated count without their own tick. |
| 48 | const tasksRef = React.useRef(tasks); |
| 49 | tasksRef.current = tasks; |
| 50 | const [, setTick] = React.useState(0); |
| 51 | React.useEffect(() => { |
| 52 | if (!hasTasks) return; |
| 53 | const interval = setInterval((tasksRef_0, setAppState_0, setTick_0) => { |
| 54 | const now = Date.now(); |
| 55 | for (const t of Object.values(tasksRef_0.current)) { |
| 56 | if (isPanelAgentTask(t) && (t.evictAfter ?? Infinity) <= now) { |
| 57 | evictTerminalTask(t.id, setAppState_0); |
| 58 | } |
| 59 | } |
| 60 | setTick_0((prev: number) => prev + 1); |
| 61 | }, 1000, tasksRef, setAppState, setTick); |
| 62 | return () => clearInterval(interval); |
| 63 | }, [hasTasks, setAppState]); |
| 64 | const nameByAgentId = React.useMemo(() => { |
| 65 | const inv = new Map<string, string>(); |
| 66 | for (const [n, id] of agentNameRegistry) inv.set(id, n); |
| 67 | return inv; |
| 68 | }, [agentNameRegistry]); |
| 69 | if (visibleTasks.length === 0) { |
| 70 | return null; |
| 71 | } |
| 72 | return <Box flexDirection="column" marginTop={1}> |
| 73 | <MainLine isSelected={selectedIndex === 0} isViewed={viewingAgentTaskId === undefined} onClick={() => exitTeammateView(setAppState)} /> |
| 74 | {visibleTasks.map((task, i) => <AgentLine key={task.id} task={task} name={nameByAgentId.get(task.id)} isSelected={selectedIndex === i + 1} isViewed={viewingAgentTaskId === task.id} onClick={() => enterTeammateView(task.id, setAppState)} />)} |
| 75 | </Box>; |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Returns the number of visible coordinator tasks (for selection bounds). |
nothing calls this directly
no test coverage detected