/ utils / swarm / teammateLayoutManager.ts
teammateLayoutManager.ts
  1  import type { AgentColorName } from '../../tools/AgentTool/agentColorManager.js'
  2  import { AGENT_COLORS } from '../../tools/AgentTool/agentColorManager.js'
  3  import { detectAndGetBackend } from './backends/registry.js'
  4  import type { PaneBackend } from './backends/types.js'
  5  
  6  // Track color assignments for teammates (persisted per session)
  7  const teammateColorAssignments = new Map<string, AgentColorName>()
  8  let colorIndex = 0
  9  
 10  /**
 11   * Gets the appropriate backend for the current environment.
 12   * detectAndGetBackend() caches internally — no need for a second cache here.
 13   */
 14  async function getBackend(): Promise<PaneBackend> {
 15    return (await detectAndGetBackend()).backend
 16  }
 17  
 18  /**
 19   * Assigns a unique color to a teammate from the available palette.
 20   * Colors are assigned in round-robin order.
 21   */
 22  export function assignTeammateColor(teammateId: string): AgentColorName {
 23    const existing = teammateColorAssignments.get(teammateId)
 24    if (existing) {
 25      return existing
 26    }
 27  
 28    const color = AGENT_COLORS[colorIndex % AGENT_COLORS.length]!
 29    teammateColorAssignments.set(teammateId, color)
 30    colorIndex++
 31  
 32    return color
 33  }
 34  
 35  /**
 36   * Gets the assigned color for a teammate, if any.
 37   */
 38  export function getTeammateColor(
 39    teammateId: string,
 40  ): AgentColorName | undefined {
 41    return teammateColorAssignments.get(teammateId)
 42  }
 43  
 44  /**
 45   * Clears all teammate color assignments.
 46   * Called during team cleanup to reset state for potential new teams.
 47   */
 48  export function clearTeammateColors(): void {
 49    teammateColorAssignments.clear()
 50    colorIndex = 0
 51  }
 52  
 53  /**
 54   * Checks if we're currently running inside a tmux session.
 55   * Uses the detection module directly for this check.
 56   */
 57  export async function isInsideTmux(): Promise<boolean> {
 58    const { isInsideTmux: checkTmux } = await import('./backends/detection.js')
 59    return checkTmux()
 60  }
 61  
 62  /**
 63   * Creates a new teammate pane in the swarm view.
 64   * Automatically selects the appropriate backend (tmux or iTerm2) based on environment.
 65   *
 66   * When running INSIDE tmux:
 67   * - Uses TmuxBackend to split the current window
 68   * - Leader stays on left (30%), teammates on right (70%)
 69   *
 70   * When running in iTerm2 (not in tmux) with it2 CLI:
 71   * - Uses ITermBackend for native iTerm2 split panes
 72   *
 73   * When running OUTSIDE tmux/iTerm2:
 74   * - Falls back to TmuxBackend with external claude-swarm session
 75   */
 76  export async function createTeammatePaneInSwarmView(
 77    teammateName: string,
 78    teammateColor: AgentColorName,
 79  ): Promise<{ paneId: string; isFirstTeammate: boolean }> {
 80    const backend = await getBackend()
 81    return backend.createTeammatePaneInSwarmView(teammateName, teammateColor)
 82  }
 83  
 84  /**
 85   * Enables pane border status for a window (shows pane titles).
 86   * Delegates to the detected backend.
 87   */
 88  export async function enablePaneBorderStatus(
 89    windowTarget?: string,
 90    useSwarmSocket = false,
 91  ): Promise<void> {
 92    const backend = await getBackend()
 93    return backend.enablePaneBorderStatus(windowTarget, useSwarmSocket)
 94  }
 95  
 96  /**
 97   * Sends a command to a specific pane.
 98   * Delegates to the detected backend.
 99   */
100  export async function sendCommandToPane(
101    paneId: string,
102    command: string,
103    useSwarmSocket = false,
104  ): Promise<void> {
105    const backend = await getBackend()
106    return backend.sendCommandToPane(paneId, command, useSwarmSocket)
107  }