/ hooks / useTeammateViewAutoExit.ts
useTeammateViewAutoExit.ts
 1  import { useEffect } from 'react'
 2  import { useAppState, useSetAppState } from '../state/AppState.js'
 3  import { exitTeammateView } from '../state/teammateViewHelpers.js'
 4  import { isInProcessTeammateTask } from '../tasks/InProcessTeammateTask/types.js'
 5  
 6  /**
 7   * Auto-exits teammate viewing mode when the viewed teammate
 8   * is killed or encounters an error. Users stay viewing completed
 9   * teammates so they can review the full transcript.
10   */
11  export function useTeammateViewAutoExit(): void {
12    const setAppState = useSetAppState()
13    const viewingAgentTaskId = useAppState(s => s.viewingAgentTaskId)
14    // Select only the viewed task, not the full tasks map — otherwise every
15    // streaming update from any teammate re-renders this hook.
16    const task = useAppState(s =>
17      s.viewingAgentTaskId ? s.tasks[s.viewingAgentTaskId] : undefined,
18    )
19  
20    const viewedTask = task && isInProcessTeammateTask(task) ? task : undefined
21    const viewedStatus = viewedTask?.status
22    const viewedError = viewedTask?.error
23    const taskExists = task !== undefined
24  
25    useEffect(() => {
26      // Not viewing any teammate
27      if (!viewingAgentTaskId) {
28        return
29      }
30  
31      // Task no longer exists in the map — evicted out from under us.
32      // Check raw `task` not teammate-narrowed `viewedTask`; local_agent
33      // tasks exist but narrow to undefined, which would eject immediately.
34      if (!taskExists) {
35        exitTeammateView(setAppState)
36        return
37      }
38      // Status checks below are teammate-only (viewedTask is teammate-narrowed).
39      // For local_agent, viewedStatus is undefined → all checks falsy → no eject.
40      if (!viewedTask) return
41  
42      // Auto-exit if teammate is killed, stopped, has error, or is no longer running
43      // This handles shutdown scenarios where teammate becomes inactive
44      if (
45        viewedStatus === 'killed' ||
46        viewedStatus === 'failed' ||
47        viewedError ||
48        (viewedStatus !== 'running' &&
49          viewedStatus !== 'completed' &&
50          viewedStatus !== 'pending')
51      ) {
52        exitTeammateView(setAppState)
53        return
54      }
55    }, [
56      viewingAgentTaskId,
57      taskExists,
58      viewedTask,
59      viewedStatus,
60      viewedError,
61      setAppState,
62    ])
63  }