source dump of claude code
at main 155 lines 5.3 kB view raw
1import * as React from 'react' 2import { useAppState, useAppStateStore } from '../../state/AppState.js' 3import { 4 getActiveAgentForInput, 5 getViewedTeammateTask, 6} from '../../state/selectors.js' 7import { 8 AGENT_COLOR_TO_THEME_COLOR, 9 AGENT_COLORS, 10 type AgentColorName, 11 getAgentColor, 12} from '../../tools/AgentTool/agentColorManager.js' 13import { getStandaloneAgentName } from '../../utils/standaloneAgent.js' 14import { isInsideTmux } from '../../utils/swarm/backends/detection.js' 15import { 16 getCachedDetectionResult, 17 isInProcessEnabled, 18} from '../../utils/swarm/backends/registry.js' 19import { getSwarmSocketName } from '../../utils/swarm/constants.js' 20import { 21 getAgentName, 22 getTeammateColor, 23 getTeamName, 24 isTeammate, 25} from '../../utils/teammate.js' 26import { isInProcessTeammate } from '../../utils/teammateContext.js' 27import type { Theme } from '../../utils/theme.js' 28 29type SwarmBannerInfo = { 30 text: string 31 bgColor: keyof Theme 32} | null 33 34/** 35 * Hook that returns banner information for swarm, standalone agent, or --agent CLI context. 36 * - Leader (not in tmux): Returns "tmux -L ... attach" command with cyan background 37 * - Leader (in tmux / in-process): Falls through to standalone-agent check — shows 38 * /rename name + /color background if set, else null 39 * - Teammate: Returns "teammate@team" format with their assigned color background 40 * - Viewing a background agent (CoordinatorTaskPanel): Returns agent name with its color 41 * - Standalone agent: Returns agent name with their color background (no @team) 42 * - --agent CLI flag: Returns "@agentName" with cyan background 43 */ 44export function useSwarmBanner(): SwarmBannerInfo { 45 const teamContext = useAppState(s => s.teamContext) 46 const standaloneAgentContext = useAppState(s => s.standaloneAgentContext) 47 const agent = useAppState(s => s.agent) 48 // Subscribe so the banner updates on enter/exit teammate view even though 49 // getActiveAgentForInput reads it from store.getState(). 50 useAppState(s => s.viewingAgentTaskId) 51 const store = useAppStateStore() 52 const [insideTmux, setInsideTmux] = React.useState<boolean | null>(null) 53 54 React.useEffect(() => { 55 void isInsideTmux().then(setInsideTmux) 56 }, []) 57 58 const state = store.getState() 59 60 // Teammate process: show @agentName with assigned color. 61 // In-process teammates run headless — their banner shows in the leader UI instead. 62 if (isTeammate() && !isInProcessTeammate()) { 63 const agentName = getAgentName() 64 if (agentName && getTeamName()) { 65 return { 66 text: `@${agentName}`, 67 bgColor: toThemeColor( 68 teamContext?.selfAgentColor ?? getTeammateColor(), 69 ), 70 } 71 } 72 } 73 74 // Leader with spawned teammates: tmux-attach hint when external, else show 75 // the viewed teammate's name when inside tmux / native panes / in-process. 76 const hasTeammates = 77 teamContext?.teamName && 78 teamContext.teammates && 79 Object.keys(teamContext.teammates).length > 0 80 if (hasTeammates) { 81 const viewedTeammate = getViewedTeammateTask(state) 82 const viewedColor = toThemeColor(viewedTeammate?.identity.color) 83 const inProcessMode = isInProcessEnabled() 84 const nativePanes = getCachedDetectionResult()?.isNative ?? false 85 86 if (insideTmux === false && !inProcessMode && !nativePanes) { 87 return { 88 text: `View teammates: \`tmux -L ${getSwarmSocketName()} a\``, 89 bgColor: viewedColor, 90 } 91 } 92 if ( 93 (insideTmux === true || inProcessMode || nativePanes) && 94 viewedTeammate 95 ) { 96 return { 97 text: `@${viewedTeammate.identity.agentName}`, 98 bgColor: viewedColor, 99 } 100 } 101 // insideTmux === null: still loading — fall through. 102 // Not viewing a teammate: fall through so /rename and /color are honored. 103 } 104 105 // Viewing a background agent (CoordinatorTaskPanel): local_agent tasks aren't 106 // InProcessTeammates, so getViewedTeammateTask misses them. Reverse-lookup the 107 // name from agentNameRegistry the same way CoordinatorAgentStatus does. 108 const active = getActiveAgentForInput(state) 109 if (active.type === 'named_agent') { 110 const task = active.task 111 let name: string | undefined 112 for (const [n, id] of state.agentNameRegistry) { 113 if (id === task.id) { 114 name = n 115 break 116 } 117 } 118 return { 119 text: name ? `@${name}` : task.description, 120 bgColor: getAgentColor(task.agentType) ?? 'cyan_FOR_SUBAGENTS_ONLY', 121 } 122 } 123 124 // Standalone agent (/rename, /color): name and/or custom color, no @team. 125 const standaloneName = getStandaloneAgentName(state) 126 const standaloneColor = standaloneAgentContext?.color 127 if (standaloneName || standaloneColor) { 128 return { 129 text: standaloneName ?? '', 130 bgColor: toThemeColor(standaloneColor), 131 } 132 } 133 134 // --agent CLI flag (when not handled above). 135 if (agent) { 136 const agentDef = state.agentDefinitions.activeAgents.find( 137 a => a.agentType === agent, 138 ) 139 return { 140 text: agent, 141 bgColor: toThemeColor(agentDef?.color, 'promptBorder'), 142 } 143 } 144 145 return null 146} 147 148function toThemeColor( 149 colorName: string | undefined, 150 fallback: keyof Theme = 'cyan_FOR_SUBAGENTS_ONLY', 151): keyof Theme { 152 return colorName && AGENT_COLORS.includes(colorName as AgentColorName) 153 ? AGENT_COLOR_TO_THEME_COLOR[colorName as AgentColorName] 154 : fallback 155}