useTeammateShutdownNotification.ts
1 import { useEffect, useRef } from 'react' 2 import { getIsRemoteMode } from '../../bootstrap/state.js' 3 import { 4 type Notification, 5 useNotifications, 6 } from '../../context/notifications.js' 7 import { useAppState } from '../../state/AppState.js' 8 import { isInProcessTeammateTask } from '../../tasks/InProcessTeammateTask/types.js' 9 10 function parseCount(notif: Notification): number { 11 if (!('text' in notif)) { 12 return 1 13 } 14 const match = notif.text.match(/^(\d+)/) 15 return match?.[1] ? parseInt(match[1], 10) : 1 16 } 17 18 function foldSpawn(acc: Notification, _incoming: Notification): Notification { 19 return makeSpawnNotif(parseCount(acc) + 1) 20 } 21 22 function makeSpawnNotif(count: number): Notification { 23 return { 24 key: 'teammate-spawn', 25 text: count === 1 ? '1 agent spawned' : `${count} agents spawned`, 26 priority: 'low', 27 timeoutMs: 5000, 28 fold: foldSpawn, 29 } 30 } 31 32 function foldShutdown( 33 acc: Notification, 34 _incoming: Notification, 35 ): Notification { 36 return makeShutdownNotif(parseCount(acc) + 1) 37 } 38 39 function makeShutdownNotif(count: number): Notification { 40 return { 41 key: 'teammate-shutdown', 42 text: count === 1 ? '1 agent shut down' : `${count} agents shut down`, 43 priority: 'low', 44 timeoutMs: 5000, 45 fold: foldShutdown, 46 } 47 } 48 49 /** 50 * Fires batched notifications when in-process teammates spawn or shut down. 51 * Uses fold() to combine repeated events into a single notification 52 * like "3 agents spawned" or "2 agents shut down". 53 */ 54 export function useTeammateLifecycleNotification(): void { 55 const tasks = useAppState(s => s.tasks) 56 const { addNotification } = useNotifications() 57 const seenRunningRef = useRef<Set<string>>(new Set()) 58 const seenCompletedRef = useRef<Set<string>>(new Set()) 59 60 useEffect(() => { 61 if (getIsRemoteMode()) return 62 for (const [id, task] of Object.entries(tasks)) { 63 if (!isInProcessTeammateTask(task)) { 64 continue 65 } 66 67 if (task.status === 'running' && !seenRunningRef.current.has(id)) { 68 seenRunningRef.current.add(id) 69 addNotification(makeSpawnNotif(1)) 70 } 71 72 if (task.status === 'completed' && !seenCompletedRef.current.has(id)) { 73 seenCompletedRef.current.add(id) 74 addNotification(makeShutdownNotif(1)) 75 } 76 } 77 }, [tasks, addNotification]) 78 }