/ hooks / notifs / useTeammateShutdownNotification.ts
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  }