Add mail display to gt status (gt-um4iu)
- Show unread mail count and first message subject for each agent - Display format: "mail: 📬 N unread → Subject..." - Only shows mail line when agent has unread messages - Fix agent bead ID generation for global agents (mayor/, deacon/) Example output: 🏭 Refinery gt-refinery-gastown running hook: refinery Handoff mail: 📬 8 unread → MERGE_READY morsov 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/steveyegge/gastown/internal/constants"
|
"github.com/steveyegge/gastown/internal/constants"
|
||||||
"github.com/steveyegge/gastown/internal/crew"
|
"github.com/steveyegge/gastown/internal/crew"
|
||||||
"github.com/steveyegge/gastown/internal/git"
|
"github.com/steveyegge/gastown/internal/git"
|
||||||
|
"github.com/steveyegge/gastown/internal/mail"
|
||||||
"github.com/steveyegge/gastown/internal/rig"
|
"github.com/steveyegge/gastown/internal/rig"
|
||||||
"github.com/steveyegge/gastown/internal/style"
|
"github.com/steveyegge/gastown/internal/style"
|
||||||
"github.com/steveyegge/gastown/internal/tmux"
|
"github.com/steveyegge/gastown/internal/tmux"
|
||||||
@@ -48,15 +49,17 @@ type TownStatus struct {
|
|||||||
|
|
||||||
// AgentRuntime represents the runtime state of an agent.
|
// AgentRuntime represents the runtime state of an agent.
|
||||||
type AgentRuntime struct {
|
type AgentRuntime struct {
|
||||||
Name string `json:"name"` // Display name (e.g., "mayor", "witness")
|
Name string `json:"name"` // Display name (e.g., "mayor", "witness")
|
||||||
Address string `json:"address"` // Full address (e.g., "gastown/witness")
|
Address string `json:"address"` // Full address (e.g., "gastown/witness")
|
||||||
Session string `json:"session"` // tmux session name
|
Session string `json:"session"` // tmux session name
|
||||||
Role string `json:"role"` // Role type
|
Role string `json:"role"` // Role type
|
||||||
Running bool `json:"running"` // Is tmux session running?
|
Running bool `json:"running"` // Is tmux session running?
|
||||||
HasWork bool `json:"has_work"` // Has pinned work?
|
HasWork bool `json:"has_work"` // Has pinned work?
|
||||||
WorkTitle string `json:"work_title,omitempty"` // Title of pinned work
|
WorkTitle string `json:"work_title,omitempty"` // Title of pinned work
|
||||||
HookBead string `json:"hook_bead,omitempty"` // Pinned bead ID from agent bead
|
HookBead string `json:"hook_bead,omitempty"` // Pinned bead ID from agent bead
|
||||||
State string `json:"state,omitempty"` // Agent state from agent bead
|
State string `json:"state,omitempty"` // Agent state from agent bead
|
||||||
|
UnreadMail int `json:"unread_mail"` // Number of unread messages
|
||||||
|
FirstSubject string `json:"first_subject,omitempty"` // Subject of first unread message
|
||||||
}
|
}
|
||||||
|
|
||||||
// RigStatus represents status of a single rig.
|
// RigStatus represents status of a single rig.
|
||||||
@@ -131,11 +134,14 @@ func runStatus(cmd *cobra.Command, args []string) error {
|
|||||||
gastownBeadsPath := filepath.Join(townRoot, "gastown", "mayor", "rig")
|
gastownBeadsPath := filepath.Join(townRoot, "gastown", "mayor", "rig")
|
||||||
agentBeads := beads.New(gastownBeadsPath)
|
agentBeads := beads.New(gastownBeadsPath)
|
||||||
|
|
||||||
|
// Create mail router for inbox lookups
|
||||||
|
mailRouter := mail.NewRouter(townRoot)
|
||||||
|
|
||||||
// Build status
|
// Build status
|
||||||
status := TownStatus{
|
status := TownStatus{
|
||||||
Name: townConfig.Name,
|
Name: townConfig.Name,
|
||||||
Location: townRoot,
|
Location: townRoot,
|
||||||
Agents: discoverGlobalAgents(t, agentBeads),
|
Agents: discoverGlobalAgents(t, agentBeads, mailRouter),
|
||||||
Rigs: make([]RigStatus, 0, len(rigs)),
|
Rigs: make([]RigStatus, 0, len(rigs)),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +173,7 @@ func runStatus(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Discover runtime state for all agents in this rig
|
// Discover runtime state for all agents in this rig
|
||||||
rs.Agents = discoverRigAgents(t, r, rs.Crews, agentBeads)
|
rs.Agents = discoverRigAgents(t, r, rs.Crews, agentBeads, mailRouter)
|
||||||
|
|
||||||
status.Rigs = append(status.Rigs, rs)
|
status.Rigs = append(status.Rigs, rs)
|
||||||
|
|
||||||
@@ -311,8 +317,12 @@ func renderAgentDetails(agent AgentRuntime, indent string, hooks []AgentHookInfo
|
|||||||
agentBeadID := "gt-" + agent.Name
|
agentBeadID := "gt-" + agent.Name
|
||||||
if agent.Address != "" && agent.Address != agent.Name {
|
if agent.Address != "" && agent.Address != agent.Name {
|
||||||
// Use address for full path agents like gastown/crew/joe → gt-crew-gastown-joe
|
// Use address for full path agents like gastown/crew/joe → gt-crew-gastown-joe
|
||||||
parts := strings.Split(agent.Address, "/")
|
addr := strings.TrimSuffix(agent.Address, "/") // Remove trailing slash for global agents
|
||||||
if len(parts) >= 2 {
|
parts := strings.Split(addr, "/")
|
||||||
|
if len(parts) == 1 {
|
||||||
|
// Global agent: mayor/, deacon/ → gt-mayor, gt-deacon
|
||||||
|
agentBeadID = "gt-" + parts[0]
|
||||||
|
} else if len(parts) >= 2 {
|
||||||
if parts[1] == "crew" && len(parts) >= 3 {
|
if parts[1] == "crew" && len(parts) >= 3 {
|
||||||
agentBeadID = fmt.Sprintf("gt-crew-%s-%s", parts[0], parts[2])
|
agentBeadID = fmt.Sprintf("gt-crew-%s-%s", parts[0], parts[2])
|
||||||
} else if parts[1] == "witness" || parts[1] == "refinery" {
|
} else if parts[1] == "witness" || parts[1] == "refinery" {
|
||||||
@@ -354,6 +364,15 @@ func renderAgentDetails(agent AgentRuntime, indent string, hooks []AgentHookInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s hook: %s\n", indent, hookStr)
|
fmt.Printf("%s hook: %s\n", indent, hookStr)
|
||||||
|
|
||||||
|
// Line 3: Mail (if any unread)
|
||||||
|
if agent.UnreadMail > 0 {
|
||||||
|
mailStr := fmt.Sprintf("📬 %d unread", agent.UnreadMail)
|
||||||
|
if agent.FirstSubject != "" {
|
||||||
|
mailStr = fmt.Sprintf("📬 %d unread → %s", agent.UnreadMail, truncateWithEllipsis(agent.FirstSubject, 35))
|
||||||
|
}
|
||||||
|
fmt.Printf("%s mail: %s\n", indent, mailStr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatHookInfo formats the hook bead and title for display
|
// formatHookInfo formats the hook bead and title for display
|
||||||
@@ -423,14 +442,14 @@ func discoverRigHooks(r *rig.Rig, crews []string) []AgentHookInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// discoverGlobalAgents checks runtime state for town-level agents (Mayor, Deacon).
|
// discoverGlobalAgents checks runtime state for town-level agents (Mayor, Deacon).
|
||||||
func discoverGlobalAgents(t *tmux.Tmux, agentBeads *beads.Beads) []AgentRuntime {
|
func discoverGlobalAgents(t *tmux.Tmux, agentBeads *beads.Beads, mailRouter *mail.Router) []AgentRuntime {
|
||||||
var agents []AgentRuntime
|
var agents []AgentRuntime
|
||||||
|
|
||||||
// Check Mayor
|
// Check Mayor
|
||||||
mayorRunning, _ := t.HasSession(MayorSessionName)
|
mayorRunning, _ := t.HasSession(MayorSessionName)
|
||||||
mayor := AgentRuntime{
|
mayor := AgentRuntime{
|
||||||
Name: "mayor",
|
Name: "mayor",
|
||||||
Address: "mayor",
|
Address: "mayor/",
|
||||||
Session: MayorSessionName,
|
Session: MayorSessionName,
|
||||||
Role: "coordinator",
|
Role: "coordinator",
|
||||||
Running: mayorRunning,
|
Running: mayorRunning,
|
||||||
@@ -447,13 +466,15 @@ func discoverGlobalAgents(t *tmux.Tmux, agentBeads *beads.Beads) []AgentRuntime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Get mail info
|
||||||
|
populateMailInfo(&mayor, mailRouter)
|
||||||
agents = append(agents, mayor)
|
agents = append(agents, mayor)
|
||||||
|
|
||||||
// Check Deacon
|
// Check Deacon
|
||||||
deaconRunning, _ := t.HasSession(DeaconSessionName)
|
deaconRunning, _ := t.HasSession(DeaconSessionName)
|
||||||
deacon := AgentRuntime{
|
deacon := AgentRuntime{
|
||||||
Name: "deacon",
|
Name: "deacon",
|
||||||
Address: "deacon",
|
Address: "deacon/",
|
||||||
Session: DeaconSessionName,
|
Session: DeaconSessionName,
|
||||||
Role: "health-check",
|
Role: "health-check",
|
||||||
Running: deaconRunning,
|
Running: deaconRunning,
|
||||||
@@ -469,13 +490,33 @@ func discoverGlobalAgents(t *tmux.Tmux, agentBeads *beads.Beads) []AgentRuntime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Get mail info
|
||||||
|
populateMailInfo(&deacon, mailRouter)
|
||||||
agents = append(agents, deacon)
|
agents = append(agents, deacon)
|
||||||
|
|
||||||
return agents
|
return agents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// populateMailInfo fetches unread mail count and first subject for an agent
|
||||||
|
func populateMailInfo(agent *AgentRuntime, router *mail.Router) {
|
||||||
|
if router == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mailbox, err := router.GetMailbox(agent.Address)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, unread, _ := mailbox.Count()
|
||||||
|
agent.UnreadMail = unread
|
||||||
|
if unread > 0 {
|
||||||
|
if messages, err := mailbox.ListUnread(); err == nil && len(messages) > 0 {
|
||||||
|
agent.FirstSubject = messages[0].Subject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// discoverRigAgents checks runtime state for all agents in a rig.
|
// discoverRigAgents checks runtime state for all agents in a rig.
|
||||||
func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *beads.Beads) []AgentRuntime {
|
func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *beads.Beads, mailRouter *mail.Router) []AgentRuntime {
|
||||||
var agents []AgentRuntime
|
var agents []AgentRuntime
|
||||||
|
|
||||||
// Check Witness
|
// Check Witness
|
||||||
@@ -501,6 +542,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
populateMailInfo(&witness, mailRouter)
|
||||||
agents = append(agents, witness)
|
agents = append(agents, witness)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +569,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
populateMailInfo(&refinery, mailRouter)
|
||||||
agents = append(agents, refinery)
|
agents = append(agents, refinery)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,6 +596,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
populateMailInfo(&polecat, mailRouter)
|
||||||
agents = append(agents, polecat)
|
agents = append(agents, polecat)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,6 +623,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
populateMailInfo(&crewAgent, mailRouter)
|
||||||
agents = append(agents, crewAgent)
|
agents = append(agents, crewAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user