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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user