/ utils / swarm / reconnection.ts
reconnection.ts
  1  /**
  2   * Swarm Reconnection Module
  3   *
  4   * Handles initialization of swarm context for teammates.
  5   * - Fresh spawns: Initialize from CLI args (set in main.tsx via dynamicTeamContext)
  6   * - Resumed sessions: Initialize from teamName/agentName stored in the transcript
  7   */
  8  
  9  import type { AppState } from '../../state/AppState.js'
 10  import { logForDebugging } from '../debug.js'
 11  import { logError } from '../log.js'
 12  import { getDynamicTeamContext } from '../teammate.js'
 13  import { getTeamFilePath, readTeamFile } from './teamHelpers.js'
 14  
 15  /**
 16   * Computes the initial teamContext for AppState.
 17   *
 18   * This is called synchronously in main.tsx to compute the teamContext
 19   * BEFORE the first render, eliminating the need for useEffect workarounds.
 20   *
 21   * @returns The teamContext object to include in initialState, or undefined if not a teammate
 22   */
 23  export function computeInitialTeamContext():
 24    | AppState['teamContext']
 25    | undefined {
 26    // dynamicTeamContext is set in main.tsx from CLI args
 27    const context = getDynamicTeamContext()
 28  
 29    if (!context?.teamName || !context?.agentName) {
 30      logForDebugging(
 31        '[Reconnection] computeInitialTeamContext: No teammate context set (not a teammate)',
 32      )
 33      return undefined
 34    }
 35  
 36    const { teamName, agentId, agentName } = context
 37  
 38    // Read team file to get lead agent ID
 39    const teamFile = readTeamFile(teamName)
 40    if (!teamFile) {
 41      logError(
 42        new Error(
 43          `[computeInitialTeamContext] Could not read team file for ${teamName}`,
 44        ),
 45      )
 46      return undefined
 47    }
 48  
 49    const teamFilePath = getTeamFilePath(teamName)
 50  
 51    const isLeader = !agentId
 52  
 53    logForDebugging(
 54      `[Reconnection] Computed initial team context for ${isLeader ? 'leader' : `teammate ${agentName}`} in team ${teamName}`,
 55    )
 56  
 57    return {
 58      teamName,
 59      teamFilePath,
 60      leadAgentId: teamFile.leadAgentId,
 61      selfAgentId: agentId,
 62      selfAgentName: agentName,
 63      isLeader,
 64      teammates: {},
 65    }
 66  }
 67  
 68  /**
 69   * Initialize teammate context from a resumed session.
 70   *
 71   * This is called when resuming a session that has teamName/agentName stored
 72   * in the transcript. It sets up teamContext in AppState so that heartbeat
 73   * and other swarm features work correctly.
 74   */
 75  export function initializeTeammateContextFromSession(
 76    setAppState: (updater: (prev: AppState) => AppState) => void,
 77    teamName: string,
 78    agentName: string,
 79  ): void {
 80    // Read team file to get lead agent ID
 81    const teamFile = readTeamFile(teamName)
 82    if (!teamFile) {
 83      logError(
 84        new Error(
 85          `[initializeTeammateContextFromSession] Could not read team file for ${teamName} (agent: ${agentName})`,
 86        ),
 87      )
 88      return
 89    }
 90  
 91    // Find the member in the team file to get their agentId
 92    const member = teamFile.members.find(m => m.name === agentName)
 93    if (!member) {
 94      logForDebugging(
 95        `[Reconnection] Member ${agentName} not found in team ${teamName} - may have been removed`,
 96      )
 97    }
 98    const agentId = member?.agentId
 99  
100    const teamFilePath = getTeamFilePath(teamName)
101  
102    // Set teamContext in AppState
103    setAppState(prev => ({
104      ...prev,
105      teamContext: {
106        teamName,
107        teamFilePath,
108        leadAgentId: teamFile.leadAgentId,
109        selfAgentId: agentId,
110        selfAgentName: agentName,
111        isLeader: false,
112        teammates: {},
113      },
114    }))
115  
116    logForDebugging(
117      `[Reconnection] Initialized agent context from session for ${agentName} in team ${teamName}`,
118    )
119  }