({
teammate,
isLast,
isSelected,
isForegrounded,
allIdle,
showPreview
}: Props)
| 70 | return allLines.reverse(); |
| 71 | } |
| 72 | export function TeammateSpinnerLine({ |
| 73 | teammate, |
| 74 | isLast, |
| 75 | isSelected, |
| 76 | isForegrounded, |
| 77 | allIdle, |
| 78 | showPreview |
| 79 | }: Props): React.ReactNode { |
| 80 | const [randomVerb] = useState(() => teammate.spinnerVerb ?? sample(getSpinnerVerbs())); |
| 81 | const [pastTenseVerb] = useState(() => teammate.pastTenseVerb ?? sample(TURN_COMPLETION_VERBS)); |
| 82 | const isHighlighted = isSelected || isForegrounded; |
| 83 | const treeChar = isHighlighted ? isLast ? '╘═' : '╞═' : isLast ? '└─' : '├─'; |
| 84 | const nameColor = toInkColor(teammate.identity.color); |
| 85 | const { |
| 86 | columns |
| 87 | } = useTerminalSize(); |
| 88 | |
| 89 | // Track when teammate became idle (for "Idle for X..." display) |
| 90 | const idleStartRef = useRef<number | null>(null); |
| 91 | // Freeze elapsed time when entering all-idle state |
| 92 | const frozenDurationRef = useRef<string | null>(null); |
| 93 | |
| 94 | // Track idle start time |
| 95 | if (teammate.isIdle && idleStartRef.current === null) { |
| 96 | idleStartRef.current = Date.now(); |
| 97 | } else if (!teammate.isIdle) { |
| 98 | idleStartRef.current = null; |
| 99 | } |
| 100 | |
| 101 | // Reset frozen duration when leaving all-idle state |
| 102 | if (!allIdle && frozenDurationRef.current !== null) { |
| 103 | frozenDurationRef.current = null; |
| 104 | } |
| 105 | |
| 106 | // Get elapsed idle time (how long they've been idle) - for "Idle for X..." display |
| 107 | const idleElapsedTime = useElapsedTime(idleStartRef.current ?? Date.now(), teammate.isIdle && !allIdle); |
| 108 | |
| 109 | // Freeze the duration when we first detect all idle |
| 110 | // Use the teammate's actual work time (since task started) for the past-tense display |
| 111 | if (allIdle && frozenDurationRef.current === null) { |
| 112 | frozenDurationRef.current = formatDuration(Math.max(0, Date.now() - teammate.startTime - (teammate.totalPausedMs ?? 0))); |
| 113 | } |
| 114 | |
| 115 | // Use frozen work duration when all idle, otherwise use idle elapsed time |
| 116 | const displayTime = allIdle ? frozenDurationRef.current ?? (() => { |
| 117 | throw new Error(`frozenDurationRef is null for idle teammate ${teammate.identity.agentName}`); |
| 118 | })() : idleElapsedTime; |
| 119 | |
| 120 | // Layout: paddingLeft(3) + pointer(1) + space(1) + treeChar(2) + space(1) = 8 fixed chars |
| 121 | // Then optionally: @name + ": " OR just ": " |
| 122 | // Then: activity text + optional extras (stats, hints) |
| 123 | const basePrefix = 8; |
| 124 | const fullAgentName = `@${teammate.identity.agentName}`; |
| 125 | const fullNameWidth = stringWidth(fullAgentName); |
| 126 | |
| 127 | // Get stats from progress |
| 128 | const toolUseCount = teammate.progress?.toolUseCount ?? 0; |
| 129 | const tokenCount = teammate.progress?.tokenCount ?? 0; |
nothing calls this directly
no test coverage detected