feat(statusline): add per-agent-type health tracking (#344)

Adds per-agent-type health tracking to the Mayor's tmux statusline, showing
working/idle counts for Polecats, Witnesses, Refineries, and Deacon.

All agent types are always displayed, even when no agents of that type are
running (shows as '0/0 😺').

Format: active: 4/4 😺 6/10 👁️ 7/10 🏭 1/1 

Co-authored-by: gastown/crew/dennis <steve.yegge@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
sauerdaniel
2026-01-12 11:09:57 +01:00
committed by GitHub
parent f6fd76172e
commit 3247b57926

View File

@@ -229,9 +229,63 @@ func runMayorStatusLine(t *tmux.Tmux) error {
}
}
// Track per-agent-type health (working/zombie counts)
type agentHealth struct {
total int
working int
}
// Initialize health tracker for tracked agent types
healthByType := map[AgentType]*agentHealth{
AgentPolecat: {},
AgentWitness: {},
AgentRefinery: {},
AgentDeacon: {},
}
for _, s := range sessions {
agent := categorizeSession(s)
if agent == nil {
continue
}
// Skip Mayor (always 1) and Crew (not tracked)
if agent.Type == AgentMayor || agent.Type == AgentCrew {
continue
}
health := healthByType[agent.Type]
if health == nil {
continue
}
health.total++
// Detect working state via ✻ symbol
if isSessionWorking(t, s) {
health.working++
}
// Non-working sessions are zombies (polecats) or idle (persistent agents)
}
// Build status
var parts []string
parts = append(parts, fmt.Sprintf("%d 😺", polecatCount))
// Add per-agent-type health in consistent order
// Format: "1/10 😺" = 1 working out of 10 total
// Only show agent types that have sessions
agentOrder := []AgentType{AgentPolecat, AgentWitness, AgentRefinery, AgentDeacon}
var agentParts []string
for _, agentType := range agentOrder {
health := healthByType[agentType]
if health.total == 0 {
continue
}
icon := AgentTypeIcons[agentType]
agentParts = append(agentParts, fmt.Sprintf("%d/%d %s", health.working, health.total, icon))
}
if len(agentParts) > 0 {
parts = append(parts, strings.Join(agentParts, " "))
}
// Build rig status display with LED indicators
// 🟢 = both witness and refinery running (fully active)
@@ -592,6 +646,27 @@ func runRefineryStatusLine(t *tmux.Tmux, rigName string) error {
return nil
}
// isSessionWorking detects if a Claude Code session is actively working.
// Returns true if the ✻ symbol is visible in the pane (indicates Claude is processing).
// Returns false for idle sessions (showing prompt) or if state cannot be determined.
func isSessionWorking(t *tmux.Tmux, session string) bool {
// Capture last few lines of the pane
lines, err := t.CapturePaneLines(session, 5)
if err != nil || len(lines) == 0 {
return false
}
// Check all captured lines for the working indicator
// ✻ appears in Claude's status line when actively processing
for _, line := range lines {
if strings.Contains(line, "✻") {
return true
}
}
return false
}
// getUnreadMailCount returns unread mail count for an identity.
// Fast path - returns 0 on any error.
func getUnreadMailCount(identity string) int {